mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 12:50:38 +00:00
feat: Pdf templates customer/company addresses
This commit is contained in:
@@ -167,17 +167,13 @@ block content
|
||||
|
||||
//- Address section
|
||||
div(class=`${prefix}-address-root`)
|
||||
if showBilledFromAddress
|
||||
if showCompanyAddress
|
||||
div(class=`${prefix}-address-from`)
|
||||
strong #{companyName}
|
||||
each item in billedFromAddres
|
||||
div(class=`${prefix}-address-from__item`) #{item}
|
||||
div !{companyAddress}
|
||||
|
||||
if showBillingToAddress
|
||||
if showCustomerAddress
|
||||
div(class=`${prefix}-address-to`)
|
||||
strong #{billedToLabel}
|
||||
each item in billedToAddress
|
||||
div(class=`${prefix}-address-to__item`) #{item}
|
||||
div !{customerAddress}
|
||||
|
||||
//- Invoice table
|
||||
table(class=`${prefix}-table`)
|
||||
|
||||
@@ -31,6 +31,7 @@ export class PdfTemplatesController extends BaseController {
|
||||
this.validationResult,
|
||||
this.editPdfTemplate.bind(this)
|
||||
);
|
||||
router.get('/state', this.getOrganizationBrandingState.bind(this));
|
||||
router.get(
|
||||
'/',
|
||||
[query('resource').optional()],
|
||||
@@ -175,4 +176,20 @@ export class PdfTemplatesController extends BaseController {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
async getOrganizationBrandingState(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { tenantId } = req;
|
||||
|
||||
try {
|
||||
const data =
|
||||
await this.pdfTemplateApplication.getPdfTemplateBrandingState(tenantId);
|
||||
|
||||
return res.status(200).send({ data });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
7
packages/server/src/services/Attachments/utils.ts
Normal file
7
packages/server/src/services/Attachments/utils.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import path from 'path';
|
||||
import config from '@/config';
|
||||
|
||||
|
||||
export const getUploadedObjectUri = (objectKey: string) => {
|
||||
return path.join(config.s3.endpoint, config.s3.bucket, objectKey);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Service } from 'typedi';
|
||||
import { TenantMetadata } from '@/system/models';
|
||||
import { CommonOrganizationBrandingAttributes } from './types';
|
||||
|
||||
@Service()
|
||||
export class GetOrganizationBrandingAttributes {
|
||||
/**
|
||||
* Retrieves the given organization branding attributes initial state.
|
||||
* @param {number} tenantId
|
||||
* @returns {Promise<CommonOrganizationBrandingAttributes>}
|
||||
*/
|
||||
async getOrganizationBrandingAttributes(
|
||||
tenantId: number
|
||||
): Promise<CommonOrganizationBrandingAttributes> {
|
||||
const tenantMetadata = await TenantMetadata.query().findOne({ tenantId });
|
||||
|
||||
const companyName = tenantMetadata?.name;
|
||||
const primaryColor = tenantMetadata?.primaryColor;
|
||||
const companyLogoKey = tenantMetadata?.logoKey;
|
||||
const companyLogoUri = tenantMetadata?.logoUri;
|
||||
const companyAddress = tenantMetadata?.addressTextFormatted;
|
||||
|
||||
return {
|
||||
companyName,
|
||||
companyAddress,
|
||||
companyLogoUri,
|
||||
companyLogoKey,
|
||||
primaryColor,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { GetOrganizationBrandingAttributes } from './GetOrganizationBrandingAttributes';
|
||||
|
||||
@Service()
|
||||
export class GetPdfTemplateBrandingState {
|
||||
@Inject()
|
||||
private getOrgBrandingAttributes: GetOrganizationBrandingAttributes;
|
||||
|
||||
getBrandingState(tenantId: number) {
|
||||
const brandingAttributes =
|
||||
this.getOrgBrandingAttributes.getOrganizationBrandingAttributes(tenantId);
|
||||
|
||||
return brandingAttributes;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||
import { getTransactionTypeLabel } from '@/utils/transactions-types';
|
||||
import { getUploadedObjectUri } from '../Attachments/utils';
|
||||
|
||||
export class GetPdfTemplateTransformer extends Transformer {
|
||||
/**
|
||||
@@ -56,7 +57,7 @@ class GetPdfTemplateAttributesTransformer extends Transformer {
|
||||
*/
|
||||
protected companyLogoUri(template) {
|
||||
return template.companyLogoKey
|
||||
? `https://bigcapital.sfo3.digitaloceanspaces.com/${template.companyLogoKey}`
|
||||
? getUploadedObjectUri(template.companyLogoKey)
|
||||
: '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { GetPdfTemplate } from './GetPdfTemplate';
|
||||
import { GetPdfTemplates } from './GetPdfTemplates';
|
||||
import { EditPdfTemplate } from './EditPdfTemplate';
|
||||
import { AssignPdfTemplateDefault } from './AssignPdfTemplateDefault';
|
||||
import { GetPdfTemplateBrandingState } from './GetPdfTemplateBrandingState';
|
||||
|
||||
@Service()
|
||||
export class PdfTemplateApplication {
|
||||
@@ -27,6 +28,9 @@ export class PdfTemplateApplication {
|
||||
@Inject()
|
||||
private assignPdfTemplateDefaultService: AssignPdfTemplateDefault;
|
||||
|
||||
@Inject()
|
||||
private getPdfTemplateBrandingStateService: GetPdfTemplateBrandingState;
|
||||
|
||||
/**
|
||||
* Creates a new PDF template.
|
||||
* @param {number} tenantId -
|
||||
@@ -120,4 +124,12 @@ export class PdfTemplateApplication {
|
||||
templateId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} tenantId
|
||||
*/
|
||||
public async getPdfTemplateBrandingState(tenantId: number) {
|
||||
return this.getPdfTemplateBrandingStateService.getBrandingState(tenantId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,3 +65,12 @@ export interface ICreateInvoicePdfTemplateDTO {
|
||||
statementLabel?: string;
|
||||
showStatement?: boolean;
|
||||
}
|
||||
|
||||
|
||||
export interface CommonOrganizationBrandingAttributes {
|
||||
companyName?: string;
|
||||
primaryColor?: string;
|
||||
companyLogoKey?: string;
|
||||
companyLogoUri?: string;
|
||||
companyAddress?: string;
|
||||
}
|
||||
|
||||
@@ -2,26 +2,39 @@ import { Inject, Service } from 'typedi';
|
||||
import { mergePdfTemplateWithDefaultAttributes } from './utils';
|
||||
import { GetPdfTemplate } from '@/services/PdfTemplate/GetPdfTemplate';
|
||||
import { defaultInvoicePdfTemplateAttributes } from './constants';
|
||||
import { GetOrganizationBrandingAttributes } from '@/services/PdfTemplate/GetOrganizationBrandingAttributes';
|
||||
|
||||
@Service()
|
||||
export class SaleInvoicePdfTemplate {
|
||||
@Inject()
|
||||
private getPdfTemplateService: GetPdfTemplate;
|
||||
|
||||
@Inject()
|
||||
private getOrgBrandingAttributes: GetOrganizationBrandingAttributes;
|
||||
|
||||
/**
|
||||
* Retrieves the invoice pdf template.
|
||||
* @param {number} tenantId
|
||||
* @param {number} invoiceTemplateId
|
||||
* @returns
|
||||
* @param {number} tenantId
|
||||
* @param {number} invoiceTemplateId
|
||||
* @returns
|
||||
*/
|
||||
async getInvoicePdfTemplate(tenantId: number, invoiceTemplateId: number){
|
||||
async getInvoicePdfTemplate(tenantId: number, invoiceTemplateId: number) {
|
||||
const template = await this.getPdfTemplateService.getPdfTemplate(
|
||||
tenantId,
|
||||
invoiceTemplateId
|
||||
);
|
||||
// Retrieves the organization branding attributes.
|
||||
const commonOrgBrandingAttrs =
|
||||
await this.getOrgBrandingAttributes.getOrganizationBrandingAttributes(
|
||||
tenantId
|
||||
);
|
||||
const organizationBrandingAttrs = {
|
||||
...defaultInvoicePdfTemplateAttributes,
|
||||
...commonOrgBrandingAttrs,
|
||||
};
|
||||
const attributes = mergePdfTemplateWithDefaultAttributes(
|
||||
template.attributes,
|
||||
defaultInvoicePdfTemplateAttributes
|
||||
organizationBrandingAttrs
|
||||
);
|
||||
return {
|
||||
...template,
|
||||
|
||||
@@ -163,7 +163,7 @@ export const SaleInvoicesSampleData = [
|
||||
},
|
||||
];
|
||||
|
||||
export const defaultInvoicePdfTemplateAttributes = {
|
||||
export const defaultInvoicePdfTemplateAttributes = {
|
||||
primaryColor: 'red',
|
||||
secondaryColor: 'red',
|
||||
|
||||
@@ -184,8 +184,11 @@ export const defaultInvoicePdfTemplateAttributes = {
|
||||
showInvoiceNumber: true,
|
||||
|
||||
// Address
|
||||
showBillingToAddress: true,
|
||||
showBilledFromAddress: true,
|
||||
showCustomerAddress: true,
|
||||
customerAddress: '',
|
||||
|
||||
showCompanyAddress: true,
|
||||
companyAddress: '',
|
||||
billedToLabel: 'Billed To',
|
||||
|
||||
// Entries
|
||||
@@ -229,22 +232,7 @@ export const defaultInvoicePdfTemplateAttributes = {
|
||||
{ label: 'Sample Tax2 (7.00%)', amount: '21.74' },
|
||||
],
|
||||
|
||||
// # Statement
|
||||
statementLabel: 'Statement',
|
||||
showStatement: true,
|
||||
billedToAddress: [
|
||||
'Bigcapital Technology, Inc.',
|
||||
'131 Continental Dr Suite 305 Newark,',
|
||||
'Delaware 19713',
|
||||
'United States',
|
||||
'+1 762-339-5634',
|
||||
'ahmed@bigcapital.app',
|
||||
],
|
||||
billedFromAddres: [
|
||||
'131 Continental Dr Suite 305 Newark,',
|
||||
'Delaware 19713',
|
||||
'United States',
|
||||
'+1 762-339-5634',
|
||||
'ahmed@bigcapital.app',
|
||||
],
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { addressTextFormat } from '@/utils/address-text-format';
|
||||
import BaseModel from 'models/Model';
|
||||
import { getUploadedObjectUri } from '../../services/Attachments/utils';
|
||||
|
||||
export default class TenantMetadata extends BaseModel {
|
||||
baseCurrency!: string;
|
||||
@@ -58,9 +59,7 @@ export default class TenantMetadata extends BaseModel {
|
||||
* @returns {string | null}
|
||||
*/
|
||||
public get logoUri() {
|
||||
return this.logoKey
|
||||
? `https://bigcapital.sfo3.digitaloceanspaces.com/${this.logoKey}`
|
||||
: null;
|
||||
return this.logoKey ? getUploadedObjectUri(this.logoKey) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import React, { createContext, useContext } from 'react';
|
||||
import {
|
||||
GetPdfTemplateBrandingStateResponse,
|
||||
GetPdfTemplateResponse,
|
||||
useGetPdfTemplate,
|
||||
useGetPdfTemplateBrandingState,
|
||||
} from '@/hooks/query/pdf-templates';
|
||||
import { Spinner } from '@blueprintjs/core';
|
||||
|
||||
@@ -9,6 +11,10 @@ interface PdfTemplateContextValue {
|
||||
templateId: number | string;
|
||||
pdfTemplate: GetPdfTemplateResponse | undefined;
|
||||
isPdfTemplateLoading: boolean;
|
||||
|
||||
// Branding state.
|
||||
brandingTemplateState: GetPdfTemplateBrandingStateResponse | undefined;
|
||||
isBrandingTemplateLoading: boolean;
|
||||
}
|
||||
|
||||
interface BrandingTemplateProps {
|
||||
@@ -28,15 +34,23 @@ export const BrandingTemplateBoot = ({
|
||||
useGetPdfTemplate(templateId, {
|
||||
enabled: !!templateId,
|
||||
});
|
||||
// Retreives the branding template state.
|
||||
const { data: brandingTemplateState, isLoading: isBrandingTemplateLoading } =
|
||||
useGetPdfTemplateBrandingState();
|
||||
|
||||
const value = {
|
||||
templateId,
|
||||
pdfTemplate,
|
||||
isPdfTemplateLoading,
|
||||
|
||||
brandingTemplateState,
|
||||
isBrandingTemplateLoading,
|
||||
};
|
||||
|
||||
if (isPdfTemplateLoading) {
|
||||
return <Spinner size={20} />
|
||||
const isLoading = isPdfTemplateLoading || isBrandingTemplateLoading;
|
||||
|
||||
if (isLoading) {
|
||||
return <Spinner size={20} />;
|
||||
}
|
||||
return (
|
||||
<PdfTemplateContext.Provider value={value}>
|
||||
|
||||
@@ -44,15 +44,16 @@ export const useBrandingTemplateFormInitialValues = <
|
||||
>(
|
||||
initialValues = {},
|
||||
) => {
|
||||
const { pdfTemplate } = useBrandingTemplateBoot();
|
||||
const { pdfTemplate, brandingTemplateState } = useBrandingTemplateBoot();
|
||||
|
||||
const defaultPdfTemplate = {
|
||||
const brandingAttributes = {
|
||||
templateName: pdfTemplate?.templateName,
|
||||
...brandingTemplateState,
|
||||
...pdfTemplate?.attributes,
|
||||
};
|
||||
return {
|
||||
...initialValues,
|
||||
...(transformToForm(defaultPdfTemplate, initialValues) as T),
|
||||
...(transformToForm(brandingAttributes, initialValues) as T),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -45,8 +45,12 @@ export interface InvoicePaperTemplateProps {
|
||||
bigtitle?: string;
|
||||
|
||||
// Address
|
||||
showBillingToAddress?: boolean;
|
||||
showBilledFromAddress?: boolean;
|
||||
showCustomerAddress?: boolean;
|
||||
customerAddress?: string;
|
||||
|
||||
showCompanyAddress?: boolean;
|
||||
companyAddress?: string;
|
||||
|
||||
billedToLabel?: string;
|
||||
|
||||
// Entries
|
||||
@@ -90,9 +94,6 @@ export interface InvoicePaperTemplateProps {
|
||||
|
||||
lines?: Array<PapaerLine>;
|
||||
taxes?: Array<PaperTax>;
|
||||
|
||||
billedFromAddres?: string;
|
||||
billedToAddress?: string;
|
||||
}
|
||||
|
||||
export function InvoicePaperTemplate({
|
||||
@@ -118,8 +119,12 @@ export function InvoicePaperTemplate({
|
||||
showInvoiceNumber = true,
|
||||
|
||||
// Address
|
||||
showBillingToAddress = true,
|
||||
showBilledFromAddress = true,
|
||||
showCustomerAddress = true,
|
||||
customerAddress = DefaultPdfTemplateAddressBilledTo,
|
||||
|
||||
showCompanyAddress = true,
|
||||
companyAddress = DefaultPdfTemplateAddressBilledFrom,
|
||||
|
||||
billedToLabel = 'Billed To',
|
||||
|
||||
// Entries
|
||||
@@ -171,8 +176,6 @@ export function InvoicePaperTemplate({
|
||||
statementLabel = 'Statement',
|
||||
showStatement = true,
|
||||
statement = DefaultPdfTemplateStatement,
|
||||
billedToAddress = DefaultPdfTemplateAddressBilledTo,
|
||||
billedFromAddres = DefaultPdfTemplateAddressBilledFrom,
|
||||
}: InvoicePaperTemplateProps) {
|
||||
return (
|
||||
<PaperTemplate
|
||||
@@ -202,16 +205,16 @@ export function InvoicePaperTemplate({
|
||||
</PaperTemplate.TermsList>
|
||||
|
||||
<PaperTemplate.AddressesGroup>
|
||||
{showBilledFromAddress && (
|
||||
{showCompanyAddress && (
|
||||
<PaperTemplate.Address>
|
||||
<strong>{companyName}</strong>
|
||||
<Box dangerouslySetInnerHTML={{ __html: billedFromAddres }} />
|
||||
<Box dangerouslySetInnerHTML={{ __html: companyAddress }} />
|
||||
</PaperTemplate.Address>
|
||||
)}
|
||||
{showBillingToAddress && (
|
||||
|
||||
{showCustomerAddress && (
|
||||
<PaperTemplate.Address>
|
||||
<strong>{billedToLabel}</strong>
|
||||
<Box dangerouslySetInnerHTML={{ __html: billedToAddress }} />
|
||||
<Box dangerouslySetInnerHTML={{ __html: customerAddress }} />
|
||||
</PaperTemplate.Address>
|
||||
)}
|
||||
</PaperTemplate.AddressesGroup>
|
||||
|
||||
@@ -26,8 +26,9 @@ export const initialValues = {
|
||||
companyName: 'Bigcapital Technology, Inc.',
|
||||
|
||||
// Addresses
|
||||
showBilledFromAddress: true,
|
||||
showBillingToAddress: true,
|
||||
showCustomerAddress: true,
|
||||
showCompanyAddress: true,
|
||||
companyAddress: '',
|
||||
billedToLabel: 'Billed To',
|
||||
|
||||
// Entries
|
||||
|
||||
@@ -203,3 +203,28 @@ export const useAssignPdfTemplateAsDefault = (
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
// Retrieve organization branding state.
|
||||
// --------------------------------------------------
|
||||
export interface GetPdfTemplateBrandingStateResponse {
|
||||
companyName: string;
|
||||
companyAddress: string;
|
||||
companyLogoUri: string;
|
||||
companyLogoKey: string;
|
||||
primaryColor: string;
|
||||
}
|
||||
|
||||
export const useGetPdfTemplateBrandingState = (
|
||||
options?: UseQueryOptions<GetPdfTemplateBrandingStateResponse, Error>,
|
||||
): UseQueryResult<GetPdfTemplateBrandingStateResponse, Error> => {
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useQuery<GetPdfTemplateBrandingStateResponse, Error>(
|
||||
[PdfTemplatesQueryKey, 'state'],
|
||||
() =>
|
||||
apiRequest
|
||||
.get('/pdf-templates/state')
|
||||
.then((res) => transformToCamelCase(res.data?.data)),
|
||||
options,
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user