import { string } from 'alga-js' import fetchHelper from './fetchHelper' /** * Productions (m_production) linked to the lines of a sales order via * M_Production.C_OrderLine_ID, plus a per-line aggregation (produced vs. * planned qty). Shared by `orders/[id]/productions.get.ts` (position chips) * and `orders/[id]/roadmap.get.ts` (order roadmap). * * iDempiere REST has no join filter (C_OrderLine_ID.C_Order_ID), so the line * ids are resolved first and the productions are queried with an OR chain * (the `in` operator is not reliable on this backend). Pass `lineIds` when the * caller already has the order lines to save the extra round trip. */ export interface OrderProductionRecord { id: number documentNo: string name: string description: string movementDate: string datePromised: string productionQty: number docStatusId: string docStatus: string processed: boolean isCreated: boolean orderLineId: number | null productId: number | null product: string bomId: number | null locator: string organizationId: number | null } export const mapProductionRecord = (p: any): OrderProductionRecord => { const docStatusId = p.DocStatus?.id || p.DocStatus || '' return { id: p.id, documentNo: p.DocumentNo || '', name: p.Name || '', description: p.Description || '', movementDate: p.MovementDate || '', datePromised: p.DatePromised || '', // Planned/produced quantity of the finished product. For a completed // production this IS the produced qty (the finished-good line is booked // with +ProductionQty); for drafts it's the planned qty. productionQty: Number(p.ProductionQty) || 0, docStatusId, docStatus: p.DocStatus?.identifier || docStatusId, processed: p.Processed === true, isCreated: (p.IsCreated?.id ?? p.IsCreated) === 'Y' || p.IsCreated === true, orderLineId: p.C_OrderLine_ID?.id || null, productId: p.M_Product_ID?.id || null, product: p.M_Product_ID?.identifier || '', bomId: p.PP_Product_BOM_ID?.id || null, locator: p.M_Locator_ID?.identifier || '', organizationId: p.AD_Org_ID?.id || null } } export const fetchOrderLineIds = async (event: any, token: any, orderId: number | string): Promise => { const id = Number(orderId) if (!Number.isFinite(id) || id <= 0) return [] const linesRes: any = await fetchHelper(event, `models/c_orderline?$filter=${string.urlEncode('C_Order_ID eq ' + id)}&$select=Line,QtyOrdered,M_Product_ID&$top=500`, 'GET', token, null) return (linesRes?.records || []).map((l: any) => Number(l.id)).filter((n: number) => Number.isFinite(n) && n > 0) } export const fetchOrderProductions = async (event: any, token: any, orderId: number | string, lineIds?: number[]) => { const ids = Array.isArray(lineIds) ? lineIds.filter((n) => Number.isFinite(n) && n > 0) : await fetchOrderLineIds(event, token, orderId) if (ids.length === 0) { return { records: [] as OrderProductionRecord[], byOrderLine: {} as Record } } const filter = ids.map((lid) => 'C_OrderLine_ID eq ' + lid).join(' OR ') const res: any = await fetchHelper(event, `models/m_production?$filter=${string.urlEncode(filter)}&$orderby=${string.urlEncode('MovementDate desc, Created desc')}&$top=200`, 'GET', token, null) const records: OrderProductionRecord[] = (res?.records || []).map(mapProductionRecord) // Per-line aggregation: total produced (completed/closed only) vs. planned. const byOrderLine: Record = {} for (const r of records) { if (!r.orderLineId) continue const key = String(r.orderLineId) if (!byOrderLine[key]) byOrderLine[key] = { productions: [], producedQty: 0, plannedQty: 0 } byOrderLine[key].productions.push(r) if (['CO', 'CL'].includes(r.docStatusId)) byOrderLine[key].producedQty += r.productionQty else if (!['VO', 'RE'].includes(r.docStatusId)) byOrderLine[key].plannedQty += r.productionQty } return { records, byOrderLine } }