From c8e7a2c7d905d5b5dce2f5c3ff9e56340b59280e Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Wed, 24 Jan 2024 00:00:15 +0200 Subject: [PATCH 01/31] feat(server): convert invoice status after sending mail notification --- packages/server/src/interfaces/SaleInvoice.ts | 12 +++++ packages/server/src/loaders/eventEmitter.ts | 2 + .../Sales/Invoices/DeliverSaleInvoice.ts | 4 +- .../Sales/Invoices/SendSaleInvoiceMail.ts | 36 ++++++++++++-- .../Sales/Invoices/SendSaleInvoiceMailJob.ts | 4 +- .../Invoices/SendSaleInvoiceMailReminder.ts | 23 ++++++++- ...InvoiceChangeStatusOnMailSentSubscriber.ts | 49 +++++++++++++++++++ packages/server/src/subscribers/events.ts | 13 +++-- 8 files changed, 129 insertions(+), 14 deletions(-) create mode 100644 packages/server/src/services/Sales/Invoices/subscribers/InvoiceChangeStatusOnMailSentSubscriber.ts diff --git a/packages/server/src/interfaces/SaleInvoice.ts b/packages/server/src/interfaces/SaleInvoice.ts index 394319e86..61594306a 100644 --- a/packages/server/src/interfaces/SaleInvoice.ts +++ b/packages/server/src/interfaces/SaleInvoice.ts @@ -201,3 +201,15 @@ export interface ISaleInvoiceNotifyPayload { saleInvoiceId: number; messageDTO: SendInvoiceMailDTO; } + +export interface ISaleInvoiceMailSend { + tenantId: number; + saleInvoiceId: number; + messageOptions: SendInvoiceMailDTO; +} + +export interface ISaleInvoiceMailSent { + tenantId: number; + saleInvoiceId: number; + messageOptions: SendInvoiceMailDTO; +} diff --git a/packages/server/src/loaders/eventEmitter.ts b/packages/server/src/loaders/eventEmitter.ts index fa1942b72..5e052f376 100644 --- a/packages/server/src/loaders/eventEmitter.ts +++ b/packages/server/src/loaders/eventEmitter.ts @@ -84,6 +84,7 @@ import { WriteInvoiceTaxTransactionsSubscriber } from '@/services/TaxRates/subsc import { BillTaxRateValidateSubscriber } from '@/services/TaxRates/subscribers/BillTaxRateValidateSubscriber'; import { WriteBillTaxTransactionsSubscriber } from '@/services/TaxRates/subscribers/WriteBillTaxTransactionsSubscriber'; import { SyncItemTaxRateOnEditTaxSubscriber } from '@/services/TaxRates/SyncItemTaxRateOnEditTaxSubscriber'; +import { InvoiceChangeStatusOnMailSentSubscriber } from '@/services/Sales/Invoices/subscribers/InvoiceChangeStatusOnMailSentSubscriber'; export default () => { return new EventPublisher(); @@ -152,6 +153,7 @@ export const susbcribers = () => { // #Invoices InvoicePaymentGLRewriteSubscriber, InvoiceCostGLEntriesSubscriber, + InvoiceChangeStatusOnMailSentSubscriber, BillPaymentsGLEntriesRewriteSubscriber, diff --git a/packages/server/src/services/Sales/Invoices/DeliverSaleInvoice.ts b/packages/server/src/services/Sales/Invoices/DeliverSaleInvoice.ts index 30ba635a6..f5500a03a 100644 --- a/packages/server/src/services/Sales/Invoices/DeliverSaleInvoice.ts +++ b/packages/server/src/services/Sales/Invoices/DeliverSaleInvoice.ts @@ -4,7 +4,6 @@ import { ServiceError } from '@/exceptions'; import { ISaleInvoiceDeliveringPayload, ISaleInvoiceEventDeliveredPayload, - ISystemUser, } from '@/interfaces'; import { ERRORS } from './constants'; import { Inject, Service } from 'typedi'; @@ -36,8 +35,7 @@ export class DeliverSaleInvoice { */ public async deliverSaleInvoice( tenantId: number, - saleInvoiceId: number, - authorizedUser: ISystemUser + saleInvoiceId: number ): Promise { const { SaleInvoice } = this.tenancy.models(tenantId); diff --git a/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMail.ts b/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMail.ts index 05db4f73e..eff8b2603 100644 --- a/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMail.ts +++ b/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMail.ts @@ -1,6 +1,6 @@ import { Inject, Service } from 'typedi'; import Mail from '@/lib/Mail'; -import { SendInvoiceMailDTO } from '@/interfaces'; +import { ISaleInvoiceMailSend, SendInvoiceMailDTO } from '@/interfaces'; import { SaleInvoicePdf } from './SaleInvoicePdf'; import { SendSaleInvoiceMailCommon } from './SendInvoiceInvoiceMailCommon'; import { @@ -8,6 +8,8 @@ import { DEFAULT_INVOICE_MAIL_SUBJECT, } from './constants'; import { parseAndValidateMailOptions } from '@/services/MailNotification/utils'; +import events from '@/subscribers/events'; +import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; @Service() export class SendSaleInvoiceMail { @@ -20,6 +22,9 @@ export class SendSaleInvoiceMail { @Inject('agenda') private agenda: any; + @Inject() + private eventPublisher: EventPublisher; + /** * Sends the invoice mail of the given sale invoice. * @param {number} tenantId @@ -29,14 +34,21 @@ export class SendSaleInvoiceMail { public async triggerMail( tenantId: number, saleInvoiceId: number, - messageDTO: SendInvoiceMailDTO + messageOptions: SendInvoiceMailDTO ) { const payload = { tenantId, saleInvoiceId, - messageDTO, + messageOptions, }; await this.agenda.now('sale-invoice-mail-send', payload); + + // Triggers the event `onSaleInvoicePreMailSend`. + await this.eventPublisher.emitAsync(events.saleInvoice.onPreMailSend, { + tenantId, + saleInvoiceId, + messageOptions, + } as ISaleInvoiceMailSend); } /** @@ -64,7 +76,7 @@ export class SendSaleInvoiceMail { public async sendMail( tenantId: number, saleInvoiceId: number, - messageDTO: SendInvoiceMailDTO + messageOptions: SendInvoiceMailDTO ) { const defaultMessageOpts = await this.getMailOption( tenantId, @@ -73,7 +85,7 @@ export class SendSaleInvoiceMail { // Merge message opts with default options and validate the incoming options. const messageOpts = parseAndValidateMailOptions( defaultMessageOpts, - messageDTO + messageOptions ); const mail = new Mail() .setSubject(messageOpts.subject) @@ -90,6 +102,20 @@ export class SendSaleInvoiceMail { { filename: 'invoice.pdf', content: invoicePdfBuffer }, ]); } + // Triggers the event `onSaleInvoiceSend`. + await this.eventPublisher.emitAsync(events.saleInvoice.onMailSend, { + tenantId, + saleInvoiceId, + messageOptions, + } as ISaleInvoiceMailSend); + await mail.send(); + + // Triggers the event `onSaleInvoiceSend`. + await this.eventPublisher.emitAsync(events.saleInvoice.onMailSent, { + tenantId, + saleInvoiceId, + messageOptions, + } as ISaleInvoiceMailSend); } } diff --git a/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMailJob.ts b/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMailJob.ts index 3c1e49a6c..9de941f5f 100644 --- a/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMailJob.ts +++ b/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMailJob.ts @@ -19,11 +19,11 @@ export class SendSaleInvoiceMailJob { * Triggers sending invoice mail. */ private handler = async (job, done: Function) => { - const { tenantId, saleInvoiceId, messageDTO } = job.attrs.data; + const { tenantId, saleInvoiceId, messageOptions } = job.attrs.data; const sendInvoiceMail = Container.get(SendSaleInvoiceMail); try { - await sendInvoiceMail.sendMail(tenantId, saleInvoiceId, messageDTO); + await sendInvoiceMail.sendMail(tenantId, saleInvoiceId, messageOptions); done(); } catch (error) { console.log(error); diff --git a/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMailReminder.ts b/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMailReminder.ts index f16db0172..7829a3bf2 100644 --- a/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMailReminder.ts +++ b/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMailReminder.ts @@ -1,5 +1,9 @@ import { Inject, Service } from 'typedi'; -import { SendInvoiceMailDTO } from '@/interfaces'; +import { + ISaleInvoiceMailSend, + ISaleInvoiceMailSent, + SendInvoiceMailDTO, +} from '@/interfaces'; import Mail from '@/lib/Mail'; import { SaleInvoicePdf } from './SaleInvoicePdf'; import { SendSaleInvoiceMailCommon } from './SendInvoiceInvoiceMailCommon'; @@ -8,6 +12,8 @@ import { DEFAULT_INVOICE_REMINDER_MAIL_SUBJECT, } from './constants'; import { parseAndValidateMailOptions } from '@/services/MailNotification/utils'; +import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; +import events from '@/subscribers/events'; @Service() export class SendInvoiceMailReminder { @@ -20,6 +26,9 @@ export class SendInvoiceMailReminder { @Inject() private invoiceCommonMail: SendSaleInvoiceMailCommon; + @Inject() + private eventPublisher: EventPublisher; + /** * Triggers the reminder mail of the given sale invoice. * @param {number} tenantId @@ -86,6 +95,18 @@ export class SendInvoiceMailReminder { { filename: 'invoice.pdf', content: invoicePdfBuffer }, ]); } + // Triggers the event `onSaleInvoiceSend`. + await this.eventPublisher.emitAsync(events.saleInvoice.onMailReminderSend, { + saleInvoiceId, + messageOptions, + } as ISaleInvoiceMailSend); + await mail.send(); + + // Triggers the event `onSaleInvoiceSent`. + await this.eventPublisher.emitAsync(events.saleInvoice.onMailReminderSent, { + saleInvoiceId, + messageOptions, + } as ISaleInvoiceMailSent); } } diff --git a/packages/server/src/services/Sales/Invoices/subscribers/InvoiceChangeStatusOnMailSentSubscriber.ts b/packages/server/src/services/Sales/Invoices/subscribers/InvoiceChangeStatusOnMailSentSubscriber.ts new file mode 100644 index 000000000..fc4392777 --- /dev/null +++ b/packages/server/src/services/Sales/Invoices/subscribers/InvoiceChangeStatusOnMailSentSubscriber.ts @@ -0,0 +1,49 @@ +import { Inject, Service } from 'typedi'; +import events from '@/subscribers/events'; +import { ISaleInvoiceMailSent } from '@/interfaces'; +import { DeliverSaleInvoice } from '../DeliverSaleInvoice'; +import { ServiceError } from '@/exceptions'; +import { ERRORS } from '../constants'; + +@Service() +export class InvoiceChangeStatusOnMailSentSubscriber { + @Inject() + private markInvoiceDelivedService: DeliverSaleInvoice; + + /** + * Attaches events. + */ + public attach(bus) { + bus.subscribe(events.saleInvoice.onPreMailSend, this.markInvoiceDelivered); + bus.subscribe( + events.saleInvoice.onMailReminderSent, + this.markInvoiceDelivered + ); + } + + /** + * Marks the invoice delivered once the invoice mail sent. + * @param {ISaleInvoiceMailSent} + * @returns {Promise} + */ + private markInvoiceDelivered = async ({ + tenantId, + saleInvoiceId, + messageOptions, + }: ISaleInvoiceMailSent) => { + try { + await this.markInvoiceDelivedService.deliverSaleInvoice( + tenantId, + saleInvoiceId + ); + } catch (error) { + if ( + error instanceof ServiceError && + error.errorType === ERRORS.SALE_INVOICE_ALREADY_DELIVERED + ) { + } else { + throw error; + } + } + }; +} diff --git a/packages/server/src/subscribers/events.ts b/packages/server/src/subscribers/events.ts index e54f48152..a46c32696 100644 --- a/packages/server/src/subscribers/events.ts +++ b/packages/server/src/subscribers/events.ts @@ -131,7 +131,14 @@ export default { onNotifiedSms: 'onSaleInvoiceNotifiedSms', onNotifyMail: 'onSaleInvoiceNotifyMail', - onNotifyReminderMail: 'onSaleInvoiceNotifyReminderMail' + onNotifyReminderMail: 'onSaleInvoiceNotifyReminderMail', + + onPreMailSend: 'onSaleInvoicePreMailSend', + onMailSend: 'onSaleInvoiceMailSend', + onMailSent: 'onSaleInvoiceMailSent', + + onMailReminderSend: 'onSaleInvoiceMailReminderSend', + onMailReminderSent: 'onSaleInvoiceMailReminderSent', }, /** @@ -164,7 +171,7 @@ export default { onRejecting: 'onSaleEstimateRejecting', onRejected: 'onSaleEstimateRejected', - onNotifyMail: 'onSaleEstimateNotifyMail' + onNotifyMail: 'onSaleEstimateNotifyMail', }, /** @@ -580,6 +587,6 @@ export default { onActivated: 'onTaxRateActivated', onInactivating: 'onTaxRateInactivating', - onInactivated: 'onTaxRateInactivated' + onInactivated: 'onTaxRateInactivated', }, }; From 9d4e7cec9e8d2c627890d1b82ad9130df67ac7ff Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Wed, 24 Jan 2024 20:03:04 +0200 Subject: [PATCH 02/31] feat(webapp): add ability to redirect to list to mail dialogs --- .../EstimateMailDialog/EstimateMailDialog.tsx | 8 +++++++- .../EstimateMailDialog/EstimateMailDialogBoot.tsx | 4 ++++ .../EstimateMailDialogContent.tsx | 11 ++++++++--- .../EstimateMailDialog/EstimateMailDialogForm.tsx | 14 +++++++++++--- .../InvoiceMailDialog/InvoiceMailDialog.tsx | 8 +++++++- .../InvoiceMailDialog/InvoiceMailDialogBoot.tsx | 4 ++++ .../InvoiceMailDialog/InvoiceMailDialogContent.tsx | 9 ++++++++- .../InvoiceMailDialog/InvoiceMailDialogForm.tsx | 10 +++++++++- .../PaymentMailDialog/PaymentMailDialog.tsx | 8 +++++++- .../PaymentMailDialog/PaymentMailDialogBoot.tsx | 4 +++- .../PaymentMailDialog/PaymentMailDialogContent.tsx | 7 ++++++- .../PaymentMailDialog/PaymentMailDialogForm.tsx | 13 +++++++++++-- .../ReceiptMailDialog/ReceiptMailDialog.tsx | 8 +++++++- .../ReceiptMailDialog/ReceiptMailDialogBoot.tsx | 4 ++++ .../ReceiptMailDialog/ReceiptMailDialogContent.tsx | 9 +++++++-- .../ReceiptMailDialog/ReceiptMailDialogForm.tsx | 10 +++++++++- 16 files changed, 112 insertions(+), 19 deletions(-) diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialog.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialog.tsx index 9965db833..a90f0e94a 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialog.tsx +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialog.tsx @@ -13,7 +13,12 @@ const EstimateMailDialogContent = React.lazy( */ function EstimateMailDialog({ dialogName, - payload: { estimateId = null }, + payload: { + estimateId = null, + + // Redirect to the estimates list after mail submitting. + redirectToEstimatesList = false, + }, isOpen, }) { return ( @@ -29,6 +34,7 @@ function EstimateMailDialog({ diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogBoot.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogBoot.tsx index d68afb4c5..65b05a9c1 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogBoot.tsx +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogBoot.tsx @@ -6,12 +6,14 @@ import { DialogContent } from '@/components'; interface EstimateMailDialogBootValues { estimateId: number; mailOptions: any; + redirectToEstimatesList: boolean; } const EstimateMailDialagBoot = createContext(); interface EstimateMailDialogBootProps { estimateId: number; + redirectToEstimatesList?: boolean; children: React.ReactNode; } @@ -20,6 +22,7 @@ interface EstimateMailDialogBootProps { */ function EstimateMailDialogBoot({ estimateId, + redirectToEstimatesList, ...props }: EstimateMailDialogBootProps) { const { data: mailOptions, isLoading: isMailOptionsLoading } = @@ -29,6 +32,7 @@ function EstimateMailDialogBoot({ saleEstimateId: estimateId, mailOptions, isMailOptionsLoading, + redirectToEstimatesList, }; return ( diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogContent.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogContent.tsx index ad67bb048..3eb0af76d 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogContent.tsx +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogContent.tsx @@ -4,14 +4,19 @@ import { EstimateMailDialogForm } from './EstimateMailDialogForm'; interface EstimateMailDialogContentProps { dialogName: string; estimateId: number; + redirectToEstimatesList?: boolean; } export default function EstimateMailDialogContent({ dialogName, estimateId, + redirectToEstimatesList, }: EstimateMailDialogContentProps) { return ( - - + + - ) + ); } diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogForm.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogForm.tsx index f8811cdbb..4c6e7e943 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogForm.tsx +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogForm.tsx @@ -1,6 +1,8 @@ // @ts-nocheck import { Formik } from 'formik'; import * as R from 'ramda'; +import { Intent } from '@blueprintjs/core'; +import { useHistory } from 'react-router-dom'; import { useEstimateMailDialogBoot } from './EstimateMailDialogBoot'; import { DialogsName } from '@/constants/dialogs'; import withDialogActions from '@/containers/Dialog/withDialogActions'; @@ -12,7 +14,6 @@ import { transformMailFormToInitialValues, transformMailFormToRequest, } from '@/containers/SendMailNotification/utils'; -import { Intent } from '@blueprintjs/core'; import { AppToaster } from '@/components'; const initialFormValues = { @@ -29,7 +30,10 @@ function EstimateMailDialogFormRoot({ closeDialog, }) { const { mutateAsync: sendEstimateMail } = useSendSaleEstimateMail(); - const { mailOptions, saleEstimateId } = useEstimateMailDialogBoot(); + const { mailOptions, saleEstimateId, redirectToEstimatesList } = + useEstimateMailDialogBoot(); + + const history = useHistory(); const initialValues = transformMailFormToInitialValues( mailOptions, @@ -48,8 +52,12 @@ function EstimateMailDialogFormRoot({ }); closeDialog(DialogsName.EstimateMail); setSubmitting(false); + + if (redirectToEstimatesList) { + history.push('/estimates'); + } }) - .catch((error) => { + .catch(() => { setSubmitting(false); closeDialog(DialogsName.EstimateMail); AppToaster.show({ diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialog.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialog.tsx index 63430ce10..1b385b83f 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialog.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialog.tsx @@ -13,7 +13,12 @@ const InvoiceMailDialogContent = React.lazy( */ function InvoiceMailDialog({ dialogName, - payload: { invoiceId = null }, + payload: { + invoiceId = null, + + // Redirects to the invoices list. + redirectToInvoicesList = false, + }, isOpen, }) { return ( @@ -29,6 +34,7 @@ function InvoiceMailDialog({ diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialogBoot.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialogBoot.tsx index ae16a0cf2..8c7d5f7e2 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialogBoot.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialogBoot.tsx @@ -6,12 +6,14 @@ import { DialogContent } from '@/components'; interface InvoiceMailDialogBootValues { invoiceId: number; mailOptions: any; + redirectToInvoicesList: boolean; } const InvoiceMailDialagBoot = createContext(); interface InvoiceMailDialogBootProps { invoiceId: number; + redirectToInvoicesList?: boolean; children: React.ReactNode; } @@ -20,6 +22,7 @@ interface InvoiceMailDialogBootProps { */ function InvoiceMailDialogBoot({ invoiceId, + redirectToInvoicesList, ...props }: InvoiceMailDialogBootProps) { const { data: mailOptions, isLoading: isMailOptionsLoading } = @@ -29,6 +32,7 @@ function InvoiceMailDialogBoot({ saleInvoiceId: invoiceId, mailOptions, isMailOptionsLoading, + redirectToInvoicesList, }; return ( diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialogContent.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialogContent.tsx index 37f3f091f..769a722b6 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialogContent.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialogContent.tsx @@ -4,13 +4,20 @@ import { InvoiceMailDialogForm } from './InvoiceMailDialogForm'; interface InvoiceMailDialogContentProps { dialogName: string; invoiceId: number; + + // Redirect to invoices list after submitting the message. + redirectToInvoicesList?: boolean; } export default function InvoiceMailDialogContent({ dialogName, invoiceId, + redirectToInvoicesList, }: InvoiceMailDialogContentProps) { return ( - + ); diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialogForm.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialogForm.tsx index 794ed890d..697e8e9a1 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialogForm.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialogForm.tsx @@ -15,6 +15,7 @@ import { transformMailFormToRequest, transformMailFormToInitialValues, } from '@/containers/SendMailNotification/utils'; +import { useHistory } from 'react-router-dom'; const initialFormValues = { ...initialMailNotificationValues, @@ -29,7 +30,9 @@ function InvoiceMailDialogFormRoot({ // #withDialogActions closeDialog, }) { - const { mailOptions, saleInvoiceId } = useInvoiceMailDialogBoot(); + const history = useHistory(); + const { mailOptions, saleInvoiceId, redirectToInvoicesList } = + useInvoiceMailDialogBoot(); const { mutateAsync: sendInvoiceMail } = useSendSaleInvoiceMail(); const initialValues = transformMailFormToInitialValues( @@ -49,6 +52,11 @@ function InvoiceMailDialogFormRoot({ }); closeDialog(DialogsName.InvoiceMail); setSubmitting(false); + + // Redirect to the dashboard if the option was enabled. + if (redirectToInvoicesList) { + history.push('/invoices'); + } }) .catch(() => { AppToaster.show({ diff --git a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentMailDialog/PaymentMailDialog.tsx b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentMailDialog/PaymentMailDialog.tsx index 6da51d03e..32c175ed9 100644 --- a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentMailDialog/PaymentMailDialog.tsx +++ b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentMailDialog/PaymentMailDialog.tsx @@ -13,7 +13,12 @@ const PaymentMailDialogContent = React.lazy( */ function PaymentMailDialog({ dialogName, - payload: { paymentReceiveId = null }, + payload: { + paymentReceiveId = null, + + // Redirects to the payments list on mail submitting. + redirectToPaymentsList = false, + }, isOpen, }) { return ( @@ -29,6 +34,7 @@ function PaymentMailDialog({ diff --git a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentMailDialog/PaymentMailDialogBoot.tsx b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentMailDialog/PaymentMailDialogBoot.tsx index aa08bd2e1..5fcbd4afa 100644 --- a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentMailDialog/PaymentMailDialogBoot.tsx +++ b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentMailDialog/PaymentMailDialogBoot.tsx @@ -13,6 +13,7 @@ const PaymentMailDialogBootContext = interface PaymentMailDialogBootProps { paymentReceiveId: number; + redirectToPaymentsList: boolean; children: React.ReactNode; } @@ -29,7 +30,8 @@ function PaymentMailDialogBoot({ const provider = { mailOptions, isMailOptionsLoading, - paymentReceiveId + paymentReceiveId, + redirectToPaymentsList }; return ( diff --git a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentMailDialog/PaymentMailDialogContent.tsx b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentMailDialog/PaymentMailDialogContent.tsx index 12fa57e05..33597cfa9 100644 --- a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentMailDialog/PaymentMailDialogContent.tsx +++ b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentMailDialog/PaymentMailDialogContent.tsx @@ -4,13 +4,18 @@ import { PaymentMailDialogForm } from './PaymentMailDialogForm'; interface PaymentMailDialogContentProps { dialogName: string; paymentReceiveId: number; + redirectToPaymentsList: boolean; } export default function PaymentMailDialogContent({ dialogName, paymentReceiveId, + redirectToPaymentsList, }: PaymentMailDialogContentProps) { return ( - + ); diff --git a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentMailDialog/PaymentMailDialogForm.tsx b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentMailDialog/PaymentMailDialogForm.tsx index bf0aa578b..f397a8740 100644 --- a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentMailDialog/PaymentMailDialogForm.tsx +++ b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentMailDialog/PaymentMailDialogForm.tsx @@ -3,7 +3,6 @@ import { Formik, FormikBag } from 'formik'; import * as R from 'ramda'; import { Intent } from '@blueprintjs/core'; import { usePaymentMailDialogBoot } from './PaymentMailDialogBoot'; -import withDialogActions from '@/containers/Dialog/withDialogActions'; import { DialogsName } from '@/constants/dialogs'; import { useSendPaymentReceiveMail } from '@/hooks/query'; import { PaymentMailDialogFormContent } from './PaymentMailDialogFormContent'; @@ -14,6 +13,8 @@ import { transformMailFormToInitialValues, } from '@/containers/SendMailNotification/utils'; import { AppToaster } from '@/components'; +import { useHistory } from 'react-router-dom'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; const initialFormValues = { ...initialMailNotificationValues, @@ -28,9 +29,12 @@ export function PaymentMailDialogFormRoot({ // #withDialogActions closeDialog, }) { - const { mailOptions, paymentReceiveId } = usePaymentMailDialogBoot(); + const { mailOptions, paymentReceiveId, redirectToPaymentsList } = + usePaymentMailDialogBoot(); const { mutateAsync: sendPaymentMail } = useSendPaymentReceiveMail(); + const history = useHistory(); + const initialValues = transformMailFormToInitialValues( mailOptions, initialFormValues, @@ -51,6 +55,11 @@ export function PaymentMailDialogFormRoot({ }); setSubmitting(false); closeDialog(DialogsName.PaymentMail); + + // Redirects to payments list if the option is enabled. + if (redirectToPaymentsList) { + history.push('/payment-receives'); + } }) .catch(() => { AppToaster.show({ diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialog.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialog.tsx index a64ad5531..eb68d7d37 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialog.tsx +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialog.tsx @@ -13,7 +13,12 @@ const ReceiptMailDialogContent = React.lazy( */ function ReceiptMailDialog({ dialogName, - payload: { receiptId = null }, + payload: { + receiptId = null, + + // Redirects to receipts list after mail submitting. + redirectToReceiptsList = false, + }, isOpen, }) { return ( @@ -29,6 +34,7 @@ function ReceiptMailDialog({ diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialogBoot.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialogBoot.tsx index 09eeb55f1..54f7200db 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialogBoot.tsx +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialogBoot.tsx @@ -6,6 +6,7 @@ import { DialogContent } from '@/components'; interface ReceiptMailDialogBootValues { receiptId: number; mailOptions: any; + redirectToReceiptsList: boolean; } const ReceiptMailDialogBootContext = @@ -14,6 +15,7 @@ const ReceiptMailDialogBootContext = interface ReceiptMailDialogBootProps { receiptId: number; children: React.ReactNode; + redirectToReceiptsList?: boolean; } /** @@ -21,6 +23,7 @@ interface ReceiptMailDialogBootProps { */ function ReceiptMailDialogBoot({ receiptId, + redirectToReceiptsList = false, ...props }: ReceiptMailDialogBootProps) { const { data: mailOptions, isLoading: isMailOptionsLoading } = @@ -30,6 +33,7 @@ function ReceiptMailDialogBoot({ saleReceiptId: receiptId, mailOptions, isMailOptionsLoading, + redirectToReceiptsList, }; return ( diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialogContent.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialogContent.tsx index 955620f86..586de745c 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialogContent.tsx +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialogContent.tsx @@ -3,15 +3,20 @@ import { ReceiptMailDialogBoot } from './ReceiptMailDialogBoot'; import { ReceiptMailDialogForm } from './ReceiptMailDialogForm'; interface ReceiptMailDialogContentProps { - dialogName: string + dialogName: string; receiptId: number; + redirectToReceiptsList?: boolean; } export default function ReceiptMailDialogContent({ dialogName, receiptId, + redirectToReceiptsList = false, }: ReceiptMailDialogContentProps) { return ( - + ); diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialogForm.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialogForm.tsx index fb9b845af..d46ea7eb0 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialogForm.tsx +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialogForm.tsx @@ -2,6 +2,7 @@ import { Formik, FormikBag } from 'formik'; import * as R from 'ramda'; import { Intent } from '@blueprintjs/core'; +import { useHistory } from 'react-router-dom'; import { useReceiptMailDialogBoot } from './ReceiptMailDialogBoot'; import withDialogActions from '@/containers/Dialog/withDialogActions'; import { DialogsName } from '@/constants/dialogs'; @@ -24,9 +25,12 @@ interface ReceiptMailFormValues extends MailNotificationFormValues { } function ReceiptMailDialogFormRoot({ closeDialog }) { - const { mailOptions, saleReceiptId } = useReceiptMailDialogBoot(); + const { mailOptions, saleReceiptId, redirectToReceiptsList } = + useReceiptMailDialogBoot(); const { mutateAsync: sendReceiptMail } = useSendSaleReceiptMail(); + const history = useHistory(); + // Transformes mail options to initial form values. const initialValues = transformMailFormToInitialValues( mailOptions, @@ -48,6 +52,10 @@ function ReceiptMailDialogFormRoot({ closeDialog }) { }); closeDialog(DialogsName.ReceiptMail); setSubmitting(false); + + if (redirectToReceiptsList) { + history.push('/receipts'); + } }) .catch(() => { AppToaster.show({ From 760dbc6cfc7d890f99c08a73cf76cb5e72801db1 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Thu, 25 Jan 2024 21:52:07 +0200 Subject: [PATCH 03/31] feat(server): change estimate and receipts status once delivering mail --- .../server/src/interfaces/PaymentReceive.ts | 6 +++ .../server/src/interfaces/SaleEstimate.ts | 8 +++- packages/server/src/interfaces/SaleReceipt.ts | 6 +++ packages/server/src/loaders/eventEmitter.ts | 9 +++- .../Accounts/AccountTransactionTransformer.ts | 1 - .../src/services/Accounts/AccountTransform.ts | 2 +- .../src/services/Accounts/ActivateAccount.ts | 4 -- .../Sales/Estimates/SendSaleEstimateMail.ts | 15 ++++++- .../SaleEstimateMarkApprovedOnMailSent.ts | 43 +++++++++++++++++++ .../PaymentReceiveMailNotification.ts | 13 ++++++ .../Receipts/SaleReceiptMailNotification.ts | 22 ++++++++-- .../SaleReceiptCostGLEntriesSubscriber.ts | 2 +- ...aleReceiptMarkClosedOnMailSentSubcriber.ts | 41 ++++++++++++++++++ packages/server/src/subscribers/events.ts | 12 ++++++ 14 files changed, 171 insertions(+), 13 deletions(-) create mode 100644 packages/server/src/services/Sales/Estimates/subscribers/SaleEstimateMarkApprovedOnMailSent.ts create mode 100644 packages/server/src/services/Sales/Receipts/subscribers/SaleReceiptMarkClosedOnMailSentSubcriber.ts diff --git a/packages/server/src/interfaces/PaymentReceive.ts b/packages/server/src/interfaces/PaymentReceive.ts index 2926d923c..329d0d944 100644 --- a/packages/server/src/interfaces/PaymentReceive.ts +++ b/packages/server/src/interfaces/PaymentReceive.ts @@ -173,3 +173,9 @@ export type IPaymentReceiveGLCommonEntry = Pick< export interface PaymentReceiveMailOpts extends CommonMailOptions {} export interface PaymentReceiveMailOptsDTO extends CommonMailOptionsDTO {} + +export interface PaymentReceiveMailPresendEvent { + tenantId: number; + paymentReceiveId: number; + messageOptions: PaymentReceiveMailOptsDTO; +} diff --git a/packages/server/src/interfaces/SaleEstimate.ts b/packages/server/src/interfaces/SaleEstimate.ts index 171c8a0d1..9ac17295c 100644 --- a/packages/server/src/interfaces/SaleEstimate.ts +++ b/packages/server/src/interfaces/SaleEstimate.ts @@ -132,4 +132,10 @@ export interface SaleEstimateMailOptions extends CommonMailOptions { export interface SaleEstimateMailOptionsDTO extends CommonMailOptionsDTO { attachEstimate?: boolean; -} \ No newline at end of file +} + +export interface ISaleEstimateMailPresendEvent { + tenantId: number; + saleEstimateId: number; + messageOptions: SaleEstimateMailOptionsDTO; +} diff --git a/packages/server/src/interfaces/SaleReceipt.ts b/packages/server/src/interfaces/SaleReceipt.ts index 1e8ffa98e..8904767c6 100644 --- a/packages/server/src/interfaces/SaleReceipt.ts +++ b/packages/server/src/interfaces/SaleReceipt.ts @@ -143,3 +143,9 @@ export interface SaleReceiptMailOpts extends CommonMailOptions { export interface SaleReceiptMailOptsDTO extends CommonMailOptionsDTO { attachReceipt?: boolean; } + +export interface ISaleReceiptMailPresend { + tenantId: number; + saleReceiptId: number; + messageOptions: SaleReceiptMailOptsDTO; +} diff --git a/packages/server/src/loaders/eventEmitter.ts b/packages/server/src/loaders/eventEmitter.ts index 5e052f376..5d3269646 100644 --- a/packages/server/src/loaders/eventEmitter.ts +++ b/packages/server/src/loaders/eventEmitter.ts @@ -85,6 +85,8 @@ import { BillTaxRateValidateSubscriber } from '@/services/TaxRates/subscribers/B import { WriteBillTaxTransactionsSubscriber } from '@/services/TaxRates/subscribers/WriteBillTaxTransactionsSubscriber'; import { SyncItemTaxRateOnEditTaxSubscriber } from '@/services/TaxRates/SyncItemTaxRateOnEditTaxSubscriber'; import { InvoiceChangeStatusOnMailSentSubscriber } from '@/services/Sales/Invoices/subscribers/InvoiceChangeStatusOnMailSentSubscriber'; +import { SaleReceiptMarkClosedOnMailSentSubcriber } from '@/services/Sales/Receipts/subscribers/SaleReceiptMarkClosedOnMailSentSubcriber'; +import { SaleEstimateMarkApprovedOnMailSent } from '@/services/Sales/Estimates/subscribers/SaleEstimateMarkApprovedOnMailSent'; export default () => { return new EventPublisher(); @@ -105,8 +107,12 @@ export const susbcribers = () => { InventorySubscriber, CustomerWriteGLOpeningBalanceSubscriber, VendorsWriteGLOpeningSubscriber, + + // # Estimate SaleEstimateAutoSerialSubscriber, SaleEstimateSmsNotificationSubscriber, + SaleEstimateMarkApprovedOnMailSent, + ExpensesWriteGLSubscriber, SaleReceiptAutoSerialSubscriber, SaleInvoiceAutoIncrementSubscriber, @@ -159,6 +165,7 @@ export const susbcribers = () => { // # Receipts SaleReceiptCostGLEntriesSubscriber, + SaleReceiptMarkClosedOnMailSentSubcriber, // Transaction locking. SalesTransactionLockingGuardSubscriber, @@ -201,6 +208,6 @@ export const susbcribers = () => { BillTaxRateValidateSubscriber, WriteBillTaxTransactionsSubscriber, - SyncItemTaxRateOnEditTaxSubscriber + SyncItemTaxRateOnEditTaxSubscriber, ]; }; diff --git a/packages/server/src/services/Accounts/AccountTransactionTransformer.ts b/packages/server/src/services/Accounts/AccountTransactionTransformer.ts index 857fe5ccc..d0e3e487f 100644 --- a/packages/server/src/services/Accounts/AccountTransactionTransformer.ts +++ b/packages/server/src/services/Accounts/AccountTransactionTransformer.ts @@ -1,6 +1,5 @@ import { IAccountTransaction } from '@/interfaces'; import { Transformer } from '@/lib/Transformer/Transformer'; -import { transaction } from 'objection'; export default class AccountTransactionTransformer extends Transformer { /** diff --git a/packages/server/src/services/Accounts/AccountTransform.ts b/packages/server/src/services/Accounts/AccountTransform.ts index 9297994be..98e19553e 100644 --- a/packages/server/src/services/Accounts/AccountTransform.ts +++ b/packages/server/src/services/Accounts/AccountTransform.ts @@ -34,7 +34,7 @@ export class AccountTransformer extends Transformer { /** * Retrieve formatted account amount. - * @param {IAccount} invoice + * @param {IAccount} invoice * @returns {string} */ protected formattedAmount = (account: IAccount): string => { diff --git a/packages/server/src/services/Accounts/ActivateAccount.ts b/packages/server/src/services/Accounts/ActivateAccount.ts index 1fcd104f6..26afd836d 100644 --- a/packages/server/src/services/Accounts/ActivateAccount.ts +++ b/packages/server/src/services/Accounts/ActivateAccount.ts @@ -5,7 +5,6 @@ import { IAccountEventActivatedPayload } from '@/interfaces'; import events from '@/subscribers/events'; import UnitOfWork from '@/services/UnitOfWork'; import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; -import { CommandAccountValidators } from './CommandAccountValidators'; @Service() export class ActivateAccount { @@ -18,9 +17,6 @@ export class ActivateAccount { @Inject() private uow: UnitOfWork; - @Inject() - private validator: CommandAccountValidators; - /** * Activates/Inactivates the given account. * @param {number} tenantId diff --git a/packages/server/src/services/Sales/Estimates/SendSaleEstimateMail.ts b/packages/server/src/services/Sales/Estimates/SendSaleEstimateMail.ts index 258496306..0ac580b90 100644 --- a/packages/server/src/services/Sales/Estimates/SendSaleEstimateMail.ts +++ b/packages/server/src/services/Sales/Estimates/SendSaleEstimateMail.ts @@ -8,11 +8,14 @@ import { import { SaleEstimatesPdf } from './SaleEstimatesPdf'; import { GetSaleEstimate } from './GetSaleEstimate'; import { + ISaleEstimateMailPresendEvent, SaleEstimateMailOptions, SaleEstimateMailOptionsDTO, } from '@/interfaces'; import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification'; import { parseAndValidateMailOptions } from '@/services/MailNotification/utils'; +import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; +import events from '@/subscribers/events'; @Service() export class SendSaleEstimateMail { @@ -31,6 +34,9 @@ export class SendSaleEstimateMail { @Inject('agenda') private agenda: any; + @Inject() + private eventPublisher: EventPublisher; + /** * Triggers the reminder mail of the given sale estimate. * @param {number} tenantId - @@ -49,6 +55,13 @@ export class SendSaleEstimateMail { messageOptions, }; await this.agenda.now('sale-estimate-mail-send', payload); + + // Triggers `onSaleEstimatePreMailSend` event. + await this.eventPublisher.emitAsync(events.saleEstimate.onPreMailSend, { + tenantId, + saleEstimateId, + messageOptions, + } as ISaleEstimateMailPresendEvent); } /** @@ -99,7 +112,7 @@ export class SendSaleEstimateMail { return { ...mailOptions, data: formatterData, - attachEstimate: true + attachEstimate: true, }; }; diff --git a/packages/server/src/services/Sales/Estimates/subscribers/SaleEstimateMarkApprovedOnMailSent.ts b/packages/server/src/services/Sales/Estimates/subscribers/SaleEstimateMarkApprovedOnMailSent.ts new file mode 100644 index 000000000..99caa3952 --- /dev/null +++ b/packages/server/src/services/Sales/Estimates/subscribers/SaleEstimateMarkApprovedOnMailSent.ts @@ -0,0 +1,43 @@ +import { Inject, Service } from 'typedi'; +import events from '@/subscribers/events'; +import { ISaleEstimateMailPresendEvent } from '@/interfaces'; +import { DeliverSaleEstimate } from '../DeliverSaleEstimate'; +import { ServiceError } from '@/exceptions'; +import { ERRORS } from '../constants'; + +@Service() +export class SaleEstimateMarkApprovedOnMailSent { + @Inject() + private deliverEstimateService: DeliverSaleEstimate; + + /** + * Attaches events. + */ + public attach(bus) { + bus.subscribe(events.saleEstimate.onPreMailSend, this.markEstimateApproved); + } + + /** + * Marks the given estimate approved on submitting mail. + * @param {ISaleEstimateMailPresendEvent} + */ + private markEstimateApproved = async ({ + tenantId, + saleEstimateId, + }: ISaleEstimateMailPresendEvent) => { + try { + await this.deliverEstimateService.deliverSaleEstimate( + tenantId, + saleEstimateId + ); + } catch (error) { + if ( + error instanceof ServiceError && + error.errorType === ERRORS.SALE_ESTIMATE_ALREADY_DELIVERED + ) { + } else { + throw error; + } + } + }; +} diff --git a/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveMailNotification.ts b/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveMailNotification.ts index acb1ea7a1..bd8d4fa64 100644 --- a/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveMailNotification.ts +++ b/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveMailNotification.ts @@ -2,6 +2,7 @@ import { Inject, Service } from 'typedi'; import { PaymentReceiveMailOpts, PaymentReceiveMailOptsDTO, + PaymentReceiveMailPresendEvent, SendInvoiceMailDTO, } from '@/interfaces'; import Mail from '@/lib/Mail'; @@ -13,6 +14,8 @@ import { import { GetPaymentReceive } from './GetPaymentReceive'; import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification'; import { parseAndValidateMailOptions } from '@/services/MailNotification/utils'; +import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; +import events from '@/subscribers/events'; @Service() export class SendPaymentReceiveMailNotification { @@ -28,6 +31,9 @@ export class SendPaymentReceiveMailNotification { @Inject('agenda') private agenda: any; + @Inject() + private eventPublisher: EventPublisher; + /** * Sends the mail of the given payment receive. * @param {number} tenantId @@ -46,6 +52,13 @@ export class SendPaymentReceiveMailNotification { messageDTO, }; await this.agenda.now('payment-receive-mail-send', payload); + + // Triggers `onPaymentReceivePreMailSend` event. + await this.eventPublisher.emitAsync(events.paymentReceive.onPreMailSend, { + tenantId, + paymentReceiveId, + messageOptions: messageDTO, + } as PaymentReceiveMailPresendEvent); } /** diff --git a/packages/server/src/services/Sales/Receipts/SaleReceiptMailNotification.ts b/packages/server/src/services/Sales/Receipts/SaleReceiptMailNotification.ts index 572bed2f8..24add40cc 100644 --- a/packages/server/src/services/Sales/Receipts/SaleReceiptMailNotification.ts +++ b/packages/server/src/services/Sales/Receipts/SaleReceiptMailNotification.ts @@ -7,9 +7,15 @@ import { DEFAULT_RECEIPT_MAIL_CONTENT, DEFAULT_RECEIPT_MAIL_SUBJECT, } from './constants'; -import { SaleReceiptMailOpts, SaleReceiptMailOptsDTO } from '@/interfaces'; +import { + ISaleReceiptMailPresend, + SaleReceiptMailOpts, + SaleReceiptMailOptsDTO, +} from '@/interfaces'; import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification'; import { parseAndValidateMailOptions } from '@/services/MailNotification/utils'; +import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; +import events from '@/subscribers/events'; @Service() export class SaleReceiptMailNotification { @@ -25,6 +31,9 @@ export class SaleReceiptMailNotification { @Inject() private contactMailNotification: ContactMailNotification; + @Inject() + private eventPublisher: EventPublisher; + @Inject('agenda') private agenda: any; @@ -37,14 +46,21 @@ export class SaleReceiptMailNotification { public async triggerMail( tenantId: number, saleReceiptId: number, - messageOpts: SaleReceiptMailOptsDTO + messageOptions: SaleReceiptMailOptsDTO ) { const payload = { tenantId, saleReceiptId, - messageOpts, + messageOpts: messageOptions, }; await this.agenda.now('sale-receipt-mail-send', payload); + + // Triggers the event `onSaleReceiptPreMailSend`. + await this.eventPublisher.emitAsync(events.saleReceipt.onPreMailSend, { + tenantId, + saleReceiptId, + messageOptions, + } as ISaleReceiptMailPresend); } /** diff --git a/packages/server/src/services/Sales/Receipts/subscribers/SaleReceiptCostGLEntriesSubscriber.ts b/packages/server/src/services/Sales/Receipts/subscribers/SaleReceiptCostGLEntriesSubscriber.ts index 5e6311005..39590198b 100644 --- a/packages/server/src/services/Sales/Receipts/subscribers/SaleReceiptCostGLEntriesSubscriber.ts +++ b/packages/server/src/services/Sales/Receipts/subscribers/SaleReceiptCostGLEntriesSubscriber.ts @@ -6,7 +6,7 @@ import { SaleReceiptCostGLEntries } from '../SaleReceiptCostGLEntries'; @Service() export class SaleReceiptCostGLEntriesSubscriber { @Inject() - saleReceiptCostEntries: SaleReceiptCostGLEntries; + private saleReceiptCostEntries: SaleReceiptCostGLEntries; /** * Attaches events. diff --git a/packages/server/src/services/Sales/Receipts/subscribers/SaleReceiptMarkClosedOnMailSentSubcriber.ts b/packages/server/src/services/Sales/Receipts/subscribers/SaleReceiptMarkClosedOnMailSentSubcriber.ts new file mode 100644 index 000000000..3a8d26394 --- /dev/null +++ b/packages/server/src/services/Sales/Receipts/subscribers/SaleReceiptMarkClosedOnMailSentSubcriber.ts @@ -0,0 +1,41 @@ +import { ISaleReceiptMailPresend } from '@/interfaces'; +import events from '@/subscribers/events'; +import { CloseSaleReceipt } from '../CloseSaleReceipt'; +import { Inject, Service } from 'typedi'; +import { ServiceError } from '@/exceptions'; +import { ERRORS } from '../constants'; + +@Service() +export class SaleReceiptMarkClosedOnMailSentSubcriber { + @Inject() + private closeReceiptService: CloseSaleReceipt; + + /** + * Attaches events. + */ + public attach(bus) { + bus.subscribe(events.saleReceipt.onPreMailSend, this.markReceiptClosed); + } + + /** + * Marks the sale receipt closed on submitting mail. + * @param {ISaleReceiptMailPresend} + */ + private markReceiptClosed = async ({ + tenantId, + saleReceiptId, + messageOptions, + }: ISaleReceiptMailPresend) => { + try { + await this.closeReceiptService.closeSaleReceipt(tenantId, saleReceiptId); + } catch (error) { + if ( + error instanceof ServiceError && + error.errorType === ERRORS.SALE_RECEIPT_IS_ALREADY_CLOSED + ) { + } else { + throw error; + } + } + }; +} diff --git a/packages/server/src/subscribers/events.ts b/packages/server/src/subscribers/events.ts index a46c32696..882027f98 100644 --- a/packages/server/src/subscribers/events.ts +++ b/packages/server/src/subscribers/events.ts @@ -172,6 +172,10 @@ export default { onRejected: 'onSaleEstimateRejected', onNotifyMail: 'onSaleEstimateNotifyMail', + + onPreMailSend: 'onSaleEstimatePreMailSend', + onMailSend: 'onSaleEstimateMailSend', + onMailSent: 'onSaleEstimateMailSend', }, /** @@ -195,6 +199,10 @@ export default { onNotifySms: 'onSaleReceiptNotifySms', onNotifiedSms: 'onSaleReceiptNotifiedSms', + + onPreMailSend: 'onSaleReceiptPreMailSend', + onMailSend: 'onSaleReceiptMailSend', + onMailSent: 'onSaleReceiptMailSent', }, /** @@ -215,6 +223,10 @@ export default { onNotifySms: 'onPaymentReceiveNotifySms', onNotifiedSms: 'onPaymentReceiveNotifiedSms', + + onPreMailSend: 'onPaymentReceivePreMailSend', + onMailSend: 'onPaymentReceiveMailSend', + onMailSent: 'onPaymentReceiveMailSent', }, /** From 63708ae839b91b2c802c171d91c1bae9df9c710b Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Thu, 25 Jan 2024 21:56:18 +0200 Subject: [PATCH 04/31] feat(webapp): showing up mail popup once saving invoice, receipt and estimate --- packages/webapp/src/constants/dialogs.ts | 4 ++ .../Dialogs/EstimateFormMailDeliverDialog.tsx | 39 ++++++++++++++++++ .../EstimateFormMailDeliverDialogContent.tsx | 40 +++++++++++++++++++ .../EstimateForm/EstimateFloatingActions.tsx | 2 +- .../Estimates/EstimateForm/EstimateForm.tsx | 13 +++++- .../EstimateForm/EstimateFormDialogs.tsx | 5 +++ .../EstimateMailDialog/EstimateMailDialog.tsx | 19 +++------ .../EstimateMailDialogBody.tsx | 33 +++++++++++++++ .../EstimateMailDialogContent.tsx | 18 ++++----- .../EstimateMailDialogForm.tsx | 12 +++--- .../InvoiceFormMailDeliverDialog.tsx | 39 ++++++++++++++++++ .../InvoiceFormMailDeliverDialogContent.tsx | 40 +++++++++++++++++++ .../InvoiceForm/InvoiceFloatingActions.tsx | 8 ++-- .../Invoices/InvoiceForm/InvoiceForm.tsx | 18 ++++++++- .../InvoiceForm/InvoiceFormDialogs.tsx | 15 ++++--- .../InvoiceMailDialog/InvoiceMailDialog.tsx | 20 +++------- .../InvoiceMailDialogBody.tsx | 36 +++++++++++++++++ .../InvoiceMailDialogContent.tsx | 22 +++++----- .../InvoiceMailDialogForm.tsx | 26 ++---------- .../Sales/Invoices/InvoiceMailDialog/index.ts | 3 +- .../Dialogs/ReceiptFormMailDeliverDialog.tsx | 39 ++++++++++++++++++ .../ReceiptFormMailDeliverDialogContent.tsx | 40 +++++++++++++++++++ .../Receipts/ReceiptForm/ReceiptForm.tsx | 21 +++++++--- .../ReceiptForm/ReceiptFormDialogs.tsx | 5 +++ .../ReceiptFormFloatingActions.tsx | 2 +- .../ReceiptMailDialog/ReceiptMailDialog.tsx | 19 +++------ .../ReceiptMailDialogBody.tsx | 33 +++++++++++++++ .../ReceiptMailDialogContent.tsx | 20 +++++----- .../ReceiptMailDialogForm.tsx | 29 ++++++-------- 29 files changed, 482 insertions(+), 138 deletions(-) create mode 100644 packages/webapp/src/containers/Sales/Estimates/EstimateForm/Dialogs/EstimateFormMailDeliverDialog.tsx create mode 100644 packages/webapp/src/containers/Sales/Estimates/EstimateForm/Dialogs/EstimateFormMailDeliverDialogContent.tsx create mode 100644 packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogBody.tsx create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceForm/Dialogs/InvoiceFormMailDeliverDialog/InvoiceFormMailDeliverDialog.tsx create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceForm/Dialogs/InvoiceFormMailDeliverDialog/InvoiceFormMailDeliverDialogContent.tsx create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialogBody.tsx create mode 100644 packages/webapp/src/containers/Sales/Receipts/ReceiptForm/Dialogs/ReceiptFormMailDeliverDialog.tsx create mode 100644 packages/webapp/src/containers/Sales/Receipts/ReceiptForm/Dialogs/ReceiptFormMailDeliverDialogContent.tsx create mode 100644 packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialogBody.tsx diff --git a/packages/webapp/src/constants/dialogs.ts b/packages/webapp/src/constants/dialogs.ts index c9bb52a0e..e7aa4d324 100644 --- a/packages/webapp/src/constants/dialogs.ts +++ b/packages/webapp/src/constants/dialogs.ts @@ -53,4 +53,8 @@ export enum DialogsName { EstimateMail = 'estimate-mail', ReceiptMail = 'receipt-mail', PaymentMail = 'payment-mail', + + InvoiceFormMailDeliver = 'InvoiceFormMailDeliver', + EstimateFormMailDeliver = 'EstimateFormMailDeliver', + ReceiptFormMailDeliver = 'ReceiptFormMailDeliver', } diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateForm/Dialogs/EstimateFormMailDeliverDialog.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/Dialogs/EstimateFormMailDeliverDialog.tsx new file mode 100644 index 000000000..6a0b832c3 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/Dialogs/EstimateFormMailDeliverDialog.tsx @@ -0,0 +1,39 @@ +// @ts-nocheck +import React from 'react'; +import { Dialog, DialogSuspense } from '@/components'; +import withDialogRedux from '@/components/DialogReduxConnect'; +import { compose } from '@/utils'; + +const EstimateFormMailDeliverDialogContent = React.lazy( + () => import('./EstimateFormMailDeliverDialogContent'), +); + +/** + * Estimate mail dialog. + */ +function EstimateFormMailDeliverDialog({ + dialogName, + payload: { estimateId = null }, + isOpen, +}) { + return ( + + + + + + ); +} + +export default compose(withDialogRedux())(EstimateFormMailDeliverDialog); diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateForm/Dialogs/EstimateFormMailDeliverDialogContent.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/Dialogs/EstimateFormMailDeliverDialogContent.tsx new file mode 100644 index 000000000..e77e3ee99 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/Dialogs/EstimateFormMailDeliverDialogContent.tsx @@ -0,0 +1,40 @@ +// @ts-nocheck +import * as R from 'ramda'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; +import { useHistory } from 'react-router-dom'; +import EstimateMailDialogContent from '../../EstimateMailDialog/EstimateMailDialogContent'; +import { DialogsName } from '@/constants/dialogs'; + +interface EstimateFormDeliverDialogContent { + estimateId: number; +} + +function EstimateFormDeliverDialogContentRoot({ + estimateId, + + // #withDialogActions + closeDialog, +}: EstimateFormDeliverDialogContent) { + const history = useHistory(); + + const handleSubmit = () => { + closeDialog(DialogsName.EstimateFormMailDeliver); + history.push('/estimates'); + }; + const handleCancel = () => { + closeDialog(DialogsName.EstimateFormMailDeliver); + history.push('/estimates'); + }; + + return ( + + ); +} + +export default R.compose(withDialogActions)( + EstimateFormDeliverDialogContentRoot, +); diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFloatingActions.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFloatingActions.tsx index df212c38c..474730d93 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFloatingActions.tsx +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFloatingActions.tsx @@ -29,7 +29,7 @@ export default function EstimateFloatingActions() { // Handle submit & deliver button click. const handleSubmitDeliverBtnClick = (event) => { - setSubmitPayload({ redirect: true, deliver: true }); + setSubmitPayload({ redirect: false, deliverViaMail: true }); submitForm(); }; diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateForm.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateForm.tsx index aff0888d6..b1e665d3d 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateForm.tsx +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateForm.tsx @@ -36,11 +36,16 @@ import { handleErrors, resetFormState, } from './utils'; +import { DialogsName } from '@/constants/dialogs'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; /** * Estimate form. */ function EstimateForm({ + // #withDialogActions + openDialog, + // #withSettings estimateNextNumber, estimateNumberPrefix, @@ -108,7 +113,7 @@ function EstimateForm({ delivered: submitPayload.deliver, }; // Handle the request success. - const onSuccess = (response) => { + const onSuccess = (res) => { AppToaster.show({ message: intl.get( isNewMode @@ -126,6 +131,11 @@ function EstimateForm({ if (submitPayload.resetForm) { resetFormState({ resetForm, initialValues, values }); } + if (submitPayload.deliverViaMail) { + openDialog(DialogsName.EstimateFormMailDeliver, { + estimateId: res.data.id, + }); + } }; // Handle the request error. const onError = ({ @@ -180,6 +190,7 @@ function EstimateForm({ } export default compose( + withDialogActions, withSettings(({ estimatesSettings }) => ({ estimateNextNumber: estimatesSettings?.nextNumber, estimateNumberPrefix: estimatesSettings?.numberPrefix, diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFormDialogs.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFormDialogs.tsx index aa1c165a1..a50326486 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFormDialogs.tsx +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFormDialogs.tsx @@ -2,6 +2,8 @@ import React from 'react'; import { useFormikContext } from 'formik'; import EstimateNumberDialog from '@/containers/Dialogs/EstimateNumberDialog'; +import EstimateFormMailDeliverDialog from './Dialogs/EstimateFormMailDeliverDialog'; +import { DialogsName } from '@/constants/dialogs'; /** * Estimate form dialogs. @@ -25,6 +27,9 @@ export default function EstimateFormDialogs() { dialogName={'estimate-number-form'} onConfirm={handleEstimateNumberFormConfirm} /> + ); } diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialog.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialog.tsx index a90f0e94a..0d13e07fb 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialog.tsx +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialog.tsx @@ -4,21 +4,16 @@ import { Dialog, DialogSuspense } from '@/components'; import withDialogRedux from '@/components/DialogReduxConnect'; import { compose } from '@/utils'; -const EstimateMailDialogContent = React.lazy( - () => import('./EstimateMailDialogContent'), +const EstimateMailDialogBody = React.lazy( + () => import('./EstimateMailDialogBody'), ); /** - * Invoice mail dialog. + * Estimate mail dialog. */ function EstimateMailDialog({ dialogName, - payload: { - estimateId = null, - - // Redirect to the estimates list after mail submitting. - redirectToEstimatesList = false, - }, + payload: { estimateId = null }, isOpen, }) { return ( @@ -31,11 +26,7 @@ function EstimateMailDialog({ style={{ width: 600 }} > - + ); diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogBody.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogBody.tsx new file mode 100644 index 000000000..2fa1c0472 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogBody.tsx @@ -0,0 +1,33 @@ +// @ts-nocheck +import * as R from 'ramda'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; +import EstimateMailDialogContent from './EstimateMailDialogContent'; +import { DialogsName } from '@/constants/dialogs'; + +interface EstimateMailDialogBodyProps { + estimateId: number; +} + +function EstimateMailDialogBodyRoot({ + estimateId, + + // #withDialogActions + closeDialog, +}: EstimateMailDialogBodyProps) { + const handleSubmit = () => { + closeDialog(DialogsName.EstimateMail); + }; + const handleCancelClick = () => { + closeDialog(DialogsName.EstimateMail); + }; + + return ( + + ); +} + +export default R.compose(withDialogActions)(EstimateMailDialogBodyRoot); diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogContent.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogContent.tsx index 3eb0af76d..c673f71c6 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogContent.tsx +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogContent.tsx @@ -2,21 +2,21 @@ import { EstimateMailDialogBoot } from './EstimateMailDialogBoot'; import { EstimateMailDialogForm } from './EstimateMailDialogForm'; interface EstimateMailDialogContentProps { - dialogName: string; estimateId: number; - redirectToEstimatesList?: boolean; + onFormSubmit?: () => void; + onCancelClick?: () => void; } export default function EstimateMailDialogContent({ - dialogName, estimateId, - redirectToEstimatesList, + onFormSubmit, + onCancelClick, }: EstimateMailDialogContentProps) { return ( - - + + ); } diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogForm.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogForm.tsx index 4c6e7e943..8f51add43 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogForm.tsx +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialogForm.tsx @@ -2,7 +2,6 @@ import { Formik } from 'formik'; import * as R from 'ramda'; import { Intent } from '@blueprintjs/core'; -import { useHistory } from 'react-router-dom'; import { useEstimateMailDialogBoot } from './EstimateMailDialogBoot'; import { DialogsName } from '@/constants/dialogs'; import withDialogActions from '@/containers/Dialog/withDialogActions'; @@ -26,6 +25,9 @@ interface EstimateMailFormValues extends MailNotificationFormValues { } function EstimateMailDialogFormRoot({ + onFormSubmit, + onCancelClick, + // #withDialogClose closeDialog, }) { @@ -33,8 +35,6 @@ function EstimateMailDialogFormRoot({ const { mailOptions, saleEstimateId, redirectToEstimatesList } = useEstimateMailDialogBoot(); - const history = useHistory(); - const initialValues = transformMailFormToInitialValues( mailOptions, initialFormValues, @@ -52,10 +52,7 @@ function EstimateMailDialogFormRoot({ }); closeDialog(DialogsName.EstimateMail); setSubmitting(false); - - if (redirectToEstimatesList) { - history.push('/estimates'); - } + onFormSubmit && onFormSubmit(); }) .catch(() => { setSubmitting(false); @@ -64,6 +61,7 @@ function EstimateMailDialogFormRoot({ message: 'Something went wrong.', intent: Intent.DANGER, }); + onCancelClick && onCancelClick(); }); }; diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/Dialogs/InvoiceFormMailDeliverDialog/InvoiceFormMailDeliverDialog.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/Dialogs/InvoiceFormMailDeliverDialog/InvoiceFormMailDeliverDialog.tsx new file mode 100644 index 000000000..f6ceb38f8 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/Dialogs/InvoiceFormMailDeliverDialog/InvoiceFormMailDeliverDialog.tsx @@ -0,0 +1,39 @@ +// @ts-nocheck +import React from 'react'; +import { Dialog, DialogSuspense } from '@/components'; +import withDialogRedux from '@/components/DialogReduxConnect'; +import { compose } from '@/utils'; + +const InvoiceFormMailDeliverDialogContent = React.lazy( + () => import('./InvoiceFormMailDeliverDialogContent'), +); + +/** + * Invoice mail dialog. + */ +function InvoiceFormMailDeliverDialog({ + dialogName, + payload: { invoiceId = null }, + isOpen, +}) { + return ( + + + + + + ); +} + +export default compose(withDialogRedux())(InvoiceFormMailDeliverDialog); diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/Dialogs/InvoiceFormMailDeliverDialog/InvoiceFormMailDeliverDialogContent.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/Dialogs/InvoiceFormMailDeliverDialog/InvoiceFormMailDeliverDialogContent.tsx new file mode 100644 index 000000000..8ce5e7c12 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/Dialogs/InvoiceFormMailDeliverDialog/InvoiceFormMailDeliverDialogContent.tsx @@ -0,0 +1,40 @@ +// @ts-nocheck +import * as R from 'ramda'; +import { useHistory } from 'react-router-dom'; +import InvoiceMailDialogContent from '../../../InvoiceMailDialog/InvoiceMailDialogContent'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; +import { DialogsName } from '@/constants/dialogs'; + +interface InvoiceFormDeliverDialogContent { + invoiceId: number; +} + +function InvoiceFormDeliverDialogContentRoot({ + invoiceId, + + // #withDialogActions + closeDialog, +}: InvoiceFormDeliverDialogContent) { + const history = useHistory(); + + const handleSubmit = () => { + history.push('/invoices'); + closeDialog(DialogsName.InvoiceFormMailDeliver); + }; + const handleCancel = () => { + history.push('/invoices'); + closeDialog(DialogsName.InvoiceFormMailDeliver); + }; + + return ( + + ); +} + +export default R.compose(withDialogActions)( + InvoiceFormDeliverDialogContentRoot, +); diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFloatingActions.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFloatingActions.tsx index e212c8b7d..0a9a72913 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFloatingActions.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFloatingActions.tsx @@ -30,19 +30,19 @@ export default function InvoiceFloatingActions() { const { setSubmitPayload, invoice } = useInvoiceFormContext(); // Handle submit & deliver button click. - const handleSubmitDeliverBtnClick = (event) => { - setSubmitPayload({ redirect: true, deliver: true }); + const handleSubmitDeliverBtnClick = () => { + setSubmitPayload({ redirectToEdit: true, deliverViaMail: true }); submitForm(); }; // Handle submit, deliver & new button click. - const handleSubmitDeliverAndNewBtnClick = (event) => { + const handleSubmitDeliverAndNewBtnClick = () => { setSubmitPayload({ redirect: false, deliver: true, resetForm: true }); submitForm(); }; // Handle submit, deliver & continue editing button click. - const handleSubmitDeliverContinueEditingBtnClick = (event) => { + const handleSubmitDeliverContinueEditingBtnClick = () => { setSubmitPayload({ redirect: false, deliver: true }); submitForm(); }; diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceForm.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceForm.tsx index 6b9f234ac..c47f7bc53 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceForm.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceForm.tsx @@ -34,12 +34,20 @@ import { transformValueToRequest, resetFormState, } from './utils'; -import { InvoiceExchangeRateSync, InvoiceNoSyncSettingsToForm } from './components'; +import { + InvoiceExchangeRateSync, + InvoiceNoSyncSettingsToForm, +} from './components'; +import { DialogsName } from '@/constants/dialogs'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; /** * Invoice form. */ function InvoiceForm({ + // #withDialogActions + openDialog, + // #withSettings invoiceNextNumber, invoiceNumberPrefix, @@ -111,7 +119,7 @@ function InvoiceForm({ from_estimate_id: estimateId, }; // Handle the request success. - const onSuccess = () => { + const onSuccess = (res) => { AppToaster.show({ message: intl.get( isNewMode @@ -123,6 +131,11 @@ function InvoiceForm({ }); setSubmitting(false); + if (submitPayload.deliverViaMail) { + openDialog(DialogsName.InvoiceFormMailDeliver, { + invoiceId: res.data.id, + }); + } if (submitPayload.redirect) { history.push('/invoices'); } @@ -201,4 +214,5 @@ export default compose( invoiceTermsConditions: invoiceSettings?.termsConditions, })), withCurrentOrganization(), + withDialogActions, )(InvoiceForm); diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormDialogs.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormDialogs.tsx index eb0e3e0ba..fca6a8bcb 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormDialogs.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormDialogs.tsx @@ -1,8 +1,8 @@ // @ts-nocheck -import React from 'react'; import { useFormikContext } from 'formik'; import InvoiceNumberDialog from '@/containers/Dialogs/InvoiceNumberDialog'; import { DialogsName } from '@/constants/dialogs'; +import InvoiceFormMailDeliverDialog from './Dialogs/InvoiceFormMailDeliverDialog/InvoiceFormMailDeliverDialog'; /** * Invoice form dialogs. @@ -23,9 +23,14 @@ export default function InvoiceFormDialogs() { }; return ( - + <> + + + ); } diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialog.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialog.tsx index 1b385b83f..02c629e7c 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialog.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialog.tsx @@ -4,8 +4,8 @@ import { Dialog, DialogSuspense } from '@/components'; import withDialogRedux from '@/components/DialogReduxConnect'; import { compose } from '@/utils'; -const InvoiceMailDialogContent = React.lazy( - () => import('./InvoiceMailDialogContent'), +const InvoiceMailDialogBody = React.lazy( + () => import('./InvoiceMailDialogBody'), ); /** @@ -13,12 +13,7 @@ const InvoiceMailDialogContent = React.lazy( */ function InvoiceMailDialog({ dialogName, - payload: { - invoiceId = null, - - // Redirects to the invoices list. - redirectToInvoicesList = false, - }, + payload: { invoiceId = null }, isOpen, }) { return ( @@ -26,16 +21,13 @@ function InvoiceMailDialog({ name={dialogName} title={'Invoice Mail'} isOpen={isOpen} - canEscapeJeyClose={true} + canEscapeJeyClose={false} + isCloseButtonShown={false} autoFocus={true} style={{ width: 600 }} > - + ); diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialogBody.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialogBody.tsx new file mode 100644 index 000000000..3728c60ce --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialogBody.tsx @@ -0,0 +1,36 @@ +// @ts-nocheck +import * as R from 'ramda'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; +import InvoiceMailDialogContent, { + InvoiceMailDialogContentProps, +} from './InvoiceMailDialogContent'; +import { DialogsName } from '@/constants/dialogs'; + +export interface InvoiceMailDialogBodyProps + extends InvoiceMailDialogContentProps {} + +function InvoiceMailDialogBodyRoot({ + invoiceId, + onCancelClick, + onFormSubmit, + + // #withDialogActions + closeDialog, +}: InvoiceMailDialogBodyProps) { + const handleCancelClick = () => { + closeDialog(DialogsName.InvoiceMail); + }; + const handleSubmitClick = () => { + closeDialog(DialogsName.InvoiceMail); + }; + + return ( + + ); +} + +export default R.compose(withDialogActions)(InvoiceMailDialogBodyRoot); diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialogContent.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialogContent.tsx index 769a722b6..dbecb34fc 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialogContent.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialogContent.tsx @@ -1,24 +1,22 @@ import { InvoiceMailDialogBoot } from './InvoiceMailDialogBoot'; import { InvoiceMailDialogForm } from './InvoiceMailDialogForm'; -interface InvoiceMailDialogContentProps { - dialogName: string; +export interface InvoiceMailDialogContentProps { invoiceId: number; - - // Redirect to invoices list after submitting the message. - redirectToInvoicesList?: boolean; + onFormSubmit?: () => void; + onCancelClick?: () => void; } export default function InvoiceMailDialogContent({ - dialogName, invoiceId, - redirectToInvoicesList, + onFormSubmit, + onCancelClick, }: InvoiceMailDialogContentProps) { return ( - - + + ); } diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialogForm.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialogForm.tsx index 697e8e9a1..a91c03466 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialogForm.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialogForm.tsx @@ -1,12 +1,9 @@ // @ts-nocheck import { Formik } from 'formik'; -import * as R from 'ramda'; import { Intent } from '@blueprintjs/core'; import { useInvoiceMailDialogBoot } from './InvoiceMailDialogBoot'; -import { DialogsName } from '@/constants/dialogs'; import { AppToaster } from '@/components'; import { useSendSaleInvoiceMail } from '@/hooks/query'; -import withDialogActions from '@/containers/Dialog/withDialogActions'; import { InvoiceMailDialogFormContent } from './InvoiceMailDialogFormContent'; import { InvoiceMailFormSchema } from './InvoiceMailDialogForm.schema'; import { @@ -15,7 +12,6 @@ import { transformMailFormToRequest, transformMailFormToInitialValues, } from '@/containers/SendMailNotification/utils'; -import { useHistory } from 'react-router-dom'; const initialFormValues = { ...initialMailNotificationValues, @@ -26,13 +22,8 @@ interface InvoiceMailFormValues extends MailNotificationFormValues { attachInvoice: boolean; } -function InvoiceMailDialogFormRoot({ - // #withDialogActions - closeDialog, -}) { - const history = useHistory(); - const { mailOptions, saleInvoiceId, redirectToInvoicesList } = - useInvoiceMailDialogBoot(); +export function InvoiceMailDialogForm({ onFormSubmit, onCancelClick }) { + const { mailOptions, saleInvoiceId } = useInvoiceMailDialogBoot(); const { mutateAsync: sendInvoiceMail } = useSendSaleInvoiceMail(); const initialValues = transformMailFormToInitialValues( @@ -50,13 +41,8 @@ function InvoiceMailDialogFormRoot({ message: 'The mail notification has been sent successfully.', intent: Intent.SUCCESS, }); - closeDialog(DialogsName.InvoiceMail); setSubmitting(false); - - // Redirect to the dashboard if the option was enabled. - if (redirectToInvoicesList) { - history.push('/invoices'); - } + onFormSubmit && onFormSubmit(values); }) .catch(() => { AppToaster.show({ @@ -68,7 +54,7 @@ function InvoiceMailDialogFormRoot({ }; // Handle the close button click. const handleClose = () => { - closeDialog(DialogsName.InvoiceMail); + onCancelClick && onCancelClick(); }; return ( @@ -81,7 +67,3 @@ function InvoiceMailDialogFormRoot({ ); } - -export const InvoiceMailDialogForm = R.compose(withDialogActions)( - InvoiceMailDialogFormRoot, -); diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/index.ts b/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/index.ts index b64dcaaf3..b40bce27b 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/index.ts +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceMailDialog/index.ts @@ -1 +1,2 @@ -export * from './InvoiceMailDialog'; \ No newline at end of file +export * from './InvoiceMailDialog'; +export * from './InvoiceMailDialogContent'; \ No newline at end of file diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/Dialogs/ReceiptFormMailDeliverDialog.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/Dialogs/ReceiptFormMailDeliverDialog.tsx new file mode 100644 index 000000000..60f2758ff --- /dev/null +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/Dialogs/ReceiptFormMailDeliverDialog.tsx @@ -0,0 +1,39 @@ +// @ts-nocheck +import React from 'react'; +import { Dialog, DialogSuspense } from '@/components'; +import withDialogRedux from '@/components/DialogReduxConnect'; +import { compose } from '@/utils'; + +const ReceiptFormMailDeliverDialogContent = React.lazy( + () => import('./ReceiptFormMailDeliverDialogContent'), +); + +/** + * Receipt mail dialog. + */ +function ReceiptFormMailDeliverDialog({ + dialogName, + payload: { receiptId = null }, + isOpen, +}) { + return ( + + + + + + ); +} + +export default compose(withDialogRedux())(ReceiptFormMailDeliverDialog); diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/Dialogs/ReceiptFormMailDeliverDialogContent.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/Dialogs/ReceiptFormMailDeliverDialogContent.tsx new file mode 100644 index 000000000..4b5d31e40 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/Dialogs/ReceiptFormMailDeliverDialogContent.tsx @@ -0,0 +1,40 @@ +// @ts-nocheck +import * as R from 'ramda'; +import { useHistory } from 'react-router-dom'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; +import ReceiptMailDialogContent from '../../ReceiptMailDialog/ReceiptMailDialogContent'; +import { DialogsName } from '@/constants/dialogs'; + +interface ReceiptFormDeliverDialogContent { + receiptId: number; +} + +function ReceiptFormDeliverDialogContentRoot({ + receiptId, + + // #withDialogActions + closeDialog, +}: ReceiptFormDeliverDialogContent) { + const history = useHistory(); + + const handleSubmit = () => { + history.push('/receipts'); + closeDialog(DialogsName.ReceiptFormMailDeliver); + }; + const handleCancel = () => { + history.push('/receipts'); + closeDialog(DialogsName.ReceiptFormMailDeliver); + }; + + return ( + + ); +} + +export default R.compose(withDialogActions)( + ReceiptFormDeliverDialogContentRoot, +); diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptForm.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptForm.tsx index 62206bdbf..d7010d2ae 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptForm.tsx +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptForm.tsx @@ -34,12 +34,20 @@ import { transformFormValuesToRequest, resetFormState, } from './utils'; -import { ReceiptSyncAutoExRateToForm, ReceiptSyncIncrementSettingsToForm } from './components'; +import { + ReceiptSyncAutoExRateToForm, + ReceiptSyncIncrementSettingsToForm, +} from './components'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; +import { DialogsName } from '@/constants/dialogs'; /** * Receipt form. */ function ReceiptForm({ + // #withDialogActions + openDialog, + // #withSettings receiptNextNumber, receiptNumberPrefix, @@ -84,10 +92,7 @@ function ReceiptForm({ }), }; // Handle the form submit. - const handleFormSubmit = ( - values, - { setErrors, setSubmitting, resetForm }, - ) => { + const handleFormSubmit = (values, { setErrors, setSubmitting }) => { const entries = values.entries.filter( (item) => item.item_id && item.quantity, ); @@ -124,6 +129,11 @@ function ReceiptForm({ if (submitPayload.resetForm) { resetFormState(); } + if (submitPayload.deliverMail) { + openDialog(DialogsName.ReceiptFormMailDeliver, { + receiptId: response.data.id, + }); + } }; // Handle the request error. @@ -179,6 +189,7 @@ function ReceiptForm({ } export default compose( + withDialogActions, withDashboardActions, withSettings(({ receiptSettings }) => ({ receiptNextNumber: receiptSettings?.nextNumber, diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormDialogs.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormDialogs.tsx index 4fe2cb947..30477aee6 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormDialogs.tsx +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormDialogs.tsx @@ -2,6 +2,8 @@ import React from 'react'; import { useFormikContext } from 'formik'; import ReceiptNumberDialog from '@/containers/Dialogs/ReceiptNumberDialog'; +import ReceiptFormMailDeliverDialog from './Dialogs/ReceiptFormMailDeliverDialog'; +import { DialogsName } from '@/constants/dialogs'; /** * Receipt form dialogs. @@ -27,6 +29,9 @@ export default function ReceiptFormDialogs() { dialogName={'receipt-number-form'} onConfirm={handleReceiptNumberFormConfirm} /> + ); } diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormFloatingActions.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormFloatingActions.tsx index d8d83dfb5..4db00c3ae 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormFloatingActions.tsx +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormFloatingActions.tsx @@ -33,7 +33,7 @@ export default function ReceiptFormFloatingActions() { // Handle submit & close button click. const handleSubmitCloseBtnClick = (event) => { - setSubmitPayload({ redirect: true, status: true }); + setSubmitPayload({ redirect: false, deliverMail: true, status: true }); submitForm(); }; diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialog.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialog.tsx index eb68d7d37..69a0e64a9 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialog.tsx +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialog.tsx @@ -4,21 +4,16 @@ import { Dialog, DialogSuspense } from '@/components'; import withDialogRedux from '@/components/DialogReduxConnect'; import { compose } from '@/utils'; -const ReceiptMailDialogContent = React.lazy( - () => import('./ReceiptMailDialogContent'), +const ReceiptMailDialogBody = React.lazy( + () => import('./ReceiptMailDialogBody'), ); /** - * Invoice mail dialog. + * Receipt mail dialog. */ function ReceiptMailDialog({ dialogName, - payload: { - receiptId = null, - - // Redirects to receipts list after mail submitting. - redirectToReceiptsList = false, - }, + payload: { receiptId = null }, isOpen, }) { return ( @@ -31,11 +26,7 @@ function ReceiptMailDialog({ style={{ width: 600 }} > - + ); diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialogBody.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialogBody.tsx new file mode 100644 index 000000000..fbd379b84 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialogBody.tsx @@ -0,0 +1,33 @@ +// @ts-nocheck +import * as R from 'ramda'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; +import ReceiptMailDialogContent, { + ReceiptMailDialogContentProps, +} from './ReceiptMailDialogContent'; +import { DialogsName } from '@/constants/dialogs'; + +interface ReceiptMailDialogBodyProps extends ReceiptMailDialogContentProps {} + +function ReceiptMailDialogBodyRoot({ + receiptId, + + // #withDialogActions + closeDialog, +}: ReceiptMailDialogBodyProps) { + const handleCancelClick = () => { + closeDialog(DialogsName.ReceiptMail); + }; + const handleSubmitClick = () => { + closeDialog(DialogsName.ReceiptMail); + }; + + return ( + + ); +} + +export default R.compose(withDialogActions)(ReceiptMailDialogBodyRoot); diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialogContent.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialogContent.tsx index 586de745c..a02966a1c 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialogContent.tsx +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialogContent.tsx @@ -2,22 +2,22 @@ import React from 'react'; import { ReceiptMailDialogBoot } from './ReceiptMailDialogBoot'; import { ReceiptMailDialogForm } from './ReceiptMailDialogForm'; -interface ReceiptMailDialogContentProps { - dialogName: string; +export interface ReceiptMailDialogContentProps { receiptId: number; - redirectToReceiptsList?: boolean; + onFormSubmit?: () => void; + onCancelClick?: () => void; } export default function ReceiptMailDialogContent({ - dialogName, receiptId, - redirectToReceiptsList = false, + onFormSubmit, + onCancelClick }: ReceiptMailDialogContentProps) { return ( - - + + ); } diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialogForm.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialogForm.tsx index d46ea7eb0..db2808f4c 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialogForm.tsx +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialogForm.tsx @@ -2,7 +2,6 @@ import { Formik, FormikBag } from 'formik'; import * as R from 'ramda'; import { Intent } from '@blueprintjs/core'; -import { useHistory } from 'react-router-dom'; import { useReceiptMailDialogBoot } from './ReceiptMailDialogBoot'; import withDialogActions from '@/containers/Dialog/withDialogActions'; import { DialogsName } from '@/constants/dialogs'; @@ -24,12 +23,18 @@ interface ReceiptMailFormValues extends MailNotificationFormValues { attachReceipt: boolean; } -function ReceiptMailDialogFormRoot({ closeDialog }) { - const { mailOptions, saleReceiptId, redirectToReceiptsList } = - useReceiptMailDialogBoot(); - const { mutateAsync: sendReceiptMail } = useSendSaleReceiptMail(); +interface ReceiptMailDialogFormProps { + onFormSubmit?: () => void; + onCancelClick?: () => void; +} - const history = useHistory(); +export function ReceiptMailDialogForm({ + // #props + onFormSubmit, + onCancelClick, +}: ReceiptMailDialogFormProps) { + const { mailOptions, saleReceiptId } = useReceiptMailDialogBoot(); + const { mutateAsync: sendReceiptMail } = useSendSaleReceiptMail(); // Transformes mail options to initial form values. const initialValues = transformMailFormToInitialValues( @@ -50,12 +55,8 @@ function ReceiptMailDialogFormRoot({ closeDialog }) { message: 'The mail notification has been sent successfully.', intent: Intent.SUCCESS, }); - closeDialog(DialogsName.ReceiptMail); setSubmitting(false); - - if (redirectToReceiptsList) { - history.push('/receipts'); - } + onFormSubmit && onFormSubmit(values); }) .catch(() => { AppToaster.show({ @@ -67,7 +68,7 @@ function ReceiptMailDialogFormRoot({ closeDialog }) { }; // Handle the close button click. const handleClose = () => { - closeDialog(DialogsName.ReceiptMail); + onCancelClick && onCancelClick(); }; return ( @@ -76,7 +77,3 @@ function ReceiptMailDialogFormRoot({ closeDialog }) { ); } - -export const ReceiptMailDialogForm = R.compose(withDialogActions)( - ReceiptMailDialogFormRoot, -); From 7b5287ee8011e607fac5b9efcf153661a8ca6736 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 5 Feb 2024 18:50:34 +0200 Subject: [PATCH 05/31] fix(server): Revert the paid amount to bill transaction after editing bill payment amount --- .../BillPayments/BillPaymentValidators.ts | 14 -------------- .../Purchases/BillPayments/DeleteBillPayment.ts | 10 ++-------- .../Purchases/BillPayments/EditBillPayment.ts | 12 ++++++------ .../Purchases/BillPayments/GetBillPayment.ts | 13 ++----------- .../Purchases/BillPayments/GetPaymentBills.ts | 7 +++---- .../Sales/PaymentReceives/EditPaymentReceive.ts | 3 ++- 6 files changed, 15 insertions(+), 44 deletions(-) diff --git a/packages/server/src/services/Purchases/BillPayments/BillPaymentValidators.ts b/packages/server/src/services/Purchases/BillPayments/BillPaymentValidators.ts index 3aa2902ca..8bbfadbff 100644 --- a/packages/server/src/services/Purchases/BillPayments/BillPaymentValidators.ts +++ b/packages/server/src/services/Purchases/BillPayments/BillPaymentValidators.ts @@ -10,7 +10,6 @@ import { import TenancyService from '@/services/Tenancy/TenancyService'; import { ServiceError } from '@/exceptions'; import { ACCOUNT_TYPE } from '@/data/AccountTypes'; -import { BillPayment } from '@/models'; import { ERRORS } from './constants'; @Service() @@ -18,19 +17,6 @@ export class BillPaymentValidators { @Inject() private tenancy: TenancyService; - /** - * Validates the payment existance. - * @param {BillPayment | undefined | null} payment - * @throws {ServiceError(ERRORS.PAYMENT_MADE_NOT_FOUND)} - */ - public async validateBillPaymentExistance( - payment: BillPayment | undefined | null - ) { - if (!payment) { - throw new ServiceError(ERRORS.PAYMENT_MADE_NOT_FOUND); - } - } - /** * Validates the bill payment existance. * @param {Request} req diff --git a/packages/server/src/services/Purchases/BillPayments/DeleteBillPayment.ts b/packages/server/src/services/Purchases/BillPayments/DeleteBillPayment.ts index 4ab1c9a25..e4a1ab1fa 100644 --- a/packages/server/src/services/Purchases/BillPayments/DeleteBillPayment.ts +++ b/packages/server/src/services/Purchases/BillPayments/DeleteBillPayment.ts @@ -1,6 +1,5 @@ import { Knex } from 'knex'; import UnitOfWork from '@/services/UnitOfWork'; -import { BillPaymentValidators } from './BillPaymentValidators'; import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; import HasTenancyService from '@/services/Tenancy/TenancyService'; import { Inject, Service } from 'typedi'; @@ -21,9 +20,6 @@ export class DeleteBillPayment { @Inject() private uow: UnitOfWork; - @Inject() - private validators: BillPaymentValidators; - /** * Deletes the bill payment and associated transactions. * @param {number} tenantId - Tenant id. @@ -36,10 +32,8 @@ export class DeleteBillPayment { // Retrieve the bill payment or throw not found service error. const oldBillPayment = await BillPayment.query() .withGraphFetched('entries') - .findById(billPaymentId); - - // Validates the bill payment existance. - this.validators.validateBillPaymentExistance(oldBillPayment); + .findById(billPaymentId) + .throwIfNotFound(); // Deletes the bill transactions with associated transactions under // unit-of-work envirement. diff --git a/packages/server/src/services/Purchases/BillPayments/EditBillPayment.ts b/packages/server/src/services/Purchases/BillPayments/EditBillPayment.ts index 20c72d38b..de18853bb 100644 --- a/packages/server/src/services/Purchases/BillPayments/EditBillPayment.ts +++ b/packages/server/src/services/Purchases/BillPayments/EditBillPayment.ts @@ -57,12 +57,12 @@ export class EditBillPayment { const tenantMeta = await TenantMetadata.query().findOne({ tenantId }); - const oldBillPayment = await BillPayment.query().findById(billPaymentId); + const oldBillPayment = await BillPayment.query() + .findById(billPaymentId) + .withGraphFetched('entries') + .throwIfNotFound(); - // Validates the bill payment existance. - this.validators.validateBillPaymentExistance(oldBillPayment); - - // + // Retrieves the bill payment vendor or throw not found error. const vendor = await Contact.query() .modify('vendor') .findById(billPaymentDTO.vendorId) @@ -126,7 +126,7 @@ export class EditBillPayment { trx, } as IBillPaymentEditingPayload); - // Deletes the bill payment transaction graph from the storage. + // Edits the bill payment transaction graph on the storage. const billPayment = await BillPayment.query(trx).upsertGraphAndFetch({ id: billPaymentId, ...billPaymentObj, diff --git a/packages/server/src/services/Purchases/BillPayments/GetBillPayment.ts b/packages/server/src/services/Purchases/BillPayments/GetBillPayment.ts index 5984e8932..ccb1cab77 100644 --- a/packages/server/src/services/Purchases/BillPayments/GetBillPayment.ts +++ b/packages/server/src/services/Purchases/BillPayments/GetBillPayment.ts @@ -1,12 +1,8 @@ import { IBillPayment } from '@/interfaces'; import HasTenancyService from '@/services/Tenancy/TenancyService'; import { Inject, Service } from 'typedi'; -import { ERRORS } from './constants'; -import { ServiceError } from '@/exceptions'; import { BillPaymentTransformer } from './BillPaymentTransformer'; import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable'; -import { BillsValidators } from '../Bills/BillsValidators'; -import { BillPaymentValidators } from './BillPaymentValidators'; @Service() export class GetBillPayment { @@ -16,9 +12,6 @@ export class GetBillPayment { @Inject() private transformer: TransformerInjectable; - @Inject() - private validators: BillPaymentValidators; - /** * Retrieve bill payment. * @param {number} tenantId @@ -37,10 +30,8 @@ export class GetBillPayment { .withGraphFetched('paymentAccount') .withGraphFetched('transactions') .withGraphFetched('branch') - .findById(billPyamentId); - - // Validates the bill payment existance. - this.validators.validateBillPaymentExistance(billPayment); + .findById(billPyamentId) + .throwIfNotFound(); return this.transformer.transform( tenantId, diff --git a/packages/server/src/services/Purchases/BillPayments/GetPaymentBills.ts b/packages/server/src/services/Purchases/BillPayments/GetPaymentBills.ts index 7b86c8f04..ec839411b 100644 --- a/packages/server/src/services/Purchases/BillPayments/GetPaymentBills.ts +++ b/packages/server/src/services/Purchases/BillPayments/GetPaymentBills.ts @@ -18,10 +18,9 @@ export class GetPaymentBills { public async getPaymentBills(tenantId: number, billPaymentId: number) { const { Bill, BillPayment } = this.tenancy.models(tenantId); - const billPayment = await BillPayment.query().findById(billPaymentId); - - // Validates the bill payment existance. - this.validators.validateBillPaymentExistance(billPayment); + const billPayment = await BillPayment.query() + .findById(billPaymentId) + .throwIfNotFound(); const paymentBillsIds = billPayment.entries.map((entry) => entry.id); diff --git a/packages/server/src/services/Sales/PaymentReceives/EditPaymentReceive.ts b/packages/server/src/services/Sales/PaymentReceives/EditPaymentReceive.ts index b99413748..635f48946 100644 --- a/packages/server/src/services/Sales/PaymentReceives/EditPaymentReceive.ts +++ b/packages/server/src/services/Sales/PaymentReceives/EditPaymentReceive.ts @@ -61,7 +61,8 @@ export class EditPaymentReceive { // Validate the payment receive existance. const oldPaymentReceive = await PaymentReceive.query() .withGraphFetched('entries') - .findById(paymentReceiveId); + .findById(paymentReceiveId) + .throwIfNotFound(); // Validates the payment existance. this.validators.validatePaymentExistance(oldPaymentReceive); From b38020d3971f562ed1aafb3f93b2b1cc95bf5a8d Mon Sep 17 00:00:00 2001 From: "a.nasouf" Date: Mon, 5 Feb 2024 18:58:02 +0200 Subject: [PATCH 06/31] fix: gmail email addresses dots gets removed --- .../src/api/controllers/Contacts/Contacts.ts | 126 +++++++++--------- .../webapp/src/components/Details/index.tsx | 2 +- 2 files changed, 64 insertions(+), 64 deletions(-) diff --git a/packages/server/src/api/controllers/Contacts/Contacts.ts b/packages/server/src/api/controllers/Contacts/Contacts.ts index a05ee93fd..2449e7d6f 100644 --- a/packages/server/src/api/controllers/Contacts/Contacts.ts +++ b/packages/server/src/api/controllers/Contacts/Contacts.ts @@ -1,11 +1,11 @@ -import { check, param, query, body, ValidationChain } from 'express-validator'; -import { Router, Request, Response, NextFunction } from 'express'; -import { Inject, Service } from 'typedi'; -import { ServiceError } from '@/exceptions'; -import BaseController from '@/api/controllers/BaseController'; -import ContactsService from '@/services/Contacts/ContactsService'; -import DynamicListingService from '@/services/DynamicListing/DynamicListService'; -import { DATATYPES_LENGTH } from '@/data/DataTypes'; +import { check, param, query, body, ValidationChain } from "express-validator"; +import { Router, Request, Response, NextFunction } from "express"; +import { Inject, Service } from "typedi"; +import { ServiceError } from "@/exceptions"; +import BaseController from "@/api/controllers/BaseController"; +import ContactsService from "@/services/Contacts/ContactsService"; +import DynamicListingService from "@/services/DynamicListing/DynamicListService"; +import { DATATYPES_LENGTH } from "@/data/DataTypes"; @Service() export default class ContactsController extends BaseController { @@ -22,28 +22,28 @@ export default class ContactsController extends BaseController { const router = Router(); router.get( - '/auto-complete', + "/auto-complete", [...this.autocompleteQuerySchema], this.validationResult, this.asyncMiddleware(this.autocompleteContacts.bind(this)), this.dynamicListService.handlerErrorsToResponse ); router.get( - '/:id', - [param('id').exists().isNumeric().toInt()], + "/:id", + [param("id").exists().isNumeric().toInt()], this.validationResult, this.asyncMiddleware(this.getContact.bind(this)) ); router.post( - '/:id/inactivate', - [param('id').exists().isNumeric().toInt()], + "/:id/inactivate", + [param("id").exists().isNumeric().toInt()], this.validationResult, this.asyncMiddleware(this.inactivateContact.bind(this)), this.handlerServiceErrors ); router.post( - '/:id/activate', - [param('id').exists().isNumeric().toInt()], + "/:id/activate", + [param("id").exists().isNumeric().toInt()], this.validationResult, this.asyncMiddleware(this.activateContact.bind(this)), this.handlerServiceErrors @@ -56,11 +56,11 @@ export default class ContactsController extends BaseController { */ get autocompleteQuerySchema() { return [ - query('column_sort_by').optional().trim().escape(), - query('sort_order').optional().isIn(['desc', 'asc']), + query("column_sort_by").optional().trim().escape(), + query("sort_order").optional().isIn(["desc", "asc"]), - query('stringified_filter_roles').optional().isJSON(), - query('limit').optional().isNumeric().toInt(), + query("stringified_filter_roles").optional().isJSON(), + query("limit").optional().isNumeric().toInt(), ]; } @@ -97,8 +97,8 @@ export default class ContactsController extends BaseController { const { tenantId } = req; const filter = { filterRoles: [], - sortOrder: 'asc', - columnSortBy: 'display_name', + sortOrder: "asc", + columnSortBy: "display_name", limit: 10, ...this.matchedQueryData(req), }; @@ -118,170 +118,170 @@ export default class ContactsController extends BaseController { */ get contactDTOSchema(): ValidationChain[] { return [ - check('salutation') + check("salutation") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('first_name') + check("first_name") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('last_name') + check("last_name") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('company_name') + check("company_name") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('display_name') + check("display_name") .exists() .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('email') + check("email") .optional({ nullable: true }) .isString() - .normalizeEmail() + .normalizeEmail({ gmail_remove_dots: false }) .isEmail() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('website') + check("website") .optional({ nullable: true }) .isString() .trim() .isURL() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('work_phone') + check("work_phone") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('personal_phone') + check("personal_phone") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('billing_address_1') + check("billing_address_1") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('billing_address_2') + check("billing_address_2") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('billing_address_city') + check("billing_address_city") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('billing_address_country') + check("billing_address_country") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('billing_address_email') + check("billing_address_email") .optional({ nullable: true }) .isString() .isEmail() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('billing_address_postcode') + check("billing_address_postcode") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('billing_address_phone') + check("billing_address_phone") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('billing_address_state') + check("billing_address_state") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('shipping_address_1') + check("shipping_address_1") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('shipping_address_2') + check("shipping_address_2") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('shipping_address_city') + check("shipping_address_city") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('shipping_address_country') + check("shipping_address_country") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('shipping_address_email') + check("shipping_address_email") .optional({ nullable: true }) .isString() .isEmail() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('shipping_address_postcode') + check("shipping_address_postcode") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('shipping_address_phone') + check("shipping_address_phone") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('shipping_address_state') + check("shipping_address_state") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check('note') + check("note") .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.TEXT }), - check('active').optional().isBoolean().toBoolean(), + check("active").optional().isBoolean().toBoolean(), ]; } @@ -291,19 +291,19 @@ export default class ContactsController extends BaseController { */ get contactNewDTOSchema(): ValidationChain[] { return [ - check('opening_balance') + check("opening_balance") .optional({ nullable: true }) .isInt({ min: 0, max: DATATYPES_LENGTH.DECIMAL_13_3 }) .toInt(), - check('opening_balance_exchange_rate') + check("opening_balance_exchange_rate") .default(1) .isFloat({ gt: 0 }) .toFloat(), - body('opening_balance_at') - .if(body('opening_balance').exists()) + body("opening_balance_at") + .if(body("opening_balance").exists()) .exists() .isISO8601(), - check('opening_balance_branch_id') + check("opening_balance_branch_id") .optional({ nullable: true }) .isNumeric() .toInt(), @@ -322,7 +322,7 @@ export default class ContactsController extends BaseController { * @returns {ValidationChain[]} */ get specificContactSchema(): ValidationChain[] { - return [param('id').exists().isNumeric().toInt()]; + return [param("id").exists().isNumeric().toInt()]; } /** @@ -340,7 +340,7 @@ export default class ContactsController extends BaseController { return res.status(200).send({ id: contactId, - message: 'The given contact activated successfully.', + message: "The given contact activated successfully.", }); } catch (error) { next(error); @@ -362,7 +362,7 @@ export default class ContactsController extends BaseController { return res.status(200).send({ id: contactId, - message: 'The given contact inactivated successfully.', + message: "The given contact inactivated successfully.", }); } catch (error) { next(error); @@ -383,19 +383,19 @@ export default class ContactsController extends BaseController { next: NextFunction ) { if (error instanceof ServiceError) { - if (error.errorType === 'contact_not_found') { + if (error.errorType === "contact_not_found") { return res.boom.badRequest(null, { - errors: [{ type: 'CONTACT.NOT.FOUND', code: 100 }], + errors: [{ type: "CONTACT.NOT.FOUND", code: 100 }], }); } - if (error.errorType === 'CONTACT_ALREADY_ACTIVE') { + if (error.errorType === "CONTACT_ALREADY_ACTIVE") { return res.boom.badRequest(null, { - errors: [{ type: 'CONTACT_ALREADY_ACTIVE', code: 700 }], + errors: [{ type: "CONTACT_ALREADY_ACTIVE", code: 700 }], }); } - if (error.errorType === 'CONTACT_ALREADY_INACTIVE') { + if (error.errorType === "CONTACT_ALREADY_INACTIVE") { return res.boom.badRequest(null, { - errors: [{ type: 'CONTACT_ALREADY_INACTIVE', code: 800 }], + errors: [{ type: "CONTACT_ALREADY_INACTIVE", code: 800 }], }); } } diff --git a/packages/webapp/src/components/Details/index.tsx b/packages/webapp/src/components/Details/index.tsx index e6803b069..9879fc7c0 100644 --- a/packages/webapp/src/components/Details/index.tsx +++ b/packages/webapp/src/components/Details/index.tsx @@ -66,7 +66,7 @@ export function DetailItem({ label, children, name, align, className }) { > {label} -
{children}
+
{children}
); } From a6db4fb6dffad1c75edf053c7f7526049e555f50 Mon Sep 17 00:00:00 2001 From: "a.nasouf" Date: Mon, 5 Feb 2024 19:52:48 +0200 Subject: [PATCH 07/31] fix: some keywords are not localized --- packages/webapp/src/containers/Authentication/Login.tsx | 2 +- packages/webapp/src/containers/Authentication/Register.tsx | 2 +- .../webapp/src/containers/Authentication/ResetPassword.tsx | 6 +++--- .../src/containers/Authentication/SendResetPassword.tsx | 6 +++--- .../src/containers/Authentication/SendResetPasswordForm.tsx | 5 ++--- packages/webapp/src/lang/ar/index.json | 5 +++++ packages/webapp/src/lang/en/index.json | 5 +++++ 7 files changed, 20 insertions(+), 11 deletions(-) diff --git a/packages/webapp/src/containers/Authentication/Login.tsx b/packages/webapp/src/containers/Authentication/Login.tsx index 9efbac5e9..0ac30e1bc 100644 --- a/packages/webapp/src/containers/Authentication/Login.tsx +++ b/packages/webapp/src/containers/Authentication/Login.tsx @@ -70,7 +70,7 @@ function LoginFooterLinks() { {!signupDisabled && ( - Don't have an account? Sign up + )} diff --git a/packages/webapp/src/containers/Authentication/Register.tsx b/packages/webapp/src/containers/Authentication/Register.tsx index 5a42bbf67..32225c850 100644 --- a/packages/webapp/src/containers/Authentication/Register.tsx +++ b/packages/webapp/src/containers/Authentication/Register.tsx @@ -87,7 +87,7 @@ function RegisterFooterLinks() { return ( - Return to Sign In + diff --git a/packages/webapp/src/containers/Authentication/ResetPassword.tsx b/packages/webapp/src/containers/Authentication/ResetPassword.tsx index 136d28174..afb43308b 100644 --- a/packages/webapp/src/containers/Authentication/ResetPassword.tsx +++ b/packages/webapp/src/containers/Authentication/ResetPassword.tsx @@ -5,7 +5,7 @@ import { Formik } from 'formik'; import { Intent, Position } from '@blueprintjs/core'; import { Link, useParams, useHistory } from 'react-router-dom'; -import { AppToaster } from '@/components'; +import { AppToaster, FormattedMessage as T } from '@/components'; import { useAuthResetPassword } from '@/hooks/query'; import AuthInsider from '@/containers/Authentication/AuthInsider'; @@ -86,11 +86,11 @@ function ResetPasswordFooterLinks() { {!signupDisabled && ( - Don't have an account? Sign up + )} - Return to Sign In + ); diff --git a/packages/webapp/src/containers/Authentication/SendResetPassword.tsx b/packages/webapp/src/containers/Authentication/SendResetPassword.tsx index c90f872c1..b8f24831c 100644 --- a/packages/webapp/src/containers/Authentication/SendResetPassword.tsx +++ b/packages/webapp/src/containers/Authentication/SendResetPassword.tsx @@ -5,7 +5,7 @@ import { Formik } from 'formik'; import { Link, useHistory } from 'react-router-dom'; import { Intent } from '@blueprintjs/core'; -import { AppToaster } from '@/components'; +import { AppToaster, FormattedMessage as T } from '@/components'; import { useAuthSendResetPassword } from '@/hooks/query'; import SendResetPasswordForm from './SendResetPasswordForm'; @@ -82,11 +82,11 @@ function SendResetPasswordFooterLinks() { {!signupDisabled && ( - Don't have an account? Sign up + )} - Return to Sign In + ); diff --git a/packages/webapp/src/containers/Authentication/SendResetPasswordForm.tsx b/packages/webapp/src/containers/Authentication/SendResetPasswordForm.tsx index 3f2718d59..a2f6e114b 100644 --- a/packages/webapp/src/containers/Authentication/SendResetPasswordForm.tsx +++ b/packages/webapp/src/containers/Authentication/SendResetPasswordForm.tsx @@ -14,8 +14,7 @@ export default function SendResetPasswordForm({ isSubmitting }) { return (
- Enter the email address associated with your account and we'll send you - a link to reset your password. + }> @@ -29,7 +28,7 @@ export default function SendResetPasswordForm({ isSubmitting }) { large={true} loading={isSubmitting} > - Reset Password + ); diff --git a/packages/webapp/src/lang/ar/index.json b/packages/webapp/src/lang/ar/index.json index 976b049d6..6e3996d91 100644 --- a/packages/webapp/src/lang/ar/index.json +++ b/packages/webapp/src/lang/ar/index.json @@ -20,6 +20,11 @@ "log_in": "تسجيل الدخول", "forget_my_password": "نسيت كلمة المرور الخاصة بي", "keep_me_logged_in": "تذكرني", + "dont_have_an_account": "ليس لديك حساب؟", + "sign_up": "تسجيل", + "return_to": "عودة إلى", + "sign_in": "صفحة الدخول", + "enter_the_email_address_associated_with_your_account": "قم بادخال بريدك الإلكتروني المرتبط بالحساب وسوف نرسل لك رابط لاعادة تعيين كلمة المرور.", "create_an_account": "إنشاء حساب", "need_bigcapital_account": "تحتاج إلى حساب Bigcapital؟", "show": "عرض", diff --git a/packages/webapp/src/lang/en/index.json b/packages/webapp/src/lang/en/index.json index 5522f57d6..12a36e5bd 100644 --- a/packages/webapp/src/lang/en/index.json +++ b/packages/webapp/src/lang/en/index.json @@ -19,6 +19,11 @@ "log_in": "Log in", "forget_my_password": "Forget my password", "keep_me_logged_in": "Keep me logged in", + "dont_have_an_account": "Don't have an account?", + "sign_up": "Sign up", + "return_to": "Return to", + "sign_in": "Sign In", + "enter_the_email_address_associated_with_your_account": "Enter the email address associated with your account and we'll send you a link to reset your password.", "create_an_account": "Create an account", "need_bigcapital_account": "Need a Bigcapital account ?", "show": "Show", From f7f77b12c9d70b9b89f56d18ec9a368b6f1b568d Mon Sep 17 00:00:00 2001 From: "a.nasouf" Date: Mon, 5 Feb 2024 20:05:10 +0200 Subject: [PATCH 08/31] fix: file formatting --- .../src/api/controllers/Contacts/Contacts.ts | 138 +++++++++--------- 1 file changed, 69 insertions(+), 69 deletions(-) diff --git a/packages/server/src/api/controllers/Contacts/Contacts.ts b/packages/server/src/api/controllers/Contacts/Contacts.ts index 2449e7d6f..751654a8e 100644 --- a/packages/server/src/api/controllers/Contacts/Contacts.ts +++ b/packages/server/src/api/controllers/Contacts/Contacts.ts @@ -1,11 +1,11 @@ -import { check, param, query, body, ValidationChain } from "express-validator"; -import { Router, Request, Response, NextFunction } from "express"; -import { Inject, Service } from "typedi"; -import { ServiceError } from "@/exceptions"; -import BaseController from "@/api/controllers/BaseController"; -import ContactsService from "@/services/Contacts/ContactsService"; -import DynamicListingService from "@/services/DynamicListing/DynamicListService"; -import { DATATYPES_LENGTH } from "@/data/DataTypes"; +import { check, param, query, body, ValidationChain } from 'express-validator'; +import { Router, Request, Response, NextFunction } from 'express'; +import { Inject, Service } from 'typedi'; +import { ServiceError } from '@/exceptions'; +import BaseController from '@/api/controllers/BaseController'; +import ContactsService from '@/services/Contacts/ContactsService'; +import DynamicListingService from '@/services/DynamicListing/DynamicListService'; +import { DATATYPES_LENGTH } from '@/data/DataTypes'; @Service() export default class ContactsController extends BaseController { @@ -22,31 +22,31 @@ export default class ContactsController extends BaseController { const router = Router(); router.get( - "/auto-complete", + '/auto-complete', [...this.autocompleteQuerySchema], this.validationResult, this.asyncMiddleware(this.autocompleteContacts.bind(this)), - this.dynamicListService.handlerErrorsToResponse + this.dynamicListService.handlerErrorsToResponse, ); router.get( - "/:id", - [param("id").exists().isNumeric().toInt()], + '/:id', + [param('id').exists().isNumeric().toInt()], this.validationResult, - this.asyncMiddleware(this.getContact.bind(this)) + this.asyncMiddleware(this.getContact.bind(this)), ); router.post( - "/:id/inactivate", - [param("id").exists().isNumeric().toInt()], + '/:id/inactivate', + [param('id').exists().isNumeric().toInt()], this.validationResult, this.asyncMiddleware(this.inactivateContact.bind(this)), - this.handlerServiceErrors + this.handlerServiceErrors, ); router.post( - "/:id/activate", - [param("id").exists().isNumeric().toInt()], + '/:id/activate', + [param('id').exists().isNumeric().toInt()], this.validationResult, this.asyncMiddleware(this.activateContact.bind(this)), - this.handlerServiceErrors + this.handlerServiceErrors, ); return router; } @@ -56,11 +56,11 @@ export default class ContactsController extends BaseController { */ get autocompleteQuerySchema() { return [ - query("column_sort_by").optional().trim().escape(), - query("sort_order").optional().isIn(["desc", "asc"]), + query('column_sort_by').optional().trim().escape(), + query('sort_order').optional().isIn(['desc', 'asc']), - query("stringified_filter_roles").optional().isJSON(), - query("limit").optional().isNumeric().toInt(), + query('stringified_filter_roles').optional().isJSON(), + query('limit').optional().isNumeric().toInt(), ]; } @@ -77,7 +77,7 @@ export default class ContactsController extends BaseController { try { const contact = await this.contactsService.getContact( tenantId, - contactId + contactId, ); return res.status(200).send({ customer: this.transfromToResponse(contact), @@ -97,15 +97,15 @@ export default class ContactsController extends BaseController { const { tenantId } = req; const filter = { filterRoles: [], - sortOrder: "asc", - columnSortBy: "display_name", + sortOrder: 'asc', + columnSortBy: 'display_name', limit: 10, ...this.matchedQueryData(req), }; try { const contacts = await this.contactsService.autocompleteContacts( tenantId, - filter + filter, ); return res.status(200).send({ contacts }); } catch (error) { @@ -118,170 +118,170 @@ export default class ContactsController extends BaseController { */ get contactDTOSchema(): ValidationChain[] { return [ - check("salutation") + check('salutation') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("first_name") + check('first_name') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("last_name") + check('last_name') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("company_name") + check('company_name') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("display_name") + check('display_name') .exists() .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("email") + check('email') .optional({ nullable: true }) .isString() .normalizeEmail({ gmail_remove_dots: false }) .isEmail() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("website") + check('website') .optional({ nullable: true }) .isString() .trim() .isURL() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("work_phone") + check('work_phone') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("personal_phone") + check('personal_phone') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("billing_address_1") + check('billing_address_1') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("billing_address_2") + check('billing_address_2') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("billing_address_city") + check('billing_address_city') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("billing_address_country") + check('billing_address_country') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("billing_address_email") + check('billing_address_email') .optional({ nullable: true }) .isString() .isEmail() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("billing_address_postcode") + check('billing_address_postcode') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("billing_address_phone") + check('billing_address_phone') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("billing_address_state") + check('billing_address_state') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("shipping_address_1") + check('shipping_address_1') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("shipping_address_2") + check('shipping_address_2') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("shipping_address_city") + check('shipping_address_city') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("shipping_address_country") + check('shipping_address_country') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("shipping_address_email") + check('shipping_address_email') .optional({ nullable: true }) .isString() .isEmail() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("shipping_address_postcode") + check('shipping_address_postcode') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("shipping_address_phone") + check('shipping_address_phone') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("shipping_address_state") + check('shipping_address_state') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.STRING }), - check("note") + check('note') .optional({ nullable: true }) .isString() .trim() .escape() .isLength({ max: DATATYPES_LENGTH.TEXT }), - check("active").optional().isBoolean().toBoolean(), + check('active').optional().isBoolean().toBoolean(), ]; } @@ -291,19 +291,19 @@ export default class ContactsController extends BaseController { */ get contactNewDTOSchema(): ValidationChain[] { return [ - check("opening_balance") + check('opening_balance') .optional({ nullable: true }) .isInt({ min: 0, max: DATATYPES_LENGTH.DECIMAL_13_3 }) .toInt(), - check("opening_balance_exchange_rate") + check('opening_balance_exchange_rate') .default(1) .isFloat({ gt: 0 }) .toFloat(), - body("opening_balance_at") - .if(body("opening_balance").exists()) + body('opening_balance_at') + .if(body('opening_balance').exists()) .exists() .isISO8601(), - check("opening_balance_branch_id") + check('opening_balance_branch_id') .optional({ nullable: true }) .isNumeric() .toInt(), @@ -322,7 +322,7 @@ export default class ContactsController extends BaseController { * @returns {ValidationChain[]} */ get specificContactSchema(): ValidationChain[] { - return [param("id").exists().isNumeric().toInt()]; + return [param('id').exists().isNumeric().toInt()]; } /** @@ -340,7 +340,7 @@ export default class ContactsController extends BaseController { return res.status(200).send({ id: contactId, - message: "The given contact activated successfully.", + message: 'The given contact activated successfully.', }); } catch (error) { next(error); @@ -362,7 +362,7 @@ export default class ContactsController extends BaseController { return res.status(200).send({ id: contactId, - message: "The given contact inactivated successfully.", + message: 'The given contact inactivated successfully.', }); } catch (error) { next(error); @@ -380,22 +380,22 @@ export default class ContactsController extends BaseController { error: Error, req: Request, res: Response, - next: NextFunction + next: NextFunction, ) { if (error instanceof ServiceError) { - if (error.errorType === "contact_not_found") { + if (error.errorType === 'contact_not_found') { return res.boom.badRequest(null, { - errors: [{ type: "CONTACT.NOT.FOUND", code: 100 }], + errors: [{ type: 'CONTACT.NOT.FOUND', code: 100 }], }); } - if (error.errorType === "CONTACT_ALREADY_ACTIVE") { + if (error.errorType === 'CONTACT_ALREADY_ACTIVE') { return res.boom.badRequest(null, { - errors: [{ type: "CONTACT_ALREADY_ACTIVE", code: 700 }], + errors: [{ type: 'CONTACT_ALREADY_ACTIVE', code: 700 }], }); } - if (error.errorType === "CONTACT_ALREADY_INACTIVE") { + if (error.errorType === 'CONTACT_ALREADY_INACTIVE') { return res.boom.badRequest(null, { - errors: [{ type: "CONTACT_ALREADY_INACTIVE", code: 800 }], + errors: [{ type: 'CONTACT_ALREADY_INACTIVE', code: 800 }], }); } } From 12740223a8664cf8464628e62ab3863366988885 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 5 Feb 2024 22:38:56 +0200 Subject: [PATCH 09/31] fix: Invalid bill payment amount on editing bill payment --- .../server/src/api/controllers/Purchases/Bills.ts | 10 ++++++++++ .../services/Purchases/Bills/BillsValidators.ts | 14 ++++++++++++++ .../src/services/Purchases/Bills/EditBill.ts | 6 ++++++ .../src/services/Purchases/Bills/constants.ts | 1 + packages/webapp/src/constants/errors.tsx | 6 ++++-- .../containers/Purchases/Bills/BillForm/utils.tsx | 9 +++++++++ .../PaymentForm/PaymentMadeEntriesTable.tsx | 3 ++- .../Sales/Invoices/InvoiceForm/utils.tsx | 10 ++++++++++ .../PaymentReceiveItemsTable.tsx | 3 ++- packages/webapp/src/lang/en/index.json | 2 ++ 10 files changed, 60 insertions(+), 4 deletions(-) diff --git a/packages/server/src/api/controllers/Purchases/Bills.ts b/packages/server/src/api/controllers/Purchases/Bills.ts index 9de79ecf7..4f65eab26 100644 --- a/packages/server/src/api/controllers/Purchases/Bills.ts +++ b/packages/server/src/api/controllers/Purchases/Bills.ts @@ -560,6 +560,16 @@ export default class BillsController extends BaseController { errors: [{ type: 'ITEM_ENTRY_TAX_RATE_ID_NOT_FOUND', code: 1900 }], }); } + if (error.errorType === 'BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT') { + return res.boom.badRequest(null, { + errors: [ + { + type: 'BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT', + code: 2000, + }, + ], + }); + } } next(error); } diff --git a/packages/server/src/services/Purchases/Bills/BillsValidators.ts b/packages/server/src/services/Purchases/Bills/BillsValidators.ts index cba38dfbb..9f209e40d 100644 --- a/packages/server/src/services/Purchases/Bills/BillsValidators.ts +++ b/packages/server/src/services/Purchases/Bills/BillsValidators.ts @@ -21,6 +21,20 @@ export class BillsValidators { } } + /** + * Validates the bill amount is bigger than paid amount. + * @param {number} billAmount + * @param {number} paidAmount + */ + public validateBillAmountBiggerPaidAmount( + billAmount: number, + paidAmount: number, + ) { + if (billAmount < paidAmount) { + throw new ServiceError(ERRORS.BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT); + } + } + /** * Validates the bill number existance. */ diff --git a/packages/server/src/services/Purchases/Bills/EditBill.ts b/packages/server/src/services/Purchases/Bills/EditBill.ts index 26121c120..7b10bb7f1 100644 --- a/packages/server/src/services/Purchases/Bills/EditBill.ts +++ b/packages/server/src/services/Purchases/Bills/EditBill.ts @@ -103,6 +103,7 @@ export class EditBill { tenantId, billDTO.entries ); + // Transforms the bill DTO to model object. const billObj = await this.transformerDTO.billDTOToModel( tenantId, @@ -111,6 +112,11 @@ export class EditBill { authorizedUser, oldBill ); + // Validate bill total amount should be bigger than paid amount. + this.validators.validateBillAmountBiggerPaidAmount( + billObj.amount, + oldBill.paymentAmount + ); // Validate landed cost entries that have allocated cost could not be deleted. await this.entriesService.validateLandedCostEntriesNotDeleted( oldBill.entries, diff --git a/packages/server/src/services/Purchases/Bills/constants.ts b/packages/server/src/services/Purchases/Bills/constants.ts index 12afad4c7..9cc8566c8 100644 --- a/packages/server/src/services/Purchases/Bills/constants.ts +++ b/packages/server/src/services/Purchases/Bills/constants.ts @@ -18,6 +18,7 @@ export const ERRORS = { LANDED_COST_ENTRIES_SHOULD_BE_INVENTORY_ITEMS: 'LANDED_COST_ENTRIES_SHOULD_BE_INVENTORY_ITEMS', BILL_HAS_APPLIED_TO_VENDOR_CREDIT: 'BILL_HAS_APPLIED_TO_VENDOR_CREDIT', + BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT: 'BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT', }; export const DEFAULT_VIEW_COLUMNS = []; diff --git a/packages/webapp/src/constants/errors.tsx b/packages/webapp/src/constants/errors.tsx index 2411c4d74..22f6345cb 100644 --- a/packages/webapp/src/constants/errors.tsx +++ b/packages/webapp/src/constants/errors.tsx @@ -9,6 +9,8 @@ export const ERROR = { SALE_INVOICE_NO_NOT_UNIQUE: 'SALE_INVOICE_NO_NOT_UNIQUE', SALE_ESTIMATE_IS_ALREADY_CONVERTED_TO_INVOICE: 'SALE_ESTIMATE_IS_ALREADY_CONVERTED_TO_INVOICE', + INVOICE_AMOUNT_SMALLER_THAN_PAYMENT_AMOUNT: + 'INVOICE_AMOUNT_SMALLER_THAN_PAYMENT_AMOUNT', // Sales Receipts SALE_RECEIPT_NUMBER_NOT_UNIQUE: 'SALE_RECEIPT_NUMBER_NOT_UNIQUE', @@ -17,6 +19,6 @@ export const ERROR = { // Bills BILL_NUMBER_EXISTS: 'BILL.NUMBER.EXISTS', SALE_INVOICE_NO_IS_REQUIRED: 'SALE_INVOICE_NO_IS_REQUIRED', - ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED:"ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED", - + ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED: + 'ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED', }; diff --git a/packages/webapp/src/containers/Purchases/Bills/BillForm/utils.tsx b/packages/webapp/src/containers/Purchases/Bills/BillForm/utils.tsx index 763573b30..f98f02757 100644 --- a/packages/webapp/src/containers/Purchases/Bills/BillForm/utils.tsx +++ b/packages/webapp/src/containers/Purchases/Bills/BillForm/utils.tsx @@ -67,6 +67,7 @@ export const ERRORS = { BILL_NUMBER_EXISTS: 'BILL.NUMBER.EXISTS', ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED: 'ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED', + BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT: 'BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT', }; /** * Transformes the bill to initial values of edit form. @@ -200,6 +201,14 @@ export const handleErrors = (errors, { setErrors }) => { }), ); } + if ( + errors.some((e) => e.type === ERRORS.BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT) + ) { + AppToaster.show({ + intent: Intent.DANGER, + message: intl.get('bill.total_smaller_than_paid_amount'), + }); + } }; export const useSetPrimaryBranchToForm = () => { diff --git a/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeEntriesTable.tsx b/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeEntriesTable.tsx index cd0228799..841a3f359 100644 --- a/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeEntriesTable.tsx +++ b/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeEntriesTable.tsx @@ -30,6 +30,7 @@ export default function PaymentMadeEntriesTable({ // Formik context. const { values: { vendor_id }, + errors, } = useFormikContext(); // Handle update data. @@ -63,7 +64,7 @@ export default function PaymentMadeEntriesTable({ data={entries} spinnerProps={false} payload={{ - errors: [], + errors: errors?.entries || [], updateData: handleUpdateData, currencyCode, }} diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/utils.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/utils.tsx index 8c32f017c..a2ff9988a 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/utils.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/utils.tsx @@ -112,6 +112,16 @@ export const transformErrors = (errors, { setErrors }) => { intent: Intent.DANGER, }); } + if ( + errors.some( + ({ type }) => type === ERROR.INVOICE_AMOUNT_SMALLER_THAN_PAYMENT_AMOUNT, + ) + ) { + AppToaster.show({ + message: intl.get('sale_invoice.total_smaller_than_paid_amount'), + intent: Intent.DANGER, + }); + } if ( errors.some((error) => error.type === ERROR.SALE_INVOICE_NO_IS_REQUIRED) ) { diff --git a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveItemsTable.tsx b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveItemsTable.tsx index 5b8a1be34..910403c7e 100644 --- a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveItemsTable.tsx +++ b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveItemsTable.tsx @@ -28,6 +28,7 @@ export default function PaymentReceiveItemsTable({ // Formik context. const { values: { customer_id }, + errors, } = useFormikContext(); // No results message. @@ -58,7 +59,7 @@ export default function PaymentReceiveItemsTable({ data={entries} spinnerProps={false} payload={{ - errors: [], + errors: errors?.entries || [], updateData: handleUpdateData, currencyCode, }} diff --git a/packages/webapp/src/lang/en/index.json b/packages/webapp/src/lang/en/index.json index 5522f57d6..bf780d4cf 100644 --- a/packages/webapp/src/lang/en/index.json +++ b/packages/webapp/src/lang/en/index.json @@ -653,7 +653,9 @@ "invoice_number_is_not_unqiue": "Invoice number is not unqiue", "sale_receipt_number_not_unique": "Receipt number is not unique", "sale_invoice_number_is_exists": "Sale invoice number is exists", + "sale_invoice.total_smaller_than_paid_amount": "The invoice total is smaller than the invoice paid amount.", "bill_number_exists": "Bill number exists", + "bill.total_smaller_than_paid_amount": "The bill total is smaller than the bill paid amount.", "ok": "Ok!", "quantity_cannot_be_zero_or_empty": "Quantity cannot be zero or empty.", "customer_email": "Customer email", From 528d447443a4b2ada6f1e365e0878ab6c43e30e5 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 5 Feb 2024 23:04:02 +0200 Subject: [PATCH 10/31] fix(server): Trial balance total row shouldn't show if accounts have no balances --- .../TrialBalanceSheet/TrialBalanceSheet.ts | 4 ---- .../TrialBalanceSheet/TrialBalanceSheetTable.ts | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts index 38ed3a944..8554268e3 100644 --- a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts +++ b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet.ts @@ -252,10 +252,6 @@ export default class TrialBalanceSheet extends FinancialSheet { * @return {ITrialBalanceSheetData} */ public reportData(): ITrialBalanceSheetData { - // Don't return noting if the journal has no transactions. - if (this.repository.totalAccountsLedger.isEmpty()) { - return null; - } // Retrieve accounts nodes. const accounts = this.accountsSection(this.repository.accounts); diff --git a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetTable.ts b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetTable.ts index 1cbb2e7e6..5de03eb71 100644 --- a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetTable.ts +++ b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetTable.ts @@ -46,7 +46,7 @@ export class TrialBalanceSheetTable extends R.compose( this.query = query; this.i18n = i18n; } - + /** * Retrieve the common columns for all report nodes. * @param {ITableColumnAccessor[]} @@ -123,7 +123,7 @@ export class TrialBalanceSheetTable extends R.compose( */ public tableRows = (): ITableRow[] => { return R.compose( - R.append(this.totalTableRow()), + R.unless(R.isEmpty, R.append(this.totalTableRow())), R.concat(this.accountsTableRows()) )([]); }; From 374f1acf8a48c6035d3777f63c1faffda15f5b41 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Tue, 6 Feb 2024 10:54:41 +0200 Subject: [PATCH 11/31] fix: payment receive subtotal shouldn't be rounded --- .../PaymentReceives/PaymentReceiveTransformer.ts | 13 +++++++++++++ .../PaymentReceiveDetailTableFooter.tsx | 5 ++--- .../Drawers/PaymentReceiveDetailDrawer/utils.tsx | 12 +++++------- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveTransformer.ts b/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveTransformer.ts index 531023ce3..5ca84db07 100644 --- a/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveTransformer.ts +++ b/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveTransformer.ts @@ -10,6 +10,7 @@ export class PaymentReceiveTransfromer extends Transformer { */ public includeAttributes = (): string[] => { return [ + 'subtotalFormatted', 'formattedPaymentDate', 'formattedAmount', 'formattedExchangeRate', @@ -26,6 +27,18 @@ export class PaymentReceiveTransfromer extends Transformer { return this.formatDate(payment.paymentDate); }; + /** + * Retrieve the formatted payment subtotal. + * @param {IPaymentReceive} payment + * @returns {string} + */ + protected subtotalFormatted = (payment: IPaymentReceive): string => { + return formatNumber(payment.amount, { + currencyCode: payment.currencyCode, + money: false, + }); + }; + /** * Retrieve formatted payment amount. * @param {ISaleInvoice} invoice diff --git a/packages/webapp/src/containers/Drawers/PaymentReceiveDetailDrawer/PaymentReceiveDetailTableFooter.tsx b/packages/webapp/src/containers/Drawers/PaymentReceiveDetailDrawer/PaymentReceiveDetailTableFooter.tsx index eeee13ffe..868aecc0d 100644 --- a/packages/webapp/src/containers/Drawers/PaymentReceiveDetailDrawer/PaymentReceiveDetailTableFooter.tsx +++ b/packages/webapp/src/containers/Drawers/PaymentReceiveDetailDrawer/PaymentReceiveDetailTableFooter.tsx @@ -3,10 +3,9 @@ import React from 'react'; import styled from 'styled-components'; import { - FormatNumber, + T, TotalLineTextStyle, TotalLineBorderStyle, - T, TotalLine, TotalLines, } from '@/components'; @@ -27,7 +26,7 @@ export default function PaymentReceiveDetailTableFooter() { > } - value={} + value={paymentReceive.subtotal_formatted} /> } diff --git a/packages/webapp/src/containers/Drawers/PaymentReceiveDetailDrawer/utils.tsx b/packages/webapp/src/containers/Drawers/PaymentReceiveDetailDrawer/utils.tsx index 8971f3ec6..984eb5658 100644 --- a/packages/webapp/src/containers/Drawers/PaymentReceiveDetailDrawer/utils.tsx +++ b/packages/webapp/src/containers/Drawers/PaymentReceiveDetailDrawer/utils.tsx @@ -10,7 +10,7 @@ import { MenuItem, Menu, } from '@blueprintjs/core'; -import { Icon, FormatNumberCell } from '@/components'; +import { Icon } from '@/components'; import { getColumnWidth } from '@/utils'; import { usePaymentReceiveDetailContext } from './PaymentReceiveDetailProvider'; @@ -40,9 +40,8 @@ export const usePaymentReceiveEntriesColumns = () => { }, { Header: intl.get('invoice_amount'), - accessor: 'invoice.balance', - Cell: FormatNumberCell, - width: getColumnWidth(entries, 'invoice.balance', { + accessor: 'invoice.total_formatted', + width: getColumnWidth(entries, 'invoice.total_formatted', { minWidth: 60, magicSpacing: 5, }), @@ -51,10 +50,9 @@ export const usePaymentReceiveEntriesColumns = () => { }, { Header: intl.get('amount_due'), - accessor: 'invoice.due_amount', - Cell: FormatNumberCell, + accessor: 'invoice.due_amount_formatted', align: 'right', - width: getColumnWidth(entries, 'invoice.due_amount', { + width: getColumnWidth(entries, 'invoice.due_amount_formatted', { minWidth: 60, magicSpacing: 5, }), From 0f678e61c5322ae5c299e3a18bb3bffbfd83862e Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Tue, 6 Feb 2024 20:31:48 +0200 Subject: [PATCH 12/31] fix: Decimal amounts are rounded when create a new transaction on some transactions types --- .../src/services/CreditNotes/CreditNoteTransformer.ts | 10 ++++++++++ .../VendorCredits/VendorCreditTransformer.ts | 10 ++++++++++ .../Sales/Estimates/SaleEstimateTransformer.ts | 10 ++++++++++ .../services/Sales/Invoices/ItemEntryTransformer.ts | 11 ++++++++++- .../services/Sales/Receipts/SaleReceiptTransformer.ts | 10 ++++++++++ .../CreditNoteDetailTableFooter.tsx | 5 +---- .../EstimateDetailTableFooter.tsx | 3 +-- .../containers/Drawers/EstimateDetailDrawer/utils.tsx | 5 ++--- .../ReceiptDetailDrawer/ReceiptDetailTableFooter.tsx | 2 +- .../JournalEntriesTransactions/components.tsx | 7 ++++--- .../VendorCreditDetailDrawerFooter.tsx | 3 +-- .../Drawers/VendorCreditDetailDrawer/utils.tsx | 5 ++--- 12 files changed, 62 insertions(+), 19 deletions(-) diff --git a/packages/server/src/services/CreditNotes/CreditNoteTransformer.ts b/packages/server/src/services/CreditNotes/CreditNoteTransformer.ts index f532c2eab..6ed80a6f0 100644 --- a/packages/server/src/services/CreditNotes/CreditNoteTransformer.ts +++ b/packages/server/src/services/CreditNotes/CreditNoteTransformer.ts @@ -14,6 +14,7 @@ export class CreditNoteTransformer extends Transformer { 'formattedCreditNoteDate', 'formattedAmount', 'formattedCreditsUsed', + 'formattedSubtotal', 'entries', ]; }; @@ -60,6 +61,15 @@ export class CreditNoteTransformer extends Transformer { }); }; + /** + * Retrieves the formatted subtotal. + * @param {ICreditNote} credit + * @returns {string} + */ + protected formattedSubtotal = (credit): string => { + return formatNumber(credit.amount, { money: false }); + }; + /** * Retrieves the entries of the credit note. * @param {ICreditNote} credit diff --git a/packages/server/src/services/Purchases/VendorCredits/VendorCreditTransformer.ts b/packages/server/src/services/Purchases/VendorCredits/VendorCreditTransformer.ts index 3d74ee770..be1431ac2 100644 --- a/packages/server/src/services/Purchases/VendorCredits/VendorCreditTransformer.ts +++ b/packages/server/src/services/Purchases/VendorCredits/VendorCreditTransformer.ts @@ -11,6 +11,7 @@ export class VendorCreditTransformer extends Transformer { public includeAttributes = (): string[] => { return [ 'formattedAmount', + 'formattedSubtotal', 'formattedVendorCreditDate', 'formattedCreditsRemaining', 'entries', @@ -37,6 +38,15 @@ export class VendorCreditTransformer extends Transformer { }); }; + /** + * Retrieves the vendor credit formatted subtotal. + * @param {IVendorCredit} vendorCredit + * @returns {string} + */ + protected formattedSubtotal = (vendorCredit): string => { + return formatNumber(vendorCredit.amount, { money: false }); + }; + /** * Retrieve formatted credits remaining. * @param {IVendorCredit} credit diff --git a/packages/server/src/services/Sales/Estimates/SaleEstimateTransformer.ts b/packages/server/src/services/Sales/Estimates/SaleEstimateTransformer.ts index 1102f7bd0..8cd99a9db 100644 --- a/packages/server/src/services/Sales/Estimates/SaleEstimateTransformer.ts +++ b/packages/server/src/services/Sales/Estimates/SaleEstimateTransformer.ts @@ -10,6 +10,7 @@ export class SaleEstimateTransfromer extends Transformer { */ public includeAttributes = (): string[] => { return [ + 'formattedSubtotal', 'formattedAmount', 'formattedEstimateDate', 'formattedExpirationDate', @@ -76,6 +77,15 @@ export class SaleEstimateTransfromer extends Transformer { }); }; + /** + * Retrieves the formatted invoice subtotal. + * @param {ISaleEstimate} estimate + * @returns {string} + */ + protected formattedSubtotal = (estimate: ISaleEstimate): string => { + return formatNumber(estimate.amount, { money: false }); + }; + /** * Retrieves the entries of the sale estimate. * @param {ISaleEstimate} estimate diff --git a/packages/server/src/services/Sales/Invoices/ItemEntryTransformer.ts b/packages/server/src/services/Sales/Invoices/ItemEntryTransformer.ts index ad0d88525..dbaea4862 100644 --- a/packages/server/src/services/Sales/Invoices/ItemEntryTransformer.ts +++ b/packages/server/src/services/Sales/Invoices/ItemEntryTransformer.ts @@ -8,7 +8,16 @@ export class ItemEntryTransformer extends Transformer { * @returns {Array} */ public includeAttributes = (): string[] => { - return ['rateFormatted', 'totalFormatted']; + return ['quantityFormatted', 'rateFormatted', 'totalFormatted']; + }; + + /** + * Retrieves the formatted quantitty of item entry. + * @param {IItemEntry} entry + * @returns {string} + */ + protected quantityFormatted = (entry: IItemEntry): string => { + return formatNumber(entry.quantity, { money: false }); }; /** diff --git a/packages/server/src/services/Sales/Receipts/SaleReceiptTransformer.ts b/packages/server/src/services/Sales/Receipts/SaleReceiptTransformer.ts index c8b950711..9e5d3a127 100644 --- a/packages/server/src/services/Sales/Receipts/SaleReceiptTransformer.ts +++ b/packages/server/src/services/Sales/Receipts/SaleReceiptTransformer.ts @@ -12,6 +12,7 @@ export class SaleReceiptTransformer extends Transformer { */ public includeAttributes = (): string[] => { return [ + 'formattedSubtotal', 'formattedAmount', 'formattedReceiptDate', 'formattedClosedAtDate', @@ -37,6 +38,15 @@ export class SaleReceiptTransformer extends Transformer { return this.formatDate(receipt.closedAt); }; + /** + * Retrieves the estimate formatted subtotal. + * @param {ISaleReceipt} receipt + * @returns {string} + */ + protected formattedSubtotal = (receipt: ISaleReceipt): string => { + return formatNumber(receipt.amount, { money: false }); + }; + /** * Retrieve formatted invoice amount. * @param {ISaleReceipt} estimate diff --git a/packages/webapp/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailTableFooter.tsx b/packages/webapp/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailTableFooter.tsx index 66ecf1b66..6171686cf 100644 --- a/packages/webapp/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailTableFooter.tsx +++ b/packages/webapp/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailTableFooter.tsx @@ -1,12 +1,9 @@ // @ts-nocheck -import React from 'react'; import styled from 'styled-components'; - import { T, TotalLines, TotalLine, - FormatNumber, TotalLineBorderStyle, TotalLineTextStyle, } from '@/components'; @@ -23,7 +20,7 @@ export default function CreditNoteDetailTableFooter() { } - value={} + value={creditNote.formatted_subtotal} /> } diff --git a/packages/webapp/src/containers/Drawers/EstimateDetailDrawer/EstimateDetailTableFooter.tsx b/packages/webapp/src/containers/Drawers/EstimateDetailDrawer/EstimateDetailTableFooter.tsx index c5a79c935..8bc3ee96a 100644 --- a/packages/webapp/src/containers/Drawers/EstimateDetailDrawer/EstimateDetailTableFooter.tsx +++ b/packages/webapp/src/containers/Drawers/EstimateDetailDrawer/EstimateDetailTableFooter.tsx @@ -8,7 +8,6 @@ import { TotalLine, TotalLineBorderStyle, TotalLineTextStyle, - FormatNumber, } from '@/components'; import { useEstimateDetailDrawerContext } from './EstimateDetailDrawerProvider'; @@ -23,7 +22,7 @@ export default function EstimateDetailTableFooter() { } - value={} + value={estimate.formatted_subtotal} borderStyle={TotalLineBorderStyle.SingleDark} /> { }, { Header: intl.get('quantity'), - accessor: 'quantity', + accessor: 'quantity_formatted', Cell: FormatNumberCell, - width: getColumnWidth(entries, 'quantity', { + width: getColumnWidth(entries, 'quantity_formatted', { minWidth: 60, magicSpacing: 5, }), @@ -59,7 +59,6 @@ export const useEstimateReadonlyEntriesColumns = () => { { Header: intl.get('amount'), accessor: 'total_formatted', - Cell: FormatNumberCell, width: getColumnWidth(entries, 'total_formatted', { minWidth: 60, magicSpacing: 5, diff --git a/packages/webapp/src/containers/Drawers/ReceiptDetailDrawer/ReceiptDetailTableFooter.tsx b/packages/webapp/src/containers/Drawers/ReceiptDetailDrawer/ReceiptDetailTableFooter.tsx index 657606f17..b5687a091 100644 --- a/packages/webapp/src/containers/Drawers/ReceiptDetailDrawer/ReceiptDetailTableFooter.tsx +++ b/packages/webapp/src/containers/Drawers/ReceiptDetailDrawer/ReceiptDetailTableFooter.tsx @@ -23,7 +23,7 @@ export default function ReceiptDetailTableFooter() { } - value={receipt.formatted_amount} + value={receipt.formatted_subtotal} /> } diff --git a/packages/webapp/src/containers/Drawers/VendorCreditDetailDrawer/JournalEntriesTransactions/components.tsx b/packages/webapp/src/containers/Drawers/VendorCreditDetailDrawer/JournalEntriesTransactions/components.tsx index 247d21a46..99c8101de 100644 --- a/packages/webapp/src/containers/Drawers/VendorCreditDetailDrawer/JournalEntriesTransactions/components.tsx +++ b/packages/webapp/src/containers/Drawers/VendorCreditDetailDrawer/JournalEntriesTransactions/components.tsx @@ -13,7 +13,6 @@ export const useJournalEntriesTransactionsColumns = () => { () => [ { Header: intl.get('date'), - accessor: 'date', accessor: 'formatted_date', Cell: FormatDateCell, width: 140, @@ -34,15 +33,17 @@ export const useJournalEntriesTransactionsColumns = () => { }, { Header: intl.get('credit'), - accessor: ({ credit }) => credit.formatted_amount, + accessor: 'credit.formatted_amount', width: 100, className: 'credit', + align: 'right', }, { Header: intl.get('debit'), - accessor: ({ debit }) => debit.formatted_amount, + accessor: 'debit.formatted_amount', width: 100, className: 'debit', + align: 'right', }, ], [], diff --git a/packages/webapp/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetailDrawerFooter.tsx b/packages/webapp/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetailDrawerFooter.tsx index e11cbce67..0bfd435e9 100644 --- a/packages/webapp/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetailDrawerFooter.tsx +++ b/packages/webapp/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetailDrawerFooter.tsx @@ -8,7 +8,6 @@ import { TotalLine, TotalLineBorderStyle, TotalLineTextStyle, - FormatNumber, } from '@/components'; import { useVendorCreditDetailDrawerContext } from './VendorCreditDetailDrawerProvider'; @@ -23,7 +22,7 @@ export default function VendorCreditDetailDrawerFooter() { } - value={vendorCredit.formatted_amount} + value={vendorCredit.formatted_subtotal} borderStyle={TotalLineBorderStyle.SingleDark} /> { }, { Header: intl.get('quantity'), - accessor: 'quantity', - Cell: FormatNumberCell, - width: getColumnWidth(entries, 'quantity', { + accessor: 'quantity_formatted', + width: getColumnWidth(entries, 'quantity_formatted', { minWidth: 60, magicSpacing: 5, }), From 0c61f85707c83c4ebd6e6cd4866bc147034685d6 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Tue, 6 Feb 2024 20:38:25 +0200 Subject: [PATCH 13/31] chore: remove format number from estimate quantity --- .../src/containers/Drawers/EstimateDetailDrawer/utils.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/webapp/src/containers/Drawers/EstimateDetailDrawer/utils.tsx b/packages/webapp/src/containers/Drawers/EstimateDetailDrawer/utils.tsx index 84acab544..eb9def4e6 100644 --- a/packages/webapp/src/containers/Drawers/EstimateDetailDrawer/utils.tsx +++ b/packages/webapp/src/containers/Drawers/EstimateDetailDrawer/utils.tsx @@ -2,7 +2,7 @@ import React from 'react'; import intl from 'react-intl-universal'; import { getColumnWidth } from '@/utils'; -import { FormatNumberCell, TextOverviewTooltipCell } from '@/components'; +import { TextOverviewTooltipCell } from '@/components'; import { useEstimateDetailDrawerContext } from './EstimateDetailDrawerProvider'; /** @@ -36,7 +36,6 @@ export const useEstimateReadonlyEntriesColumns = () => { { Header: intl.get('quantity'), accessor: 'quantity_formatted', - Cell: FormatNumberCell, width: getColumnWidth(entries, 'quantity_formatted', { minWidth: 60, magicSpacing: 5, From 17dbe9713b97267a350a9cd52076ed5ba91af857 Mon Sep 17 00:00:00 2001 From: "a.nasouf" Date: Sat, 10 Feb 2024 19:59:12 +0200 Subject: [PATCH 14/31] fix: remove normalizeEmail function --- packages/server/src/api/controllers/Contacts/Contacts.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/server/src/api/controllers/Contacts/Contacts.ts b/packages/server/src/api/controllers/Contacts/Contacts.ts index 751654a8e..24b99e09f 100644 --- a/packages/server/src/api/controllers/Contacts/Contacts.ts +++ b/packages/server/src/api/controllers/Contacts/Contacts.ts @@ -153,7 +153,6 @@ export default class ContactsController extends BaseController { check('email') .optional({ nullable: true }) .isString() - .normalizeEmail({ gmail_remove_dots: false }) .isEmail() .isLength({ max: DATATYPES_LENGTH.STRING }), check('website') From 9395ef094a4edcb1b265208bbfbeb56e2ade7b30 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sat, 10 Feb 2024 22:20:54 +0200 Subject: [PATCH 15/31] feat(server): wip printing financial reports --- .../scss/modules/financial-sheet.scss | 41 ++++++++++++ .../views/modules/financial-sheet.pug | 21 +++++++ .../FinancialStatements/APAgingSummary.ts | 10 +++ .../FinancialStatements/ARAgingSummary.ts | 12 +++- .../FinancialStatements/BalanceSheet.ts | 10 +++ .../FinancialStatements/CashFlow/CashFlow.ts | 10 +++ .../FinancialStatements/JournalSheet.ts | 9 +++ .../FinancialStatements/ProfitLossSheet.ts | 9 +++ .../FinancialStatements/SalesByItems.ts | 11 +++- .../FinancialStatements/TrialBalanceSheet.ts | 13 +++- .../VendorBalanceSummary/index.ts | 12 ++++ .../AgingSummary/APAgingSummaryApplication.ts | 14 +++++ .../APAgingSummaryPdfInjectable.ts | 34 ++++++++++ .../AgingSummary/ARAgingSummaryApplication.ts | 14 +++++ .../ARAgingSummaryPdfInjectable.ts | 34 ++++++++++ .../BalanceSheet/BalanceSheetApplication.ts | 10 +++ .../BalanceSheetExportInjectable.ts | 17 +++++ .../BalanceSheet/BalanceSheetPdfInjectable.ts | 34 ++++++++++ .../BalanceSheet/BalanceSheetTable.ts | 4 +- .../CashFlow/CashflowSheetApplication.ts | 17 +++++ .../CashFlow/CashflowTablePdfInjectable.ts | 33 ++++++++++ .../CustomerBalanceSummaryApplication.ts | 14 +++++ .../CustomerBalanceSummaryPdf.ts | 34 ++++++++++ .../JournalSheet/JournalSheetApplication.ts | 14 +++++ .../JournalSheet/JournalSheetPdfInjectable.ts | 34 ++++++++++ .../ProfitLossSheetApplication.ts | 14 +++++ .../ProfitLossSheetExportInjectable.ts | 7 +++ .../ProfitLossTablePdfInjectable.ts | 34 ++++++++++ .../SalesByItems/SalesByItemsApplication.ts | 17 +++++ .../SalesByItems/SalesByItemsPdfInjectable.ts | 34 ++++++++++ .../FinancialStatements/TableSheetPdf.ts | 62 +++++++++++++++++++ .../TrialBalanceExportInjectable.ts | 19 +++++- .../TrialBalanceSheetApplication.ts | 10 +++ .../TrialBalanceSheetPdfInjectsable.ts | 34 ++++++++++ .../VendorBalanceSummaryApplication.ts | 14 +++++ .../VendorBalanceSummaryPdf.ts | 34 ++++++++++ 36 files changed, 738 insertions(+), 6 deletions(-) create mode 100644 packages/server/resources/scss/modules/financial-sheet.scss create mode 100644 packages/server/resources/views/modules/financial-sheet.pug create mode 100644 packages/server/src/services/FinancialStatements/AgingSummary/APAgingSummaryPdfInjectable.ts create mode 100644 packages/server/src/services/FinancialStatements/AgingSummary/ARAgingSummaryPdfInjectable.ts create mode 100644 packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetPdfInjectable.ts create mode 100644 packages/server/src/services/FinancialStatements/CashFlow/CashflowTablePdfInjectable.ts create mode 100644 packages/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryPdf.ts create mode 100644 packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetPdfInjectable.ts create mode 100644 packages/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossTablePdfInjectable.ts create mode 100644 packages/server/src/services/FinancialStatements/SalesByItems/SalesByItemsPdfInjectable.ts create mode 100644 packages/server/src/services/FinancialStatements/TableSheetPdf.ts create mode 100644 packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetPdfInjectsable.ts create mode 100644 packages/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryPdf.ts diff --git a/packages/server/resources/scss/modules/financial-sheet.scss b/packages/server/resources/scss/modules/financial-sheet.scss new file mode 100644 index 000000000..5924dc2a3 --- /dev/null +++ b/packages/server/resources/scss/modules/financial-sheet.scss @@ -0,0 +1,41 @@ + +.sheet{} +.sheet__company-name{ + margin: 0; + font-size: 1.6rem; +} +.sheet__sheet-type { + margin: 0 +} +.sheet__sheet-date { + margin-top: 0.5rem; +} + +.sheet__header { + text-align: center; + margin-bottom: 2rem; +} + +.sheet__table { + border-top: 1px solid #000; + table-layout: fixed; + border-spacing: 0; + text-align: left; +} + +.sheet__table thead th { + color: #000; + border-bottom: 1px solid #000000; + padding: 0.5rem; +} + +.sheet__table tbody td { + border-bottom: 0; + padding-top: 0.32rem; + padding-bottom: 0.32rem; + padding-left: 0.5rem; + padding-right: 0.5rem; + color: #252A31; + border-bottom: 1px solid rgb(37, 42, 49); + +} \ No newline at end of file diff --git a/packages/server/resources/views/modules/financial-sheet.pug b/packages/server/resources/views/modules/financial-sheet.pug new file mode 100644 index 000000000..019c74626 --- /dev/null +++ b/packages/server/resources/views/modules/financial-sheet.pug @@ -0,0 +1,21 @@ +block head + style + //- include ../../css/modules/financial-sheet.css + +block content + .sheet + .sheet__header + .sheet__company-name=organizationName + .sheet__sheet-type=sheetName + .sheet__sheet-date=sheetDate + + table.sheet__table + thead + tr + each column in table.columns + th= column.label + tbody + each row in table.rows + tr + each cell in row.cells + td= cell.value \ No newline at end of file diff --git a/packages/server/src/api/controllers/FinancialStatements/APAgingSummary.ts b/packages/server/src/api/controllers/FinancialStatements/APAgingSummary.ts index 5d626896c..d87873a2b 100644 --- a/packages/server/src/api/controllers/FinancialStatements/APAgingSummary.ts +++ b/packages/server/src/api/controllers/FinancialStatements/APAgingSummary.ts @@ -71,6 +71,7 @@ export default class APAgingSummaryReportController extends BaseFinancialReportC ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_XLSX, + ACCEPT_TYPE.APPLICATION_PDF ]); // Retrieves the json table format. if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) { @@ -98,6 +99,15 @@ export default class APAgingSummaryReportController extends BaseFinancialReportC 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ); return res.send(buffer); + // Retrieves the pdf format. + } else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) { + const pdfContent = await this.APAgingSummaryApp.pdf(tenantId, filter); + + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + return res.send(pdfContent); // Retrieves the json format. } else { const sheet = await this.APAgingSummaryApp.sheet(tenantId, filter); diff --git a/packages/server/src/api/controllers/FinancialStatements/ARAgingSummary.ts b/packages/server/src/api/controllers/FinancialStatements/ARAgingSummary.ts index 10e42e900..86b4b920a 100644 --- a/packages/server/src/api/controllers/FinancialStatements/ARAgingSummary.ts +++ b/packages/server/src/api/controllers/FinancialStatements/ARAgingSummary.ts @@ -11,7 +11,7 @@ import { ACCEPT_TYPE } from '@/interfaces/Http'; @Service() export default class ARAgingSummaryReportController extends BaseFinancialReportController { @Inject() - ARAgingSummaryApp: ARAgingSummaryApplication; + private ARAgingSummaryApp: ARAgingSummaryApplication; /** * Router constructor. @@ -69,6 +69,7 @@ export default class ARAgingSummaryReportController extends BaseFinancialReportC ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_XLSX, + ACCEPT_TYPE.APPLICATION_PDF ]); // Retrieves the xlsx format. if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) { @@ -96,6 +97,15 @@ export default class ARAgingSummaryReportController extends BaseFinancialReportC res.setHeader('Content-Type', 'text/csv'); return res.send(buffer); + // Retrieves the pdf format. + } else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) { + const pdfContent = await this.ARAgingSummaryApp.pdf(tenantId, filter); + + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + return res.send(pdfContent); // Retrieves the json format. } else { const sheet = await this.ARAgingSummaryApp.sheet(tenantId, filter); diff --git a/packages/server/src/api/controllers/FinancialStatements/BalanceSheet.ts b/packages/server/src/api/controllers/FinancialStatements/BalanceSheet.ts index 0af53d723..fda717a38 100644 --- a/packages/server/src/api/controllers/FinancialStatements/BalanceSheet.ts +++ b/packages/server/src/api/controllers/FinancialStatements/BalanceSheet.ts @@ -101,6 +101,7 @@ export default class BalanceSheetStatementController extends BaseFinancialReport ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_XLSX, ACCEPT_TYPE.APPLICATION_CSV, + ACCEPT_TYPE.APPLICATION_PDF, ]); // Retrieves the json table format. if (ACCEPT_TYPE.APPLICATION_JSON_TABLE == acceptType) { @@ -128,6 +129,15 @@ export default class BalanceSheetStatementController extends BaseFinancialReport 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ); return res.send(buffer); + // Retrieves the pdf format. + } else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) { + const pdfContent = await this.balanceSheetApp.pdf(tenantId, filter); + + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + res.send(pdfContent); } else { const sheet = await this.balanceSheetApp.sheet(tenantId, filter); diff --git a/packages/server/src/api/controllers/FinancialStatements/CashFlow/CashFlow.ts b/packages/server/src/api/controllers/FinancialStatements/CashFlow/CashFlow.ts index bab04246d..bc24b5379 100644 --- a/packages/server/src/api/controllers/FinancialStatements/CashFlow/CashFlow.ts +++ b/packages/server/src/api/controllers/FinancialStatements/CashFlow/CashFlow.ts @@ -79,6 +79,7 @@ export default class CashFlowController extends BaseFinancialReportController { ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_XLSX, + ACCEPT_TYPE.APPLICATION_PDF ]); // Retrieves the json table format. if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) { @@ -106,6 +107,15 @@ export default class CashFlowController extends BaseFinancialReportController { 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ); return res.send(buffer); + // Retrieves the pdf format. + } else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) { + const pdfContent = await this.cashflowSheetApp.pdf(tenantId, filter); + + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + return res.send(pdfContent); // Retrieves the json format. } else { const cashflow = await this.cashflowSheetApp.sheet(tenantId, filter); diff --git a/packages/server/src/api/controllers/FinancialStatements/JournalSheet.ts b/packages/server/src/api/controllers/FinancialStatements/JournalSheet.ts index 871bc9af8..0355766db 100644 --- a/packages/server/src/api/controllers/FinancialStatements/JournalSheet.ts +++ b/packages/server/src/api/controllers/FinancialStatements/JournalSheet.ts @@ -72,6 +72,7 @@ export default class JournalSheetController extends BaseFinancialReportControlle ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_XLSX, ACCEPT_TYPE.APPLICATION_CSV, + ACCEPT_TYPE.APPLICATION_PDF, ]); // Retrieves the json table format. @@ -97,6 +98,14 @@ export default class JournalSheetController extends BaseFinancialReportControlle ); return res.send(buffer); // Retrieves the json format. + } else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) { + const pdfContent = await this.journalSheetApp.pdf(tenantId, filter); + + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + res.send(pdfContent); } else { const sheet = await this.journalSheetApp.sheet(tenantId, filter); diff --git a/packages/server/src/api/controllers/FinancialStatements/ProfitLossSheet.ts b/packages/server/src/api/controllers/FinancialStatements/ProfitLossSheet.ts index 8c2404335..995df07b4 100644 --- a/packages/server/src/api/controllers/FinancialStatements/ProfitLossSheet.ts +++ b/packages/server/src/api/controllers/FinancialStatements/ProfitLossSheet.ts @@ -96,6 +96,7 @@ export default class ProfitLossSheetController extends BaseFinancialReportContro ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_XLSX, + ACCEPT_TYPE.APPLICATION_PDF, ]); try { // Retrieves the csv format. @@ -125,6 +126,14 @@ export default class ProfitLossSheetController extends BaseFinancialReportContro ); return res.send(sheet); // Retrieves the json format. + } else if (acceptType === ACCEPT_TYPE.APPLICATION_PDF) { + const pdfContent = await this.profitLossSheetApp.pdf(tenantId, filter); + + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + return res.send(pdfContent); } else { const sheet = await this.profitLossSheetApp.sheet(tenantId, filter); diff --git a/packages/server/src/api/controllers/FinancialStatements/SalesByItems.ts b/packages/server/src/api/controllers/FinancialStatements/SalesByItems.ts index bac67231c..28ab611c0 100644 --- a/packages/server/src/api/controllers/FinancialStatements/SalesByItems.ts +++ b/packages/server/src/api/controllers/FinancialStatements/SalesByItems.ts @@ -11,7 +11,7 @@ import { SalesByItemsApplication } from '@/services/FinancialStatements/SalesByI @Service() export default class SalesByItemsReportController extends BaseFinancialReportController { @Inject() - salesByItemsApp: SalesByItemsApplication; + private salesByItemsApp: SalesByItemsApplication; /** * Router constructor. @@ -71,6 +71,7 @@ export default class SalesByItemsReportController extends BaseFinancialReportCon ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_XLSX, + ACCEPT_TYPE.APPLICATION_PDF, ]); // Retrieves the csv format. if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) { @@ -96,6 +97,14 @@ export default class SalesByItemsReportController extends BaseFinancialReportCon ); return res.send(buffer); // Retrieves the json format. + } else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) { + const pdfContent = await this.salesByItemsApp.pdf(tenantId, filter); + + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + return res.send(pdfContent); } else { const sheet = await this.salesByItemsApp.sheet(tenantId, filter); return res.status(200).send(sheet); diff --git a/packages/server/src/api/controllers/FinancialStatements/TrialBalanceSheet.ts b/packages/server/src/api/controllers/FinancialStatements/TrialBalanceSheet.ts index ce23c1071..b92e355a2 100644 --- a/packages/server/src/api/controllers/FinancialStatements/TrialBalanceSheet.ts +++ b/packages/server/src/api/controllers/FinancialStatements/TrialBalanceSheet.ts @@ -3,7 +3,6 @@ import { Request, Response, Router, NextFunction } from 'express'; import { query, ValidationChain } from 'express-validator'; import { castArray } from 'lodash'; import asyncMiddleware from '@/api/middleware/asyncMiddleware'; -import TrialBalanceSheetService from '@/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetInjectable'; import BaseFinancialReportController from './BaseFinancialReportController'; import { AbilitySubject, ReportsAction } from '@/interfaces'; import CheckPolicies from '@/api/middleware/CheckPolicies'; @@ -81,6 +80,7 @@ export default class TrialBalanceSheetController extends BaseFinancialReportCont ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_XLSX, + ACCEPT_TYPE.APPLICATION_PDF, ]); // Retrieves in json table format. if (acceptType === ACCEPT_TYPE.APPLICATION_JSON_TABLE) { @@ -109,6 +109,17 @@ export default class TrialBalanceSheetController extends BaseFinancialReportCont res.setHeader('Content-Type', 'text/csv'); return res.send(buffer); + // Retrieves in pdf format. + } else if (acceptType === ACCEPT_TYPE.APPLICATION_PDF) { + const pdfContent = await this.trialBalanceSheetApp.pdf( + tenantId, + filter + ); + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + res.send(pdfContent); // Retrieves in json format. } else { const { data, query, meta } = await this.trialBalanceSheetApp.sheet( diff --git a/packages/server/src/api/controllers/FinancialStatements/VendorBalanceSummary/index.ts b/packages/server/src/api/controllers/FinancialStatements/VendorBalanceSummary/index.ts index ade69cb62..f1e26a0ed 100644 --- a/packages/server/src/api/controllers/FinancialStatements/VendorBalanceSummary/index.ts +++ b/packages/server/src/api/controllers/FinancialStatements/VendorBalanceSummary/index.ts @@ -72,6 +72,7 @@ export default class VendorBalanceSummaryReportController extends BaseFinancialR ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_XLSX, + ACCEPT_TYPE.APPLICATION_PDF, ]); // Retrieves the csv format. @@ -100,6 +101,17 @@ export default class VendorBalanceSummaryReportController extends BaseFinancialR filter ); return res.status(200).send(table); + // Retrieves the pdf format. + } else if (acceptType === ACCEPT_TYPE.APPLICATION_PDF) { + const pdfContent = await this.vendorBalanceSummaryApp.pdf( + tenantId, + filter + ); + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + return res.send(pdfContent); // Retrieves the json format. } else { const sheet = await this.vendorBalanceSummaryApp.sheet( diff --git a/packages/server/src/services/FinancialStatements/AgingSummary/APAgingSummaryApplication.ts b/packages/server/src/services/FinancialStatements/AgingSummary/APAgingSummaryApplication.ts index 73c08b2d8..52e6db251 100644 --- a/packages/server/src/services/FinancialStatements/AgingSummary/APAgingSummaryApplication.ts +++ b/packages/server/src/services/FinancialStatements/AgingSummary/APAgingSummaryApplication.ts @@ -3,6 +3,7 @@ import { APAgingSummaryExportInjectable } from './APAgingSummaryExportInjectable import { APAgingSummaryTableInjectable } from './APAgingSummaryTableInjectable'; import { IAPAgingSummaryQuery } from '@/interfaces'; import { APAgingSummaryService } from './APAgingSummaryService'; +import { APAgingSummaryPdfInjectable } from './APAgingSummaryPdfInjectable'; @Service() export class APAgingSummaryApplication { @@ -15,6 +16,9 @@ export class APAgingSummaryApplication { @Inject() private APAgingSummarySheet: APAgingSummaryService; + @Inject() + private APAgingSumaryPdf: APAgingSummaryPdfInjectable; + /** * Retrieve the A/P aging summary in sheet format. * @param {number} tenantId @@ -50,4 +54,14 @@ export class APAgingSummaryApplication { public xlsx(tenantId: number, query: IAPAgingSummaryQuery) { return this.APAgingSummaryExport.xlsx(tenantId, query); } + + /** + * Retrieves the A/P aging summary in pdf format. + * @param {number} tenantId + * @param {IAPAgingSummaryQuery} query + * @returns {Promise} + */ + public pdf(tenantId: number, query: IAPAgingSummaryQuery) { + return this.APAgingSumaryPdf.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/AgingSummary/APAgingSummaryPdfInjectable.ts b/packages/server/src/services/FinancialStatements/AgingSummary/APAgingSummaryPdfInjectable.ts new file mode 100644 index 000000000..e1a74f80a --- /dev/null +++ b/packages/server/src/services/FinancialStatements/AgingSummary/APAgingSummaryPdfInjectable.ts @@ -0,0 +1,34 @@ +import { Inject, Service } from 'typedi'; +import { IAPAgingSummaryQuery } from '@/interfaces'; +import { TableSheetPdf } from '../TableSheetPdf'; +import { APAgingSummaryTableInjectable } from './APAgingSummaryTableInjectable'; + +@Service() +export class APAgingSummaryPdfInjectable { + @Inject() + private APAgingSummaryTable: APAgingSummaryTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Converts the given A/P aging summary sheet table to pdf. + * @param {number} tenantId - Tenant ID. + * @param {IAPAgingSummaryQuery} query - Balance sheet query. + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: IAPAgingSummaryQuery + ): Promise { + const table = await this.APAgingSummaryTable.table(tenantId, query); + const sheetName = 'AR Aging Summary'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} diff --git a/packages/server/src/services/FinancialStatements/AgingSummary/ARAgingSummaryApplication.ts b/packages/server/src/services/FinancialStatements/AgingSummary/ARAgingSummaryApplication.ts index d3282ca4b..f24932f13 100644 --- a/packages/server/src/services/FinancialStatements/AgingSummary/ARAgingSummaryApplication.ts +++ b/packages/server/src/services/FinancialStatements/AgingSummary/ARAgingSummaryApplication.ts @@ -3,6 +3,7 @@ import { IARAgingSummaryQuery } from '@/interfaces'; import { ARAgingSummaryTableInjectable } from './ARAgingSummaryTableInjectable'; import { ARAgingSummaryExportInjectable } from './ARAgingSummaryExportInjectable'; import ARAgingSummaryService from './ARAgingSummaryService'; +import { ARAgingSummaryPdfInjectable } from './ARAgingSummaryPdfInjectable'; @Service() export class ARAgingSummaryApplication { @@ -15,6 +16,9 @@ export class ARAgingSummaryApplication { @Inject() private ARAgingSummarySheet: ARAgingSummaryService; + @Inject() + private ARAgingSummaryPdf: ARAgingSummaryPdfInjectable; + /** * Retrieve the A/R aging summary sheet. * @param {number} tenantId @@ -50,4 +54,14 @@ export class ARAgingSummaryApplication { public csv(tenantId: number, query: IARAgingSummaryQuery) { return this.ARAgingSummaryExport.csv(tenantId, query); } + + /** + * Retrieves the A/R aging summary in pdf format. + * @param {number} tenantId + * @param {IARAgingSummaryQuery} query + * @returns {Promise} + */ + public pdf(tenantId: number, query: IARAgingSummaryQuery) { + return this.ARAgingSummaryPdf.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/AgingSummary/ARAgingSummaryPdfInjectable.ts b/packages/server/src/services/FinancialStatements/AgingSummary/ARAgingSummaryPdfInjectable.ts new file mode 100644 index 000000000..57ea3fef5 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/AgingSummary/ARAgingSummaryPdfInjectable.ts @@ -0,0 +1,34 @@ +import { Inject, Service } from 'typedi'; +import { IARAgingSummaryQuery } from '@/interfaces'; +import { TableSheetPdf } from '../TableSheetPdf'; +import { ARAgingSummaryTableInjectable } from './ARAgingSummaryTableInjectable'; + +@Service() +export class ARAgingSummaryPdfInjectable { + @Inject() + private ARAgingSummaryTable: ARAgingSummaryTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Converts the given balance sheet table to pdf. + * @param {number} tenantId - Tenant ID. + * @param {IBalanceSheetQuery} query - Balance sheet query. + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: IARAgingSummaryQuery + ): Promise { + const table = await this.ARAgingSummaryTable.table(tenantId, query); + const sheetName = 'AR Aging Summary'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} diff --git a/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetApplication.ts b/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetApplication.ts index 01ab77bfe..0c965a5f5 100644 --- a/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetApplication.ts +++ b/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetApplication.ts @@ -54,4 +54,14 @@ export class BalanceSheetApplication { public csv(tenantId: number, query: IBalanceSheetQuery): Promise { return this.balanceSheetExport.csv(tenantId, query); } + + /** + * Retrieves the balance sheet in pdf format. + * @param {number} tenantId + * @param {IBalanceSheetQuery} query + * @returns {Promise} + */ + public pdf(tenantId: number, query: IBalanceSheetQuery) { + return this.balanceSheetExport.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetExportInjectable.ts b/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetExportInjectable.ts index 2c43d5f80..9198d8536 100644 --- a/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetExportInjectable.ts +++ b/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetExportInjectable.ts @@ -2,12 +2,16 @@ import { Inject, Service } from 'typedi'; import { BalanceSheetTableInjectable } from './BalanceSheetTableInjectable'; import { TableSheet } from '@/lib/Xlsx/TableSheet'; import { IBalanceSheetQuery } from '@/interfaces'; +import { BalanceSheetPdfInjectable } from './BalanceSheetPdfInjectable'; @Service() export class BalanceSheetExportInjectable { @Inject() private balanceSheetTable: BalanceSheetTableInjectable; + @Inject() + private balanceSheetPdf: BalanceSheetPdfInjectable; + /** * Retrieves the trial balance sheet in XLSX format. * @param {number} tenantId @@ -40,4 +44,17 @@ export class BalanceSheetExportInjectable { return tableCsv; } + + /** + * Retrieves the balance sheet in pdf format. + * @param {number} tenantId + * @param {IBalanceSheetQuery} query + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: IBalanceSheetQuery + ): Promise { + return this.balanceSheetPdf.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetPdfInjectable.ts b/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetPdfInjectable.ts new file mode 100644 index 000000000..44fd991b3 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetPdfInjectable.ts @@ -0,0 +1,34 @@ +import { Inject, Service } from 'typedi'; +import { IBalanceSheetQuery } from '@/interfaces'; +import { BalanceSheetTableInjectable } from './BalanceSheetTableInjectable'; +import { TableSheetPdf } from '../TableSheetPdf'; + +@Service() +export class BalanceSheetPdfInjectable { + @Inject() + private balanceSheetTable: BalanceSheetTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Converts the given balance sheet table to pdf. + * @param {number} tenantId - Tenant ID. + * @param {IBalanceSheetQuery} query - Balance sheet query. + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: IBalanceSheetQuery + ): Promise { + const table = await this.balanceSheetTable.table(tenantId, query); + const sheetName = 'Balance Sheet'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} diff --git a/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetTable.ts b/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetTable.ts index 349b1a8f4..088362766 100644 --- a/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetTable.ts +++ b/packages/server/src/services/FinancialStatements/BalanceSheet/BalanceSheetTable.ts @@ -43,13 +43,13 @@ export default class BalanceSheetTable extends R.compose( /** * @param {} */ - reportData: IBalanceSheetStatementData; + private reportData: IBalanceSheetStatementData; /** * Balance sheet query. * @parma {} */ - query: BalanceSheetQuery; + private query: BalanceSheetQuery; /** * Constructor method. diff --git a/packages/server/src/services/FinancialStatements/CashFlow/CashflowSheetApplication.ts b/packages/server/src/services/FinancialStatements/CashFlow/CashflowSheetApplication.ts index 0fd8b7357..72a587f52 100644 --- a/packages/server/src/services/FinancialStatements/CashFlow/CashflowSheetApplication.ts +++ b/packages/server/src/services/FinancialStatements/CashFlow/CashflowSheetApplication.ts @@ -3,6 +3,7 @@ import { CashflowExportInjectable } from './CashflowExportInjectable'; import { ICashFlowStatementQuery } from '@/interfaces'; import CashFlowStatementService from './CashFlowService'; import { CashflowTableInjectable } from './CashflowTableInjectable'; +import { CashflowTablePdfInjectable } from './CashflowTablePdfInjectable'; @Service() export class CashflowSheetApplication { @@ -15,6 +16,9 @@ export class CashflowSheetApplication { @Inject() private cashflowTable: CashflowTableInjectable; + @Inject() + private cashflowPdf: CashflowTablePdfInjectable; + /** * Retrieves the cashflow sheet * @param {number} tenantId @@ -55,4 +59,17 @@ export class CashflowSheetApplication { ): Promise { return this.cashflowExport.csv(tenantId, query); } + + /** + * Retrieves the cashflow sheet in pdf format. + * @param {number} tenantId + * @param {ICashFlowStatementQuery} query + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: ICashFlowStatementQuery + ): Promise { + return this.cashflowPdf.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/CashFlow/CashflowTablePdfInjectable.ts b/packages/server/src/services/FinancialStatements/CashFlow/CashflowTablePdfInjectable.ts new file mode 100644 index 000000000..eaab60db4 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/CashFlow/CashflowTablePdfInjectable.ts @@ -0,0 +1,33 @@ +import { Inject } from 'typedi'; +import { CashflowTableInjectable } from './CashflowTableInjectable'; +import { TableSheetPdf } from '../TableSheetPdf'; +import { ICashFlowStatementQuery } from '@/interfaces'; + +export class CashflowTablePdfInjectable { + @Inject() + private cashflowTable: CashflowTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Converts the given cashflow sheet table to pdf. + * @param {number} tenantId - Tenant ID. + * @param {IBalanceSheetQuery} query - Balance sheet query. + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: ICashFlowStatementQuery + ): Promise { + const table = await this.cashflowTable.table(tenantId, query); + const sheetName = 'Cashflow Sheet'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} diff --git a/packages/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryApplication.ts b/packages/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryApplication.ts index 964cd91a9..a1693e7d8 100644 --- a/packages/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryApplication.ts +++ b/packages/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryApplication.ts @@ -3,6 +3,7 @@ import { CustomerBalanceSummaryExportInjectable } from './CustomerBalanceSummary import { CustomerBalanceSummaryTableInjectable } from './CustomerBalanceSummaryTableInjectable'; import { ICustomerBalanceSummaryQuery } from '@/interfaces'; import { CustomerBalanceSummaryService } from './CustomerBalanceSummaryService'; +import { CustomerBalanceSummaryPdf } from './CustomerBalanceSummaryPdf'; @Service() export class CustomerBalanceSummaryApplication { @@ -15,6 +16,9 @@ export class CustomerBalanceSummaryApplication { @Inject() private customerBalanceSummarySheet: CustomerBalanceSummaryService; + @Inject() + private customerBalanceSummaryPdf: CustomerBalanceSummaryPdf; + /** * Retrieves the customer balance sheet in json format. * @param {number} tenantId @@ -57,4 +61,14 @@ export class CustomerBalanceSummaryApplication { public csv(tenantId: number, query: ICustomerBalanceSummaryQuery) { return this.customerBalanceSummaryExport.csv(tenantId, query); } + + /** + * Retrieves the customer balance sheet in PDF format. + * @param {number} tenantId + * @param {ICustomerBalanceSummaryQuery} query + * @returns {Promise} + */ + public pdf(tenantId: number, query: ICustomerBalanceSummaryQuery) { + return this.customerBalanceSummaryPdf.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryPdf.ts b/packages/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryPdf.ts new file mode 100644 index 000000000..67e74349b --- /dev/null +++ b/packages/server/src/services/FinancialStatements/CustomerBalanceSummary/CustomerBalanceSummaryPdf.ts @@ -0,0 +1,34 @@ +import { Inject, Service } from 'typedi'; +import { IAPAgingSummaryQuery, ICustomerBalanceSummaryQuery } from '@/interfaces'; +import { TableSheetPdf } from '../TableSheetPdf'; +import { CustomerBalanceSummaryTableInjectable } from './CustomerBalanceSummaryTableInjectable'; + +@Service() +export class CustomerBalanceSummaryPdf { + @Inject() + private customerBalanceSummaryTable: CustomerBalanceSummaryTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Converts the given A/P aging summary sheet table to pdf. + * @param {number} tenantId - Tenant ID. + * @param {IAPAgingSummaryQuery} query - Balance sheet query. + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: ICustomerBalanceSummaryQuery + ): Promise { + const table = await this.customerBalanceSummaryTable.table(tenantId, query); + const sheetName = 'Customer Balance Summary'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} diff --git a/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetApplication.ts b/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetApplication.ts index 4c403ff58..8d3f5d614 100644 --- a/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetApplication.ts +++ b/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetApplication.ts @@ -3,6 +3,7 @@ import { JournalSheetService } from './JournalSheetService'; import { JournalSheetTableInjectable } from './JournalSheetTableInjectable'; import { IJournalReportQuery, IJournalTable } from '@/interfaces'; import { JournalSheetExportInjectable } from './JournalSheetExport'; +import { JournalSheetPdfInjectable } from './JournalSheetPdfInjectable'; export class JournalSheetApplication { @Inject() @@ -14,6 +15,9 @@ export class JournalSheetApplication { @Inject() private journalExport: JournalSheetExportInjectable; + @Inject() + private journalPdf: JournalSheetPdfInjectable; + /** * Retrieves the journal sheet. * @param {number} tenantId @@ -56,4 +60,14 @@ export class JournalSheetApplication { public csv(tenantId: number, query: IJournalReportQuery) { return this.journalExport.csv(tenantId, query); } + + /** + * Retrieves the journal sheet in pdf format. + * @param {number} tenantId + * @param {IJournalReportQuery} query + * @returns {Promise} + */ + public pdf(tenantId: number, query: IJournalReportQuery) { + return this.journalPdf.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetPdfInjectable.ts b/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetPdfInjectable.ts new file mode 100644 index 000000000..e3fcbfb26 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/JournalSheet/JournalSheetPdfInjectable.ts @@ -0,0 +1,34 @@ +import { IJournalReportQuery } from '@/interfaces'; +import { TableSheetPdf } from '../TableSheetPdf'; +import { JournalSheetTableInjectable } from './JournalSheetTableInjectable'; +import { Inject, Service } from 'typedi'; + +@Service() +export class JournalSheetPdfInjectable { + @Inject() + private journalSheetTable: JournalSheetTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Converts the given journal sheet table to pdf. + * @param {number} tenantId - Tenant ID. + * @param {IBalanceSheetQuery} query - Balance sheet query. + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: IJournalReportQuery + ): Promise { + const table = await this.journalSheetTable.table(tenantId, query); + const sheetName = 'Journal Sheet'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} diff --git a/packages/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheetApplication.ts b/packages/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheetApplication.ts index eee89e73a..6d15e2cc6 100644 --- a/packages/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheetApplication.ts +++ b/packages/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheetApplication.ts @@ -3,6 +3,7 @@ import { ProfitLossSheetExportInjectable } from './ProfitLossSheetExportInjectab import { ProfitLossSheetTableInjectable } from './ProfitLossSheetTableInjectable'; import { IProfitLossSheetQuery, IProfitLossSheetTable } from '@/interfaces'; import ProfitLossSheetService from './ProfitLossSheetService'; +import { ProfitLossTablePdfInjectable } from './ProfitLossTablePdfInjectable'; @Service() export class ProfitLossSheetApplication { @@ -15,6 +16,9 @@ export class ProfitLossSheetApplication { @Inject() private profitLossSheet: ProfitLossSheetService; + @Inject() + private profitLossPdf: ProfitLossTablePdfInjectable; + /** * Retreives the profit/loss sheet. * @param {number} tenantId @@ -57,4 +61,14 @@ export class ProfitLossSheetApplication { public xlsx(tenantId: number, query: IProfitLossSheetQuery): Promise { return this.profitLossExport.xlsx(tenantId, query); } + + /** + * Retrieves the profit/loss sheet in pdf format. + * @param {number} tenantId + * @param {IProfitLossSheetQuery} query + * @returns {Promise} + */ + public pdf(tenantId: number, query: IProfitLossSheetQuery): Promise { + return this.profitLossPdf.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheetExportInjectable.ts b/packages/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheetExportInjectable.ts index ba2371797..4fa9762d3 100644 --- a/packages/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheetExportInjectable.ts +++ b/packages/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossSheetExportInjectable.ts @@ -40,4 +40,11 @@ export class ProfitLossSheetExportInjectable { return tableCsv; } + + public async pdf( + tenantId: number, + query: IProfitLossSheetQuery + ): Promise { + const table = await this.profitLossSheetTable.table(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossTablePdfInjectable.ts b/packages/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossTablePdfInjectable.ts new file mode 100644 index 000000000..24b720b1a --- /dev/null +++ b/packages/server/src/services/FinancialStatements/ProfitLossSheet/ProfitLossTablePdfInjectable.ts @@ -0,0 +1,34 @@ +import { Inject, Service } from 'typedi'; +import { IProfitLossSheetQuery } from '@/interfaces'; +import { ProfitLossSheetTableInjectable } from './ProfitLossSheetTableInjectable'; +import { TableSheetPdf } from '../TableSheetPdf'; + +@Service() +export class ProfitLossTablePdfInjectable { + @Inject() + private profitLossTable: ProfitLossSheetTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Retrieves the profit/loss sheet in pdf format. + * @param {number} tenantId + * @param {number} query + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: IProfitLossSheetQuery + ): Promise { + const table = await this.profitLossTable.table(tenantId, query); + const sheetName = 'Profit & Loss Sheet'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} diff --git a/packages/server/src/services/FinancialStatements/SalesByItems/SalesByItemsApplication.ts b/packages/server/src/services/FinancialStatements/SalesByItems/SalesByItemsApplication.ts index b3b51869f..498e3eb66 100644 --- a/packages/server/src/services/FinancialStatements/SalesByItems/SalesByItemsApplication.ts +++ b/packages/server/src/services/FinancialStatements/SalesByItems/SalesByItemsApplication.ts @@ -8,6 +8,7 @@ import { import { SalesByItemsReportService } from './SalesByItemsService'; import { SalesByItemsTableInjectable } from './SalesByItemsTableInjectable'; import { SalesByItemsExport } from './SalesByItemsExport'; +import { SalesByItemsPdfInjectable } from './SalesByItemsPdfInjectable'; @Service() export class SalesByItemsApplication { @@ -20,6 +21,9 @@ export class SalesByItemsApplication { @Inject() private salesByItemsExport: SalesByItemsExport; + @Inject() + private salesByItemsPdf: SalesByItemsPdfInjectable; + /** * Retrieves the sales by items report in json format. * @param {number} tenantId @@ -71,4 +75,17 @@ export class SalesByItemsApplication { ): Promise { return this.salesByItemsExport.xlsx(tenantId, filter); } + + /** + * Retrieves the sales by items in pdf format. + * @param {number} tenantId + * @param {ISalesByItemsReportQuery} query + * @returns {Promise} + */ + public pdf( + tenantId: number, + query: ISalesByItemsReportQuery + ): Promise { + return this.salesByItemsPdf.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/SalesByItems/SalesByItemsPdfInjectable.ts b/packages/server/src/services/FinancialStatements/SalesByItems/SalesByItemsPdfInjectable.ts new file mode 100644 index 000000000..6cf3d98ec --- /dev/null +++ b/packages/server/src/services/FinancialStatements/SalesByItems/SalesByItemsPdfInjectable.ts @@ -0,0 +1,34 @@ +import { Inject, Service } from 'typedi'; +import { ISalesByItemsReportQuery } from '@/interfaces'; +import { SalesByItemsTableInjectable } from './SalesByItemsTableInjectable'; +import { TableSheetPdf } from '../TableSheetPdf'; + +@Service() +export class SalesByItemsPdfInjectable { + @Inject() + private salesByItemsTable: SalesByItemsTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Retrieves the sales by items sheet in pdf format. + * @param {number} tenantId + * @param {number} query + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: ISalesByItemsReportQuery + ): Promise { + const table = await this.salesByItemsTable.table(tenantId, query); + const sheetName = 'Sales By Items'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} diff --git a/packages/server/src/services/FinancialStatements/TableSheetPdf.ts b/packages/server/src/services/FinancialStatements/TableSheetPdf.ts new file mode 100644 index 000000000..16fd0f6f9 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/TableSheetPdf.ts @@ -0,0 +1,62 @@ +import { Inject, Service } from 'typedi'; +import * as R from 'ramda'; +import { ITableColumn, ITableData, ITableRow } from '@/interfaces'; +import { ChromiumlyTenancy } from '@/services/ChromiumlyTenancy/ChromiumlyTenancy'; +import { TemplateInjectable } from '@/services/TemplateInjectable/TemplateInjectable'; +import { FinancialTableStructure } from './FinancialTableStructure'; + +@Service() +export class TableSheetPdf { + @Inject() + private templateInjectable: TemplateInjectable; + + @Inject() + private chromiumlyTenancy: ChromiumlyTenancy; + + /** + * Converts the table to PDF. + * @param {number} tenantId - + * @param {IFinancialTable} table - + * @param {string} sheetName - Sheet name. + * @param {string} sheetDate - Sheet date. + */ + public async convertToPdf( + tenantId: number, + table: ITableData, + sheetName: string, + sheetDate: string + ) { + const columns = this.tablePdfColumns(table.columns); + const rows = this.tablePdfRows(table.rows); + const htmlContent = await this.templateInjectable.render( + tenantId, + 'modules/financial-sheet', + { + sheetName, + sheetDate, + table: { rows, columns }, + } + ); + return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent, { + margins: { top: 0, bottom: 0, left: 0, right: 0 }, + }); + } + + /** + * Converts the table columns to pdf columns. + * @param {ITableColumn[]} columns + * @returns {ITableColumn[]} + */ + private tablePdfColumns = (columns: ITableColumn[]): ITableColumn[] => { + return columns; + }; + + /** + * Converts the table rows to pdf rows. + * @param {ITableRow[]} rows - + * @returns {ITableRow[]} + */ + private tablePdfRows = (rows: ITableRow[]): ITableRow[] => { + return R.compose(FinancialTableStructure.flatNestedTree)(rows); + }; +} diff --git a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceExportInjectable.ts b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceExportInjectable.ts index a515f1beb..62b847f7c 100644 --- a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceExportInjectable.ts +++ b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceExportInjectable.ts @@ -1,13 +1,17 @@ +import { Inject, Service } from 'typedi'; import { TableSheet } from '@/lib/Xlsx/TableSheet'; import { ITrialBalanceSheetQuery } from '@/interfaces'; -import { Inject, Service } from 'typedi'; import { TrialBalanceSheetTableInjectable } from './TrialBalanceSheetTableInjectable'; +import { TrialBalanceSheetPdfInjectable } from './TrialBalanceSheetPdfInjectsable'; @Service() export class TrialBalanceExportInjectable { @Inject() private trialBalanceSheetTable: TrialBalanceSheetTableInjectable; + @Inject() + private trialBalanceSheetPdf: TrialBalanceSheetPdfInjectable; + /** * Retrieves the trial balance sheet in XLSX format. * @param {number} tenantId @@ -40,4 +44,17 @@ export class TrialBalanceExportInjectable { return tableCsv; } + + /** + * Retrieves the trial balance sheet in PDF format. + * @param {number} tenantId + * @param {ITrialBalanceSheetQuery} query + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: ITrialBalanceSheetQuery + ): Promise { + return this.trialBalanceSheetPdf.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetApplication.ts b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetApplication.ts index a771c8f15..20c485ecc 100644 --- a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetApplication.ts +++ b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetApplication.ts @@ -57,4 +57,14 @@ export class TrialBalanceSheetApplication { public async xlsx(tenantId: number, query: ITrialBalanceSheetQuery) { return this.exportable.xlsx(tenantId, query); } + + /** + * Retrieve the trial balance sheet in pdf format. + * @param {number} tenantId + * @param {ITrialBalanceSheetQuery} query + * @returns {Promise} + */ + public async pdf(tenantId: number, query: ITrialBalanceSheetQuery) { + return this.exportable.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetPdfInjectsable.ts b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetPdfInjectsable.ts new file mode 100644 index 000000000..1907dd26d --- /dev/null +++ b/packages/server/src/services/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetPdfInjectsable.ts @@ -0,0 +1,34 @@ +import { Inject, Service } from 'typedi'; +import { ITrialBalanceSheetQuery } from '@/interfaces'; +import { TableSheetPdf } from '../TableSheetPdf'; +import { TrialBalanceSheetTableInjectable } from './TrialBalanceSheetTableInjectable'; + +@Service() +export class TrialBalanceSheetPdfInjectable { + @Inject() + private trialBalanceSheetTable: TrialBalanceSheetTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Converts the given balance sheet table to pdf. + * @param {number} tenantId - Tenant ID. + * @param {IBalanceSheetQuery} query - Balance sheet query. + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: ITrialBalanceSheetQuery + ): Promise { + const table = await this.trialBalanceSheetTable.table(tenantId, query); + const sheetName = 'Trial Balance Sheet'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} diff --git a/packages/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryApplication.ts b/packages/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryApplication.ts index 5fe4bc74d..c02eac224 100644 --- a/packages/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryApplication.ts +++ b/packages/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryApplication.ts @@ -3,6 +3,7 @@ import { IVendorBalanceSummaryQuery } from '@/interfaces'; import { VendorBalanceSummaryTableInjectable } from './VendorBalanceSummaryTableInjectable'; import { VendorBalanceSummaryExportInjectable } from './VendorBalanceSummaryExportInjectable'; import { VendorBalanceSummaryService } from './VendorBalanceSummaryService'; +import { VendorBalanceSummaryPdf } from './VendorBalanceSummaryPdf'; @Service() export class VendorBalanceSummaryApplication { @@ -15,6 +16,9 @@ export class VendorBalanceSummaryApplication { @Inject() private vendorBalanceSummaryExport: VendorBalanceSummaryExportInjectable; + @Inject() + private vendorBalanceSummaryPdf: VendorBalanceSummaryPdf; + /** * Retrieves the vendor balance summary sheet in sheet format. * @param {number} tenantId @@ -59,4 +63,14 @@ export class VendorBalanceSummaryApplication { ): Promise { return this.vendorBalanceSummaryExport.csv(tenantId, query); } + + /** + * Retrieves the vendor balance summary sheet in pdf format. + * @param {number} tenantId + * @param {IVendorBalanceSummaryQuery} query + * @returns {Promise} + */ + public pdf(tenantId: number, query: IVendorBalanceSummaryQuery) { + return this.vendorBalanceSummaryPdf.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryPdf.ts b/packages/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryPdf.ts new file mode 100644 index 000000000..a404d673e --- /dev/null +++ b/packages/server/src/services/FinancialStatements/VendorBalanceSummary/VendorBalanceSummaryPdf.ts @@ -0,0 +1,34 @@ +import { Inject, Service } from 'typedi'; +import { IVendorBalanceSummaryQuery } from '@/interfaces'; +import { TableSheetPdf } from '../TableSheetPdf'; +import { VendorBalanceSummaryTableInjectable } from './VendorBalanceSummaryTableInjectable'; + +@Service() +export class VendorBalanceSummaryPdf { + @Inject() + private vendorBalanceSummaryTable: VendorBalanceSummaryTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Retrieves the sales by items sheet in pdf format. + * @param {number} tenantId + * @param {number} query + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: IVendorBalanceSummaryQuery + ): Promise { + const table = await this.vendorBalanceSummaryTable.table(tenantId, query); + const sheetName = 'Sales By Items'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} From b11c531cf538201d3938a5d92f9ddc5ccdfac387 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 11 Feb 2024 01:14:31 +0200 Subject: [PATCH 16/31] feat(server): wip priting financial reports --- .../CustomerBalanceSummary/index.ts | 14 ++++++++ .../FinancialStatements/GeneralLedger.ts | 12 +++++++ .../InventoryDetails/index.ts | 10 ++++++ .../InventoryValuationSheet.ts | 10 ++++++ .../FinancialStatements/JournalSheet.ts | 2 +- .../FinancialStatements/PurchasesByItem.ts | 11 +++++- .../SalesTaxLiabilitySummary/index.ts | 11 ++++++ .../TransactionsByCustomers/index.ts | 1 + .../TransactionsByVendors/index.ts | 12 +++++++ .../GeneralLedger/GeneralLedgerApplication.ts | 17 +++++++++ .../GeneralLedger/GeneralLedgerPdf.ts | 34 ++++++++++++++++++ .../InventoryDetailsApplication.ts | 14 ++++++++ .../InventoryDetailsTablePdf.ts | 36 +++++++++++++++++++ .../InventoryValuationSheetApplication.ts | 17 +++++++++ .../InventoryValuationSheetPdf.ts | 35 ++++++++++++++++++ .../PurchasesByItemsApplication.ts | 17 +++++++++ .../PurchasesByItems/PurchasesByItemsPdf.ts | 34 ++++++++++++++++++ .../SalesTaxLiabilitySummaryApplication.ts | 17 +++++++++ .../SalesTaxLiabiltiySummaryPdf.ts | 35 ++++++++++++++++++ .../TransactionsByCustomersApplication.ts | 17 +++++++++ .../TransactionsByCustomersPdf.ts | 36 +++++++++++++++++++ .../TransactionsByVendorApplication.ts | 14 ++++++++ .../TransactionsByVendorPdf.ts | 34 ++++++++++++++++++ 23 files changed, 438 insertions(+), 2 deletions(-) create mode 100644 packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerPdf.ts create mode 100644 packages/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsTablePdf.ts create mode 100644 packages/server/src/services/FinancialStatements/InventoryValuationSheet/InventoryValuationSheetPdf.ts create mode 100644 packages/server/src/services/FinancialStatements/PurchasesByItems/PurchasesByItemsPdf.ts create mode 100644 packages/server/src/services/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabiltiySummaryPdf.ts create mode 100644 packages/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersPdf.ts create mode 100644 packages/server/src/services/FinancialStatements/TransactionsByVendor/TransactionsByVendorPdf.ts diff --git a/packages/server/src/api/controllers/FinancialStatements/CustomerBalanceSummary/index.ts b/packages/server/src/api/controllers/FinancialStatements/CustomerBalanceSummary/index.ts index 6c10543f5..e69fcdc1c 100644 --- a/packages/server/src/api/controllers/FinancialStatements/CustomerBalanceSummary/index.ts +++ b/packages/server/src/api/controllers/FinancialStatements/CustomerBalanceSummary/index.ts @@ -75,6 +75,7 @@ export default class CustomerBalanceSummaryReportController extends BaseFinancia ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_XLSX, + ACCEPT_TYPE.APPLICATION_PDF, ]); // Retrieves the xlsx format. @@ -109,6 +110,19 @@ export default class CustomerBalanceSummaryReportController extends BaseFinancia filter ); return res.status(200).send(table); + // Retrieves the pdf format. + } else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) { + const buffer = await this.customerBalanceSummaryApp.pdf( + tenantId, + filter + ); + + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': buffer.length, + }); + return res.send(buffer); + // Retrieves the json format. } else { const sheet = await this.customerBalanceSummaryApp.sheet( tenantId, diff --git a/packages/server/src/api/controllers/FinancialStatements/GeneralLedger.ts b/packages/server/src/api/controllers/FinancialStatements/GeneralLedger.ts index c86da8eae..4c3aec4a9 100644 --- a/packages/server/src/api/controllers/FinancialStatements/GeneralLedger.ts +++ b/packages/server/src/api/controllers/FinancialStatements/GeneralLedger.ts @@ -71,6 +71,7 @@ export default class GeneralLedgerReportController extends BaseFinancialReportCo ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_XLSX, ACCEPT_TYPE.APPLICATION_CSV, + ACCEPT_TYPE.APPLICATION_PDF, ]); // Retrieves the table format. if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) { @@ -95,6 +96,17 @@ export default class GeneralLedgerReportController extends BaseFinancialReportCo 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ); return res.send(buffer); + // Retrieves the pdf format. + } else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) { + const pdfContent = await this.generalLedgerApplication.pdf( + tenantId, + filter + ); + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + return res.send(pdfContent); // Retrieves the json format. } else { const sheet = await this.generalLedgerApplication.sheet(tenantId, filter); diff --git a/packages/server/src/api/controllers/FinancialStatements/InventoryDetails/index.ts b/packages/server/src/api/controllers/FinancialStatements/InventoryDetails/index.ts index 07f91af4a..3288ee847 100644 --- a/packages/server/src/api/controllers/FinancialStatements/InventoryDetails/index.ts +++ b/packages/server/src/api/controllers/FinancialStatements/InventoryDetails/index.ts @@ -96,6 +96,7 @@ export default class InventoryDetailsController extends BaseController { ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_XLSX, + ACCEPT_TYPE.APPLICATION_PDF, ]); // Retrieves the csv format. if (acceptType === ACCEPT_TYPE.APPLICATION_CSV) { @@ -127,6 +128,15 @@ export default class InventoryDetailsController extends BaseController { filter ); return res.status(200).send(table); + // Retrieves the pdf format. + } else if (acceptType === ACCEPT_TYPE.APPLICATION_PDF) { + const buffer = await this.inventoryItemDetailsApp.pdf(tenantId, filter); + + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': buffer.length, + }); + return res.send(buffer); } else { const sheet = await this.inventoryItemDetailsApp.sheet( tenantId, diff --git a/packages/server/src/api/controllers/FinancialStatements/InventoryValuationSheet.ts b/packages/server/src/api/controllers/FinancialStatements/InventoryValuationSheet.ts index 3a2d3c196..b31a911a2 100644 --- a/packages/server/src/api/controllers/FinancialStatements/InventoryValuationSheet.ts +++ b/packages/server/src/api/controllers/FinancialStatements/InventoryValuationSheet.ts @@ -79,6 +79,7 @@ export default class InventoryValuationReportController extends BaseFinancialRep ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_XLSX, ACCEPT_TYPE.APPLICATION_CSV, + ACCEPT_TYPE.APPLICATION_PDF, ]); // Retrieves the json table format. @@ -104,6 +105,15 @@ export default class InventoryValuationReportController extends BaseFinancialRep 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ); return res.send(buffer); + // Retrieves the pdf format. + } else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) { + const pdfContent = await this.inventoryValuationApp.pdf(tenantId, filter); + + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + return res.status(200).send(pdfContent); // Retrieves the json format. } else { const { data, query, meta } = await this.inventoryValuationApp.sheet( diff --git a/packages/server/src/api/controllers/FinancialStatements/JournalSheet.ts b/packages/server/src/api/controllers/FinancialStatements/JournalSheet.ts index 0355766db..561f69329 100644 --- a/packages/server/src/api/controllers/FinancialStatements/JournalSheet.ts +++ b/packages/server/src/api/controllers/FinancialStatements/JournalSheet.ts @@ -82,7 +82,7 @@ export default class JournalSheetController extends BaseFinancialReportControlle // Retrieves the csv format. } else if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) { const buffer = await this.journalSheetApp.csv(tenantId, filter); - + res.setHeader('Content-Disposition', 'attachment; filename=output.csv'); res.setHeader('Content-Type', 'text/csv'); diff --git a/packages/server/src/api/controllers/FinancialStatements/PurchasesByItem.ts b/packages/server/src/api/controllers/FinancialStatements/PurchasesByItem.ts index 2e72587c0..2c92bdc97 100644 --- a/packages/server/src/api/controllers/FinancialStatements/PurchasesByItem.ts +++ b/packages/server/src/api/controllers/FinancialStatements/PurchasesByItem.ts @@ -75,8 +75,8 @@ export default class PurchasesByItemReportController extends BaseFinancialReport ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_XLSX, ACCEPT_TYPE.APPLICATION_CSV, + ACCEPT_TYPE.APPLICATION_PDF, ]); - // JSON table response format. if (ACCEPT_TYPE.APPLICATION_JSON_TABLE === acceptType) { const table = await this.purchasesByItemsApp.table(tenantId, filter); @@ -100,6 +100,15 @@ export default class PurchasesByItemReportController extends BaseFinancialReport 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ); return res.send(buffer); + // PDF response format. + } else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) { + const pdfContent = await this.purchasesByItemsApp.pdf(tenantId, filter); + + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + return res.send(pdfContent); // Json response format. } else { const sheet = await this.purchasesByItemsApp.sheet(tenantId, filter); diff --git a/packages/server/src/api/controllers/FinancialStatements/SalesTaxLiabilitySummary/index.ts b/packages/server/src/api/controllers/FinancialStatements/SalesTaxLiabilitySummary/index.ts index 933b5c9c4..42a96aab2 100644 --- a/packages/server/src/api/controllers/FinancialStatements/SalesTaxLiabilitySummary/index.ts +++ b/packages/server/src/api/controllers/FinancialStatements/SalesTaxLiabilitySummary/index.ts @@ -62,6 +62,7 @@ export default class SalesTaxLiabilitySummary extends BaseFinancialReportControl ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_XLSX, + ACCEPT_TYPE.APPLICATION_PDF, ]); // Retrieves the json table format. @@ -97,6 +98,16 @@ export default class SalesTaxLiabilitySummary extends BaseFinancialReportControl return res.send(buffer); // Retrieves the json format. + } else if (acceptType === ACCEPT_TYPE.APPLICATION_PDF) { + const pdfContent = await this.salesTaxLiabilitySummaryApp.pdf( + tenantId, + filter + ); + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + return res.status(200).send(pdfContent); } else { const sheet = await this.salesTaxLiabilitySummaryApp.sheet( tenantId, diff --git a/packages/server/src/api/controllers/FinancialStatements/TransactionsByCustomers/index.ts b/packages/server/src/api/controllers/FinancialStatements/TransactionsByCustomers/index.ts index 4bc3b1f44..42a619ec8 100644 --- a/packages/server/src/api/controllers/FinancialStatements/TransactionsByCustomers/index.ts +++ b/packages/server/src/api/controllers/FinancialStatements/TransactionsByCustomers/index.ts @@ -70,6 +70,7 @@ export default class TransactionsByCustomersReportController extends BaseFinanci ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_XLSX, + ACCEPT_TYPE.APPLICATION_PDF, ]); try { // Retrieves the json table format. diff --git a/packages/server/src/api/controllers/FinancialStatements/TransactionsByVendors/index.ts b/packages/server/src/api/controllers/FinancialStatements/TransactionsByVendors/index.ts index a0c1bf037..c437892f4 100644 --- a/packages/server/src/api/controllers/FinancialStatements/TransactionsByVendors/index.ts +++ b/packages/server/src/api/controllers/FinancialStatements/TransactionsByVendors/index.ts @@ -71,6 +71,7 @@ export default class TransactionsByVendorsReportController extends BaseFinancial ACCEPT_TYPE.APPLICATION_JSON_TABLE, ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_XLSX, + ACCEPT_TYPE.APPLICATION_PDF, ]); // Retrieves the xlsx format. @@ -101,6 +102,17 @@ export default class TransactionsByVendorsReportController extends BaseFinancial filter ); return res.status(200).send(table); + // Retrieves the pdf format. + } else if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) { + const pdfContent = await this.transactionsByVendorsApp.pdf( + tenantId, + filter + ); + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Length': pdfContent.length, + }); + return res.send(pdfContent); // Retrieves the json format. } else { const sheet = await this.transactionsByVendorsApp.sheet( diff --git a/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerApplication.ts b/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerApplication.ts index 924b0da8c..6257e34d8 100644 --- a/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerApplication.ts +++ b/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerApplication.ts @@ -6,6 +6,7 @@ import { import { GeneralLedgerTableInjectable } from './GeneralLedgerTableInjectable'; import { GeneralLedgerExportInjectable } from './GeneralLedgerExport'; import { GeneralLedgerService } from './GeneralLedgerService'; +import { GeneralLedgerPdf } from './GeneralLedgerPdf'; export class GeneralLedgerApplication { @Inject() @@ -17,6 +18,9 @@ export class GeneralLedgerApplication { @Inject() private GLSheet: GeneralLedgerService; + @Inject() + private GLPdf: GeneralLedgerPdf; + /** * Retrieves the G/L sheet in json format. * @param {number} tenantId @@ -63,4 +67,17 @@ export class GeneralLedgerApplication { ): Promise { return this.GLExport.csv(tenantId, query); } + + /** + * Retrieves the G/L sheet in pdf format. + * @param {number} tenantId + * @param {IGeneralLedgerSheetQuery} query + * @returns {Promise} + */ + public pdf( + tenantId: number, + query: IGeneralLedgerSheetQuery + ): Promise { + return this.GLPdf.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerPdf.ts b/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerPdf.ts new file mode 100644 index 000000000..d1d12ae43 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/GeneralLedger/GeneralLedgerPdf.ts @@ -0,0 +1,34 @@ +import { Inject, Service } from "typedi"; +import { TableSheetPdf } from "../TableSheetPdf"; +import { GeneralLedgerTableInjectable } from "./GeneralLedgerTableInjectable"; +import { IGeneralLedgerSheetQuery } from "@/interfaces"; + +@Service() +export class GeneralLedgerPdf { + @Inject() + private generalLedgerTable: GeneralLedgerTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Converts the general ledger sheet table to pdf. + * @param {number} tenantId - Tenant ID. + * @param {IGeneralLedgerSheetQuery} query - + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: IGeneralLedgerSheetQuery + ): Promise { + const table = await this.generalLedgerTable.table(tenantId, query); + const sheetName = 'General Ledger'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} \ No newline at end of file diff --git a/packages/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsApplication.ts b/packages/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsApplication.ts index a2639094e..50cbd2a2f 100644 --- a/packages/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsApplication.ts +++ b/packages/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsApplication.ts @@ -6,6 +6,7 @@ import { Inject, Service } from 'typedi'; import { InventoryDetailsExportInjectable } from './InventoryDetailsExportInjectable'; import { InventoryDetailsTableInjectable } from './InventoryDetailsTableInjectable'; import { InventoryDetailsService } from './InventoryDetailsService'; +import { InventoryDetailsTablePdf } from './InventoryDetailsTablePdf'; @Service() export class InventortyDetailsApplication { @@ -18,6 +19,9 @@ export class InventortyDetailsApplication { @Inject() private inventoryDetails: InventoryDetailsService; + @Inject() + private inventoryDetailsPdf: InventoryDetailsTablePdf; + /** * Retrieves the inventory details report in sheet format. * @param {number} tenantId @@ -63,4 +67,14 @@ export class InventortyDetailsApplication { public csv(tenantId: number, query: IInventoryDetailsQuery): Promise { return this.inventoryDetailsExport.csv(tenantId, query); } + + /** + * Retrieves the inventory details report in PDF format. + * @param {number} tenantId + * @param {IInventoryDetailsQuery} query + * @returns {Promise} + */ + public pdf(tenantId: number, query: IInventoryDetailsQuery) { + return this.inventoryDetailsPdf.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsTablePdf.ts b/packages/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsTablePdf.ts new file mode 100644 index 000000000..1c2077fc6 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/InventoryDetails/InventoryDetailsTablePdf.ts @@ -0,0 +1,36 @@ +import { Inject, Service } from "typedi"; +import { InventoryDetailsTableInjectable } from "./InventoryDetailsTableInjectable"; +import { TableSheetPdf } from "../TableSheetPdf"; +import { IInventoryDetailsQuery } from "@/interfaces"; + + +@Service() +export class InventoryDetailsTablePdf { + @Inject() + private inventoryDetailsTable: InventoryDetailsTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Converts the given inventory details sheet table to pdf. + * @param {number} tenantId - Tenant ID. + * @param {IBalanceSheetQuery} query - Balance sheet query. + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: IInventoryDetailsQuery + ): Promise { + const table = await this.inventoryDetailsTable.table(tenantId, query); + const sheetName = 'Inventory Items Details'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } + +} \ No newline at end of file diff --git a/packages/server/src/services/FinancialStatements/InventoryValuationSheet/InventoryValuationSheetApplication.ts b/packages/server/src/services/FinancialStatements/InventoryValuationSheet/InventoryValuationSheetApplication.ts index 43406b0d8..7c4a65967 100644 --- a/packages/server/src/services/FinancialStatements/InventoryValuationSheet/InventoryValuationSheetApplication.ts +++ b/packages/server/src/services/FinancialStatements/InventoryValuationSheet/InventoryValuationSheetApplication.ts @@ -7,6 +7,7 @@ import { Inject, Service } from 'typedi'; import { InventoryValuationSheetService } from './InventoryValuationSheetService'; import { InventoryValuationSheetTableInjectable } from './InventoryValuationSheetTableInjectable'; import { InventoryValuationSheetExportable } from './InventoryValuationSheetExportable'; +import { InventoryValuationSheetPdf } from './InventoryValuationSheetPdf'; @Service() export class InventoryValuationSheetApplication { @@ -19,6 +20,9 @@ export class InventoryValuationSheetApplication { @Inject() private inventoryValuationExport: InventoryValuationSheetExportable; + @Inject() + private inventoryValuationPdf: InventoryValuationSheetPdf; + /** * Retrieves the inventory valuation json format. * @param {number} tenantId @@ -73,4 +77,17 @@ export class InventoryValuationSheetApplication { ): Promise { return this.inventoryValuationExport.csv(tenantId, query); } + + /** + * Retrieves the inventory valuation pdf format. + * @param {number} tenantId + * @param {IInventoryValuationReportQuery} query + * @returns {Promise} + */ + public pdf( + tenantId: number, + query: IInventoryValuationReportQuery + ): Promise { + return this.inventoryValuationPdf.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/InventoryValuationSheet/InventoryValuationSheetPdf.ts b/packages/server/src/services/FinancialStatements/InventoryValuationSheet/InventoryValuationSheetPdf.ts new file mode 100644 index 000000000..6148816d8 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/InventoryValuationSheet/InventoryValuationSheetPdf.ts @@ -0,0 +1,35 @@ +import { Inject, Service } from "typedi"; +import { InventoryValuationSheetTableInjectable } from "./InventoryValuationSheetTableInjectable"; +import { TableSheetPdf } from "../TableSheetPdf"; +import { IInventoryValuationReportQuery } from "@/interfaces"; + + +@Service() +export class InventoryValuationSheetPdf { + @Inject() + private inventoryValuationTable: InventoryValuationSheetTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Converts the given balance sheet table to pdf. + * @param {number} tenantId - Tenant ID. + * @param {IBalanceSheetQuery} query - Balance sheet query. + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: IInventoryValuationReportQuery + ): Promise { + const table = await this.inventoryValuationTable.table(tenantId, query); + const sheetName = 'Inventory Valuation Sheet'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} \ No newline at end of file diff --git a/packages/server/src/services/FinancialStatements/PurchasesByItems/PurchasesByItemsApplication.ts b/packages/server/src/services/FinancialStatements/PurchasesByItems/PurchasesByItemsApplication.ts index c9a94bdbc..eeecaff38 100644 --- a/packages/server/src/services/FinancialStatements/PurchasesByItems/PurchasesByItemsApplication.ts +++ b/packages/server/src/services/FinancialStatements/PurchasesByItems/PurchasesByItemsApplication.ts @@ -7,6 +7,7 @@ import { } from '@/interfaces/PurchasesByItemsSheet'; import { PurchasesByItemsTableInjectable } from './PurchasesByItemsTableInjectable'; import { PurchasesByItemsService } from './PurchasesByItemsService'; +import { PurchasesByItemsPdf } from './PurchasesByItemsPdf'; @Service() export class PurcahsesByItemsApplication { @@ -19,6 +20,9 @@ export class PurcahsesByItemsApplication { @Inject() private purchasesByItemsExport: PurchasesByItemsExport; + @Inject() + private purchasesByItemsPdf: PurchasesByItemsPdf; + /** * Retrieves the purchases by items in json format. * @param {number} tenantId @@ -70,4 +74,17 @@ export class PurcahsesByItemsApplication { ): Promise { return this.purchasesByItemsExport.xlsx(tenantId, query); } + + /** + * Retrieves the purchases by items in pdf format. + * @param {number} tenantId + * @param {IPurchasesByItemsReportQuery} filter + * @returns {Promise} + */ + public pdf( + tenantId: number, + filter: IPurchasesByItemsReportQuery + ): Promise { + return this.purchasesByItemsPdf.pdf(tenantId, filter); + } } diff --git a/packages/server/src/services/FinancialStatements/PurchasesByItems/PurchasesByItemsPdf.ts b/packages/server/src/services/FinancialStatements/PurchasesByItems/PurchasesByItemsPdf.ts new file mode 100644 index 000000000..36645aeb3 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/PurchasesByItems/PurchasesByItemsPdf.ts @@ -0,0 +1,34 @@ +import { Inject, Service } from 'typedi'; +import { TableSheetPdf } from '../TableSheetPdf'; +import { PurchasesByItemsTableInjectable } from './PurchasesByItemsTableInjectable'; +import { IPurchasesByItemsReportQuery } from '@/interfaces/PurchasesByItemsSheet'; + +@Service() +export class PurchasesByItemsPdf { + @Inject() + private purchasesByItemsTable: PurchasesByItemsTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Converts the given journal sheet table to pdf. + * @param {number} tenantId - Tenant ID. + * @param {IBalanceSheetQuery} query - Balance sheet query. + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: IPurchasesByItemsReportQuery + ): Promise { + const table = await this.purchasesByItemsTable.table(tenantId, query); + const sheetName = 'Purchases By Items'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} diff --git a/packages/server/src/services/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabilitySummaryApplication.ts b/packages/server/src/services/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabilitySummaryApplication.ts index f0e5a5248..3b9f0d7b3 100644 --- a/packages/server/src/services/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabilitySummaryApplication.ts +++ b/packages/server/src/services/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabilitySummaryApplication.ts @@ -3,6 +3,7 @@ import { SalesTaxLiabilitySummaryQuery } from '@/interfaces/SalesTaxLiabilitySum import { SalesTaxLiabilitySummaryTableInjectable } from './SalesTaxLiabilitySummaryTableInjectable'; import { SalesTaxLiabilitySummaryExportInjectable } from './SalesTaxLiabilitySummaryExportInjectable'; import { SalesTaxLiabilitySummaryService } from './SalesTaxLiabilitySummaryService'; +import { SalesTaxLiabiltiySummaryPdf } from './SalesTaxLiabiltiySummaryPdf'; @Service() export class SalesTaxLiabilitySummaryApplication { @@ -15,6 +16,9 @@ export class SalesTaxLiabilitySummaryApplication { @Inject() private salesTaxLiabilityTable: SalesTaxLiabilitySummaryTableInjectable; + @Inject() + private salesTaxLiabiltiyPdf: SalesTaxLiabiltiySummaryPdf; + /** * Retrieves the sales tax liability summary in json format. * @param {number} tenantId @@ -60,4 +64,17 @@ export class SalesTaxLiabilitySummaryApplication { ): Promise { return this.salesTaxLiabilityExport.csv(tenantId, query); } + + /** + * Retrieves the sales tax liability summary in PDF format. + * @param {number} tenantId + * @param {SalesTaxLiabilitySummaryQuery} query + * @returns {Promise} + */ + public pdf( + tenantId: number, + query: SalesTaxLiabilitySummaryQuery + ): Promise { + return this.salesTaxLiabiltiyPdf.pdf(tenantId, query): + } } diff --git a/packages/server/src/services/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabiltiySummaryPdf.ts b/packages/server/src/services/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabiltiySummaryPdf.ts new file mode 100644 index 000000000..1b9a509a0 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/SalesTaxLiabilitySummary/SalesTaxLiabiltiySummaryPdf.ts @@ -0,0 +1,35 @@ +import { Inject, Service } from 'typedi'; +import { TableSheetPdf } from '../TableSheetPdf'; +import { SalesTaxLiabilitySummaryTableInjectable } from './SalesTaxLiabilitySummaryTableInjectable'; +import { ISalesByItemsReportQuery } from '@/interfaces'; +import { SalesTaxLiabilitySummaryQuery } from '@/interfaces/SalesTaxLiabilitySummary'; + +@Service() +export class SalesTaxLiabiltiySummaryPdf { + @Inject() + private salesTaxLiabiltiySummaryTable: SalesTaxLiabilitySummaryTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Converts the given sales tax liability summary table to pdf. + * @param {number} tenantId - Tenant ID. + * @param {ISalesByItemsReportQuery} query - Balance sheet query. + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: SalesTaxLiabilitySummaryQuery + ): Promise { + const table = await this.salesTaxLiabiltiySummaryTable.table(tenantId, query); + const sheetName = 'Sales Tax Liability Summary'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} diff --git a/packages/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersApplication.ts b/packages/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersApplication.ts index b729c219a..c3e4feb7e 100644 --- a/packages/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersApplication.ts +++ b/packages/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersApplication.ts @@ -6,6 +6,7 @@ import { import { TransactionsByCustomersTableInjectable } from './TransactionsByCustomersTableInjectable'; import { TransactionsByCustomersExportInjectable } from './TransactionsByCustomersExportInjectable'; import { TransactionsByCustomersSheet } from './TransactionsByCustomersService'; +import { TransactionsByCustomersPdf } from './TransactionsByCustomersPdf'; @Service() export class TransactionsByCustomerApplication { @@ -18,6 +19,9 @@ export class TransactionsByCustomerApplication { @Inject() private transactionsByCustomersSheet: TransactionsByCustomersSheet; + @Inject() + private transactionsByCustomersPdf: TransactionsByCustomersPdf; + /** * Retrieves the transactions by customers sheet in json format. * @param {number} tenantId @@ -69,4 +73,17 @@ export class TransactionsByCustomerApplication { ): Promise { return this.transactionsByCustomersExport.xlsx(tenantId, query); } + + /** + * Retrieves the transactions by vendors sheet in PDF format. + * @param {number} tenantId + * @param {ITransactionsByCustomersFilter} query + * @returns {Promise} + */ + public pdf( + tenantId: number, + query: ITransactionsByCustomersFilter + ): Promise { + return this.transactionsByCustomersPdf.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersPdf.ts b/packages/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersPdf.ts new file mode 100644 index 000000000..2926e63e6 --- /dev/null +++ b/packages/server/src/services/FinancialStatements/TransactionsByCustomer/TransactionsByCustomersPdf.ts @@ -0,0 +1,36 @@ +import { ITransactionsByCustomersFilter } from '@/interfaces'; +import { Inject } from 'typedi'; +import { TableSheetPdf } from '../TableSheetPdf'; +import { TransactionsByCustomersTableInjectable } from './TransactionsByCustomersTableInjectable'; + +export class TransactionsByCustomersPdf { + @Inject() + private transactionsByCustomersTable: TransactionsByCustomersTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Retrieves the transactions by customers in PDF format. + * @param {number} tenantId - Tenant ID. + * @param {IBalanceSheetQuery} query - Balance sheet query. + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: ITransactionsByCustomersFilter + ): Promise { + const table = await this.transactionsByCustomersTable.table( + tenantId, + query + ); + const sheetName = 'Transactions By Customers'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} diff --git a/packages/server/src/services/FinancialStatements/TransactionsByVendor/TransactionsByVendorApplication.ts b/packages/server/src/services/FinancialStatements/TransactionsByVendor/TransactionsByVendorApplication.ts index d8d424a30..744993bb0 100644 --- a/packages/server/src/services/FinancialStatements/TransactionsByVendor/TransactionsByVendorApplication.ts +++ b/packages/server/src/services/FinancialStatements/TransactionsByVendor/TransactionsByVendorApplication.ts @@ -7,6 +7,7 @@ import { import { TransactionsByVendorExportInjectable } from './TransactionsByVendorExportInjectable'; import { TransactionsByVendorTableInjectable } from './TransactionsByVendorTableInjectable'; import { TransactionsByVendorsInjectable } from './TransactionsByVendorInjectable'; +import { TransactionsByVendorsPdf } from './TransactionsByVendorPdf'; @Service() export class TransactionsByVendorApplication { @@ -19,6 +20,9 @@ export class TransactionsByVendorApplication { @Inject() private transactionsByVendorSheet: TransactionsByVendorsInjectable; + @Inject() + private transactionsByVendorPdf: TransactionsByVendorsPdf; + /** * Retrieves the transactions by vendor in sheet format. * @param {number} tenantId @@ -72,4 +76,14 @@ export class TransactionsByVendorApplication { ): Promise { return this.transactionsByVendorExport.xlsx(tenantId, query); } + + /** + * Retrieves the transactions by vendor in PDF format. + * @param {number} tenantId + * @param {ITransactionsByVendorsFilter} query + * @returns {Promise} + */ + public pdf(tenantId: number, query: ITransactionsByVendorsFilter) { + return this.transactionsByVendorPdf.pdf(tenantId, query); + } } diff --git a/packages/server/src/services/FinancialStatements/TransactionsByVendor/TransactionsByVendorPdf.ts b/packages/server/src/services/FinancialStatements/TransactionsByVendor/TransactionsByVendorPdf.ts new file mode 100644 index 000000000..6c8b7f68a --- /dev/null +++ b/packages/server/src/services/FinancialStatements/TransactionsByVendor/TransactionsByVendorPdf.ts @@ -0,0 +1,34 @@ +import { Inject, Service } from 'typedi'; +import { ITransactionsByVendorsFilter } from '@/interfaces'; +import { TableSheetPdf } from '../TableSheetPdf'; +import { TransactionsByVendorTableInjectable } from './TransactionsByVendorTableInjectable'; + +@Service() +export class TransactionsByVendorsPdf { + @Inject() + private transactionsByVendorTable: TransactionsByVendorTableInjectable; + + @Inject() + private tableSheetPdf: TableSheetPdf; + + /** + * Converts the given balance sheet table to pdf. + * @param {number} tenantId - Tenant ID. + * @param {IBalanceSheetQuery} query - Balance sheet query. + * @returns {Promise} + */ + public async pdf( + tenantId: number, + query: ITransactionsByVendorsFilter + ): Promise { + const table = await this.transactionsByVendorTable.table(tenantId, query); + const sheetName = 'Transactions By Vendors'; + + return this.tableSheetPdf.convertToPdf( + tenantId, + table.table, + sheetName, + table.meta.baseCurrency + ); + } +} From 09ad725a6779efd9b87528c251a9c8fcdb16a984 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 11 Feb 2024 16:12:41 +0200 Subject: [PATCH 17/31] feat(webapp): wip print preview financial reports --- .../src/components/FormattedMessage/index.tsx | 9 +++- packages/webapp/src/constants/dialogs.ts | 5 +++ .../BalanceSheet/BalanceSheet.tsx | 3 ++ .../BalanceSheet/BalanceSheetActionsBar.tsx | 11 +++++ .../BalanceSheet/BalanceSheetDialogs.tsx | 10 +++++ .../BalanceSheetPdfDialog.tsx | 39 ++++++++++++++++ .../BalanceSheetPdfDialogContent.tsx | 42 ++++++++++++++++++ .../dialogs/BalanceSheetPdfDialog/index.ts | 1 + .../CashFlowStatement/CashFlowStatement.tsx | 3 ++ .../CashFlowStatementActionsBar.tsx | 12 +++++ .../CashflowSheetDialogs.tsx | 12 +++++ .../CashflowSheetPdfDialog.tsx | 44 +++++++++++++++++++ .../CashflowSheetPdfDialogContent.tsx | 42 ++++++++++++++++++ .../CashflowSheetPdfDialog/index.ts | 1 + .../ProfitLossSheet/ProfitLossActionsBar.tsx | 11 +++++ .../ProfitLossSheet/ProfitLossSheet.tsx | 3 ++ .../ProfitLossSheetDialogs.tsx | 10 +++++ .../ProfitLossSheetPdfDialog.tsx | 44 +++++++++++++++++++ .../ProfitLossSheetPdfDialogContent.tsx | 42 ++++++++++++++++++ .../ProfitLossSheetPdfDialog/index.ts | 1 + .../TrialBalanceActionsBar.tsx | 12 +++++ .../TrialBalanceSheet/TrialBalanceSheet.tsx | 3 ++ .../TrialBalanceSheetDialogs.tsx | 12 +++++ .../TrialBalanceSheetPdfDialog.tsx | 44 +++++++++++++++++++ .../TrialBalanceSheetPdfDialogContent.tsx | 42 ++++++++++++++++++ .../TrialBalanceSheetPdfDialog/index.ts | 1 + .../src/hooks/query/financialReports.tsx | 30 +++++++++++++ 27 files changed, 487 insertions(+), 2 deletions(-) create mode 100644 packages/webapp/src/containers/FinancialStatements/BalanceSheet/BalanceSheetDialogs.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/BalanceSheet/dialogs/BalanceSheetPdfDialog/BalanceSheetPdfDialog.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/BalanceSheet/dialogs/BalanceSheetPdfDialog/BalanceSheetPdfDialogContent.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/BalanceSheet/dialogs/BalanceSheetPdfDialog/index.ts create mode 100644 packages/webapp/src/containers/FinancialStatements/CashFlowStatement/CashflowSheetDialogs.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/CashFlowStatement/CashflowSheetPdfDialog/CashflowSheetPdfDialog.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/CashFlowStatement/CashflowSheetPdfDialog/CashflowSheetPdfDialogContent.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/CashFlowStatement/CashflowSheetPdfDialog/index.ts create mode 100644 packages/webapp/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossSheetDialogs.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossSheetPdfDialog/ProfitLossSheetPdfDialog.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossSheetPdfDialog/ProfitLossSheetPdfDialogContent.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/ProfitLossSheet/ProfitLossSheetPdfDialog/index.ts create mode 100644 packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/TrialBalanceSheetDialogs.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/dialogs/TrialBalanceSheetPdfDialog/TrialBalanceSheetPdfDialog.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/dialogs/TrialBalanceSheetPdfDialog/TrialBalanceSheetPdfDialogContent.tsx create mode 100644 packages/webapp/src/containers/FinancialStatements/TrialBalanceSheet/dialogs/TrialBalanceSheetPdfDialog/index.ts diff --git a/packages/webapp/src/components/FormattedMessage/index.tsx b/packages/webapp/src/components/FormattedMessage/index.tsx index b23418325..6dd6ff98b 100644 --- a/packages/webapp/src/components/FormattedMessage/index.tsx +++ b/packages/webapp/src/components/FormattedMessage/index.tsx @@ -1,8 +1,13 @@ // @ts-nocheck import intl from 'react-intl-universal'; -export function FormattedMessage({ id, values }) { - return intl.get(id, values); +interface FormattedMessageProps { + id: string; + values?: Record; +} + +export function FormattedMessage({ id, values }: FormattedMessageProps) { + return <>{intl.get(id, values)}; } export function FormattedHTMLMessage({ ...args }) { diff --git a/packages/webapp/src/constants/dialogs.ts b/packages/webapp/src/constants/dialogs.ts index c9bb52a0e..74ec090cf 100644 --- a/packages/webapp/src/constants/dialogs.ts +++ b/packages/webapp/src/constants/dialogs.ts @@ -53,4 +53,9 @@ export enum DialogsName { EstimateMail = 'estimate-mail', ReceiptMail = 'receipt-mail', PaymentMail = 'payment-mail', + BalanceSheetPdfPreview = 'BalanceSheetPdfPreview', + TrialBalanceSheetPdfPreview = 'TrialBalanceSheetPdfPreview', + CashflowSheetPdfPreview = 'CashflowSheetPdfPreview', + ProfitLossSheetPdfPreview = 'ProfitLossSheetPdfPreview', + } diff --git a/packages/webapp/src/containers/FinancialStatements/BalanceSheet/BalanceSheet.tsx b/packages/webapp/src/containers/FinancialStatements/BalanceSheet/BalanceSheet.tsx index 787c7f14e..11928622f 100644 --- a/packages/webapp/src/containers/FinancialStatements/BalanceSheet/BalanceSheet.tsx +++ b/packages/webapp/src/containers/FinancialStatements/BalanceSheet/BalanceSheet.tsx @@ -13,6 +13,7 @@ import { useBalanceSheetQuery } from './utils'; import { compose } from '@/utils'; import withBalanceSheetActions from './withBalanceSheetActions'; +import { BalanceSheetDialogs } from './BalanceSheetDialogs'; /** * Balance sheet. @@ -67,6 +68,8 @@ function BalanceSheet({ + + ); } diff --git a/packages/webapp/src/containers/FinancialStatements/BalanceSheet/BalanceSheetActionsBar.tsx b/packages/webapp/src/containers/FinancialStatements/BalanceSheet/BalanceSheetActionsBar.tsx index 0421ea2ae..af8de6c12 100644 --- a/packages/webapp/src/containers/FinancialStatements/BalanceSheet/BalanceSheetActionsBar.tsx +++ b/packages/webapp/src/containers/FinancialStatements/BalanceSheet/BalanceSheetActionsBar.tsx @@ -17,7 +17,9 @@ import { BalanceSheetExportMenu } from './components'; import { useBalanceSheetContext } from './BalanceSheetProvider'; import withBalanceSheet from './withBalanceSheet'; import withBalanceSheetActions from './withBalanceSheetActions'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; import { compose, saveInvoke } from '@/utils'; +import { DialogsName } from '@/constants/dialogs'; /** * Balance sheet - actions bar. @@ -29,6 +31,9 @@ function BalanceSheetActionsBar({ // #withBalanceSheetActions toggleBalanceSheetFilterDrawer: toggleFilterDrawer, + // #withDialogsActions + openDialog, + // #ownProps numberFormat, onNumberFormatSubmit, @@ -50,6 +55,10 @@ function BalanceSheetActionsBar({ saveInvoke(onNumberFormatSubmit, values); }; + const handlePdfPrintBtnSubmit = () => { + openDialog(DialogsName.BalanceSheetPdfPreview) + } + return ( @@ -111,6 +120,7 @@ function BalanceSheetActionsBar({