diff --git a/fonts/Roboto-Black.ttf b/fonts/Roboto-Black.ttf new file mode 100644 index 0000000..58fa175 Binary files /dev/null and b/fonts/Roboto-Black.ttf differ diff --git a/fonts/Roboto-BlackItalic.ttf b/fonts/Roboto-BlackItalic.ttf new file mode 100644 index 0000000..0a4dfd0 Binary files /dev/null and b/fonts/Roboto-BlackItalic.ttf differ diff --git a/fonts/Roboto-Bold.ttf b/fonts/Roboto-Bold.ttf new file mode 100644 index 0000000..e64db79 Binary files /dev/null and b/fonts/Roboto-Bold.ttf differ diff --git a/fonts/Roboto-BoldItalic.ttf b/fonts/Roboto-BoldItalic.ttf new file mode 100644 index 0000000..5e39ae9 Binary files /dev/null and b/fonts/Roboto-BoldItalic.ttf differ diff --git a/fonts/Roboto-Italic.ttf b/fonts/Roboto-Italic.ttf new file mode 100644 index 0000000..65498ee Binary files /dev/null and b/fonts/Roboto-Italic.ttf differ diff --git a/fonts/Roboto-Light.ttf b/fonts/Roboto-Light.ttf new file mode 100644 index 0000000..a7e0284 Binary files /dev/null and b/fonts/Roboto-Light.ttf differ diff --git a/fonts/Roboto-LightItalic.ttf b/fonts/Roboto-LightItalic.ttf new file mode 100644 index 0000000..867b76d Binary files /dev/null and b/fonts/Roboto-LightItalic.ttf differ diff --git a/fonts/Roboto-Medium.ttf b/fonts/Roboto-Medium.ttf new file mode 100644 index 0000000..0707e15 Binary files /dev/null and b/fonts/Roboto-Medium.ttf differ diff --git a/fonts/Roboto-MediumItalic.ttf b/fonts/Roboto-MediumItalic.ttf new file mode 100644 index 0000000..4e3bf0d Binary files /dev/null and b/fonts/Roboto-MediumItalic.ttf differ diff --git a/fonts/Roboto-Regular.ttf b/fonts/Roboto-Regular.ttf new file mode 100644 index 0000000..2d116d9 Binary files /dev/null and b/fonts/Roboto-Regular.ttf differ diff --git a/fonts/Roboto-Thin.ttf b/fonts/Roboto-Thin.ttf new file mode 100644 index 0000000..ab68508 Binary files /dev/null and b/fonts/Roboto-Thin.ttf differ diff --git a/fonts/Roboto-ThinItalic.ttf b/fonts/Roboto-ThinItalic.ttf new file mode 100644 index 0000000..b2c3933 Binary files /dev/null and b/fonts/Roboto-ThinItalic.ttf differ diff --git a/image/logo.jpeg b/image/logo.jpeg new file mode 100644 index 0000000..d46fdff Binary files /dev/null and b/image/logo.jpeg differ diff --git a/src/app.module.ts b/src/app.module.ts index a73ced2..920256f 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -69,8 +69,8 @@ import { NewsModel } from './modules/web-information/news/data/models/news.model import { BannerModule } from './modules/web-information/banner/banner.module'; import { BannerModel } from './modules/web-information/banner/data/models/banner.model'; import { MailModule } from './modules/configuration/mail/mail.module'; -import { ExportModule } from './modules/configuration/export/export.module'; import { PosLogModel } from './modules/configuration/log/data/models/pos-log.model'; +import { ExportModule } from './modules/configuration/export/export.module'; @Module({ imports: [ @@ -124,7 +124,7 @@ import { PosLogModel } from './modules/configuration/log/data/models/pos-log.mod CqrsModule, CouchModule, CronModule, - // ExportModule, + ExportModule, GoogleCalendarModule, LogModule, MailModule, diff --git a/src/database/migrations/1722693550579-add-column-to-transactions-table.ts b/src/database/migrations/1722693550579-add-column-to-transactions-table.ts new file mode 100644 index 0000000..a80690a --- /dev/null +++ b/src/database/migrations/1722693550579-add-column-to-transactions-table.ts @@ -0,0 +1,19 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddColumnToTransactionsTable1722693550579 + implements MigrationInterface +{ + name = 'AddColumnToTransactionsTable1722693550579'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "transactions" ADD "booking_date_before" date`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "transactions" DROP COLUMN "booking_date_before"`, + ); + } +} diff --git a/src/modules/configuration/constant/infrastructure/constant.controller.ts b/src/modules/configuration/constant/infrastructure/constant.controller.ts index fd0e04b..06a851b 100644 --- a/src/modules/configuration/constant/infrastructure/constant.controller.ts +++ b/src/modules/configuration/constant/infrastructure/constant.controller.ts @@ -10,6 +10,7 @@ import { RefundType, } from 'src/modules/transaction/refund/constants'; import { GateType } from 'src/modules/web-information/gate/constants'; +import { InvoiceType } from '../../export/constants'; @ApiTags('configuration - constant') @Controller('v1/constant') @@ -64,4 +65,9 @@ export class ConstantController { async gateType(): Promise { return Object.values(GateType); } + + @Get('invoice-type') + async invoiceType(): Promise { + return Object.values(InvoiceType); + } } diff --git a/src/modules/configuration/export/constants.ts b/src/modules/configuration/export/constants.ts new file mode 100644 index 0000000..41fb902 --- /dev/null +++ b/src/modules/configuration/export/constants.ts @@ -0,0 +1,10 @@ +export enum InvoiceType { + BOOKING_INVOICE = 'this is your invoice', + PAYMENT_CONFIRMATION = 'payment confirmation', + INVOICE_EXPIRED = 'invoice has expired', + REFUND_REQUEST = 'your refund request', + REFUND_CONFIRMATION = 'refund confirmation', + BOOKING_DATE_CHANGE = 'booking date change', +} + +export const PhoneNumber = '088'; diff --git a/src/modules/configuration/export/domain/helpers/pdf-make.helper.ts b/src/modules/configuration/export/domain/helpers/pdf-make.helper.ts deleted file mode 100644 index 964db1b..0000000 --- a/src/modules/configuration/export/domain/helpers/pdf-make.helper.ts +++ /dev/null @@ -1,44 +0,0 @@ -import * as fs from 'fs'; -import { InvoiceOldTempalte } from '../templates/invoice-old.template'; -const PdfPrinter = require('pdfmake'); - -export function PdfMaker(transaction) { - var fonts = { - Courier: { - normal: 'Courier', - bold: 'Courier-Bold', - italics: 'Courier-Oblique', - bolditalics: 'Courier-BoldOblique', - }, - Helvetica: { - normal: 'Helvetica', - bold: 'Helvetica-Bold', - italics: 'Helvetica-Oblique', - bolditalics: 'Helvetica-BoldOblique', - }, - Times: { - normal: 'Times-Roman', - bold: 'Times-Bold', - italics: 'Times-Italic', - bolditalics: 'Times-BoldItalic', - }, - Symbol: { - normal: 'Symbol', - }, - ZapfDingbats: { - normal: 'ZapfDingbats', - }, - }; - - var printer = new PdfPrinter(fonts); - - var docDefinition = InvoiceOldTempalte(transaction); - - var pdfDoc = printer.createPdfKitDocument(docDefinition); - pdfDoc.pipe( - fs.createWriteStream( - `uploads/invoice/${transaction.invoice_code.split('/').join('-')}.pdf`, - ), - ); - pdfDoc.end(); -} diff --git a/src/modules/configuration/export/domain/managers/pdf-make.manager.ts b/src/modules/configuration/export/domain/managers/pdf-make.manager.ts new file mode 100644 index 0000000..b33065f --- /dev/null +++ b/src/modules/configuration/export/domain/managers/pdf-make.manager.ts @@ -0,0 +1,55 @@ +import { Injectable } from '@nestjs/common'; +import { BaseCustomManager } from 'src/core/modules/domain/usecase/managers/base-custom.manager'; +import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity'; +import { EventTopics } from 'src/core/strings/constants/interface.constants'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model'; +import { GeneratePdf } from '../templates/helpers/generate-pdf.helper'; + +@Injectable() +export class PdfMakeManager extends BaseCustomManager { + get entityTarget(): any { + return TransactionModel; + } + + get eventTopics(): EventTopics[] { + return []; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async process(): Promise { + try { + const transaction = await this.dataService.getOneByOptions({ + where: { + id: this.data.id, + }, + relations: ['items'], + }); + const banks = await this.dataServiceFirstOpt.getManyByOptions({ + where: { + status: STATUS.ACTIVE, + }, + }); + + this.result = GeneratePdf(transaction, this.data.invoice_type, banks); + } catch (error) { + console.log(error, 'generate pdf'); + } + return; + } + + async afterProcess(): Promise { + return; + } + + getResult() { + return this.result; + } +} diff --git a/src/modules/configuration/export/domain/templates/helpers/generate-pdf.helper.ts b/src/modules/configuration/export/domain/templates/helpers/generate-pdf.helper.ts new file mode 100644 index 0000000..aab5da3 --- /dev/null +++ b/src/modules/configuration/export/domain/templates/helpers/generate-pdf.helper.ts @@ -0,0 +1,43 @@ +import { InvoiceTempalte } from '../invoice.template'; +const PdfPrinter = require('pdfmake'); +const { PassThrough } = require('stream'); + +export async function GeneratePdf(transaction, invoiceType, banks) { + var fonts = { + Roboto: { + normal: './fonts/Roboto-Regular.ttf', + bold: './fonts/Roboto-Medium.ttf', + italics: './fonts/Roboto-Italic.ttf', + bolditalics: './fonts/Roboto-MediumItalic.ttf', + }, + }; + + const printer = new PdfPrinter(fonts); + + const docDefinition = InvoiceTempalte(transaction, invoiceType, banks); + + const createPdfBuffer = (docDefinition) => { + return new Promise((resolve, reject) => { + const pdfDoc = printer.createPdfKitDocument(docDefinition); + const chunks = []; + const stream = new PassThrough(); + + pdfDoc.pipe(stream); + pdfDoc.end(); + + stream.on('data', (chunk) => { + chunks.push(chunk); + }); + + stream.on('end', () => { + const pdfBuffer = Buffer.concat(chunks); + resolve(pdfBuffer); + }); + + stream.on('error', reject); + }); + }; + + const pdfBuffer = await createPdfBuffer(docDefinition); + return pdfBuffer; +} diff --git a/src/modules/configuration/export/domain/templates/helpers/invoice-mapping.helper.ts b/src/modules/configuration/export/domain/templates/helpers/invoice-mapping.helper.ts index d008070..904f3c4 100644 --- a/src/modules/configuration/export/domain/templates/helpers/invoice-mapping.helper.ts +++ b/src/modules/configuration/export/domain/templates/helpers/invoice-mapping.helper.ts @@ -1,6 +1,8 @@ import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity'; +import { InvoiceType, PhoneNumber } from '../../../constants'; +import { PaymentMethodEntity } from 'src/modules/transaction/payment-method/domain/entities/payment-method.entity'; -export function mappingHeader() { +export function mappingHeader(transaction: TransactionEntity, invoiceType) { return [ { stack: [ @@ -34,17 +36,17 @@ export function mappingHeader() { ], }, { - text: 'BOOKING DATE CHANGE', + text: invoiceType.toUpperCase(), fontSize: 30, alignment: 'center', }, { columns: [ { - text: 'INV-20808/032', + text: transaction.invoice_code, }, { - text: '10 Juli 2024', + text: new Date(transaction.booking_date).toDateString(), alignment: 'right', }, ], @@ -83,8 +85,30 @@ export function mappingFooter() { ]; } -export function mappingPrice(transaction: TransactionEntity) { +export function mappingPrice( + transaction: TransactionEntity, + invoiceType: InvoiceType, +) { const result = []; + const totalData = [ + InvoiceType.REFUND_CONFIRMATION, + InvoiceType.REFUND_REQUEST, + ].includes(invoiceType) + ? transaction['refund'].refund_total + : Number(transaction.payment_total).toLocaleString('id-ID', { + style: 'currency', + currency: 'IDR', + }); + const subTotalData = [ + InvoiceType.REFUND_CONFIRMATION, + InvoiceType.REFUND_REQUEST, + ].includes(invoiceType) + ? transaction['refund'].refund_total + : Number(transaction.payment_sub_total).toLocaleString('id-ID', { + style: 'currency', + currency: 'IDR', + }); + const sub_total = [ { text: 'SUBTOTAL', @@ -93,10 +117,7 @@ export function mappingPrice(transaction: TransactionEntity) { margin: [0, 5, 0, 5], }, { - text: Number(transaction.payment_sub_total).toLocaleString('id-ID', { - style: 'currency', - currency: 'IDR', - }), + text: subTotalData, margin: [0, 5, 0, 5], }, ]; @@ -133,10 +154,7 @@ export function mappingPrice(transaction: TransactionEntity) { margin: [0, 5, 0, 5], }, { - text: Number(transaction.payment_total).toLocaleString('id-ID', { - style: 'currency', - currency: 'IDR', - }), + text: totalData, border: [false, false, false, true], margin: [0, 5, 0, 5], }, @@ -146,7 +164,7 @@ export function mappingPrice(transaction: TransactionEntity) { return result; } -export function mappingItem(items) { +export function mappingItem(transaction, invoiceType: InvoiceType) { const header = [ { text: 'ITEM', @@ -182,62 +200,244 @@ export function mappingItem(items) { ]; const result = []; - items.forEach((item) => { - const dataRow = [ - { - text: item.item_name, - margin: [0, 5, 0, 5], - alignment: 'left', - }, - { - text: item.qty, - margin: [0, 5, 0, 5], - alignment: 'center', - }, - { - text: Number(item.item_price).toLocaleString('id-ID', { - style: 'currency', - currency: 'IDR', - }), - margin: [0, 5, 0, 5], - alignment: 'right', - }, - { - text: Number(item.total_price).toLocaleString('id-ID', { - style: 'currency', - currency: 'IDR', - }), - alignment: 'right', - margin: [0, 5, 0, 5], - }, - ]; - result.push(dataRow); - }); + + if ( + [InvoiceType.REFUND_CONFIRMATION, InvoiceType.REFUND_REQUEST].includes( + invoiceType, + ) + ) { + transaction.refund?.refund_items + ?.filter((item) => Number(item.qty_refund) > 0) + .forEach((item) => { + const dataRow = [ + { + text: item.transaction_item.item_name, + margin: [0, 5, 0, 5], + alignment: 'left', + }, + { + text: item.qty_refund, + margin: [0, 5, 0, 5], + alignment: 'center', + }, + { + text: Number(item.transaction_item.total_price).toLocaleString( + 'id-ID', + { + style: 'currency', + currency: 'IDR', + }, + ), + margin: [0, 5, 0, 5], + alignment: 'right', + }, + { + text: Number(item.refund_total).toLocaleString('id-ID', { + style: 'currency', + currency: 'IDR', + }), + alignment: 'right', + margin: [0, 5, 0, 5], + }, + ]; + result.push(dataRow); + }); + } else { + transaction.items.forEach((item) => { + const dataRow = [ + { + text: item.item_name, + margin: [0, 5, 0, 5], + alignment: 'left', + }, + { + text: item.qty, + margin: [0, 5, 0, 5], + alignment: 'center', + }, + { + text: Number(item.item_price).toLocaleString('id-ID', { + style: 'currency', + currency: 'IDR', + }), + margin: [0, 5, 0, 5], + alignment: 'right', + }, + { + text: Number(item.total_price).toLocaleString('id-ID', { + style: 'currency', + currency: 'IDR', + }), + alignment: 'right', + margin: [0, 5, 0, 5], + }, + ]; + result.push(dataRow); + }); + } const body = [header, ...result]; return body; } -export function mappingBody() { +export function mappingBody( + transaction: TransactionEntity, + invoiceType: InvoiceType, +) { // booking date change information - return [ - "Great news! We've successfully updated your booking date as per your request. \n We're exited to accommodate your new plans and ensure everything goes smoothly \n\n", - 'Here are your updated booking details:', - { - text: '\n\n Original Booking Date: 20 Agustus 2024 \n New Booking Date: 20 Agustus 2025 \n\n', - bold: true, - }, - "Here's a quick recap of your order :", - ]; + if (invoiceType == InvoiceType.BOOKING_DATE_CHANGE) { + return [ + "Great news! We've successfully updated your booking date as per your request. \n We're exited to accommodate your new plans and ensure everything goes smoothly \n\n", + 'Here are your updated booking details:', + { + text: `\n\n Original Booking Date: ${new Date( + transaction['booking_date_before'], + ).toDateString()} \n New Booking Date: ${new Date( + transaction.booking_date, + ).toDateString()} \n\n`, + bold: true, + }, + "Here's a quick recap of your order :", + ]; + } + + // booking invoice + else if (invoiceType == InvoiceType.BOOKING_INVOICE) { + return [ + "Thank you for choosing us! We're absolutely thrilled and can't wait to embark on this exiting day with you. See you soon for fun times ahead!", + ]; + } else if (invoiceType == InvoiceType.PAYMENT_CONFIRMATION) { + // booking payment confirmation + return [ + 'We are exited to inform you that your payment has been successfully received! \n', + 'Attached to this email, you will find your confirmation receipt. \n', + 'Please keep this safe as you will need to show it at the entrance upon your arrival. \n', + "It's your golden ticket to all the fun and excitement awaiting you \n\n", + "Here's a quick recap: \n", + ]; + } + + // expired information invoice + // else if (invoiceType == InvoiceType.INVOICE_EXPIRED) { + + // return [ + // "We hope this message finds you well!", + // "Uh-oh! it looks like your invoice, dated 15 Juli, has officially expired as of 15 Juli. But no worries, we can fix this together \n", + // "To keep the goof times rolling, our friendly support team is just a call away at \n", + // "0564645 \n\n", + // "Here are the detail of the expired invoice: " + + // ] + // } + + // refund information + else if (invoiceType == InvoiceType.REFUND_REQUEST) { + return [ + "We'ew trully sorry for any inconvenience that led to this request. \n", + "We've received your refund request for: \n", + ]; + } + + // refund confirmation + else if (invoiceType == InvoiceType.REFUND_CONFIRMATION) { + return [ + 'Good news! \n', + "We've successfully processed your refund for: \n", + ]; + } } -export function mappingBodyBottom() { - // booking date change information - return [ - "For your convenience, we've attached a new confirmation receipt reflecting these changes \n", - 'Please be sure to bring this updated receipt with you on the new date \n\n', - 'If you have any questions or need further assistance, our friendly support team is just a call away at \n\n', - "Thank you and we can't wait to see you and make sure you hove an amazing time!", - ]; +export function mappingBodyBottom( + transaction: TransactionEntity, + invoiceType: InvoiceType, + banks: PaymentMethodEntity[], +) { + if (invoiceType == InvoiceType.BOOKING_DATE_CHANGE) { + // booking date change information + return [ + "For your convenience, we've attached a new confirmation receipt reflecting these changes \n", + 'Please be sure to bring this updated receipt with you on the new date \n\n', + 'If you have any questions or need further assistance, our friendly support team is just a call away at \n\n', + PhoneNumber, + "\nThank you and we can't wait to see you and make sure you hove an amazing time!", + ]; + } else if (invoiceType == InvoiceType.BOOKING_INVOICE) { + // booking invoice + return [ + 'Just a friendly reminder that your invoice will expire on 24 Agustus 2024 \n', + 'To keep things running smoothly, please ensure your payment is completed before this data. \n', + '\nFor youe convenience, here is a list of our account details \n\n', + { + text: [ + banks.forEach((bank) => { + return { + text: `${bank.issuer_name} ${bank.account_number} a/n ${bank.account_name} \n`, + bold: true, + }; + }), + ], + }, + "\n Once you've made the payment, please kindly email or send the proof of payment so we can proceed with your booking promptly\n", + 'If you have any questions or need assistance, feel free to reach out to our support team at\n', + PhoneNumber, + ]; + } + + // booking payment confirmation + else if (invoiceType == InvoiceType.PAYMENT_CONFIRMATION) { + return [ + 'If you have any questions or need assistance, feel free to reach out to our support team at\n', + PhoneNumber, + "\nDon't forget to bring a smile and your confirmation receipt (attached) for a smooth entry. \n", + "We can't wait to see you and ensure you have an amazing time with us!", + ]; + } + + // expired information invoice + else if (invoiceType == InvoiceType.INVOICE_EXPIRED) { + return []; + } + + // refund information + else if (invoiceType == InvoiceType.REFUND_REQUEST) { + return [ + "Your satisfaction is important to us, and we're commited to resolving this as quickly as possible.\n", + 'Our team is already on it and will process your refund request promptly. \n', + "We'll keep you updated and notify you once the refund has been processed. \n", + "If you have any questions or need futher assistance, don't hestitate to reach out us at \n", + PhoneNumber, + '\n\n', + 'Thank you for your patience and understanding \n', + 'We appreciate your feedback and here to make things right!', + ]; + } + + // refund confirmation + else if (invoiceType == InvoiceType.REFUND_CONFIRMATION) { + return [ + 'Here are the details of your refund: \n\n', + { + text: `Transaction Number: ${transaction.invoice_code} \n`, + }, + { + text: `Refund Processed: ${transaction['refund'].refund_date}\n`, + }, + { + text: `Bank Account: ${transaction['refund'].bank_name} \n`, + }, + { + text: `Account Number: ${transaction['refund'].bank_account_number} \n`, + }, + { + text: `Account Name: ${transaction['refund'].bank_account_name} \n`, + }, + "\n We hope this helps make things righ, and we're he to assist if you need anything else. \n", + 'You should see the refund reflected in your account within 3 Business days \n\n', + 'Thank you for your patience and understanding\n', + 'If you have any questions or need assistance, feel free to reach out to our support team at \n', + PhoneNumber, + "\nWe're alyways here to help!", + ]; + } } diff --git a/src/modules/configuration/export/domain/templates/invoice-old.template.ts b/src/modules/configuration/export/domain/templates/invoice-old.template.ts deleted file mode 100644 index b568b25..0000000 --- a/src/modules/configuration/export/domain/templates/invoice-old.template.ts +++ /dev/null @@ -1,426 +0,0 @@ -export function InvoiceOldTempalte(transaction) { - return { - content: [ - { - columns: [ - [ - { - text: 'Invoice', - color: '#333333', - width: '*', - fontSize: 28, - bold: true, - alignment: 'left', - margin: [0, 0, 0, 15], - }, - { - stack: mappingHeader(transaction), - }, - ], - [ - { - text: 'Customer Detail', - color: '#aaaaab', - bold: true, - fontSize: 14, - alignment: 'right', - margin: [0, 20, 0, 5], - }, - { - stack: [ - { - columns: [ - { - text: 'Name', - color: '#aaaaab', - bold: true, - width: '*', - fontSize: 12, - alignment: 'right', - }, - { - text: transaction.customer_name ?? 'Customer', - bold: true, - color: '#333333', - fontSize: 12, - alignment: 'right', - width: 100, - }, - ], - }, - { - columns: [ - { - text: 'Email', - color: '#aaaaab', - bold: true, - width: '*', - fontSize: 12, - alignment: 'right', - }, - { - text: transaction.customer_email ?? '-', - bold: true, - color: '#333333', - fontSize: 12, - alignment: 'right', - width: 100, - }, - ], - }, - { - columns: [ - { - text: 'Phone', - color: '#aaaaab', - bold: true, - width: '*', - fontSize: 12, - alignment: 'right', - }, - { - text: transaction.customer_phone ?? '-', - bold: true, - color: '#333333', - fontSize: 12, - alignment: 'right', - width: 100, - }, - ], - }, - ], - }, - ], - ], - }, - '\n\n', - { - layout: { - defaultBorder: false, - hLineWidth: function (i, node) { - return 1; - }, - vLineWidth: function (i, node) { - return 1; - }, - hLineColor: function (i, node) { - if (i === 1 || i === 0) { - return '#bfdde8'; - } - return '#eaeaea'; - }, - vLineColor: function (i, node) { - return '#eaeaea'; - }, - hLineStyle: function (i, node) { - // if (i === 0 || i === node.table.body.length) { - return null; - //} - }, - // vLineStyle: function (i, node) { return {dash: { length: 10, space: 4 }}; }, - paddingLeft: function (i, node) { - return 10; - }, - paddingRight: function (i, node) { - return 10; - }, - paddingTop: function (i, node) { - return 2; - }, - paddingBottom: function (i, node) { - return 2; - }, - fillColor: function (rowIndex, node, columnIndex) { - return '#fff'; - }, - }, - table: { - headerRows: 1, - widths: ['*', 'auto', 'auto', 100], - body: mappingItem(transaction.items), - }, - }, - '\n', - { - layout: { - defaultBorder: false, - hLineWidth: function (i, node) { - return null; - }, - vLineWidth: function (i, node) { - return 1; - }, - hLineColor: function (i, node) { - return i === 0 ? '#000000' : '#eaeaea'; - }, - vLineColor: function (i, node) { - return '#eaeaea'; - }, - hLineStyle: function (i, node) { - // if (i === 0 || i === node.table.body.length) { - return null; - //} - }, - // vLineStyle: function (i, node) { return {dash: { length: 10, space: 4 }}; }, - paddingLeft: function (i, node) { - return 10; - }, - paddingRight: function (i, node) { - return 10; - }, - paddingTop: function (i, node) { - return 3; - }, - paddingBottom: function (i, node) { - return 3; - }, - fillColor: function (rowIndex, node, columnIndex) { - return '#fff'; - }, - }, - table: { - headerRows: 1, - widths: ['*', 100], - body: mappingPrice(transaction), - }, - }, - '\n\n', - { - text: 'NOTES', - style: 'notesTitle', - }, - { - text: 'Some notes goes here \n Notes second line', - style: 'notesText', - }, - ], - styles: { - notesTitle: { - fontSize: 10, - bold: true, - margin: [0, 50, 0, 3], - }, - notesText: { - fontSize: 10, - }, - }, - defaultStyle: { - columnGap: 20, - font: 'Times', - }, - }; -} - -function mappingHeader(transaction) { - const result = [ - { - columns: [ - { - text: 'Receipt No.', - color: '#aaaaab', - bold: true, - width: '*', - fontSize: 12, - alignment: 'left', - }, - { - text: transaction.invoice_code, - bold: true, - color: '#333333', - fontSize: 12, - alignment: 'left', - width: 100, - }, - ], - }, - { - columns: [ - { - text: 'Date Issued', - color: '#aaaaab', - bold: true, - width: '*', - fontSize: 12, - alignment: 'left', - }, - { - text: transaction.invoice_date, - bold: true, - color: '#333333', - fontSize: 12, - alignment: 'left', - width: 100, - }, - ], - }, - { - columns: [ - { - text: 'Status', - color: '#aaaaab', - bold: true, - fontSize: 12, - alignment: 'left', - width: '*', - }, - { - text: transaction.status, - bold: true, - fontSize: 14, - alignment: 'left', - color: 'green', - width: 100, - }, - ], - }, - ]; - - return result; -} - -function mappingPrice(transaction) { - const result = []; - const sub_total = [ - { - text: 'Subtotal', - border: [false, true, false, true], - alignment: 'right', - margin: [0, 5, 0, 5], - }, - { - border: [false, true, false, true], - text: Number(transaction.payment_sub_total).toLocaleString('id-ID', { - style: 'currency', - currency: 'IDR', - }), - alignment: 'right', - margin: [0, 5, 0, 5], - }, - ]; - result.push(sub_total); - - if (Number(transaction.payment_discount_total ?? 0) > 0) { - const discount = [ - { - text: 'Discount', - border: [false, false, false, true], - alignment: 'right', - margin: [0, 5, 0, 5], - }, - { - text: Number(transaction.payment_discount_total).toLocaleString( - 'id-ID', - { - style: 'currency', - currency: 'IDR', - }, - ), - border: [false, false, false, true], - alignment: 'right', - margin: [0, 5, 0, 5], - }, - ]; - result.push(discount); - } - - const total = [ - { - text: 'Total', - bold: true, - alignment: 'right', - border: [false, false, false, true], - margin: [0, 5, 0, 5], - }, - { - text: Number(transaction.payment_total).toLocaleString('id-ID', { - style: 'currency', - currency: 'IDR', - }), - bold: true, - alignment: 'right', - border: [false, false, false, true], - margin: [0, 5, 0, 5], - }, - ]; - result.push(total); - - return result; -} - -function mappingItem(items) { - const header = [ - { - text: 'DESCRIPTION', - alignment: 'center', - fillColor: '#eaf2f5', - border: [false, true, false, true], - margin: [0, 5, 0, 5], - textTransform: 'uppercase', - }, - { - text: 'QTY', - alignment: 'center', - fillColor: '#eaf2f5', - border: [false, true, false, true], - margin: [0, 5, 0, 5], - textTransform: 'uppercase', - }, - { - text: 'UNIT PRICE', - alignment: 'center', - fillColor: '#eaf2f5', - border: [false, true, false, true], - margin: [0, 5, 0, 5], - textTransform: 'uppercase', - }, - { - text: 'TOTAL', - border: [false, true, false, true], - alignment: 'center', - fillColor: '#eaf2f5', - margin: [0, 5, 0, 5], - textTransform: 'uppercase', - }, - ]; - - const result = []; - items.forEach((item) => { - const dataRow = [ - { - text: item.item_name, - border: [false, false, false, true], - margin: [0, 5, 0, 5], - alignment: 'left', - }, - { - text: item.qty, - border: [false, false, false, true], - margin: [0, 5, 0, 5], - alignment: 'center', - }, - { - text: Number(item.item_price).toLocaleString('id-ID', { - style: 'currency', - currency: 'IDR', - }), - border: [false, false, false, true], - margin: [0, 5, 0, 5], - alignment: 'right', - }, - { - text: Number(item.total_price).toLocaleString('id-ID', { - style: 'currency', - currency: 'IDR', - }), - border: [false, false, false, true], - alignment: 'right', - margin: [0, 5, 0, 5], - }, - ]; - result.push(dataRow); - }); - - const body = [header, ...result]; - - return body; -} diff --git a/src/modules/configuration/export/domain/templates/invoice.template.ts b/src/modules/configuration/export/domain/templates/invoice.template.ts index f3a33d2..ba639dc 100644 --- a/src/modules/configuration/export/domain/templates/invoice.template.ts +++ b/src/modules/configuration/export/domain/templates/invoice.template.ts @@ -1,32 +1,54 @@ -// playground requires you to assign document definition to a variable called dd - import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity'; import { + mappingBody, + mappingBodyBottom, mappingFooter, mappingHeader, mappingItem, mappingPrice, } from './helpers/invoice-mapping.helper'; +import { InvoiceType } from '../../constants'; +import { PaymentMethodEntity } from 'src/modules/transaction/payment-method/domain/entities/payment-method.entity'; +import * as fs from 'fs'; + +export function InvoiceTempalte( + transaction: TransactionEntity, + invoiceType: InvoiceType, + banks: PaymentMethodEntity[], +) { + const filePath = './image/logo.jpeg'; + const imageBase64 = fs.readFileSync(filePath).toString('base64'); + const imageUrl = `data:image/png;base64,${imageBase64}`; -export function InvoiceOldTempalte(transaction: TransactionEntity) { return { content: [ { alignment: 'justify', - columns: mappingHeader(), + columns: mappingHeader(transaction, invoiceType), }, '\n\n', + + // tipe booking date change tidak ada { columns: [ { - text: 'Dear, \n Customer \n - \n Booking Date: 15 Juli 2024', + text: `Dear, \n ${transaction.customer_name} \n ${ + transaction.customer_phone + } \n Booking Date: ${new Date( + transaction.booking_date, + ).toDateString()}`, bold: true, }, ], }, - "\n\n Thank you for choosing us! We're absolutely thrilled and can't wait to embark on this exiting day with you. See you soon for fun times ahead!\n\n", + '\n', + { + text: mappingBody(transaction, invoiceType), + }, + '\n', + { layout: { defaultBorder: false, @@ -66,7 +88,7 @@ export function InvoiceOldTempalte(transaction: TransactionEntity) { }, table: { widths: ['*', 'auto', 'auto', 100], - body: mappingItem(transaction), + body: mappingItem(transaction, invoiceType), }, }, '\n', @@ -110,16 +132,15 @@ export function InvoiceOldTempalte(transaction: TransactionEntity) { table: { headerRows: 1, widths: ['*', 100], - body: mappingPrice(transaction), + body: mappingPrice(transaction, invoiceType), }, }, - '\n', - 'Just a friendly reminder that your invoice will expire on . \n', - 'To keep things running smoothly, please ensure your payment is compoleted before this data. \n\n', - 'For your convenience, here is a list of our account details :', - "\n\n Once you've made the payment, please kindly email or send the proof of payment so we can proceed with your booking promptly. \n", - 'If you have any questions or need assistance, feel free to reach out to our support team at 0821-3242-4523 \n\n\n\n', + '\n', + { + text: mappingBodyBottom(transaction, invoiceType, banks), + }, + '\n', { layout: { @@ -187,5 +208,8 @@ export function InvoiceOldTempalte(transaction: TransactionEntity) { defaultStyle: { columnGap: 20, }, + images: { + data: imageUrl, + }, }; } diff --git a/src/modules/configuration/export/export.module.ts b/src/modules/configuration/export/export.module.ts index 382f7dc..e4ed0a6 100644 --- a/src/modules/configuration/export/export.module.ts +++ b/src/modules/configuration/export/export.module.ts @@ -1,10 +1,23 @@ import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; -import { ExportController } from './infrastructure/export.controller'; +import { PdfMakeManager } from './domain/managers/pdf-make.manager'; +import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service'; +import { PaymentMethodDataService } from 'src/modules/transaction/payment-method/data/services/payment-method-data.service'; +import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model'; +import { TransactionItemModel } from 'src/modules/transaction/transaction/data/models/transaction-item.model'; +import { PaymentMethodModel } from 'src/modules/transaction/payment-method/data/models/payment-method.model'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { CqrsModule } from '@nestjs/cqrs'; +import { TransactionTaxModel } from 'src/modules/transaction/transaction/data/models/transaction-tax.model'; @Module({ - imports: [ConfigModule.forRoot()], - controllers: [ExportController], - providers: [], + imports: [ + ConfigModule.forRoot(), + TypeOrmModule.forFeature([TransactionModel], CONNECTION_NAME.DEFAULT), + CqrsModule, + ], + controllers: [], + providers: [PdfMakeManager], }) export class ExportModule {} diff --git a/src/modules/configuration/export/infrastructure/export.controller.ts b/src/modules/configuration/export/infrastructure/export.controller.ts index 705b1d5..75034c5 100644 --- a/src/modules/configuration/export/infrastructure/export.controller.ts +++ b/src/modules/configuration/export/infrastructure/export.controller.ts @@ -1,7 +1,6 @@ import { Controller, Post } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { Public } from 'src/core/guards'; -import { PdfMaker } from '../domain/helpers/pdf-make.helper'; @ApiTags('export') @Controller('export') diff --git a/src/modules/configuration/mail/domain/email-template/change-date-information.html b/src/modules/configuration/mail/domain/email-template/change-date-information.html index e69de29..c3827f5 100644 --- a/src/modules/configuration/mail/domain/email-template/change-date-information.html +++ b/src/modules/configuration/mail/domain/email-template/change-date-information.html @@ -0,0 +1,404 @@ + + + + + + + Email Confirmation + + + + + + + + + + +
  +
