GWM : GRN pdf

This commit is contained in:
Gowtham M 2026-07-15 18:08:05 +05:30
parent 7cd180b4e9
commit 75f1758c01
2 changed files with 62 additions and 129 deletions

View File

@ -174,30 +174,22 @@ const buildVendorAddressLine = (address) => {
const buildGrnPdfPayload = (grn, company) => { const buildGrnPdfPayload = (grn, company) => {
const vendorAddress = pickVendorAddress(grn.vendor?.vendor_addresses || []); const vendorAddress = pickVendorAddress(grn.vendor?.vendor_addresses || []);
const items = (grn.items || []).map((line) => { const items = (grn.items || []).map((line) => ({
const acceptedQty = toNum(line.accepted_qty); line_no: line.line_no,
const rejectedQty = toNum(line.rejected_qty); item_name: line.item?.item_name || '-',
const rate = toNum(line.rate); item_code: line.item?.item_code || null,
return { uom: line.uom?.code || line.uom?.name || '-',
line_no: line.line_no, ordered_qty: toNum(line.ordered_qty),
item_name: line.item?.item_name || '-', current_qty: toNum(line.current_qty),
item_code: line.item?.item_code || null, accepted_qty: toNum(line.accepted_qty),
remarks: line.remarks || line.rejection_reason || null, rejected_qty: toNum(line.rejected_qty),
uom: line.uom?.code || line.uom?.name || '-', batch_no: line.batch_no || null,
ordered_qty: toNum(line.ordered_qty), mfg_date: line.mfg_date || null,
current_qty: toNum(line.current_qty), expiry_date: line.expiry_date || null,
accepted_qty: acceptedQty, rejection_reason: line.rejection_reason || null,
rejected_qty: rejectedQty, storage_location: line.storage_location || null,
rate, remarks: line.remarks || null,
line_total: acceptedQty * rate, }));
};
});
const acceptedValue = items.reduce((sum, line) => sum + toNum(line.line_total), 0);
const rejectedValue = items.reduce(
(sum, line) => sum + toNum(line.rejected_qty) * toNum(line.rate),
0
);
return { return {
company: { company: {
@ -211,8 +203,6 @@ const buildGrnPdfPayload = (grn, company) => {
po_number: grn.purchase_order?.po_number || '-', po_number: grn.purchase_order?.po_number || '-',
vendor_invoice_no: grn.vendor_invoice_no || null, vendor_invoice_no: grn.vendor_invoice_no || null,
vendor_invoice_date: grn.vendor_invoice_date || null, vendor_invoice_date: grn.vendor_invoice_date || null,
vendor_invoice_amount:
grn.vendor_invoice_amount != null ? toNum(grn.vendor_invoice_amount) : null,
vehicle_no: grn.vehicle_no || null, vehicle_no: grn.vehicle_no || null,
lr_no: grn.lr_no || null, lr_no: grn.lr_no || null,
lr_date: grn.lr_date || null, lr_date: grn.lr_date || null,
@ -236,11 +226,6 @@ const buildGrnPdfPayload = (grn, company) => {
pincode: grn.warehouse?.pincode || '', pincode: grn.warehouse?.pincode || '',
}, },
items, items,
totals: {
accepted_value: acceptedValue,
rejected_value: rejectedValue,
grand_total: acceptedValue,
},
generated_at: `${formatDate(new Date(), { style: 'datetime' })} IST`, generated_at: `${formatDate(new Date(), { style: 'datetime' })} IST`,
}; };
}; };

View File

@ -1,6 +1,4 @@
const { formatCurrency } = require('../helpers/formatCurrency');
const { formatDate } = require('../helpers/formatDate'); const { formatDate } = require('../helpers/formatDate');
const { numberToWords } = require('../helpers/numberToWords');
const getDummyGrnData = () => ({ const getDummyGrnData = () => ({
company: { company: {
@ -20,7 +18,6 @@ const getDummyGrnData = () => ({
po_number: 'PO/2026-27/00007', po_number: 'PO/2026-27/00007',
vendor_invoice_no: 'TSL/INV/2026/441', vendor_invoice_no: 'TSL/INV/2026/441',
vendor_invoice_date: '2026-07-13', vendor_invoice_date: '2026-07-13',
vendor_invoice_amount: 139943.75,
vehicle_no: 'TN-09-AB-4521', vehicle_no: 'TN-09-AB-4521',
lr_no: 'LR-77821', lr_no: 'LR-77821',
lr_date: '2026-07-13', lr_date: '2026-07-13',
@ -53,8 +50,12 @@ const getDummyGrnData = () => ({
current_qty: 25, current_qty: 25,
accepted_qty: 25, accepted_qty: 25,
rejected_qty: 0, rejected_qty: 0,
rate: 4250, batch_no: 'BAT-TMT-2407',
line_total: 106250, mfg_date: '2026-06-01',
expiry_date: null,
rejection_reason: null,
storage_location: 'Bay A-12',
remarks: null,
}, },
{ {
line_no: 2, line_no: 2,
@ -65,28 +66,30 @@ const getDummyGrnData = () => ({
current_qty: 500, current_qty: 500,
accepted_qty: 500, accepted_qty: 500,
rejected_qty: 0, rejected_qty: 0,
rate: 380, batch_no: 'CEM-JUN-26',
line_total: 190000, mfg_date: '2026-05-15',
expiry_date: '2027-05-15',
rejection_reason: null,
storage_location: 'Shed B',
remarks: null,
}, },
{ {
line_no: 3, line_no: 3,
item_name: 'River sand', item_name: 'River sand',
item_code: 'ITM-SND-01', item_code: 'ITM-SND-01',
remarks: '2 bags damaged in transit',
uom: 'CFT', uom: 'CFT',
ordered_qty: 1200, ordered_qty: 1200,
current_qty: 1200, current_qty: 1200,
accepted_qty: 1180, accepted_qty: 1180,
rejected_qty: 20, rejected_qty: 20,
rate: 45, batch_no: null,
line_total: 53100, mfg_date: null,
expiry_date: null,
rejection_reason: 'Damaged bags in transit',
storage_location: 'Yard 2',
remarks: 'Confirm bag count on delivery',
}, },
], ],
totals: {
accepted_value: 349350,
rejected_value: 900,
grand_total: 349350,
},
generated_at: '14/07/2026 09:40', generated_at: '14/07/2026 09:40',
}); });
@ -116,19 +119,26 @@ const qtyDisplay = (value) => {
return Number.isInteger(num) ? String(num) : num.toFixed(2); return Number.isInteger(num) ? String(num) : num.toFixed(2);
}; };
const displayOrDash = (value) => {
if (value == null || String(value).trim() === '') return '-';
return String(value);
};
const generateGrnHtml = (inputData = getDummyGrnData()) => { const generateGrnHtml = (inputData = getDummyGrnData()) => {
const data = inputData || getDummyGrnData(); const data = inputData || getDummyGrnData();
const company = data.company || {}; const company = data.company || {};
const grn = data.grn || {}; const grn = data.grn || {};
const vendor = data.vendor || {}; const vendor = data.vendor || {};
const warehouse = data.warehouse || {}; const warehouse = data.warehouse || {};
const totals = data.totals || {};
const itemsRows = (data.items || []) const itemsRows = (data.items || [])
.map((item, index) => { .map((item, index) => {
const code = item.item_code const code = item.item_code
? `<div class="item-code">${escapeHtml(item.item_code)}</div>` ? `<div class="item-code">${escapeHtml(item.item_code)}</div>`
: ''; : '';
const storage = item.storage_location
? `<div class="item-note">Location: ${escapeHtml(item.storage_location)}</div>`
: '';
const remarks = item.remarks const remarks = item.remarks
? `<div class="item-note">*${escapeHtml(item.remarks)}</div>` ? `<div class="item-note">*${escapeHtml(item.remarks)}</div>`
: ''; : '';
@ -138,6 +148,7 @@ const generateGrnHtml = (inputData = getDummyGrnData()) => {
<td class="col-item"> <td class="col-item">
<div class="item-name">${escapeHtml(item.item_name || '-')}</div> <div class="item-name">${escapeHtml(item.item_name || '-')}</div>
${code} ${code}
${storage}
${remarks} ${remarks}
</td> </td>
<td class="col-qty text-right">${qtyDisplay(item.ordered_qty)}</td> <td class="col-qty text-right">${qtyDisplay(item.ordered_qty)}</td>
@ -145,8 +156,10 @@ const generateGrnHtml = (inputData = getDummyGrnData()) => {
<td class="col-qty text-right">${qtyDisplay(item.accepted_qty)}</td> <td class="col-qty text-right">${qtyDisplay(item.accepted_qty)}</td>
<td class="col-qty text-right">${qtyDisplay(item.rejected_qty)}</td> <td class="col-qty text-right">${qtyDisplay(item.rejected_qty)}</td>
<td class="col-uom">${escapeHtml(item.uom || '-')}</td> <td class="col-uom">${escapeHtml(item.uom || '-')}</td>
<td class="col-rate text-right">${formatCurrency(item.rate)}</td> <td class="col-batch">${escapeHtml(displayOrDash(item.batch_no))}</td>
<td class="col-amt text-right">${formatCurrency(item.line_total)}</td> <td class="col-date">${formatDate(item.mfg_date, { style: 'numeric' })}</td>
<td class="col-date">${formatDate(item.expiry_date, { style: 'numeric' })}</td>
<td class="col-reason">${escapeHtml(displayOrDash(item.rejection_reason))}</td>
</tr> </tr>
`; `;
}) })
@ -288,8 +301,8 @@ const generateGrnHtml = (inputData = getDummyGrnData()) => {
padding: 8px 3px; padding: 8px 3px;
border-bottom: 1.5px solid #111111; border-bottom: 1.5px solid #111111;
color: #9a9a9a; color: #9a9a9a;
font-size: 8.5px; font-size: 8px;
letter-spacing: 0.06em; letter-spacing: 0.05em;
text-transform: uppercase; text-transform: uppercase;
font-weight: 600; font-weight: 600;
text-align: left; text-align: left;
@ -301,14 +314,15 @@ const generateGrnHtml = (inputData = getDummyGrnData()) => {
padding: 9px 3px; padding: 9px 3px;
border-bottom: 1px solid #e5e5e5; border-bottom: 1px solid #e5e5e5;
vertical-align: top; vertical-align: top;
font-size: 10.5px; font-size: 10px;
} }
.col-no { width: 4%; color: #666666; } .col-no { width: 3%; color: #666666; }
.col-item { width: 28%; } .col-item { width: 20%; }
.col-qty { width: 8%; } .col-qty { width: 7%; }
.col-uom { width: 6%; } .col-uom { width: 5%; }
.col-rate { width: 11%; } .col-batch { width: 10%; }
.col-amt { width: 13%; } .col-date { width: 9%; }
.col-reason { width: 14%; }
.item-name { font-weight: 600; } .item-name { font-weight: 600; }
.item-code { .item-code {
margin-top: 1px; margin-top: 1px;
@ -318,49 +332,10 @@ const generateGrnHtml = (inputData = getDummyGrnData()) => {
.item-note { .item-note {
margin-top: 2px; margin-top: 2px;
color: #888888; color: #888888;
font-size: 9.5px; font-size: 9px;
font-style: italic; font-style: italic;
} }
.summary-wrap {
margin-top: 16px;
display: flex;
justify-content: flex-end;
}
.summary { width: 280px; }
.summary-row {
display: flex;
justify-content: space-between;
gap: 24px;
padding: 3px 0;
color: #444444;
font-size: 11px;
}
.summary-row .amount { color: #111111; font-weight: 600; }
.grand-row {
display: flex;
justify-content: space-between;
gap: 24px;
margin-top: 6px;
padding: 8px 0;
border-top: 1.5px solid #111111;
border-bottom: 1.5px solid #111111;
font-size: 14px;
font-weight: 800;
}
.amount-words {
margin-top: 10px;
border: 1px solid #cfcfcf;
background: #f7f7f7;
padding: 8px 10px;
}
.amount-words .words {
margin-top: 2px;
font-weight: 700;
font-size: 11px;
line-height: 1.4;
}
.notes { margin-top: 18px; } .notes { margin-top: 18px; }
.notes-block { margin-bottom: 12px; } .notes-block { margin-bottom: 12px; }
.notes-block .body { .notes-block .body {
@ -482,8 +457,10 @@ const generateGrnHtml = (inputData = getDummyGrnData()) => {
<th class="col-qty text-right">Accepted</th> <th class="col-qty text-right">Accepted</th>
<th class="col-qty text-right">Rejected</th> <th class="col-qty text-right">Rejected</th>
<th class="col-uom">UOM</th> <th class="col-uom">UOM</th>
<th class="col-rate text-right">Rate</th> <th class="col-batch">Batch</th>
<th class="col-amt text-right">Amount</th> <th class="col-date">Mfg date</th>
<th class="col-date">Expiry</th>
<th class="col-reason">Reason</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -491,35 +468,6 @@ const generateGrnHtml = (inputData = getDummyGrnData()) => {
</tbody> </tbody>
</table> </table>
<div class="summary-wrap">
<div class="summary">
<div class="summary-row">
<span>Accepted value</span>
<span class="amount">${formatCurrency(totals.accepted_value)}</span>
</div>
<div class="summary-row">
<span>Rejected value</span>
<span class="amount">${formatCurrency(totals.rejected_value)}</span>
</div>
${
grn.vendor_invoice_amount != null && grn.vendor_invoice_amount !== ''
? `<div class="summary-row">
<span>Vendor invoice amount</span>
<span class="amount">${formatCurrency(grn.vendor_invoice_amount)}</span>
</div>`
: ''
}
<div class="grand-row">
<span>Grand total</span>
<span>${formatCurrency(totals.grand_total)}</span>
</div>
<div class="amount-words">
<div class="label">Amount in words</div>
<div class="words">${escapeHtml(numberToWords(totals.grand_total))}</div>
</div>
</div>
</div>
<div class="notes"> <div class="notes">
${ ${
grn.remarks grn.remarks