GWM : GRN pdf
This commit is contained in:
parent
7cd180b4e9
commit
75f1758c01
@ -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`,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user