+ + + + + + +
+ + + + +
+

Dear,

+

{{customer_name}}

+

{{customer_phone}}

+ +

Great News! We've successfully updated your booking date as per your request.

+

We're excited to accommodate your new plans and ensure evertyhing goes smoothly.

+ +

Here are your updated booking details

+ Original Booking Date: {{booking_date_before}} + New Booking Date: {{booking_date}} + +

For yout convenience, we've attached a new confirmation receipt reflecting these changes.

+

Please be sure to bring this updated receipt with you on the new date

+ +

To keep the good times rolling, our friendly support team is just a call away at

+ {{phone_cs}} +
+ +

Thank you and we can't wait to see you and make sure you have an amazing time!

+ +

+ Best Regrads,
+ WEplayground +
+
+
+
 
+ + + \ No newline at end of file diff --git a/src/modules/configuration/mail/domain/email-template/payment-confirmation-bank.html b/src/modules/configuration/mail/domain/email-template/invoice-bank.html similarity index 84% rename from src/modules/configuration/mail/domain/email-template/payment-confirmation-bank.html rename to src/modules/configuration/mail/domain/email-template/invoice-bank.html index daa023e..e20d983 100644 --- a/src/modules/configuration/mail/domain/email-template/payment-confirmation-bank.html +++ b/src/modules/configuration/mail/domain/email-template/invoice-bank.html @@ -4,7 +4,7 @@ - Email Ibunda + Email Confirmation + + + + + + + + + +
  +
