import { string } from 'alga-js' import fetchHelper from './fetchHelper' import getTokenHelper from './getTokenHelper' // Server-side BACKSTOP for m_inout creation: iDempiere refuses an m_inout // without C_DocType_ID ("FillMandatory - Document Type"), and some callers // legitimately end up with an empty docTypeId (e.g. a failed client-side // doc-type lookup). Resolving a default here only ever runs when the body // carried no doc type — a case that today is a guaranteed error — and is // fail-soft: on lookup failure nothing is merged and the POST behaves exactly // as before. // // Discriminators verified against production c_doctype (DocBaseType + IsSOTrx): // MMS: customer shipment → DocBaseType MMS, IsSOTrx Y ("MM Shipment") // MCR: customer return → DocBaseType MMR, IsSOTrx Y ("MM Customer Return") // MMR: vendor receipt → DocBaseType MMR, IsSOTrx N ("MM Receipt") // MVR: vendor return → DocBaseType MMS, IsSOTrx N ("MM Vendor Return") export type InOutDocKind = 'MMS' | 'MCR' | 'MMR' | 'MVR' const TTL_MS = 10 * 60 * 1000 const cache = new Map() const FILTERS: Record = { MMS: "DocBaseType eq 'MMS' AND IsSOTrx eq true", MCR: "DocBaseType eq 'MMR' AND IsSOTrx eq true", MMR: "DocBaseType eq 'MMR' AND IsSOTrx eq false", MVR: "DocBaseType eq 'MMS' AND IsSOTrx eq false", } // Name tie-breakers matching /api/types/[id]'s conventions. const PREFERRED_NAME: Partial> = { MMS: 'MM Shipment', MCR: 'MM Customer Return', MMR: 'MM Receipt', MVR: 'MM Vendor Return', } // Map an inout create body (isSOTrx + MovementType) to the doc kind. export function inOutDocKind(isSOTrx: any, movementType?: string | null): InOutDocKind { const so = isSOTrx === true || isSOTrx === 'true' || isSOTrx === 'Y' const mt = String(movementType ?? '') if (so) return mt === 'C+' ? 'MCR' : 'MMS' return mt === 'V-' ? 'MVR' : 'MMR' } export async function resolveDefaultInOutDocTypeId(event: any, kind: InOutDocKind, authToken: any = null): Promise { const clientCookie = Number(getCookie(event, 'logship_client_id')) const clientId = Number.isFinite(clientCookie) && clientCookie > 0 ? clientCookie : null const key = `${clientId ?? 'tok'}:${kind}` const hit = cache.get(key) if (hit && Date.now() - hit.at < TTL_MS) return hit.id try { const token = authToken ?? await getTokenHelper(event) // The token is already client-scoped; the explicit AD_Client_ID clause is a // safety net against system-level doc types leaking in. const clientClause = clientId ? `AD_Client_ID eq ${clientId} AND ` : '' const res: any = await fetchHelper( event, `models/c_doctype?$filter=${string.urlEncode(clientClause + 'IsActive eq true AND ' + FILTERS[kind])}&$orderby=${string.urlEncode('C_DocType_ID')}`, 'GET', token, null ) const records: any[] = Array.isArray(res?.records) ? res.records : [] const pick = records.find(r => r?.IsDefault === true) ?? records.find(r => r?.Name === PREFERRED_NAME[kind]) ?? records[0] const id = pick?.id ?? null cache.set(key, { id, at: Date.now() }) if (!id) console.warn('[inoutDocType] no doc type found for', kind, 'client', clientId) return id } catch (e: any) { console.warn('[inoutDocType] lookup failed for', kind, '—', e?.message) // stale-if-error; null → caller merges nothing (same error as before) return hit?.id ?? null } } // FK patch in the app's write shape; empty object when unresolved. export function docTypeFkPatch(id: number | null): Record { return id ? { C_DocType_ID: { id: id, tableName: 'C_DocType' } } : {} }