diff --git a/packages/server/src/api/controllers/Sales/PaymentReceives.ts b/packages/server/src/api/controllers/Sales/PaymentReceives.ts index 7cfa93a00..8604682ba 100644 --- a/packages/server/src/api/controllers/Sales/PaymentReceives.ts +++ b/packages/server/src/api/controllers/Sales/PaymentReceives.ts @@ -1,9 +1,11 @@ import { Inject, Service } from 'typedi'; import { Router, Request, Response, NextFunction } from 'express'; -import { check, param, query, ValidationChain } from 'express-validator'; +import { body, check, param, query, ValidationChain } from 'express-validator'; import { AbilitySubject, IPaymentReceiveDTO, + IPaymentReceiveMailOpts, + // IPaymentReceiveMailOpts, PaymentReceiveAction, } from '@/interfaces'; import BaseController from '@/api/controllers/BaseController'; @@ -117,6 +119,19 @@ export default class PaymentReceivesController extends BaseController { asyncMiddleware(this.deletePaymentReceive.bind(this)), this.handleServiceErrors ); + router.post( + '/:id/mail', + [ + ...this.paymentReceiveValidation, + body('subject').isString().optional(), + body('from').isString().optional(), + body('to').isString().optional(), + body('body').isString().optional(), + body('attach_invoice').optional().isBoolean().toBoolean(), + ], + this.sendPaymentReceiveByMail.bind(this), + this.handleServiceErrors + ); return router; } @@ -416,27 +431,26 @@ export default class PaymentReceivesController extends BaseController { const { id: paymentReceiveId } = req.params; try { - const paymentReceive = - await this.paymentReceiveApplication.getPaymentReceive( - tenantId, - paymentReceiveId - ); - const ACCEPT_TYPE = { APPLICATION_PDF: 'application/pdf', APPLICATION_JSON: 'application/json', }; res.format({ - [ACCEPT_TYPE.APPLICATION_JSON]: () => { + [ACCEPT_TYPE.APPLICATION_JSON]: async () => { + const paymentReceive = + await this.paymentReceiveApplication.getPaymentReceive( + tenantId, + paymentReceiveId + ); return res.status(200).send({ - payment_receive: this.transfromToResponse(paymentReceive), + payment_receive: paymentReceive, }); }, [ACCEPT_TYPE.APPLICATION_PDF]: async () => { const pdfContent = await this.paymentReceiveApplication.getPaymentReceivePdf( tenantId, - paymentReceive + paymentReceiveId ); res.set({ 'Content-Type': 'application/pdf', @@ -507,6 +521,38 @@ export default class PaymentReceivesController extends BaseController { } }; + /** + * Sends mail invoice of the given sale invoice. + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next + * @returns + */ + public sendPaymentReceiveByMail = async ( + req: Request, + res: Response, + next: NextFunction + ) => { + const { tenantId } = req; + const { id: paymentReceiveId } = req.params; + const paymentMailDTO: IPaymentReceiveMailOpts = this.matchedBodyData(req, { + includeOptionals: false, + }); + try { + await this.paymentReceiveApplication.notifyPaymentByMail( + tenantId, + paymentReceiveId, + paymentMailDTO + ); + return res.status(200).send({ + code: 200, + message: 'The payment notification has been sent successfully.', + }); + } catch (error) { + next(error); + } + }; + /** * Handles service errors. * @param error @@ -514,7 +560,7 @@ export default class PaymentReceivesController extends BaseController { * @param res * @param next */ - handleServiceErrors( + private handleServiceErrors( error: Error, req: Request, res: Response, diff --git a/packages/server/src/api/controllers/Sales/SalesEstimates.ts b/packages/server/src/api/controllers/Sales/SalesEstimates.ts index 9f3cf3719..c896ecbfd 100644 --- a/packages/server/src/api/controllers/Sales/SalesEstimates.ts +++ b/packages/server/src/api/controllers/Sales/SalesEstimates.ts @@ -1,10 +1,11 @@ import { Router, Request, Response, NextFunction } from 'express'; -import { check, param, query } from 'express-validator'; +import { body, check, param, query } from 'express-validator'; import { Inject, Service } from 'typedi'; import { AbilitySubject, ISaleEstimateDTO, SaleEstimateAction, + SaleEstimateMailOptions, } from '@/interfaces'; import BaseController from '@/api/controllers/BaseController'; import asyncMiddleware from '@/api/middleware/asyncMiddleware'; @@ -121,6 +122,20 @@ export default class SalesEstimatesController extends BaseController { this.handleServiceErrors, this.dynamicListService.handlerErrorsToResponse ); + router.post( + '/:id/mail', + [ + ...this.validateSpecificEstimateSchema, + body('subject').isString().optional(), + body('from').isString().optional(), + body('to').isString().optional(), + body('body').isString().optional(), + body('attach_invoice').optional().isBoolean().toBoolean(), + ], + this.validationResult, + asyncMiddleware(this.sendSaleEstimateMail.bind(this)), + this.handleServiceErrors + ); return router; } @@ -362,22 +377,22 @@ export default class SalesEstimatesController extends BaseController { const { tenantId } = req; try { - const estimate = await this.saleEstimatesApplication.getSaleEstimate( - tenantId, - estimateId - ); // Response formatter. res.format({ // JSON content type. - [ACCEPT_TYPE.APPLICATION_JSON]: () => { - return res.status(200).send(this.transfromToResponse({ estimate })); + [ACCEPT_TYPE.APPLICATION_JSON]: async () => { + const estimate = await this.saleEstimatesApplication.getSaleEstimate( + tenantId, + estimateId + ); + return res.status(200).send({ estimate }); }, // PDF content type. [ACCEPT_TYPE.APPLICATION_PDF]: async () => { const pdfContent = await this.saleEstimatesApplication.getSaleEstimatePdf( tenantId, - estimate + estimateId ); res.set({ 'Content-Type': 'application/pdf', @@ -478,6 +493,38 @@ export default class SalesEstimatesController extends BaseController { } }; + /** + * Send the sale estimate mail. + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next + */ + private sendSaleEstimateMail = async ( + req: Request, + res: Response, + next: NextFunction + ) => { + const { tenantId } = req; + const { id: invoiceId } = req.params; + const saleEstimateDTO: SaleEstimateMailOptions = this.matchedBodyData(req, { + includeOptionals: false, + }); + + try { + await this.saleEstimatesApplication.sendSaleEstimateMail( + tenantId, + invoiceId, + saleEstimateDTO + ); + return res.status(200).send({ + code: 200, + message: 'The sale estimate mail has been sent successfully.', + }); + } catch (error) { + next(error); + } + }; + /** * Handles service errors. * @param {Error} error diff --git a/packages/server/src/api/controllers/Sales/SalesReceipts.ts b/packages/server/src/api/controllers/Sales/SalesReceipts.ts index 3eabcf84e..89d7edef2 100644 --- a/packages/server/src/api/controllers/Sales/SalesReceipts.ts +++ b/packages/server/src/api/controllers/Sales/SalesReceipts.ts @@ -1,9 +1,9 @@ import { Router, Request, Response, NextFunction } from 'express'; -import { check, param, query } from 'express-validator'; +import { body, check, param, query } from 'express-validator'; import { Inject, Service } from 'typedi'; import asyncMiddleware from '@/api/middleware/asyncMiddleware'; import BaseController from '../BaseController'; -import { ISaleReceiptDTO } from '@/interfaces/SaleReceipt'; +import { ISaleReceiptDTO, SaleReceiptMailOpts } from '@/interfaces/SaleReceipt'; import { ServiceError } from '@/exceptions'; import DynamicListingService from '@/services/DynamicListing/DynamicListService'; import CheckPolicies from '@/api/middleware/CheckPolicies'; @@ -46,6 +46,20 @@ export default class SalesReceiptsController extends BaseController { this.saleReceiptSmsDetails, this.handleServiceErrors ); + router.post( + '/:id/mail', + [ + ...this.specificReceiptValidationSchema, + body('subject').isString().optional(), + body('from').isString().optional(), + body('to').isString().optional(), + body('body').isString().optional(), + body('attach_invoice').optional().isBoolean().toBoolean(), + ], + this.validationResult, + asyncMiddleware(this.sendSaleReceiptMail.bind(this)), + this.handleServiceErrors + ); router.post( '/:id', CheckPolicies(SaleReceiptAction.Edit, AbilitySubject.SaleReceipt), @@ -314,26 +328,24 @@ export default class SalesReceiptsController extends BaseController { * @param {Response} res * @param {NextFunction} next */ - async getSaleReceipt(req: Request, res: Response, next: NextFunction) { + public async getSaleReceipt(req: Request, res: Response, next: NextFunction) { const { id: saleReceiptId } = req.params; const { tenantId } = req; try { - const saleReceipt = await this.saleReceiptsApplication.getSaleReceipt( - tenantId, - saleReceiptId - ); res.format({ - 'application/json': () => { - return res - .status(200) - .send(this.transfromToResponse({ saleReceipt })); + 'application/json': async () => { + const saleReceipt = await this.saleReceiptsApplication.getSaleReceipt( + tenantId, + saleReceiptId + ); + return res.status(200).send({ saleReceipt }); }, 'application/pdf': async () => { const pdfContent = await this.saleReceiptsApplication.getSaleReceiptPdf( tenantId, - saleReceipt + saleReceiptId ); res.set({ 'Content-Type': 'application/pdf', @@ -405,6 +417,39 @@ export default class SalesReceiptsController extends BaseController { } }; + /** + * Sends mail notification of the given sale receipt. + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next + */ + public sendSaleReceiptMail = async ( + req: Request, + res: Response, + next: NextFunction + ) => { + const { tenantId } = req; + const { id: receiptId } = req.params; + const receiptMailDTO: SaleReceiptMailOpts = this.matchedBodyData(req, { + includeOptionals: false, + }); + + try { + await this.saleReceiptsApplication.sendSaleReceiptMail( + tenantId, + receiptId, + receiptMailDTO + ); + return res.status(200).send({ + code: 200, + message: + 'The sale receipt notification via sms has been sent successfully.', + }); + } catch (error) { + next(error); + } + }; + /** * Handles service errors. * @param {Error} error diff --git a/packages/server/src/interfaces/PaymentReceive.ts b/packages/server/src/interfaces/PaymentReceive.ts index 6f8d8552a..c919182ae 100644 --- a/packages/server/src/interfaces/PaymentReceive.ts +++ b/packages/server/src/interfaces/PaymentReceive.ts @@ -165,3 +165,7 @@ export type IPaymentReceiveGLCommonEntry = Pick< | 'createdAt' | 'branchId' >; + +export interface IPaymentReceiveMailOpts { + +} \ No newline at end of file diff --git a/packages/server/src/interfaces/SaleEstimate.ts b/packages/server/src/interfaces/SaleEstimate.ts index f2a820e98..3a503e1fd 100644 --- a/packages/server/src/interfaces/SaleEstimate.ts +++ b/packages/server/src/interfaces/SaleEstimate.ts @@ -124,3 +124,11 @@ export interface ISaleEstimateApprovedEvent { saleEstimate: ISaleEstimate; trx: Knex.Transaction; } + +export interface SaleEstimateMailOptions { + to: string; + from: string; + subject: string; + body: string; + attachInvoice?: boolean; +} \ No newline at end of file diff --git a/packages/server/src/interfaces/SaleReceipt.ts b/packages/server/src/interfaces/SaleReceipt.ts index 4d319ec4f..102513f7e 100644 --- a/packages/server/src/interfaces/SaleReceipt.ts +++ b/packages/server/src/interfaces/SaleReceipt.ts @@ -134,3 +134,7 @@ export interface ISaleReceiptDeletingPayload { oldSaleReceipt: ISaleReceipt; trx: Knex.Transaction; } + +export interface SaleReceiptMailOpts { + +} \ No newline at end of file diff --git a/packages/server/src/loaders/jobs.ts b/packages/server/src/loaders/jobs.ts index 9beb7a2bc..74c7d6b6e 100644 --- a/packages/server/src/loaders/jobs.ts +++ b/packages/server/src/loaders/jobs.ts @@ -7,6 +7,9 @@ import OrganizationSetupJob from 'jobs/OrganizationSetup'; import OrganizationUpgrade from 'jobs/OrganizationUpgrade'; import { SendSaleInvoiceMailJob } from '@/services/Sales/Invoices/SendSaleInvoiceMailJob'; import { SendSaleInvoiceReminderMailJob } from '@/services/Sales/Invoices/SendSaleInvoiceMailReminderJob'; +import { SendSaleEstimateMailJob } from '@/services/Sales/Estimates/SendSaleEstimateMailJob'; +import { SaleReceiptMailNotificationJob } from '@/services/Sales/Receipts/SaleReceiptMailNotificationJob'; +import { PaymentReceiveMailNotificationJob } from '@/services/Sales/PaymentReceives/PaymentReceiveMailNotificationJob'; export default ({ agenda }: { agenda: Agenda }) => { new ResetPasswordMailJob(agenda); @@ -17,6 +20,9 @@ export default ({ agenda }: { agenda: Agenda }) => { new OrganizationUpgrade(agenda); new SendSaleInvoiceMailJob(agenda); new SendSaleInvoiceReminderMailJob(agenda); + new SendSaleEstimateMailJob(agenda); + new SaleReceiptMailNotificationJob(agenda); + new PaymentReceiveMailNotificationJob(agenda); agenda.start(); }; diff --git a/packages/server/src/services/Sales/Estimates/SendSaleEstimateMail.ts b/packages/server/src/services/Sales/Estimates/SendSaleEstimateMail.ts index bc558d424..b30a7a013 100644 --- a/packages/server/src/services/Sales/Estimates/SendSaleEstimateMail.ts +++ b/packages/server/src/services/Sales/Estimates/SendSaleEstimateMail.ts @@ -29,12 +29,12 @@ export class SendSaleEstimateMail { * Triggers the reminder mail of the given sale estimate. * @param {number} tenantId * @param {number} saleEstimateId - * @param messageOptions + * @param {SaleEstimateMailOptions} messageOptions */ public async triggerMail( tenantId: number, saleEstimateId: number, - messageOptions: any + messageOptions: SaleEstimateMailOptions ) { const payload = { tenantId, @@ -114,7 +114,6 @@ export class SendSaleEstimateMail { ...messageOptions, }; const formatter = R.curry(this.formatText)(tenantId, saleEstimateId); - const toEmail = parsedMessageOpts.to; const subject = await formatter(parsedMessageOpts.subject); const body = await formatter(parsedMessageOpts.body); const attachments = []; @@ -131,7 +130,7 @@ export class SendSaleEstimateMail { } await new Mail() .setSubject(subject) - .setTo(toEmail) + .setTo(parsedMessageOpts.to) .setContent(body) .setAttachments(attachments) .send(); diff --git a/packages/server/src/services/Sales/Invoices/SaleInvoicesApplication.ts b/packages/server/src/services/Sales/Invoices/SaleInvoicesApplication.ts index 38c2c721f..4e00bc813 100644 --- a/packages/server/src/services/Sales/Invoices/SaleInvoicesApplication.ts +++ b/packages/server/src/services/Sales/Invoices/SaleInvoicesApplication.ts @@ -329,7 +329,7 @@ export class SaleInvoiceApplication { * @param {number} tenantId * @param {number} saleInvoiceId * @param {SendInvoiceMailDTO} messageDTO - * @returns + * @returns {Promise} */ public sendSaleInvoiceMail( tenantId: number,