+ + + + + + +
+ + + + +
+

Dear,

+

{{customer_name}}

+

{{customer_phone}}

+ +

We hope this message finds you well!

+ +

Uh-oh! it looks like your invoice, dated {{invoice_date}}, has officially expired as of {{expired_date}}

+

But no worries, we can fix this together!

+ +

To keep the good times rolling, our friendly support team is just a call away at

+ {{phone_cs}} + +

Here are the details of the expired invoice:

+

Booking Date: {{booking_date}}

+

Total Invoice: {{total_payment}}

+ +

+ Best Regrads,
+ WEplayground +
+
+
+
 
+ + + \ No newline at end of file diff --git a/src/modules/configuration/mail/domain/email-template/payment-confirmation-midtrans.html b/src/modules/configuration/mail/domain/email-template/invoice-midtrans.html similarity index 84% rename from src/modules/configuration/mail/domain/email-template/payment-confirmation-midtrans.html rename to src/modules/configuration/mail/domain/email-template/invoice-midtrans.html index b1cc797..589f005 100644 --- a/src/modules/configuration/mail/domain/email-template/payment-confirmation-midtrans.html +++ b/src/modules/configuration/mail/domain/email-template/invoice-midtrans.html @@ -4,7 +4,7 @@ - Email Ibunda + Email Confirmation + + + + + + + + + +
  +
