Merge branch 'develop' into add-mail-invoice-receipt-schema

This commit is contained in:
Ahmed Bouhuolia
2024-11-10 14:37:11 +02:00
36 changed files with 4860 additions and 177 deletions

View File

@@ -4,10 +4,10 @@
"scripts": { "scripts": {
"dev": "lerna run dev", "dev": "lerna run dev",
"build": "lerna run build", "build": "lerna run build",
"dev:webapp": "lerna run dev --scope \"@bigcapital/webapp\" --scope \"@bigcapital/utils\"", "dev:webapp": "lerna run dev --scope \"@bigcapital/webapp\" --scope \"@bigcapital/utils\" --scope \"@bigcapital/pdf-templates\"",
"build:webapp": "lerna run build --scope \"@bigcapital/webapp\" --scope \"@bigcapital/utils\"", "build:webapp": "lerna run build --scope \"@bigcapital/webapp\" --scope \"@bigcapital/utils\" --scope \"@bigcapital/pdf-templates\"",
"dev:server": "lerna run dev --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\"", "dev:server": "lerna run dev --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\" --scope \"@bigcapital/pdf-templates\" --scope \"@bigcapital/email-components\"",
"build:server": "lerna run build --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\"", "build:server": "lerna run build --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\" --scope \"@bigcapital/pdf-templates\" --scope \"@bigcapital/email-components\"",
"serve:server": "lerna run serve --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\"", "serve:server": "lerna run serve --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\"",
"test:e2e": "playwright test", "test:e2e": "playwright test",
"prepare": "husky install" "prepare": "husky install"

View File

@@ -37,7 +37,8 @@ export interface CommonMailOptions {
cc?: Array<string>; cc?: Array<string>;
bcc?: Array<string>; bcc?: Array<string>;
formatArgs?: Record<string, any>; formatArgs?: Record<string, any>;
toOptions: Array<AddressItem>;
fromOptions: Array<AddressItem>;
} }
export interface CommonMailOptionsDTO extends Partial<CommonMailOptions> { export interface CommonMailOptionsDTO extends Partial<CommonMailOptions> {}
}

View File

@@ -338,21 +338,22 @@ export interface InvoicePdfTemplateAttributes {
subtotalLabel: string; subtotalLabel: string;
discountLabel: string; discountLabel: string;
paymentMadeLabel: string; paymentMadeLabel: string;
balanceDueLabel: string;
showTotal: boolean; showTotal: boolean;
showSubtotal: boolean; showSubtotal: boolean;
showDiscount: boolean; showDiscount: boolean;
showTaxes: boolean; showTaxes: boolean;
showPaymentMade: boolean; showPaymentMade: boolean;
showDueAmount: boolean;
showBalanceDue: boolean;
total: string; total: string;
subtotal: string; subtotal: string;
discount: string; discount: string;
paymentMade: string; paymentMade: string;
balanceDue: string;
// Due Amount
dueAmount: string;
showDueAmount: boolean;
dueAmountLabel: string;
termsConditionsLabel: string; termsConditionsLabel: string;
showTermsConditions: boolean; showTermsConditions: boolean;

View File

@@ -58,7 +58,7 @@ export class PdfTemplate extends TenantModel {
* @returns {string} * @returns {string}
*/ */
get companyLogoUri() { get companyLogoUri() {
return this.attributes.companyLogoKey return this.attributes?.companyLogoKey
? getUploadedObjectUri(this.attributes.companyLogoKey) ? getUploadedObjectUri(this.attributes.companyLogoKey)
: ''; : '';
} }

View File

@@ -23,22 +23,24 @@ export class ContactMailNotification {
public async getDefaultMailOptions( public async getDefaultMailOptions(
tenantId: number, tenantId: number,
customerId: number customerId: number
): Promise<Pick<CommonMailOptions, 'to' | 'from'>> { ): Promise<
Pick<CommonMailOptions, 'to' | 'from' | 'toOptions' | 'fromOptions'>
> {
const { Customer } = this.tenancy.models(tenantId); const { Customer } = this.tenancy.models(tenantId);
const customer = await Customer.query() const customer = await Customer.query()
.findById(customerId) .findById(customerId)
.throwIfNotFound(); .throwIfNotFound();
const toAddresses = customer.contactAddresses; const toOptions = customer.contactAddresses;
const fromAddresses = await this.mailTenancy.senders(tenantId); const fromOptions = await this.mailTenancy.senders(tenantId);
const toAddress = toAddresses.find((a) => a.primary); const toAddress = toOptions.find((a) => a.primary);
const fromAddress = fromAddresses.find((a) => a.primary); const fromAddress = fromOptions.find((a) => a.primary);
const to = toAddress?.mail ? castArray(toAddress?.mail) : []; const to = toAddress?.mail ? castArray(toAddress?.mail) : [];
const from = fromAddress?.mail ? castArray(fromAddress?.mail) : []; const from = fromAddress?.mail ? castArray(fromAddress?.mail) : [];
return { to, from }; return { to, from, toOptions, fromOptions };
} }
/** /**

View File

@@ -19,9 +19,6 @@ export class SaleInvoicePdf {
@Inject() @Inject()
private chromiumlyTenancy: ChromiumlyTenancy; private chromiumlyTenancy: ChromiumlyTenancy;
@Inject()
private templateInjectable: TemplateInjectable;
@Inject() @Inject()
private getInvoiceService: GetSaleInvoice; private getInvoiceService: GetSaleInvoice;

View File

@@ -129,8 +129,8 @@ export class SendSaleInvoiceMailCommon {
...commonArgs, ...commonArgs,
'Customer Name': invoice.customer.displayName, 'Customer Name': invoice.customer.displayName,
'Invoice Number': invoice.invoiceNo, 'Invoice Number': invoice.invoiceNo,
'Invoice DueAmount': invoice.dueAmountFormatted, 'Invoice Due Amount': invoice.dueAmountFormatted,
'Invoice DueDate': invoice.dueDateFormatted, 'Invoice Due Date': invoice.dueDateFormatted,
'Invoice Date': invoice.invoiceDateFormatted, 'Invoice Date': invoice.invoiceDateFormatted,
'Invoice Amount': invoice.totalFormatted, 'Invoice Amount': invoice.totalFormatted,
'Overdue Days': invoice.overdueDays, 'Overdue Days': invoice.overdueDays,

View File

@@ -1,6 +1,10 @@
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import Mail from '@/lib/Mail'; import Mail from '@/lib/Mail';
import { ISaleInvoiceMailSend, SendInvoiceMailDTO } from '@/interfaces'; import {
ISaleInvoiceMailSend,
SaleInvoiceMailOptions,
SendInvoiceMailDTO,
} from '@/interfaces';
import { SaleInvoicePdf } from './SaleInvoicePdf'; import { SaleInvoicePdf } from './SaleInvoicePdf';
import { SendSaleInvoiceMailCommon } from './SendInvoiceInvoiceMailCommon'; import { SendSaleInvoiceMailCommon } from './SendInvoiceInvoiceMailCommon';
import { mergeAndValidateMailOptions } from '@/services/MailNotification/utils'; import { mergeAndValidateMailOptions } from '@/services/MailNotification/utils';
@@ -47,6 +51,34 @@ export class SendSaleInvoiceMail {
} as ISaleInvoiceMailSend); } as ISaleInvoiceMailSend);
} }
/**
* Retrieves the formatted mail options.
* @param {number} tenantId
* @param {number} saleInvoiceId
* @param {SendInvoiceMailDTO} messageOptions
* @returns {Promise<SaleInvoiceMailOptions>}
*/
async getFormattedMailOptions(
tenantId: number,
saleInvoiceId: number,
messageOptions: SendInvoiceMailDTO
): Promise<SaleInvoiceMailOptions> {
const defaultMessageOptions = await this.invoiceMail.getInvoiceMailOptions(
tenantId,
saleInvoiceId
);
// Merges message options with default options and parses the options values.
const parsedMessageOptions = mergeAndValidateMailOptions(
defaultMessageOptions,
messageOptions
);
return this.invoiceMail.formatInvoiceMailOptions(
tenantId,
saleInvoiceId,
parsedMessageOptions
);
}
/** /**
* Triggers the mail invoice. * Triggers the mail invoice.
* @param {number} tenantId * @param {number} tenantId
@@ -59,21 +91,11 @@ export class SendSaleInvoiceMail {
saleInvoiceId: number, saleInvoiceId: number,
messageOptions: SendInvoiceMailDTO messageOptions: SendInvoiceMailDTO
) { ) {
const defaultMessageOptions = await this.invoiceMail.getInvoiceMailOptions( const formattedMessageOptions = await this.getFormattedMailOptions(
tenantId, tenantId,
saleInvoiceId saleInvoiceId,
);
// Merges message options with default options and parses the options values.
const parsedMessageOptions = mergeAndValidateMailOptions(
defaultMessageOptions,
messageOptions messageOptions
); );
const formattedMessageOptions =
await this.invoiceMail.formatInvoiceMailOptions(
tenantId,
saleInvoiceId,
parsedMessageOptions
);
const mail = new Mail() const mail = new Mail()
.setSubject(formattedMessageOptions.subject) .setSubject(formattedMessageOptions.subject)
.setTo(formattedMessageOptions.to) .setTo(formattedMessageOptions.to)

View File

@@ -27,7 +27,7 @@ export const transformInvoiceToPdfTemplate = (
total: invoice.totalFormatted, total: invoice.totalFormatted,
subtotal: invoice.subtotalFormatted, subtotal: invoice.subtotalFormatted,
paymentMade: invoice.paymentAmountFormatted, paymentMade: invoice.paymentAmountFormatted,
balanceDue: invoice.balanceAmountFormatted, dueAmount: invoice.dueAmountFormatted,
termsConditions: invoice.termsConditions, termsConditions: invoice.termsConditions,
statement: invoice.invoiceMessage, statement: invoice.invoiceMessage,

View File

@@ -112,17 +112,20 @@ export class SendPaymentReceiveMailNotification {
}; };
/** /**
* Triggers the mail invoice. * Retrieves the formatted mail options of the given payment receive.
* @param {number} tenantId * @param {number} tenantId
* @param {number} saleInvoiceId * @param {number} paymentReceiveId
* @param {SendInvoiceMailDTO} messageDTO * @param {SendInvoiceMailDTO} messageDTO
* @returns {Promise<void>} * @returns {Promise<PaymentReceiveMailOpts>}
*/ */
public async sendMail( public getFormattedMailOptions = async (
tenantId: number, tenantId: number,
paymentReceiveId: number, paymentReceiveId: number,
messageDTO: SendInvoiceMailDTO messageDTO: SendInvoiceMailDTO
): Promise<void> { ) => {
const formatterArgs = await this.textFormatter(tenantId, paymentReceiveId);
// Default message options.
const defaultMessageOpts = await this.getMailOptions( const defaultMessageOpts = await this.getMailOptions(
tenantId, tenantId,
paymentReceiveId paymentReceiveId
@@ -132,17 +135,43 @@ export class SendPaymentReceiveMailNotification {
defaultMessageOpts, defaultMessageOpts,
messageDTO messageDTO
); );
// Formats the message options.
return this.contactMailNotification.formatMailOptions(
tenantId,
parsedMessageOpts,
formatterArgs
);
};
/**
* Triggers the mail invoice.
* @param {number} tenantId
* @param {number} saleInvoiceId - Invoice id.
* @param {SendInvoiceMailDTO} messageDTO - Message options.
* @returns {Promise<void>}
*/
public async sendMail(
tenantId: number,
paymentReceiveId: number,
messageDTO: SendInvoiceMailDTO
): Promise<void> {
// Retrieves the formatted mail options.
const formattedMessageOptions = await this.getFormattedMailOptions(
tenantId,
paymentReceiveId,
messageDTO
);
const mail = new Mail() const mail = new Mail()
.setSubject(parsedMessageOpts.subject) .setSubject(formattedMessageOptions.subject)
.setTo(parsedMessageOpts.to) .setTo(formattedMessageOptions.to)
.setCC(parsedMessageOpts.cc) .setCC(formattedMessageOptions.cc)
.setBCC(parsedMessageOpts.bcc) .setBCC(formattedMessageOptions.bcc)
.setContent(parsedMessageOpts.message); .setContent(formattedMessageOptions.message);
const eventPayload = { const eventPayload = {
tenantId, tenantId,
paymentReceiveId, paymentReceiveId,
messageOptions: parsedMessageOpts, messageOptions: formattedMessageOptions,
}; };
// Triggers `onPaymentReceiveMailSend` event. // Triggers `onPaymentReceiveMailSend` event.
await this.eventPublisher.emitAsync( await this.eventPublisher.emitAsync(

View File

@@ -1,15 +1,16 @@
export const DEFAULT_PAYMENT_MAIL_SUBJECT = 'Payment Received by {CompanyName}'; export const DEFAULT_PAYMENT_MAIL_SUBJECT =
'Payment Received for {Customer Name} from {Company Name}';
export const DEFAULT_PAYMENT_MAIL_CONTENT = ` export const DEFAULT_PAYMENT_MAIL_CONTENT = `
<p>Dear {CustomerName}</p> <p>Dear {Customer Name}</p>
<p>Thank you for your payment. It was a pleasure doing business with you. We look forward to work together again!</p> <p>Thank you for your payment. It was a pleasure doing business with you. We look forward to work together again!</p>
<p> <p>
Payment Date : <strong>{PaymentDate}</strong><br /> Payment Date : <strong>{Payment Date}</strong><br />
Amount : <strong>{PaymentAmount}</strong></br /> Amount : <strong>{Payment Amount}</strong></br />
</p> </p>
<p> <p>
<i>Regards</i><br /> <i>Regards</i><br />
<i>{CompanyName}</i> <i>{Company Name}</i>
</p> </p>
`; `;

View File

@@ -114,6 +114,58 @@ export class SaleReceiptMailNotification {
return transformReceiptToMailDataArgs(receipt); return transformReceiptToMailDataArgs(receipt);
}; };
/**
* Formats the mail options of the given sale receipt.
* @param {number} tenantId
* @param {number} receiptId
* @param {SaleReceiptMailOpts} mailOptions
* @returns {Promise<SaleReceiptMailOpts>}
*/
public async formatEstimateMailOptions(
tenantId: number,
receiptId: number,
mailOptions: SaleReceiptMailOpts
): Promise<SaleReceiptMailOpts> {
const formatterArgs = await this.textFormatterArgs(tenantId, receiptId);
const formattedOptions =
(await this.contactMailNotification.formatMailOptions(
tenantId,
mailOptions,
formatterArgs
)) as SaleReceiptMailOpts;
return formattedOptions;
}
/**
* Retrieves the formatted mail options of the given sale receipt.
* @param {number} tenantId
* @param {number} saleReceiptId
* @param {SaleReceiptMailOptsDTO} messageOpts
* @returns {Promise<SaleReceiptMailOpts>}
*/
public getFormatMailOptions = async (
tenantId: number,
saleReceiptId: number,
messageOpts: SaleReceiptMailOptsDTO
): Promise<SaleReceiptMailOpts> => {
const defaultMessageOptions = await this.getMailOptions(
tenantId,
saleReceiptId
);
// Merges message opts with default options.
const parsedMessageOpts = mergeAndValidateMailOptions(
defaultMessageOptions,
messageOpts
) as SaleReceiptMailOpts;
// Formats the message options.
return this.formatEstimateMailOptions(
tenantId,
saleReceiptId,
parsedMessageOpts
);
};
/** /**
* Triggers the mail notification of the given sale receipt. * Triggers the mail notification of the given sale receipt.
* @param {number} tenantId - Tenant id. * @param {number} tenantId - Tenant id.
@@ -126,25 +178,21 @@ export class SaleReceiptMailNotification {
saleReceiptId: number, saleReceiptId: number,
messageOpts: SaleReceiptMailOptsDTO messageOpts: SaleReceiptMailOptsDTO
) { ) {
const defaultMessageOptions = await this.getMailOptions( // Formats the message options.
const formattedMessageOptions = await this.getFormatMailOptions(
tenantId, tenantId,
saleReceiptId saleReceiptId,
);
// Merges message opts with default options.
const parsedMessageOpts = mergeAndValidateMailOptions(
defaultMessageOptions,
messageOpts messageOpts
) as SaleReceiptMailOpts; );
const mail = new Mail() const mail = new Mail()
.setSubject(parsedMessageOpts.subject) .setSubject(formattedMessageOptions.subject)
.setTo(parsedMessageOpts.to) .setTo(formattedMessageOptions.to)
.setCC(parsedMessageOpts.cc) .setCC(formattedMessageOptions.cc)
.setBCC(parsedMessageOpts.bcc) .setBCC(formattedMessageOptions.bcc)
.setContent(parsedMessageOpts.message); .setContent(formattedMessageOptions.message);
// Attaches the receipt pdf document. // Attaches the receipt pdf document.
if (parsedMessageOpts.attachReceipt) { if (formattedMessageOptions.attachReceipt) {
// Retrieves document buffer of the receipt pdf document. // Retrieves document buffer of the receipt pdf document.
const [receiptPdfBuffer, filename] = const [receiptPdfBuffer, filename] =
await this.receiptPdfService.saleReceiptPdf(tenantId, saleReceiptId); await this.receiptPdfService.saleReceiptPdf(tenantId, saleReceiptId);

View File

@@ -75,7 +75,7 @@ function LoginFooterLinks() {
)} )}
<AuthFooterLink> <AuthFooterLink>
<Link to={'/auth/send_reset_password'}> <Link to={'/auth/send_reset_password'}>
<T id={'forget_my_password'} /> <T id={'forgot_my_password'} />
</Link> </Link>
</AuthFooterLink> </AuthFooterLink>
</AuthFooterLinks> </AuthFooterLinks>

View File

@@ -99,7 +99,7 @@ function RegisterFooterLinks() {
<AuthFooterLink> <AuthFooterLink>
<Link to={'/auth/send_reset_password'}> <Link to={'/auth/send_reset_password'}>
<T id={'forget_my_password'} /> <T id={'forgot_my_password'} />
</Link> </Link>
</AuthFooterLink> </AuthFooterLink>
</AuthFooterLinks> </AuthFooterLinks>

View File

@@ -48,6 +48,7 @@ export const useBrandingTemplateFormInitialValues = <
const brandingAttributes = { const brandingAttributes = {
templateName: pdfTemplate?.templateName, templateName: pdfTemplate?.templateName,
companyLogoUri: pdfTemplate?.companyLogoUri,
...pdfTemplate?.attributes, ...pdfTemplate?.attributes,
}; };
return { return {
@@ -56,14 +57,16 @@ export const useBrandingTemplateFormInitialValues = <
}; };
}; };
export const useBrandingState = (state?: Partial<BrandingState>): BrandingState => { export const useBrandingState = (
state?: Partial<BrandingState>,
): BrandingState => {
const { brandingTemplateState } = useBrandingTemplateBoot(); const { brandingTemplateState } = useBrandingTemplateBoot();
return { return {
...brandingTemplateState, ...brandingTemplateState,
...state ...state,
} };
} };
export const getCustomizeDrawerNameFromResource = (resource: string) => { export const getCustomizeDrawerNameFromResource = (resource: string) => {
const pairs = { const pairs = {

View File

@@ -32,7 +32,7 @@ function EstimateMailDialogFormRoot({
closeDialog, closeDialog,
}) { }) {
const { mutateAsync: sendEstimateMail } = useSendSaleEstimateMail(); const { mutateAsync: sendEstimateMail } = useSendSaleEstimateMail();
const { mailOptions, saleEstimateId, redirectToEstimatesList } = const { mailOptions, saleEstimateId, } =
useEstimateMailDialogBoot(); useEstimateMailDialogBoot();
const initialValues = transformMailFormToInitialValues( const initialValues = transformMailFormToInitialValues(

View File

@@ -25,8 +25,8 @@ export function EstimateMailDialogFormContent({
<Form> <Form>
<div className={Classes.DIALOG_BODY}> <div className={Classes.DIALOG_BODY}>
<MailNotificationForm <MailNotificationForm
fromAddresses={mailOptions.from_addresses} fromAddresses={mailOptions.from_options}
toAddresses={mailOptions.to_addresses} toAddresses={mailOptions.to_options}
/> />
<AttachFormGroup name={'attachEstimate'} inline> <AttachFormGroup name={'attachEstimate'} inline>
<FSwitch name={'attachEstimate'} label={'Attach Estimate'} /> <FSwitch name={'attachEstimate'} label={'Attach Estimate'} />

View File

@@ -22,6 +22,7 @@ interface PaymentMailDialogBootProps {
*/ */
function PaymentMailDialogBoot({ function PaymentMailDialogBoot({
paymentReceiveId, paymentReceiveId,
redirectToPaymentsList,
...props ...props
}: PaymentMailDialogBootProps) { }: PaymentMailDialogBootProps) {
const { data: mailOptions, isLoading: isMailOptionsLoading } = const { data: mailOptions, isLoading: isMailOptionsLoading } =

View File

@@ -25,8 +25,8 @@ export function PaymentMailDialogFormContent({
<Form> <Form>
<div className={Classes.DIALOG_BODY}> <div className={Classes.DIALOG_BODY}>
<MailNotificationForm <MailNotificationForm
fromAddresses={mailOptions.from_addresses} fromAddresses={mailOptions.from_options}
toAddresses={mailOptions.to_addresses} toAddresses={mailOptions.to_options}
/> />
<AttachFormGroup name={'attachPayment'} inline> <AttachFormGroup name={'attachPayment'} inline>
<FSwitch name={'attachPayment'} label={'Attach Payment'} /> <FSwitch name={'attachPayment'} label={'Attach Payment'} />

View File

@@ -25,8 +25,8 @@ export function ReceiptMailDialogFormContent({
<Form> <Form>
<div className={Classes.DIALOG_BODY}> <div className={Classes.DIALOG_BODY}>
<MailNotificationForm <MailNotificationForm
fromAddresses={mailOptions.from_addresses} fromAddresses={mailOptions.from_options}
toAddresses={mailOptions.to_addresses} toAddresses={mailOptions.to_options}
/> />
<AttachFormGroup name={'attachReceipt:'} inline> <AttachFormGroup name={'attachReceipt:'} inline>
<FSwitch name={'attachReceipt:'} label={'Attach Receipt'} /> <FSwitch name={'attachReceipt:'} label={'Attach Receipt'} />

View File

@@ -66,7 +66,7 @@ export function MailNotificationForm({
</FFormGroup> </FFormGroup>
</HeaderBox> </HeaderBox>
<MailMessageEditor name={'body'} /> <MailMessageEditor name={'message'} />
</Box> </Box>
); );
} }

View File

@@ -5,7 +5,7 @@ export const initialMailNotificationValues = {
from: [], from: [],
to: [], to: [],
subject: '', subject: '',
body: '', message: '',
}; };
export interface MailNotificationFormValues { export interface MailNotificationFormValues {
@@ -26,7 +26,7 @@ export const transformMailFormToRequest = (
}; };
/** /**
* Transformes the mail options response values to form initial values. * Transforms the mail options response values to form initial values.
* @param {any} mailOptions * @param {any} mailOptions
* @param {MailNotificationFormValues} initialValues * @param {MailNotificationFormValues} initialValues
* @returns {MailNotificationFormValues} * @returns {MailNotificationFormValues}

View File

@@ -38,6 +38,7 @@ export interface GetPdfTemplateValues {}
export interface GetPdfTemplateResponse { export interface GetPdfTemplateResponse {
templateName: string; templateName: string;
companyLogoUri?: string | null;
attributes: Record<string, any>; attributes: Record<string, any>;
predefined: boolean; predefined: boolean;
default: boolean; default: boolean;

View File

@@ -18,7 +18,7 @@
"to_date": "الى تاريخ", "to_date": "الى تاريخ",
"report_date_range": "تقرير نطاق التاريخ", "report_date_range": "تقرير نطاق التاريخ",
"log_in": "تسجيل الدخول", "log_in": "تسجيل الدخول",
"forget_my_password": "نسيت كلمة المرور الخاصة بي", "forgot_my_password": "نسيت كلمة المرور الخاصة بي",
"keep_me_logged_in": "تذكرني", "keep_me_logged_in": "تذكرني",
"dont_have_an_account": "ليس لديك حساب؟", "dont_have_an_account": "ليس لديك حساب؟",
"sign_up": "تسجيل", "sign_up": "تسجيل",
@@ -1570,8 +1570,8 @@
"refund": "استرجاع", "refund": "استرجاع",
"landed_cost.dialog.label_select_transaction": "حدد المعاملة ", "landed_cost.dialog.label_select_transaction": "حدد المعاملة ",
"landed_cost.dialog.label_select_transaction_entry": "حدد سطر المعاملة ", "landed_cost.dialog.label_select_transaction_entry": "حدد سطر المعاملة ",
"landed_cost.dialog.label_unallocated_cost_amount":"قيمة التكلفة غير المحملة:", "landed_cost.dialog.label_unallocated_cost_amount": "قيمة التكلفة غير المحملة:",
"landed_cost.error.the_total_located_cost_is_bigger_than_the_transaction_line":"إجمالي قيمة التكلفة المحملة أكبر من قيمة سطر المعاملة.", "landed_cost.error.the_total_located_cost_is_bigger_than_the_transaction_line": "إجمالي قيمة التكلفة المحملة أكبر من قيمة سطر المعاملة.",
"landed_cost.once_your_delete_this_located_landed_cost": "بمجرد حذف معاملة تحميل التكلفة ، لن تتمكن من استعادتها لاحقًا ، هل أنت متأكد من أنك تريد حذف هذه المعاملة؟", "landed_cost.once_your_delete_this_located_landed_cost": "بمجرد حذف معاملة تحميل التكلفة ، لن تتمكن من استعادتها لاحقًا ، هل أنت متأكد من أنك تريد حذف هذه المعاملة؟",
"refund_credit_note.dialog.label": "استرجاع اموال", "refund_credit_note.dialog.label": "استرجاع اموال",
"refund_credit_note.dialog.success_message": "تم انشاء معاملة استرجاع الاموال لإشعار الدائن بنجاح.", "refund_credit_note.dialog.success_message": "تم انشاء معاملة استرجاع الاموال لإشعار الدائن بنجاح.",

View File

@@ -16,7 +16,7 @@
"to_date": "To date", "to_date": "To date",
"report_date_range": "Report date range", "report_date_range": "Report date range",
"log_in": "Log in", "log_in": "Log in",
"forget_my_password": "Forget my password", "forgot_my_password": "Forgot my password",
"keep_me_logged_in": "Keep me logged in", "keep_me_logged_in": "Keep me logged in",
"dont_have_an_account": "Don't have an account?", "dont_have_an_account": "Don't have an account?",
"sign_up": "Sign up", "sign_up": "Sign up",

View File

@@ -0,0 +1,6 @@
// @ts-nocheck
export default {
"login": "Ingresar",
"reset_password": "Resetear contraseña",
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,61 @@
// @ts-nocheck
import printValue from '../printValue';
export const locale = {
mixed: {
default: '${path} es inválido',
required: '${path} es un campo requerido',
oneOf: '${path} debe ser uno de los siguientes valores: ${values}',
notOneOf: '${path} no debe ser uno de los siguientes valores: ${values}',
notType: ({ path, type, value, originalValue }) => {
let isCast = originalValue != null && originalValue !== value;
let msg =
`${path} debe ser de tipo \`${type}\`, ` +
`pero el valor final fue: \`${printValue(value, true)}\`` +
(isCast
? ` (convertido del valor \`${printValue(originalValue, true)}\`).`
: '.');
if (value === null) {
msg += `\n Si "null" se pretende como un valor vacío, asegúrate de marcar el esquema como \`.nullable()\``;
}
return msg;
},
defined: '${path} debe estar definido',
},
string: {
length: '${path} debe tener exactamente ${length} caracteres',
min: '${path} debe tener al menos ${min} caracteres',
max: '${path} debe tener como máximo ${max} caracteres',
matches: '${path} debe coincidir con lo siguiente: "${regex}"',
email: '${path} debe ser un correo electrónico válido',
url: '${path} debe ser una URL válida',
trim: '${path} debe ser una cadena recortada',
lowercase: '${path} debe ser una cadena en minúsculas',
uppercase: '${path} debe ser una cadena en mayúsculas',
},
number: {
min: '${path} debe ser mayor o igual a ${min}',
max: '${path} debe ser menor o igual a ${max}',
lessThan: '${path} debe ser menor que ${less}',
moreThan: '${path} debe ser mayor que ${more}',
notEqual: '${path} no debe ser igual a ${notEqual}',
positive: '${path} debe ser un número positivo',
negative: '${path} debe ser un número negativo',
integer: '${path} debe ser un número entero',
},
date: {
min: '${path} debe ser posterior a ${min}',
max: '${path} debe ser anterior a ${max}',
},
boolean: {},
object: {
noUnknown:
'${path} no puede tener claves no especificadas en la forma del objeto',
},
array: {
min: '${path} debe tener al menos ${min} elementos',
max: '${path} debe tener como máximo ${max} elementos',
},
};

View File

@@ -0,0 +1,7 @@
// @ts-nocheck
export default {
"login": "Logga in",
"reset_password": "Återställ lösenord",
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,61 @@
// @ts-nocheck
import printValue from '../printValue';
export const locale = {
mixed: {
default: '${path} är inkorrekt',
required: '${path} är ett obligatoriskt fält ',
oneOf: '${path} måste vara en av följande värden: ${values}',
notOneOf: '${path} kan inte vara en av följande värden: ${values}',
notType: ({ path, type, value, originalValue }) => {
let isCast = originalValue != null && originalValue !== value;
let msg =
`${path} måste ha typen \`${type}\`, ` +
`men det slutliga värdet var: \`${printValue(value, true)}\`` +
(isCast
? ` (gjord av värdet \`${printValue(originalValue, true)}\`).`
: '.');
if (value === null) {
msg += `\n Om ”null” är avsett som ett tomt värde ska du se till att markera schemat som \`.nullable()\``;
}
return msg;
},
defined: '${path} måste vara definierad',
},
string: {
length: '${path} måste vara exakt ${length} tecken',
min: '${path} måste vara som minst ${min} tecken',
max: '${path} måste vara som mest ${max} tecken',
matches: '${path} måste matcha det följande: "${regex}"',
email: '${path} måste vara en giltig e-post',
url: '${path} måste vara en giltig URL',
trim: '${path} måste vara en trimmad sträng',
lowercase: '${path} måste vara en sträng med små bokstäver',
uppercase: '${path} måste vara en sträng med stora bokstäver',
},
number: {
min: '${path} måste vara större eller lika med ${min}',
max: '${path} måste vara mindre eller lika med ${max}',
lessThan: '${path} måste vara mindre än ${less}',
moreThan: '${path} måste vara större än ${more}',
notEqual: '${path} får inte vara lika med ${notEqual}',
positive: '${path} får inte vara ett positivt nummer',
negative: '${path} får inte vara ett negativt nummer',
integer: '${path} måste vara ett nummer',
},
date: {
min: '${path} fältet måste vara senare än ${min}',
max: '${path} fältet måste vara tidigare än ${max}',
},
boolean: {},
object: {
noUnknown:
'${path}-fältet kan inte ha nycklar som inte anges i objektform',
},
array: {
min: '${path}-fältet måste ha minst ${min} objekt',
max: '${path} fältet måste ha mindre än eller lika med ${max} objekt',
},
};

View File

@@ -1,3 +1,4 @@
import { Meta } from '@storybook/react'; import { Meta } from '@storybook/react';
import { StoryFn } from '@storybook/react'; import { StoryFn } from '@storybook/react';
import { import {
@@ -15,22 +16,3 @@ export default meta;
const Template: StoryFn<typeof InvoicePaymentEmail> = ( const Template: StoryFn<typeof InvoicePaymentEmail> = (
args: InvoicePaymentEmailProps args: InvoicePaymentEmailProps
) => <InvoicePaymentEmail {...args} />; ) => <InvoicePaymentEmail {...args} />;
export const PreviewInvoicePaymentMail = Template.bind({});
PreviewInvoicePaymentMail.args = {
preview: 'Preview text',
companyName: 'ABC Company',
companyLogoUri: 'https://example.com/logo.png',
invoiceAmount: '100.00',
dueDate: '2022-12-31',
invoiceMessage: 'Thank you for your purchase!',
invoiceNumber: 'INV-001',
dueAmount: '100.00',
total: '100.00',
viewInvoiceButtonUrl: 'https://example.com/invoice',
items: [
{ label: 'Item 1', quantity: '1', rate: '50.00' },
{ label: 'Item 2', quantity: '2', rate: '25.00' },
],
};

View File

@@ -4,7 +4,6 @@
"scripts": { "scripts": {
"build": "webpack --config webpack.config.js", "build": "webpack --config webpack.config.js",
"lint": "eslint .", "lint": "eslint .",
"preview": "vite preview",
"storybook:dev": "storybook dev -p 6006", "storybook:dev": "storybook dev -p 6006",
"storybook:build": "storybook build" "storybook:build": "storybook build"
}, },

View File

@@ -71,10 +71,12 @@ export interface InvoicePaperTemplateProps extends PaperTemplateProps {
totalLabel?: string; totalLabel?: string;
total?: string; total?: string;
// Discount
showDiscount?: boolean; showDiscount?: boolean;
discountLabel?: string; discountLabel?: string;
discount?: string; discount?: string;
// Subtotal
showSubtotal?: boolean; showSubtotal?: boolean;
subtotalLabel?: string; subtotalLabel?: string;
subtotal?: string; subtotal?: string;
@@ -85,10 +87,10 @@ export interface InvoicePaperTemplateProps extends PaperTemplateProps {
showTaxes?: boolean; showTaxes?: boolean;
// Due Amount
showDueAmount?: boolean; showDueAmount?: boolean;
showBalanceDue?: boolean; dueAmountLabel?: string;
balanceDueLabel?: string; dueAmount?: string;
balanceDue?: string;
// Footer // Footer
termsConditionsLabel?: string; termsConditionsLabel?: string;
@@ -144,7 +146,7 @@ export function InvoicePaperTemplate({
subtotalLabel = 'Subtotal', subtotalLabel = 'Subtotal',
discountLabel = 'Discount', discountLabel = 'Discount',
paymentMadeLabel = 'Payment Made', paymentMadeLabel = 'Payment Made',
balanceDueLabel = 'Balance Due', dueAmountLabel = 'Balance Due',
// Totals // Totals
showTotal = true, showTotal = true,
@@ -153,13 +155,12 @@ export function InvoicePaperTemplate({
showTaxes = true, showTaxes = true,
showPaymentMade = true, showPaymentMade = true,
showDueAmount = true, showDueAmount = true,
showBalanceDue = true,
total = '$662.75', total = '$662.75',
subtotal = '630.00', subtotal = '630.00',
discount = '0.00', discount = '0.00',
paymentMade = '100.00', paymentMade = '100.00',
balanceDue = '$562.75', dueAmount = '$562.75',
// Footer paragraphs. // Footer paragraphs.
termsConditionsLabel = 'Terms & Conditions', termsConditionsLabel = 'Terms & Conditions',
@@ -297,10 +298,10 @@ export function InvoicePaperTemplate({
amount={paymentMade} amount={paymentMade}
/> />
)} )}
{showBalanceDue && ( {showDueAmount && (
<PaperTemplate.TotalLine <PaperTemplate.TotalLine
label={balanceDueLabel} label={dueAmountLabel}
amount={balanceDue} amount={dueAmount}
border={PaperTemplateTotalBorder.Dark} border={PaperTemplateTotalBorder.Dark}
style={{ fontWeight: 500 }} style={{ fontWeight: 500 }}
/> />

View File

@@ -19,7 +19,6 @@
"lib": ["ESNext", "DOM", "DOM.Iterable"], "lib": ["ESNext", "DOM", "DOM.Iterable"],
"module": "ESNext", "module": "ESNext",
"target": "ESNext", "target": "ESNext",
"types": ["vitest/globals"],
"resolveJsonModule": true, "resolveJsonModule": true,
"outDir": "dist", "outDir": "dist",
}, },

View File

@@ -1,61 +0,0 @@
import react from '@vitejs/plugin-react';
import path from 'node:path';
import { defineConfig } from 'vitest/config';
import dts from 'vite-plugin-dts';
import tailwindcss from 'tailwindcss';
import { UserConfigExport } from 'vite';
import { name } from './package.json';
const app = async (): Promise<UserConfigExport> => {
/**
* Removes everything before the last
* @octocat/library-repo -> library-repo
* vite-component-library-template -> vite-component-library-template
*/
const formattedName = name.match(/[^/]+$/)?.[0] ?? name;
return defineConfig({
define: {
isBrowser: 'false', // This will replace isBrowser with false in the bundled code
},
ssr: {
noExternal: true,
},
plugins: [
react(),
dts({
insertTypesEntry: true,
}),
],
css: {
postcss: {
plugins: [tailwindcss],
},
},
build: {
lib: {
entry: path.resolve(__dirname, 'src/lib/main.ts'),
name: formattedName,
formats: ['es', 'umd'],
fileName: (format: string) => `${formattedName}.${format}.js`,
},
rollupOptions: {
// external: ['react', 'react/jsx-runtime', 'react-dom', 'tailwindcss'],
// output: {
// globals: {
// react: 'React',
// 'react/jsx-runtime': 'react/jsx-runtime',
// 'react-dom': 'ReactDOM',
// tailwindcss: 'tailwindcss',
// },
// },
},
},
test: {
globals: true,
environment: 'jsdom',
},
});
};
// https://vitejs.dev/config/
export default app;