fix: mail state

This commit is contained in:
Ahmed Bouhuolia
2025-06-09 15:37:20 +02:00
parent 4366bf478a
commit 90d6bea9b9
12 changed files with 285 additions and 64 deletions

View File

@@ -1,8 +1,5 @@
import { Transformer } from '@/modules/Transformer/Transformer'; import { Transformer } from '@/modules/Transformer/Transformer';
import { Bill } from '../models/Bill'; import { Bill } from '../models/Bill';
import { AttachmentTransformer } from '@/modules/Attachments/Attachment.transformer';
import { ItemEntryTransformer } from '@/modules/TransactionItemEntry/ItemEntry.transformer';
import { SaleInvoiceTaxEntryTransformer } from '@/modules/SaleInvoices/queries/SaleInvoiceTaxEntry.transformer';
export class BillTransformer extends Transformer { export class BillTransformer extends Transformer {
/** /**
@@ -23,6 +20,9 @@ export class BillTransformer extends Transformer {
'subtotalLocalFormatted', 'subtotalLocalFormatted',
'subtotalExcludingTaxFormatted', 'subtotalExcludingTaxFormatted',
'taxAmountWithheldLocalFormatted', 'taxAmountWithheldLocalFormatted',
'discountAmountFormatted',
'discountPercentageFormatted',
'adjustmentFormatted',
'totalFormatted', 'totalFormatted',
'totalLocalFormatted', 'totalLocalFormatted',
'taxes', 'taxes',
@@ -183,6 +183,37 @@ export class BillTransformer extends Transformer {
}); });
}; };
/**
* Retrieves the formatted discount amount.
* @param {IBill} bill
* @returns {string}
*/
protected discountAmountFormatted = (bill: Bill): string => {
return this.formatNumber(bill.discountAmount, {
currencyCode: bill.currencyCode,
});
};
/**
* Retrieves the formatted discount percentage.
* @param {IBill} bill
* @returns {string}
*/
protected discountPercentageFormatted = (bill: Bill): string => {
return bill.discountPercentage ? `${bill.discountPercentage}%` : '';
};
/**
* Retrieves the formatted adjustment amount.
* @param {IBill} bill
* @returns {string}
*/
protected adjustmentFormatted = (bill: Bill): string => {
return this.formatNumber(bill.adjustment, {
currencyCode: bill.currencyCode,
});
};
/** /**
* Retrieve the taxes lines of bill. * Retrieve the taxes lines of bill.
* @param {Bill} bill * @param {Bill} bill

View File

@@ -17,6 +17,7 @@ import {
EditPaymentReceivedDto, EditPaymentReceivedDto,
} from './dtos/PaymentReceived.dto'; } from './dtos/PaymentReceived.dto';
import { PaymentsReceivedPagesService } from './queries/PaymentsReceivedPages.service'; import { PaymentsReceivedPagesService } from './queries/PaymentsReceivedPages.service';
import { GetPaymentReceivedMailState } from './queries/GetPaymentReceivedMailState.service';
@Injectable() @Injectable()
export class PaymentReceivesApplication { export class PaymentReceivesApplication {
@@ -28,6 +29,7 @@ export class PaymentReceivesApplication {
private getPaymentReceivedService: GetPaymentReceivedService, private getPaymentReceivedService: GetPaymentReceivedService,
private getPaymentReceiveInvoicesService: GetPaymentReceivedInvoices, private getPaymentReceiveInvoicesService: GetPaymentReceivedInvoices,
private sendPaymentReceiveMailNotification: SendPaymentReceiveMailNotification, private sendPaymentReceiveMailNotification: SendPaymentReceiveMailNotification,
private getPaymentReceivedMailStateService: GetPaymentReceivedMailState,
private getPaymentReceivePdfService: GetPaymentReceivedPdfService, private getPaymentReceivePdfService: GetPaymentReceivedPdfService,
private getPaymentReceivedStateService: GetPaymentReceivedStateService, private getPaymentReceivedStateService: GetPaymentReceivedStateService,
private paymentsReceivedPagesService: PaymentsReceivedPagesService, private paymentsReceivedPagesService: PaymentsReceivedPagesService,
@@ -125,7 +127,7 @@ export class PaymentReceivesApplication {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
public getPaymentMailOptions(paymentReceiveId: number) { public getPaymentMailOptions(paymentReceiveId: number) {
return this.sendPaymentReceiveMailNotification.getMailOptions( return this.getPaymentReceivedMailStateService.getMailOptions(
paymentReceiveId, paymentReceiveId,
); );
} }

View File

@@ -37,6 +37,8 @@ import { SEND_PAYMENT_RECEIVED_MAIL_QUEUE } from './constants';
import { PaymentsReceivedExportable } from './commands/PaymentsReceivedExportable'; import { PaymentsReceivedExportable } from './commands/PaymentsReceivedExportable';
import { PaymentsReceivedImportable } from './commands/PaymentsReceivedImportable'; import { PaymentsReceivedImportable } from './commands/PaymentsReceivedImportable';
import { PaymentsReceivedPagesService } from './queries/PaymentsReceivedPages.service'; import { PaymentsReceivedPagesService } from './queries/PaymentsReceivedPages.service';
import { GetPaymentReceivedMailTemplate } from './queries/GetPaymentReceivedMailTemplate.service';
import { GetPaymentReceivedMailState } from './queries/GetPaymentReceivedMailState.service';
@Module({ @Module({
controllers: [PaymentReceivesController], controllers: [PaymentReceivesController],
@@ -64,7 +66,9 @@ import { PaymentsReceivedPagesService } from './queries/PaymentsReceivedPages.se
SendPaymentReceivedMailProcessor, SendPaymentReceivedMailProcessor,
PaymentsReceivedExportable, PaymentsReceivedExportable,
PaymentsReceivedImportable, PaymentsReceivedImportable,
PaymentsReceivedPagesService PaymentsReceivedPagesService,
GetPaymentReceivedMailTemplate,
GetPaymentReceivedMailState
], ],
exports: [ exports: [
PaymentReceivesApplication, PaymentReceivesApplication,

View File

@@ -25,6 +25,7 @@ import { Mail } from '@/modules/Mail/Mail';
import { MailTransporter } from '@/modules/Mail/MailTransporter.service'; import { MailTransporter } from '@/modules/Mail/MailTransporter.service';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
import { GetPaymentReceivedMailTemplate } from '../queries/GetPaymentReceivedMailTemplate.service';
@Injectable() @Injectable()
export class SendPaymentReceiveMailNotification { export class SendPaymentReceiveMailNotification {
@@ -34,6 +35,7 @@ export class SendPaymentReceiveMailNotification {
private readonly eventEmitter: EventEmitter2, private readonly eventEmitter: EventEmitter2,
private readonly mailTransport: MailTransporter, private readonly mailTransport: MailTransporter,
private readonly tenancyContext: TenancyContext, private readonly tenancyContext: TenancyContext,
private readonly paymentMailTemplate: GetPaymentReceivedMailTemplate,
@InjectQueue(SEND_PAYMENT_RECEIVED_MAIL_QUEUE) @InjectQueue(SEND_PAYMENT_RECEIVED_MAIL_QUEUE)
private readonly sendPaymentMailQueue: Queue, private readonly sendPaymentMailQueue: Queue,
@@ -86,23 +88,27 @@ export class SendPaymentReceiveMailNotification {
*/ */
public getMailOptions = async ( public getMailOptions = async (
paymentId: number, paymentId: number,
defaultSubject: string = DEFAULT_PAYMENT_MAIL_SUBJECT,
defaultContent: string = DEFAULT_PAYMENT_MAIL_CONTENT,
): Promise<PaymentReceiveMailOpts> => { ): Promise<PaymentReceiveMailOpts> => {
const paymentReceive = await this.paymentReceiveModel() const paymentReceived = await this.paymentReceiveModel()
.query() .query()
.findById(paymentId) .findById(paymentId)
.throwIfNotFound(); .throwIfNotFound();
const formatArgs = await this.textFormatter(paymentId); const formatArgs = await this.textFormatter(paymentReceived.id);
// Retrieves the default mail options.
const mailOptions = const mailOptions =
await this.contactMailNotification.getDefaultMailOptions( await this.contactMailNotification.getDefaultMailOptions(
paymentReceive.customerId, paymentReceived.customerId,
); );
return { return {
...mailOptions, ...mailOptions,
subject: DEFAULT_PAYMENT_MAIL_SUBJECT, message: defaultContent,
message: DEFAULT_PAYMENT_MAIL_CONTENT, subject: defaultSubject,
...formatArgs, attachPdf: true,
formatArgs,
}; };
}; };
@@ -115,7 +121,40 @@ export class SendPaymentReceiveMailNotification {
invoiceId: number, invoiceId: number,
): Promise<Record<string, string>> => { ): Promise<Record<string, string>> => {
const payment = await this.getPaymentService.getPaymentReceive(invoiceId); const payment = await this.getPaymentService.getPaymentReceive(invoiceId);
return transformPaymentReceivedToMailDataArgs(payment); const commonArgs = await this.contactMailNotification.getCommonFormatArgs();
const paymentArgs = transformPaymentReceivedToMailDataArgs(payment);
return {
...commonArgs,
...paymentArgs,
};
};
/**
* Formats the mail options of the given payment receive.
* @param {number} paymentReceiveId
* @param {PaymentReceiveMailOpts} mailOptions
* @returns {Promise<PaymentReceiveMailOpts>}
*/
public formattedMailOptions = async (
paymentReceiveId: number,
mailOptions: PaymentReceiveMailOpts,
): Promise<PaymentReceiveMailOpts> => {
const formatterArgs = await this.textFormatter(paymentReceiveId);
const formattedOptions =
await this.contactMailNotification.formatMailOptions(
mailOptions,
formatterArgs,
);
// Retrieves the mail template.
const message = await this.paymentMailTemplate.getMailTemplate(
paymentReceiveId,
{
message: formattedOptions.message,
preview: formattedOptions.message,
},
);
return { ...formattedOptions, message };
}; };
/** /**

View File

@@ -36,6 +36,7 @@ export class GetPaymentReceivedMailState {
const mailOptions = const mailOptions =
await this.paymentReceivedMail.getMailOptions(paymentId); await this.paymentReceivedMail.getMailOptions(paymentId);
const transformed = await this.transformer.transform( const transformed = await this.transformer.transform(
paymentReceive, paymentReceive,
new GetPaymentReceivedMailStateTransformer(), new GetPaymentReceivedMailStateTransformer(),

View File

@@ -42,8 +42,7 @@ export class GetPaymentReceivedMailTemplate {
/** /**
* Retrieves the mail template html content. * Retrieves the mail template html content.
* @param {number} tenantId * @param {number} paymentReceivedId
* @param {number} paymentReceivedId
* @param {Partial<PaymentReceivedEmailTemplateProps>} overrideAttributes * @param {Partial<PaymentReceivedEmailTemplateProps>} overrideAttributes
* @returns * @returns
*/ */

View File

@@ -1,5 +1,6 @@
import * as moment from 'moment'; import * as moment from 'moment';
import { Model } from 'objection'; import { Model } from 'objection';
import { defaultTo } from 'lodash';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel'; import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
import { ExportableModel } from '@/modules/Export/decorators/ExportableModel.decorator'; import { ExportableModel } from '@/modules/Export/decorators/ExportableModel.decorator';
import { ImportableModel } from '@/modules/Import/decorators/Import.decorator'; import { ImportableModel } from '@/modules/Import/decorators/Import.decorator';
@@ -8,6 +9,7 @@ import { SaleEstimateMeta } from './SaleEstimate.meta';
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry'; import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
import { Document } from '@/modules/ChromiumlyTenancy/models/Document'; import { Document } from '@/modules/ChromiumlyTenancy/models/Document';
import { Customer } from '@/modules/Customers/models/Customer'; import { Customer } from '@/modules/Customers/models/Customer';
import { DiscountType } from '@/common/types/Discount';
@ExportableModel() @ExportableModel()
@ImportableModel() @ImportableModel()
@@ -42,6 +44,11 @@ export class SaleEstimate extends TenantBaseModel {
branchId?: number; branchId?: number;
warehouseId?: number; warehouseId?: number;
discount: number;
discountType: DiscountType;
adjustment: number;
public entries!: ItemEntry[]; public entries!: ItemEntry[];
public attachments!: Document[]; public attachments!: Document[];
public customer!: Customer; public customer!: Customer;
@@ -71,9 +78,69 @@ export class SaleEstimate extends TenantBaseModel {
'isConvertedToInvoice', 'isConvertedToInvoice',
'isApproved', 'isApproved',
'isRejected', 'isRejected',
'discountAmount',
'discountPercentage',
'total',
'totalLocal',
'subtotal',
'subtotalLocal',
]; ];
} }
/**
* Estimate subtotal.
* @returns {number}
*/
get subtotal() {
return this.amount;;
}
/**
* Estimate subtotal in local currency.
* @returns {number}
*/
get subtotalLocal() {
return this.localAmount;
}
/**
* Discount amount.
* @returns {number}
*/
get discountAmount() {
return this.discountType === DiscountType.Amount
? this.discount
: this.subtotal * (this.discount / 100);
}
/**
* Discount percentage.
* @returns {number | null}
*/
get discountPercentage(): number | null {
return this.discountType === DiscountType.Percentage
? this.discount
: null;
}
/**
* Estimate total.
* @returns {number}
*/
get total() {
const adjustmentAmount = defaultTo(this.adjustment, 0);
return this.subtotal - this.discountAmount - adjustmentAmount;
}
/**
* Estimate total in local currency.
* @returns {number}
*/
get totalLocal() {
return this.total * this.exchangeRate;
}
/** /**
* Estimate amount in local currency. * Estimate amount in local currency.
* @returns {number} * @returns {number}

View File

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

View File

@@ -20,6 +20,16 @@ export class GetSaleEstimateMailStateTransformer extends SaleEstimateTransfromer
'subtotal', 'subtotal',
'subtotalFormatted', 'subtotalFormatted',
'discountAmount',
'discountAmountFormatted',
'discountPercentage',
'discountPercentageFormatted',
'discountLabel',
'adjustment',
'adjustmentFormatted',
'adjustmentLabel',
'estimateNumber', 'estimateNumber',
'entries', 'entries',
@@ -98,15 +108,14 @@ export class GetSaleEstimateMailStateTransformer extends SaleEstimateTransfromer
} }
/** /**
* Retrieves the formatted total of the estimate. * Retrieves the discount label of the estimate.
* @param estimate * @param estimate
* @returns {string} * @returns {string}
*/ */
protected totalFormatted(estimate) { protected discountLabel(estimate) {
return this.formatMoney(estimate.amount, { return estimate.discountType === 'percentage'
currencyCode: estimate.currencyCode, ? `Discount [${estimate.discountPercentageFormatted}]`
money: true, : 'Discount';
});
} }
/** /**
@@ -115,7 +124,7 @@ export class GetSaleEstimateMailStateTransformer extends SaleEstimateTransfromer
* @returns {string} * @returns {string}
*/ */
protected subtotalFormatted = (estimate) => { protected subtotalFormatted = (estimate) => {
return this.formatNumber(estimate.amount, { money: false }); return this.formattedSubtotal(estimate);
}; };
/** /**

View File

@@ -17,6 +17,13 @@ export class SaleEstimateTransfromer extends Transformer {
'formattedDeliveredAtDate', 'formattedDeliveredAtDate',
'formattedApprovedAtDate', 'formattedApprovedAtDate',
'formattedRejectedAtDate', 'formattedRejectedAtDate',
'discountAmountFormatted',
'discountPercentageFormatted',
'adjustmentFormatted',
'totalFormatted',
'totalLocalFormatted',
'formattedCreatedAt', 'formattedCreatedAt',
'entries', 'entries',
'attachments', 'attachments',
@@ -97,6 +104,65 @@ export class SaleEstimateTransfromer extends Transformer {
return this.formatNumber(estimate.amount, { money: false }); return this.formatNumber(estimate.amount, { money: false });
}; };
/**
* Retrieves formatted discount amount.
* @param {SaleEstimate} estimate
* @returns {string}
*/
protected discountAmountFormatted = (estimate: SaleEstimate): string => {
return this.formatNumber(estimate.discountAmount, {
currencyCode: estimate.currencyCode,
excerptZero: true,
});
};
/**
* Retrieves formatted discount percentage.
* @param estimate
* @returns {string}
*/
protected discountPercentageFormatted = (estimate: SaleEstimate): string => {
return estimate.discountPercentage
? `${estimate.discountPercentage}%`
: '';
};
/**
* Retrieves formatted adjustment amount.
* @param estimate
* @returns {string}
*/
protected adjustmentFormatted = (estimate: SaleEstimate): string => {
return this.formatMoney(estimate.adjustment, {
currencyCode: estimate.currencyCode,
excerptZero: true,
});
};
/**
* Retrieves the formatted estimate total.
* @returns {string}
*/
protected totalFormatted = (estimate: SaleEstimate): string => {
return this.formatMoney(estimate.total, {
currencyCode: estimate.currencyCode,
});
};
/**
* Retrieves the formatted estimate total in local currency.
* @param estimate
* @returns {string}
*/
protected totalLocalFormatted = (estimate: SaleEstimate): string => {
return this.formatMoney(estimate.totalLocal, {
currencyCode: estimate.currencyCode,
});
};
/** /**
* Retrieves the entries of the sale estimate. * Retrieves the entries of the sale estimate.
* @param {ISaleEstimate} estimate * @param {ISaleEstimate} estimate

View File

@@ -116,32 +116,6 @@ export class SaleReceiptApplication {
return this.getSaleReceiptPdfService.saleReceiptPdf(saleReceiptId); return this.getSaleReceiptPdfService.saleReceiptPdf(saleReceiptId);
} }
/**
* Notify receipt customer by SMS of the given sale receipt.
* @param {number} tenantId
* @param {number} saleReceiptId
* @returns
*/
// public saleReceiptNotifyBySms(tenantId: number, saleReceiptId: number) {
// return this.saleReceiptNotifyBySmsService.notifyBySms(
// tenantId,
// saleReceiptId,
// );
// }
/**
* Retrieves sms details of the given sale receipt.
* @param {number} tenantId
* @param {number} saleReceiptId
* @returns
*/
// public getSaleReceiptSmsDetails(tenantId: number, saleReceiptId: number) {
// return this.saleReceiptNotifyBySmsService.smsDetails(
// tenantId,
// saleReceiptId,
// );
// }
/** /**
* Sends the receipt mail of the given sale receipt. * Sends the receipt mail of the given sale receipt.
* @param {number} tenantId * @param {number} tenantId
@@ -159,17 +133,6 @@ export class SaleReceiptApplication {
); );
} }
/**
* Retrieves the default mail options of the given sale receipt.
* @param {number} saleReceiptId - Sale receipt identifier.
* @returns {Promise<SaleReceiptMailOpts>}
*/
public getSaleReceiptMail(
saleReceiptId: number,
): Promise<SaleReceiptMailOpts> {
return this.saleReceiptNotifyByMailService.getMailOptions(saleReceiptId);
}
/** /**
* Retrieves the current state of the sale receipt. * Retrieves the current state of the sale receipt.
* @returns {Promise<ISaleReceiptState>} - A promise resolving to the sale receipt state. * @returns {Promise<ISaleReceiptState>} - A promise resolving to the sale receipt state.
@@ -191,7 +154,7 @@ export class SaleReceiptApplication {
* Retrieves the mail state of the given sale receipt. * Retrieves the mail state of the given sale receipt.
* @param {number} saleReceiptId * @param {number} saleReceiptId
*/ */
public getSaleReceiptMailState( public getSaleReceiptMail(
saleReceiptId: number, saleReceiptId: number,
): Promise<ISaleReceiptState> { ): Promise<ISaleReceiptState> {
return this.getSaleReceiptMailStateService.getMailState(saleReceiptId); return this.getSaleReceiptMailStateService.getMailState(saleReceiptId);

View File

@@ -19,6 +19,11 @@ export class SaleReceiptTransformer extends Transformer {
'subtotalLocalFormatted', 'subtotalLocalFormatted',
'totalFormatted', 'totalFormatted',
'totalLocalFormatted', 'totalLocalFormatted',
'discountAmountFormatted',
'discountPercentageFormatted',
'adjustmentFormatted',
'entries', 'entries',
'attachments', 'attachments',
]; ];
@@ -107,7 +112,6 @@ export class SaleReceiptTransformer extends Transformer {
/** /**
* Retrieves formatted discount amount. * Retrieves formatted discount amount.
* @param receipt
* @returns {string} * @returns {string}
*/ */
protected discountAmountFormatted = (receipt: SaleReceipt): string => { protected discountAmountFormatted = (receipt: SaleReceipt): string => {
@@ -118,7 +122,6 @@ export class SaleReceiptTransformer extends Transformer {
/** /**
* Retrieves formatted discount percentage. * Retrieves formatted discount percentage.
* @param receipt
* @returns {string} * @returns {string}
*/ */
protected discountPercentageFormatted = (receipt: SaleReceipt): string => { protected discountPercentageFormatted = (receipt: SaleReceipt): string => {
@@ -127,7 +130,6 @@ export class SaleReceiptTransformer extends Transformer {
/** /**
* Retrieves formatted adjustment amount. * Retrieves formatted adjustment amount.
* @param receipt
* @returns {string} * @returns {string}
*/ */
protected adjustmentFormatted = (receipt: SaleReceipt): string => { protected adjustmentFormatted = (receipt: SaleReceipt): string => {