+ + + + + + +
+ + + + +
+

Dear,

+

{{customer_name}}

+

{{customer_phone}}

+ +

We are excited to inform you that your payment has been successfully received!

+

Attached to this email, you will find your confirmatin receipt

+

Please keep this safe as you will need to show it at the entrance upon your arrival

+

It's your golden ticket to all the fun and excitement awaiting you!

+ +
+

Here's a quick recap:

+ +

Booking Date: {{booking_date}}

+

Invoice Code: {{invoice_code}}

+

Payment Date: {{payment_date}}

+

Payment Code: {{payment_code}}

+

Payment Via: {{payment_via}}

+

Account No: {{account_no}}

+

On Behalf Of: {{account_name}}

+
+ +

If you have any questions or need assistance, feel free to reach out to our support team at

+ {{phone_cs}} +
+ +

Font forget to bring a smile and your confirmation receipt (attached) for a smooth entry

+

We can't wait to see you and ensure you have an amazing time with us!

+ +

+ Best Regrads,
+ WEplayground +
+
+
+
 
+ + + \ No newline at end of file diff --git a/src/modules/configuration/mail/domain/email-template/refund-confirmation.html b/src/modules/configuration/mail/domain/email-template/refund-confirmation.html index e69de29..a938560 100644 --- a/src/modules/configuration/mail/domain/email-template/refund-confirmation.html +++ b/src/modules/configuration/mail/domain/email-template/refund-confirmation.html @@ -0,0 +1,415 @@ + + + + + + + Email Confirmation + + + + + + + + + + +
  +
+ + + + + + +
+ + + + +
+

Dear,

+

{{customer_name}}

+

{{customer_phone}}

+ +

Good News!

+

We've successfully processed your refund for:

+ +

Here are the details of your refund:

+

Transaction Date: {{booking_date}}

+

Transaction Code: {{invoice_code}}

+

Total Refund: {{refund.refund_total}}

+

{{{refund_items}}}

+ +

Transaction Number: {{invoice_code}}

+

Refund Processed Date: {{refund.refund_date}}

+

Bank Account: {{refund.bank_name}}

+

Account Number: {{refund.bank_account_number}}

+

Account Name: {{refund.bank_account_name}}

+ +

We hope this helps make things right, and we're here to assist if you need anything else

+

You should see the refund in your account within 3 business days

+ + +

Thank you for your patience and understanding

+

If you have any questions or need further assistance, don't hesitate to reach out us at

+ {{phone_cs}} + +
+ +

Thank you and we can't wait to see you and make sure you have an amazing time!

+ +

+ Best Regrads,
+ WEplayground +
+
+
+
 
+ + + \ No newline at end of file diff --git a/src/modules/configuration/mail/domain/email-template/refund-request.html b/src/modules/configuration/mail/domain/email-template/refund-request.html index e69de29..1c36420 100644 --- a/src/modules/configuration/mail/domain/email-template/refund-request.html +++ b/src/modules/configuration/mail/domain/email-template/refund-request.html @@ -0,0 +1,409 @@ + + + + + + + Email Confirmation + + + + + + + + + + +
  +
