From 4ab20ac76a6af3b3124ca6690f58e39b49d1579e Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 13 Jan 2025 16:06:55 +0200 Subject: [PATCH] refactor: mail services to nestjs --- packages/server-nest/package.json | 4 +- .../server-nest/src/modules/App/App.module.ts | 2 + .../BankingTransactions.controller.ts | 7 +- .../BankingTransactionsApplication.service.ts | 6 +- .../queries/GetBankAccounts.service.ts | 5 +- .../types/BankingTransactions.types.ts | 13 +- .../BillPaymentsApplication.service.ts | 1 + .../src/modules/Bills/Bills.application.ts | 6 +- .../modules/Bills/queries/GetBills.service.ts | 2 +- .../CreditNoteApplication.service.ts | 12 +- .../queries/GetCreditNotes.service.ts | 2 +- .../CreditNotes/types/CreditNotes.types.ts | 21 +- .../models/InventoryCostLotTracker.ts | 4 +- .../models/InventoryTransaction.ts | 9 +- .../src/modules/Mail/Mail.constants.ts | 1 + .../src/modules/Mail/Mail.module.ts | 33 ++ packages/server-nest/src/modules/Mail/Mail.ts | 131 ++++++ .../src/modules/Mail/Mail.types.ts | 11 + .../modules/Mail/MailTransporter.service.ts | 15 + .../ContactMailNotification.ts | 80 ++++ .../MailNotification.module.ts | 8 + .../MailNotification.types.ts | 44 ++ .../src/modules/MailNotification/constants.ts | 6 + .../src/modules/MailNotification/utils.ts | 56 +++ .../modules/MailTenancy/MailTenancy.module.ts | 9 + .../MailTenancy/MailTenancy.service.ts | 27 ++ .../queries/GetManualJournals.service.ts | 5 +- .../types/ManualJournals.types.ts | 11 +- .../PaymentReceived.application.ts | 1 + .../PaymentsReceived.controller.ts | 5 +- .../PaymentsReceived.module.ts | 2 + .../PaymentReceivedMailNotification.ts | 295 +++++++------- .../types/PaymentReceived.types.ts | 16 +- .../SaleEstimates.application.ts | 43 +- .../SaleEstimates/SaleEstimates.controller.ts | 24 +- .../SaleEstimates/SaleEstimates.module.ts | 5 +- .../commands/SendSaleEstimateMail.ts | 362 ++++++++--------- .../queries/GetSaleEstimatePdf.ts | 93 +++++ .../SaleEstimates/queries/SaleEstimatesPdf.ts | 116 ------ .../types/SaleEstimates.types.ts | 23 +- .../modules/SaleInvoices/SaleInvoice.types.ts | 49 ++- .../SaleInvoices/SaleInvoices.application.ts | 17 +- .../SaleInvoices/SaleInvoices.module.ts | 15 +- .../SendInvoiceInvoiceMailCommon.service.ts | 230 +++++------ .../commands/SendSaleInvoiceMail.ts | 240 ++++++----- .../commands/SendSaleInvoiceMailReminder.ts | 192 ++++----- .../commands/WriteoffSaleInvoice.service.ts | 6 + .../inventory/InvoiceInventoryTransactions.ts | 37 +- .../SaleInvoices/queries/GetSaleInvoices.ts | 133 +++--- .../SaleReceiptApplication.service.ts | 23 +- .../SaleReceipts/SaleReceipts.module.ts | 4 +- .../commands/SaleReceiptMailNotification.ts | 384 ++++++++---------- .../queries/GetSaleReceipts.service.ts | 7 +- .../SaleReceipts/types/SaleReceipts.types.ts | 27 +- .../VendorCredit/VendorCredits.controller.ts | 3 +- .../VendorCreditsApplication.service.ts | 2 +- .../queries/GetVendorCredits.service.ts | 1 + ...orCreditInventoryTransactionsSusbcriber.ts | 4 +- .../VendorCredit/types/VendorCredit.types.ts | 13 +- .../src/modules/Vendors/Vendors.controller.ts | 1 + packages/server/src/lib/Mail/index.ts | 20 +- pnpm-lock.yaml | 15 + 62 files changed, 1635 insertions(+), 1304 deletions(-) create mode 100644 packages/server-nest/src/modules/Mail/Mail.constants.ts create mode 100644 packages/server-nest/src/modules/Mail/Mail.module.ts create mode 100644 packages/server-nest/src/modules/Mail/Mail.ts create mode 100644 packages/server-nest/src/modules/Mail/Mail.types.ts create mode 100644 packages/server-nest/src/modules/Mail/MailTransporter.service.ts create mode 100644 packages/server-nest/src/modules/MailNotification/ContactMailNotification.ts create mode 100644 packages/server-nest/src/modules/MailNotification/MailNotification.module.ts create mode 100644 packages/server-nest/src/modules/MailNotification/MailNotification.types.ts create mode 100644 packages/server-nest/src/modules/MailNotification/constants.ts create mode 100644 packages/server-nest/src/modules/MailNotification/utils.ts create mode 100644 packages/server-nest/src/modules/MailTenancy/MailTenancy.module.ts create mode 100644 packages/server-nest/src/modules/MailTenancy/MailTenancy.service.ts create mode 100644 packages/server-nest/src/modules/SaleEstimates/queries/GetSaleEstimatePdf.ts delete mode 100644 packages/server-nest/src/modules/SaleEstimates/queries/SaleEstimatesPdf.ts diff --git a/packages/server-nest/package.json b/packages/server-nest/package.json index b2bcbfaf2..2ccc8d068 100644 --- a/packages/server-nest/package.json +++ b/packages/server-nest/package.json @@ -36,6 +36,7 @@ "@nestjs/swagger": "^7.4.2", "@nestjs/throttler": "^6.2.1", "@supercharge/promise-pool": "^3.2.0", + "@types/nodemailer": "^6.4.17", "@types/passport-local": "^1.0.38", "@types/ramda": "^0.30.2", "accounting": "^0.4.1", @@ -51,8 +52,8 @@ "express-validator": "^7.2.0", "form-data": "^4.0.0", "fp-ts": "^2.16.9", - "js-money": "^0.6.3", "is-my-json-valid": "^2.20.5", + "js-money": "^0.6.3", "knex": "^3.1.0", "lamda": "^0.4.1", "lodash": "^4.17.21", @@ -61,6 +62,7 @@ "mysql2": "^3.11.3", "nestjs-cls": "^4.4.1", "nestjs-i18n": "^10.4.9", + "nodemailer": "^6.3.0", "object-hash": "^2.0.3", "objection": "^3.1.5", "passport": "^0.7.0", diff --git a/packages/server-nest/src/modules/App/App.module.ts b/packages/server-nest/src/modules/App/App.module.ts index 9445bd0a1..c8ef58b44 100644 --- a/packages/server-nest/src/modules/App/App.module.ts +++ b/packages/server-nest/src/modules/App/App.module.ts @@ -65,6 +65,7 @@ import { SettingsModule } from '../Settings/Settings.module'; import { InventoryAdjustmentsModule } from '../InventoryAdjutments/InventoryAdjustments.module'; import { PostHogModule } from '../EventsTracker/postHog.module'; import { EventTrackerModule } from '../EventsTracker/EventTracker.module'; +import { MailModule } from '../Mail/Mail.module'; @Module({ imports: [ @@ -120,6 +121,7 @@ import { EventTrackerModule } from '../EventsTracker/EventTracker.module'; TenancyModelsModule, ChromiumlyTenancyModule, TransformerModule, + MailModule, ItemsModule, ItemCategoryModule, AccountsModule, diff --git a/packages/server-nest/src/modules/BankingTransactions/BankingTransactions.controller.ts b/packages/server-nest/src/modules/BankingTransactions/BankingTransactions.controller.ts index a4e03cfa1..b5db2b292 100644 --- a/packages/server-nest/src/modules/BankingTransactions/BankingTransactions.controller.ts +++ b/packages/server-nest/src/modules/BankingTransactions/BankingTransactions.controller.ts @@ -8,7 +8,10 @@ import { Query, } from '@nestjs/common'; import { BankingTransactionsApplication } from './BankingTransactionsApplication.service'; -import { ICashflowNewCommandDTO } from './types/BankingTransactions.types'; +import { + IBankAccountsFilter, + ICashflowNewCommandDTO, +} from './types/BankingTransactions.types'; import { PublicRoute } from '../Auth/Jwt.guard'; @Controller('banking/transactions') @@ -19,7 +22,7 @@ export class BankingTransactionsController { ) {} @Get('') - async getBankAccounts(@Query() filterDTO: ICashflowAccountsFilter) { + async getBankAccounts(@Query() filterDTO: IBankAccountsFilter) { return this.bankingTransactionsApplication.getBankAccounts(filterDTO); } diff --git a/packages/server-nest/src/modules/BankingTransactions/BankingTransactionsApplication.service.ts b/packages/server-nest/src/modules/BankingTransactions/BankingTransactionsApplication.service.ts index 1e724d2e0..5090e8d8f 100644 --- a/packages/server-nest/src/modules/BankingTransactions/BankingTransactionsApplication.service.ts +++ b/packages/server-nest/src/modules/BankingTransactions/BankingTransactionsApplication.service.ts @@ -2,7 +2,7 @@ import { Knex } from 'knex'; import { DeleteCashflowTransaction } from './commands/DeleteCashflowTransaction.service'; import { CreateBankTransactionService } from './commands/CreateBankTransaction.service'; import { GetBankTransactionService } from './queries/GetBankTransaction.service'; -import { ICashflowNewCommandDTO } from './types/BankingTransactions.types'; +import { IBankAccountsFilter, ICashflowNewCommandDTO } from './types/BankingTransactions.types'; import { Injectable } from '@nestjs/common'; import { GetBankAccountsService } from './queries/GetBankAccounts.service'; @@ -48,9 +48,9 @@ export class BankingTransactionsApplication { /** * Retrieves the cashflow accounts. - * @param {ICashflowAccountsFilter} filterDTO + * @param {IBankAccountsFilter} filterDTO */ - public getBankAccounts(filterDTO: ICashflowAccountsFilter) { + public getBankAccounts(filterDTO: IBankAccountsFilter) { return this.getBankAccountsService.getBankAccounts(filterDTO); } } diff --git a/packages/server-nest/src/modules/BankingTransactions/queries/GetBankAccounts.service.ts b/packages/server-nest/src/modules/BankingTransactions/queries/GetBankAccounts.service.ts index 19f1c9ea2..b37f813cd 100644 --- a/packages/server-nest/src/modules/BankingTransactions/queries/GetBankAccounts.service.ts +++ b/packages/server-nest/src/modules/BankingTransactions/queries/GetBankAccounts.service.ts @@ -4,6 +4,7 @@ import { DynamicListService } from '@/modules/DynamicListing/DynamicList.service import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service'; import { CashflowAccountTransformer } from './BankAccountTransformer'; import { ACCOUNT_TYPE } from '@/constants/accounts'; +import { IBankAccountsFilter } from '../types/BankingTransactions.types'; @Injectable() export class GetBankAccountsService { @@ -12,7 +13,7 @@ export class GetBankAccountsService { private readonly transformer: TransformerInjectable, @Inject(BankAccount.name) - private readonly bankAccountModel: typeof BankAccount + private readonly bankAccountModel: typeof BankAccount, ) {} /** @@ -21,7 +22,7 @@ export class GetBankAccountsService { * @returns {ICashflowAccount[]} */ public async getBankAccounts( - filterDTO: ICashflowAccountsFilter, + filterDTO: IBankAccountsFilter, ): Promise { // Parsees accounts list filter DTO. const filter = this.dynamicListService.parseStringifiedFilter(filterDTO); diff --git a/packages/server-nest/src/modules/BankingTransactions/types/BankingTransactions.types.ts b/packages/server-nest/src/modules/BankingTransactions/types/BankingTransactions.types.ts index 1e7f8145b..e9086ca3b 100644 --- a/packages/server-nest/src/modules/BankingTransactions/types/BankingTransactions.types.ts +++ b/packages/server-nest/src/modules/BankingTransactions/types/BankingTransactions.types.ts @@ -1,6 +1,6 @@ -import { Knex } from "knex"; -import { UncategorizedBankTransaction } from "../models/UncategorizedBankTransaction"; -import { BankTransaction } from "../models/BankTransaction"; +import { Knex } from 'knex'; +import { UncategorizedBankTransaction } from '../models/UncategorizedBankTransaction'; +import { BankTransaction } from '../models/BankTransaction'; export interface IPendingTransactionRemovingEventPayload { uncategorizedTransactionId: number; @@ -49,6 +49,13 @@ export interface ICashflowNewCommandDTO extends ICashflowCommandDTO { uncategorizedTransactionId?: number; } +export interface IBankAccountsFilter { + inactiveMode: boolean; + stringifiedFilterRoles?: string; + sortOrder: string; + columnSortBy: string; +} + export enum CashflowDirection { IN = 'in', OUT = 'out', diff --git a/packages/server-nest/src/modules/BillPayments/BillPaymentsApplication.service.ts b/packages/server-nest/src/modules/BillPayments/BillPaymentsApplication.service.ts index 17e60119e..a9cd939ad 100644 --- a/packages/server-nest/src/modules/BillPayments/BillPaymentsApplication.service.ts +++ b/packages/server-nest/src/modules/BillPayments/BillPaymentsApplication.service.ts @@ -6,6 +6,7 @@ import { EditBillPayment } from './commands/EditBillPayment.service'; import { GetBillPayment } from './queries/GetBillPayment.service'; import { GetPaymentBills } from './queries/GetPaymentBills.service'; import { IBillPaymentDTO } from './types/BillPayments.types'; +import { GetBillPayments } from '../Bills/queries/GetBillPayments'; /** * Bill payments application. diff --git a/packages/server-nest/src/modules/Bills/Bills.application.ts b/packages/server-nest/src/modules/Bills/Bills.application.ts index c71b6e071..2328a5e5c 100644 --- a/packages/server-nest/src/modules/Bills/Bills.application.ts +++ b/packages/server-nest/src/modules/Bills/Bills.application.ts @@ -1,14 +1,14 @@ import { CreateBill } from './commands/CreateBill.service'; import { EditBillService } from './commands/EditBill.service'; import { GetBill } from './queries/GetBill'; -// import { GetBills } from './queries/GetBills'; import { DeleteBill } from './commands/DeleteBill.service'; -import { IBillDTO, IBillEditDTO } from './Bills.types'; +import { IBillDTO, IBillEditDTO, IBillsFilter } from './Bills.types'; import { GetDueBills } from './queries/GetDueBills.service'; import { OpenBillService } from './commands/OpenBill.service'; -import { GetBillPayments } from './queries/GetBillPayments'; import { Injectable } from '@nestjs/common'; import { GetBillsService } from './queries/GetBills.service'; +// import { GetBillPayments } from './queries/GetBillPayments'; +// import { GetBills } from './queries/GetBills'; @Injectable() export class BillsApplication { diff --git a/packages/server-nest/src/modules/Bills/queries/GetBills.service.ts b/packages/server-nest/src/modules/Bills/queries/GetBills.service.ts index 0bd68c6cf..51de6a884 100644 --- a/packages/server-nest/src/modules/Bills/queries/GetBills.service.ts +++ b/packages/server-nest/src/modules/Bills/queries/GetBills.service.ts @@ -18,7 +18,7 @@ export class GetBillsService { /** * Retrieve bills data table list. - * @param {IBillsFilter} billsFilter - + * @param {IBillsFilter} billsFilter - */ public async getBills(filterDTO: IBillsFilter): Promise<{ bills: Bill; diff --git a/packages/server-nest/src/modules/CreditNotes/CreditNoteApplication.service.ts b/packages/server-nest/src/modules/CreditNotes/CreditNoteApplication.service.ts index 3159b57a9..f2e530abc 100644 --- a/packages/server-nest/src/modules/CreditNotes/CreditNoteApplication.service.ts +++ b/packages/server-nest/src/modules/CreditNotes/CreditNoteApplication.service.ts @@ -14,12 +14,12 @@ import { GetCreditNotesService } from './queries/GetCreditNotes.service'; @Injectable() export class CreditNoteApplication { constructor( - private createCreditNoteService: CreateCreditNoteService, - private editCreditNoteService: EditCreditNoteService, - private openCreditNoteService: OpenCreditNoteService, - private deleteCreditNoteService: DeleteCreditNoteService, - private getCreditNotePdfService: GetCreditNotePdf, - private getCreditNotesService: GetCreditNotesService, + private readonly createCreditNoteService: CreateCreditNoteService, + private readonly editCreditNoteService: EditCreditNoteService, + private readonly openCreditNoteService: OpenCreditNoteService, + private readonly deleteCreditNoteService: DeleteCreditNoteService, + private readonly getCreditNotePdfService: GetCreditNotePdf, + private readonly getCreditNotesService: GetCreditNotesService, ) {} /** diff --git a/packages/server-nest/src/modules/CreditNotes/queries/GetCreditNotes.service.ts b/packages/server-nest/src/modules/CreditNotes/queries/GetCreditNotes.service.ts index ba3f1d097..eb9aa2e83 100644 --- a/packages/server-nest/src/modules/CreditNotes/queries/GetCreditNotes.service.ts +++ b/packages/server-nest/src/modules/CreditNotes/queries/GetCreditNotes.service.ts @@ -1,7 +1,7 @@ import * as R from 'ramda'; import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service'; import { DynamicListService } from '@/modules/DynamicListing/DynamicList.service'; -import { ICreditNotesQueryDTO } from '../types/CreditNotes.types'; +import { GetCreditNotesResponse, ICreditNotesQueryDTO } from '../types/CreditNotes.types'; import { CreditNote } from '../models/CreditNote'; import { CreditNoteTransformer } from './CreditNoteTransformer'; import { Inject } from '@nestjs/common'; diff --git a/packages/server-nest/src/modules/CreditNotes/types/CreditNotes.types.ts b/packages/server-nest/src/modules/CreditNotes/types/CreditNotes.types.ts index c8a7488fa..1207fa7e9 100644 --- a/packages/server-nest/src/modules/CreditNotes/types/CreditNotes.types.ts +++ b/packages/server-nest/src/modules/CreditNotes/types/CreditNotes.types.ts @@ -3,6 +3,8 @@ import { CreditNote } from '../models/CreditNote'; import { RefundCreditNote } from '../../CreditNoteRefunds/models/RefundCreditNote'; import { AttachmentLinkDTO } from '@/modules/Attachments/Attachments.types'; import { IItemEntryDTO } from '@/modules/TransactionItemEntry/ItemEntry.types'; +import { IFilterMeta, IPaginationMeta } from '@/interfaces/Model'; +import { IDynamicListFilter } from '@/modules/DynamicListing/DynamicFilter/DynamicFilter.types'; export interface ICreditNoteEntryNewDTO extends IItemEntryDTO {} @@ -91,17 +93,12 @@ export interface ICreditNoteOpenedPayload { oldCreditNote: CreditNote; trx: Knex.Transaction; } - -export interface ICreditNotesQueryDTO { - filterQuery?: (query: any) => void; +export interface ICreditNotesQueryDTO extends IDynamicListFilter { + page: number; + pageSize: number; + searchKeyword?: string; } -// export interface ICreditNotesQueryDTO extends IDynamicListFilter { -// page: number; -// pageSize: number; -// searchKeyword?: string; -// } - export interface ICreditNoteRefundDTO { fromAccountId: number; amount: number; @@ -129,6 +126,12 @@ export interface ICreditNoteRefundDTO { // | 'branchId' // >; +export interface GetCreditNotesResponse { + creditNotes: CreditNote[]; + pagination: IPaginationMeta; + filterMeta: IFilterMeta; +} + export interface CreditNotePdfTemplateAttributes { // # Primary color primaryColor: string; diff --git a/packages/server-nest/src/modules/InventoryCost/models/InventoryCostLotTracker.ts b/packages/server-nest/src/modules/InventoryCost/models/InventoryCostLotTracker.ts index f5351434a..bf604ff9a 100644 --- a/packages/server-nest/src/modules/InventoryCost/models/InventoryCostLotTracker.ts +++ b/packages/server-nest/src/modules/InventoryCost/models/InventoryCostLotTracker.ts @@ -1,6 +1,6 @@ import { Model } from 'objection'; import { castArray } from 'lodash'; -import moment from 'moment'; +import moment, { unitOfTime } from 'moment'; import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice'; import { SaleReceipt } from '@/modules/SaleReceipts/models/SaleReceipt'; import { Item } from '@/modules/Items/models/Item'; @@ -55,7 +55,7 @@ export class InventoryCostLotTracker extends BaseModel { query.groupBy('date'); query.groupBy('item_id'); }, - filterDateRange(query, startDate, endDate, type = 'day') { + filterDateRange(query, startDate, endDate, type: unitOfTime.StartOf = 'day') { const dateFormat = 'YYYY-MM-DD'; const fromDate = moment(startDate).startOf(type).format(dateFormat); const toDate = moment(endDate).endOf(type).format(dateFormat); diff --git a/packages/server-nest/src/modules/InventoryCost/models/InventoryTransaction.ts b/packages/server-nest/src/modules/InventoryCost/models/InventoryTransaction.ts index 27c3f4cf5..c4eac468c 100644 --- a/packages/server-nest/src/modules/InventoryCost/models/InventoryTransaction.ts +++ b/packages/server-nest/src/modules/InventoryCost/models/InventoryTransaction.ts @@ -1,6 +1,6 @@ import { Model, raw } from 'objection'; import { castArray } from 'lodash'; -import moment from 'moment'; +import moment, { unitOfTime } from 'moment'; import { BaseModel } from '@/models/Model'; import { getTransactionTypeLabel } from '@/modules/BankingTransactions/utils'; import { TInventoryTransactionDirection } from '../types/InventoryCost.types'; @@ -48,7 +48,12 @@ export class InventoryTransaction extends BaseModel { */ static get modifiers() { return { - filterDateRange(query, startDate, endDate, type = 'day') { + filterDateRange( + query, + startDate, + endDate, + type: unitOfTime.StartOf = 'day', + ) { const dateFormat = 'YYYY-MM-DD'; const fromDate = moment(startDate).startOf(type).format(dateFormat); const toDate = moment(endDate).endOf(type).format(dateFormat); diff --git a/packages/server-nest/src/modules/Mail/Mail.constants.ts b/packages/server-nest/src/modules/Mail/Mail.constants.ts new file mode 100644 index 000000000..473e50b5d --- /dev/null +++ b/packages/server-nest/src/modules/Mail/Mail.constants.ts @@ -0,0 +1 @@ +export const MAIL_TRANSPORTER_PROVIDER = 'MAIL_TRANSPORTER'; \ No newline at end of file diff --git a/packages/server-nest/src/modules/Mail/Mail.module.ts b/packages/server-nest/src/modules/Mail/Mail.module.ts new file mode 100644 index 000000000..43011b9ce --- /dev/null +++ b/packages/server-nest/src/modules/Mail/Mail.module.ts @@ -0,0 +1,33 @@ +import { Module } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { createTransport } from 'nodemailer'; +import { MAIL_TRANSPORTER_PROVIDER } from './Mail.constants'; +import { MailTransporter } from './MailTransporter.service'; + +@Module({ + imports: [ + { + module: MailModule, + providers: [ + { + provide: MAIL_TRANSPORTER_PROVIDER, + useFactory: (configService: ConfigService) => { + // Create reusable transporter object using the default SMTP transport + const transporter = createTransport({ + host: configService.get('mail.host'), + port: configService.get('mail.port'), + secure: configService.get('mail.secure'), // true for 465, false for other ports + auth: { + user: configService.get('mail.username'), + pass: configService.get('mail.password'), + }, + }); + return transporter; + }, + }, + ], + }, + MailTransporter + ], +}) +export class MailModule {} diff --git a/packages/server-nest/src/modules/Mail/Mail.ts b/packages/server-nest/src/modules/Mail/Mail.ts new file mode 100644 index 000000000..7c0e6f050 --- /dev/null +++ b/packages/server-nest/src/modules/Mail/Mail.ts @@ -0,0 +1,131 @@ +import * as fs from 'fs'; +import * as Mustache from 'mustache'; +import * as path from 'path'; +import { IMailAttachment } from './Mail.types'; + +export class Mail { + view: string; + subject: string = ''; + content: string = ''; + to: string | string[]; + cc: string | string[]; + bcc: string | string[]; + replyTo: string | string[]; + from: string = `${process.env.MAIL_FROM_NAME} ${process.env.MAIL_FROM_ADDRESS}`; + data: { [key: string]: string | number }; + attachments: IMailAttachment[]; + + /** + * Mail options. + */ + public get mailOptions() { + return { + to: this.to, + from: this.from, + cc: this.cc, + bcc: this.bcc, + subject: this.subject, + html: this.html, + attachments: this.attachments, + replyTo: this.replyTo, + }; + } + + /** + * Retrieves the html content of the mail. + * @returns {string} + */ + public get html() { + return this.view ? Mail.render(this.view, this.data) : this.content; + } + + /** + * Set send mail to address. + * @param {string} to - + */ + setTo(to: string | string[]) { + this.to = to; + return this; + } + + setCC(cc: string | string[]) { + this.cc = cc; + return this; + } + + setBCC(bcc: string | string[]) { + this.bcc = bcc; + return this; + } + + setReplyTo(replyTo: string | string[]) { + this.replyTo = replyTo; + return this; + } + + /** + * Sets from address to the mail. + * @param {string} from + * @return {} + */ + setFrom(from: string) { + this.from = from; + return this; + } + + /** + * Set attachments to the mail. + * @param {IMailAttachment[]} attachments + * @returns {Mail} + */ + setAttachments(attachments: IMailAttachment[]) { + this.attachments = attachments; + return this; + } + + /** + * Set mail subject. + * @param {string} subject + */ + setSubject(subject: string) { + this.subject = subject; + return this; + } + + /** + * Set view directory. + * @param {string} view + */ + setView(view: string) { + this.view = view; + return this; + } + + setData(data) { + this.data = data; + return this; + } + + setContent(content: string) { + this.content = content; + return this; + } + + /** + * Renders the view template with the given data. + * @param {object} data + * @return {string} + */ + static render(view: string, data: Record): string { + const viewContent = Mail.getViewContent(view); + return Mustache.render(viewContent, data); + } + + /** + * Retrieve view content from the view directory. + */ + static getViewContent(view: string): string { + const filePath = path.join(global.__views_dir, `/${view}`); + return fs.readFileSync(filePath, 'utf8'); + } +} diff --git a/packages/server-nest/src/modules/Mail/Mail.types.ts b/packages/server-nest/src/modules/Mail/Mail.types.ts new file mode 100644 index 000000000..e722415ec --- /dev/null +++ b/packages/server-nest/src/modules/Mail/Mail.types.ts @@ -0,0 +1,11 @@ +export type IMailAttachment = MailAttachmentPath | MailAttachmentContent; + +export interface MailAttachmentPath { + filename: string; + path: string; + cid: string; +} +export interface MailAttachmentContent { + filename: string; + content: Buffer; +} diff --git a/packages/server-nest/src/modules/Mail/MailTransporter.service.ts b/packages/server-nest/src/modules/Mail/MailTransporter.service.ts new file mode 100644 index 000000000..cbae45ea2 --- /dev/null +++ b/packages/server-nest/src/modules/Mail/MailTransporter.service.ts @@ -0,0 +1,15 @@ +import { Transporter } from 'nodemailer'; +import { Mail } from './Mail'; +import { Inject } from '@nestjs/common'; +import { MAIL_TRANSPORTER_PROVIDER } from './Mail.constants'; + +export class MailTransporter { + constructor( + @Inject(MAIL_TRANSPORTER_PROVIDER) + private readonly transporter: Transporter, + ) {} + + send(mail: Mail) { + return this.transporter.sendMail(mail.mailOptions); + } +} diff --git a/packages/server-nest/src/modules/MailNotification/ContactMailNotification.ts b/packages/server-nest/src/modules/MailNotification/ContactMailNotification.ts new file mode 100644 index 000000000..be2e29c54 --- /dev/null +++ b/packages/server-nest/src/modules/MailNotification/ContactMailNotification.ts @@ -0,0 +1,80 @@ +import { castArray } from 'lodash'; +import { Inject, Injectable } from '@nestjs/common'; +import { MailTenancy } from '../MailTenancy/MailTenancy.service'; +import { TenancyContext } from '../Tenancy/TenancyContext.service'; +import { Customer } from '../Customers/models/Customer'; +import { CommonMailOptions } from './MailNotification.types'; + +@Injectable() +export class ContactMailNotification { + constructor( + private readonly mailTenancy: MailTenancy, + private readonly tenantContext: TenancyContext, + + @Inject(Customer.name) + private readonly customerModel: typeof Customer, + ) {} + + /** + * Gets the default mail address of the given contact. + * @param {number} invoiceId - Contact id. + * @returns {Promise>} + */ + public async getDefaultMailOptions( + customerId: number, + ): Promise< + Pick + > { + const customer = await this.customerModel + .query() + .findById(customerId) + .throwIfNotFound(); + + const toOptions = customer.contactAddresses; + const fromOptions = await this.mailTenancy.senders(); + + const toAddress = toOptions.find((a) => a.primary); + const fromAddress = fromOptions.find((a) => a.primary); + + const to = toAddress?.mail ? castArray(toAddress?.mail) : []; + const from = fromAddress?.mail ? castArray(fromAddress?.mail) : []; + + return { to, from, toOptions, fromOptions }; + } + + /** + * Retrieves the mail options of the given contact. + * @param {number} tenantId - Tenant id. + * @returns {Promise} + */ + public async formatMailOptions( + mailOptions: CommonMailOptions, + formatterArgs?: Record, + ): Promise { + const commonFormatArgs = await this.getCommonFormatArgs(); + const formatArgs = { + ...commonFormatArgs, + ...formatterArgs, + }; + const subjectFormatted = formatSmsMessage(mailOptions?.subject, formatArgs); + const messageFormatted = formatSmsMessage(mailOptions?.message, formatArgs); + + return { + ...mailOptions, + subject: subjectFormatted, + message: messageFormatted, + }; + } + + /** + * Retrieves the common format args. + * @returns {Promise>} + */ + public async getCommonFormatArgs(): Promise> { + const tenantMetadata = await this.tenantContext.getTenantMetadata(); + + return { + ['Company Name']: tenantMetadata.name, + }; + } +} diff --git a/packages/server-nest/src/modules/MailNotification/MailNotification.module.ts b/packages/server-nest/src/modules/MailNotification/MailNotification.module.ts new file mode 100644 index 000000000..7be87b9d4 --- /dev/null +++ b/packages/server-nest/src/modules/MailNotification/MailNotification.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { ContactMailNotification } from './ContactMailNotification'; + +@Module({ + imports: [ContactMailNotification], + exports: [ContactMailNotification], +}) +export class MailNotificationModule {} diff --git a/packages/server-nest/src/modules/MailNotification/MailNotification.types.ts b/packages/server-nest/src/modules/MailNotification/MailNotification.types.ts new file mode 100644 index 000000000..faae8437e --- /dev/null +++ b/packages/server-nest/src/modules/MailNotification/MailNotification.types.ts @@ -0,0 +1,44 @@ +export type IMailAttachment = MailAttachmentPath | MailAttachmentContent; + +export interface MailAttachmentPath { + filename: string; + path: string; + cid: string; +} +export interface MailAttachmentContent { + filename: string; + content: Buffer; +} + +export interface IMailable { + constructor(view: string, data?: { [key: string]: string | number }); + send(): Promise; + build(): void; + setData(data: { [key: string]: string | number }): IMailable; + setTo(to: string): IMailable; + setFrom(from: string): IMailable; + setSubject(subject: string): IMailable; + setView(view: string): IMailable; + render(data?: { [key: string]: string | number }): string; + getViewContent(): string; +} + +export interface AddressItem { + label: string; + mail: string; + primary?: boolean; +} + +export interface CommonMailOptions { + from: Array; + subject: string; + message: string; + to: Array; + cc?: Array; + bcc?: Array; + formatArgs?: Record; + toOptions: Array; + fromOptions: Array; +} + +export interface CommonMailOptionsDTO extends Partial {} diff --git a/packages/server-nest/src/modules/MailNotification/constants.ts b/packages/server-nest/src/modules/MailNotification/constants.ts new file mode 100644 index 000000000..95b720d70 --- /dev/null +++ b/packages/server-nest/src/modules/MailNotification/constants.ts @@ -0,0 +1,6 @@ +export const ERRORS = { + MAIL_FROM_NOT_FOUND: 'Mail from address not found', + MAIL_TO_NOT_FOUND: 'Mail to address not found', + MAIL_SUBJECT_NOT_FOUND: 'Mail subject not found', + MAIL_BODY_NOT_FOUND: 'Mail body not found', +}; diff --git a/packages/server-nest/src/modules/MailNotification/utils.ts b/packages/server-nest/src/modules/MailNotification/utils.ts new file mode 100644 index 000000000..63e9550ba --- /dev/null +++ b/packages/server-nest/src/modules/MailNotification/utils.ts @@ -0,0 +1,56 @@ +import { castArray, isEmpty } from 'lodash'; +import { ServiceError } from '@/exceptions'; +import { CommonMailOptions } from '@/interfaces'; +import { ERRORS } from './constants'; + +/** + * Merges the mail options with incoming options. + * @param {Partial} mailOptions + * @param {Partial} overridedOptions + */ +export function parseMailOptions( + mailOptions: CommonMailOptions, + overridedOptions: Partial +): CommonMailOptions { + const mergedMessageOptions = { + ...mailOptions, + ...overridedOptions, + }; + const parsedMessageOptions = { + ...mergedMessageOptions, + from: mergedMessageOptions?.from + ? castArray(mergedMessageOptions?.from) + : [], + to: mergedMessageOptions?.to ? castArray(mergedMessageOptions?.to) : [], + cc: mergedMessageOptions?.cc ? castArray(mergedMessageOptions?.cc) : [], + bcc: mergedMessageOptions?.bcc ? castArray(mergedMessageOptions?.bcc) : [], + }; + return parsedMessageOptions; +} + +export function validateRequiredMailOptions( + mailOptions: Partial +) { + if (isEmpty(mailOptions.from)) { + throw new ServiceError(ERRORS.MAIL_FROM_NOT_FOUND); + } + if (isEmpty(mailOptions.to)) { + throw new ServiceError(ERRORS.MAIL_TO_NOT_FOUND); + } + if (isEmpty(mailOptions.subject)) { + throw new ServiceError(ERRORS.MAIL_SUBJECT_NOT_FOUND); + } + if (isEmpty(mailOptions.message)) { + throw new ServiceError(ERRORS.MAIL_BODY_NOT_FOUND); + } +} + +export const mergeAndValidateMailOptions = ( + mailOptions: CommonMailOptions, + overridedOptions: Partial +): CommonMailOptions => { + const parsedMessageOptions = parseMailOptions(mailOptions, overridedOptions); + validateRequiredMailOptions(parsedMessageOptions); + + return parsedMessageOptions; +}; diff --git a/packages/server-nest/src/modules/MailTenancy/MailTenancy.module.ts b/packages/server-nest/src/modules/MailTenancy/MailTenancy.module.ts new file mode 100644 index 000000000..be1dc9616 --- /dev/null +++ b/packages/server-nest/src/modules/MailTenancy/MailTenancy.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { MailTenancy } from './MailTenancy.service'; +import { TenancyContext } from '../Tenancy/TenancyContext.service'; + +@Module({ + imports: [], + providers: [MailTenancy, TenancyContext], +}) +export class MailTenancyModule {} diff --git a/packages/server-nest/src/modules/MailTenancy/MailTenancy.service.ts b/packages/server-nest/src/modules/MailTenancy/MailTenancy.service.ts new file mode 100644 index 000000000..3720f7bc9 --- /dev/null +++ b/packages/server-nest/src/modules/MailTenancy/MailTenancy.service.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@nestjs/common'; +import { TenancyContext } from '../Tenancy/TenancyContext.service'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class MailTenancy { + constructor( + private readonly tenancyContext: TenancyContext, + private readonly config: ConfigService + ) {} + + /** + * Retrieves the senders mails of the given tenant. + */ + public async senders() { + const tenantMetadata = await this.tenancyContext.getTenantMetadata(); + const from = this.config.get('mail.from'); + + return [ + { + mail: from, + label: tenantMetadata.name, + primary: true, + } + ].filter((item) => item.mail) + } +} \ No newline at end of file diff --git a/packages/server-nest/src/modules/ManualJournals/queries/GetManualJournals.service.ts b/packages/server-nest/src/modules/ManualJournals/queries/GetManualJournals.service.ts index 2e480bd4e..9072cac03 100644 --- a/packages/server-nest/src/modules/ManualJournals/queries/GetManualJournals.service.ts +++ b/packages/server-nest/src/modules/ManualJournals/queries/GetManualJournals.service.ts @@ -5,6 +5,7 @@ import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectab import { DynamicListService } from '@/modules/DynamicListing/DynamicList.service'; import { ManualJournal } from '../models/ManualJournal'; import { IFilterMeta, IPaginationMeta } from '@/interfaces/Model'; +import { IManualJournalsFilter } from '../types/ManualJournals.types'; @Injectable() export class GetManualJournals { @@ -26,7 +27,6 @@ export class GetManualJournals { /** * Retrieve manual journals datatable list. - * @param {number} tenantId - * @param {IManualJournalsFilter} filter - */ public getManualJournals = async ( @@ -44,7 +44,8 @@ export class GetManualJournals { ManualJournal, filter, ); - const { results, pagination } = await this.manualJournalModel.query() + const { results, pagination } = await this.manualJournalModel + .query() .onBuild((builder) => { dynamicService.buildQuery()(builder); builder.withGraphFetched('entries.account'); diff --git a/packages/server-nest/src/modules/ManualJournals/types/ManualJournals.types.ts b/packages/server-nest/src/modules/ManualJournals/types/ManualJournals.types.ts index f092e7db2..06bcb087b 100644 --- a/packages/server-nest/src/modules/ManualJournals/types/ManualJournals.types.ts +++ b/packages/server-nest/src/modules/ManualJournals/types/ManualJournals.types.ts @@ -5,6 +5,7 @@ import { Knex } from 'knex'; // import { AttachmentLinkDTO } from './Attachments'; import { ManualJournal } from '../models/ManualJournal'; import { AttachmentLinkDTO } from '@/modules/Attachments/Attachments.types'; +import { IDynamicListFilter } from '@/modules/DynamicListing/DynamicFilter/DynamicFilter.types'; export interface IManualJournalEntryDTO { index: number; @@ -31,11 +32,11 @@ export interface IManualJournalDTO { attachments?: AttachmentLinkDTO[]; } -// export interface IManualJournalsFilter extends IDynamicListFilterDTO { -// stringifiedFilterRoles?: string; -// page: number; -// pageSize: number; -// } +export interface IManualJournalsFilter extends IDynamicListFilter { + stringifiedFilterRoles?: string; + page: number; + pageSize: number; +} export interface IManualJournalEventPublishedPayload { // tenantId: number; diff --git a/packages/server-nest/src/modules/PaymentReceived/PaymentReceived.application.ts b/packages/server-nest/src/modules/PaymentReceived/PaymentReceived.application.ts index 0ce4c448b..58172ff2e 100644 --- a/packages/server-nest/src/modules/PaymentReceived/PaymentReceived.application.ts +++ b/packages/server-nest/src/modules/PaymentReceived/PaymentReceived.application.ts @@ -2,6 +2,7 @@ import { IPaymentReceivedCreateDTO, IPaymentReceivedEditDTO, IPaymentReceivedSmsDetails, + IPaymentsReceivedFilter, // IPaymentsReceivedFilter, // ISystemUser, // PaymentReceiveMailOptsDTO, diff --git a/packages/server-nest/src/modules/PaymentReceived/PaymentsReceived.controller.ts b/packages/server-nest/src/modules/PaymentReceived/PaymentsReceived.controller.ts index 6257b6cb7..0b12b608d 100644 --- a/packages/server-nest/src/modules/PaymentReceived/PaymentsReceived.controller.ts +++ b/packages/server-nest/src/modules/PaymentReceived/PaymentsReceived.controller.ts @@ -78,11 +78,10 @@ export class PaymentReceivesController { @Get(':id/pdf') public getPaymentReceivePdf( - @Param('id', ParseIntPipe) paymentReceiveId: number, + @Param('id', ParseIntPipe) paymentReceivedId: number, ) { return this.paymentReceivesApplication.getPaymentReceivePdf( - 1, - paymentReceiveId, + paymentReceivedId, ); } } diff --git a/packages/server-nest/src/modules/PaymentReceived/PaymentsReceived.module.ts b/packages/server-nest/src/modules/PaymentReceived/PaymentsReceived.module.ts index 934d6c33a..4a2f8a1cb 100644 --- a/packages/server-nest/src/modules/PaymentReceived/PaymentsReceived.module.ts +++ b/packages/server-nest/src/modules/PaymentReceived/PaymentsReceived.module.ts @@ -26,6 +26,7 @@ import { PaymentReceivedSyncInvoicesSubscriber } from './subscribers/PaymentRece import { PaymentReceivedInvoiceSync } from './commands/PaymentReceivedInvoiceSync.service'; import { LedgerModule } from '../Ledger/Ledger.module'; import { AccountsModule } from '../Accounts/Accounts.module'; +import { SendPaymentReceiveMailNotification } from './commands/PaymentReceivedMailNotification'; @Module({ controllers: [PaymentReceivesController], @@ -48,6 +49,7 @@ import { AccountsModule } from '../Accounts/Accounts.module'; PaymentReceivedAutoIncrementSubscriber, PaymentReceivedGLEntriesSubscriber, PaymentReceivedSyncInvoicesSubscriber, + SendPaymentReceiveMailNotification ], exports: [PaymentReceivesApplication, CreatePaymentReceivedService], imports: [ diff --git a/packages/server-nest/src/modules/PaymentReceived/commands/PaymentReceivedMailNotification.ts b/packages/server-nest/src/modules/PaymentReceived/commands/PaymentReceivedMailNotification.ts index 02de87252..c02e92098 100644 --- a/packages/server-nest/src/modules/PaymentReceived/commands/PaymentReceivedMailNotification.ts +++ b/packages/server-nest/src/modules/PaymentReceived/commands/PaymentReceivedMailNotification.ts @@ -1,162 +1,159 @@ -// import { Inject, Injectable } from '@nestjs/common'; -// import { -// PaymentReceiveMailOpts, -// PaymentReceiveMailOptsDTO, -// PaymentReceiveMailPresendEvent, -// SendInvoiceMailDTO, -// } from './types/PaymentReceived.types'; -// import Mail from '@/lib/Mail'; -// import { -// DEFAULT_PAYMENT_MAIL_CONTENT, -// DEFAULT_PAYMENT_MAIL_SUBJECT, -// } from './constants'; -// import { GetPaymentReceived } from './queries/GetPaymentReceived.service'; -// import { transformPaymentReceivedToMailDataArgs } from './utils'; -// import { PaymentReceived } from './models/PaymentReceived'; -// import { EventEmitter2 } from '@nestjs/event-emitter'; -// import { events } from '@/common/events/events'; +import { Inject, Injectable } from '@nestjs/common'; +import { + DEFAULT_PAYMENT_MAIL_CONTENT, + DEFAULT_PAYMENT_MAIL_SUBJECT, +} from '../constants'; +import { transformPaymentReceivedToMailDataArgs } from './utils'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { events } from '@/common/events/events'; +import { ContactMailNotification } from '@/modules/MailNotification/ContactMailNotification'; +import { PaymentReceived } from '../models/PaymentReceived'; +import { GetPaymentReceivedService } from '../queries/GetPaymentReceived.service'; +import { mergeAndValidateMailOptions } from '@/modules/MailNotification/utils'; +import { PaymentReceiveMailOptsDTO } from '../types/PaymentReceived.types'; +import { PaymentReceiveMailOpts } from '../types/PaymentReceived.types'; +import { PaymentReceiveMailPresendEvent } from '../types/PaymentReceived.types'; +import { SendInvoiceMailDTO } from '@/modules/SaleInvoices/SaleInvoice.types'; -// @Injectable() -// export class SendPaymentReceiveMailNotification { -// constructor( -// private getPaymentService: GetPaymentReceived, -// private contactMailNotification: ContactMailNotification, +@Injectable() +export class SendPaymentReceiveMailNotification { + constructor( + private getPaymentService: GetPaymentReceivedService, + private contactMailNotification: ContactMailNotification, + private eventEmitter: EventEmitter2, -// @Inject('agenda') private agenda: any, -// private eventPublisher: EventEmitter2, + @Inject(PaymentReceived.name) + private paymentReceiveModel: typeof PaymentReceived, + ) {} -// @Inject(PaymentReceived.name) -// private paymentReceiveModel: typeof PaymentReceived, -// ) {} + /** + * Sends the mail of the given payment receive. + * @param {number} tenantId + * @param {number} paymentReceiveId + * @param {PaymentReceiveMailOptsDTO} messageDTO + * @returns {Promise} + */ + public async triggerMail( + paymentReceiveId: number, + messageDTO: PaymentReceiveMailOptsDTO, + ): Promise { + const payload = { + paymentReceiveId, + messageDTO, + }; + // await this.agenda.now('payment-receive-mail-send', payload); -// /** -// * Sends the mail of the given payment receive. -// * @param {number} tenantId -// * @param {number} paymentReceiveId -// * @param {PaymentReceiveMailOptsDTO} messageDTO -// * @returns {Promise} -// */ -// public async triggerMail( -// paymentReceiveId: number, -// messageDTO: PaymentReceiveMailOptsDTO, -// ): Promise { -// const payload = { -// paymentReceiveId, -// messageDTO, -// }; -// await this.agenda.now('payment-receive-mail-send', payload); + // Triggers `onPaymentReceivePreMailSend` event. + await this.eventEmitter.emitAsync(events.paymentReceive.onPreMailSend, { + paymentReceiveId, + messageOptions: messageDTO, + } as PaymentReceiveMailPresendEvent); + } -// // Triggers `onPaymentReceivePreMailSend` event. -// await this.eventPublisher.emitAsync(events.paymentReceive.onPreMailSend, { -// paymentReceiveId, -// messageOptions: messageDTO, -// } as PaymentReceiveMailPresendEvent); -// } + /** + * Retrieves the default payment mail options. + * @param {number} paymentReceiveId - Payment receive id. + * @returns {Promise} + */ + public getMailOptions = async ( + paymentId: number, + ): Promise => { + const paymentReceive = await this.paymentReceiveModel + .query() + .findById(paymentId) + .throwIfNotFound(); -// /** -// * Retrieves the default payment mail options. -// * @param {number} paymentReceiveId - Payment receive id. -// * @returns {Promise} -// */ -// public getMailOptions = async ( -// paymentId: number, -// ): Promise => { -// const paymentReceive = await this.paymentReceiveModel -// .query() -// .findById(paymentId) -// .throwIfNotFound(); + const formatArgs = await this.textFormatter(paymentId); -// const formatArgs = await this.textFormatter(paymentId); + const mailOptions = + await this.contactMailNotification.getDefaultMailOptions( + paymentReceive.customerId, + ); + return { + ...mailOptions, + subject: DEFAULT_PAYMENT_MAIL_SUBJECT, + message: DEFAULT_PAYMENT_MAIL_CONTENT, + ...formatArgs, + }; + }; -// const mailOptions = -// await this.contactMailNotification.getDefaultMailOptions( -// paymentReceive.customerId, -// ); -// return { -// ...mailOptions, -// subject: DEFAULT_PAYMENT_MAIL_SUBJECT, -// message: DEFAULT_PAYMENT_MAIL_CONTENT, -// ...formatArgs, -// }; -// }; + /** + * Retrieves the formatted text of the given sale invoice. + * @param {number} invoiceId - Sale invoice id. + * @returns {Promise>} + */ + public textFormatter = async ( + invoiceId: number, + ): Promise> => { + const payment = await this.getPaymentService.getPaymentReceive(invoiceId); + return transformPaymentReceivedToMailDataArgs(payment); + }; -// /** -// * Retrieves the formatted text of the given sale invoice. -// * @param {number} invoiceId - Sale invoice id. -// * @returns {Promise>} -// */ -// public textFormatter = async ( -// invoiceId: number, -// ): Promise> => { -// const payment = await this.getPaymentService.getPaymentReceive(invoiceId); -// return transformPaymentReceivedToMailDataArgs(payment); -// }; + /** + * Retrieves the formatted mail options of the given payment receive. + * @param {number} tenantId + * @param {number} paymentReceiveId + * @param {SendInvoiceMailDTO} messageDTO + * @returns {Promise} + */ + public getFormattedMailOptions = async ( + paymentReceiveId: number, + messageDTO: SendInvoiceMailDTO, + ) => { + const formatterArgs = await this.textFormatter(paymentReceiveId); -// /** -// * Retrieves the formatted mail options of the given payment receive. -// * @param {number} tenantId -// * @param {number} paymentReceiveId -// * @param {SendInvoiceMailDTO} messageDTO -// * @returns {Promise} -// */ -// public getFormattedMailOptions = async ( -// paymentReceiveId: number, -// messageDTO: SendInvoiceMailDTO, -// ) => { -// const formatterArgs = await this.textFormatter(paymentReceiveId); + // Default message options. + const defaultMessageOpts = await this.getMailOptions(paymentReceiveId); + // Parsed message opts with default options. + const parsedMessageOpts = mergeAndValidateMailOptions( + defaultMessageOpts, + messageDTO, + ); + // Formats the message options. + return this.contactMailNotification.formatMailOptions( + parsedMessageOpts, + formatterArgs, + ); + }; -// // Default message options. -// const defaultMessageOpts = await this.getMailOptions(paymentReceiveId); -// // Parsed message opts with default options. -// const parsedMessageOpts = mergeAndValidateMailOptions( -// defaultMessageOpts, -// messageDTO, -// ); -// // Formats the message options. -// return this.contactMailNotification.formatMailOptions( -// parsedMessageOpts, -// formatterArgs, -// ); -// }; + /** + * Triggers the mail invoice. + * @param {number} tenantId + * @param {number} saleInvoiceId - Invoice id. + * @param {SendInvoiceMailDTO} messageDTO - Message options. + * @returns {Promise} + */ + public async sendMail( + paymentReceiveId: number, + messageDTO: PaymentReceiveMailOptsDTO, + ): Promise { + // Retrieves the formatted mail options. + const formattedMessageOptions = await this.getFormattedMailOptions( + paymentReceiveId, + messageDTO, + ); + const mail = new Mail() + .setSubject(formattedMessageOptions.subject) + .setTo(formattedMessageOptions.to) + .setCC(formattedMessageOptions.cc) + .setBCC(formattedMessageOptions.bcc) + .setContent(formattedMessageOptions.message); -// /** -// * Triggers the mail invoice. -// * @param {number} tenantId -// * @param {number} saleInvoiceId - Invoice id. -// * @param {SendInvoiceMailDTO} messageDTO - Message options. -// * @returns {Promise} -// */ -// public async sendMail( -// paymentReceiveId: number, -// messageDTO: SendInvoiceMailDTO, -// ): Promise { -// // Retrieves the formatted mail options. -// const formattedMessageOptions = await this.getFormattedMailOptions( -// paymentReceiveId, -// messageDTO, -// ); -// const mail = new Mail() -// .setSubject(formattedMessageOptions.subject) -// .setTo(formattedMessageOptions.to) -// .setCC(formattedMessageOptions.cc) -// .setBCC(formattedMessageOptions.bcc) -// .setContent(formattedMessageOptions.message); + const eventPayload = { + paymentReceiveId, + messageOptions: formattedMessageOptions, + }; + // Triggers `onPaymentReceiveMailSend` event. + await this.eventEmitter.emitAsync( + events.paymentReceive.onMailSend, + eventPayload, + ); + await mail.send(); -// const eventPayload = { -// paymentReceiveId, -// messageOptions: formattedMessageOptions, -// }; -// // Triggers `onPaymentReceiveMailSend` event. -// await this.eventPublisher.emitAsync( -// events.paymentReceive.onMailSend, -// eventPayload, -// ); -// await mail.send(); - -// // Triggers `onPaymentReceiveMailSent` event. -// await this.eventPublisher.emitAsync( -// events.paymentReceive.onMailSent, -// eventPayload, -// ); -// } -// } + // Triggers `onPaymentReceiveMailSent` event. + await this.eventEmitter.emitAsync( + events.paymentReceive.onMailSent, + eventPayload, + ); + } +} diff --git a/packages/server-nest/src/modules/PaymentReceived/types/PaymentReceived.types.ts b/packages/server-nest/src/modules/PaymentReceived/types/PaymentReceived.types.ts index 7d0359796..cefe06529 100644 --- a/packages/server-nest/src/modules/PaymentReceived/types/PaymentReceived.types.ts +++ b/packages/server-nest/src/modules/PaymentReceived/types/PaymentReceived.types.ts @@ -2,6 +2,7 @@ import { AttachmentLinkDTO } from '@/modules/Attachments/Attachments.types'; import { Knex } from 'knex'; import { PaymentReceived } from '../models/PaymentReceived'; import { IDynamicListFilter } from '@/modules/DynamicListing/DynamicFilter/DynamicFilter.types'; +import { CommonMailOptions, CommonMailOptionsDTO } from '@/modules/MailNotification/MailNotification.types'; export interface IPaymentReceivedCreateDTO { customerId: number; @@ -144,15 +145,12 @@ export enum PaymentReceiveAction { // | 'branchId' // >; -// export interface PaymentReceiveMailOpts extends CommonMailOptions {} - -// export interface PaymentReceiveMailOptsDTO extends CommonMailOptionsDTO {} - -// export interface PaymentReceiveMailPresendEvent { -// tenantId: number; -// paymentReceiveId: number; -// messageOptions: PaymentReceiveMailOptsDTO; -// } +export interface PaymentReceiveMailOpts extends CommonMailOptions {} +export interface PaymentReceiveMailOptsDTO extends CommonMailOptionsDTO {} +export interface PaymentReceiveMailPresendEvent { + paymentReceiveId: number; + messageOptions: PaymentReceiveMailOptsDTO; +} export interface PaymentReceivedPdfLineItem { item: string; diff --git a/packages/server-nest/src/modules/SaleEstimates/SaleEstimates.application.ts b/packages/server-nest/src/modules/SaleEstimates/SaleEstimates.application.ts index 7a22cab5a..a6a503abc 100644 --- a/packages/server-nest/src/modules/SaleEstimates/SaleEstimates.application.ts +++ b/packages/server-nest/src/modules/SaleEstimates/SaleEstimates.application.ts @@ -4,6 +4,8 @@ import { // IPaginationMeta, // IPaymentReceivedSmsDetails, ISaleEstimateDTO, + ISalesEstimatesFilter, + SaleEstimateMailOptionsDTO, // ISalesEstimatesFilter, // SaleEstimateMailOptions, // SaleEstimateMailOptionsDTO, @@ -16,12 +18,12 @@ import { DeliverSaleEstimateService } from './commands/DeliverSaleEstimate.servi import { ApproveSaleEstimateService } from './commands/ApproveSaleEstimate.service'; import { RejectSaleEstimateService } from './commands/RejectSaleEstimate.service'; // import { SaleEstimateNotifyBySms } from './commands/SaleEstimateSmsNotify'; -// import { SaleEstimatesPdf } from './queries/SaleEstimatesPdf'; -// import { SendSaleEstimateMail } from './commands/SendSaleEstimateMail'; +import { SendSaleEstimateMail } from './commands/SendSaleEstimateMail'; import { GetSaleEstimateState } from './queries/GetSaleEstimateState.service'; +import { GetSaleEstimatesService } from './queries/GetSaleEstimates.service'; import { Injectable } from '@nestjs/common'; import { IFilterMeta, IPaginationMeta } from '@/interfaces/Model'; -import { GetSaleEstimatesService } from './queries/GetSaleEstimates.service'; +import { GetSaleEstimatePdf } from './queries/GetSaleEstimatePdf'; @Injectable() export class SaleEstimatesApplication { @@ -34,10 +36,10 @@ export class SaleEstimatesApplication { private readonly deliverSaleEstimateService: DeliverSaleEstimateService, private readonly approveSaleEstimateService: ApproveSaleEstimateService, private readonly rejectSaleEstimateService: RejectSaleEstimateService, - // private readonly saleEstimateNotifyBySmsService: SaleEstimateNotifyBySms, - // private readonly saleEstimatesPdfService: SaleEstimatesPdf, - // private readonly sendEstimateMailService: SendSaleEstimateMail, + private readonly sendEstimateMailService: SendSaleEstimateMail, private readonly getSaleEstimateStateService: GetSaleEstimateState, + private readonly saleEstimatesPdfService: GetSaleEstimatePdf, + // private readonly saleEstimateNotifyBySmsService: SaleEstimateNotifyBySms, ) {} /** @@ -136,25 +138,28 @@ export class SaleEstimatesApplication { /** * Retrieve the PDF content of the given sale estimate. * @param {number} saleEstimateId - Sale estimate ID. - * @returns + * @returns {Promise<[Buffer, string]>} */ public getSaleEstimatePdf(saleEstimateId: number) { - // return this.saleEstimatesPdfService.getSaleEstimatePdf( - // saleEstimateId, - // ); + return this.saleEstimatesPdfService.getSaleEstimatePdf( + saleEstimateId, + ); } /** * Send the reminder mail of the given sale estimate. * @param {number} saleEstimateId - Sale estimate ID. + * @param {SaleEstimateMailOptionsDTO} saleEstimateMailOpts - Sale estimate mail options. * @returns {Promise} */ - public sendSaleEstimateMail() // saleEstimateMailOpts: SaleEstimateMailOptionsDTO, // saleEstimateId: number, - { - // return this.sendEstimateMailService.triggerMail( - // saleEstimateId, - // saleEstimateMailOpts, - // ); + public sendSaleEstimateMail( + saleEstimateId: number, + saleEstimateMailOpts: SaleEstimateMailOptionsDTO, + ) { + return this.sendEstimateMailService.triggerMail( + saleEstimateId, + saleEstimateMailOpts, + ); } /** @@ -163,9 +168,9 @@ export class SaleEstimatesApplication { * @returns {Promise} */ public getSaleEstimateMail(saleEstimateId: number) { - // return this.sendEstimateMailService.getMailOptions( - // saleEstimateId, - // ); + return this.sendEstimateMailService.getMailOptions( + saleEstimateId, + ); } /** diff --git a/packages/server-nest/src/modules/SaleEstimates/SaleEstimates.controller.ts b/packages/server-nest/src/modules/SaleEstimates/SaleEstimates.controller.ts index d19a5e488..89a6fe348 100644 --- a/packages/server-nest/src/modules/SaleEstimates/SaleEstimates.controller.ts +++ b/packages/server-nest/src/modules/SaleEstimates/SaleEstimates.controller.ts @@ -12,8 +12,8 @@ import { import { SaleEstimatesApplication } from './SaleEstimates.application'; import { ISaleEstimateDTO, - // ISalesEstimatesFilter, - // SaleEstimateMailOptionsDTO, + ISalesEstimatesFilter, + SaleEstimateMailOptionsDTO, } from './types/SaleEstimates.types'; import { SaleEstimate } from './models/SaleEstimate'; import { PublicRoute } from '../Auth/Jwt.guard'; @@ -107,16 +107,16 @@ export class SaleEstimatesController { return this.saleEstimatesApplication.getSaleEstimatePdf(saleEstimateId); } - // @Post(':id/mail') - // public sendSaleEstimateMail( - // @Param('id', ParseIntPipe) saleEstimateId: number, - // @Body() mailOptions: SaleEstimateMailOptionsDTO, - // ) { - // return this.saleEstimatesApplication.sendSaleEstimateMail( - // saleEstimateId, - // mailOptions, - // ); - // } + @Post(':id/mail') + public sendSaleEstimateMail( + @Param('id', ParseIntPipe) saleEstimateId: number, + @Body() mailOptions: SaleEstimateMailOptionsDTO, + ) { + return this.saleEstimatesApplication.sendSaleEstimateMail( + saleEstimateId, + mailOptions, + ); + } @Get(':id/mail') public getSaleEstimateMail( diff --git a/packages/server-nest/src/modules/SaleEstimates/SaleEstimates.module.ts b/packages/server-nest/src/modules/SaleEstimates/SaleEstimates.module.ts index b70bf5bd1..ca9a3f830 100644 --- a/packages/server-nest/src/modules/SaleEstimates/SaleEstimates.module.ts +++ b/packages/server-nest/src/modules/SaleEstimates/SaleEstimates.module.ts @@ -23,6 +23,7 @@ import { SaleEstimatesApplication } from './SaleEstimates.application'; import { DeleteSaleEstimate } from './commands/DeleteSaleEstimate.service'; import { GetSaleEstimate } from './queries/GetSaleEstimate.service'; import { GetSaleEstimateState } from './queries/GetSaleEstimateState.service'; +import { SendSaleEstimateMail } from './commands/SendSaleEstimateMail'; // import { SaleEstimateNotifyBySms } from './commands/SaleEstimateSmsNotify'; // import { SendSaleEstimateMail } from './commands/SendSaleEstimateMail'; // @@ -51,9 +52,9 @@ import { GetSaleEstimateState } from './queries/GetSaleEstimateState.service'; SaleEstimateDTOTransformer, TenancyContext, TransformerInjectable, - SaleEstimatesApplication + SaleEstimatesApplication, + SendSaleEstimateMail, // SaleEstimateNotifyBySms, - // SendSaleEstimateMail,p ], }) export class SaleEstimatesModule {} diff --git a/packages/server-nest/src/modules/SaleEstimates/commands/SendSaleEstimateMail.ts b/packages/server-nest/src/modules/SaleEstimates/commands/SendSaleEstimateMail.ts index 470a4d879..eea8c24e9 100644 --- a/packages/server-nest/src/modules/SaleEstimates/commands/SendSaleEstimateMail.ts +++ b/packages/server-nest/src/modules/SaleEstimates/commands/SendSaleEstimateMail.ts @@ -1,205 +1,187 @@ -// import { Inject, Service } from 'typedi'; -// import Mail from '@/lib/Mail'; -// import HasTenancyService from '@/services/Tenancy/TenancyService'; -// import { -// DEFAULT_ESTIMATE_REMINDER_MAIL_CONTENT, -// DEFAULT_ESTIMATE_REMINDER_MAIL_SUBJECT, -// } from '../constants'; -// import { SaleEstimatesPdf } from '../queries/SaleEstimatesPdf'; -// import { GetSaleEstimate } from '../queries/GetSaleEstimate.service'; -// import { -// ISaleEstimateMailPresendEvent, -// SaleEstimateMailOptions, -// SaleEstimateMailOptionsDTO, -// } from '@/interfaces'; -// import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification'; -// import { mergeAndValidateMailOptions } from '@/services/MailNotification/utils'; -// import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; -// import events from '@/subscribers/events'; -// import { transformEstimateToMailDataArgs } from '../utils'; +import { Inject, Injectable } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { ContactMailNotification } from '@/modules/MailNotification/ContactMailNotification'; +import { + DEFAULT_ESTIMATE_REMINDER_MAIL_CONTENT, + DEFAULT_ESTIMATE_REMINDER_MAIL_SUBJECT, +} from '../constants'; +import { GetSaleEstimate } from '../queries/GetSaleEstimate.service'; +import { transformEstimateToMailDataArgs } from '../utils'; +import { GetSaleEstimatePdf } from '../queries/GetSaleEstimatePdf'; +import { events } from '@/common/events/events'; +import { SaleEstimate } from '../models/SaleEstimate'; +import { mergeAndValidateMailOptions } from '@/modules/MailNotification/utils'; +import { + ISaleEstimateMailPresendEvent, + SaleEstimateMailOptionsDTO, +} from '../types/SaleEstimates.types'; +import { SaleEstimateMailOptions } from '../types/SaleEstimates.types'; +import { Mail } from '@/modules/Mail/Mail'; +import { MailTransporter } from '@/modules/Mail/MailTransporter.service'; -// @Service() -// export class SendSaleEstimateMail { -// @Inject() -// private tenancy: HasTenancyService; +@Injectable() +export class SendSaleEstimateMail { + /** + * @param {GetSaleEstimatePdf} estimatePdf - Estimate pdf service. + * @param {GetSaleEstimate} getSaleEstimateService - Get sale estimate service. + * @param {ContactMailNotification} contactMailNotification - Contact mail notification service. + * @param {EventEmitter2} eventPublisher - Event emitter. + * @param {MailTransporter} mailTransporter - Mail transporter service. + * @param {typeof SaleEstimate} saleEstimateModel - Sale estimate model. + */ + constructor( + private readonly estimatePdf: GetSaleEstimatePdf, + private readonly getSaleEstimateService: GetSaleEstimate, + private readonly contactMailNotification: ContactMailNotification, + private readonly eventPublisher: EventEmitter2, + private readonly mailTransporter: MailTransporter, -// @Inject() -// private estimatePdf: SaleEstimatesPdf; + @Inject(SaleEstimate.name) + private readonly saleEstimateModel: typeof SaleEstimate, + ) {} -// @Inject() -// private getSaleEstimateService: GetSaleEstimate; + /** + * Triggers the reminder mail of the given sale estimate. + * @param {number} saleEstimateId - Sale estimate id. + * @param {SaleEstimateMailOptionsDTO} messageOptions - Sale estimate mail options. + * @returns {Promise} + */ + public async triggerMail( + saleEstimateId: number, + messageOptions: SaleEstimateMailOptionsDTO, + ): Promise { + const payload = { + saleEstimateId, + messageOptions, + }; + // await this.agenda.now('sale-estimate-mail-send', payload); -// @Inject() -// private contactMailNotification: ContactMailNotification; + // Triggers `onSaleEstimatePreMailSend` event. + await this.eventPublisher.emitAsync(events.saleEstimate.onPreMailSend, { + saleEstimateId, + messageOptions, + } as ISaleEstimateMailPresendEvent); + } -// @Inject('agenda') -// private agenda: any; + /** + * Formate the text of the mail. + * @param {number} estimateId - Estimate id. + * @returns {Promise>} + */ + public formatterArgs = async (estimateId: number) => { + const estimate = await this.getSaleEstimateService.getEstimate(estimateId); + return transformEstimateToMailDataArgs(estimate); + }; -// @Inject() -// private eventPublisher: EventPublisher; + /** + * Retrieves the mail options. + * @param {number} saleEstimateId - Sale estimate id. + * @param {string} defaultSubject - Default subject. + * @param {string} defaultMessage - Default message. + * @returns {Promise} + */ + public getMailOptions = async ( + saleEstimateId: number, + defaultSubject: string = DEFAULT_ESTIMATE_REMINDER_MAIL_SUBJECT, + defaultMessage: string = DEFAULT_ESTIMATE_REMINDER_MAIL_CONTENT, + ): Promise => { + const saleEstimate = await this.saleEstimateModel + .query() + .findById(saleEstimateId) + .throwIfNotFound(); -// /** -// * Triggers the reminder mail of the given sale estimate. -// * @param {number} tenantId - -// * @param {number} saleEstimateId - -// * @param {SaleEstimateMailOptionsDTO} messageOptions - -// * @returns {Promise} -// */ -// public async triggerMail( -// tenantId: number, -// saleEstimateId: number, -// messageOptions: SaleEstimateMailOptionsDTO -// ): Promise { -// const payload = { -// tenantId, -// saleEstimateId, -// messageOptions, -// }; -// await this.agenda.now('sale-estimate-mail-send', payload); + const formatArgs = await this.formatterArgs(saleEstimateId); -// // Triggers `onSaleEstimatePreMailSend` event. -// await this.eventPublisher.emitAsync(events.saleEstimate.onPreMailSend, { -// tenantId, -// saleEstimateId, -// messageOptions, -// } as ISaleEstimateMailPresendEvent); -// } + const mailOptions = + await this.contactMailNotification.getDefaultMailOptions( + saleEstimate.customerId, + ); + return { + ...mailOptions, + message: defaultMessage, + subject: defaultSubject, + attachEstimate: true, + formatArgs, + }; + }; -// /** -// * Formate the text of the mail. -// * @param {number} tenantId - Tenant id. -// * @param {number} estimateId - Estimate id. -// * @returns {Promise>} -// */ -// public formatterArgs = async (tenantId: number, estimateId: number) => { -// const estimate = await this.getSaleEstimateService.getEstimate( -// tenantId, -// estimateId -// ); -// return transformEstimateToMailDataArgs(estimate); -// }; + /** + * Formats the given mail options. + * @param {number} saleEstimateId - Sale estimate id. + * @param {SaleEstimateMailOptions} mailOptions - Sale estimate mail options. + * @returns {Promise} + */ + public formatMailOptions = async ( + saleEstimateId: number, + mailOptions: SaleEstimateMailOptions, + ): Promise => { + const formatterArgs = await this.formatterArgs(saleEstimateId); + const formattedOptions = + await this.contactMailNotification.formatMailOptions( + mailOptions, + formatterArgs, + ); + return { ...formattedOptions }; + }; -// /** -// * Retrieves the mail options. -// * @param {number} tenantId -// * @param {number} saleEstimateId -// * @returns {Promise} -// */ -// public getMailOptions = async ( -// tenantId: number, -// saleEstimateId: number, -// defaultSubject: string = DEFAULT_ESTIMATE_REMINDER_MAIL_SUBJECT, -// defaultMessage: string = DEFAULT_ESTIMATE_REMINDER_MAIL_CONTENT -// ): Promise => { -// const { SaleEstimate } = this.tenancy.models(tenantId); + /** + * Sends the mail notification of the given sale estimate. + * @param {number} saleEstimateId - Sale estimate id. + * @param {SaleEstimateMailOptions} messageOptions - Sale estimate mail options. + * @returns {Promise} + */ + public async sendMail( + saleEstimateId: number, + messageOptions: SaleEstimateMailOptionsDTO, + ): Promise { + const localMessageOpts = await this.getMailOptions(saleEstimateId); + // Overrides and validates the given mail options. + const parsedMessageOptions = mergeAndValidateMailOptions( + localMessageOpts, + messageOptions, + ) as SaleEstimateMailOptions; -// const saleEstimate = await SaleEstimate.query() -// .findById(saleEstimateId) -// .throwIfNotFound(); + const formattedOptions = await this.formatMailOptions( + saleEstimateId, + parsedMessageOptions, + ); + const mail = new Mail() + .setSubject(formattedOptions.subject) + .setTo(formattedOptions.to) + .setCC(formattedOptions.cc) + .setBCC(formattedOptions.bcc) + .setContent(formattedOptions.message); -// const formatArgs = await this.formatterArgs(tenantId, saleEstimateId); + // Attaches the estimate pdf to the mail. + if (formattedOptions.attachEstimate) { + // Retrieves the estimate pdf and attaches it to the mail. + const [estimatePdfBuffer, estimateFilename] = + await this.estimatePdf.getSaleEstimatePdf(saleEstimateId); -// const mailOptions = -// await this.contactMailNotification.getDefaultMailOptions( -// tenantId, -// saleEstimate.customerId -// ); -// return { -// ...mailOptions, -// message: defaultMessage, -// subject: defaultSubject, -// attachEstimate: true, -// formatArgs, -// }; -// }; + mail.setAttachments([ + { + filename: `${estimateFilename}.pdf`, + content: estimatePdfBuffer, + }, + ]); + } -// /** -// * Formats the given mail options. -// * @param {number} tenantId -// * @param {number} saleEstimateId -// * @param {SaleEstimateMailOptions} mailOptions -// * @returns {Promise} -// */ -// public formatMailOptions = async ( -// tenantId: number, -// saleEstimateId: number, -// mailOptions: SaleEstimateMailOptions -// ): Promise => { -// const formatterArgs = await this.formatterArgs(tenantId, saleEstimateId); -// const formattedOptions = -// await this.contactMailNotification.formatMailOptions( -// tenantId, -// mailOptions, -// formatterArgs -// ); -// return { ...formattedOptions }; -// }; + const eventPayload = { + saleEstimateId, + messageOptions, + formattedOptions, + }; + // Triggers `onSaleEstimateMailSend` event. + await this.eventPublisher.emitAsync( + events.saleEstimate.onMailSend, + eventPayload as ISaleEstimateMailPresendEvent, + ); + await this.mailTransporter.send(mail); -// /** -// * Sends the mail notification of the given sale estimate. -// * @param {number} tenantId -// * @param {number} saleEstimateId -// * @param {SaleEstimateMailOptions} messageOptions -// * @returns {Promise} -// */ -// public async sendMail( -// tenantId: number, -// saleEstimateId: number, -// messageOptions: SaleEstimateMailOptionsDTO -// ): Promise { -// const localMessageOpts = await this.getMailOptions( -// tenantId, -// saleEstimateId -// ); -// // Overrides and validates the given mail options. -// const parsedMessageOptions = mergeAndValidateMailOptions( -// localMessageOpts, -// messageOptions -// ) as SaleEstimateMailOptions; - -// const formattedOptions = await this.formatMailOptions( -// tenantId, -// saleEstimateId, -// parsedMessageOptions -// ); -// const mail = new Mail() -// .setSubject(formattedOptions.subject) -// .setTo(formattedOptions.to) -// .setCC(formattedOptions.cc) -// .setBCC(formattedOptions.bcc) -// .setContent(formattedOptions.message); - -// // Attaches the estimate pdf to the mail. -// if (formattedOptions.attachEstimate) { -// // Retrieves the estimate pdf and attaches it to the mail. -// const [estimatePdfBuffer, estimateFilename] = -// await this.estimatePdf.getSaleEstimatePdf(tenantId, saleEstimateId); - -// mail.setAttachments([ -// { -// filename: `${estimateFilename}.pdf`, -// content: estimatePdfBuffer, -// }, -// ]); -// } - -// const eventPayload = { -// tenantId, -// saleEstimateId, -// messageOptions, -// formattedOptions, -// }; -// // Triggers `onSaleEstimateMailSend` event. -// await this.eventPublisher.emitAsync( -// events.saleEstimate.onMailSend, -// eventPayload as ISaleEstimateMailPresendEvent -// ); -// await mail.send(); - -// // Triggers `onSaleEstimateMailSent` event. -// await this.eventPublisher.emitAsync( -// events.saleEstimate.onMailSent, -// eventPayload as ISaleEstimateMailPresendEvent -// ); -// } -// } + // Triggers `onSaleEstimateMailSent` event. + await this.eventPublisher.emitAsync( + events.saleEstimate.onMailSent, + eventPayload as ISaleEstimateMailPresendEvent, + ); + } +} diff --git a/packages/server-nest/src/modules/SaleEstimates/queries/GetSaleEstimatePdf.ts b/packages/server-nest/src/modules/SaleEstimates/queries/GetSaleEstimatePdf.ts new file mode 100644 index 000000000..3c9ab97c9 --- /dev/null +++ b/packages/server-nest/src/modules/SaleEstimates/queries/GetSaleEstimatePdf.ts @@ -0,0 +1,93 @@ +import { GetSaleEstimate } from './GetSaleEstimate.service'; +import { transformEstimateToPdfTemplate } from '../utils'; +import { EstimatePdfBrandingAttributes } from '../constants'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { SaleEstimatePdfTemplate } from '@/modules/SaleInvoices/queries/SaleEstimatePdfTemplate.service'; +import { TemplateInjectable } from '@/modules/TemplateInjectable/TemplateInjectable.service'; +import { ChromiumlyTenancy } from '@/modules/ChromiumlyTenancy/ChromiumlyTenancy.service'; +import { PdfTemplateModel } from '@/modules/PdfTemplate/models/PdfTemplate'; +import { Inject, Injectable } from '@nestjs/common'; +import { events } from '@/common/events/events'; +import { SaleEstimate } from '../models/SaleEstimate'; + +@Injectable() +export class GetSaleEstimatePdf { + constructor( + private readonly chromiumlyTenancy: ChromiumlyTenancy, + private readonly templateInjectable: TemplateInjectable, + private readonly getSaleEstimate: GetSaleEstimate, + private readonly estimatePdfTemplate: SaleEstimatePdfTemplate, + private readonly eventPublisher: EventEmitter2, + + @Inject(PdfTemplateModel.name) + private readonly pdfTemplateModel: typeof PdfTemplateModel, + + @Inject(SaleEstimate.name) + private readonly saleEstimateModel: typeof SaleEstimate, + ) {} + + /** + * Retrieve sale invoice pdf content. + * @param {number} tenantId - + * @param {ISaleInvoice} saleInvoice - + */ + public async getSaleEstimatePdf( + saleEstimateId: number, + ): Promise<[Buffer, string]> { + const filename = await this.getSaleEstimateFilename(saleEstimateId); + const brandingAttributes = + await this.getEstimateBrandingAttributes(saleEstimateId); + const htmlContent = await this.templateInjectable.render( + 'modules/estimate-regular', + brandingAttributes, + ); + const content = + await this.chromiumlyTenancy.convertHtmlContent(htmlContent); + const eventPayload = { saleEstimateId }; + + // Triggers the `onSaleEstimatePdfViewed` event. + await this.eventPublisher.emitAsync( + events.saleEstimate.onPdfViewed, + eventPayload, + ); + return [content, filename]; + } + + /** + * Retrieves the filename file document of the given estimate. + * @param {number} estimateId - Estimate id. + * @returns {Promise} + */ + private async getSaleEstimateFilename(estimateId: number) { + const estimate = await this.saleEstimateModel.query().findById(estimateId); + + return `Estimate-${estimate.estimateNumber}`; + } + + /** + * Retrieves the given estimate branding attributes. + * @param {number} tenantId - Tenant id. + * @param {number} estimateId - Estimate id. + * @returns {Promise} + */ + async getEstimateBrandingAttributes( + estimateId: number, + ): Promise { + const saleEstimate = await this.getSaleEstimate.getEstimate(estimateId); + // Retrieve the invoice template id of not found get the default template id. + const templateId = + saleEstimate.pdfTemplateId ?? + ( + await this.pdfTemplateModel.query().findOne({ + resource: 'SaleEstimate', + default: true, + }) + )?.id; + const brandingTemplate = + await this.estimatePdfTemplate.getEstimatePdfTemplate(templateId); + return { + ...brandingTemplate.attributes, + ...transformEstimateToPdfTemplate(saleEstimate), + }; + } +} diff --git a/packages/server-nest/src/modules/SaleEstimates/queries/SaleEstimatesPdf.ts b/packages/server-nest/src/modules/SaleEstimates/queries/SaleEstimatesPdf.ts deleted file mode 100644 index 751539fd7..000000000 --- a/packages/server-nest/src/modules/SaleEstimates/queries/SaleEstimatesPdf.ts +++ /dev/null @@ -1,116 +0,0 @@ -// import { Inject, Service } from 'typedi'; -// import { ChromiumlyTenancy } from '@/services/ChromiumlyTenancy/ChromiumlyTenancy'; -// import { TemplateInjectable } from '@/services/TemplateInjectable/TemplateInjectable'; -// import { GetSaleEstimate } from './GetSaleEstimate.service'; -// import HasTenancyService from '@/services/Tenancy/TenancyService'; -// import { SaleEstimatePdfTemplate } from '../Invoices/SaleEstimatePdfTemplate'; -// import { transformEstimateToPdfTemplate } from '../utils'; -// import { EstimatePdfBrandingAttributes } from '../constants'; -// import events from '@/subscribers/events'; -// import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; - -// @Service() -// export class SaleEstimatesPdf { -// @Inject() -// private tenancy: HasTenancyService; - -// @Inject() -// private chromiumlyTenancy: ChromiumlyTenancy; - -// @Inject() -// private templateInjectable: TemplateInjectable; - -// @Inject() -// private getSaleEstimate: GetSaleEstimate; - -// @Inject() -// private estimatePdfTemplate: SaleEstimatePdfTemplate; - -// @Inject() -// private eventPublisher: EventPublisher; - -// /** -// * Retrieve sale invoice pdf content. -// * @param {number} tenantId - -// * @param {ISaleInvoice} saleInvoice - -// */ -// public async getSaleEstimatePdf( -// tenantId: number, -// saleEstimateId: number -// ): Promise<[Buffer, string]> { -// const filename = await this.getSaleEstimateFilename( -// tenantId, -// saleEstimateId -// ); -// const brandingAttributes = await this.getEstimateBrandingAttributes( -// tenantId, -// saleEstimateId -// ); -// const htmlContent = await this.templateInjectable.render( -// tenantId, -// 'modules/estimate-regular', -// brandingAttributes -// ); -// const content = await this.chromiumlyTenancy.convertHtmlContent( -// tenantId, -// htmlContent -// ); -// const eventPayload = { tenantId, saleEstimateId }; - -// // Triggers the `onSaleEstimatePdfViewed` event. -// await this.eventPublisher.emitAsync( -// events.saleEstimate.onPdfViewed, -// eventPayload -// ); -// return [content, filename]; -// } - -// /** -// * Retrieves the filename file document of the given estimate. -// * @param {number} tenantId -// * @param {number} estimateId -// * @returns {Promise} -// */ -// private async getSaleEstimateFilename(tenantId: number, estimateId: number) { -// const { SaleEstimate } = this.tenancy.models(tenantId); - -// const estimate = await SaleEstimate.query().findById(estimateId); - -// return `Estimate-${estimate.estimateNumber}`; -// } - -// /** -// * Retrieves the given estimate branding attributes. -// * @param {number} tenantId - Tenant id. -// * @param {number} estimateId - Estimate id. -// * @returns {Promise} -// */ -// async getEstimateBrandingAttributes( -// tenantId: number, -// estimateId: number -// ): Promise { -// const { PdfTemplate } = this.tenancy.models(tenantId); -// const saleEstimate = await this.getSaleEstimate.getEstimate( -// tenantId, -// estimateId -// ); -// // Retrieve the invoice template id of not found get the default template id. -// const templateId = -// saleEstimate.pdfTemplateId ?? -// ( -// await PdfTemplate.query().findOne({ -// resource: 'SaleEstimate', -// default: true, -// }) -// )?.id; -// const brandingTemplate = -// await this.estimatePdfTemplate.getEstimatePdfTemplate( -// tenantId, -// templateId -// ); -// return { -// ...brandingTemplate.attributes, -// ...transformEstimateToPdfTemplate(saleEstimate), -// }; -// } -// } diff --git a/packages/server-nest/src/modules/SaleEstimates/types/SaleEstimates.types.ts b/packages/server-nest/src/modules/SaleEstimates/types/SaleEstimates.types.ts index 11ce07ee5..4c46c49c9 100644 --- a/packages/server-nest/src/modules/SaleEstimates/types/SaleEstimates.types.ts +++ b/packages/server-nest/src/modules/SaleEstimates/types/SaleEstimates.types.ts @@ -5,6 +5,8 @@ import { SaleEstimate } from '../models/SaleEstimate'; import { IItemEntryDTO } from '@/modules/TransactionItemEntry/ItemEntry.types'; import { AttachmentLinkDTO } from '@/modules/Attachments/Attachments.types'; import { IDynamicListFilter } from '@/modules/DynamicListing/DynamicFilter/DynamicFilter.types'; +import { CommonMailOptionsDTO } from '@/modules/MailNotification/MailNotification.types'; +import { CommonMailOptions } from '@/modules/MailNotification/MailNotification.types'; export interface ISaleEstimateDTO { customerId: number; @@ -104,19 +106,18 @@ export interface ISaleEstimateApprovedEvent { trx: Knex.Transaction; } -// export interface SaleEstimateMailOptions extends CommonMailOptions { -// attachEstimate?: boolean; -// } +export interface SaleEstimateMailOptions extends CommonMailOptions { + attachEstimate?: boolean; +} -// export interface SaleEstimateMailOptionsDTO extends CommonMailOptionsDTO { -// attachEstimate?: boolean; -// } +export interface SaleEstimateMailOptionsDTO extends CommonMailOptionsDTO { + attachEstimate?: boolean; +} -// export interface ISaleEstimateMailPresendEvent { -// // tenantId: number; -// saleEstimateId: number; -// messageOptions: SaleEstimateMailOptionsDTO; -// } +export interface ISaleEstimateMailPresendEvent { + saleEstimateId: number; + messageOptions: SaleEstimateMailOptionsDTO; +} export interface ISaleEstimateState { defaultTemplateId: number; diff --git a/packages/server-nest/src/modules/SaleInvoices/SaleInvoice.types.ts b/packages/server-nest/src/modules/SaleInvoices/SaleInvoice.types.ts index 792ae8f5c..6f558313f 100644 --- a/packages/server-nest/src/modules/SaleInvoices/SaleInvoice.types.ts +++ b/packages/server-nest/src/modules/SaleInvoices/SaleInvoice.types.ts @@ -2,6 +2,8 @@ import { Knex } from 'knex'; import { IItemEntryDTO } from '../TransactionItemEntry/ItemEntry.types'; import { AttachmentLinkDTO } from '../Attachments/Attachments.types'; import { SaleInvoice } from './models/SaleInvoice'; +import { IDynamicListFilter } from '../DynamicListing/DynamicFilter/DynamicFilter.types'; +import { CommonMailOptionsDTO } from '../MailNotification/MailNotification.types'; // import SaleInvoice from './models/SaleInvoice'; // import { SystemUser } from '../System/models/SystemUser'; // import { ISystemUser, IAccount, ITaxTransaction } from '@/interfaces'; @@ -66,12 +68,12 @@ export interface ISaleInvoiceCreateDTO extends ISaleInvoiceDTO { export interface ISaleInvoiceEditDTO extends ISaleInvoiceDTO {} -// export interface ISalesInvoicesFilter extends IDynamicListFilter { -// page: number; -// pageSize: number; -// searchKeyword?: string; -// filterQuery?: (q: Knex.QueryBuilder) => void; -// } +export interface ISalesInvoicesFilter extends IDynamicListFilter { + page: number; + pageSize: number; + searchKeyword?: string; + filterQuery?: (q: Knex.QueryBuilder) => void; +} export interface ISaleInvoiceWriteoffDTO { expenseAccountId: number; @@ -212,28 +214,25 @@ export enum SaleInvoiceAction { // entries?: Array<{ label: string; total: string; quantity: string | number }>; // } -// export interface SendInvoiceMailDTO extends CommonMailOptionsDTO { -// attachInvoice?: boolean; -// } +export interface SendInvoiceMailDTO extends CommonMailOptionsDTO { + attachInvoice?: boolean; +} -// export interface ISaleInvoiceNotifyPayload { -// tenantId: number; -// saleInvoiceId: number; -// messageDTO: SendInvoiceMailDTO; -// } +export interface ISaleInvoiceNotifyPayload { + saleInvoiceId: number; + messageDTO: SendInvoiceMailDTO; +} -// export interface ISaleInvoiceMailSend { -// tenantId: number; -// saleInvoiceId: number; -// messageOptions: SendInvoiceMailDTO; -// formattedMessageOptions: SaleInvoiceMailOptions; -// } +export interface ISaleInvoiceMailSend { + saleInvoiceId: number; + messageOptions: SendInvoiceMailDTO; + // formattedMessageOptions: SaleInvoiceMailOptions; +} -// export interface ISaleInvoiceMailSent { -// tenantId: number; -// saleInvoiceId: number; -// messageOptions: SendInvoiceMailDTO; -// } +export interface ISaleInvoiceMailSent { + saleInvoiceId: number; + messageOptions: SendInvoiceMailDTO; +} // Invoice Pdf Document export interface InvoicePdfLine { diff --git a/packages/server-nest/src/modules/SaleInvoices/SaleInvoices.application.ts b/packages/server-nest/src/modules/SaleInvoices/SaleInvoices.application.ts index 3939c55b7..0dde4191f 100644 --- a/packages/server-nest/src/modules/SaleInvoices/SaleInvoices.application.ts +++ b/packages/server-nest/src/modules/SaleInvoices/SaleInvoices.application.ts @@ -18,7 +18,9 @@ import { ISaleInvoiceCreateDTO, ISaleInvoiceEditDTO, ISaleInvoiceWriteoffDTO, + ISalesInvoicesFilter, } from './SaleInvoice.types'; +import { GetSaleInvoicesService } from './queries/GetSaleInvoices'; @Injectable() export class SaleInvoiceApplication { @@ -26,18 +28,18 @@ export class SaleInvoiceApplication { private createSaleInvoiceService: CreateSaleInvoice, private deleteSaleInvoiceService: DeleteSaleInvoice, private getSaleInvoiceService: GetSaleInvoice, - // private getSaleInvoicesService: GetSaleInvoices, + private getSaleInvoicesService: GetSaleInvoicesService, private editSaleInvoiceService: EditSaleInvoice, private deliverSaleInvoiceService: DeliverSaleInvoice, private getReceivableSaleInvoicesService: GetSaleInvoicesPayable, private writeoffInvoiceService: WriteoffSaleInvoice, private getInvoicePaymentsService: GetInvoicePaymentsService, private pdfSaleInvoiceService: SaleInvoicePdf, + private getSaleInvoiceStateService: GetSaleInvoiceState, // private invoiceSms: SaleInvoiceNotifyBySms, - // private sendInvoiceReminderService: SendInvoiceMailReminder, + private sendInvoiceReminderService: SendInvoiceMailReminder, // private sendSaleInvoiceMailService: SendSaleInvoiceMail, // private getSaleInvoiceMailStateService: GetSaleInvoiceMailState, - private getSaleInvoiceStateService: GetSaleInvoiceState, ) {} /** @@ -77,13 +79,12 @@ export class SaleInvoiceApplication { /** * Retrieves the given sale invoice details. - * @param {number} tenantId * @param {ISalesInvoicesFilter} filterDTO - * @returns + * @returns {Promise<{ salesInvoices: SaleInvoice[]; pagination: IPaginationMeta; filterMeta: IFilterMeta; }>} */ - // public getSaleInvoices(filterDTO: ISalesInvoicesFilter) { - // return this.getSaleInvoicesService.getSaleInvoices(filterDTO); - // } + public getSaleInvoices(filterDTO: ISalesInvoicesFilter) { + return this.getSaleInvoicesService.getSaleInvoices(filterDTO); + } /** * Retrieves sale invoice details. diff --git a/packages/server-nest/src/modules/SaleInvoices/SaleInvoices.module.ts b/packages/server-nest/src/modules/SaleInvoices/SaleInvoices.module.ts index a821f3708..88c4e98e7 100644 --- a/packages/server-nest/src/modules/SaleInvoices/SaleInvoices.module.ts +++ b/packages/server-nest/src/modules/SaleInvoices/SaleInvoices.module.ts @@ -8,8 +8,6 @@ import { DeliverSaleInvoice } from './commands/DeliverSaleInvoice.service'; import { EditSaleInvoice } from './commands/EditSaleInvoice.service'; import { GenerateShareLink } from './commands/GenerateInvoicePaymentLink.service'; import { SaleInvoiceIncrement } from './commands/SaleInvoiceIncrement.service'; -// import { SendSaleInvoiceMail } from './commands/SendSaleInvoiceMail'; -// import { SendSaleInvoiceReminderMailJob } from './commands/SendSaleInvoiceMailReminderJob'; import { GetInvoicePaymentMail } from './queries/GetInvoicePaymentMail.service'; import { GetSaleInvoice } from './queries/GetSaleInvoice.service'; import { GetSaleInvoicesPayable } from './queries/GetSaleInvoicesPayable.service'; @@ -37,6 +35,10 @@ import { LedgerModule } from '../Ledger/Ledger.module'; import { AccountsModule } from '../Accounts/Accounts.module'; import SaleInvoiceWriteoffSubscriber from './subscribers/SaleInvoiceWriteoffSubscriber'; import { SaleInvoiceWriteoffGLStorage } from './commands/writeoff/SaleInvoiceWriteoffGLStorage'; +import { InvoiceInventoryTransactions } from './commands/inventory/InvoiceInventoryTransactions'; +import { SendSaleEstimateMail } from '../SaleEstimates/commands/SendSaleEstimateMail'; +import { SendInvoiceMailReminder } from './commands/SendSaleInvoiceMailReminder'; +import { MailModule } from '../Mail/Mail.module'; @Module({ imports: [ @@ -48,7 +50,8 @@ import { SaleInvoiceWriteoffGLStorage } from './commands/writeoff/SaleInvoiceWri WarehousesModule, TaxRatesModule, LedgerModule, - AccountsModule + AccountsModule, + MailModule, ], controllers: [SaleInvoicesController], providers: [ @@ -57,7 +60,6 @@ import { SaleInvoiceWriteoffGLStorage } from './commands/writeoff/SaleInvoiceWri DeleteSaleInvoice, GetSaleInvoicesPayable, DeliverSaleInvoice, - // SendSaleInvoiceMail, GenerateShareLink, GetInvoicePaymentMail, SaleInvoiceIncrement, @@ -79,7 +81,10 @@ import { SaleInvoiceWriteoffGLStorage } from './commands/writeoff/SaleInvoiceWri SaleInvoiceGLEntries, InvoiceGLEntriesSubscriber, SaleInvoiceWriteoffGLStorage, - SaleInvoiceWriteoffSubscriber + SaleInvoiceWriteoffSubscriber, + InvoiceInventoryTransactions, + SendSaleEstimateMail, + SendInvoiceMailReminder, ], }) export class SaleInvoicesModule {} diff --git a/packages/server-nest/src/modules/SaleInvoices/commands/SendInvoiceInvoiceMailCommon.service.ts b/packages/server-nest/src/modules/SaleInvoices/commands/SendInvoiceInvoiceMailCommon.service.ts index 42c98b021..1e7f3f3c0 100644 --- a/packages/server-nest/src/modules/SaleInvoices/commands/SendInvoiceInvoiceMailCommon.service.ts +++ b/packages/server-nest/src/modules/SaleInvoices/commands/SendInvoiceInvoiceMailCommon.service.ts @@ -1,129 +1,117 @@ -// import { Inject, Service } from 'typedi'; -// import { SaleInvoiceMailOptions } from '@/interfaces'; -// import HasTenancyService from '@/services/Tenancy/TenancyService'; -// import { GetSaleInvoice } from '../queries/GetSaleInvoice.service'; -// import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification'; -// import { -// DEFAULT_INVOICE_MAIL_CONTENT, -// DEFAULT_INVOICE_MAIL_SUBJECT, -// } from '../constants'; -// import { GetInvoicePaymentMail } from '../queries/GetInvoicePaymentMail.service'; -// import { GenerateShareLink } from './GenerateInvoicePaymentLink.service'; +import { GetSaleInvoice } from '../queries/GetSaleInvoice.service'; +import { + DEFAULT_INVOICE_MAIL_CONTENT, + DEFAULT_INVOICE_MAIL_SUBJECT, +} from '../constants'; +import { GetInvoicePaymentMail } from '../queries/GetInvoicePaymentMail.service'; +import { GenerateShareLink } from './GenerateInvoicePaymentLink.service'; +import { Inject, Injectable } from '@nestjs/common'; +import { SaleInvoice } from '../models/SaleInvoice'; +import { ContactMailNotification } from '@/modules/MailNotification/ContactMailNotification'; -// @Service() -// export class SendSaleInvoiceMailCommon { -// constructor( -// private getSaleInvoiceService: GetSaleInvoice, -// private contactMailNotification: ContactMailNotification, -// private getInvoicePaymentMail: GetInvoicePaymentMail, -// private generatePaymentLinkService: GenerateShareLink, -// ) {} +@Injectable() +export class SendSaleInvoiceMailCommon { + constructor( + private getSaleInvoiceService: GetSaleInvoice, + private contactMailNotification: ContactMailNotification, + private getInvoicePaymentMail: GetInvoicePaymentMail, + private generatePaymentLinkService: GenerateShareLink, -// /** -// * Retrieves the mail options. -// * @param {number} tenantId - Tenant id. -// * @param {number} invoiceId - Invoice id. -// * @param {string} defaultSubject - Subject text. -// * @param {string} defaultBody - Subject body. -// * @returns {Promise} -// */ -// public async getInvoiceMailOptions( -// invoiceId: number, -// defaultSubject: string = DEFAULT_INVOICE_MAIL_SUBJECT, -// defaultMessage: string = DEFAULT_INVOICE_MAIL_CONTENT, -// ): Promise { -// const { SaleInvoice } = this.tenancy.models(tenantId); + @Inject(SaleInvoice.name) + private readonly saleInvoiceModel: typeof SaleInvoice, + ) {} -// const saleInvoice = await SaleInvoice.query() -// .findById(invoiceId) -// .throwIfNotFound(); + /** + * Retrieves the mail options. + * @param {number} invoiceId - Invoice id. + * @param {string} defaultSubject - Subject text. + * @param {string} defaultBody - Subject body. + * @returns {Promise} + */ + public async getInvoiceMailOptions( + invoiceId: number, + defaultSubject: string = DEFAULT_INVOICE_MAIL_SUBJECT, + defaultMessage: string = DEFAULT_INVOICE_MAIL_CONTENT, + ): Promise { + const saleInvoice = await this.saleInvoiceModel + .query() + .findById(invoiceId) + .throwIfNotFound(); -// const contactMailDefaultOptions = -// await this.contactMailNotification.getDefaultMailOptions( -// tenantId, -// saleInvoice.customerId, -// ); -// const formatArgs = await this.getInvoiceFormatterArgs(tenantId, invoiceId); + const contactMailDefaultOptions = + await this.contactMailNotification.getDefaultMailOptions( + saleInvoice.customerId, + ); + const formatArgs = await this.getInvoiceFormatterArgs(invoiceId); -// return { -// ...contactMailDefaultOptions, -// subject: defaultSubject, -// message: defaultMessage, -// attachInvoice: true, -// formatArgs, -// }; -// } + return { + ...contactMailDefaultOptions, + subject: defaultSubject, + message: defaultMessage, + attachInvoice: true, + formatArgs, + }; + } -// /** -// * Formats the given invoice mail options. -// * @param {number} tenantId -// * @param {number} invoiceId -// * @param {SaleInvoiceMailOptions} mailOptions -// * @returns {Promise} -// */ -// public async formatInvoiceMailOptions( -// tenantId: number, -// invoiceId: number, -// mailOptions: SaleInvoiceMailOptions, -// ): Promise { -// const formatterArgs = await this.getInvoiceFormatterArgs( -// tenantId, -// invoiceId, -// ); -// const formattedOptions = -// await this.contactMailNotification.formatMailOptions( -// tenantId, -// mailOptions, -// formatterArgs, -// ); -// // Generates the a new payment link for the given invoice. -// const paymentLink = -// await this.generatePaymentLinkService.generatePaymentLink( -// tenantId, -// invoiceId, -// 'public', -// ); -// const message = await this.getInvoicePaymentMail.getMailTemplate( -// tenantId, -// invoiceId, -// { -// // # Invoice message -// invoiceMessage: formattedOptions.message, -// preview: formattedOptions.message, + /** + * Formats the given invoice mail options. + * @param {number} invoiceId + * @param {SaleInvoiceMailOptions} mailOptions + * @returns {Promise} + */ + public async formatInvoiceMailOptions( + invoiceId: number, + mailOptions: SaleInvoiceMailOptions, + ): Promise { + const formatterArgs = await this.getInvoiceFormatterArgs(invoiceId); + const formattedOptions = + await this.contactMailNotification.formatMailOptions( + mailOptions, + formatterArgs, + ); + // Generates the a new payment link for the given invoice. + const paymentLink = + await this.generatePaymentLinkService.generatePaymentLink( + invoiceId, + 'public', + ); + const message = await this.getInvoicePaymentMail.getMailTemplate( + invoiceId, + { + // # Invoice message + invoiceMessage: formattedOptions.message, + preview: formattedOptions.message, -// // # Payment link -// viewInvoiceButtonUrl: paymentLink.link, -// }, -// ); -// return { ...formattedOptions, message }; -// } + // # Payment link + viewInvoiceButtonUrl: paymentLink.link, + }, + ); + return { ...formattedOptions, message }; + } -// /** -// * Retrieves the formatted text of the given sale invoice. -// * @param {number} tenantId - Tenant id. -// * @param {number} invoiceId - Sale invoice id. -// * @param {string} text - The given text. -// * @returns {Promise} -// */ -// public getInvoiceFormatterArgs = async ( -// tenantId: number, -// invoiceId: number, -// ): Promise> => { -// const invoice = await this.getSaleInvoiceService.getSaleInvoice( -// tenantId, -// invoiceId, -// ); -// const commonArgs = -// await this.contactMailNotification.getCommonFormatArgs(tenantId); -// return { -// ...commonArgs, -// 'Customer Name': invoice.customer.displayName, -// 'Invoice Number': invoice.invoiceNo, -// 'Invoice Due Amount': invoice.dueAmountFormatted, -// 'Invoice Due Date': invoice.dueDateFormatted, -// 'Invoice Date': invoice.invoiceDateFormatted, -// 'Invoice Amount': invoice.totalFormatted, -// 'Overdue Days': invoice.overdueDays, -// }; -// }; -// } + /** + * Retrieves the formatted text of the given sale invoice. + * @param {number} tenantId - Tenant id. + * @param {number} invoiceId - Sale invoice id. + * @param {string} text - The given text. + * @returns {Promise} + */ + public getInvoiceFormatterArgs = async ( + invoiceId: number, + ): Promise> => { + const invoice = await this.getSaleInvoiceService.getSaleInvoice(invoiceId); + const commonArgs = + await this.contactMailNotification.getCommonFormatArgs(tenantId); + return { + ...commonArgs, + 'Customer Name': invoice.customer.displayName, + 'Invoice Number': invoice.invoiceNo, + 'Invoice Due Amount': invoice.dueAmountFormatted, + 'Invoice Due Date': invoice.dueDateFormatted, + 'Invoice Date': invoice.invoiceDateFormatted, + 'Invoice Amount': invoice.totalFormatted, + 'Overdue Days': invoice.overdueDays, + }; + }; +} + \ No newline at end of file diff --git a/packages/server-nest/src/modules/SaleInvoices/commands/SendSaleInvoiceMail.ts b/packages/server-nest/src/modules/SaleInvoices/commands/SendSaleInvoiceMail.ts index c54d8dc38..8bb7475fd 100644 --- a/packages/server-nest/src/modules/SaleInvoices/commands/SendSaleInvoiceMail.ts +++ b/packages/server-nest/src/modules/SaleInvoices/commands/SendSaleInvoiceMail.ts @@ -1,136 +1,124 @@ -// import { Inject, Service } from 'typedi'; -// import Mail from '@/lib/Mail'; -// import { -// ISaleInvoiceMailSend, -// SaleInvoiceMailOptions, -// SendInvoiceMailDTO, -// } from '@/interfaces'; -// import { SaleInvoicePdf } from '../queries/SaleInvoicePdf.service'; -// import { SendSaleInvoiceMailCommon } from './SendInvoiceInvoiceMailCommon.service'; -// import { mergeAndValidateMailOptions } from '@/services/MailNotification/utils'; -// import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; -// import events from '@/subscribers/events'; +import { Injectable } from '@nestjs/common'; +import { SaleInvoicePdf } from '../queries/SaleInvoicePdf.service'; +import { SendSaleInvoiceMailCommon } from './SendInvoiceInvoiceMailCommon.service'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { events } from '@/common/events/events'; +import { mergeAndValidateMailOptions } from '@/modules/MailNotification/utils'; +import { SendInvoiceMailDTO } from '../SaleInvoice.types'; +import { ISaleInvoiceMailSend } from '../SaleInvoice.types'; +import { Mail } from '@/modules/Mail/Mail'; +import { MailTransporter } from '@/modules/Mail/MailTransporter.service'; -// @Service() -// export class SendSaleInvoiceMail { -// @Inject() -// private invoicePdf: SaleInvoicePdf; +@Injectable() +export class SendSaleInvoiceMail { + /** + * @param {SaleInvoicePdf} invoicePdf - Sale invoice pdf service. + * @param {SendSaleInvoiceMailCommon} invoiceMail - Sale invoice mail service. + * @param {EventEmitter2} eventEmitter - Event emitter. + * @param {MailTransporter} mailTransporter - Mail transporter service. + */ + constructor( + private readonly invoicePdf: SaleInvoicePdf, + private readonly invoiceMail: SendSaleInvoiceMailCommon, + private readonly eventEmitter: EventEmitter2, + private readonly mailTransporter: MailTransporter, + ) {} -// @Inject() -// private invoiceMail: SendSaleInvoiceMailCommon; + /** + * Sends the invoice mail of the given sale invoice. + * @param {number} tenantId + * @param {number} saleInvoiceId + * @param {SendInvoiceMailDTO} messageDTO + */ + public async triggerMail( + saleInvoiceId: number, + messageOptions: SendInvoiceMailDTO, + ) { + const payload = { + saleInvoiceId, + messageOptions, + }; + // await this.agenda.now('sale-invoice-mail-send', payload); -// @Inject() -// private eventPublisher: EventPublisher; + // Triggers the event `onSaleInvoicePreMailSend`. + await this.eventEmitter.emitAsync(events.saleInvoice.onPreMailSend, { + saleInvoiceId, + messageOptions, + } as ISaleInvoiceMailSend); + } -// @Inject('agenda') -// private agenda: any; + /** + * Retrieves the formatted mail options. + * @param {number} saleInvoiceId + * @param {SendInvoiceMailDTO} messageOptions + * @returns {Promise} + */ + async getFormattedMailOptions( + saleInvoiceId: number, + messageOptions: SendInvoiceMailDTO, + ): Promise { + const defaultMessageOptions = + await this.invoiceMail.getInvoiceMailOptions(saleInvoiceId); -// /** -// * Sends the invoice mail of the given sale invoice. -// * @param {number} tenantId -// * @param {number} saleInvoiceId -// * @param {SendInvoiceMailDTO} messageDTO -// */ -// public async triggerMail( -// tenantId: number, -// saleInvoiceId: number, -// messageOptions: SendInvoiceMailDTO -// ) { -// const payload = { -// tenantId, -// saleInvoiceId, -// messageOptions, -// }; -// await this.agenda.now('sale-invoice-mail-send', payload); + // Merges message options with default options and parses the options values. + const parsedMessageOptions = mergeAndValidateMailOptions( + defaultMessageOptions, + messageOptions, + ); + return this.invoiceMail.formatInvoiceMailOptions( + saleInvoiceId, + parsedMessageOptions, + ); + } -// // Triggers the event `onSaleInvoicePreMailSend`. -// await this.eventPublisher.emitAsync(events.saleInvoice.onPreMailSend, { -// tenantId, -// saleInvoiceId, -// messageOptions, -// } as ISaleInvoiceMailSend); -// } + /** + * Triggers the mail invoice. + * @param {number} saleInvoiceId - Sale invoice id. + * @param {SendInvoiceMailDTO} messageDTO - Message options. + * @returns {Promise} + */ + public async sendMail( + saleInvoiceId: number, + messageOptions: SendInvoiceMailDTO, + ) { + const formattedMessageOptions = await this.getFormattedMailOptions( + saleInvoiceId, + messageOptions, + ); + const mail = new Mail() + .setSubject(formattedMessageOptions.subject) + .setTo(formattedMessageOptions.to) + .setCC(formattedMessageOptions.cc) + .setBCC(formattedMessageOptions.bcc) + .setContent(formattedMessageOptions.message); -// /** -// * Retrieves the formatted mail options. -// * @param {number} tenantId -// * @param {number} saleInvoiceId -// * @param {SendInvoiceMailDTO} messageOptions -// * @returns {Promise} -// */ -// async getFormattedMailOptions( -// tenantId: number, -// saleInvoiceId: number, -// messageOptions: SendInvoiceMailDTO -// ): Promise { -// const defaultMessageOptions = await this.invoiceMail.getInvoiceMailOptions( -// tenantId, -// saleInvoiceId -// ); -// // Merges message options with default options and parses the options values. -// const parsedMessageOptions = mergeAndValidateMailOptions( -// defaultMessageOptions, -// messageOptions -// ); -// return this.invoiceMail.formatInvoiceMailOptions( -// tenantId, -// saleInvoiceId, -// parsedMessageOptions -// ); -// } + // Attach invoice document. + if (formattedMessageOptions.attachInvoice) { + // Retrieves document buffer of the invoice pdf document. + const [invoicePdfBuffer, invoiceFilename] = + await this.invoicePdf.getSaleInvoicePdf(saleInvoiceId); -// /** -// * Triggers the mail invoice. -// * @param {number} tenantId -// * @param {number} saleInvoiceId -// * @param {SendInvoiceMailDTO} messageDTO -// * @returns {Promise} -// */ -// public async sendMail( -// tenantId: number, -// saleInvoiceId: number, -// messageOptions: SendInvoiceMailDTO -// ) { -// const formattedMessageOptions = await this.getFormattedMailOptions( -// tenantId, -// saleInvoiceId, -// messageOptions -// ); -// const mail = new Mail() -// .setSubject(formattedMessageOptions.subject) -// .setTo(formattedMessageOptions.to) -// .setCC(formattedMessageOptions.cc) -// .setBCC(formattedMessageOptions.bcc) -// .setContent(formattedMessageOptions.message); + mail.setAttachments([ + { filename: `${invoiceFilename}.pdf`, content: invoicePdfBuffer }, + ]); + } + const eventPayload = { + saleInvoiceId, + messageOptions, + formattedMessageOptions, + } as ISaleInvoiceMailSend; -// // Attach invoice document. -// if (formattedMessageOptions.attachInvoice) { -// // Retrieves document buffer of the invoice pdf document. -// const [invoicePdfBuffer, invoiceFilename] = -// await this.invoicePdf.saleInvoicePdf(tenantId, saleInvoiceId); + // Triggers the event `onSaleInvoiceSend`. + await this.eventEmitter.emitAsync( + events.saleInvoice.onMailSend, + eventPayload, + ); + await this.mailTransporter.send(mail); -// mail.setAttachments([ -// { filename: `${invoiceFilename}.pdf`, content: invoicePdfBuffer }, -// ]); -// } -// const eventPayload = { -// tenantId, -// saleInvoiceId, -// messageOptions, -// formattedMessageOptions, -// } as ISaleInvoiceMailSend; - -// // Triggers the event `onSaleInvoiceSend`. -// await this.eventPublisher.emitAsync( -// events.saleInvoice.onMailSend, -// eventPayload -// ); -// await mail.send(); - -// // Triggers the event `onSaleInvoiceSend`. -// await this.eventPublisher.emitAsync( -// events.saleInvoice.onMailSent, -// eventPayload -// ); -// } -// } + // Triggers the event `onSaleInvoiceSend`. + await this.eventEmitter.emitAsync( + events.saleInvoice.onMailSent, + eventPayload, + ); + } +} diff --git a/packages/server-nest/src/modules/SaleInvoices/commands/SendSaleInvoiceMailReminder.ts b/packages/server-nest/src/modules/SaleInvoices/commands/SendSaleInvoiceMailReminder.ts index 6bd49e09c..11ce2ef97 100644 --- a/packages/server-nest/src/modules/SaleInvoices/commands/SendSaleInvoiceMailReminder.ts +++ b/packages/server-nest/src/modules/SaleInvoices/commands/SendSaleInvoiceMailReminder.ts @@ -1,112 +1,96 @@ -// import { Inject, Service } from 'typedi'; -// import { -// ISaleInvoiceMailSend, -// ISaleInvoiceMailSent, -// SendInvoiceMailDTO, -// } from '@/interfaces'; -// import Mail from '@/lib/Mail'; -// import { SaleInvoicePdf } from '../queries/SaleInvoicePdf'; -// import { SendSaleInvoiceMailCommon } from './SendInvoiceInvoiceMailCommon'; -// import { -// DEFAULT_INVOICE_REMINDER_MAIL_CONTENT, -// DEFAULT_INVOICE_REMINDER_MAIL_SUBJECT, -// } from '../constants'; -// import { parseAndValidateMailOptions } from '@/services/MailNotification/utils'; -// import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; -// import events from '@/subscribers/events'; +import { Injectable } from '@nestjs/common'; +import { + DEFAULT_INVOICE_REMINDER_MAIL_CONTENT, + DEFAULT_INVOICE_REMINDER_MAIL_SUBJECT, +} from '../constants'; +import { SaleInvoicePdf } from '../queries/SaleInvoicePdf.service'; +import { SendSaleInvoiceMailCommon } from './SendInvoiceInvoiceMailCommon.service'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { events } from '@/common/events/events'; +import { ISaleInvoiceMailSend, ISaleInvoiceMailSent, SendInvoiceMailDTO } from '../SaleInvoice.types'; +import { mergeAndValidateMailOptions } from '@/modules/MailNotification/utils'; +import { MailTransporter } from '@/modules/Mail/MailTransporter.service'; +import { Mail } from '@/modules/Mail/Mail'; -// @Service() -// export class SendInvoiceMailReminder { -// @Inject('agenda') -// private agenda: any; +@Injectable() +export class SendInvoiceMailReminder { + constructor( + private readonly invoicePdf: SaleInvoicePdf, + private readonly invoiceCommonMail: SendSaleInvoiceMailCommon, + private readonly eventEmitter: EventEmitter2, + private readonly mailTransporter: MailTransporter, + ) {} -// @Inject() -// private invoicePdf: SaleInvoicePdf; + /** + * Triggers the reminder mail of the given sale invoice. + * @param {number} saleInvoiceId + */ + public async triggerMail( + saleInvoiceId: number, + messageOptions: SendInvoiceMailDTO, + ) { + const payload = { + saleInvoiceId, + messageOptions, + }; + // await this.agenda.now('sale-invoice-reminder-mail-send', payload); + } -// @Inject() -// private invoiceCommonMail: SendSaleInvoiceMailCommon; + /** + * Retrieves the mail options of the given sale invoice. + * @param {number} saleInvoiceId - The sale invocie id. + * @returns {Promise} + */ + public async getMailOption(saleInvoiceId: number) { + return this.invoiceCommonMail.getMailOption( + saleInvoiceId, + DEFAULT_INVOICE_REMINDER_MAIL_SUBJECT, + DEFAULT_INVOICE_REMINDER_MAIL_CONTENT, + ); + } -// @Inject() -// private eventPublisher: EventPublisher; + /** + * Triggers the mail invoice. + * @param {number} saleInvoiceId - Sale invoice id. + * @param {SendInvoiceMailDTO} messageOptions - The message options. + * @returns {Promise} + */ + public async sendMail( + saleInvoiceId: number, + messageOptions: SendInvoiceMailDTO, + ) { + const localMessageOpts = await this.getMailOption(saleInvoiceId); -// /** -// * Triggers the reminder mail of the given sale invoice. -// * @param {number} tenantId -// * @param {number} saleInvoiceId -// */ -// public async triggerMail( -// tenantId: number, -// saleInvoiceId: number, -// messageOptions: SendInvoiceMailDTO -// ) { -// const payload = { -// tenantId, -// saleInvoiceId, -// messageOptions, -// }; -// await this.agenda.now('sale-invoice-reminder-mail-send', payload); -// } + const messageOpts = mergeAndValidateMailOptions( + localMessageOpts, + messageOptions, + ); + const mail = new Mail() + .setSubject(messageOpts.subject) + .setTo(messageOpts.to) + .setContent(messageOpts.body); -// /** -// * Retrieves the mail options of the given sale invoice. -// * @param {number} tenantId -// * @param {number} saleInvoiceId -// * @returns {Promise} -// */ -// public async getMailOption(tenantId: number, saleInvoiceId: number) { -// return this.invoiceCommonMail.getMailOption( -// tenantId, -// saleInvoiceId, -// DEFAULT_INVOICE_REMINDER_MAIL_SUBJECT, -// DEFAULT_INVOICE_REMINDER_MAIL_CONTENT -// ); -// } + if (messageOpts.attachInvoice) { + // Retrieves document buffer of the invoice pdf document. + const [invoicePdfBuffer, filename] = await this.invoicePdf.getSaleInvoicePdf( + saleInvoiceId, + ); + mail.setAttachments([ + { filename, content: invoicePdfBuffer }, + ]); + } + // Triggers the event `onSaleInvoiceSend`. + await this.eventEmitter.emitAsync(events.saleInvoice.onMailReminderSend, { + saleInvoiceId, + messageOptions, + } as ISaleInvoiceMailSend); -// /** -// * Triggers the mail invoice. -// * @param {number} tenantId -// * @param {number} saleInvoiceId -// * @param {SendInvoiceMailDTO} messageOptions -// * @returns {Promise} -// */ -// public async sendMail( -// tenantId: number, -// saleInvoiceId: number, -// messageOptions: SendInvoiceMailDTO -// ) { -// const localMessageOpts = await this.getMailOption(tenantId, saleInvoiceId); + await this.mailTransporter.send(mail); -// const messageOpts = parseAndValidateMailOptions( -// localMessageOpts, -// messageOptions -// ); -// const mail = new Mail() -// .setSubject(messageOpts.subject) -// .setTo(messageOpts.to) -// .setContent(messageOpts.body); - -// if (messageOpts.attachInvoice) { -// // Retrieves document buffer of the invoice pdf document. -// const invoicePdfBuffer = await this.invoicePdf.saleInvoicePdf( -// tenantId, -// saleInvoiceId -// ); -// mail.setAttachments([ -// { 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); -// } -// } + // Triggers the event `onSaleInvoiceSent`. + await this.eventEmitter.emitAsync(events.saleInvoice.onMailReminderSent, { + saleInvoiceId, + messageOptions, + } as ISaleInvoiceMailSent); + } +} diff --git a/packages/server-nest/src/modules/SaleInvoices/commands/WriteoffSaleInvoice.service.ts b/packages/server-nest/src/modules/SaleInvoices/commands/WriteoffSaleInvoice.service.ts index fc246f4c5..e728dac2a 100644 --- a/packages/server-nest/src/modules/SaleInvoices/commands/WriteoffSaleInvoice.service.ts +++ b/packages/server-nest/src/modules/SaleInvoices/commands/WriteoffSaleInvoice.service.ts @@ -16,6 +16,12 @@ import { ServiceError } from '../../Items/ServiceError'; @Injectable() export class WriteoffSaleInvoice { + /** + * @param {EventEmitter2} eventPublisher - Event emitter. + * @param {UnitOfWork} uow - Unit of work. + * @param {CommandSaleInvoiceValidators} validators - Command sale invoice validators. + * @param {typeof SaleInvoice} saleInvoiceModel - Sale invoice model. + */ constructor( private readonly eventPublisher: EventEmitter2, private readonly uow: UnitOfWork, diff --git a/packages/server-nest/src/modules/SaleInvoices/commands/inventory/InvoiceInventoryTransactions.ts b/packages/server-nest/src/modules/SaleInvoices/commands/inventory/InvoiceInventoryTransactions.ts index c04413836..a5c75e1ff 100644 --- a/packages/server-nest/src/modules/SaleInvoices/commands/inventory/InvoiceInventoryTransactions.ts +++ b/packages/server-nest/src/modules/SaleInvoices/commands/inventory/InvoiceInventoryTransactions.ts @@ -1,38 +1,34 @@ -import { Inject, Service } from 'typedi'; +import { InventoryService } from '@/modules/InventoryCost/Inventory'; +import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service'; +import { Injectable } from '@nestjs/common'; import { Knex } from 'knex'; -import { ISaleInvoice } from '@/interfaces'; -import ItemsEntriesService from '@/services/Items/ItemsEntriesService'; -import InventoryService from '@/services/Inventory/Inventory'; +import { SaleInvoice } from '../../models/SaleInvoice'; -@Service() +@Injectable() export class InvoiceInventoryTransactions { - @Inject() - private itemsEntriesService: ItemsEntriesService; - - @Inject() - private inventoryService: InventoryService; + constructor( + private readonly itemsEntriesService: ItemsEntriesService, + private readonly inventoryService: InventoryService, + ) {} /** * Records the inventory transactions of the given sale invoice in case * the invoice has inventory entries only. - * - * @param {number} tenantId - Tenant id. * @param {SaleInvoice} saleInvoice - Sale invoice DTO. * @param {number} saleInvoiceId - Sale invoice id. * @param {boolean} override - Allow to override old transactions. * @return {Promise} */ public async recordInventoryTranscactions( - saleInvoice: ISaleInvoice, + saleInvoice: SaleInvoice, override?: boolean, - trx?: Knex.Transaction + trx?: Knex.Transaction, ): Promise { // Loads the inventory items entries of the given sale invoice. const inventoryEntries = await this.itemsEntriesService.filterInventoryEntries( - tenantId, saleInvoice.entries, - trx + trx, ); const transaction = { transactionId: saleInvoice.id, @@ -48,30 +44,27 @@ export class InvoiceInventoryTransactions { createdAt: saleInvoice.createdAt, }; await this.inventoryService.recordInventoryTransactionsFromItemsEntries( - tenantId, transaction, override, - trx + trx, ); } /** * Reverting the inventory transactions once the invoice deleted. - * @param {number} tenantId - Tenant id. * @param {number} billId - Bill id. * @return {Promise} */ public async revertInventoryTransactions( saleInvoiceId: number, - trx?: Knex.Transaction + trx?: Knex.Transaction, ): Promise { // Delete the inventory transaction of the given sale invoice. const { oldInventoryTransactions } = await this.inventoryService.deleteInventoryTransactions( - tenantId, saleInvoiceId, 'SaleInvoice', - trx + trx, ); } } diff --git a/packages/server-nest/src/modules/SaleInvoices/queries/GetSaleInvoices.ts b/packages/server-nest/src/modules/SaleInvoices/queries/GetSaleInvoices.ts index 472ddeb01..b4ab5a188 100644 --- a/packages/server-nest/src/modules/SaleInvoices/queries/GetSaleInvoices.ts +++ b/packages/server-nest/src/modules/SaleInvoices/queries/GetSaleInvoices.ts @@ -1,80 +1,65 @@ -// import { Inject, Service } from 'typedi'; -// import * as R from 'ramda'; -// import { -// IFilterMeta, -// IPaginationMeta, -// ISaleInvoice, -// ISalesInvoicesFilter, -// } from '@/interfaces'; -// import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable'; -// import HasTenancyService from '@/services/Tenancy/TenancyService'; -// import DynamicListingService from '@/services/DynamicListing/DynamicListService'; -// import { SaleInvoiceTransformer } from './SaleInvoice.transformer'; +import * as R from 'ramda'; +import { SaleInvoiceTransformer } from './SaleInvoice.transformer'; +import { Injectable } from '@nestjs/common'; +import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service'; +import { DynamicListService } from '@/modules/DynamicListing/DynamicList.service'; +import { IFilterMeta, IPaginationMeta } from '@/interfaces/Model'; +import { SaleInvoice } from '../models/SaleInvoice'; +import { ISalesInvoicesFilter } from '../SaleInvoice.types'; -// @Service() -// export class GetSaleInvoices { -// @Inject() -// private tenancy: HasTenancyService; +@Injectable() +export class GetSaleInvoicesService { + constructor( + private readonly dynamicListService: DynamicListService, + private readonly transformer: TransformerInjectable, + ) {} -// @Inject() -// private dynamicListService: DynamicListingService; + /** + * Retrieve sales invoices filterable and paginated list. + * @param {ISalesInvoicesFilter} filterDTO - + * @returns {Promise<{ salesInvoices: SaleInvoice[]; pagination: IPaginationMeta; filterMeta: IFilterMeta; }>} + */ + public async getSaleInvoices(filterDTO: ISalesInvoicesFilter): Promise<{ + salesInvoices: SaleInvoice[]; + pagination: IPaginationMeta; + filterMeta: IFilterMeta; + }> { + // Parses stringified filter roles. + const filter = this.parseListFilterDTO(filterDTO); -// @Inject() -// private transformer: TransformerInjectable; + // Dynamic list service. + const dynamicFilter = await this.dynamicListService.dynamicList( + SaleInvoice, + filter, + ); + const { results, pagination } = await SaleInvoice.query() + .onBuild((builder) => { + builder.withGraphFetched('entries.item'); + builder.withGraphFetched('customer'); + dynamicFilter.buildQuery()(builder); + filterDTO?.filterQuery && filterDTO?.filterQuery(builder); + }) + .pagination(filter.page - 1, filter.pageSize); -// /** -// * Retrieve sales invoices filterable and paginated list. -// * @param {Request} req -// * @param {Response} res -// * @param {NextFunction} next -// */ -// public async getSaleInvoices( -// filterDTO: ISalesInvoicesFilter -// ): Promise<{ -// salesInvoices: ISaleInvoice[]; -// pagination: IPaginationMeta; -// filterMeta: IFilterMeta; -// }> { -// const { SaleInvoice } = this.tenancy.models(tenantId); + // Retrieves the transformed sale invoices. + const salesInvoices = await this.transformer.transform( + results, + new SaleInvoiceTransformer(), + ); -// // Parses stringified filter roles. -// const filter = this.parseListFilterDTO(filterDTO); + return { + salesInvoices, + pagination, + filterMeta: dynamicFilter.getResponseMeta(), + }; + } -// // Dynamic list service. -// const dynamicFilter = await this.dynamicListService.dynamicList( -// tenantId, -// SaleInvoice, -// filter -// ); -// const { results, pagination } = await SaleInvoice.query() -// .onBuild((builder) => { -// builder.withGraphFetched('entries.item'); -// builder.withGraphFetched('customer'); -// dynamicFilter.buildQuery()(builder); -// filterDTO?.filterQuery && filterDTO?.filterQuery(builder); -// }) -// .pagination(filter.page - 1, filter.pageSize); - -// // Retrieves the transformed sale invoices. -// const salesInvoices = await this.transformer.transform( -// tenantId, -// results, -// new SaleInvoiceTransformer() -// ); - -// return { -// salesInvoices, -// pagination, -// filterMeta: dynamicFilter.getResponseMeta(), -// }; -// } - -// /** -// * Parses the sale invoice list filter DTO. -// * @param filterDTO -// * @returns -// */ -// private parseListFilterDTO(filterDTO) { -// return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO); -// } -// } + /** + * Parses the sale invoice list filter DTO. + * @param filterDTO + * @returns + */ + private parseListFilterDTO(filterDTO) { + return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO); + } +} diff --git a/packages/server-nest/src/modules/SaleReceipts/SaleReceiptApplication.service.ts b/packages/server-nest/src/modules/SaleReceipts/SaleReceiptApplication.service.ts index 2bd2a7d07..267d32b8b 100644 --- a/packages/server-nest/src/modules/SaleReceipts/SaleReceiptApplication.service.ts +++ b/packages/server-nest/src/modules/SaleReceipts/SaleReceiptApplication.service.ts @@ -12,10 +12,12 @@ import { ISaleReceiptDTO, ISaleReceiptState, ISalesReceiptsFilter, + SaleReceiptMailOpts, } from './types/SaleReceipts.types'; import { GetSaleReceiptsService } from './queries/GetSaleReceipts.service'; import { SaleReceipt } from './models/SaleReceipt'; -import { IPaginationMeta } from '@/interfaces/Model'; +import { IFilterMeta, IPaginationMeta } from '@/interfaces/Model'; +import { SaleReceiptMailNotification } from './commands/SaleReceiptMailNotification'; @Injectable() export class SaleReceiptApplication { @@ -28,8 +30,8 @@ export class SaleReceiptApplication { private closeSaleReceiptService: CloseSaleReceipt, private getSaleReceiptPdfService: SaleReceiptsPdfService, // private saleReceiptNotifyBySmsService: SaleReceiptNotifyBySms, - // private saleReceiptNotifyByMailService: SaleReceiptMailNotification, private getSaleReceiptStateService: GetSaleReceiptState, + private saleReceiptNotifyByMailService: SaleReceiptMailNotification, ) {} /** @@ -158,19 +160,14 @@ export class SaleReceiptApplication { /** * Retrieves the default mail options of the given sale receipt. - * @param {number} tenantId - * @param {number} saleReceiptId + * @param {number} saleReceiptId - Sale receipt identifier. * @returns {Promise} */ - // public getSaleReceiptMail( - // tenantId: number, - // saleReceiptId: number, - // ): Promise { - // return this.saleReceiptNotifyByMailService.getMailOptions( - // tenantId, - // saleReceiptId, - // ); - // } + public getSaleReceiptMail( + saleReceiptId: number, + ): Promise { + return this.saleReceiptNotifyByMailService.getMailOptions(saleReceiptId); + } /** * Retrieves the current state of the sale receipt. diff --git a/packages/server-nest/src/modules/SaleReceipts/SaleReceipts.module.ts b/packages/server-nest/src/modules/SaleReceipts/SaleReceipts.module.ts index 735d76f11..df8a10123 100644 --- a/packages/server-nest/src/modules/SaleReceipts/SaleReceipts.module.ts +++ b/packages/server-nest/src/modules/SaleReceipts/SaleReceipts.module.ts @@ -26,6 +26,7 @@ import { LedgerModule } from '../Ledger/Ledger.module'; import { AccountsModule } from '../Accounts/Accounts.module'; import { SaleReceiptInventoryTransactionsSubscriber } from './inventory/SaleReceiptWriteInventoryTransactions'; import { GetSaleReceiptsService } from './queries/GetSaleReceipts.service'; +import { SaleReceiptMailNotification } from './commands/SaleReceiptMailNotification'; @Module({ controllers: [SaleReceiptsController], @@ -57,7 +58,8 @@ import { GetSaleReceiptsService } from './queries/GetSaleReceipts.service'; SaleReceiptGLEntries, SaleReceiptGLEntriesSubscriber, SaleReceiptInventoryTransactionsSubscriber, - GetSaleReceiptsService + GetSaleReceiptsService, + SaleReceiptMailNotification ], }) export class SaleReceiptsModule {} diff --git a/packages/server-nest/src/modules/SaleReceipts/commands/SaleReceiptMailNotification.ts b/packages/server-nest/src/modules/SaleReceipts/commands/SaleReceiptMailNotification.ts index 9cbb00563..24d4dd7f4 100644 --- a/packages/server-nest/src/modules/SaleReceipts/commands/SaleReceiptMailNotification.ts +++ b/packages/server-nest/src/modules/SaleReceipts/commands/SaleReceiptMailNotification.ts @@ -1,220 +1,192 @@ -// import HasTenancyService from '@/services/Tenancy/TenancyService'; -// import { Inject, Service } from 'typedi'; -// import Mail from '@/lib/Mail'; -// import { GetSaleReceipt } from '../queries/GetSaleReceipt'; -// import { SaleReceiptsPdf } from '../queries/SaleReceiptsPdfService'; -// import { -// DEFAULT_RECEIPT_MAIL_CONTENT, -// DEFAULT_RECEIPT_MAIL_SUBJECT, -// } from '../constants'; -// import { -// ISaleReceiptMailPresend, -// SaleReceiptMailOpts, -// SaleReceiptMailOptsDTO, -// } from '@/interfaces'; -// import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification'; -// import { mergeAndValidateMailOptions } from '@/services/MailNotification/utils'; -// import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; -// import events from '@/subscribers/events'; -// import { transformReceiptToMailDataArgs } from '../utils'; +import { + DEFAULT_RECEIPT_MAIL_CONTENT, + DEFAULT_RECEIPT_MAIL_SUBJECT, +} from '../constants'; +import { mergeAndValidateMailOptions } from '@/modules/MailNotification/utils'; +import { transformReceiptToMailDataArgs } from '../utils'; +import { Injectable } from '@nestjs/common'; +import { GetSaleReceipt } from '../queries/GetSaleReceipt.service'; +import { SaleReceiptsPdfService } from '../queries/SaleReceiptsPdf.service'; +import { ContactMailNotification } from '@/modules/MailNotification/ContactMailNotification'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { events } from '@/common/events/events'; +import { + ISaleReceiptMailPresend, + SaleReceiptMailOpts, + SaleReceiptMailOptsDTO, +} from '../types/SaleReceipts.types'; +import { SaleReceipt } from '../models/SaleReceipt'; +import { MailTransporter } from '@/modules/Mail/MailTransporter.service'; +import { Mail } from '@/modules/Mail/Mail'; -// @Service() -// export class SaleReceiptMailNotification { -// @Inject() -// private tenancy: HasTenancyService; +@Injectable() +export class SaleReceiptMailNotification { + /** + * @param {GetSaleReceipt} getSaleReceiptService - Get sale receipt service. + * @param {SaleReceiptsPdfService} receiptPdfService - Sale receipt pdf service. + * @param {ContactMailNotification} contactMailNotification - Contact mail notification service. + * @param {EventEmitter2} eventEmitter - Event emitter. + * @param {MailTransporter} mailTransporter - Mail transporter service. + */ + constructor( + private readonly getSaleReceiptService: GetSaleReceipt, + private readonly receiptPdfService: SaleReceiptsPdfService, + private readonly contactMailNotification: ContactMailNotification, + private readonly eventEmitter: EventEmitter2, + private readonly mailTransporter: MailTransporter, + ) {} -// @Inject() -// private getSaleReceiptService: GetSaleReceipt; + /** + * Sends the receipt mail of the given sale receipt. + * @param {number} tenantId + * @param {number} saleReceiptId + * @param {SaleReceiptMailOptsDTO} messageDTO + */ + public async triggerMail( + saleReceiptId: number, + messageOptions: SaleReceiptMailOptsDTO, + ) { + const payload = { + saleReceiptId, + messageOpts: messageOptions, + }; + // await this.agenda.now('sale-receipt-mail-send', payload); -// @Inject() -// private receiptPdfService: SaleReceiptsPdf; + // Triggers the event `onSaleReceiptPreMailSend`. + await this.eventEmitter.emitAsync(events.saleReceipt.onPreMailSend, { + saleReceiptId, + messageOptions, + } as ISaleReceiptMailPresend); + } -// @Inject() -// private contactMailNotification: ContactMailNotification; + /** + * Retrieves the mail options of the given sale receipt. + * @param {number} saleReceiptId + * @returns {Promise} + */ + public async getMailOptions( + saleReceiptId: number, + ): Promise { + const saleReceipt = await SaleReceipt.query() + .findById(saleReceiptId) + .throwIfNotFound(); -// @Inject() -// private eventPublisher: EventPublisher; + const formatArgs = await this.textFormatterArgs(saleReceiptId); + const mailOptions = + await this.contactMailNotification.getDefaultMailOptions( + saleReceipt.customerId, + ); + return { + ...mailOptions, + message: DEFAULT_RECEIPT_MAIL_CONTENT, + subject: DEFAULT_RECEIPT_MAIL_SUBJECT, + attachReceipt: true, + formatArgs, + }; + } -// @Inject('agenda') -// private agenda: any; + /** + * Retrieves the formatted text of the given sale receipt. + * @param {number} tenantId - Tenant id. + * @param {number} receiptId - Sale receipt id. + * @param {string} text - The given text. + * @returns {Promise} + */ + public textFormatterArgs = async ( + receiptId: number, + ): Promise> => { + const receipt = await this.getSaleReceiptService.getSaleReceipt(receiptId); -// /** -// * Sends the receipt mail of the given sale receipt. -// * @param {number} tenantId -// * @param {number} saleReceiptId -// * @param {SaleReceiptMailOptsDTO} messageDTO -// */ -// public async triggerMail( -// tenantId: number, -// saleReceiptId: number, -// messageOptions: SaleReceiptMailOptsDTO -// ) { -// const payload = { -// tenantId, -// saleReceiptId, -// messageOpts: messageOptions, -// }; -// await this.agenda.now('sale-receipt-mail-send', payload); + return transformReceiptToMailDataArgs(receipt); + }; -// // Triggers the event `onSaleReceiptPreMailSend`. -// await this.eventPublisher.emitAsync(events.saleReceipt.onPreMailSend, { -// tenantId, -// saleReceiptId, -// messageOptions, -// } as ISaleReceiptMailPresend); -// } + /** + * Formats the mail options of the given sale receipt. + * @param {number} tenantId + * @param {number} receiptId + * @param {SaleReceiptMailOpts} mailOptions + * @returns {Promise} + */ + public async formatEstimateMailOptions( + receiptId: number, + mailOptions: SaleReceiptMailOpts, + ): Promise { + const formatterArgs = await this.textFormatterArgs(receiptId); + const formattedOptions = + (await this.contactMailNotification.formatMailOptions( + mailOptions, + formatterArgs, + )) as SaleReceiptMailOpts; + return formattedOptions; + } -// /** -// * Retrieves the mail options of the given sale receipt. -// * @param {number} tenantId -// * @param {number} saleReceiptId -// * @returns {Promise} -// */ -// public async getMailOptions( -// tenantId: number, -// saleReceiptId: number -// ): Promise { -// const { SaleReceipt } = this.tenancy.models(tenantId); + /** + * Retrieves the formatted mail options of the given sale receipt. + * @param {number} tenantId + * @param {number} saleReceiptId + * @param {SaleReceiptMailOptsDTO} messageOpts + * @returns {Promise} + */ + public getFormatMailOptions = async ( + saleReceiptId: number, + messageOpts: SaleReceiptMailOptsDTO, + ): Promise => { + const defaultMessageOptions = await this.getMailOptions(saleReceiptId); + // Merges message opts with default options. + const parsedMessageOpts = mergeAndValidateMailOptions( + defaultMessageOptions, + messageOpts, + ) as SaleReceiptMailOpts; -// const saleReceipt = await SaleReceipt.query() -// .findById(saleReceiptId) -// .throwIfNotFound(); + // Formats the message options. + return this.formatEstimateMailOptions(saleReceiptId, parsedMessageOpts); + }; -// const formatArgs = await this.textFormatterArgs(tenantId, saleReceiptId); + /** + * Triggers the mail notification of the given sale receipt. + * @param {number} saleReceiptId - Sale receipt id. + * @param {SaleReceiptMailOpts} messageDTO - message options. + * @returns {Promise} + */ + public async sendMail( + saleReceiptId: number, + messageOpts: SaleReceiptMailOptsDTO, + ) { + // Formats the message options. + const formattedMessageOptions = await this.getFormatMailOptions( + saleReceiptId, + messageOpts, + ); + const mail = new Mail() + .setSubject(formattedMessageOptions.subject) + .setTo(formattedMessageOptions.to) + .setCC(formattedMessageOptions.cc) + .setBCC(formattedMessageOptions.bcc) + .setContent(formattedMessageOptions.message); -// const mailOptions = -// await this.contactMailNotification.getDefaultMailOptions( -// tenantId, -// saleReceipt.customerId -// ); -// return { -// ...mailOptions, -// message: DEFAULT_RECEIPT_MAIL_CONTENT, -// subject: DEFAULT_RECEIPT_MAIL_SUBJECT, -// attachReceipt: true, -// formatArgs, -// }; -// } + // Attaches the receipt pdf document. + if (formattedMessageOptions.attachReceipt) { + // Retrieves document buffer of the receipt pdf document. + const [receiptPdfBuffer, filename] = + await this.receiptPdfService.saleReceiptPdf(saleReceiptId); -// /** -// * Retrieves the formatted text of the given sale receipt. -// * @param {number} tenantId - Tenant id. -// * @param {number} receiptId - Sale receipt id. -// * @param {string} text - The given text. -// * @returns {Promise} -// */ -// public textFormatterArgs = async ( -// tenantId: number, -// receiptId: number -// ): Promise> => { -// const receipt = await this.getSaleReceiptService.getSaleReceipt( -// tenantId, -// receiptId -// ); -// return transformReceiptToMailDataArgs(receipt); -// }; + mail.setAttachments([ + { filename: `${filename}.pdf`, content: receiptPdfBuffer }, + ]); + } + const eventPayload = { + saleReceiptId, + messageOptions: {}, + }; + await this.eventEmitter.emitAsync( + events.saleReceipt.onMailSend, + eventPayload, + ); + await this.mailTransporter.send(mail); -// /** -// * Formats the mail options of the given sale receipt. -// * @param {number} tenantId -// * @param {number} receiptId -// * @param {SaleReceiptMailOpts} mailOptions -// * @returns {Promise} -// */ -// public async formatEstimateMailOptions( -// tenantId: number, -// receiptId: number, -// mailOptions: SaleReceiptMailOpts -// ): Promise { -// const formatterArgs = await this.textFormatterArgs(tenantId, receiptId); -// const formattedOptions = -// (await this.contactMailNotification.formatMailOptions( -// tenantId, -// mailOptions, -// formatterArgs -// )) as SaleReceiptMailOpts; -// return formattedOptions; -// } - -// /** -// * Retrieves the formatted mail options of the given sale receipt. -// * @param {number} tenantId -// * @param {number} saleReceiptId -// * @param {SaleReceiptMailOptsDTO} messageOpts -// * @returns {Promise} -// */ -// public getFormatMailOptions = async ( -// tenantId: number, -// saleReceiptId: number, -// messageOpts: SaleReceiptMailOptsDTO -// ): Promise => { -// const defaultMessageOptions = await this.getMailOptions( -// tenantId, -// saleReceiptId -// ); -// // Merges message opts with default options. -// const parsedMessageOpts = mergeAndValidateMailOptions( -// defaultMessageOptions, -// messageOpts -// ) as SaleReceiptMailOpts; - -// // Formats the message options. -// return this.formatEstimateMailOptions( -// tenantId, -// saleReceiptId, -// parsedMessageOpts -// ); -// }; - -// /** -// * Triggers the mail notification of the given sale receipt. -// * @param {number} tenantId - Tenant id. -// * @param {number} saleReceiptId - Sale receipt id. -// * @param {SaleReceiptMailOpts} messageDTO - message options. -// * @returns {Promise} -// */ -// public async sendMail( -// tenantId: number, -// saleReceiptId: number, -// messageOpts: SaleReceiptMailOptsDTO -// ) { -// // Formats the message options. -// const formattedMessageOptions = await this.getFormatMailOptions( -// tenantId, -// saleReceiptId, -// messageOpts -// ); -// const mail = new Mail() -// .setSubject(formattedMessageOptions.subject) -// .setTo(formattedMessageOptions.to) -// .setCC(formattedMessageOptions.cc) -// .setBCC(formattedMessageOptions.bcc) -// .setContent(formattedMessageOptions.message); - -// // Attaches the receipt pdf document. -// if (formattedMessageOptions.attachReceipt) { -// // Retrieves document buffer of the receipt pdf document. -// const [receiptPdfBuffer, filename] = -// await this.receiptPdfService.saleReceiptPdf(tenantId, saleReceiptId); - -// mail.setAttachments([ -// { filename: `${filename}.pdf`, content: receiptPdfBuffer }, -// ]); -// } -// const eventPayload = { -// tenantId, -// saleReceiptId, -// messageOptions: {}, -// }; -// await this.eventPublisher.emitAsync( -// events.saleReceipt.onMailSend, -// eventPayload -// ); -// await mail.send(); - -// await this.eventPublisher.emitAsync( -// events.saleReceipt.onMailSent, -// eventPayload -// ); -// } -// } + await this.eventEmitter.emitAsync( + events.saleReceipt.onMailSent, + eventPayload, + ); + } +} diff --git a/packages/server-nest/src/modules/SaleReceipts/queries/GetSaleReceipts.service.ts b/packages/server-nest/src/modules/SaleReceipts/queries/GetSaleReceipts.service.ts index ee6014873..c13772bed 100644 --- a/packages/server-nest/src/modules/SaleReceipts/queries/GetSaleReceipts.service.ts +++ b/packages/server-nest/src/modules/SaleReceipts/queries/GetSaleReceipts.service.ts @@ -1,11 +1,11 @@ import * as R from 'ramda'; -import { SaleReceiptTransformer } from './SaleReceiptTransformer'; import { Inject, Injectable } from '@nestjs/common'; +import { SaleReceiptTransformer } from './SaleReceiptTransformer'; import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service'; import { DynamicListService } from '@/modules/DynamicListing/DynamicList.service'; +import { IFilterMeta, IPaginationMeta } from '@/interfaces/Model'; import { ISalesReceiptsFilter } from '../types/SaleReceipts.types'; import { SaleReceipt } from '../models/SaleReceipt'; -import { IPaginationMeta } from '@/interfaces/Model'; interface GetSaleReceiptsSettings { fetchEntriesGraph?: boolean; @@ -22,8 +22,7 @@ export class GetSaleReceiptsService { /** * Retrieve sales receipts paginated and filterable list. - * @param {number} tenantId - * @param {ISaleReceiptFilter} salesReceiptsFilter + * @param {ISalesReceiptsFilter} salesReceiptsFilter - Sales receipts filter. */ public async getSaleReceipts(filterDTO: ISalesReceiptsFilter): Promise<{ data: SaleReceipt[]; diff --git a/packages/server-nest/src/modules/SaleReceipts/types/SaleReceipts.types.ts b/packages/server-nest/src/modules/SaleReceipts/types/SaleReceipts.types.ts index 9130c6183..4bd6b1bcb 100644 --- a/packages/server-nest/src/modules/SaleReceipts/types/SaleReceipts.types.ts +++ b/packages/server-nest/src/modules/SaleReceipts/types/SaleReceipts.types.ts @@ -3,6 +3,8 @@ import { Knex } from 'knex'; // import { CommonMailOptions, CommonMailOptionsDTO } from '../SaleInvoices/types/Mailable'; import { AttachmentLinkDTO } from '../../Attachments/Attachments.types'; import { SaleReceipt } from '../models/SaleReceipt'; +import { CommonMailOptionsDTO } from '@/modules/MailNotification/MailNotification.types'; +import { CommonMailOptions } from '@/modules/MailNotification/MailNotification.types'; export interface ISalesReceiptsFilter { filterQuery?: (query: any) => void; @@ -92,19 +94,19 @@ export interface ISaleReceiptDeletingPayload { trx: Knex.Transaction; } -// export interface SaleReceiptMailOpts extends CommonMailOptions { -// attachReceipt: boolean; -// } +export interface SaleReceiptMailOpts extends CommonMailOptions { + attachReceipt: boolean; +} -// export interface SaleReceiptMailOptsDTO extends CommonMailOptionsDTO { -// attachReceipt?: boolean; -// } +export interface SaleReceiptMailOptsDTO extends CommonMailOptionsDTO { + attachReceipt?: boolean; +} -// export interface ISaleReceiptMailPresend { -// tenantId: number; -// saleReceiptId: number; -// messageOptions: SaleReceiptMailOptsDTO; -// } +export interface ISaleReceiptMailPresend { + tenantId: number; + saleReceiptId: number; + messageOptions: SaleReceiptMailOptsDTO; +} export interface ISaleReceiptBrandingTemplateAttributes { primaryColor: string; @@ -162,7 +164,6 @@ export interface ISaleReceiptBrandingTemplateAttributes { receiptDateLabel: string; } - export interface ISaleReceiptState { defaultTemplateId: number; -} \ No newline at end of file +} diff --git a/packages/server-nest/src/modules/VendorCredit/VendorCredits.controller.ts b/packages/server-nest/src/modules/VendorCredit/VendorCredits.controller.ts index d182c52cd..8a4d9f327 100644 --- a/packages/server-nest/src/modules/VendorCredit/VendorCredits.controller.ts +++ b/packages/server-nest/src/modules/VendorCredit/VendorCredits.controller.ts @@ -12,6 +12,7 @@ import { VendorCreditsApplicationService } from './VendorCreditsApplication.serv import { IVendorCreditCreateDTO, IVendorCreditEditDTO, + IVendorCreditsQueryDTO, } from './types/VendorCredit.types'; import { PublicRoute } from '../Auth/Jwt.guard'; @@ -33,7 +34,7 @@ export class VendorCreditsController { } @Get() - async getVendorCredits(@Query() filterDTO: IVendorCreditsFilter) { + async getVendorCredits(@Query() filterDTO: IVendorCreditsQueryDTO) { return this.vendorCreditsApplication.getVendorCredits(filterDTO); } diff --git a/packages/server-nest/src/modules/VendorCredit/VendorCreditsApplication.service.ts b/packages/server-nest/src/modules/VendorCredit/VendorCreditsApplication.service.ts index 8d1aa2317..f1c63bacb 100644 --- a/packages/server-nest/src/modules/VendorCredit/VendorCreditsApplication.service.ts +++ b/packages/server-nest/src/modules/VendorCredit/VendorCreditsApplication.service.ts @@ -3,7 +3,7 @@ import { CreateVendorCreditService } from './commands/CreateVendorCredit.service import { DeleteVendorCreditService } from './commands/DeleteVendorCredit.service'; import { EditVendorCreditService } from './commands/EditVendorCredit.service'; import { GetVendorCreditService } from './queries/GetVendorCredit.service'; -import { IVendorCreditEditDTO } from './types/VendorCredit.types'; +import { IVendorCreditEditDTO, IVendorCreditsQueryDTO } from './types/VendorCredit.types'; import { IVendorCreditCreateDTO } from './types/VendorCredit.types'; import { Injectable } from '@nestjs/common'; import { OpenVendorCreditService } from './commands/OpenVendorCredit.service'; diff --git a/packages/server-nest/src/modules/VendorCredit/queries/GetVendorCredits.service.ts b/packages/server-nest/src/modules/VendorCredit/queries/GetVendorCredits.service.ts index 8f776f47d..4d1b7350e 100644 --- a/packages/server-nest/src/modules/VendorCredit/queries/GetVendorCredits.service.ts +++ b/packages/server-nest/src/modules/VendorCredit/queries/GetVendorCredits.service.ts @@ -4,6 +4,7 @@ import { VendorCreditTransformer } from './VendorCreditTransformer'; import { DynamicListService } from '@/modules/DynamicListing/DynamicList.service'; import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service'; import { VendorCredit } from '../models/VendorCredit'; +import { IVendorCreditsQueryDTO } from '../types/VendorCredit.types'; @Injectable() export class GetVendorCreditsService { diff --git a/packages/server-nest/src/modules/VendorCredit/subscribers/VendorCreditInventoryTransactionsSusbcriber.ts b/packages/server-nest/src/modules/VendorCredit/subscribers/VendorCreditInventoryTransactionsSusbcriber.ts index 5fc2d4634..a7619b625 100644 --- a/packages/server-nest/src/modules/VendorCredit/subscribers/VendorCreditInventoryTransactionsSusbcriber.ts +++ b/packages/server-nest/src/modules/VendorCredit/subscribers/VendorCreditInventoryTransactionsSusbcriber.ts @@ -38,7 +38,7 @@ export default class VendorCreditInventoryTransactionsSubscriber { */ @OnEvent(events.vendorCredit.onEdited) public async rewriteInventroyTransactionsOnceEdited({ - vendorCreditId, + oldVendorCredit, vendorCredit, trx, }: IVendorCreditEditedPayload) { @@ -46,7 +46,7 @@ export default class VendorCreditInventoryTransactionsSubscriber { if (!vendorCredit.openedAt) return null; await this.inventoryTransactions.editInventoryTransactions( - vendorCreditId, + oldVendorCredit.id, vendorCredit, trx, ); diff --git a/packages/server-nest/src/modules/VendorCredit/types/VendorCredit.types.ts b/packages/server-nest/src/modules/VendorCredit/types/VendorCredit.types.ts index 2ebfac076..95b6bc3a0 100644 --- a/packages/server-nest/src/modules/VendorCredit/types/VendorCredit.types.ts +++ b/packages/server-nest/src/modules/VendorCredit/types/VendorCredit.types.ts @@ -4,6 +4,7 @@ import { AttachmentLinkDTO } from '@/modules/Attachments/Attachments.types'; import { IRefundVendorCreditDTO } from '@/modules/VendorCreditsRefund/types/VendorCreditRefund.types'; import { IItemEntryDTO } from '@/modules/TransactionItemEntry/ItemEntry.types'; import { DiscountType } from '@/common/types/Discount'; +import { IDynamicListFilter } from '@/modules/DynamicListing/DynamicFilter/DynamicFilter.types'; export enum VendorCreditAction { Create = 'Create', @@ -15,12 +16,12 @@ export enum VendorCreditAction { export interface IVendorCreditEntryDTO extends IItemEntryDTO {} -// export interface IVendorCreditsQueryDTO extends IDynamicListFilter { -// page: number; -// pageSize: number; -// searchKeyword?: string; -// filterQuery?: (q: any) => void; -// } +export interface IVendorCreditsQueryDTO extends IDynamicListFilter { + page: number; + pageSize: number; + searchKeyword?: string; + filterQuery?: (q: any) => void; +} export interface IVendorCreditDTO { vendorId: number; diff --git a/packages/server-nest/src/modules/Vendors/Vendors.controller.ts b/packages/server-nest/src/modules/Vendors/Vendors.controller.ts index 045add1f0..324f2773a 100644 --- a/packages/server-nest/src/modules/Vendors/Vendors.controller.ts +++ b/packages/server-nest/src/modules/Vendors/Vendors.controller.ts @@ -13,6 +13,7 @@ import { IVendorEditDTO, IVendorNewDTO, IVendorOpeningBalanceEditDTO, + IVendorsFilter, } from './types/Vendors.types'; import { PublicRoute } from '../Auth/Jwt.guard'; diff --git a/packages/server/src/lib/Mail/index.ts b/packages/server/src/lib/Mail/index.ts index 9381a4e07..e0fbdae31 100644 --- a/packages/server/src/lib/Mail/index.ts +++ b/packages/server/src/lib/Mail/index.ts @@ -1,6 +1,5 @@ -import fs from 'fs'; +import * as fs from 'fs'; import Mustache from 'mustache'; -import { Container } from 'typedi'; import path from 'path'; import { IMailAttachment } from '@/interfaces'; @@ -40,23 +39,6 @@ export default class Mail { return this.view ? Mail.render(this.view, this.data) : this.content; } - /** - * Sends the given mail to the target address. - */ - public send() { - return new Promise((resolve, reject) => { - const Mail = Container.get('mail'); - - Mail.sendMail(this.mailOptions, (error) => { - if (error) { - reject(error); - return; - } - resolve(true); - }); - }); - } - /** * Set send mail to address. * @param {string} to - diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 21bd339fa..146271f44 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -532,6 +532,9 @@ importers: '@supercharge/promise-pool': specifier: ^3.2.0 version: 3.2.0 + '@types/nodemailer': + specifier: ^6.4.17 + version: 6.4.17 '@types/passport-local': specifier: ^1.0.38 version: 1.0.38 @@ -577,6 +580,9 @@ importers: fp-ts: specifier: ^2.16.9 version: 2.16.9 + is-my-json-valid: + specifier: ^2.20.5 + version: 2.20.6 js-money: specifier: ^0.6.3 version: 0.6.3 @@ -604,6 +610,9 @@ importers: nestjs-i18n: specifier: ^10.4.9 version: 10.5.0(@nestjs/common@10.4.7)(@nestjs/core@10.4.7)(class-validator@0.14.1)(rxjs@7.8.1) + nodemailer: + specifier: ^6.3.0 + version: 6.9.13 object-hash: specifier: ^2.0.3 version: 2.2.0 @@ -12329,6 +12338,12 @@ packages: resolution: {integrity: sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==} dev: false + /@types/nodemailer@6.4.17: + resolution: {integrity: sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==} + dependencies: + '@types/node': 20.5.1 + dev: false + /@types/normalize-package-data@2.4.4: resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==}