feat: Enhance host agreement generation with AWS S3 integration and document creation capabilities using pdf-lib and docx libraries. Added new dependencies for document processing and improved address and duration handling.
This commit is contained in:
@@ -28,6 +28,13 @@ import {
|
||||
import ApiError from '../../../common/utils/helper/ApiError';
|
||||
import { hostCompanyDetailsSchema } from '../../../common/utils/validation/host/hostCompanyDetails.validation';
|
||||
import config from '../../../config/config';
|
||||
import AWS from 'aws-sdk';
|
||||
import dayjs from 'dayjs';
|
||||
import { toWords } from 'number-to-words';
|
||||
import { PDFDocument, StandardFonts, rgb } from 'pdf-lib';
|
||||
import { Document, Packer, Paragraph, TextRun } from 'docx';
|
||||
import PizZip from 'pizzip';
|
||||
import Docxtemplater from 'docxtemplater';
|
||||
import { CreateActivityInput } from '../dto/createActivity.schema';
|
||||
import {
|
||||
AddPaymentDetailsDTO,
|
||||
@@ -105,6 +112,260 @@ function computeBasePriceAndTaxes(
|
||||
const normalize = (v?: string | null) =>
|
||||
v ? v.trim().toLowerCase() : null;
|
||||
|
||||
const s3 = new AWS.S3({ region: config.aws.region });
|
||||
|
||||
async function getTemplateFromS3() {
|
||||
const obj = await s3
|
||||
.getObject({
|
||||
Bucket: config.aws.bucketName,
|
||||
Key: 'Documents/MinglarAdmin/AgreementDoc/CopyofMinglarHostAgreement.docx',
|
||||
})
|
||||
.promise();
|
||||
|
||||
return obj.Body as Buffer;
|
||||
}
|
||||
|
||||
async function generateDocxFromTemplate(buffer: Buffer, data: any) {
|
||||
const zip = new PizZip(buffer);
|
||||
const doc = new Docxtemplater(zip);
|
||||
|
||||
doc.setData({
|
||||
companyName: data.companyName,
|
||||
companyType: data.companyType,
|
||||
fullAddress: data.fullAddress,
|
||||
effectiveDate: data.effectiveDate,
|
||||
expiryDate: data.expiryDate,
|
||||
durationText: data.durationText,
|
||||
commissionText: data.commissionText,
|
||||
acceptDate: data.acceptDate,
|
||||
});
|
||||
|
||||
doc.render();
|
||||
|
||||
return doc.getZip().generate({ type: 'nodebuffer' });
|
||||
}
|
||||
|
||||
function buildFullAddress(host: any) {
|
||||
return [
|
||||
host.address1,
|
||||
host.address2,
|
||||
host.cities?.cityName || host.cities?.name,
|
||||
host.states?.stateName || host.states?.name,
|
||||
host.countries?.countryName || host.countries?.name,
|
||||
host.pinCode,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(', ');
|
||||
}
|
||||
|
||||
function buildDurationText(host: any) {
|
||||
if (!host.durationNumber || !host.durationFrequency) {
|
||||
throw new ApiError(400, 'Duration not configured');
|
||||
}
|
||||
const numberWord = toWords(host.durationNumber);
|
||||
const freq =
|
||||
host.durationFrequency === 'month' && host.durationNumber > 1
|
||||
? 'months'
|
||||
: host.durationFrequency === 'year' && host.durationNumber > 1
|
||||
? 'years'
|
||||
: host.durationFrequency;
|
||||
|
||||
return `${numberWord} (${host.durationNumber}) ${freq}`;
|
||||
}
|
||||
|
||||
function buildExpiryDate(host: any) {
|
||||
if (!host.agreementStartDate) {
|
||||
throw new ApiError(400, 'Agreement start date missing');
|
||||
}
|
||||
|
||||
return dayjs(host.agreementStartDate)
|
||||
.add(host.durationNumber, host.durationFrequency)
|
||||
.format('DD-MMM-YY');
|
||||
}
|
||||
|
||||
function buildCommissionText(host: any) {
|
||||
if (host.isCommisionBase) {
|
||||
if (!host.commisionPer) {
|
||||
throw new ApiError(400, 'Commission % missing');
|
||||
}
|
||||
return `${host.commisionPer}% commission`;
|
||||
}
|
||||
|
||||
if (!host.amountPerBooking) {
|
||||
throw new ApiError(400, 'Per booking amount missing');
|
||||
}
|
||||
|
||||
return `₹${host.amountPerBooking} per booking`;
|
||||
}
|
||||
|
||||
async function generateAgreementPdfBuffer(vars: {
|
||||
effectiveDate: string;
|
||||
companyName: string;
|
||||
companyType?: string | null;
|
||||
fullAddress: string;
|
||||
durationText: string;
|
||||
expiryDate: string;
|
||||
commissionText: string;
|
||||
acceptDate: string;
|
||||
}) {
|
||||
const pdfDoc = await PDFDocument.create();
|
||||
const page = pdfDoc.addPage([595, 842]); // A4
|
||||
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
|
||||
const fontBold = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
|
||||
|
||||
const { width, height } = page.getSize();
|
||||
const margin = 50;
|
||||
let cursorY = height - margin;
|
||||
const lineHeight = 18;
|
||||
|
||||
const drawLine = (text: string, bold = false) => {
|
||||
page.drawText(text, {
|
||||
x: margin,
|
||||
y: cursorY,
|
||||
size: 12,
|
||||
font: bold ? fontBold : font,
|
||||
color: rgb(0, 0, 0),
|
||||
});
|
||||
cursorY -= lineHeight;
|
||||
};
|
||||
|
||||
// Title
|
||||
page.drawText('HOST AGREEMENT', {
|
||||
x: margin,
|
||||
y: cursorY,
|
||||
size: 16,
|
||||
font: fontBold,
|
||||
color: rgb(0, 0, 0),
|
||||
});
|
||||
cursorY -= lineHeight * 2;
|
||||
|
||||
drawLine(`Effective Date: ${vars.effectiveDate}`, true);
|
||||
drawLine(`Host Legal Name: ${vars.companyName}`);
|
||||
if (vars.companyType) {
|
||||
drawLine(`Company Type: ${vars.companyType}`);
|
||||
}
|
||||
drawLine(`Address: ${vars.fullAddress}`);
|
||||
cursorY -= lineHeight;
|
||||
|
||||
drawLine(`Agreement Duration: ${vars.durationText}`);
|
||||
drawLine(`Expiry Date: ${vars.expiryDate}`);
|
||||
drawLine(`Commercials: ${vars.commissionText}`);
|
||||
cursorY -= lineHeight * 2;
|
||||
|
||||
drawLine('Host:', true);
|
||||
drawLine(vars.companyName);
|
||||
drawLine(`Date of Acceptance: ${vars.acceptDate}`);
|
||||
|
||||
const pdfBytes = await pdfDoc.save();
|
||||
return Buffer.from(pdfBytes);
|
||||
}
|
||||
|
||||
async function generateAgreementDocxBuffer(vars: {
|
||||
effectiveDate: string;
|
||||
companyName: string;
|
||||
companyType?: string | null;
|
||||
fullAddress: string;
|
||||
durationText: string;
|
||||
expiryDate: string;
|
||||
commissionText: string;
|
||||
acceptDate: string;
|
||||
}) {
|
||||
const paragraphs: Paragraph[] = [];
|
||||
|
||||
paragraphs.push(
|
||||
new Paragraph({
|
||||
children: [
|
||||
new TextRun({
|
||||
text: 'HOST AGREEMENT',
|
||||
bold: true,
|
||||
size: 32,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
paragraphs.push(
|
||||
new Paragraph({
|
||||
children: [
|
||||
new TextRun({
|
||||
text: `Effective Date: ${vars.effectiveDate}`,
|
||||
bold: true,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
paragraphs.push(
|
||||
new Paragraph({
|
||||
children: [
|
||||
new TextRun(`Host Legal Name: ${vars.companyName}`),
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
if (vars.companyType) {
|
||||
paragraphs.push(
|
||||
new Paragraph({
|
||||
children: [new TextRun(`Company Type: ${vars.companyType}`)],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
paragraphs.push(
|
||||
new Paragraph({
|
||||
children: [new TextRun(`Address: ${vars.fullAddress}`)],
|
||||
}),
|
||||
);
|
||||
|
||||
paragraphs.push(
|
||||
new Paragraph({
|
||||
children: [new TextRun(`Agreement Duration: ${vars.durationText}`)],
|
||||
}),
|
||||
);
|
||||
|
||||
paragraphs.push(
|
||||
new Paragraph({
|
||||
children: [new TextRun(`Expiry Date: ${vars.expiryDate}`)],
|
||||
}),
|
||||
);
|
||||
|
||||
paragraphs.push(
|
||||
new Paragraph({
|
||||
children: [new TextRun(`Commercials: ${vars.commissionText}`)],
|
||||
}),
|
||||
);
|
||||
|
||||
paragraphs.push(
|
||||
new Paragraph({
|
||||
children: [new TextRun({ text: 'Host:', bold: true })],
|
||||
}),
|
||||
);
|
||||
|
||||
paragraphs.push(
|
||||
new Paragraph({
|
||||
children: [new TextRun(vars.companyName)],
|
||||
}),
|
||||
);
|
||||
|
||||
paragraphs.push(
|
||||
new Paragraph({
|
||||
children: [new TextRun(`Date of Acceptance: ${vars.acceptDate}`)],
|
||||
}),
|
||||
);
|
||||
|
||||
const doc = new Document({
|
||||
sections: [
|
||||
{
|
||||
properties: {},
|
||||
children: paragraphs,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const buffer = await Packer.toBuffer(doc);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
const findOrCreateCountry = async (
|
||||
tx: any,
|
||||
countryName?: string | null,
|
||||
@@ -685,20 +946,103 @@ export class HostService {
|
||||
}
|
||||
|
||||
async acceptMinglarAgreement(user_xid: number) {
|
||||
const hostDetails = await this.prisma.hostHeader.findFirst({
|
||||
where: { userXid: user_xid },
|
||||
select: {
|
||||
id: true,
|
||||
userXid: true,
|
||||
const host = await this.prisma.hostHeader.findFirst({
|
||||
where: { userXid: user_xid, isActive: true },
|
||||
include: {
|
||||
cities: true,
|
||||
states: true,
|
||||
countries: true,
|
||||
companyTypes: true,
|
||||
},
|
||||
});
|
||||
await this.prisma.hostHeader.update({
|
||||
where: { id: hostDetails.id },
|
||||
data: {
|
||||
stepper: STEPPER.AGREEMENT_ACCEPTED,
|
||||
isApproved: true,
|
||||
agreementAccepted: true,
|
||||
},
|
||||
|
||||
if (!host) {
|
||||
throw new ApiError(404, 'Host not found for this user');
|
||||
}
|
||||
|
||||
const effectiveDate = host.agreementStartDate
|
||||
? dayjs(host.agreementStartDate).format('DD-MMM-YY')
|
||||
: dayjs().format('DD-MMM-YY');
|
||||
|
||||
const durationText = buildDurationText(host);
|
||||
const expiryDate = buildExpiryDate(host);
|
||||
const commissionText = buildCommissionText(host);
|
||||
const fullAddress = buildFullAddress(host);
|
||||
const acceptDate = dayjs().format('DD-MMM-YYYY');
|
||||
|
||||
const agreementVars = {
|
||||
effectiveDate,
|
||||
companyName: host.companyName,
|
||||
companyType: host.companyTypes?.companyTypeName,
|
||||
fullAddress,
|
||||
durationText,
|
||||
expiryDate,
|
||||
commissionText,
|
||||
acceptDate,
|
||||
};
|
||||
|
||||
const templateBuffer = await getTemplateFromS3();
|
||||
const docxBuffer = await generateDocxFromTemplate(templateBuffer, agreementVars);
|
||||
const pdfBuffer = await generateAgreementPdfBuffer(agreementVars);
|
||||
|
||||
const existingCount = await this.prisma.hostAgreement.count({
|
||||
where: { hostXid: host.id, isActive: true },
|
||||
});
|
||||
|
||||
const nextVersionNumber = `AG${existingCount + 1}`;
|
||||
const baseKey = `Documents/Host/${host.id}/agreements/${nextVersionNumber}`;
|
||||
|
||||
const docxKey = `${baseKey}.docx`;
|
||||
const pdfKey = `${baseKey}.pdf`;
|
||||
|
||||
await s3
|
||||
.upload({
|
||||
Bucket: config.aws.bucketName,
|
||||
Key: docxKey,
|
||||
Body: docxBuffer,
|
||||
ContentType:
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
ACL: 'private',
|
||||
})
|
||||
.promise();
|
||||
|
||||
await s3
|
||||
.upload({
|
||||
Bucket: config.aws.bucketName,
|
||||
Key: pdfKey,
|
||||
Body: pdfBuffer,
|
||||
ContentType: 'application/pdf',
|
||||
ACL: 'private',
|
||||
})
|
||||
.promise();
|
||||
|
||||
const pdfUrl = `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${pdfKey}`;
|
||||
|
||||
await this.prisma.$transaction(async (tx) => {
|
||||
// Optional: mark previous agreements inactive
|
||||
await tx.hostAgreement.updateMany({
|
||||
where: { hostXid: host.id, isActive: true },
|
||||
data: { isActive: false },
|
||||
});
|
||||
|
||||
await tx.hostAgreement.create({
|
||||
data: {
|
||||
hostXid: host.id,
|
||||
filePath: pdfUrl,
|
||||
versionNumber: nextVersionNumber,
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
|
||||
await tx.hostHeader.update({
|
||||
where: { id: host.id },
|
||||
data: {
|
||||
stepper: STEPPER.AGREEMENT_ACCEPTED,
|
||||
isApproved: true,
|
||||
agreementAccepted: true,
|
||||
agreementStartDate: host.agreementStartDate || new Date(),
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user