+ + + + + + +
+ + + + +
+

Dear,

+

{{customer_name}}

+

{{customer_phone}}

+ +

We're trully sorry for any inconvenience that led to this request

+

We've received your refund request for :

+ +

Transaction Date: {{booking_date}}

+

Transaction Code: {{invoice_code}}

+

Refund Code: {{refund.code}}

+

Total Refund: {{refund.refund_total}}

+

{{{refund_items}}}

+ +

Your satisfaction is important to us, and we're commited to resolving this as quickly as possible

+

Our team is already on it and will process your refund request promptly

+

We'll keep you updated and notify you once the refund has been processed

+ +

If you have any questions or need assistance, feel free to reach out to our support team at

+ {{phone_cs}} + +
+ +

Thank you for your patience and understanding

+

We appriciate your feedback and are here to make things right

+ +

+ Best Regrads,
+ WEplayground +
+
+
+
 
+ + + \ No newline at end of file diff --git a/src/modules/configuration/mail/domain/handlers/payment-transaction.handler.ts b/src/modules/configuration/mail/domain/handlers/payment-transaction.handler.ts index e3fb8ad..30955b4 100644 --- a/src/modules/configuration/mail/domain/handlers/payment-transaction.handler.ts +++ b/src/modules/configuration/mail/domain/handlers/payment-transaction.handler.ts @@ -4,9 +4,19 @@ import { PaymentMethodDataService } from 'src/modules/transaction/payment-method import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service'; import { TransactionChangeStatusEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-change-status.event'; import { sendEmail } from '../helpers/send-email.helper'; -import { TransactionPaymentType } from 'src/modules/transaction/transaction/constants'; +import { InvoiceType } from 'src/modules/configuration/export/constants'; +import { GeneratePdf } from 'src/modules/configuration/export/domain/templates/helpers/generate-pdf.helper'; +import { TransactionUpdatedEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-updated.event'; +import { RefundChangeStatusEvent } from 'src/modules/transaction/refund/domain/entities/event/refund-change-status.event'; +import { RefundCreatedEvent } from 'src/modules/transaction/refund/domain/entities/event/refund-created.event'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; -@EventsHandler(TransactionChangeStatusEvent) +@EventsHandler( + TransactionChangeStatusEvent, + TransactionUpdatedEvent, + RefundChangeStatusEvent, + RefundCreatedEvent, +) export class PaymentTransactionHandler implements IEventHandler { @@ -16,46 +26,252 @@ export class PaymentTransactionHandler ) {} async handle(event: TransactionChangeStatusEvent) { - const data_id = event.data.id; - const old_data = event.data.old; - const current_data = event.data.data; - let payments = []; + try { + const old_data = event.data.old; + const current_data = event.data.data; + const data_id = current_data.transaction_id ?? event.data.id; + const from_refund = event.data.module == TABLE_NAME.REFUND; - if ( - old_data.status == STATUS.DRAFT && - current_data.status == STATUS.PENDING && - current_data.payment_type != TransactionPaymentType.COUNTER && - !!current_data.customer_email - ) { - if (current_data.payment_type != TransactionPaymentType.MIDTRANS) { - payments = await this.paymentService.getManyByOptions({ - where: { - status: STATUS.ACTIVE, - }, - }); - } + const payments = await this.paymentService.getManyByOptions({ + where: { + status: STATUS.ACTIVE, + }, + }); const transaction = await this.dataService.getOneByOptions({ where: { id: data_id, }, - relations: ['items'], + relations: [ + 'items', + 'refunds', + 'refunds.refund_items', + 'refunds.refund_items.transaction_item', + ], }); - try { + Object.assign(transaction, { + booking_date: new Date(transaction.booking_date).toDateString(), + booking_date_before: new Date( + transaction.booking_date_before, + ).toDateString(), + email: transaction.customer_email, + payment_methods: payments, + }); + + const refund = transaction?.['refunds']?.find( + (refund) => ![STATUS.CANCEL].includes(refund.status), + ); + if (refund) { + Object.assign(refund, { + refund_date: new Date(refund?.refund_date).toDateString(), + request_date: new Date(refund?.request_date).toDateString(), + refund_total: Number(refund?.refund_total).toLocaleString('id-ID', { + style: 'currency', + currency: 'IDR', + }), + }); + + Object.assign(transaction, { + refund: refund, + }); + } + + if (transaction?.['refund']?.refund_items.length > 0) { + Object.assign(transaction, { + refund_items: ` +

Refund Items:

+
    + ${transaction?.['refund']?.refund_items + ?.filter((item) => Number(item.qty_refund) > 0) + .map((item) => { + return ` +
  • ${item.qty_refund} ${item.transaction_item.item_name}
  • + `; + })} +
`, + }); + } + + if (!transaction.customer_email) return; + + // refund request + if ( + from_refund && + transaction['refund'] && + [STATUS.DRAFT].includes(transaction['refund'].status) + ) { + const pdf = await GeneratePdf( + transaction, + InvoiceType.REFUND_REQUEST, + payments, + ); sendEmail( [ { ...transaction, - email: transaction.customer_email, - payment_methods: payments, + payment_total: Number(transaction.payment_total).toLocaleString( + 'id-ID', + { + style: 'currency', + currency: 'IDR', + }, + ), }, ], - 'Payment Confirmation', + InvoiceType.REFUND_REQUEST, + pdf, ); - } catch (error) { - console.log(error); } + + // refund confirmation + else if ( + from_refund && + transaction['refund'] && + transaction['refund'].status == STATUS.REFUNDED + ) { + const pdf = await GeneratePdf( + transaction, + InvoiceType.REFUND_CONFIRMATION, + payments, + ); + sendEmail( + [ + { + ...transaction, + payment_total: Number(transaction.payment_total).toLocaleString( + 'id-ID', + { + style: 'currency', + currency: 'IDR', + }, + ), + }, + ], + InvoiceType.REFUND_CONFIRMATION, + pdf, + ); + } + + // payment settled + else if ( + !from_refund && + old_data.status != current_data.status && + [STATUS.ACTIVE, STATUS.SETTLED].includes(current_data.status) + ) { + const pdf = await GeneratePdf( + transaction, + InvoiceType.PAYMENT_CONFIRMATION, + payments, + ); + sendEmail( + [ + { + ...transaction, + payment_total: Number(transaction.payment_total).toLocaleString( + 'id-ID', + { + style: 'currency', + currency: 'IDR', + }, + ), + }, + ], + InvoiceType.PAYMENT_CONFIRMATION, + pdf, + ); + } + + // payment confirm to pending + else if ( + !from_refund && + old_data.status != current_data.status && + [STATUS.PENDING].includes(current_data.status) + ) { + const pdf = await GeneratePdf( + transaction, + InvoiceType.BOOKING_INVOICE, + payments, + ); + sendEmail( + [ + { + ...transaction, + payment_total: Number(transaction.payment_total).toLocaleString( + 'id-ID', + { + style: 'currency', + currency: 'IDR', + }, + ), + }, + ], + InvoiceType.BOOKING_INVOICE, + pdf, + ); + } + + // payment expired + else if ( + !from_refund && + old_data.status != current_data.status && + [STATUS.PENDING].includes(current_data.status) + ) { + const pdf = await GeneratePdf( + transaction, + InvoiceType.INVOICE_EXPIRED, + payments, + ); + sendEmail( + [ + { + ...transaction, + payment_total: Number(transaction.payment_total).toLocaleString( + 'id-ID', + { + style: 'currency', + currency: 'IDR', + }, + ), + }, + ], + InvoiceType.INVOICE_EXPIRED, + pdf, + ); + } + + // change booking date + else if ( + !from_refund && + old_data.booking_date != current_data.booking_date && + [STATUS.SETTLED, STATUS.ACTIVE, STATUS.PENDING].includes( + current_data.status, + ) + ) { + const pdf = await GeneratePdf( + transaction, + InvoiceType.BOOKING_DATE_CHANGE, + payments, + ); + sendEmail( + [ + { + ...transaction, + payment_total: Number(transaction.payment_total).toLocaleString( + 'id-ID', + { + style: 'currency', + currency: 'IDR', + }, + ), + }, + ], + InvoiceType.BOOKING_DATE_CHANGE, + pdf, + ); + } + } catch (error) { + console.log(error); } } } diff --git a/src/modules/configuration/mail/domain/helpers/send-email.helper.ts b/src/modules/configuration/mail/domain/helpers/send-email.helper.ts index bae519c..0166d3a 100644 --- a/src/modules/configuration/mail/domain/helpers/send-email.helper.ts +++ b/src/modules/configuration/mail/domain/helpers/send-email.helper.ts @@ -3,8 +3,9 @@ import * as handlebars from 'handlebars'; import * as path from 'path'; import * as fs from 'fs'; import { TransactionPaymentType } from 'src/modules/transaction/transaction/constants'; +import { InvoiceType } from 'src/modules/configuration/export/constants'; -export async function sendEmail(receivers, subject) { +export async function sendEmail(receivers, invoiceType, attachment?) { const smtpTransport = nodemailer.createTransport({ host: process.env.EMAIL_HOST, port: process.env.EMAIL_POST, @@ -13,13 +14,10 @@ export async function sendEmail(receivers, subject) { pass: process.env.EMAIL_TOKEN, }, }); - let templateName = 'payment-confirmation-bank'; for (const receiver of receivers) { try { - if (receiver.payment_type == TransactionPaymentType.MIDTRANS) - templateName = 'payment-confirmation-midtrans'; - + const templateName = getTemplate(receiver.payment_type, invoiceType); let templatePath = path.join( __dirname, `../email-template/${templateName}.html`, @@ -33,9 +31,15 @@ export async function sendEmail(receivers, subject) { const emailContext = { from: process.env.EMAIL_SENDER ?? 'no-reply@weplayground.app', to: receiver.email, - subject: subject, + subject: invoiceType, html: htmlToSend, attachDataUrls: true, + attachments: [ + { + filename: `${invoiceType}.pdf`, + content: attachment, + }, + ], }; await new Promise((f) => setTimeout(f, 2000)); @@ -52,3 +56,24 @@ export async function sendEmail(receivers, subject) { } } } + +function getTemplate(transactionType, invoiceType) { + if (invoiceType == InvoiceType.BOOKING_DATE_CHANGE) { + return 'change-date-information'; + } else if (invoiceType == InvoiceType.INVOICE_EXPIRED) { + return 'invoice-expired'; + } else if (invoiceType == InvoiceType.REFUND_REQUEST) { + return 'refund-request'; + } else if (invoiceType == InvoiceType.REFUND_CONFIRMATION) { + return 'refund-confirmation'; + } else if (invoiceType == InvoiceType.PAYMENT_CONFIRMATION) { + return 'payment-confirmation'; + } else if ( + invoiceType == InvoiceType.BOOKING_INVOICE && + transactionType != TransactionPaymentType.MIDTRANS + ) { + return 'invoice-bank'; + } else if (invoiceType == InvoiceType.BOOKING_INVOICE) { + return 'invoice-midtrans'; + } +} diff --git a/src/modules/configuration/mail/mail.module.ts b/src/modules/configuration/mail/mail.module.ts index c45d49c..412b0c3 100644 --- a/src/modules/configuration/mail/mail.module.ts +++ b/src/modules/configuration/mail/mail.module.ts @@ -9,6 +9,7 @@ import { TransactionModel } from 'src/modules/transaction/transaction/data/model import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service'; import { PaymentTransactionHandler } from './domain/handlers/payment-transaction.handler'; import { MailTemplateController } from './infrastructure/mail.controller'; +import { PdfMakeManager } from '../export/domain/managers/pdf-make.manager'; @Module({ imports: [ @@ -21,6 +22,8 @@ import { MailTemplateController } from './infrastructure/mail.controller'; ], controllers: [MailTemplateController], providers: [ + PdfMakeManager, + PaymentTransactionHandler, PaymentMethodDataService, TransactionDataService, diff --git a/src/modules/transaction/transaction/data/models/transaction.model.ts b/src/modules/transaction/transaction/data/models/transaction.model.ts index 565b543..58bdba7 100644 --- a/src/modules/transaction/transaction/data/models/transaction.model.ts +++ b/src/modules/transaction/transaction/data/models/transaction.model.ts @@ -80,6 +80,9 @@ export class TransactionModel @Column('date', { name: 'booking_date', nullable: true }) booking_date: Date; + @Column('date', { name: 'booking_date_before', nullable: true }) + booking_date_before: Date; + @Column('date', { name: 'settlement_date', nullable: true }) settlement_date: Date; diff --git a/src/modules/transaction/transaction/domain/entities/transaction.entity.ts b/src/modules/transaction/transaction/domain/entities/transaction.entity.ts index 4b1ba0f..aa1814b 100644 --- a/src/modules/transaction/transaction/domain/entities/transaction.entity.ts +++ b/src/modules/transaction/transaction/domain/entities/transaction.entity.ts @@ -30,6 +30,7 @@ export interface TransactionEntity extends BaseStatusEntity { no_of_group: number; booking_date: Date; // tnaggal untuk booking + booking_date_before: Date; // tnaggal untuk booking settlement_date: Date; // tanggal status berubah menjadi settlement invoice_date: Date; // tanggal invoice terkirim diff --git a/src/modules/transaction/transaction/domain/usecases/managers/download-invoice-transaction.manager.ts b/src/modules/transaction/transaction/domain/usecases/managers/download-invoice-transaction.manager.ts deleted file mode 100644 index 4d7f0f7..0000000 --- a/src/modules/transaction/transaction/domain/usecases/managers/download-invoice-transaction.manager.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { BaseCustomManager } from 'src/core/modules/domain/usecase/managers/base-custom.manager'; -import { TransactionEntity } from '../../entities/transaction.entity'; -import { EventTopics } from 'src/core/strings/constants/interface.constants'; -import { TransactionModel } from '../../../data/models/transaction.model'; -import { PdfMaker } from 'src/modules/configuration/export/domain/helpers/pdf-make.helper'; - -export class InvoiceTransactionManager extends BaseCustomManager { - get entityTarget(): any { - return TransactionModel; - } - - getResult() { - return []; - } - - get eventTopics(): EventTopics[] { - return; - } - - validateProcess(): Promise { - return; - } - - beforeProcess(): Promise { - return; - } - - async process(): Promise { - const transaction = await this.dataService.getOneByOptions({ - where: { - id: this.data.id, - }, - relations: ['items'], - }); - - PdfMaker(transaction); - return; - } - - afterProcess(): Promise { - return; - } -} diff --git a/src/modules/transaction/transaction/domain/usecases/managers/update-transaction.manager.ts b/src/modules/transaction/transaction/domain/usecases/managers/update-transaction.manager.ts index 5fb66e4..c124b17 100644 --- a/src/modules/transaction/transaction/domain/usecases/managers/update-transaction.manager.ts +++ b/src/modules/transaction/transaction/domain/usecases/managers/update-transaction.manager.ts @@ -19,6 +19,13 @@ export class UpdateTransactionManager extends BaseUpdateManager { mappingRevertTransaction(this.data, TransactionType.ADMIN); + const changeDate = this.data.booking_date != this.oldData.booking_date; + + if (changeDate) { + Object.assign(this.data, { + booking_date_before: this.oldData.booking_date, + }); + } return; } diff --git a/src/modules/transaction/transaction/domain/usecases/transaction-data.orchestrator.ts b/src/modules/transaction/transaction/domain/usecases/transaction-data.orchestrator.ts index 257b3a8..f8c002c 100644 --- a/src/modules/transaction/transaction/domain/usecases/transaction-data.orchestrator.ts +++ b/src/modules/transaction/transaction/domain/usecases/transaction-data.orchestrator.ts @@ -15,7 +15,9 @@ import { BatchCancelTransactionManager } from './managers/batch-cancel-transacti import { ConfirmDataTransactionManager } from './managers/confirm-data-transaction.manager'; import { BatchConfirmDataTransactionManager } from './managers/batch-confirm-data-transaction.manager'; import { MidtransService } from 'src/modules/configuration/midtrans/data/services/midtrans.service'; -// import { InvoiceTransactionManager } from './managers/download-invoice-transaction.manager'; +import { PdfMakeManager } from 'src/modules/configuration/export/domain/managers/pdf-make.manager'; +import { PaymentMethodDataService } from 'src/modules/transaction/payment-method/data/services/payment-method-data.service'; +import { InvoiceType } from 'src/modules/configuration/export/constants'; @Injectable() export class TransactionDataOrchestrator { @@ -30,8 +32,9 @@ export class TransactionDataOrchestrator { private batchDeleteManager: BatchDeleteTransactionManager, private cancelManager: CancelTransactionManager, private batchCancelManager: BatchCancelTransactionManager, - // private invoiceManager: InvoiceTransactionManager, + private invoiceManager: PdfMakeManager, private serviceData: TransactionDataService, + private paymentMethodService: PaymentMethodDataService, private midtransService: MidtransService, ) {} @@ -49,14 +52,19 @@ export class TransactionDataOrchestrator { return this.updateManager.getResult(); } - // async invoice(dataId): Promise { - // this.invoiceManager.setData({ - // id: dataId, - // }); - // this.invoiceManager.setService(this.serviceData, TABLE_NAME.TRANSACTION); - // await this.invoiceManager.execute(); - // return this.invoiceManager.getResult(); - // } + async invoice(dataId, invoiceType): Promise { + this.invoiceManager.setData({ + id: dataId, + invoice_type: invoiceType, + }); + this.invoiceManager.setService( + this.serviceData, + TABLE_NAME.TRANSACTION, + this.paymentMethodService, + ); + await this.invoiceManager.execute(); + return this.invoiceManager.getResult(); + } async delete(dataId): Promise { this.deleteManager.setData(dataId); diff --git a/src/modules/transaction/transaction/infrastructure/dto/donwload-pdf.dto.ts b/src/modules/transaction/transaction/infrastructure/dto/donwload-pdf.dto.ts new file mode 100644 index 0000000..b0d522f --- /dev/null +++ b/src/modules/transaction/transaction/infrastructure/dto/donwload-pdf.dto.ts @@ -0,0 +1,18 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, ValidateIf } from 'class-validator'; +import { InvoiceType } from 'src/modules/configuration/export/constants'; + +export class DownloadPdfDto { + @ApiProperty({ + type: 'string', + required: false, + description: `Select ${Object.values(InvoiceType)}`, + }) + @ValidateIf((body) => body.invoice_type) + @IsEnum(InvoiceType, { + message: `invoice type must be a valid enum ${JSON.stringify( + Object.values(InvoiceType), + )}`, + }) + invoice_type: InvoiceType; +} diff --git a/src/modules/transaction/transaction/infrastructure/transaction-data.controller.ts b/src/modules/transaction/transaction/infrastructure/transaction-data.controller.ts index 06e97dc..d9c7269 100644 --- a/src/modules/transaction/transaction/infrastructure/transaction-data.controller.ts +++ b/src/modules/transaction/transaction/infrastructure/transaction-data.controller.ts @@ -6,7 +6,9 @@ import { Patch, Post, Put, + Res, } from '@nestjs/common'; +import { Response } from 'express'; import { TransactionDataOrchestrator } from '../domain/usecases/transaction-data.orchestrator'; import { TransactionDto } from './dto/transaction.dto'; import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; @@ -15,6 +17,7 @@ import { TransactionEntity } from '../domain/entities/transaction.entity'; import { BatchResult } from 'src/core/response/domain/ok-response.interface'; import { BatchIdsDto } from 'src/core/modules/infrastructure/dto/base-batch.dto'; import { Public } from 'src/core/guards'; +import { DownloadPdfDto } from './dto/donwload-pdf.dto'; @ApiTags(`${MODULE_NAME.TRANSACTION.split('-').join(' ')} - data`) @Controller(`v1/${MODULE_NAME.TRANSACTION}`) @@ -28,10 +31,18 @@ export class TransactionDataController { return await this.orchestrator.create(data); } - // @Put('/:id/invoice/download') - // async invoiceDownload(@Param('id') dataId: string): Promise { - // return await this.orchestrator.invoice(dataId); - // } + @Put('/:id/invoice/download') + async invoiceDownload( + @Param('id') dataId: string, + @Body() body: DownloadPdfDto, + @Res() res: Response, + ): Promise { + const data = await this.orchestrator.invoice(dataId, body.invoice_type); + res.setHeader('Content-Type', 'application/pdf'); + res.setHeader('Content-Disposition', 'attachment; filename=invoice.pdf'); + res.send(data); + return res; + } @Put('/batch-delete') async batchDeleted(@Body() body: BatchIdsDto): Promise { diff --git a/src/modules/transaction/transaction/transaction.module.ts b/src/modules/transaction/transaction/transaction.module.ts index 988adec..0965400 100644 --- a/src/modules/transaction/transaction/transaction.module.ts +++ b/src/modules/transaction/transaction/transaction.module.ts @@ -32,7 +32,9 @@ import { TaxModel } from '../tax/data/models/tax.model'; import { SettledTransactionHandler } from './domain/usecases/handlers/settled-transaction.handler'; import { RefundUpdatedHandler } from './domain/usecases/handlers/refund-update.handler'; import { MidtransCallbackHandler } from './domain/usecases/handlers/midtrans-transaction-callback.handler'; -import { InvoiceTransactionManager } from './domain/usecases/managers/download-invoice-transaction.manager'; +import { PdfMakeManager } from 'src/modules/configuration/export/domain/managers/pdf-make.manager'; +import { PaymentMethodDataService } from '../payment-method/data/services/payment-method-data.service'; +import { PaymentMethodModel } from '../payment-method/data/models/payment-method.model'; @Module({ imports: [ @@ -44,6 +46,7 @@ import { InvoiceTransactionManager } from './domain/usecases/managers/download-i TransactionTaxModel, TaxModel, SalesPriceFormulaModel, + PaymentMethodModel, ], CONNECTION_NAME.DEFAULT, ), @@ -65,7 +68,7 @@ import { InvoiceTransactionManager } from './domain/usecases/managers/download-i BatchDeleteTransactionManager, BatchConfirmTransactionManager, CancelTransactionManager, - InvoiceTransactionManager, + PdfMakeManager, BatchCancelTransactionManager, ConfirmDataTransactionManager, BatchConfirmDataTransactionManager, @@ -74,6 +77,7 @@ import { InvoiceTransactionManager } from './domain/usecases/managers/download-i TransactionReadService, TaxDataService, SalesPriceFormulaDataService, + PaymentMethodDataService, TransactionDataOrchestrator, TransactionReadOrchestrator,