first commit
This commit is contained in:
@@ -0,0 +1,372 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background: white;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.sheet {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border: 1px solid #222;
|
||||
}
|
||||
|
||||
.company-band {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1.35fr 0.95fr;
|
||||
min-height: 112px;
|
||||
border-bottom: 1px solid #444;
|
||||
}
|
||||
|
||||
.company-left,
|
||||
.company-middle,
|
||||
.company-right {
|
||||
padding: 4px 6px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.company-left {
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.company-middle {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.logo-placeholder {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 6px;
|
||||
color: #1c5c9d;
|
||||
}
|
||||
|
||||
.company-middle .tagline {
|
||||
margin-top: 4px;
|
||||
font-size: 10px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.company-right {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-end;
|
||||
text-align: right;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.company-right .stack {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.title-row {
|
||||
border-bottom: 1px solid #444;
|
||||
text-align: center;
|
||||
padding: 5px 0 4px;
|
||||
color: #7da3de;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.bill-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1.55fr 1fr;
|
||||
border-bottom: 1px solid #444;
|
||||
min-height: 72px;
|
||||
}
|
||||
|
||||
.bill-left,
|
||||
.bill-right {
|
||||
padding: 4px 6px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.bill-left {
|
||||
border-right: 1px solid #444;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.bill-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 2px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.bill-line {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.meta-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
border-bottom: 1px solid #444;
|
||||
}
|
||||
|
||||
.meta-col {
|
||||
padding: 4px 6px;
|
||||
font-size: 10px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.meta-col:first-child {
|
||||
border-right: 1px solid #444;
|
||||
}
|
||||
|
||||
.meta-line {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.meta-label {
|
||||
font-weight: 700;
|
||||
min-width: 112px;
|
||||
}
|
||||
|
||||
.items-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.items-table th,
|
||||
.items-table td {
|
||||
border: 1px solid #444;
|
||||
font-size: 10px;
|
||||
padding: 2px 3px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.items-table th {
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
background: #b7d2ea;
|
||||
}
|
||||
|
||||
.no-col { width: 5%; }
|
||||
.desc-col { width: 37%; }
|
||||
.remarks-col { width: 33%; }
|
||||
.amount-col { width: 12%; }
|
||||
.confirm-col { width: 13%; }
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.spacer-row td {
|
||||
height: 26px;
|
||||
}
|
||||
|
||||
.total-row td {
|
||||
font-weight: 700;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.approval-area {
|
||||
height: 94px;
|
||||
border-top: 1px solid #444;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-end;
|
||||
padding: 10px 12px 14px;
|
||||
}
|
||||
|
||||
.approval-box {
|
||||
text-align: center;
|
||||
min-width: 230px;
|
||||
}
|
||||
|
||||
.signature-line {
|
||||
margin-top: 18px;
|
||||
border-top: 1px solid #222;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.footer-row {
|
||||
border-top: 1px solid #444;
|
||||
text-align: center;
|
||||
padding: 4px 0 5px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.watermark-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.line-dots {
|
||||
letter-spacing: 3px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.company-logo-text {
|
||||
text-align: center;
|
||||
color: #1c5c9d;
|
||||
font-size: 14px;
|
||||
line-height: 1.1;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.company-logo-small {
|
||||
font-size: 10px;
|
||||
font-weight: 400;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.header-stack {
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.small-note {
|
||||
font-size: 9px;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
background: #ffeb3b;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
.multi-line {
|
||||
white-space: pre-line;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<%
|
||||
const creditCompany = typeof company !== 'undefined' && company ? company : {};
|
||||
const creditHeader = typeof header !== 'undefined' && header ? header : {};
|
||||
const creditBillTo = typeof billTo !== 'undefined' && billTo ? billTo : {};
|
||||
const creditMeta = typeof meta !== 'undefined' && meta ? meta : {};
|
||||
const creditVessel = typeof vessel !== 'undefined' && vessel ? vessel : {};
|
||||
const creditOrderMeta = typeof orderMeta !== 'undefined' && orderMeta ? orderMeta : {};
|
||||
const creditItems = typeof items !== 'undefined' && Array.isArray(items) ? items : [];
|
||||
const creditApprovedByLabel = typeof approvedByLabel !== 'undefined' && approvedByLabel ? approvedByLabel : 'Approved by GLMS Finance Dept.';
|
||||
const creditDepartmentLabel = typeof departmentLabel !== 'undefined' && departmentLabel ? departmentLabel : 'GLMS - Accounts Department';
|
||||
%>
|
||||
<div class="sheet">
|
||||
<div class="company-band">
|
||||
<div class="company-left">
|
||||
<div><strong><%= creditCompany.name || 'GREEK-LANKA MARITIME SERVICES (PVT) LTD' %></strong></div>
|
||||
<div><%= creditCompany.addressLine1 || '' %></div>
|
||||
<div><%= creditCompany.addressLine2 || '' %></div>
|
||||
<div><%= creditCompany.addressLine3 || '' %></div>
|
||||
<div><%= creditCompany.postal || '' %></div>
|
||||
<div><%= creditCompany.contact || '' %></div>
|
||||
<div><%= creditCompany.br || '' %></div>
|
||||
<div><%= creditCompany.license || '' %></div>
|
||||
</div>
|
||||
<div class="company-middle">
|
||||
<div>
|
||||
<div class="company-logo-text">GREEK<br/>LANKA</div>
|
||||
<div class="company-logo-small">We Serve the Ocean</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="company-right">
|
||||
<div class="stack">
|
||||
<div class="header-stack">
|
||||
<div style="font-size: 14px; font-weight:700; color:#7da3de; letter-spacing:0.5px;">CREDIT NOTE</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="title-row"><%= creditHeader.title || 'CREDIT NOTE' %></div>
|
||||
|
||||
<div class="bill-row">
|
||||
<div class="bill-left">
|
||||
<div><strong>Bill To :</strong> <%= creditBillTo.name || 'Master & Owner of MV TEAM VENTURES' %></div>
|
||||
<div class="multi-line"><%- (creditBillTo.details || '').replace(/\n/g, '<br/>') || 'VRIDHI MARITIME SHIP MANAGEMENT & OPERATION LLC<br/>Office No: 2004, The Prism Tower, Dubai' %></div>
|
||||
</div>
|
||||
<div class="bill-right">
|
||||
<div class="bill-line"><span><strong>CRN NO :</strong></span><span><%= creditMeta.crnNo || 'N/A' %></span></div>
|
||||
<div class="bill-line"><span><strong>DATE :</strong></span><span><%= creditMeta.date || 'N/A' %></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="meta-row">
|
||||
<div class="meta-col">
|
||||
<div class="meta-line"><span class="meta-label">Vessel Name :</span> <span><%= creditVessel.name || 'N/A' %></span></div>
|
||||
<div class="meta-line"><span class="meta-label">Port :</span> <span><%= creditVessel.port || 'N/A' %></span></div>
|
||||
<div class="meta-line"><span class="meta-label">Name of Agent :</span> <span><%= creditVessel.agent || 'N/A' %></span></div>
|
||||
</div>
|
||||
<div class="meta-col">
|
||||
<div class="meta-line"><span class="meta-label">GRT :</span> <span><%= creditVessel.grt || 'N/A' %></span></div>
|
||||
<div class="meta-line"><span class="meta-label">IMO Number :</span> <span><%= creditVessel.imo || 'N/A' %></span></div>
|
||||
<div class="meta-line"><span class="meta-label">Port/Country :</span> <span><%= creditVessel.portCountry || 'N/A' %></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="overflow-x:auto;">
|
||||
<table class="items-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="no-col">DESCRIPTION</th>
|
||||
<th class="amount-col">AMOUNT</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% if (creditItems.length > 0) { %>
|
||||
<% creditItems.forEach((item, index) => { %>
|
||||
<tr class="spacer-row">
|
||||
<td>
|
||||
<%= item.description || item.desc || '' %>
|
||||
<% if (item.note) { %>
|
||||
<div class="small-note"><%= item.note %></div>
|
||||
<% } %>
|
||||
</td>
|
||||
<td class="right"><%= (typeof item.amount !== 'undefined') ? item.amount : '' %></td>
|
||||
</tr>
|
||||
<% }); %>
|
||||
<% } %>
|
||||
<tr class="total-row">
|
||||
<td class="right">Total Amount</td>
|
||||
<td class="right"><%= total || totalAmount || 0 %></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="approval-area">
|
||||
<div class="approval-box">
|
||||
<div style="height: 42px;"></div>
|
||||
<div class="signature-line"></div>
|
||||
<div style="margin-top: 4px;"><%= creditApprovedByLabel %></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-row">
|
||||
<%= creditDepartmentLabel %>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Niolla
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is proprietary and confidential.
|
||||
* Unauthorized copying, modification, distribution, or use
|
||||
* of this file, via any medium, is strictly prohibited.
|
||||
*/
|
||||
|
||||
// app/templates/documents/creditNote/creditNote.template.js
|
||||
|
||||
const { log } = require("../../../utils/consoleLog.utill");
|
||||
|
||||
module.exports = async (data) => {
|
||||
log("📋 Preparing Credit Note data for PDF...");
|
||||
|
||||
let payload = data;
|
||||
|
||||
if (typeof payload === "string") {
|
||||
try {
|
||||
payload = JSON.parse(payload);
|
||||
} catch (error) {
|
||||
payload = {};
|
||||
}
|
||||
}
|
||||
|
||||
const source = payload.documentData || payload.document_data || payload.data || payload;
|
||||
|
||||
const pick = (...keys) => {
|
||||
for (const key of keys) {
|
||||
const value = source?.[key];
|
||||
if (value !== undefined && value !== null && value !== "") {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
};
|
||||
|
||||
// Normalize incoming fields and provide defaults matching the screenshot
|
||||
const company = {
|
||||
name: pick("companyName", "company_name") || "GREEK-LANKA MARITIME SERVICES (PVT) LTD",
|
||||
addressLine1: pick("companyAddressLine1", "company_address_line1") || "No. 56/2, Dharmapala Mw, Kotte",
|
||||
addressLine2: pick("companyAddressLine2", "company_address_line2") || "Sri Jayewardenepura",
|
||||
addressLine3: pick("companyAddressLine3", "company_address_line3") || "Sri Lanka",
|
||||
postal: pick("companyPostal", "company_postal") || "Postal Code : 10100",
|
||||
contact: pick("companyContact", "company_contact") || "Tel:+94 11 2083206 / Mobile (24/7) : +94 777 232 271",
|
||||
br: pick("companyBR", "company_br") || "BR No : PV 0022630",
|
||||
license: pick("companyLicense", "company_license") || "License No : SA00317-2024",
|
||||
logoUrl: pick("logoUrl", "logo_url") ||
|
||||
"https://res.cloudinary.com/dtthuvvir/image/upload/v1780748661/company_logo_transperent_bg_uullzw.png",
|
||||
};
|
||||
|
||||
const header = {
|
||||
title: pick("title") || "CREDIT NOTE",
|
||||
};
|
||||
|
||||
const billTo = {
|
||||
name: pick("billToName", "bill_to_name") || "Master & Owner of MV TEAM VENTURES",
|
||||
details: pick("billToDetails", "bill_to_details") || "VRIDHI MARITIME SHIP MANAGEMENT & OPERATION LLC\nOffice No: 2004, The Prism Tower, Dubai",
|
||||
};
|
||||
|
||||
const meta = {
|
||||
crnNo: pick("crnNo", "crn_no", "creditNoteNumber", "credit_note_number", "reference_no") || "GLMS/COLOMBO/474/CRN01",
|
||||
date: pick("date") || new Date().toLocaleDateString(),
|
||||
};
|
||||
|
||||
const vessel = {
|
||||
name: pick("vesselName", "vessel_name") || 'MV "TEAM VENTURES"',
|
||||
grt: pick("grt", "GRT") || "25,543 MT",
|
||||
port: pick("port", "port_name") || "REPAIRS AT COLOMBO DOCK YARD",
|
||||
imo: pick("imo", "imo_number", "imoNumber") || "9339765",
|
||||
agent: pick("nameOfAgent", "name_of_agent", "agent") || "",
|
||||
portCountry: pick("portCountry", "port_country") || "COLOMBO / SRI LANKA",
|
||||
};
|
||||
|
||||
const orderMeta = {
|
||||
orderDate: pick("orderDate", "order_date") || meta.date,
|
||||
orderNo: pick("orderNo", "order_no") || "",
|
||||
portReference: pick("portReference", "port_reference") || "",
|
||||
};
|
||||
|
||||
// Items: array of { description, amount }
|
||||
const items = (Array.isArray(source.items) ? source.items : []).map((it) => ({
|
||||
description: it.description || it.desc || it.item_name || '',
|
||||
amount: Number(it.amount || it.value || it.total || it.totalAmount || 0),
|
||||
quantity: it.quantity ?? it.qty ?? '',
|
||||
note: it.note || it.notes || '',
|
||||
}));
|
||||
|
||||
const computedTotal = items.reduce((s, i) => s + (i.amount || 0), 0);
|
||||
|
||||
const templateData = {
|
||||
company,
|
||||
header,
|
||||
billTo,
|
||||
meta,
|
||||
vessel,
|
||||
orderMeta,
|
||||
items,
|
||||
totalAmount: Number(pick("totalAmount", "total") || computedTotal),
|
||||
approvedBy: pick("approvedBy", "approved_by") || 'GLMS Finance Dept.',
|
||||
approvedByLabel: pick("approvedByLabel", "approved_by_label") || 'Approved by GLMS Finance Dept.',
|
||||
departmentLabel: pick("departmentLabel", "department_label") || 'GLMS - Accounts Department',
|
||||
};
|
||||
|
||||
// Top-level conveniences for templates and pdfRenderer
|
||||
templateData.logoUrl = company.logoUrl;
|
||||
templateData.creditNoteNumber = templateData.meta.crnNo;
|
||||
templateData.date = templateData.meta.date;
|
||||
templateData.billToName = templateData.billTo.name;
|
||||
templateData.billToDetails = templateData.billTo.details;
|
||||
templateData.vessel = templateData.vessel;
|
||||
// Provide invoiceNumber if present
|
||||
templateData.invoiceNumber = pick("invoiceNumber", "invoice_number", "invoice_no") || '';
|
||||
|
||||
// Provide subtotal/tax/discount/total for EJS summary table compatibility
|
||||
templateData.subtotal = Number(pick("subtotal", "sub_total") || computedTotal);
|
||||
templateData.tax = Number(pick("tax") || 0);
|
||||
templateData.discount = Number(pick("discount") || 0);
|
||||
templateData.total = Number(pick("totalAmount", "total") || templateData.subtotal + templateData.tax - templateData.discount);
|
||||
// Provide reason / notes
|
||||
templateData.reason = pick("reason", "remarks", "notes") || '';
|
||||
// Provide paymentTerms and notes for EJS
|
||||
templateData.paymentTerms = pick("paymentTerms", "payment_terms") || '';
|
||||
templateData.notes = pick("notes", "note") || '';
|
||||
|
||||
log("✅ Credit Note data prepared", JSON.stringify(templateData));
|
||||
|
||||
return templateData;
|
||||
};
|
||||
@@ -0,0 +1,266 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #fff;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.sheet {
|
||||
width: 100%;
|
||||
border: 1px solid #222;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.top-band {
|
||||
display: grid;
|
||||
grid-template-columns: 1.45fr 1fr;
|
||||
border-bottom: 1px solid #444;
|
||||
min-height: 76px;
|
||||
}
|
||||
|
||||
.top-left,
|
||||
.top-right {
|
||||
padding: 4px 6px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.top-right {
|
||||
text-align: right;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.top-line {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.highlight-box {
|
||||
display: grid;
|
||||
grid-template-columns: 1.6fr 1fr;
|
||||
border-bottom: 1px solid #444;
|
||||
}
|
||||
|
||||
.highlight-left,
|
||||
.highlight-right {
|
||||
padding: 4px 6px;
|
||||
min-height: 58px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.highlight-left {
|
||||
border-right: 1px solid #444;
|
||||
}
|
||||
|
||||
.highlight-row {
|
||||
display: grid;
|
||||
grid-template-columns: 155px 1fr;
|
||||
gap: 6px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.permission-line {
|
||||
text-align: center;
|
||||
padding: 10px 8px 4px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.dots {
|
||||
text-align: center;
|
||||
letter-spacing: 3px;
|
||||
color: #555;
|
||||
padding: 2px 0 8px;
|
||||
}
|
||||
|
||||
.company-name {
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
padding: 6px 0 10px;
|
||||
}
|
||||
|
||||
.company-subtitle {
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.date-row {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
border-bottom: 1px solid #444;
|
||||
padding: 0 6px 4px;
|
||||
}
|
||||
|
||||
.date-box {
|
||||
min-width: 138px;
|
||||
background: #ffeb3b;
|
||||
font-weight: 700;
|
||||
padding: 2px 8px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.form-code {
|
||||
font-size: 9px;
|
||||
padding: 2px 4px 3px;
|
||||
}
|
||||
|
||||
.items-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.items-table th,
|
||||
.items-table td {
|
||||
border: 1px solid #444;
|
||||
font-size: 10px;
|
||||
padding: 2px 3px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.items-table th {
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.items-table thead th {
|
||||
height: 33px;
|
||||
}
|
||||
|
||||
.no-col { width: 8%; }
|
||||
.qty-col { width: 7%; }
|
||||
.unit-col { width: 7%; }
|
||||
.description-col { width: 40%; }
|
||||
.rate-col { width: 13%; }
|
||||
.total-col { width: 12%; }
|
||||
.confirm-col { width: 13%; }
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.spacer-row td {
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.muted {
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<%
|
||||
const customTitle = typeof title !== 'undefined' && title ? title : 'CUSTOM DOCUMENT';
|
||||
const customDate = typeof date !== 'undefined' && date ? date : 'N/A';
|
||||
const customTelePhone = typeof telePhone !== 'undefined' && telePhone ? telePhone : '';
|
||||
const customEmail = typeof emailAddress !== 'undefined' && emailAddress ? emailAddress : '';
|
||||
const customOfficeAddressLine1 = typeof officeAddressLine1 !== 'undefined' && officeAddressLine1 ? officeAddressLine1 : '';
|
||||
const customOfficeAddressLine2 = typeof officeAddressLine2 !== 'undefined' && officeAddressLine2 ? officeAddressLine2 : '';
|
||||
const customOfficeAddressLine3 = typeof officeAddressLine3 !== 'undefined' && officeAddressLine3 ? officeAddressLine3 : '';
|
||||
const customDirectorOfCustoms = typeof directorOfCustoms !== 'undefined' && directorOfCustoms ? directorOfCustoms : '';
|
||||
const customVehicleNo = typeof vehicleNo !== 'undefined' && vehicleNo ? vehicleNo : '';
|
||||
const customPermitNo = typeof permitNo !== 'undefined' && permitNo ? permitNo : '';
|
||||
const customChiefSecurityOfficer = typeof chiefSecurityOfficer !== 'undefined' && chiefSecurityOfficer ? chiefSecurityOfficer : '';
|
||||
const customExportGate = typeof exportGate !== 'undefined' && exportGate ? exportGate : '';
|
||||
const customSlpa = typeof slpa !== 'undefined' && slpa ? slpa : '';
|
||||
const customGateNo = typeof gateNo !== 'undefined' && gateNo ? gateNo : '';
|
||||
const customPermissionText = typeof permissionText !== 'undefined' && permissionText ? permissionText : '';
|
||||
const customCompanyName = typeof companyName !== 'undefined' && companyName ? companyName : 'Oceanic Maritime Solutions (Pvt) Ltd';
|
||||
const customSubtitle = typeof subtitle !== 'undefined' && subtitle ? subtitle : 'LICENSED SHIPCHANDLERS';
|
||||
const customSectionCode = typeof sectionCode !== 'undefined' && sectionCode ? sectionCode : 'SC/FORM/06';
|
||||
const customDateLabel = typeof dateLabel !== 'undefined' && dateLabel ? dateLabel : 'DATE :';
|
||||
const customItems = typeof items !== 'undefined' && Array.isArray(items) ? items : [];
|
||||
const rows = Math.max(7, customItems.length);
|
||||
%>
|
||||
<div class="sheet">
|
||||
<div class="top-band">
|
||||
<div class="top-left">
|
||||
<div class="top-line"><span class="label">Tele :</span> <%= customTelePhone || '' %></div>
|
||||
<div class="top-line"><span class="label">E-mail Address :</span> <%= customEmail || '' %></div>
|
||||
</div>
|
||||
<div class="top-right">
|
||||
<div><%= customOfficeAddressLine1 %></div>
|
||||
<div><%= customOfficeAddressLine2 %></div>
|
||||
<div><%= customOfficeAddressLine3 %></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="highlight-box">
|
||||
<div class="highlight-left">
|
||||
<div class="highlight-row"><div class="label">The Director of Customs</div><div><%= customDirectorOfCustoms %></div></div>
|
||||
<div class="highlight-row"><div class="label">Export Gate</div><div><span style="color:#e53935;">Colombo 1.</span></div></div>
|
||||
<div class="highlight-row"><div class="label">Vehicle No. / Permit No.</div><div><%= customVehicleNo %> / <%= customPermitNo %></div></div>
|
||||
</div>
|
||||
<div class="highlight-right">
|
||||
<div class="highlight-row"><div class="label">Chief Security Officer</div><div><%= customChiefSecurityOfficer %></div></div>
|
||||
<div class="highlight-row"><div class="label">SLPA</div><div><%= customSlpa %></div></div>
|
||||
<div class="highlight-row"><div class="label">Gate No.</div><div><%= customGateNo %></div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="permission-line"><%= customPermissionText %></div>
|
||||
<div class="dots">............................................................</div>
|
||||
|
||||
<div class="company-name"><%= customCompanyName %></div>
|
||||
<div class="company-subtitle"><%= customSubtitle %></div>
|
||||
|
||||
<div class="date-row">
|
||||
<div class="date-box"><%= customDateLabel %> <%= customDate %></div>
|
||||
</div>
|
||||
|
||||
<div class="form-code"><%= customSectionCode %></div>
|
||||
|
||||
<table class="items-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="no-col">NO</th>
|
||||
<th class="qty-col">QTY</th>
|
||||
<th class="unit-col">UNIT</th>
|
||||
<th class="description-col">DESCRIPTION</th>
|
||||
<th class="rate-col">RATE</th>
|
||||
<th class="total-col">TOTAL</th>
|
||||
<th class="confirm-col">Confirm<br/>Order Rate</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% for (let index = 0; index < rows; index++) { %>
|
||||
<% const item = customItems[index] || {}; %>
|
||||
<tr class="spacer-row">
|
||||
<td class="center"><%= index + 1 %></td>
|
||||
<td class="center"><%= item.quantity || item.qty || '' %></td>
|
||||
<td class="center"><%= item.unit || '' %></td>
|
||||
<td><%= item.description || item.item_name || item.name || '' %></td>
|
||||
<td class="right"><%= item.rate || item.unit_price || '-' %></td>
|
||||
<td class="right"><%= item.total || item.amount || '-' %></td>
|
||||
<td><%= item.confirmOrderRate || item.confirm_order_rate || '' %></td>
|
||||
</tr>
|
||||
<% } %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Niolla
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is proprietary and confidential.
|
||||
* Unauthorized copying, modification, distribution, or use
|
||||
* of this file, via any medium, is strictly prohibited.
|
||||
*/
|
||||
|
||||
// app/templates/documents/customDocument/customDocument.template.js
|
||||
|
||||
const { log } = require("../../../utils/consoleLog.utill");
|
||||
|
||||
module.exports = async (data) => {
|
||||
log("📋 Preparing Custom Document data for PDF...");
|
||||
|
||||
let payload = data;
|
||||
|
||||
if (typeof payload === "string") {
|
||||
try {
|
||||
payload = JSON.parse(payload);
|
||||
} catch (error) {
|
||||
payload = {};
|
||||
}
|
||||
}
|
||||
|
||||
const source = payload.documentData || payload.document_data || payload.data || payload;
|
||||
|
||||
const pick = (...keys) => {
|
||||
for (const key of keys) {
|
||||
const value = source?.[key];
|
||||
if (value !== undefined && value !== null && value !== "") {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
};
|
||||
|
||||
const templateData = {
|
||||
logoUrl:
|
||||
pick("logoUrl", "logo_url") ||
|
||||
"https://res.cloudinary.com/dtthuvvir/image/upload/v1780748661/company_logo_transperent_bg_uullzw.png",
|
||||
|
||||
title: pick("title") || "CUSTOM DOCUMENT",
|
||||
documentNumber: pick("documentNumber", "document_number") || "",
|
||||
date: pick("date") || new Date().toLocaleDateString(),
|
||||
|
||||
telePhone: pick("telePhone", "telephone", "phone", "tel") || "",
|
||||
emailAddress: pick("emailAddress", "email", "e_mail") || "",
|
||||
|
||||
officeAddressLine1: pick("officeAddressLine1", "office_address_line1") || "",
|
||||
officeAddressLine2: pick("officeAddressLine2", "office_address_line2") || "",
|
||||
officeAddressLine3: pick("officeAddressLine3", "office_address_line3") || "",
|
||||
|
||||
directorOfCustoms: pick("directorOfCustoms", "director_of_customs") || "",
|
||||
vehicleNo: pick("vehicleNo", "vehicle_no") || "",
|
||||
permitNo: pick("permitNo", "permit_no") || "",
|
||||
chiefSecurityOfficer: pick("chiefSecurityOfficer", "chief_security_officer") || "",
|
||||
exportGate: pick("exportGate", "export_gate") || "",
|
||||
slpa: pick("slpa") || "",
|
||||
gateNo: pick("gateNo", "gate_no") || "",
|
||||
permissionText: pick("permissionText", "permission_text") || "Please grant permission to pass these Customs entry papers to supply goods to the vessel",
|
||||
|
||||
companyName: pick("companyName", "company_name") || "Oceanic Maritime Solutions (Pvt) Ltd",
|
||||
subtitle: pick("subtitle") || "LICENSED SHIPCHANDLERS",
|
||||
sectionCode: pick("sectionCode", "section_code") || "SC/FORM/06",
|
||||
dateLabel: pick("dateLabel", "date_label") || "DATE :",
|
||||
|
||||
items: Array.isArray(source.items) ? source.items : [],
|
||||
itemHeaders:
|
||||
pick("itemHeaders", "item_headers") ||
|
||||
["NO", "QTY", "UNIT", "DESCRIPTION", "RATE", "TOTAL", "Confirm Order Rate"],
|
||||
|
||||
footerText: pick("footerText", "footer_text") || "",
|
||||
};
|
||||
|
||||
log("✅ Custom Document data prepared");
|
||||
|
||||
return templateData;
|
||||
};
|
||||
@@ -0,0 +1,189 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@page {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 14px;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.sheet {
|
||||
width: 100%;
|
||||
border: 2px solid #2a5aa8;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.logo-row {
|
||||
height: 82px;
|
||||
border-bottom: 1px solid #4c4c4c;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 6px 12px 2px;
|
||||
}
|
||||
|
||||
.logo-row img {
|
||||
max-height: 64px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.title-row {
|
||||
border-bottom: 1px solid #4c4c4c;
|
||||
text-align: center;
|
||||
padding: 6px 0 7px;
|
||||
color: #2f5fa6;
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.details {
|
||||
display: grid;
|
||||
grid-template-columns: 1.6fr 1fr;
|
||||
gap: 0;
|
||||
min-height: 84px;
|
||||
border-bottom: 1px solid #bdbdbd;
|
||||
}
|
||||
|
||||
.left-pane,
|
||||
.right-pane {
|
||||
padding: 10px 8px 8px;
|
||||
}
|
||||
|
||||
.left-pane {
|
||||
border-right: 1px solid #d4d4d4;
|
||||
}
|
||||
|
||||
.label-line {
|
||||
font-size: 11px;
|
||||
line-height: 1.35;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
.label-line + .label-line {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.right-pane {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.right-row {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.right-row .value {
|
||||
font-weight: 400;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.items-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.items-table th,
|
||||
.items-table td {
|
||||
border: 1px solid #5d5d5d;
|
||||
font-size: 11px;
|
||||
padding: 5px 6px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.items-table th {
|
||||
background: #a7d8df;
|
||||
color: #111;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.no-col { width: 8%; }
|
||||
.desc-col { width: 36%; }
|
||||
.remarks-col { width: 25%; }
|
||||
.unit-col { width: 14%; }
|
||||
.qty-col { width: 17%; }
|
||||
|
||||
.items-table tbody td {
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.right {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="sheet">
|
||||
<div class="logo-row">
|
||||
<% if (logoBase64) { %>
|
||||
<img src="<%= logoBase64 %>" alt="Company Logo" />
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
<div class="title-row"><%= title || 'DELIVERY NOTE' %></div>
|
||||
|
||||
<div class="details">
|
||||
<div class="left-pane">
|
||||
<div class="label-line">Bill To: <span style="font-weight:700;"><%= billToName || 'N/A' %></span></div>
|
||||
<div class="label-line"><%= billToAddress || 'N/A' %></div>
|
||||
</div>
|
||||
<div class="right-pane">
|
||||
<div class="right-row"><span>Supply Date:</span> <span class="value"><%= supplyDate || 'N/A' %></span></div>
|
||||
<div class="right-row"><span>PO Number:</span> <span class="value"><%= poNumber || 'N/A' %></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="items-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="no-col">No.</th>
|
||||
<th class="desc-col">Description</th>
|
||||
<th class="remarks-col">Remarks</th>
|
||||
<th class="unit-col">Unit</th>
|
||||
<th class="qty-col">Quantity</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% const rows = Math.max(10, (items && items.length) || 0); %>
|
||||
<% for (let index = 0; index < rows; index++) { %>
|
||||
<% const item = items && items[index] ? items[index] : {}; %>
|
||||
<tr>
|
||||
<td class="center"><%= index + 1 %></td>
|
||||
<td><%= item.description || item.item_name || item.name || '' %></td>
|
||||
<td><%= item.remarks || '' %></td>
|
||||
<td class="center"><%= item.unit || '' %></td>
|
||||
<td class="right"><%= item.quantity || item.qty || '' %></td>
|
||||
</tr>
|
||||
<% } %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Niolla
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is proprietary and confidential.
|
||||
* Unauthorized copying, modification, distribution, or use
|
||||
* of this file, via any medium, is strictly prohibited.
|
||||
*/
|
||||
|
||||
// app/templates/documents/deliveryNote/deliveryNote.template.js
|
||||
|
||||
const { log } = require("../../../utils/consoleLog.utill");
|
||||
|
||||
module.exports = async (data) => {
|
||||
log("📋 Preparing Delivery Note data for PDF...");
|
||||
|
||||
const templateData = {
|
||||
logoUrl:
|
||||
data.logoUrl ||
|
||||
"https://res.cloudinary.com/dtthuvvir/image/upload/v1780748661/company_logo_transperent_bg_uullzw.png",
|
||||
title: data.title || "DELIVERY NOTE",
|
||||
referenceNumber:
|
||||
data.referenceNumber || data.reference_no || data.deliveryNoteNumber || data.delivery_note_number || "N/A",
|
||||
date: data.date || new Date().toLocaleDateString(),
|
||||
billToName: data.billToName || data.bill_to_name || "N/A",
|
||||
billToAddress: data.billToAddress || data.bill_to_address || "N/A",
|
||||
supplyDate: data.supplyDate || data.supply_date || data.date || new Date().toLocaleDateString(),
|
||||
poNumber: data.poNumber || data.po_number || "N/A",
|
||||
items: Array.isArray(data.items) ? data.items : [],
|
||||
};
|
||||
|
||||
log("✅ Delivery Note data prepared");
|
||||
|
||||
return templateData;
|
||||
};
|
||||
@@ -0,0 +1,345 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background: white;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.sheet {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border: 1px solid #222;
|
||||
}
|
||||
|
||||
.title-bar {
|
||||
background: #173d64;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1px;
|
||||
padding: 4px 0 5px;
|
||||
border-bottom: 1px solid #222;
|
||||
}
|
||||
|
||||
.top-area {
|
||||
display: grid;
|
||||
grid-template-columns: 1.4fr 1fr 0.65fr;
|
||||
min-height: 95px;
|
||||
border-bottom: 1px solid #444;
|
||||
}
|
||||
|
||||
.top-col {
|
||||
padding: 5px 6px;
|
||||
border-right: 1px solid #444;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.top-col:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.field-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
.field-row {
|
||||
display: grid;
|
||||
grid-template-columns: 110px 1fr;
|
||||
align-items: center;
|
||||
min-height: 17px;
|
||||
border: 1px solid #444;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.field-row:last-child {
|
||||
border-bottom: 1px solid #444;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-weight: 700;
|
||||
padding: 1px 4px;
|
||||
}
|
||||
|
||||
.value {
|
||||
padding: 1px 4px;
|
||||
}
|
||||
|
||||
.top-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.top-middle {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.top-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.meta-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1.4fr 1fr 0.65fr;
|
||||
border-bottom: 1px solid #444;
|
||||
}
|
||||
|
||||
.meta-box {
|
||||
min-height: 82px;
|
||||
padding: 4px 6px;
|
||||
border-right: 1px solid #444;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.meta-box:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.meta-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.meta-table td {
|
||||
border: 1px solid #444;
|
||||
padding: 2px 4px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.meta-table td.label-cell {
|
||||
width: 105px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.details-note {
|
||||
font-size: 9px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.items-title {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
padding: 2px 0 3px;
|
||||
border-bottom: 1px solid #444;
|
||||
background: #efefef;
|
||||
}
|
||||
|
||||
.items-wrap {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.items-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.items-table th,
|
||||
.items-table td {
|
||||
border: 1px solid #666;
|
||||
font-size: 10px;
|
||||
padding: 2px 3px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.items-table th {
|
||||
background: #d9d9d9;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.no-col { width: 5%; }
|
||||
.product-col { width: 24%; }
|
||||
.qty-col { width: 5%; }
|
||||
.unit-col { width: 5%; }
|
||||
.supp-col { width: 6%; }
|
||||
.pono-col { width: 6%; }
|
||||
.rcvdqty-col { width: 7%; }
|
||||
.issuedqty-col { width: 7%; }
|
||||
.expdate-col { width: 8%; }
|
||||
.rcvdtime-col { width: 7%; }
|
||||
.date-col { width: 7%; }
|
||||
.rejects-col { width: 6%; }
|
||||
.fatcol-col { width: 6%; }
|
||||
.remark-col { width: 7%; }
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.spacer-row td {
|
||||
height: 21px;
|
||||
}
|
||||
|
||||
.signature-section {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.signature-block {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
border-top: 1px solid #333;
|
||||
padding-top: 34px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.footer-note {
|
||||
text-align: center;
|
||||
margin-top: 12px;
|
||||
border-top: 1px solid #ddd;
|
||||
padding-top: 5px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<%
|
||||
const dispatchTitle = typeof title !== 'undefined' && title ? title : 'DISPATCH NOTE';
|
||||
const dispatchReferenceNumber = typeof referenceNumber !== 'undefined' && referenceNumber ? referenceNumber : 'N/A';
|
||||
const dispatchDate = typeof date !== 'undefined' && date ? date : 'N/A';
|
||||
const dispatchVessel = typeof vessel !== 'undefined' && vessel ? vessel : 'N/A';
|
||||
const dispatchCaptain = typeof captain !== 'undefined' && captain ? captain : 'N/A';
|
||||
const dispatchCook = typeof cook !== 'undefined' && cook ? cook : 'N/A';
|
||||
const dispatchAgent = typeof agent !== 'undefined' && agent ? agent : 'N/A';
|
||||
const dispatchDetails = typeof details !== 'undefined' && details ? details : '';
|
||||
const dispatchPlaceOfDelivery = typeof placeOfDelivery !== 'undefined' && placeOfDelivery ? placeOfDelivery : 'N/A';
|
||||
const dispatchEta = typeof eta !== 'undefined' && eta ? eta : 'N/A';
|
||||
const dispatchEtd = typeof etd !== 'undefined' && etd ? etd : 'N/A';
|
||||
const dispatchNumberOfCrew = typeof numberOfCrew !== 'undefined' && numberOfCrew ? numberOfCrew : 'N/A';
|
||||
const dispatchNextMainOrderIn = typeof nextMainOrderIn !== 'undefined' && nextMainOrderIn ? nextMainOrderIn : 'N/A';
|
||||
const dispatchPort = typeof port !== 'undefined' && port ? port : 'N/A';
|
||||
const dispatchCompany = typeof company !== 'undefined' && company ? company : 'N/A';
|
||||
const dispatchFileNo = typeof fileNo !== 'undefined' && fileNo ? fileNo : 'N/A';
|
||||
const dispatchItems = typeof items !== 'undefined' && Array.isArray(items) ? items : [];
|
||||
%>
|
||||
<div class="sheet">
|
||||
<div class="title-bar"><%= dispatchTitle %></div>
|
||||
|
||||
<div class="top-area">
|
||||
<div class="top-col">
|
||||
<div class="field-grid">
|
||||
<div class="field-row"><div class="label">Vessel:</div><div class="value"><%= dispatchVessel %></div></div>
|
||||
<div class="field-row"><div class="label">Captain:</div><div class="value"><%= dispatchCaptain %></div></div>
|
||||
<div class="field-row"><div class="label">Cook:</div><div class="value"><%= dispatchCook %></div></div>
|
||||
<div class="field-row"><div class="label">Agent:</div><div class="value"><%= dispatchAgent %></div></div>
|
||||
<div class="field-row"><div class="label">Details:</div><div class="value"><%= dispatchDetails %></div></div>
|
||||
</div>
|
||||
<div class="details-note">SC/FORM/04</div>
|
||||
</div>
|
||||
<div class="top-col">
|
||||
<div class="field-grid">
|
||||
<div class="field-row"><div class="label">Place of delivery:</div><div class="value"><%= dispatchPlaceOfDelivery %></div></div>
|
||||
<div class="field-row"><div class="label">ETA:</div><div class="value"><%= dispatchEta %></div></div>
|
||||
<div class="field-row"><div class="label">ETD:</div><div class="value"><%= dispatchEtd %></div></div>
|
||||
<div class="field-row"><div class="label">Number of crew:</div><div class="value"><%= dispatchNumberOfCrew %></div></div>
|
||||
<div class="field-row"><div class="label">Next main order in:</div><div class="value"><%= dispatchNextMainOrderIn %></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="top-col">
|
||||
<div class="field-grid">
|
||||
<div class="field-row"><div class="label">Port</div><div class="value"><%= dispatchPort %></div></div>
|
||||
<div class="field-row"><div class="label">Date</div><div class="value"><%= dispatchDate %></div></div>
|
||||
<div class="field-row"><div class="label">Company</div><div class="value"><%= dispatchCompany %></div></div>
|
||||
<div class="field-row"><div class="label">File No</div><div class="value"><%= dispatchFileNo %></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="items-title">Item Receiving Authorization and Vessel Delivery Oder details</div>
|
||||
|
||||
<div class="items-wrap">
|
||||
<table class="items-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="no-col">No</th>
|
||||
<th class="product-col">Product</th>
|
||||
<th class="qty-col">Qty</th>
|
||||
<th class="unit-col">Unit</th>
|
||||
<th class="supp-col">SUPP</th>
|
||||
<th class="pono-col">PO NO</th>
|
||||
<th class="rcvdqty-col">Rcvd Qty</th>
|
||||
<th class="issuedqty-col">Issued Qty</th>
|
||||
<th class="expdate-col">Exp Date</th>
|
||||
<th class="rcvdtime-col">Rcvd Time</th>
|
||||
<th class="date-col">Date</th>
|
||||
<th class="rejects-col">Rejects</th>
|
||||
<th class="fatcol-col">Fat Con.</th>
|
||||
<th class="remark-col">Remark</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% const rows = Math.max(5, dispatchItems.length); %>
|
||||
<% for (let index = 0; index < rows; index++) { %>
|
||||
<% const item = dispatchItems[index] || {}; %>
|
||||
<tr class="spacer-row">
|
||||
<td class="center"><%= index + 1 %></td>
|
||||
<td><%= item.product || item.description || item.item_name || item.name || '' %></td>
|
||||
<td class="center"><%= item.qty || item.quantity || '' %></td>
|
||||
<td class="center"><%= item.unit || '' %></td>
|
||||
<td class="center"><%= item.supp || item.supplier || '' %></td>
|
||||
<td class="center"><%= item.poNo || item.po_number || item.poNumber || '' %></td>
|
||||
<td class="center"><%= item.receivedQty || item.rcvdQty || '' %></td>
|
||||
<td class="center"><%= item.issuedQty || '' %></td>
|
||||
<td class="center"><%= item.expDate || item.exp_date || '' %></td>
|
||||
<td class="center"><%= item.receivedTime || item.rcvdTime || '' %></td>
|
||||
<td class="center"><%= item.date || '' %></td>
|
||||
<td class="center"><%= item.rejects || '' %></td>
|
||||
<td class="center"><%= item.fatCon || item.fat_con || '' %></td>
|
||||
<td><%= item.remark || item.remarks || '' %></td>
|
||||
</tr>
|
||||
<% } %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<% if (showSignatures) { %>
|
||||
<div class="signature-section">
|
||||
<div class="signature-block">
|
||||
<strong>Prepared By</strong>
|
||||
<div style="margin-top: 28px;">___________________</div>
|
||||
<div style="margin-top: 5px;"><%= preparedBy || 'Name' %></div>
|
||||
</div>
|
||||
<div class="signature-block">
|
||||
<strong>Approved By</strong>
|
||||
<div style="margin-top: 28px;">___________________</div>
|
||||
<div style="margin-top: 5px;"><%= approvedBy || 'Name' %></div>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<div class="footer-note">
|
||||
<p style="font-size: 10px; color: #999;">This is an official dispatch note</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Niolla
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is proprietary and confidential.
|
||||
* Unauthorized copying, modification, distribution, or use
|
||||
* of this file, via any medium, is strictly prohibited.
|
||||
*/
|
||||
|
||||
// app/templates/documents/dispatchNote/dispatchNote.template.js
|
||||
|
||||
const { log } = require("../../../utils/consoleLog.utill");
|
||||
|
||||
module.exports = async (data) => {
|
||||
log("📋 Preparing Dispatch Note data for PDF...");
|
||||
|
||||
let payload = data;
|
||||
|
||||
if (typeof payload === "string") {
|
||||
try {
|
||||
payload = JSON.parse(payload);
|
||||
} catch (error) {
|
||||
payload = {};
|
||||
}
|
||||
}
|
||||
|
||||
const source = payload.documentData || payload.document_data || payload.data || payload;
|
||||
|
||||
const pick = (...keys) => {
|
||||
for (const key of keys) {
|
||||
const value = source?.[key];
|
||||
if (value !== undefined && value !== null && value !== "") {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
};
|
||||
|
||||
const templateData = {
|
||||
logoUrl:
|
||||
pick("logoUrl", "logo_url") ||
|
||||
"https://res.cloudinary.com/dtthuvvir/image/upload/v1780748661/company_logo_transperent_bg_uullzw.png",
|
||||
|
||||
title: pick("title") || "DISPATCH NOTE",
|
||||
referenceNumber: pick("referenceNumber", "reference_number", "dnNo", "dn_no") || "N/A",
|
||||
date: pick("date") || new Date().toLocaleDateString(),
|
||||
|
||||
vessel: pick("vessel", "vesselName", "vessel_name") || "",
|
||||
captain: pick("captain", "captainName", "captain_name") || "",
|
||||
cook: pick("cook", "cookName", "cook_name") || "",
|
||||
agent: pick("agent", "agentName", "agent_name") || "",
|
||||
details: pick("details", "remarks", "note") || "",
|
||||
|
||||
placeOfDelivery: pick("placeOfDelivery", "place_of_delivery") || "",
|
||||
eta: pick("eta") || "",
|
||||
etd: pick("etd") || "",
|
||||
numberOfCrew: pick("numberOfCrew", "number_of_crew", "crewCount", "crew_count") || "",
|
||||
nextMainOrderIn: pick("nextMainOrderIn", "next_main_order_in") || "",
|
||||
port: pick("port", "portName", "port_name") || "",
|
||||
company: pick("company", "companyName", "company_name") || "",
|
||||
fileNo: pick("fileNo", "file_no") || "",
|
||||
|
||||
items: Array.isArray(source.items) ? source.items : [],
|
||||
|
||||
showSignatures: pick("showSignatures") !== false,
|
||||
preparedBy: pick("preparedBy", "prepared_by") || "",
|
||||
approvedBy: pick("approvedBy", "approved_by") || "",
|
||||
};
|
||||
|
||||
log("✅ Dispatch Note data prepared");
|
||||
|
||||
return templateData;
|
||||
};
|
||||
@@ -0,0 +1,308 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background: white;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.page {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border: 1px solid #333;
|
||||
}
|
||||
|
||||
.logo-section {
|
||||
height: 72px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-bottom: 1px solid #c9c9c9;
|
||||
}
|
||||
|
||||
.logo-section img {
|
||||
max-height: 52px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.title-section {
|
||||
text-align: center;
|
||||
padding: 5px 0 4px;
|
||||
border-bottom: 1px solid #333;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: #2d5c96;
|
||||
letter-spacing: 0.4px;
|
||||
}
|
||||
|
||||
.top-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1.6fr 1fr;
|
||||
min-height: 70px;
|
||||
border-bottom: 1px solid #c9c9c9;
|
||||
}
|
||||
|
||||
.top-left,
|
||||
.top-right,
|
||||
.mid-left,
|
||||
.mid-right {
|
||||
padding: 4px 6px;
|
||||
font-size: 10px;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
.top-left {
|
||||
border-right: 1px solid #e2e2e2;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.bill-to-label {
|
||||
font-weight: 700;
|
||||
min-width: 48px;
|
||||
}
|
||||
|
||||
.bill-to-name {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.bill-to-address {
|
||||
margin-top: 2px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.top-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
gap: 2px;
|
||||
text-align: right;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.top-right-row {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.top-right-row .value {
|
||||
font-weight: 400;
|
||||
min-width: 110px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1.4fr 1fr;
|
||||
border-bottom: 1px solid #c9c9c9;
|
||||
}
|
||||
|
||||
.mid-left {
|
||||
border-right: 1px solid #e2e2e2;
|
||||
}
|
||||
|
||||
.mid-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
text-align: right;
|
||||
min-width: 90px;
|
||||
}
|
||||
|
||||
.items-wrap {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.items-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.items-table thead th {
|
||||
background: #a8d9de;
|
||||
color: #111;
|
||||
font-weight: bold;
|
||||
font-size: 10px;
|
||||
text-align: center;
|
||||
padding: 5px 4px;
|
||||
border: 1px solid #4b4b4b;
|
||||
}
|
||||
|
||||
.items-table td {
|
||||
border: 1px solid #5d5d5d;
|
||||
font-size: 10px;
|
||||
height: 15px;
|
||||
padding: 2px 4px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.col-no { width: 8%; }
|
||||
.col-desc { width: 30%; }
|
||||
.col-remarks { width: 24%; }
|
||||
.col-qty { width: 9%; }
|
||||
.col-unit { width: 9%; }
|
||||
.col-price { width: 10%; }
|
||||
.col-total { width: 10%; }
|
||||
|
||||
.spacer-row td {
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.bottom-table {
|
||||
display: grid;
|
||||
grid-template-columns: 1.4fr 1fr;
|
||||
border-top: 1px solid #c9c9c9;
|
||||
}
|
||||
|
||||
.bottom-left,
|
||||
.bottom-right {
|
||||
padding: 4px 6px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.bottom-left {
|
||||
border-right: 1px solid #e2e2e2;
|
||||
}
|
||||
|
||||
.bottom-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.bottom-line {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.footer-row {
|
||||
border-top: 1px solid #333;
|
||||
text-align: right;
|
||||
padding: 4px 6px;
|
||||
font-size: 9px;
|
||||
color: #555;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<%
|
||||
const invoiceTitle = typeof title !== 'undefined' && title ? title : 'INVOICE';
|
||||
const invoiceJobReference = typeof jobReference !== 'undefined' && jobReference ? jobReference : 'N/A';
|
||||
const invoicePoNumber = typeof poNumber !== 'undefined' && poNumber ? poNumber : 'N/A';
|
||||
const invoiceDate = typeof date !== 'undefined' && date ? date : 'N/A';
|
||||
const invoicePageLabel = typeof pageLabel !== 'undefined' && pageLabel ? pageLabel : 'Page 01 of 02';
|
||||
const invoiceBillToName = typeof billToName !== 'undefined' && billToName ? billToName : 'Client Name';
|
||||
const invoiceBillToAddress = typeof billToAddress !== 'undefined' && billToAddress ? billToAddress : 'Address';
|
||||
const invoiceVesselName = typeof vesselName !== 'undefined' && vesselName ? vesselName : 'N/A';
|
||||
const invoicePurposeOfCall = typeof purposeOfCall !== 'undefined' && purposeOfCall ? purposeOfCall : 'N/A';
|
||||
const invoiceSupplyDate = typeof supplyDate !== 'undefined' && supplyDate ? supplyDate : 'N/A';
|
||||
const invoiceGrt = typeof grt !== 'undefined' && grt ? grt : 'N/A';
|
||||
const invoiceImoNumber = typeof imoNumber !== 'undefined' && imoNumber ? imoNumber : 'N/A';
|
||||
const invoicePortCountry = typeof portCountry !== 'undefined' && portCountry ? portCountry : 'N/A';
|
||||
const invoiceItems = typeof items !== 'undefined' && Array.isArray(items) ? items : [];
|
||||
%>
|
||||
<div class="page">
|
||||
<div class="logo-section">
|
||||
<% if (logoBase64) { %>
|
||||
<img src="<%= logoBase64 %>" alt="Company Logo" />
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
<div class="title-section"><%= invoiceTitle %></div>
|
||||
|
||||
<div class="top-grid">
|
||||
<div class="top-left">
|
||||
<div>
|
||||
<div><span class="bill-to-label">Bill To:</span> <span class="bill-to-name"><%= invoiceBillToName %></span></div>
|
||||
<div class="bill-to-address"><%= invoiceBillToAddress %></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="top-right">
|
||||
<div class="top-right-row"><span>Job Reference :</span> <span class="value"><%= invoiceJobReference %></span></div>
|
||||
<div class="top-right-row"><span>PO Number :</span> <span class="value"><%= invoicePoNumber %></span></div>
|
||||
<div class="top-right-row"><span>Date :</span> <span class="value"><%= invoiceDate %></span></div>
|
||||
<div class="top-right-row"><span></span> <span class="value"><%= invoicePageLabel %></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-grid">
|
||||
<div class="mid-left">
|
||||
<div class="mid-row"><span class="info-label">Vessel Name :</span> <span class="info-value"><%= invoiceVesselName %></span></div>
|
||||
<div class="mid-row"><span class="info-label">Purpose of Call :</span> <span class="info-value"><%= invoicePurposeOfCall %></span></div>
|
||||
<div class="mid-row"><span class="info-label">Supply Date :</span> <span class="info-value"><%= invoiceSupplyDate %></span></div>
|
||||
</div>
|
||||
<div class="mid-right">
|
||||
<div class="mid-row"><span class="info-label">GRT :</span> <span class="info-value"><%= invoiceGrt %></span></div>
|
||||
<div class="mid-row"><span class="info-label">IMO Number :</span> <span class="info-value"><%= invoiceImoNumber %></span></div>
|
||||
<div class="mid-row"><span class="info-label">Port/Country :</span> <span class="info-value"><%= invoicePortCountry %></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="items-wrap">
|
||||
<table class="items-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-no">No.</th>
|
||||
<th class="col-desc">Description</th>
|
||||
<th class="col-remarks">Remarks</th>
|
||||
<th class="col-qty">Qty</th>
|
||||
<th class="col-unit">Unit</th>
|
||||
<th class="col-price">Unit Price (USD)</th>
|
||||
<th class="col-total">Final Amount (USD)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% const rows = Math.max(10, invoiceItems.length); %>
|
||||
<% for (let index = 0; index < rows; index++) { %>
|
||||
<% const item = invoiceItems[index] || {}; %>
|
||||
<% const quantity = Number(item.quantity || item.qty || 0); %>
|
||||
<% const unitPrice = Number(item.unit_price || item.unitPrice || 0); %>
|
||||
<tr class="spacer-row">
|
||||
<td class="center"><%= index + 1 %></td>
|
||||
<td><%= item.description || item.item_name || item.name || '' %></td>
|
||||
<td><%= item.remarks || '' %></td>
|
||||
<td class="center"><%= item.quantity || item.qty || '' %></td>
|
||||
<td class="center"><%= item.unit || '' %></td>
|
||||
<td class="right"><%= item.unit_price || item.unitPrice || '' %></td>
|
||||
<td class="right"><%= quantity && unitPrice ? quantity * unitPrice : (item.total || item.amount || '') %></td>
|
||||
</tr>
|
||||
<% } %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,111 @@
|
||||
|
||||
/**
|
||||
* Copyright (c) 2026 Niolla
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is proprietary and confidential.
|
||||
* Unauthorized copying, modification, distribution, or use
|
||||
* of this file, via any medium, is strictly prohibited.
|
||||
*/
|
||||
|
||||
// app/templates/documents/invoice/invoice.template.js
|
||||
|
||||
const { log } = require("../../../utils/consoleLog.utill");
|
||||
|
||||
module.exports = async (data) => {
|
||||
log("📋 Preparing Invoice data for PDF...");
|
||||
|
||||
let payload = data;
|
||||
|
||||
if (typeof payload === "string") {
|
||||
try {
|
||||
payload = JSON.parse(payload);
|
||||
} catch (error) {
|
||||
payload = {};
|
||||
}
|
||||
}
|
||||
|
||||
const source =
|
||||
payload.documentData ||
|
||||
payload.document_data ||
|
||||
payload.data ||
|
||||
payload;
|
||||
|
||||
const deepFind = (node, keys, visited = new Set()) => {
|
||||
if (!node || typeof node !== "object" || visited.has(node)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
visited.add(node);
|
||||
|
||||
for (const key of keys) {
|
||||
const value = node[key];
|
||||
if (value !== undefined && value !== null && value !== "") {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
for (const child of Object.values(node)) {
|
||||
const found = deepFind(child, keys, visited);
|
||||
if (found !== "") {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
};
|
||||
|
||||
const pick = (...keys) => {
|
||||
const direct = deepFind(source, keys);
|
||||
|
||||
if (direct !== "") {
|
||||
return direct;
|
||||
}
|
||||
|
||||
return deepFind(payload, keys);
|
||||
};
|
||||
|
||||
const templateData = {
|
||||
logoUrl:
|
||||
pick("logoUrl", "logo_url") ||
|
||||
"https://res.cloudinary.com/dtthuvvir/image/upload/v1780748661/company_logo_transperent_bg_uullzw.png",
|
||||
|
||||
title: pick("title") || "INVOICE",
|
||||
jobReference:
|
||||
pick(
|
||||
"jobReference",
|
||||
"job_reference",
|
||||
"referenceNumber",
|
||||
"reference_no",
|
||||
"jobRef",
|
||||
"job_ref",
|
||||
) || "",
|
||||
poNumber: pick("poNumber", "po_number", "poNo", "po_no") || "",
|
||||
date: pick("date") || new Date().toLocaleDateString(),
|
||||
pageLabel: pick("pageLabel", "page_label") || "Page 01 of 02",
|
||||
|
||||
billToName: pick("billToName", "bill_to_name") || "Client Name",
|
||||
billToAddress: pick("billToAddress", "bill_to_address") || "Address",
|
||||
|
||||
vesselName: pick("vesselName", "vessel_name") || "",
|
||||
purposeOfCall: pick("purposeOfCall", "purpose_of_call") || "",
|
||||
supplyDate: pick("supplyDate", "supply_date") || "",
|
||||
grt: pick("grt", "GRT") || "",
|
||||
imoNumber: pick("imoNumber", "imo_number", "imo") || "",
|
||||
portCountry: pick("portCountry", "port_country") || "",
|
||||
|
||||
items: Array.isArray(source.items) ? source.items : [],
|
||||
|
||||
subtotal: Number(pick("subtotal", "sub_total") || 0),
|
||||
tax: Number(pick("tax") || 0),
|
||||
discount: Number(pick("discount") || 0),
|
||||
total: Number(pick("total") || 0),
|
||||
|
||||
paymentTerms: pick("paymentTerms", "payment_terms") || "",
|
||||
notes: pick("notes") || "",
|
||||
};
|
||||
|
||||
log("✅ Invoice data prepared");
|
||||
|
||||
return templateData;
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
body {
|
||||
font-family: Arial;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #f4f4f4;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
{{styles}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>PreCost Estimate</h1>
|
||||
|
||||
<p>Customer: {{customerName}}</p>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Item</th>
|
||||
<th>Description</th>
|
||||
<th>Qty</th>
|
||||
<th>Unit Price</th>
|
||||
<th>Total</th>
|
||||
</tr>
|
||||
|
||||
{{items}}
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,285 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background: white;
|
||||
font-family: 'Segoe UI', 'Roboto', 'Helvetica Neue', sans-serif;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.header-section {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30px;
|
||||
border-bottom: 2px solid #1a7a4d;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.header-section img {
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.doc-title {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #1a7a4d;
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.po-number {
|
||||
text-align: right;
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
background: #e8f5e9;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.info-left, .info-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-weight: bold;
|
||||
color: #2e7d32;
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.items-table thead {
|
||||
background-color: #2e7d32;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.items-table th {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.items-table td {
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.items-table tbody tr:nth-child(even) {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.summary-area {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.summary-table {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.summary-table td {
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.summary-table td:first-child {
|
||||
font-weight: bold;
|
||||
background-color: #f5f5f5;
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
.summary-table td:last-child {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.total-row {
|
||||
background-color: #2e7d32;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.terms-section {
|
||||
margin-top: 30px;
|
||||
border-top: 1px solid #ddd;
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.terms-section h4 {
|
||||
color: #2e7d32;
|
||||
font-size: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.terms-section p {
|
||||
font-size: 11px;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.footer-note {
|
||||
text-align: center;
|
||||
margin-top: 40px;
|
||||
border-top: 2px solid #ddd;
|
||||
padding-top: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- HEADER SECTION -->
|
||||
<div class="header-section">
|
||||
<div>
|
||||
<% if (logoBase64) { %>
|
||||
<img src="<%= logoBase64 %>" alt="Company Logo" />
|
||||
<% } %>
|
||||
</div>
|
||||
<div style="flex: 1; text-align: center;">
|
||||
<div class="doc-title">PURCHASE ORDER</div>
|
||||
</div>
|
||||
<div class="po-number">
|
||||
<strong>PO #:</strong> <%= poNumber || 'N/A' %><br/>
|
||||
<strong>Date:</strong> <%= date || 'N/A' %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- INFO GRID -->
|
||||
<div class="info-grid">
|
||||
<div class="info-left">
|
||||
<div class="info-row">
|
||||
<span class="info-label">VENDOR:</span>
|
||||
</div>
|
||||
<div style="margin-left: 0;">
|
||||
<strong><%= vendorName || 'Vendor Name' %></strong><br/>
|
||||
<%= vendorAddress || 'Address' %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-right">
|
||||
<div class="info-row">
|
||||
<span class="info-label">DELIVERY DATE:</span>
|
||||
<span class="info-value"><%= deliveryDate || 'N/A' %></span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">PAYMENT TERMS:</span>
|
||||
<span class="info-value"><%= paymentTerms || 'Net 30' %></span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">VESSEL:</span>
|
||||
<span class="info-value"><%= vesselName || 'N/A' %></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ITEMS TABLE -->
|
||||
<div style="overflow-x: auto;">
|
||||
<table class="items-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ITEM NO</th>
|
||||
<th>DESCRIPTION</th>
|
||||
<th>QTY</th>
|
||||
<th>UNIT</th>
|
||||
<th>UNIT PRICE</th>
|
||||
<th>TOTAL</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% if (items && items.length > 0) { %>
|
||||
<% items.forEach((item, index) => { %>
|
||||
<tr>
|
||||
<td><%= index + 1 %></td>
|
||||
<td><%= item.description || item.item_name || '' %></td>
|
||||
<td><%= item.quantity || 0 %></td>
|
||||
<td><%= item.unit || '' %></td>
|
||||
<td><%= item.unit_price || 0 %></td>
|
||||
<td><%= (item.quantity || 0) * (item.unit_price || 0) %></td>
|
||||
</tr>
|
||||
<% }); %>
|
||||
<% } %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- SUMMARY SECTION -->
|
||||
<div class="summary-area">
|
||||
<table class="summary-table">
|
||||
<tr>
|
||||
<td>SUB TOTAL</td>
|
||||
<td><%= subtotal || 0 %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>TAX (%)</td>
|
||||
<td><%= tax || 0 %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SHIPPING</td>
|
||||
<td><%= shipping || 0 %></td>
|
||||
</tr>
|
||||
<tr class="total-row">
|
||||
<td>TOTAL</td>
|
||||
<td><%= total || 0 %></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- TERMS SECTION -->
|
||||
<% if (paymentTermsDetails || notes) { %>
|
||||
<div class="terms-section">
|
||||
<% if (paymentTermsDetails) { %>
|
||||
<h4>TERMS & CONDITIONS</h4>
|
||||
<p><%= paymentTermsDetails %></p>
|
||||
<% } %>
|
||||
<% if (notes) { %>
|
||||
<h4>SPECIAL INSTRUCTIONS</h4>
|
||||
<p><%= notes %></p>
|
||||
<% } %>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<div class="footer-note">
|
||||
<p style="font-size: 10px; color: #999;">Please confirm receipt and delivery dates</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Niolla
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is proprietary and confidential.
|
||||
* Unauthorized copying, modification, distribution, or use
|
||||
* of this file, via any medium, is strictly prohibited.
|
||||
*/
|
||||
|
||||
// app/templates/documents/po/po.template.js
|
||||
|
||||
const { log } = require("../../../utils/consoleLog.utill");
|
||||
|
||||
module.exports = async (data) => {
|
||||
log("📋 Preparing Purchase Order data for PDF...");
|
||||
|
||||
const templateData = {
|
||||
logoUrl: "https://res.cloudinary.com/dtthuvvir/image/upload/v1780748661/company_logo_transperent_bg_uullzw.png",
|
||||
|
||||
poNumber: data.poNumber || data.po_number || "PO-001",
|
||||
date: data.date || new Date().toLocaleDateString(),
|
||||
|
||||
vendorName: data.vendorName || data.vendor_name || "Vendor Name",
|
||||
vendorAddress: data.vendorAddress || data.vendor_address || "Address",
|
||||
|
||||
deliveryDate: data.deliveryDate || data.delivery_date || "N/A",
|
||||
paymentTerms: data.paymentTerms || data.payment_terms || "Net 30",
|
||||
vesselName: data.vesselName || data.vessel_name || "N/A",
|
||||
|
||||
items: data.items || [],
|
||||
|
||||
subtotal: data.subtotal || data.sub_total || 0,
|
||||
tax: data.tax || 0,
|
||||
shipping: data.shipping || 0,
|
||||
total: data.total || 0,
|
||||
|
||||
paymentTermsDetails: data.paymentTermsDetails || data.payment_terms_details || "",
|
||||
notes: data.notes || "",
|
||||
};
|
||||
|
||||
log("✅ Purchase Order data prepared");
|
||||
|
||||
return templateData;
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Niolla
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is proprietary and confidential.
|
||||
* Unauthorized copying, modification, distribution, or use
|
||||
* of this file, via any medium, is strictly prohibited.
|
||||
*/
|
||||
|
||||
// app/templates/documents/precost/preCost.template.js
|
||||
|
||||
const { log } = require("../../../utils/consoleLog.utill");
|
||||
|
||||
module.exports = async (data) => {
|
||||
log("📋 Preparing PreCost data for PDF...");
|
||||
log("📥 Incoming data:", JSON.stringify(data, null, 2));
|
||||
|
||||
const templateData = {
|
||||
logoUrl:
|
||||
"https://res.cloudinary.com/dtthuvvir/image/upload/v1780748661/company_logo_transperent_bg_uullzw.png",
|
||||
|
||||
vesselName: data.vessel_name || "MV Vessel",
|
||||
|
||||
supplierName: data.supplyPort || "N/A",
|
||||
clientRfqNum: data.clientRfqNum || "N/A",
|
||||
omsRfqNum: data.omsRfqNum || "N/A",
|
||||
date: data.date || new Date().toLocaleDateString(),
|
||||
|
||||
items: data.items || [],
|
||||
|
||||
subtotal: data.sub_total || 0,
|
||||
discount: data.discount || 0,
|
||||
additionalCharges: data.additionalCharges || 0,
|
||||
fullTotal: data.total_cost || data.total_cost_usd || (data.sub_total - data.discount + data.additionalCharges) || 0,
|
||||
};
|
||||
|
||||
log("Data:", data);
|
||||
log("fullTotal calculation:", {
|
||||
total_cost: data.total_cost,
|
||||
total_cost_usd: data.total_cost_usd,
|
||||
calculated: data.sub_total - data.discount + data.additionalCharges,
|
||||
final: templateData.fullTotal,
|
||||
});
|
||||
|
||||
log("✅ PreCost data prepared:", JSON.stringify(templateData, null, 2));
|
||||
|
||||
return templateData;
|
||||
};
|
||||
@@ -0,0 +1,300 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background: white;
|
||||
font-family: 'Segoe UI', 'Roboto', 'Helvetica Neue', sans-serif;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.header-section {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30px;
|
||||
border-bottom: 2px solid #1a7a4d;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.header-section img {
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.po-title {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #1a7a4d;
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
background: #e8f5e9;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.info-left,
|
||||
.info-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-weight: bold;
|
||||
color: #2e7d32;
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.items-table thead {
|
||||
background-color: #2e7d32;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.items-table th {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.items-table td {
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.items-table tbody tr:nth-child(even) {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.summary-area {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.summary-table {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.summary-table td {
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.summary-table td:first-child {
|
||||
font-weight: bold;
|
||||
background-color: #f5f5f5;
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
.summary-table td:last-child {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.total-row {
|
||||
background-color: #2e7d32;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.total-row td {
|
||||
border-color: #1a7a4d;
|
||||
}
|
||||
|
||||
.footer-note {
|
||||
text-align: center;
|
||||
margin-top: 40px;
|
||||
border-top: 2px solid #ddd;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.footer-note img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.text-black{
|
||||
color: black !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- HEADER SECTION -->
|
||||
<div class="header-section">
|
||||
<div>
|
||||
<% if (logoBase64) { %>
|
||||
<img src="<%= logoBase64 %>" alt="Company Logo" />
|
||||
<% } %>
|
||||
</div>
|
||||
<div class="po-title">PRICE QUOTATION</div>
|
||||
</div>
|
||||
|
||||
<!-- INFO GRID -->
|
||||
<div class="info-grid">
|
||||
<div class="info-left">
|
||||
<div class="info-row">
|
||||
<span class="info-label">VESSEL NAME :</span>
|
||||
<span class="info-value">
|
||||
<%= vesselName %>
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">SUPPLY PORT :</span>
|
||||
<span class="info-value">
|
||||
<%= supplierName || 'Supplier Name' %>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-right">
|
||||
<div class="info-row">
|
||||
<span class="info-label">CLIENT RFQ :</span>
|
||||
<span class="info-value">
|
||||
<%= clientRfqNum || 'RFQ-001' %>
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">OMS RFQ :</span>
|
||||
<span class="info-value">
|
||||
<%= omsRfqNum || 'RFQ-001' %>
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">DATE :</span>
|
||||
<span class="info-value">
|
||||
<%= date || 'Current Date' %>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ITEMS TABLE -->
|
||||
<div style="overflow-x: auto;">
|
||||
<table class="items-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ITEM NO</th>
|
||||
<th>DESCRIPTION</th>
|
||||
<th>REMARKS</th>
|
||||
<th>QTY</th>
|
||||
<th>UNIT</th>
|
||||
<th>UNIT PRICE</th>
|
||||
<th>TOTAL PRICE</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% if (items && items.length> 0) { %>
|
||||
<% items.forEach((item, index)=> { %>
|
||||
<tr>
|
||||
<td>
|
||||
<%= index + 1 %>
|
||||
</td>
|
||||
<td>
|
||||
<%= item.item_name || '' %>
|
||||
</td>
|
||||
<td>
|
||||
<%= item.customer_remark || '' %>
|
||||
</td>
|
||||
<td>
|
||||
<%= item.quantity || 0 %>
|
||||
</td>
|
||||
<td>
|
||||
<%= item.unit || '' %>
|
||||
</td>
|
||||
<td>
|
||||
<%= item.unit_price || 0 %>
|
||||
</td>
|
||||
<td>
|
||||
<%= (item.quantity || 0) * (item.unit_price || 0) %>
|
||||
</td>
|
||||
</tr>
|
||||
<% }); %>
|
||||
<% } %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- SUMMARY SECTION -->
|
||||
<div class="summary-area">
|
||||
<table class="summary-table">
|
||||
<tr>
|
||||
<td>SUB TOTAL</td>
|
||||
<td>
|
||||
<%= subtotal || 0 %>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<% if (discount && Number(discount) !==0) { %>
|
||||
<tr>
|
||||
<td>DISCOUNT (%)</td>
|
||||
<td>
|
||||
<%= discount %>
|
||||
</td>
|
||||
</tr>
|
||||
<% } %>
|
||||
|
||||
<% if (additionalCharges && Number(additionalCharges) !==0) { %>
|
||||
<tr>
|
||||
<td>ADDITIONAL CHARGES<br />(CUSTOM/HANDLING/TRANSPORT)</td>
|
||||
<td>
|
||||
<%= additionalCharges %>
|
||||
</td>
|
||||
</tr>
|
||||
<% } %>
|
||||
|
||||
<tr class="total-row">
|
||||
<td class="text-black" >GRAND TOTAL</td>
|
||||
<td>
|
||||
<%= fullTotal || 0 %>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<div class="footer-note">
|
||||
<img src="https://res.cloudinary.com/dtthuvvir/image/upload/v1780748661/footer_ax2jyz.png" alt="Footer">
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,527 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background: white;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 2rem 1rem;
|
||||
font-family: 'Segoe UI', 'Roboto', 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
|
||||
|
||||
/* actual table style — clean, border-collapse */
|
||||
.po-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.85rem;
|
||||
font-family: 'Segoe UI', system-ui, 'Calibri', sans-serif;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
/* header sections & company info area (using table-like but not inside main table) */
|
||||
.header-section {
|
||||
margin-bottom: 1.8rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.po-title {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1px;
|
||||
color: #257339;
|
||||
border-top: 2px solid #257339;
|
||||
border-bottom: 2px solid #257339;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
margin-top: 1.5rem;
|
||||
font-family: 'Inter', 'Segoe UI', sans-serif;
|
||||
}
|
||||
|
||||
/* two column info grid (company, supplier, dates etc) */
|
||||
.info-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
gap: 1.2rem;
|
||||
margin-bottom: 2rem;
|
||||
background: #e1f3db;
|
||||
padding: 1rem 1.2rem;
|
||||
border: 1px solid #e2edf2;
|
||||
}
|
||||
|
||||
.info-left {
|
||||
flex: 2;
|
||||
min-width: 240px;
|
||||
}
|
||||
|
||||
.info-right {
|
||||
flex: 1.2;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 12px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-weight: 700;
|
||||
width: 140px;
|
||||
color: #257339;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-weight: 500;
|
||||
color: #1f2d3d;
|
||||
border-bottom: 1px dashed #bdd3e6;
|
||||
min-width: 200px;
|
||||
padding: 0 0.2rem;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.inline-placeholder {
|
||||
color: #2c7da0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
/* main items table */
|
||||
.items-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 1.2rem 0 1rem;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.items-table th,
|
||||
.items-table td {
|
||||
border: 1px solid #cddfe7;
|
||||
padding: 10px 6px;
|
||||
vertical-align: top;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.items-table th {
|
||||
background-color: #257339;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
font-size: 0.8rem;
|
||||
letter-spacing: 0.5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.items-table td {
|
||||
background-color: #fff;
|
||||
color: #1e2a3a;
|
||||
}
|
||||
|
||||
.items-table input,
|
||||
.items-table .pseudo-input {
|
||||
width: 100%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
font-family: inherit;
|
||||
font-size: 0.8rem;
|
||||
padding: 4px 2px;
|
||||
outline: none;
|
||||
border-bottom: 1px dotted #acc8dd;
|
||||
}
|
||||
|
||||
.items-table input:focus {
|
||||
border-bottom: 1px solid #1f6e8c;
|
||||
background: #fefce8;
|
||||
}
|
||||
|
||||
/* numeric cells alignment */
|
||||
.numeric-cell {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.qty-cell input,
|
||||
.price-cell input {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.total-cell {
|
||||
font-weight: 600;
|
||||
background-color: #f8fafc;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* summary section (subtotal, discount, transport) */
|
||||
.summary-area {
|
||||
margin-top: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
border-top: 2px solid #cbdde6;
|
||||
padding-top: 1.2rem;
|
||||
}
|
||||
|
||||
.summary-table {
|
||||
width: 320px;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.summary-table td {
|
||||
padding: 6px 8px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.summary-table td:first-child {
|
||||
font-weight: 700;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.summary-table td:last-child {
|
||||
text-align: right;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.total-row {
|
||||
border-top: 2px solid #2c5a74;
|
||||
font-weight: 800;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.discount-input,
|
||||
.transport-input {
|
||||
border: 1px solid #b9d0df;
|
||||
border-radius: 8px;
|
||||
padding: 4px 10px;
|
||||
width: 110px;
|
||||
text-align: right;
|
||||
font-weight: 500;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.inline-action {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
button.recalc {
|
||||
background: #eef2f5;
|
||||
border: 1px solid #b9cfdf;
|
||||
border-radius: 30px;
|
||||
padding: 4px 12px;
|
||||
font-size: 0.7rem;
|
||||
cursor: pointer;
|
||||
transition: 0.1s;
|
||||
margin-left: 10px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
button.recalc:hover {
|
||||
background: #cbdde6;
|
||||
}
|
||||
|
||||
.footer-note {
|
||||
margin-top: 2rem;
|
||||
font-size: 0.7rem;
|
||||
text-align: center;
|
||||
color: #547b97;
|
||||
border-top: 1px solid #d4e2ec;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 750px) {
|
||||
.po-container {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.info-grid {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.summary-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.items-table th,
|
||||
.items-table td {
|
||||
padding: 6px 3px;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
}
|
||||
|
||||
.editable-highlight {
|
||||
background-color: #fef9e3;
|
||||
} * {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background: white;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 2rem 1rem;
|
||||
font-family: 'Segoe UI', 'Roboto', 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
|
||||
|
||||
/* actual table style — clean, border-collapse */
|
||||
.po-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.85rem;
|
||||
font-family: 'Segoe UI', system-ui, 'Calibri', sans-serif;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
/* header sections & company info area (using table-like but not inside main table) */
|
||||
.header-section {
|
||||
margin-bottom: 1.8rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.po-title {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1px;
|
||||
color: #257339;
|
||||
border-top: 2px solid #257339;
|
||||
border-bottom: 2px solid #257339;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
margin-top: 1.5rem;
|
||||
font-family: 'Inter', 'Segoe UI', sans-serif;
|
||||
}
|
||||
|
||||
/* two column info grid (company, supplier, dates etc) */
|
||||
.info-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
gap: 1.2rem;
|
||||
margin-bottom: 2rem;
|
||||
background: #e1f3db;
|
||||
padding: 1rem 1.2rem;
|
||||
border: 1px solid #e2edf2;
|
||||
}
|
||||
|
||||
.info-left {
|
||||
flex: 2;
|
||||
min-width: 240px;
|
||||
}
|
||||
|
||||
.info-right {
|
||||
flex: 1.2;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 12px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-weight: 700;
|
||||
width: 140px;
|
||||
color: #257339;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-weight: 500;
|
||||
color: #1f2d3d;
|
||||
border-bottom: 1px dashed #bdd3e6;
|
||||
min-width: 200px;
|
||||
padding: 0 0.2rem;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.inline-placeholder {
|
||||
color: #2c7da0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
/* main items table */
|
||||
.items-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 1.2rem 0 1rem;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.items-table th,
|
||||
.items-table td {
|
||||
border: 1px solid #cddfe7;
|
||||
padding: 10px 6px;
|
||||
vertical-align: top;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.items-table th {
|
||||
background-color: #257339;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
font-size: 0.8rem;
|
||||
letter-spacing: 0.5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.items-table td {
|
||||
background-color: #fff;
|
||||
color: #1e2a3a;
|
||||
}
|
||||
|
||||
.items-table input,
|
||||
.items-table .pseudo-input {
|
||||
width: 100%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
font-family: inherit;
|
||||
font-size: 0.8rem;
|
||||
padding: 4px 2px;
|
||||
outline: none;
|
||||
border-bottom: 1px dotted #acc8dd;
|
||||
}
|
||||
|
||||
.items-table input:focus {
|
||||
border-bottom: 1px solid #1f6e8c;
|
||||
background: #fefce8;
|
||||
}
|
||||
|
||||
/* numeric cells alignment */
|
||||
.numeric-cell {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.qty-cell input,
|
||||
.price-cell input {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.total-cell {
|
||||
font-weight: 600;
|
||||
background-color: #f8fafc;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* summary section (subtotal, discount, transport) */
|
||||
.summary-area {
|
||||
margin-top: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
border-top: 2px solid #cbdde6;
|
||||
padding-top: 1.2rem;
|
||||
}
|
||||
|
||||
.summary-table {
|
||||
width: 320px;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.summary-table td {
|
||||
padding: 6px 8px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.summary-table td:first-child {
|
||||
font-weight: 700;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.summary-table td:last-child {
|
||||
text-align: right;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.total-row {
|
||||
border-top: 2px solid #2c5a74;
|
||||
font-weight: 800;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.discount-input,
|
||||
.transport-input {
|
||||
border: 1px solid #b9d0df;
|
||||
border-radius: 8px;
|
||||
padding: 4px 10px;
|
||||
width: 110px;
|
||||
text-align: right;
|
||||
font-weight: 500;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.inline-action {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
button.recalc {
|
||||
background: #eef2f5;
|
||||
border: 1px solid #b9cfdf;
|
||||
border-radius: 30px;
|
||||
padding: 4px 12px;
|
||||
font-size: 0.7rem;
|
||||
cursor: pointer;
|
||||
transition: 0.1s;
|
||||
margin-left: 10px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
button.recalc:hover {
|
||||
background: #cbdde6;
|
||||
}
|
||||
|
||||
.footer-note {
|
||||
margin-top: 2rem;
|
||||
font-size: 0.7rem;
|
||||
text-align: center;
|
||||
color: #547b97;
|
||||
border-top: 1px solid #d4e2ec;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 750px) {
|
||||
.po-container {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.info-grid {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.summary-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.items-table th,
|
||||
.items-table td {
|
||||
padding: 6px 3px;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
}
|
||||
|
||||
.editable-highlight {
|
||||
background-color: #fef9e3;
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<style>
|
||||
{{styles}}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div>
|
||||
<!-- PURCHASE ORDER title -->
|
||||
<div class="header-section">
|
||||
<div>
|
||||
<img src="{{logoBase64}}" alt="Company Logo" style="height:80px; margin-bottom:-8px;" />
|
||||
</div>
|
||||
<div class="po-title">PRICE QUOTATION</div>
|
||||
</div>
|
||||
|
||||
<!-- Company & supplier info (dynamic / editable) -->
|
||||
<div class="info-grid">
|
||||
<div class="info-left">
|
||||
<div class="info-row">
|
||||
<span class="info-label">VESSEL NAME :</span>
|
||||
<span class="info-value" id="companyName">Oceanic Maritime Solutions (Pvt) Ltd</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">SUPPLY PORT :</span>
|
||||
<span class="info-value" id="supplierName">Supplier Name</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-right">
|
||||
<div class="info-row">
|
||||
<span class="info-label">CLIENT RFQ :</span>
|
||||
<span class="info-value" id="rfqNum">RFQ-001</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">OMS RFQ :</span>
|
||||
<span class="info-value" id="rfqNum">RFQ-001</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">DATE :</span>
|
||||
<span class="info-value" id="orderDate">Current Date</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- items table: ITEM NO, DESCRIPTION, REMARKS, UNIT, QTY, UNIT PRICE, TOTAL PRICE -->
|
||||
<div style="overflow-x: auto;">
|
||||
<table class="items-table" id="poItemsTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ITEM NO</th>
|
||||
<th>DESCRIPTION</th>
|
||||
<th>REMARKS</th>
|
||||
<th>QTY</th>
|
||||
<th>UNIT</th>
|
||||
<th>UNIT PRICE</th>
|
||||
<th>TOTAL PRICE</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="itemsTableBody">
|
||||
{{items}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- summary: subtotal, discount, transportation, full total -->
|
||||
<div class="summary-area">
|
||||
<table class="summary-table" id="summaryTable">
|
||||
<tr>
|
||||
<td>SUB TOTAL</td>
|
||||
<td id="subTotalVal">{{subtotal}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>DISCOUNT <span style="font-weight:normal;">(%)</span>
|
||||
</td>
|
||||
<td id="discountAmount">{{discount}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ADDITIONAL CHARGES (CUSTOM/HANDLING/TRANSPORT)
|
||||
</td>
|
||||
<td id="transportDisplay">{{transport}}</td>
|
||||
</tr>
|
||||
<tr class="total-row">
|
||||
<td>GRAND TOTAL</td>
|
||||
<td id="fullTotalVal">{{fullTotal}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="footer-note">
|
||||
<img src="https://res.cloudinary.com/dtthuvvir/image/upload/v1780748661/footer_ax2jyz.png" alt="Footer">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user