fix(SPG-494) PDF Generator
parent
b05e6fab5a
commit
feee9a3439
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 120 KiB |
|
@ -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,
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddColumnToTransactionsTable1722693550579
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'AddColumnToTransactionsTable1722693550579';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "transactions" ADD "booking_date_before" date`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "transactions" DROP COLUMN "booking_date_before"`,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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<any> {
|
||||
return Object.values(GateType);
|
||||
}
|
||||
|
||||
@Get('invoice-type')
|
||||
async invoiceType(): Promise<any> {
|
||||
return Object.values(InvoiceType);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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';
|
|
@ -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();
|
||||
}
|
|
@ -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<TransactionEntity> {
|
||||
get entityTarget(): any {
|
||||
return TransactionModel;
|
||||
}
|
||||
|
||||
get eventTopics(): EventTopics[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
async validateProcess(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
async beforeProcess(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
async process(): Promise<void> {
|
||||
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<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
getResult() {
|
||||
return this.result;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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!",
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -0,0 +1,404 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>Email Confirmation</title>
|
||||
<style>
|
||||
/* -------------------------------------
|
||||
GLOBAL RESETS
|
||||
------------------------------------- */
|
||||
img {
|
||||
border: none;
|
||||
-ms-interpolation-mode: bicubic;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #f6f6f6;
|
||||
font-family: sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table td {
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
BODY & CONTAINER
|
||||
------------------------------------- */
|
||||
|
||||
.body {
|
||||
background-color: #f6f6f6;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink
|
||||
down on a phone or something */
|
||||
.container {
|
||||
display: block;
|
||||
Margin: 0 auto !important;
|
||||
/* makes it centered */
|
||||
max-width: 580px;
|
||||
padding: 10px;
|
||||
width: 580px;
|
||||
}
|
||||
|
||||
/* This should also be a block element, so that it will fill 100% of the .container */
|
||||
.content {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
Margin: 0 auto;
|
||||
max-width: 580px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
HEADER, FOOTER, MAIN
|
||||
------------------------------------- */
|
||||
.main {
|
||||
background: #ffffff;
|
||||
border-radius: 3px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.content-block {
|
||||
padding-bottom: 10px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
clear: both;
|
||||
Margin-top: 10px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.footer td,
|
||||
.footer p,
|
||||
.footer span,
|
||||
.footer a {
|
||||
color: #999999;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
TYPOGRAPHY
|
||||
------------------------------------- */
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
color: #000000;
|
||||
font-family: sans-serif;
|
||||
font-weight: 400;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
Margin-bottom: 30px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 35px;
|
||||
font-weight: 300;
|
||||
text-align: center;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
p,
|
||||
ul,
|
||||
ol {
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
Margin-bottom: 15px;
|
||||
}
|
||||
|
||||
p li,
|
||||
ul li,
|
||||
ol li {
|
||||
list-style-position: inside;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #3498db;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
BUTTONS
|
||||
------------------------------------- */
|
||||
.btn {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn>tbody>tr>td {
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.btn table {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.btn table td {
|
||||
background-color: #ffffff;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn a {
|
||||
background-color: #ffffff;
|
||||
border: solid 1px #3498db;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
color: #3498db;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
padding: 12px 25px;
|
||||
text-decoration: none;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.btn-primary table td {
|
||||
background-color: #3498db;
|
||||
}
|
||||
|
||||
.btn-primary a {
|
||||
background-color: #3498db;
|
||||
border-color: #3498db;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
OTHER STYLES THAT MIGHT BE USEFUL
|
||||
------------------------------------- */
|
||||
.last {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.first {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.align-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.align-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.clear {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.mt0 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.mb0 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.preheader {
|
||||
color: transparent;
|
||||
display: none;
|
||||
height: 0;
|
||||
max-height: 0;
|
||||
max-width: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.powered-by a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 0;
|
||||
border-bottom: 1px solid #f6f6f6;
|
||||
Margin: 20px 0;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
RESPONSIVE AND MOBILE FRIENDLY STYLES
|
||||
------------------------------------- */
|
||||
@media only screen and (max-width: 620px) {
|
||||
table[class=body] h1 {
|
||||
font-size: 28px !important;
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
|
||||
table[class=body] p,
|
||||
table[class=body] ul,
|
||||
table[class=body] ol,
|
||||
table[class=body] td,
|
||||
table[class=body] span,
|
||||
table[class=body] a {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
|
||||
table[class=body] .wrapper,
|
||||
table[class=body] .article {
|
||||
padding: 10px !important;
|
||||
}
|
||||
|
||||
table[class=body] .content {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
table[class=body] .container {
|
||||
padding: 0 !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table[class=body] .main {
|
||||
border-left-width: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
border-right-width: 0 !important;
|
||||
}
|
||||
|
||||
table[class=body] .btn table {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table[class=body] .btn a {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table[class=body] .img-responsive {
|
||||
height: auto !important;
|
||||
max-width: 100% !important;
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
PRESERVE THESE STYLES IN THE HEAD
|
||||
------------------------------------- */
|
||||
@media all {
|
||||
.ExternalClass {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ExternalClass,
|
||||
.ExternalClass p,
|
||||
.ExternalClass span,
|
||||
.ExternalClass font,
|
||||
.ExternalClass td,
|
||||
.ExternalClass div {
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
.apple-link a {
|
||||
color: inherit !important;
|
||||
font-family: inherit !important;
|
||||
font-size: inherit !important;
|
||||
font-weight: inherit !important;
|
||||
line-height: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.btn-primary table td:hover {
|
||||
background-color: #34495e !important;
|
||||
}
|
||||
|
||||
.btn-primary a:hover {
|
||||
background-color: #34495e !important;
|
||||
border-color: #34495e !important;
|
||||
}
|
||||
}
|
||||
|
||||
ol {
|
||||
padding: 0 0 0 1em;
|
||||
}
|
||||
|
||||
ol li {
|
||||
margin: 1em 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="body">
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td class="container">
|
||||
<div class="content">
|
||||
<table class="main">
|
||||
<!-- START MAIN CONTENT AREA -->
|
||||
<tr>
|
||||
<td class="wrapper">
|
||||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td>
|
||||
<p class="mb0"><b>Dear,</b></p>
|
||||
<p class="mb0">{{customer_name}}</p>
|
||||
<p>{{customer_phone}}</p>
|
||||
|
||||
<p class="mb0">Great News! We've successfully updated your booking date as per your request.</p>
|
||||
<p>We're excited to accommodate your new plans and ensure evertyhing goes smoothly.</p>
|
||||
|
||||
<p class="mb0">Here are your updated booking details</p>
|
||||
<b class="mb0">Original Booking Date: {{booking_date_before}}</b>
|
||||
<b>New Booking Date: {{booking_date}}</b>
|
||||
|
||||
<p class="mb0">For yout convenience, we've attached a new confirmation receipt reflecting these changes.</p>
|
||||
<p><b>Please be sure to bring this updated receipt with you on the new date</b></p>
|
||||
|
||||
<p class="mb0">To keep the good times rolling, our friendly support team is just a call away at</p>
|
||||
<b>{{phone_cs}}</b>
|
||||
<br>
|
||||
|
||||
<p>Thank you and we can't wait to see you and make sure you have an amazing time!</p>
|
||||
|
||||
<br><br>
|
||||
<b>Best Regrads,</b><br>
|
||||
<b>WEplayground</b>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- END MAIN CONTENT AREA -->
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -4,7 +4,7 @@
|
|||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>Email Ibunda</title>
|
||||
<title>Email Confirmation</title>
|
||||
<style>
|
||||
/* -------------------------------------
|
||||
GLOBAL RESETS
|
||||
|
@ -364,45 +364,44 @@
|
|||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td>
|
||||
<p>Halo {{customer_name}}</p>
|
||||
<p>Pemesanan tiket telah berhasil dengan nomor invoice {{invoice_code}}, Silahkan lakukan pembayaran</p>
|
||||
<br>
|
||||
<p class="mb0"><b>Dear,</b></p>
|
||||
<p class="mb0">{{customer_name}}</p>
|
||||
<p>{{customer_phone}}</p>
|
||||
|
||||
<p>Thank you fot choosing us! We're absolutelty thrilled and can't wait to embark on this exciting day with you. See you soon for fun times ahead</p>
|
||||
|
||||
<p class="mb0">Here's a quick recap of your invoice:</p>
|
||||
<p class="mb0">Booking Date: {{booking_date}}</p>
|
||||
<p>Total Invoice: {{payment_total}}</p>
|
||||
|
||||
<p class="mb0">Just a friendly reminder that your invoice will expire on {{expire_date}}</p>
|
||||
<p>To keep things running smoothly, please ensure your payment is completed before this data</p>
|
||||
|
||||
<p>
|
||||
<b>PEMBAYARAN DAPAT MELALUI</b>
|
||||
For your convenience, here is a list of our account details:
|
||||
<ul>
|
||||
{{#each payment_methods}}
|
||||
<li>
|
||||
<p>
|
||||
<b>{{issuer_name}}</b><br>
|
||||
<span>Name: <b>{{account_name}}</b></span><br>
|
||||
<span>Number: <b>{{account_number}}</b></span>
|
||||
</p>
|
||||
<p>{{issuer_name}} {{account_number}} a/n >{{account_name}}</p>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<p class="mb0">Once you've made the payment, please kindly email or send the proof of payment so we can proceed with your booking promptly</p>
|
||||
<p class="mb0">If you have any questions or need assistance, feel free to reach out to our support team at</p>
|
||||
<b>{{phone_cs}}</b>
|
||||
|
||||
<br><br>
|
||||
<b>Best Regrads,</b><br>
|
||||
<b>WEplayground</b>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- END MAIN CONTENT AREA -->
|
||||
</table>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
<div class="footer">
|
||||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td class="content-block powered-by">
|
||||
Powered by Skyworld
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!-- END FOOTER -->
|
||||
|
||||
<!-- END CENTERED WHITE CONTAINER -->
|
||||
</div>
|
||||
</td>
|
||||
<td> </td>
|
|
@ -0,0 +1,400 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>Email Confirmation</title>
|
||||
<style>
|
||||
/* -------------------------------------
|
||||
GLOBAL RESETS
|
||||
------------------------------------- */
|
||||
img {
|
||||
border: none;
|
||||
-ms-interpolation-mode: bicubic;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #f6f6f6;
|
||||
font-family: sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table td {
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
BODY & CONTAINER
|
||||
------------------------------------- */
|
||||
|
||||
.body {
|
||||
background-color: #f6f6f6;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink
|
||||
down on a phone or something */
|
||||
.container {
|
||||
display: block;
|
||||
Margin: 0 auto !important;
|
||||
/* makes it centered */
|
||||
max-width: 580px;
|
||||
padding: 10px;
|
||||
width: 580px;
|
||||
}
|
||||
|
||||
/* This should also be a block element, so that it will fill 100% of the .container */
|
||||
.content {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
Margin: 0 auto;
|
||||
max-width: 580px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
HEADER, FOOTER, MAIN
|
||||
------------------------------------- */
|
||||
.main {
|
||||
background: #ffffff;
|
||||
border-radius: 3px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.content-block {
|
||||
padding-bottom: 10px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
clear: both;
|
||||
Margin-top: 10px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.footer td,
|
||||
.footer p,
|
||||
.footer span,
|
||||
.footer a {
|
||||
color: #999999;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
TYPOGRAPHY
|
||||
------------------------------------- */
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
color: #000000;
|
||||
font-family: sans-serif;
|
||||
font-weight: 400;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
Margin-bottom: 30px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 35px;
|
||||
font-weight: 300;
|
||||
text-align: center;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
p,
|
||||
ul,
|
||||
ol {
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
Margin-bottom: 15px;
|
||||
}
|
||||
|
||||
p li,
|
||||
ul li,
|
||||
ol li {
|
||||
list-style-position: inside;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #3498db;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
BUTTONS
|
||||
------------------------------------- */
|
||||
.btn {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn>tbody>tr>td {
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.btn table {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.btn table td {
|
||||
background-color: #ffffff;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn a {
|
||||
background-color: #ffffff;
|
||||
border: solid 1px #3498db;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
color: #3498db;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
padding: 12px 25px;
|
||||
text-decoration: none;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.btn-primary table td {
|
||||
background-color: #3498db;
|
||||
}
|
||||
|
||||
.btn-primary a {
|
||||
background-color: #3498db;
|
||||
border-color: #3498db;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
OTHER STYLES THAT MIGHT BE USEFUL
|
||||
------------------------------------- */
|
||||
.last {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.first {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.align-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.align-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.clear {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.mt0 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.mb0 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.preheader {
|
||||
color: transparent;
|
||||
display: none;
|
||||
height: 0;
|
||||
max-height: 0;
|
||||
max-width: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.powered-by a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 0;
|
||||
border-bottom: 1px solid #f6f6f6;
|
||||
Margin: 20px 0;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
RESPONSIVE AND MOBILE FRIENDLY STYLES
|
||||
------------------------------------- */
|
||||
@media only screen and (max-width: 620px) {
|
||||
table[class=body] h1 {
|
||||
font-size: 28px !important;
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
|
||||
table[class=body] p,
|
||||
table[class=body] ul,
|
||||
table[class=body] ol,
|
||||
table[class=body] td,
|
||||
table[class=body] span,
|
||||
table[class=body] a {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
|
||||
table[class=body] .wrapper,
|
||||
table[class=body] .article {
|
||||
padding: 10px !important;
|
||||
}
|
||||
|
||||
table[class=body] .content {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
table[class=body] .container {
|
||||
padding: 0 !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table[class=body] .main {
|
||||
border-left-width: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
border-right-width: 0 !important;
|
||||
}
|
||||
|
||||
table[class=body] .btn table {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table[class=body] .btn a {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table[class=body] .img-responsive {
|
||||
height: auto !important;
|
||||
max-width: 100% !important;
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
PRESERVE THESE STYLES IN THE HEAD
|
||||
------------------------------------- */
|
||||
@media all {
|
||||
.ExternalClass {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ExternalClass,
|
||||
.ExternalClass p,
|
||||
.ExternalClass span,
|
||||
.ExternalClass font,
|
||||
.ExternalClass td,
|
||||
.ExternalClass div {
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
.apple-link a {
|
||||
color: inherit !important;
|
||||
font-family: inherit !important;
|
||||
font-size: inherit !important;
|
||||
font-weight: inherit !important;
|
||||
line-height: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.btn-primary table td:hover {
|
||||
background-color: #34495e !important;
|
||||
}
|
||||
|
||||
.btn-primary a:hover {
|
||||
background-color: #34495e !important;
|
||||
border-color: #34495e !important;
|
||||
}
|
||||
}
|
||||
|
||||
ol {
|
||||
padding: 0 0 0 1em;
|
||||
}
|
||||
|
||||
ol li {
|
||||
margin: 1em 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="body">
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td class="container">
|
||||
<div class="content">
|
||||
<table class="main">
|
||||
<!-- START MAIN CONTENT AREA -->
|
||||
<tr>
|
||||
<td class="wrapper">
|
||||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td>
|
||||
<p class="mb0"><b>Dear,</b></p>
|
||||
<p class="mb0">{{customer_name}}</p>
|
||||
<p>{{customer_phone}}</p>
|
||||
|
||||
<p>We hope this message finds you well!</p>
|
||||
|
||||
<p class="mb0">Uh-oh! it looks like your invoice, dated {{invoice_date}}, has officially expired as of {{expired_date}}</p>
|
||||
<p>But no worries, we can fix this together!</p>
|
||||
|
||||
<p class="mb0">To keep the good times rolling, our friendly support team is just a call away at</p>
|
||||
<b>{{phone_cs}}</b>
|
||||
|
||||
<p class="mb0">Here are the details of the expired invoice:</p>
|
||||
<p class="mb0">Booking Date: {{booking_date}}</p>
|
||||
<p>Total Invoice: {{total_payment}}</p>
|
||||
|
||||
<br><br>
|
||||
<b>Best Regrads,</b><br>
|
||||
<b>WEplayground</b>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- END MAIN CONTENT AREA -->
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -4,7 +4,7 @@
|
|||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>Email Ibunda</title>
|
||||
<title>Email Confirmation</title>
|
||||
<style>
|
||||
/* -------------------------------------
|
||||
GLOBAL RESETS
|
||||
|
@ -364,25 +364,40 @@
|
|||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td>
|
||||
<p>Halo {{customer_name}}</p>
|
||||
<p>Pemesanan tiket telah berhasil dengan nomor invoice {{invoice_code}}, Silahkan lakukan pembayaran dengan klik button dibawah ini</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"
|
||||
style="font-family: 'Lato', sans-serif; font-size:22px; color:#e5eaf5; line-height:24px; font-weight: 600;">
|
||||
<a href="{{payment_midtrans_url}}">Lanjutkan Pembayaran</a>
|
||||
<p class="mb0"><b>Dear,</b></p>
|
||||
<p class="mb0">{{customer_name}}</p>
|
||||
<p>{{customer_phone}}</p>
|
||||
|
||||
<p>Thank you fot choosing us! We're absolutelty thrilled and can't wait to embark on this exciting day with you. See you soon for fun times ahead</p>
|
||||
|
||||
<p class="mb0">Here's a quick recap of your invoice:</p>
|
||||
<p class="mb0">Booking Date: {{booking_date}}</p>
|
||||
<p>Total Invoice: {{payment_total}}</p>
|
||||
|
||||
<p class="mb0">Just a friendly reminder that your invoice will expire on {{expire_date}}</p>
|
||||
<p>To keep things running smoothly, please ensure your payment is completed before this data</p>
|
||||
|
||||
<p class="mb0">For your convenience, here is a list of our account details:</p>
|
||||
<a href="{{payment_midtrans_url}}">{{payment_midtrans_url}}</a>
|
||||
<br><br>
|
||||
|
||||
<p class="mb0">Once you've made the payment, please kindly email or send the proof of payment so we can proceed with your booking promptly</p>
|
||||
<p class="mb0">If you have any questions or need assistance, feel free to reach out to our support team at</p>
|
||||
<b>{{phone_cs}}</b>
|
||||
|
||||
<br><br>
|
||||
<b>Best Regrads,</b><br>
|
||||
<b>WEplayground</b>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- END MAIN CONTENT AREA -->
|
||||
</table>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
<div class="footer">
|
||||
<!-- <div class="footer">
|
||||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td class="content-block powered-by">
|
||||
|
@ -390,7 +405,7 @@
|
|||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div> -->
|
||||
<!-- END FOOTER -->
|
||||
|
||||
<!-- END CENTERED WHITE CONTAINER -->
|
|
@ -0,0 +1,412 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>Email Confirmation</title>
|
||||
<style>
|
||||
/* -------------------------------------
|
||||
GLOBAL RESETS
|
||||
------------------------------------- */
|
||||
img {
|
||||
border: none;
|
||||
-ms-interpolation-mode: bicubic;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #f6f6f6;
|
||||
font-family: sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table td {
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
BODY & CONTAINER
|
||||
------------------------------------- */
|
||||
|
||||
.body {
|
||||
background-color: #f6f6f6;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink
|
||||
down on a phone or something */
|
||||
.container {
|
||||
display: block;
|
||||
Margin: 0 auto !important;
|
||||
/* makes it centered */
|
||||
max-width: 580px;
|
||||
padding: 10px;
|
||||
width: 580px;
|
||||
}
|
||||
|
||||
/* This should also be a block element, so that it will fill 100% of the .container */
|
||||
.content {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
Margin: 0 auto;
|
||||
max-width: 580px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
HEADER, FOOTER, MAIN
|
||||
------------------------------------- */
|
||||
.main {
|
||||
background: #ffffff;
|
||||
border-radius: 3px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.content-block {
|
||||
padding-bottom: 10px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
clear: both;
|
||||
Margin-top: 10px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.footer td,
|
||||
.footer p,
|
||||
.footer span,
|
||||
.footer a {
|
||||
color: #999999;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
TYPOGRAPHY
|
||||
------------------------------------- */
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
color: #000000;
|
||||
font-family: sans-serif;
|
||||
font-weight: 400;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
Margin-bottom: 30px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 35px;
|
||||
font-weight: 300;
|
||||
text-align: center;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
p,
|
||||
ul,
|
||||
ol {
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
Margin-bottom: 15px;
|
||||
}
|
||||
|
||||
p li,
|
||||
ul li,
|
||||
ol li {
|
||||
list-style-position: inside;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #3498db;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
BUTTONS
|
||||
------------------------------------- */
|
||||
.btn {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn>tbody>tr>td {
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.btn table {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.btn table td {
|
||||
background-color: #ffffff;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn a {
|
||||
background-color: #ffffff;
|
||||
border: solid 1px #3498db;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
color: #3498db;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
padding: 12px 25px;
|
||||
text-decoration: none;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.btn-primary table td {
|
||||
background-color: #3498db;
|
||||
}
|
||||
|
||||
.btn-primary a {
|
||||
background-color: #3498db;
|
||||
border-color: #3498db;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
OTHER STYLES THAT MIGHT BE USEFUL
|
||||
------------------------------------- */
|
||||
.last {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.first {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.align-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.align-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.clear {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.mt0 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.mb0 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.preheader {
|
||||
color: transparent;
|
||||
display: none;
|
||||
height: 0;
|
||||
max-height: 0;
|
||||
max-width: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.powered-by a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 0;
|
||||
border-bottom: 1px solid #f6f6f6;
|
||||
Margin: 20px 0;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
RESPONSIVE AND MOBILE FRIENDLY STYLES
|
||||
------------------------------------- */
|
||||
@media only screen and (max-width: 620px) {
|
||||
table[class=body] h1 {
|
||||
font-size: 28px !important;
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
|
||||
table[class=body] p,
|
||||
table[class=body] ul,
|
||||
table[class=body] ol,
|
||||
table[class=body] td,
|
||||
table[class=body] span,
|
||||
table[class=body] a {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
|
||||
table[class=body] .wrapper,
|
||||
table[class=body] .article {
|
||||
padding: 10px !important;
|
||||
}
|
||||
|
||||
table[class=body] .content {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
table[class=body] .container {
|
||||
padding: 0 !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table[class=body] .main {
|
||||
border-left-width: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
border-right-width: 0 !important;
|
||||
}
|
||||
|
||||
table[class=body] .btn table {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table[class=body] .btn a {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table[class=body] .img-responsive {
|
||||
height: auto !important;
|
||||
max-width: 100% !important;
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
PRESERVE THESE STYLES IN THE HEAD
|
||||
------------------------------------- */
|
||||
@media all {
|
||||
.ExternalClass {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ExternalClass,
|
||||
.ExternalClass p,
|
||||
.ExternalClass span,
|
||||
.ExternalClass font,
|
||||
.ExternalClass td,
|
||||
.ExternalClass div {
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
.apple-link a {
|
||||
color: inherit !important;
|
||||
font-family: inherit !important;
|
||||
font-size: inherit !important;
|
||||
font-weight: inherit !important;
|
||||
line-height: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.btn-primary table td:hover {
|
||||
background-color: #34495e !important;
|
||||
}
|
||||
|
||||
.btn-primary a:hover {
|
||||
background-color: #34495e !important;
|
||||
border-color: #34495e !important;
|
||||
}
|
||||
}
|
||||
|
||||
ol {
|
||||
padding: 0 0 0 1em;
|
||||
}
|
||||
|
||||
ol li {
|
||||
margin: 1em 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="body">
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td class="container">
|
||||
<div class="content">
|
||||
<table class="main">
|
||||
<!-- START MAIN CONTENT AREA -->
|
||||
<tr>
|
||||
<td class="wrapper">
|
||||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td>
|
||||
<p class="mb0"><b>Dear,</b></p>
|
||||
<p class="mb0">{{customer_name}}</p>
|
||||
<p>{{customer_phone}}</p>
|
||||
|
||||
<p class="mb0">We are excited to inform you that your payment has been successfully received!</p>
|
||||
<p class="mb0">Attached to this email, you will find your confirmatin receipt</p>
|
||||
<p class="mb0">Please keep this safe as you will need to show it at the entrance upon your arrival</p>
|
||||
<p>It's your golden ticket to all the fun and excitement awaiting you!</p>
|
||||
|
||||
<br>
|
||||
<p class="mb0">Here's a quick recap:</p>
|
||||
|
||||
<p class="mb0">Booking Date: {{booking_date}}</p>
|
||||
<p class="mb0">Invoice Code: {{invoice_code}}</p>
|
||||
<p class="mb0">Payment Date: {{payment_date}}</p>
|
||||
<p class="mb0">Payment Code: {{payment_code}}</p>
|
||||
<p class="mb0">Payment Via: {{payment_via}}</p>
|
||||
<p class="mb0">Account No: {{account_no}}</p>
|
||||
<p>On Behalf Of: {{account_name}}</p>
|
||||
<br>
|
||||
|
||||
<p class="mb0">If you have any questions or need assistance, feel free to reach out to our support team at</p>
|
||||
<b>{{phone_cs}}</b>
|
||||
<br>
|
||||
|
||||
<p class="mb0">Font forget to bring a smile and your confirmation receipt (attached) for a smooth entry</p>
|
||||
<p>We can't wait to see you and ensure you have an amazing time with us!</p>
|
||||
|
||||
<br><br>
|
||||
<b>Best Regrads,</b><br>
|
||||
<b>WEplayground</b>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- END MAIN CONTENT AREA -->
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,415 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>Email Confirmation</title>
|
||||
<style>
|
||||
/* -------------------------------------
|
||||
GLOBAL RESETS
|
||||
------------------------------------- */
|
||||
img {
|
||||
border: none;
|
||||
-ms-interpolation-mode: bicubic;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #f6f6f6;
|
||||
font-family: sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table td {
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
BODY & CONTAINER
|
||||
------------------------------------- */
|
||||
|
||||
.body {
|
||||
background-color: #f6f6f6;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink
|
||||
down on a phone or something */
|
||||
.container {
|
||||
display: block;
|
||||
Margin: 0 auto !important;
|
||||
/* makes it centered */
|
||||
max-width: 580px;
|
||||
padding: 10px;
|
||||
width: 580px;
|
||||
}
|
||||
|
||||
/* This should also be a block element, so that it will fill 100% of the .container */
|
||||
.content {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
Margin: 0 auto;
|
||||
max-width: 580px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
HEADER, FOOTER, MAIN
|
||||
------------------------------------- */
|
||||
.main {
|
||||
background: #ffffff;
|
||||
border-radius: 3px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.content-block {
|
||||
padding-bottom: 10px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
clear: both;
|
||||
Margin-top: 10px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.footer td,
|
||||
.footer p,
|
||||
.footer span,
|
||||
.footer a {
|
||||
color: #999999;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
TYPOGRAPHY
|
||||
------------------------------------- */
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
color: #000000;
|
||||
font-family: sans-serif;
|
||||
font-weight: 400;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
Margin-bottom: 30px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 35px;
|
||||
font-weight: 300;
|
||||
text-align: center;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
p,
|
||||
ul,
|
||||
ol {
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
Margin-bottom: 15px;
|
||||
}
|
||||
|
||||
p li,
|
||||
ul li,
|
||||
ol li {
|
||||
list-style-position: inside;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #3498db;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
BUTTONS
|
||||
------------------------------------- */
|
||||
.btn {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn>tbody>tr>td {
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.btn table {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.btn table td {
|
||||
background-color: #ffffff;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn a {
|
||||
background-color: #ffffff;
|
||||
border: solid 1px #3498db;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
color: #3498db;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
padding: 12px 25px;
|
||||
text-decoration: none;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.btn-primary table td {
|
||||
background-color: #3498db;
|
||||
}
|
||||
|
||||
.btn-primary a {
|
||||
background-color: #3498db;
|
||||
border-color: #3498db;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
OTHER STYLES THAT MIGHT BE USEFUL
|
||||
------------------------------------- */
|
||||
.last {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.first {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.align-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.align-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.clear {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.mt0 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.mb0 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.preheader {
|
||||
color: transparent;
|
||||
display: none;
|
||||
height: 0;
|
||||
max-height: 0;
|
||||
max-width: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.powered-by a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 0;
|
||||
border-bottom: 1px solid #f6f6f6;
|
||||
Margin: 20px 0;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
RESPONSIVE AND MOBILE FRIENDLY STYLES
|
||||
------------------------------------- */
|
||||
@media only screen and (max-width: 620px) {
|
||||
table[class=body] h1 {
|
||||
font-size: 28px !important;
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
|
||||
table[class=body] p,
|
||||
table[class=body] ul,
|
||||
table[class=body] ol,
|
||||
table[class=body] td,
|
||||
table[class=body] span,
|
||||
table[class=body] a {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
|
||||
table[class=body] .wrapper,
|
||||
table[class=body] .article {
|
||||
padding: 10px !important;
|
||||
}
|
||||
|
||||
table[class=body] .content {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
table[class=body] .container {
|
||||
padding: 0 !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table[class=body] .main {
|
||||
border-left-width: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
border-right-width: 0 !important;
|
||||
}
|
||||
|
||||
table[class=body] .btn table {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table[class=body] .btn a {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table[class=body] .img-responsive {
|
||||
height: auto !important;
|
||||
max-width: 100% !important;
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
PRESERVE THESE STYLES IN THE HEAD
|
||||
------------------------------------- */
|
||||
@media all {
|
||||
.ExternalClass {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ExternalClass,
|
||||
.ExternalClass p,
|
||||
.ExternalClass span,
|
||||
.ExternalClass font,
|
||||
.ExternalClass td,
|
||||
.ExternalClass div {
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
.apple-link a {
|
||||
color: inherit !important;
|
||||
font-family: inherit !important;
|
||||
font-size: inherit !important;
|
||||
font-weight: inherit !important;
|
||||
line-height: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.btn-primary table td:hover {
|
||||
background-color: #34495e !important;
|
||||
}
|
||||
|
||||
.btn-primary a:hover {
|
||||
background-color: #34495e !important;
|
||||
border-color: #34495e !important;
|
||||
}
|
||||
}
|
||||
|
||||
ol {
|
||||
padding: 0 0 0 1em;
|
||||
}
|
||||
|
||||
ol li {
|
||||
margin: 1em 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="body">
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td class="container">
|
||||
<div class="content">
|
||||
<table class="main">
|
||||
<!-- START MAIN CONTENT AREA -->
|
||||
<tr>
|
||||
<td class="wrapper">
|
||||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td>
|
||||
<p class="mb0"><b>Dear,</b></p>
|
||||
<p class="mb0">{{customer_name}}</p>
|
||||
<p>{{customer_phone}}</p>
|
||||
|
||||
<p class="mb-0">Good News!</p>
|
||||
<p>We've successfully processed your refund for:</p>
|
||||
|
||||
<p class="mb0">Here are the details of your refund:</p>
|
||||
<p class="mb0">Transaction Date: <b>{{booking_date}}</b></p>
|
||||
<p class="mb0">Transaction Code: <b>{{invoice_code}}</b></p>
|
||||
<p class="mb0">Total Refund: <b>{{refund.refund_total}}</b></p>
|
||||
<p>{{{refund_items}}}</p>
|
||||
|
||||
<p class="mb0">Transaction Number: <b>{{invoice_code}}</b></p>
|
||||
<p class="mb0">Refund Processed Date: <b>{{refund.refund_date}}</b></p>
|
||||
<p class="mb0">Bank Account: <b>{{refund.bank_name}}</b></p>
|
||||
<p class="mb0">Account Number: <b>{{refund.bank_account_number}}</b></p>
|
||||
<p>Account Name: <b>{{refund.bank_account_name}}</b></p>
|
||||
|
||||
<p class="mb0">We hope this helps make things right, and we're here to assist if you need anything else</p>
|
||||
<p>You should see the refund in your account within 3 business days</p>
|
||||
|
||||
|
||||
<p class="mb0">Thank you for your patience and understanding</p>
|
||||
<p>If you have any questions or need further assistance, don't hesitate to reach out us at</p>
|
||||
<b>{{phone_cs}}</b>
|
||||
|
||||
<br>
|
||||
|
||||
<p class="mb0">Thank you and we can't wait to see you and make sure you have an amazing time!</p>
|
||||
|
||||
<br><br>
|
||||
<b>Best Regrads,</b><br>
|
||||
<b>WEplayground</b>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- END MAIN CONTENT AREA -->
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,409 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>Email Confirmation</title>
|
||||
<style>
|
||||
/* -------------------------------------
|
||||
GLOBAL RESETS
|
||||
------------------------------------- */
|
||||
img {
|
||||
border: none;
|
||||
-ms-interpolation-mode: bicubic;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #f6f6f6;
|
||||
font-family: sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table td {
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
BODY & CONTAINER
|
||||
------------------------------------- */
|
||||
|
||||
.body {
|
||||
background-color: #f6f6f6;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink
|
||||
down on a phone or something */
|
||||
.container {
|
||||
display: block;
|
||||
Margin: 0 auto !important;
|
||||
/* makes it centered */
|
||||
max-width: 580px;
|
||||
padding: 10px;
|
||||
width: 580px;
|
||||
}
|
||||
|
||||
/* This should also be a block element, so that it will fill 100% of the .container */
|
||||
.content {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
Margin: 0 auto;
|
||||
max-width: 580px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
HEADER, FOOTER, MAIN
|
||||
------------------------------------- */
|
||||
.main {
|
||||
background: #ffffff;
|
||||
border-radius: 3px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.content-block {
|
||||
padding-bottom: 10px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
clear: both;
|
||||
Margin-top: 10px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.footer td,
|
||||
.footer p,
|
||||
.footer span,
|
||||
.footer a {
|
||||
color: #999999;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
TYPOGRAPHY
|
||||
------------------------------------- */
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
color: #000000;
|
||||
font-family: sans-serif;
|
||||
font-weight: 400;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
Margin-bottom: 30px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 35px;
|
||||
font-weight: 300;
|
||||
text-align: center;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
p,
|
||||
ul,
|
||||
ol {
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
Margin-bottom: 15px;
|
||||
}
|
||||
|
||||
p li,
|
||||
ul li,
|
||||
ol li {
|
||||
list-style-position: inside;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #3498db;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
BUTTONS
|
||||
------------------------------------- */
|
||||
.btn {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn>tbody>tr>td {
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.btn table {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.btn table td {
|
||||
background-color: #ffffff;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn a {
|
||||
background-color: #ffffff;
|
||||
border: solid 1px #3498db;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
color: #3498db;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
padding: 12px 25px;
|
||||
text-decoration: none;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.btn-primary table td {
|
||||
background-color: #3498db;
|
||||
}
|
||||
|
||||
.btn-primary a {
|
||||
background-color: #3498db;
|
||||
border-color: #3498db;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
OTHER STYLES THAT MIGHT BE USEFUL
|
||||
------------------------------------- */
|
||||
.last {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.first {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.align-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.align-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.clear {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.mt0 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.mb0 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.preheader {
|
||||
color: transparent;
|
||||
display: none;
|
||||
height: 0;
|
||||
max-height: 0;
|
||||
max-width: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.powered-by a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 0;
|
||||
border-bottom: 1px solid #f6f6f6;
|
||||
Margin: 20px 0;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
RESPONSIVE AND MOBILE FRIENDLY STYLES
|
||||
------------------------------------- */
|
||||
@media only screen and (max-width: 620px) {
|
||||
table[class=body] h1 {
|
||||
font-size: 28px !important;
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
|
||||
table[class=body] p,
|
||||
table[class=body] ul,
|
||||
table[class=body] ol,
|
||||
table[class=body] td,
|
||||
table[class=body] span,
|
||||
table[class=body] a {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
|
||||
table[class=body] .wrapper,
|
||||
table[class=body] .article {
|
||||
padding: 10px !important;
|
||||
}
|
||||
|
||||
table[class=body] .content {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
table[class=body] .container {
|
||||
padding: 0 !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table[class=body] .main {
|
||||
border-left-width: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
border-right-width: 0 !important;
|
||||
}
|
||||
|
||||
table[class=body] .btn table {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table[class=body] .btn a {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table[class=body] .img-responsive {
|
||||
height: auto !important;
|
||||
max-width: 100% !important;
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
PRESERVE THESE STYLES IN THE HEAD
|
||||
------------------------------------- */
|
||||
@media all {
|
||||
.ExternalClass {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ExternalClass,
|
||||
.ExternalClass p,
|
||||
.ExternalClass span,
|
||||
.ExternalClass font,
|
||||
.ExternalClass td,
|
||||
.ExternalClass div {
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
.apple-link a {
|
||||
color: inherit !important;
|
||||
font-family: inherit !important;
|
||||
font-size: inherit !important;
|
||||
font-weight: inherit !important;
|
||||
line-height: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.btn-primary table td:hover {
|
||||
background-color: #34495e !important;
|
||||
}
|
||||
|
||||
.btn-primary a:hover {
|
||||
background-color: #34495e !important;
|
||||
border-color: #34495e !important;
|
||||
}
|
||||
}
|
||||
|
||||
ol {
|
||||
padding: 0 0 0 1em;
|
||||
}
|
||||
|
||||
ol li {
|
||||
margin: 1em 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="body">
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td class="container">
|
||||
<div class="content">
|
||||
<table class="main">
|
||||
<!-- START MAIN CONTENT AREA -->
|
||||
<tr>
|
||||
<td class="wrapper">
|
||||
<table border="0" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td>
|
||||
<p class="mb0"><b>Dear,</b></p>
|
||||
<p class="mb0">{{customer_name}}</p>
|
||||
<p>{{customer_phone}}</p>
|
||||
|
||||
<p class="mb0">We're trully sorry for any inconvenience that led to this request</p>
|
||||
<p>We've received your refund request for :</p>
|
||||
|
||||
<p class="mb0">Transaction Date: <b>{{booking_date}}</b></p>
|
||||
<p class="mb0">Transaction Code: <b>{{invoice_code}}</b></p>
|
||||
<p class="mb0">Refund Code: <b>{{refund.code}}</b></p>
|
||||
<p class="mb0">Total Refund: <b>{{refund.refund_total}}</b></p>
|
||||
<p>{{{refund_items}}}</p>
|
||||
|
||||
<p class="mb0">Your satisfaction is important to us, and we're commited to resolving this as quickly as possible</p>
|
||||
<p class="mb0">Our team is already on it and will process your refund request promptly</p>
|
||||
<p>We'll keep you updated and notify you once the refund has been processed</p>
|
||||
|
||||
<p class="mb0">If you have any questions or need assistance, feel free to reach out to our support team at</p>
|
||||
<b>{{phone_cs}}</b>
|
||||
|
||||
<br>
|
||||
|
||||
<p class="mb0">Thank you for your patience and understanding</p>
|
||||
<p>We appriciate your feedback and are here to make things right</p>
|
||||
|
||||
<br><br>
|
||||
<b>Best Regrads,</b><br>
|
||||
<b>WEplayground</b>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- END MAIN CONTENT AREA -->
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -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<TransactionChangeStatusEvent>
|
||||
{
|
||||
|
@ -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: `
|
||||
<p>Refund Items:</p>
|
||||
<ul style="padding-left:15px">
|
||||
${transaction?.['refund']?.refund_items
|
||||
?.filter((item) => Number(item.qty_refund) > 0)
|
||||
.map((item) => {
|
||||
return `
|
||||
<li>${item.qty_refund} ${item.transaction_item.item_name} </li>
|
||||
`;
|
||||
})}
|
||||
</ul>`,
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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<TransactionEntity> {
|
||||
get entityTarget(): any {
|
||||
return TransactionModel;
|
||||
}
|
||||
|
||||
getResult() {
|
||||
return [];
|
||||
}
|
||||
|
||||
get eventTopics(): EventTopics[] {
|
||||
return;
|
||||
}
|
||||
|
||||
validateProcess(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
beforeProcess(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
async process(): Promise<void> {
|
||||
const transaction = await this.dataService.getOneByOptions({
|
||||
where: {
|
||||
id: this.data.id,
|
||||
},
|
||||
relations: ['items'],
|
||||
});
|
||||
|
||||
PdfMaker(transaction);
|
||||
return;
|
||||
}
|
||||
|
||||
afterProcess(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
}
|
|
@ -19,6 +19,13 @@ export class UpdateTransactionManager extends BaseUpdateManager<TransactionEntit
|
|||
|
||||
async beforeProcess(): Promise<void> {
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<any> {
|
||||
// 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<any> {
|
||||
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<string> {
|
||||
this.deleteManager.setData(dataId);
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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<any> {
|
||||
// return await this.orchestrator.invoice(dataId);
|
||||
// }
|
||||
@Put('/:id/invoice/download')
|
||||
async invoiceDownload(
|
||||
@Param('id') dataId: string,
|
||||
@Body() body: DownloadPdfDto,
|
||||
@Res() res: Response,
|
||||
): Promise<any> {
|
||||
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<BatchResult> {
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue