MEGAINFRA-WEBAPP/scripts/n8n/po-build-callback.js

79 lines
3.2 KiB
JavaScript
Raw Permalink Normal View History

const sourceItem = $input.item;
if (!sourceItem) {
throw new Error('Build Callback Payload: no input item');
}
const parsed = (sourceItem.json && typeof sourceItem.json.output === 'object' && sourceItem.json.output !== null)
? sourceItem.json.output
: sourceItem.json;
// This item's id is the purchase_orders row Atomic Claim just claimed --
// the placeholder created at upload time (src/lib/po-placeholder.ts).
// There is no separate batch row anymore.
const purchase_order_id = $('Atomic Claim').item.json.id;
const po_number_extracted = String(parsed.po_number || '').trim();
const supplier = String(parsed.supplier || '').trim();
const project_code = String(parsed.project_code || '').trim();
if (!po_number_extracted) {
throw new Error('AI extraction returned an empty PO number');
}
const po_number_normalized = po_number_extracted
.replace(/^p\.?o\.?([-#.]|\s+)?/i, '')
.replace(/\s+/g, ' ')
.trim()
.toUpperCase();
if (!po_number_normalized) {
throw new Error('AI Agent po_number normalises to empty string: "' + po_number_extracted + '"');
}
const required_by_date = parsed.required_by_date || undefined;
// "PO Creation Date" printed on the PO: when it was ordered. Money is dated by it.
const order_date = /^\d{4}-\d{2}-\d{2}$/.test(String(parsed.order_date || '')) ? String(parsed.order_date) : undefined;
const ordered_by = parsed.ordered_by || undefined;
const rawItems = Array.isArray(parsed.items) ? parsed.items : [];
const items = rawItems.map(it => {
if (!it) return null;
return {
...it,
qty: Number(it.qty),
rate: Number(it.rate),
material_code: (it.material_code && String(it.material_code).trim() !== '' && String(it.material_code).toLowerCase() !== 'null') ? String(it.material_code).trim() : '',
cost_code: (it.cost_code && String(it.cost_code).trim() !== '' && String(it.cost_code).toLowerCase() !== 'null') ? String(it.cost_code).trim() : '',
disc_percent: (it.disc_percent !== null && it.disc_percent !== undefined && !isNaN(it.disc_percent)) ? Number(it.disc_percent) : 0,
vat_percent: (it.vat_percent !== null && it.vat_percent !== undefined && !isNaN(it.vat_percent)) ? Number(it.vat_percent) : 15
};
}).filter(it =>
it &&
typeof it.description === 'string' && it.description.trim().length > 0 &&
!isNaN(it.qty) && it.qty >= 0 &&
!isNaN(it.rate) && it.rate >= 0
);
if (items.length === 0) {
throw new Error('AI Agent returned no valid line items (all missing description/qty/rate)');
}
// Duplicate detection now lives server-side in
// /api/purchase-orders/process-batch (merge onto the existing PO, delete
// this placeholder) -- it already owns the purchase_orders schema and is
// covered by tests. n8n no longer queries or writes purchase_orders
// directly to pre-empt it.
//
// purchase_order_number is the exact field name the route validates --
// the old contract used po_number here, which would fail every upload.
return {
json: {
purchase_order_id,
extracted: {
purchase_order_number: po_number_normalized,
po_number_extracted,
supplier,
project_code,
required_by_date,
order_date,
ordered_by,
items,
raw_ocr: { source: 'ai-agent', model: 'MiniMax-M3' },
},
},
};