feat: add discount and adjustment fields to email templates.

This commit is contained in:
Ahmed Bouhuolia
2024-12-03 13:20:01 +02:00
parent 154ade9647
commit d5dacaa988
16 changed files with 598 additions and 243 deletions

View File

@@ -26,6 +26,9 @@ export interface ISaleEstimate {
branchId?: number; branchId?: number;
warehouseId?: number; warehouseId?: number;
total?: number;
totalLocal?: number;
discountAmount?: number; discountAmount?: number;
discountPercentage?: number | null; discountPercentage?: number | null;

View File

@@ -25,6 +25,12 @@ export class GetEstimateMailTemplateAttributesTransformer extends Transformer {
'subtotal', 'subtotal',
'subtotalLabel', 'subtotalLabel',
'discount',
'discountLabel',
'adjustment',
'adjustmentLabel',
'dueAmount', 'dueAmount',
'dueAmountLabel', 'dueAmountLabel',
@@ -103,7 +109,7 @@ export class GetEstimateMailTemplateAttributesTransformer extends Transformer {
* Estimate total. * Estimate total.
*/ */
public total(): string { public total(): string {
return this.options.estimate.formattedAmount; return this.options.estimate.totalFormatted;
} }
/** /**
@@ -114,11 +120,43 @@ export class GetEstimateMailTemplateAttributesTransformer extends Transformer {
return 'Total'; return 'Total';
} }
/**
* Estimate discount.
* @returns {string}
*/
public discount(): string {
return this.options.estimate?.discountAmountFormatted;
}
/**
* Estimate discount label.
* @returns {string}
*/
public discountLabel(): string {
return 'Discount';
}
/**
* Estimate adjustment.
* @returns {string}
*/
public adjustment(): string {
return this.options.estimate?.adjustmentFormatted;
}
/**
* Estimate adjustment label.
* @returns {string}
*/
public adjustmentLabel(): string {
return 'Adjustment';
}
/** /**
* Estimate subtotal. * Estimate subtotal.
*/ */
public subtotal(): string { public subtotal(): string {
return this.options.estimate.formattedAmount; return this.options.estimate.formattedSubtotal;
} }
/** /**

View File

@@ -21,6 +21,8 @@ export class SaleEstimateTransfromer extends Transformer {
'discountAmountFormatted', 'discountAmountFormatted',
'discountPercentageFormatted', 'discountPercentageFormatted',
'adjustmentFormatted', 'adjustmentFormatted',
'totalFormatted',
'totalLocalFormatted',
'formattedCreatedAt', 'formattedCreatedAt',
'entries', 'entries',
'attachments', 'attachments',
@@ -134,6 +136,27 @@ export class SaleEstimateTransfromer extends Transformer {
}); });
}; };
/**
* Retrieves the formatted estimate total.
* @returns {string}
*/
protected totalFormatted = (estimate: ISaleEstimate): string => {
return formatNumber(estimate.total, {
currencyCode: estimate.currencyCode,
});
};
/**
* Retrieves the formatted estimate total in local currency.
* @param estimate
* @returns {string}
*/
protected totalLocalFormatted = (estimate: ISaleEstimate): string => {
return formatNumber(estimate.totalLocal, {
currencyCode: estimate.currencyCode,
});
};
/** /**
* Retrieves the entries of the sale estimate. * Retrieves the entries of the sale estimate.
* @param {ISaleEstimate} estimate * @param {ISaleEstimate} estimate

View File

@@ -18,9 +18,6 @@ export class SaleEstimatesPdf {
@Inject() @Inject()
private chromiumlyTenancy: ChromiumlyTenancy; private chromiumlyTenancy: ChromiumlyTenancy;
@Inject()
private templateInjectable: TemplateInjectable;
@Inject() @Inject()
private getSaleEstimate: GetSaleEstimate; private getSaleEstimate: GetSaleEstimate;
@@ -62,6 +59,7 @@ export class SaleEstimatesPdf {
// Retireves the sale estimate html. // Retireves the sale estimate html.
const htmlContent = await this.saleEstimateHtml(tenantId, saleEstimateId); const htmlContent = await this.saleEstimateHtml(tenantId, saleEstimateId);
// Converts the html content to pdf.
const content = await this.chromiumlyTenancy.convertHtmlContent( const content = await this.chromiumlyTenancy.convertHtmlContent(
tenantId, tenantId,
htmlContent htmlContent

View File

@@ -23,6 +23,15 @@ export class GetInvoiceMailTemplateAttributesTransformer extends Transformer {
'invoiceNumber', 'invoiceNumber',
'invoiceNumberLabel', 'invoiceNumberLabel',
'subtotal',
'subtotalLabel',
'discount',
'discountLabel',
'adjustment',
'adjustmentLabel',
'total', 'total',
'totalLabel', 'totalLabel',
@@ -76,6 +85,30 @@ export class GetInvoiceMailTemplateAttributesTransformer extends Transformer {
return 'Invoice # {invoiceNumber}'; return 'Invoice # {invoiceNumber}';
} }
public subtotal(): string {
return this.options.invoice?.subtotalFormatted;
}
public subtotalLabel(): string {
return 'Subtotal';
}
public discount(): string {
return this.options.invoice?.discountAmountFormatted;
}
public discountLabel(): string {
return 'Discount';
}
public adjustment(): string {
return this.options.invoice?.adjustmentFormatted;
}
public adjustmentLabel(): string {
return 'Adjustment';
}
public total(): string { public total(): string {
return this.options.invoice?.totalFormatted; return this.options.invoice?.totalFormatted;
} }

View File

@@ -20,6 +20,12 @@ export class GetSaleReceiptMailTemplateAttributesTransformer extends Transformer
'total', 'total',
'totalLabel', 'totalLabel',
'discount',
'discountLabel',
'adjustment',
'adjustmentLabel',
'subtotal', 'subtotal',
'subtotalLabel', 'subtotalLabel',
@@ -98,7 +104,7 @@ export class GetSaleReceiptMailTemplateAttributesTransformer extends Transformer
* Receipt total. * Receipt total.
*/ */
public total(): string { public total(): string {
return this.options.receipt.formattedAmount; return this.options.receipt.totalFormatted;
} }
/** /**
@@ -109,12 +115,44 @@ export class GetSaleReceiptMailTemplateAttributesTransformer extends Transformer
return 'Total'; return 'Total';
} }
/**
* Receipt discount.
* @returns {string}
*/
public discount(): string {
return this.options.receipt?.discountAmountFormatted;
}
/**
* Receipt discount label.
* @returns {string}
*/
public discountLabel(): string {
return 'Discount';
}
/**
* Receipt adjustment.
* @returns {string}
*/
public adjustment(): string {
return this.options.receipt?.adjustmentFormatted;
}
/**
* Receipt adjustment label.
* @returns {string}
*/
public adjustmentLabel(): string {
return 'Adjustment';
}
/** /**
* Receipt subtotal. * Receipt subtotal.
* @returns {string} * @returns {string}
*/ */
public subtotal(): string { public subtotal(): string {
return this.options.receipt.formattedSubtotal; return this.options.receipt.subtotalFormatted;
} }
/** /**

19
pnpm-lock.yaml generated
View File

@@ -882,6 +882,12 @@ importers:
'@react-email/components': '@react-email/components':
specifier: 0.0.25 specifier: 0.0.25
version: 0.0.25(react-dom@18.3.1)(react@18.3.1) version: 0.0.25(react-dom@18.3.1)(react@18.3.1)
'@types/lodash.isempty':
specifier: ^4.4.9
version: 4.4.9
lodash.isempty:
specifier: ^4.4.0
version: 4.4.0
react: react:
specifier: 18.3.1 specifier: 18.3.1
version: 18.3.1 version: 18.3.1
@@ -2504,7 +2510,7 @@ packages:
'@babel/core': ^7.0.0-0 '@babel/core': ^7.0.0-0
dependencies: dependencies:
'@babel/core': 7.26.0 '@babel/core': 7.26.0
'@babel/helper-plugin-utils': 7.25.9 '@babel/helper-plugin-utils': 7.24.5
'@babel/helper-skip-transparent-expression-wrappers': 7.22.5 '@babel/helper-skip-transparent-expression-wrappers': 7.22.5
'@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.0) '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.0)
dev: true dev: true
@@ -11420,6 +11426,12 @@ packages:
- tedious - tedious
dev: false dev: false
/@types/lodash.isempty@4.4.9:
resolution: {integrity: sha512-DPSFfnT2JmZiAWNWOU8IRZws/Ha6zyGF5m06TydfsY+0dVoQqby2J61Na2QU4YtwiZ+moC6cJS6zWYBJq4wBVw==}
dependencies:
'@types/lodash': 4.17.13
dev: false
/@types/lodash@4.17.13: /@types/lodash@4.17.13:
resolution: {integrity: sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==} resolution: {integrity: sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==}
dev: false dev: false
@@ -15958,7 +15970,7 @@ packages:
postcss-modules-values: 4.0.0(postcss@8.4.47) postcss-modules-values: 4.0.0(postcss@8.4.47)
postcss-value-parser: 4.2.0 postcss-value-parser: 4.2.0
semver: 7.6.2 semver: 7.6.2
webpack: 5.91.0(esbuild@0.23.1) webpack: 5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4)
/css-minimizer-webpack-plugin@3.4.1(esbuild@0.23.1)(webpack@5.91.0): /css-minimizer-webpack-plugin@3.4.1(esbuild@0.23.1)(webpack@5.91.0):
resolution: {integrity: sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q==} resolution: {integrity: sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q==}
@@ -31178,7 +31190,7 @@ packages:
peerDependencies: peerDependencies:
webpack: ^5.0.0 webpack: ^5.0.0
dependencies: dependencies:
webpack: 5.91.0(esbuild@0.23.1) webpack: 5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4)
/styled-components@5.3.11(@babel/core@7.26.0)(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1): /styled-components@5.3.11(@babel/core@7.26.0)(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==} resolution: {integrity: sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==}
@@ -33492,6 +33504,7 @@ packages:
- '@swc/core' - '@swc/core'
- esbuild - esbuild
- uglify-js - uglify-js
dev: false
/webpack@5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0): /webpack@5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0):
resolution: {integrity: sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==} resolution: {integrity: sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==}

View File

@@ -22,11 +22,13 @@
}, },
"dependencies": { "dependencies": {
"@react-email/components": "0.0.25", "@react-email/components": "0.0.25",
"@types/lodash.isempty": "^4.4.9",
"lodash.isempty": "^4.4.0",
"react": "18.3.1",
"react-dom": "18.3.1",
"tailwindcss": "^3.4.14", "tailwindcss": "^3.4.14",
"vite-plugin-dts": "^4.3.0", "vite-plugin-dts": "^4.3.0",
"vitest": "^2.1.3", "vitest": "^2.1.3"
"react-dom": "18.3.1",
"react": "18.3.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.13.0", "@eslint/js": "^9.13.0",

View File

@@ -30,4 +30,7 @@ If you have any questions, please let us know.
Thanks, Thanks,
Bigcapital`, Bigcapital`,
subtotal: '$1,000.00',
discount: '$1,000.00',
adjustment: '$1,000.00'
}; };

View File

@@ -7,6 +7,7 @@ import {
Section, Section,
Text, Text,
} from '@react-email/components'; } from '@react-email/components';
import isEmpty from 'lodash.isempty';
import { EmailTemplateLayout } from './EmailTemplateLayout'; import { EmailTemplateLayout } from './EmailTemplateLayout';
import { CSSProperties } from 'react'; import { CSSProperties } from 'react';
import { EmailTemplate } from './EmailTemplate'; import { EmailTemplate } from './EmailTemplate';
@@ -25,6 +26,18 @@ export interface CreditNoteEmailProps {
total: string; total: string;
totalLabel?: string; totalLabel?: string;
// # Subtotal
subtotal: string;
subtotalLabel?: string;
// # Adjustment
adjustment?: string;
adjustmentLabel?: string;
// # Discount
discount?: string;
discountLabel?: string;
// # Items // # Items
items: Array<{ label: string; quantity: string; rate: string }>; items: Array<{ label: string; quantity: string; rate: string }>;
@@ -56,6 +69,18 @@ export const CreditNoteEmailTemplate: React.FC<
total, total,
totalLabel = 'Total', totalLabel = 'Total',
// # Subtotal
subtotal,
subtotalLabel = 'Subtotal',
// # Discount
discount,
discountLabel = 'Discount',
// # Adjustment
adjustment,
adjustmentLabel = 'Adjustment',
// # Credit Note # // # Credit Note #
creditNoteNumberLabel = 'Credit Note # {creditNoteNumber}', creditNoteNumberLabel = 'Credit Note # {creditNoteNumber}',
creditNoteNumber = 'CN-00001', creditNoteNumber = 'CN-00001',
@@ -70,70 +95,104 @@ export const CreditNoteEmailTemplate: React.FC<
// # Items // # Items
items = [], items = [],
}) => { }) => {
return ( return (
<EmailTemplateLayout preview={preview}> <EmailTemplateLayout preview={preview}>
<EmailTemplate> <EmailTemplate>
<Section style={mainSectionStyle}> <Section style={mainSectionStyle}>
{companyLogoUri && <EmailTemplate.CompanyLogo src={companyLogoUri} />} {companyLogoUri && <EmailTemplate.CompanyLogo src={companyLogoUri} />}
<Section style={headerInfoStyle}> <Section style={headerInfoStyle}>
<Row> <Row>
<Heading style={companyNameStyle}>{companyName}</Heading> <Heading style={companyNameStyle}>{companyName}</Heading>
</Row> </Row>
<Row> <Row>
<Text style={invoiceAmountStyle}>{total}</Text> <Text style={invoiceAmountStyle}>{total}</Text>
</Row> </Row>
<Row> <Row>
<Text style={creditNumberStyle}> <Text style={creditNumberStyle}>
{creditNoteNumberLabel?.replace( {creditNoteNumberLabel?.replace(
'{creditNoteNumber}', '{creditNoteNumber}',
creditNoteNumber creditNoteNumber
)} )}
</Text> </Text>
</Row> </Row>
</Section>
<Text style={messageStyle}>{message}</Text>
<Button
href={viewButtonUrl}
style={{
...viewInvoiceButtonStyle,
backgroundColor: primaryColor,
}}
>
{viewButtonLabel}
</Button>
<Section style={totalsSectionStyle}>
{items.map((item, index) => (
<Row key={index} style={itemLineRowStyle}>
<Column width={'50%'}>
<Text style={listItemLabelStyle}>{item.label}</Text>
</Column>
<Column width={'50%'}>
<Text style={listItemAmountStyle}>
{item.quantity} x {item.rate}
</Text>
</Column>
</Row>
))}
<Row style={totalLineRowStyle}>
<Column width={'50%'}>
<Text style={totalLineItemLabelStyle}>{totalLabel}</Text>
</Column>
<Column width={'50%'}>
<Text style={totalLineItemAmountStyle}>{total}</Text>
</Column>
</Row>
</Section>
</Section> </Section>
</EmailTemplate>
</EmailTemplateLayout> <Text style={messageStyle}>{message}</Text>
); <Button
}; href={viewButtonUrl}
style={{
...viewInvoiceButtonStyle,
backgroundColor: primaryColor,
}}
>
{viewButtonLabel}
</Button>
<Section style={totalsSectionStyle}>
{items.map((item, index) => (
<Row key={index} style={itemLineRowStyle}>
<Column width={'50%'}>
<Text style={listItemLabelStyle}>{item.label}</Text>
</Column>
<Column width={'50%'}>
<Text style={listItemAmountStyle}>
{item.quantity} x {item.rate}
</Text>
</Column>
</Row>
))}
<Row style={totalLineRowStyle}>
<Column width={'50%'}>
<Text style={totalLineItemLabelStyle}>{subtotalLabel}</Text>
</Column>
<Column width={'50%'}>
<Text style={totalLineItemAmountStyle}>{subtotal}</Text>
</Column>
</Row>
{!isEmpty(discount) && (
<Row style={itemLineRowStyle}>
<Column width={'50%'}>
<Text style={listItemLabelStyle}>{discountLabel}</Text>
</Column>
<Column width={'50%'}>
<Text style={listItemAmountStyle}>{discount}</Text>
</Column>
</Row>
)}
{!isEmpty(adjustment) && (
<Row style={itemLineRowStyle}>
<Column width={'50%'}>
<Text style={listItemLabelStyle}>{adjustmentLabel}</Text>
</Column>
<Column width={'50%'}>
<Text style={listItemAmountStyle}>{adjustment}</Text>
</Column>
</Row>
)}
<Row style={totalLineRowStyle}>
<Column width={'50%'}>
<Text style={totalLineItemLabelStyle}>{totalLabel}</Text>
</Column>
<Column width={'50%'}>
<Text style={totalLineItemAmountStyle}>{total}</Text>
</Column>
</Row>
</Section>
</Section>
</EmailTemplate>
</EmailTemplateLayout>
);
};
/** /**
* Renders the estimate mail template to string * Renders the estimate mail template to string

View File

@@ -30,4 +30,7 @@ If you have any questions, please let us know.
Thanks, Thanks,
Bigcapital`, Bigcapital`,
adjustment: '$100.00',
discount: '$100.00',
subtotal: '$100.00',
}; };

View File

@@ -1,8 +1,8 @@
import { CSSProperties } from 'react'; import { CSSProperties } from 'react';
import isEmpty from 'lodash.isempty';
import { import {
Button, Button,
Column, Column,
Container,
Heading, Heading,
render, render,
Row, Row,
@@ -30,7 +30,15 @@ export interface EstimatePaymentEmailProps {
subtotal: string; subtotal: string;
subtotalLabel?: string; subtotalLabel?: string;
// # Estimate No# // # Adjustment
adjustment?: string;
adjustmentLabel?: string;
// # Discount
discount?: string;
discountLabel?: string;
// # Estimate No.
estimateNumber?: string; estimateNumber?: string;
estimateNumberLabel?: string; estimateNumberLabel?: string;
@@ -65,6 +73,14 @@ export const EstimatePaymentEmail: React.FC<
total, total,
totalLabel = 'Total', totalLabel = 'Total',
// # Adjustment
adjustment,
adjustmentLabel = 'Adjustment',
// # Discount
discount,
discountLabel = 'Discount',
// # Subtotal // # Subtotal
subtotal, subtotal,
subtotalLabel = 'Subtotal', subtotalLabel = 'Subtotal',
@@ -87,84 +103,108 @@ export const EstimatePaymentEmail: React.FC<
// # Items // # Items
items = [], items = [],
}) => { }) => {
return ( return (
<EmailTemplateLayout preview={preview}> <EmailTemplateLayout preview={preview}>
<EmailTemplate> <EmailTemplate>
{companyLogoUri && <EmailTemplate.CompanyLogo src={companyLogoUri} />} {companyLogoUri && <EmailTemplate.CompanyLogo src={companyLogoUri} />}
<Section style={headerInfoStyle}> <Section style={headerInfoStyle}>
<Row> <Row>
<Heading style={invoiceCompanyNameStyle}>{companyName}</Heading> <Heading style={invoiceCompanyNameStyle}>{companyName}</Heading>
</Row>
<Row>
<Text style={estimateAmountStyle}>{total}</Text>
</Row>
<Row>
<Text style={estimateNumberStyle}>
{estimateNumberLabel?.replace('{estimateNumber}', estimateNumber)}
</Text>
</Row>
<Row>
<Text style={estimateExpirationStyle}>
{expirationDateLabel.replace('{expirationDate}', expirationDate)}
</Text>
</Row>
</Section>
<Text style={estimateMessageStyle}>{message}</Text>
<Button
href={viewEstimateButtonUrl}
style={{
...viewEstimateButtonStyle,
backgroundColor: primaryColor,
}}
>
{viewEstimateButtonLabel}
</Button>
<Section style={totalsSectionStyle}>
{items.map((item, index) => (
<Row key={index} style={itemLineRowStyle}>
<Column width={'50%'}>
<Text style={listItemLabelStyle}>{item.label}</Text>
</Column>
<Column width={'50%'}>
<Text style={listItemAmountStyle}>
{item.quantity} x {item.rate}
</Text>
</Column>
</Row> </Row>
<Row> ))}
<Text style={estimateAmountStyle}>{total}</Text>
</Row>
<Row>
<Text style={estimateNumberStyle}>
{estimateNumberLabel?.replace('{estimateNumber}', estimateNumber)}
</Text>
</Row>
<Row>
<Text style={estimateExpirationStyle}>
{expirationDateLabel.replace('{expirationDate}', expirationDate)}
</Text>
</Row>
</Section>
<Text style={estimateMessageStyle}>{message}</Text> <Row style={totalLineRowStyle}>
<Button <Column width={'50%'}>
href={viewEstimateButtonUrl} <Text style={totalLineItemLabelStyle}>{subtotalLabel}</Text>
style={{ </Column>
...viewEstimateButtonStyle,
backgroundColor: primaryColor,
}}
>
{viewEstimateButtonLabel}
</Button>
<Section style={totalsSectionStyle}> <Column width={'50%'}>
{items.map((item, index) => ( <Text style={totalLineItemAmountStyle}>{subtotal}</Text>
<Row key={index} style={itemLineRowStyle}> </Column>
<Column width={'50%'}> </Row>
<Text style={listItemLabelStyle}>{item.label}</Text>
</Column>
<Column width={'50%'}>
<Text style={listItemAmountStyle}>
{item.quantity} x {item.rate}
</Text>
</Column>
</Row>
))}
{!isEmpty(discount) && (
<Row style={totalLineRowStyle}> <Row style={totalLineRowStyle}>
<Column width={'50%'}> <Column width={'50%'}>
<Text style={totalLineItemLabelStyle}>{subtotalLabel}</Text> <Text style={listItemLabelStyle}>{discountLabel}</Text>
</Column> </Column>
<Column width={'50%'}> <Column width={'50%'}>
<Text style={totalLineItemAmountStyle}>{subtotal}</Text> <Text style={listItemAmountStyle}>{discount}</Text>
</Column> </Column>
</Row> </Row>
)}
{!isEmpty(adjustment) && (
<Row style={totalLineRowStyle}> <Row style={totalLineRowStyle}>
<Column width={'50%'}> <Column width={'50%'}>
<Text style={totalLineItemLabelStyle}>{totalLabel}</Text> <Text style={listItemLabelStyle}>{adjustmentLabel}</Text>
</Column> </Column>
<Column width={'50%'}> <Column width={'50%'}>
<Text style={totalLineItemAmountStyle}>{total}</Text> <Text style={listItemAmountStyle}>{adjustment}</Text>
</Column> </Column>
</Row> </Row>
</Section> )}
</EmailTemplate>
</EmailTemplateLayout> <Row style={totalLineRowStyle}>
); <Column width={'50%'}>
}; <Text style={totalLineItemLabelStyle}>{totalLabel}</Text>
</Column>
<Column width={'50%'}>
<Text style={totalLineItemAmountStyle}>{total}</Text>
</Column>
</Row>
</Section>
</EmailTemplate>
</EmailTemplateLayout>
);
};
/** /**
* Renders the estimate mail template to string * Renders the estimate mail template to string
* @param {EstimatePaymentEmailProps} props * @param {EstimatePaymentEmailProps} props
* @returns {Promise<string>} * @returns {Promise<string>}
*/ */
export const renderEstimateEmailTemplate = ( export const renderEstimateEmailTemplate = (

View File

@@ -33,6 +33,9 @@ Thanks,
Bigcapital`, Bigcapital`,
dueDate: ' 10 Oct 2024', dueDate: ' 10 Oct 2024',
total: '$1,000.00', total: '$1,000.00',
subtotal: '$1,000.00',
dueAmount: '$1,000.00', dueAmount: '$1,000.00',
items: [{ label: 'Swaniawski Muller', quantity: '1', rate: '$1,000.00' }], items: [{ label: 'Swaniawski Muller', quantity: '1', rate: '$1,000.00' }],
adjustment: '$100.00',
discount: '$100.00',
}; };

View File

@@ -1,6 +1,6 @@
import { CSSProperties } from 'react';
import { import {
Button, Button,
Container,
Section, Section,
Heading, Heading,
Text, Text,
@@ -8,7 +8,7 @@ import {
Column, Column,
render, render,
} from '@react-email/components'; } from '@react-email/components';
import { CSSProperties } from 'react'; import isEmpty from 'lodash.isempty';
import { EmailTemplateLayout } from './EmailTemplateLayout'; import { EmailTemplateLayout } from './EmailTemplateLayout';
import { EmailTemplate } from './EmailTemplate'; import { EmailTemplate } from './EmailTemplate';
@@ -36,6 +36,18 @@ export interface InvoicePaymentEmailProps {
dueAmount: string; dueAmount: string;
dueAmountLabel?: string; dueAmountLabel?: string;
// # Adjustment
adjustment?: string;
adjustmentLabel?: string;
// # Discount
discount?: string;
discountLabel?: string;
// # Subtotal
subtotal: string;
subtotalLabel?: string;
// # Due date // # Due date
dueDate: string; dueDate: string;
dueDateLabel?: string; dueDateLabel?: string;
@@ -82,6 +94,18 @@ export const InvoicePaymentEmail: React.FC<
total, total,
totalLabel = 'Total', totalLabel = 'Total',
// # Subtotal
subtotal,
subtotalLabel = 'Subtotal',
// # Discount
discount,
discountLabel = 'Discount',
// # Adjustment
adjustment,
adjustmentLabel = 'Adjustment',
// # Invoice due amount // # Invoice due amount
dueAmountLabel = 'Due Amount', dueAmountLabel = 'Due Amount',
dueAmount, dueAmount,
@@ -92,84 +116,118 @@ export const InvoicePaymentEmail: React.FC<
items, items,
}) => { }) => {
return ( return (
<EmailTemplateLayout preview={preview}> <EmailTemplateLayout preview={preview}>
<EmailTemplate> <EmailTemplate>
<Section style={mainSectionStyle}> <Section style={mainSectionStyle}>
{companyLogoUri && <EmailTemplate.CompanyLogo src={companyLogoUri} />} {companyLogoUri && <EmailTemplate.CompanyLogo src={companyLogoUri} />}
<Section style={headerInfoStyle}> <Section style={headerInfoStyle}>
<Row> <Row>
<Heading style={invoiceCompanyNameStyle}>{companyName}</Heading> <Heading style={invoiceCompanyNameStyle}>{companyName}</Heading>
</Row> </Row>
<Row> <Row>
<Text style={invoiceAmountStyle}>{invoiceAmount}</Text> <Text style={invoiceAmountStyle}>{invoiceAmount}</Text>
</Row> </Row>
<Row> <Row>
<Text style={invoiceNumberStyle}> <Text style={invoiceNumberStyle}>
{invoiceNumberLabel?.replace('{invoiceNumber}', invoiceNumber)} {invoiceNumberLabel?.replace('{invoiceNumber}', invoiceNumber)}
</Text> </Text>
</Row> </Row>
<Row> <Row>
<Text style={invoiceDateStyle}> <Text style={invoiceDateStyle}>
{dueDateLabel.replace('{dueDate}', dueDate)} {dueDateLabel.replace('{dueDate}', dueDate)}
</Text> </Text>
</Row> </Row>
</Section> </Section>
<Text style={invoiceMessageStyle}>{invoiceMessage}</Text> <Text style={invoiceMessageStyle}>{invoiceMessage}</Text>
<Button <Button
href={viewInvoiceButtonUrl} href={viewInvoiceButtonUrl}
style={{ style={{
...viewInvoiceButtonStyle, ...viewInvoiceButtonStyle,
backgroundColor: primaryColor, backgroundColor: primaryColor,
}} }}
> >
{viewInvoiceButtonLabel} {viewInvoiceButtonLabel}
</Button> </Button>
<Section style={totalsSectionStyle}> <Section style={totalsSectionStyle}>
{items.map((item, index) => ( {items.map((item, index) => (
<Row key={index} style={itemLineRowStyle}> <Row key={index} style={itemLineRowStyle}>
<Column width={'50%'}>
<Text style={listItemLabelStyle}>{item.label}</Text>
</Column>
<Column width={'50%'}>
<Text style={listItemAmountStyle}>
{item.quantity} x {item.rate}
</Text>
</Column>
</Row>
))}
<Row style={dueAmounLineRowStyle}>
<Column width={'50%'}> <Column width={'50%'}>
<Text style={dueAmountLineItemLabelStyle}> <Text style={listItemLabelStyle}>{item.label}</Text>
{dueAmountLabel} </Column>
<Column width={'50%'}>
<Text style={listItemAmountStyle}>
{item.quantity} x {item.rate}
</Text> </Text>
</Column> </Column>
</Row>
))}
<Row style={totalLineRowStyle}>
<Column width={'50%'}>
<Text style={totalLineItemLabelStyle}>{subtotalLabel}</Text>
</Column>
<Column width={'50%'}>
<Text style={totalLineItemAmountStyle}>{subtotal}</Text>
</Column>
</Row>
{!isEmpty(discount) && (
<Row style={lineRowStyle}>
<Column width={'50%'}>
<Text style={listItemLabelStyle}>{discountLabel}</Text>
</Column>
<Column width={'50%'}> <Column width={'50%'}>
<Text style={dueAmountLineItemAmountStyle}>{dueAmount}</Text> <Text style={listItemAmountStyle}>{discount}</Text>
</Column> </Column>
</Row> </Row>
)}
<Row style={totalLineRowStyle}> {!isEmpty(adjustment) && (
<Row style={lineRowStyle}>
<Column width={'50%'}> <Column width={'50%'}>
<Text style={totalLineItemLabelStyle}>{totalLabel}</Text> <Text style={listItemLabelStyle}>{adjustmentLabel}</Text>
</Column> </Column>
<Column width={'50%'}> <Column width={'50%'}>
<Text style={totalLineItemAmountStyle}>{total}</Text> <Text style={listItemAmountStyle}>{adjustment}</Text>
</Column> </Column>
</Row> </Row>
</Section> )}
<Row style={totalLineRowStyle}>
<Column width={'50%'}>
<Text style={totalLineItemLabelStyle}>{totalLabel}</Text>
</Column>
<Column width={'50%'}>
<Text style={totalLineItemAmountStyle}>{total}</Text>
</Column>
</Row>
<Row style={dueAmounLineRowStyle}>
<Column width={'50%'}>
<Text style={dueAmountLineItemLabelStyle}>
{dueAmountLabel}
</Text>
</Column>
<Column width={'50%'}>
<Text style={dueAmountLineItemAmountStyle}>{dueAmount}</Text>
</Column>
</Row>
</Section> </Section>
</EmailTemplate> </Section>
</EmailTemplateLayout> </EmailTemplate>
); </EmailTemplateLayout>
}; );
};
export const renderInvoicePaymentEmail = (props: InvoicePaymentEmailProps) => { export const renderInvoicePaymentEmail = (props: InvoicePaymentEmailProps) => {
return render(<InvoicePaymentEmail {...props} />); return render(<InvoicePaymentEmail {...props} />);
@@ -237,6 +295,11 @@ const dueAmounLineRowStyle: CSSProperties = {
height: 40, height: 40,
}; };
const lineRowStyle: CSSProperties = {
borderBottom: '1px solid #D9D9D9',
height: 40,
};
const totalLineRowStyle: CSSProperties = { const totalLineRowStyle: CSSProperties = {
borderBottom: '1px solid #000', borderBottom: '1px solid #000',
height: 40, height: 40,

View File

@@ -28,4 +28,6 @@ If you have any questions, please let us know.
Thanks, Thanks,
Bigcapital`, Bigcapital`,
adjustment: '$100.00',
discount: '$100.00',
}; };

View File

@@ -1,5 +1,5 @@
import { CSSProperties } from 'react';
import { import {
Button,
Column, Column,
Heading, Heading,
render, render,
@@ -7,14 +7,12 @@ import {
Section, Section,
Text, Text,
} from '@react-email/components'; } from '@react-email/components';
import { CSSProperties } from 'react'; import isEmpty from 'lodash.isempty';
import { EmailTemplateLayout } from './EmailTemplateLayout'; import { EmailTemplateLayout } from './EmailTemplateLayout';
import { EmailTemplate } from './EmailTemplate'; import { EmailTemplate } from './EmailTemplate';
export interface ReceiptEmailTemplateProps { export interface ReceiptEmailTemplateProps {
preview: string; preview: string;
companyName?: string; companyName?: string;
companyLogoUri: string; companyLogoUri: string;
@@ -36,6 +34,10 @@ export interface ReceiptEmailTemplateProps {
discount?: string; discount?: string;
discountLabel?: string; discountLabel?: string;
// # Adjustment
adjustment?: string;
adjustmentLabel?: string;
// # Subtotal // # Subtotal
subtotal?: string; subtotal?: string;
subtotalLabel?: string; subtotalLabel?: string;
@@ -60,6 +62,14 @@ export const ReceiptEmailTemplate: React.FC<
total = '$1,000.00', total = '$1,000.00',
totalLabel = 'Total', totalLabel = 'Total',
// # Diso
discountLabel = 'Discount',
discount,
// # ADjustment
adjustmentLabel = 'Adjustment',
adjustment,
// # Subtotal // # Subtotal
subtotal = '$1,000.00', subtotal = '$1,000.00',
subtotalLabel = 'Subtotal', subtotalLabel = 'Subtotal',
@@ -74,68 +84,92 @@ export const ReceiptEmailTemplate: React.FC<
// # Items // # Items
items = [{ label: 'Swaniawski Muller', quantity: '1', rate: '$1,000.00' }], items = [{ label: 'Swaniawski Muller', quantity: '1', rate: '$1,000.00' }],
}) => { }) => {
return ( return (
<EmailTemplateLayout preview={preview}> <EmailTemplateLayout preview={preview}>
<EmailTemplate> <EmailTemplate>
{companyLogoUri && <EmailTemplate.CompanyLogo src={companyLogoUri} />} {companyLogoUri && <EmailTemplate.CompanyLogo src={companyLogoUri} />}
<Section style={headerInfoStyle}> <Section style={headerInfoStyle}>
<Row> <Row>
<Heading style={invoiceCompanyNameStyle}>{companyName}</Heading> <Heading style={invoiceCompanyNameStyle}>{companyName}</Heading>
</Row> </Row>
<Row> <Row>
<Text style={amountStyle}>{total}</Text> <Text style={amountStyle}>{total}</Text>
</Row> </Row>
<Row> <Row>
<Text style={receiptNumberStyle}> <Text style={receiptNumberStyle}>
{receiptNumberLabel?.replace('{receiptNumber}', receiptNumber)} {receiptNumberLabel?.replace('{receiptNumber}', receiptNumber)}
</Text> </Text>
</Row> </Row>
</Section> </Section>
<Text style={messageStyle}>{message}</Text> <Text style={messageStyle}>{message}</Text>
<Section style={totalsSectionStyle}> <Section style={totalsSectionStyle}>
{items.map((item, index) => ( {items.map((item, index) => (
<Row key={index} style={itemLineRowStyle}> <Row key={index} style={itemLineRowStyle}>
<Column width={'50%'}>
<Text style={listItemLabelStyle}>{item.label}</Text>
</Column>
<Column width={'50%'}>
<Text style={listItemAmountStyle}>
{item.quantity} x {item.rate}
</Text>
</Column>
</Row>
))}
<Row style={totalLineRowStyle}>
<Column width={'50%'}> <Column width={'50%'}>
<Text style={dueAmountLineItemLabelStyle}>{subtotalLabel}</Text> <Text style={listItemLabelStyle}>{item.label}</Text>
</Column> </Column>
<Column width={'50%'}> <Column width={'50%'}>
<Text style={dueAmountLineItemAmountStyle}>{subtotal}</Text> <Text style={listItemAmountStyle}>
{item.quantity} x {item.rate}
</Text>
</Column> </Column>
</Row> </Row>
))}
<Row style={totalLineRowStyle}>
<Row style={totalLineRowStyle}>
<Column width={'50%'}>
<Text style={dueAmountLineItemLabelStyle}>{subtotalLabel}</Text>
</Column>
<Column width={'50%'}>
<Text style={dueAmountLineItemAmountStyle}>{subtotal}</Text>
</Column>
</Row>
{!isEmpty(discount) && (
<Row style={itemLineRowStyle}>
<Column width={'50%'}> <Column width={'50%'}>
<Text style={totalLineItemLabelStyle}>{totalLabel}</Text> <Text style={listItemLabelStyle}>{discountLabel}</Text>
</Column> </Column>
<Column width={'50%'}> <Column width={'50%'}>
<Text style={totalLineItemAmountStyle}>{total}</Text> <Text style={listItemAmountStyle}>{discount}</Text>
</Column> </Column>
</Row> </Row>
</Section> )}
</EmailTemplate>
</EmailTemplateLayout> {!isEmpty(adjustment) && (
); <Row style={itemLineRowStyle}>
}; <Column width={'50%'}>
<Text style={listItemLabelStyle}>{adjustmentLabel}</Text>
</Column>
<Column width={'50%'}>
<Text style={listItemAmountStyle}>{adjustment}</Text>
</Column>
</Row>
)}
<Row style={totalLineRowStyle}>
<Column width={'50%'}>
<Text style={totalLineItemLabelStyle}>{totalLabel}</Text>
</Column>
<Column width={'50%'}>
<Text style={totalLineItemAmountStyle}>{total}</Text>
</Column>
</Row>
</Section>
</EmailTemplate>
</EmailTemplateLayout>
);
};
/** /**
* Renders the sale receipt mail template to string * Renders the sale receipt mail template to string