GWM : GRN pdf
This commit is contained in:
parent
7cd180b4e9
commit
75f1758c01
@ -174,30 +174,22 @@ const buildVendorAddressLine = (address) => {
|
||||
|
||||
const buildGrnPdfPayload = (grn, company) => {
|
||||
const vendorAddress = pickVendorAddress(grn.vendor?.vendor_addresses || []);
|
||||
const items = (grn.items || []).map((line) => {
|
||||
const acceptedQty = toNum(line.accepted_qty);
|
||||
const rejectedQty = toNum(line.rejected_qty);
|
||||
const rate = toNum(line.rate);
|
||||
return {
|
||||
line_no: line.line_no,
|
||||
item_name: line.item?.item_name || '-',
|
||||
item_code: line.item?.item_code || null,
|
||||
remarks: line.remarks || line.rejection_reason || null,
|
||||
uom: line.uom?.code || line.uom?.name || '-',
|
||||
ordered_qty: toNum(line.ordered_qty),
|
||||
current_qty: toNum(line.current_qty),
|
||||
accepted_qty: acceptedQty,
|
||||
rejected_qty: rejectedQty,
|
||||
rate,
|
||||
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
|
||||
);
|
||||
const items = (grn.items || []).map((line) => ({
|
||||
line_no: line.line_no,
|
||||
item_name: line.item?.item_name || '-',
|
||||
item_code: line.item?.item_code || null,
|
||||
uom: line.uom?.code || line.uom?.name || '-',
|
||||
ordered_qty: toNum(line.ordered_qty),
|
||||
current_qty: toNum(line.current_qty),
|
||||
accepted_qty: toNum(line.accepted_qty),
|
||||
rejected_qty: toNum(line.rejected_qty),
|
||||
batch_no: line.batch_no || null,
|
||||
mfg_date: line.mfg_date || null,
|
||||
expiry_date: line.expiry_date || null,
|
||||
rejection_reason: line.rejection_reason || null,
|
||||
storage_location: line.storage_location || null,
|
||||
remarks: line.remarks || null,
|
||||
}));
|
||||
|
||||
return {
|
||||
company: {
|
||||
@ -211,8 +203,6 @@ const buildGrnPdfPayload = (grn, company) => {
|
||||
po_number: grn.purchase_order?.po_number || '-',
|
||||
vendor_invoice_no: grn.vendor_invoice_no || 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,
|
||||
lr_no: grn.lr_no || null,
|
||||
lr_date: grn.lr_date || null,
|
||||
@ -236,11 +226,6 @@ const buildGrnPdfPayload = (grn, company) => {
|
||||
pincode: grn.warehouse?.pincode || '',
|
||||
},
|
||||
items,
|
||||
totals: {
|
||||
accepted_value: acceptedValue,
|
||||
rejected_value: rejectedValue,
|
||||
grand_total: acceptedValue,
|
||||
},
|
||||
generated_at: `${formatDate(new Date(), { style: 'datetime' })} IST`,
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
const { formatCurrency } = require('../helpers/formatCurrency');
|
||||
const { formatDate } = require('../helpers/formatDate');
|
||||
const { numberToWords } = require('../helpers/numberToWords');
|
||||
|
||||
const getDummyGrnData = () => ({
|
||||
company: {
|
||||
@ -20,7 +18,6 @@ const getDummyGrnData = () => ({
|
||||
po_number: 'PO/2026-27/00007',
|
||||
vendor_invoice_no: 'TSL/INV/2026/441',
|
||||
vendor_invoice_date: '2026-07-13',
|
||||
vendor_invoice_amount: 139943.75,
|
||||
vehicle_no: 'TN-09-AB-4521',
|
||||
lr_no: 'LR-77821',
|
||||
lr_date: '2026-07-13',
|
||||
@ -53,8 +50,12 @@ const getDummyGrnData = () => ({
|
||||
current_qty: 25,
|
||||
accepted_qty: 25,
|
||||
rejected_qty: 0,
|
||||
rate: 4250,
|
||||
line_total: 106250,
|
||||
batch_no: 'BAT-TMT-2407',
|
||||
mfg_date: '2026-06-01',
|
||||
expiry_date: null,
|
||||
rejection_reason: null,
|
||||
storage_location: 'Bay A-12',
|
||||
remarks: null,
|
||||
},
|
||||
{
|
||||
line_no: 2,
|
||||
@ -65,28 +66,30 @@ const getDummyGrnData = () => ({
|
||||
current_qty: 500,
|
||||
accepted_qty: 500,
|
||||
rejected_qty: 0,
|
||||
rate: 380,
|
||||
line_total: 190000,
|
||||
batch_no: 'CEM-JUN-26',
|
||||
mfg_date: '2026-05-15',
|
||||
expiry_date: '2027-05-15',
|
||||
rejection_reason: null,
|
||||
storage_location: 'Shed B',
|
||||
remarks: null,
|
||||
},
|
||||
{
|
||||
line_no: 3,
|
||||
item_name: 'River sand',
|
||||
item_code: 'ITM-SND-01',
|
||||
remarks: '2 bags damaged in transit',
|
||||
uom: 'CFT',
|
||||
ordered_qty: 1200,
|
||||
current_qty: 1200,
|
||||
accepted_qty: 1180,
|
||||
rejected_qty: 20,
|
||||
rate: 45,
|
||||
line_total: 53100,
|
||||
batch_no: null,
|
||||
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',
|
||||
});
|
||||
|
||||
@ -116,19 +119,26 @@ const qtyDisplay = (value) => {
|
||||
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 data = inputData || getDummyGrnData();
|
||||
const company = data.company || {};
|
||||
const grn = data.grn || {};
|
||||
const vendor = data.vendor || {};
|
||||
const warehouse = data.warehouse || {};
|
||||
const totals = data.totals || {};
|
||||
|
||||
const itemsRows = (data.items || [])
|
||||
.map((item, index) => {
|
||||
const code = item.item_code
|
||||
? `<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
|
||||
? `<div class="item-note">*${escapeHtml(item.remarks)}</div>`
|
||||
: '';
|
||||
@ -138,6 +148,7 @@ const generateGrnHtml = (inputData = getDummyGrnData()) => {
|
||||
<td class="col-item">
|
||||
<div class="item-name">${escapeHtml(item.item_name || '-')}</div>
|
||||
${code}
|
||||
${storage}
|
||||
${remarks}
|
||||
</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.rejected_qty)}</td>
|
||||
<td class="col-uom">${escapeHtml(item.uom || '-')}</td>
|
||||
<td class="col-rate text-right">${formatCurrency(item.rate)}</td>
|
||||
<td class="col-amt text-right">${formatCurrency(item.line_total)}</td>
|
||||
<td class="col-batch">${escapeHtml(displayOrDash(item.batch_no))}</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>
|
||||
`;
|
||||
})
|
||||
@ -288,8 +301,8 @@ const generateGrnHtml = (inputData = getDummyGrnData()) => {
|
||||
padding: 8px 3px;
|
||||
border-bottom: 1.5px solid #111111;
|
||||
color: #9a9a9a;
|
||||
font-size: 8.5px;
|
||||
letter-spacing: 0.06em;
|
||||
font-size: 8px;
|
||||
letter-spacing: 0.05em;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
@ -301,14 +314,15 @@ const generateGrnHtml = (inputData = getDummyGrnData()) => {
|
||||
padding: 9px 3px;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
vertical-align: top;
|
||||
font-size: 10.5px;
|
||||
font-size: 10px;
|
||||
}
|
||||
.col-no { width: 4%; color: #666666; }
|
||||
.col-item { width: 28%; }
|
||||
.col-qty { width: 8%; }
|
||||
.col-uom { width: 6%; }
|
||||
.col-rate { width: 11%; }
|
||||
.col-amt { width: 13%; }
|
||||
.col-no { width: 3%; color: #666666; }
|
||||
.col-item { width: 20%; }
|
||||
.col-qty { width: 7%; }
|
||||
.col-uom { width: 5%; }
|
||||
.col-batch { width: 10%; }
|
||||
.col-date { width: 9%; }
|
||||
.col-reason { width: 14%; }
|
||||
.item-name { font-weight: 600; }
|
||||
.item-code {
|
||||
margin-top: 1px;
|
||||
@ -318,49 +332,10 @@ const generateGrnHtml = (inputData = getDummyGrnData()) => {
|
||||
.item-note {
|
||||
margin-top: 2px;
|
||||
color: #888888;
|
||||
font-size: 9.5px;
|
||||
font-size: 9px;
|
||||
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-block { margin-bottom: 12px; }
|
||||
.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">Rejected</th>
|
||||
<th class="col-uom">UOM</th>
|
||||
<th class="col-rate text-right">Rate</th>
|
||||
<th class="col-amt text-right">Amount</th>
|
||||
<th class="col-batch">Batch</th>
|
||||
<th class="col-date">Mfg date</th>
|
||||
<th class="col-date">Expiry</th>
|
||||
<th class="col-reason">Reason</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -491,35 +468,6 @@ const generateGrnHtml = (inputData = getDummyGrnData()) => {
|
||||
</tbody>
|
||||
</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">
|
||||
${
|
||||
grn.remarks
|
||||
|
||||
Loading…
Reference in New Issue
Block a user