// routes/inventoryTransfer.js // General-purpose "Inventory Transfer" — mirrors SAP B1's own Inventory // Transfer window (Inventory → Inventory Transactions → Inventory Transfer): // move stock from one warehouse to another, any item(s), not tied to a // Production/Work Order. Posts a standard SAP StockTransfers document. 'use strict'; const express = require('express'); const router = express.Router(); const { verifyToken } = require('../middleware/auth'); const appSettings = require('../services/appSettingsStore'); const { getPool } = require('../services/sqlPool'); let _sapSvc = null; function getSap(){ if(!_sapSvc) _sapSvc = require('../services/sapServiceLayer'); return _sapSvc; } const cq = (req) => req.query?.company || req.body?.company || null; // GET /api/inventory-transfer/series-info — resolves the admin-configured // Series ID to its human-readable SAP name (e.g. 28615 → "IC2627"), for the // page itself to display so a non-admin user understands which series their // transfer will post under. Deliberately NOT behind the admin-only /settings // endpoint — any logged-in user doing a transfer needs to see this. router.get('/series-info', verifyToken, async (req, res) => { const co = cq(req); const series = appSettings.inventoryTransferSeries(); if (series == null) return res.json({ success: true, series: null, seriesName: null }); try { const pool = await getPool(co); const r = await pool.request().query(`SELECT "SeriesName" FROM [dbo].[NNM1] WHERE "ObjectCode"='67' AND "Series"=${parseInt(series)}`); res.json({ success: true, series, seriesName: r.recordset[0]?.SeriesName || null }); } catch (err) { res.json({ success: true, series, seriesName: null, warning: err.message }); } }); router.post('/', verifyToken, async (req, res) => { try{ const sap = getSap(); if(!sap) return res.status(503).json({success:false,message:'SAP service not ready'}); const co = cq(req); const body = req.body || {}; const fromWarehouse = (body.fromWarehouse||'').trim(); const toWarehouse = (body.toWarehouse||'').trim(); if(!fromWarehouse||!toWarehouse) return res.status(400).json({success:false,message:'From Warehouse and To Warehouse are required'}); if(fromWarehouse===toWarehouse) return res.status(400).json({success:false,message:'From Warehouse and To Warehouse must be different'}); const rawLines = Array.isArray(body.lines) ? body.lines : []; const lines = rawLines .filter(l=>l.itemCode && (parseFloat(l.quantity)||0)>0) .map((l,idx)=>{ const line={ ItemCode: String(l.itemCode).trim(), Quantity: parseFloat(l.quantity)||0, WarehouseCode: toWarehouse, FromWarehouseCode: fromWarehouse, }; if(Array.isArray(l.batchNumbers)&&l.batchNumbers.length){ const bn=l.batchNumbers.filter(b=>b.BatchNumber&&(parseFloat(b.Quantity)||0)>0) .map(b=>({BatchNumber:b.BatchNumber,Quantity:parseFloat(b.Quantity)||0,BaseLineNumber:idx})); if(bn.length) line.BatchNumbers=bn; } return line; }); if(!lines.length) return res.status(400).json({success:false,message:'At least one item line with a quantity > 0 is required'}); const payload={ FromWarehouse: fromWarehouse, ToWarehouse: toWarehouse, StockTransferLines: lines, }; if(body.comments) payload.Comments=String(body.comments).trim(); // StockTransfers has no DocDueDate field (unlike InventoryGenEntries) — // sending it fails with "-1000 Property 'DocDueDate' of 'StockTransfer' // is invalid", confirmed live. if(body.postingDate){ payload.DocDate=body.postingDate; payload.TaxDate=body.postingDate; } // Admin-configurable (System Settings → Inventory Transfer → Document // Series) — e.g. SAP's "IC2627" series, internal ID 28615, maintained in // Admin so it never needs a code change if the series changes. const series=appSettings.inventoryTransferSeries(); if(series!=null) payload.Series=series; console.log('[INV-TRANSFER] Final payload:\n',JSON.stringify(payload,null,2)); const result = await sap.sapRequest('POST','StockTransfers',payload,co); console.log('[INV-TRANSFER] ✅ DocEntry:',result?.DocEntry,'DocNum:',result?.DocNum); require('../services/auditStore').record({ userId:req.user?.id, username:req.user?.username, role:req.user?.role, method:req.method, action:'CREATE', entity:'InventoryTransfer', entityId:String(result?.DocEntry||''), path:req.originalUrl, status:200, ok:true, summary:`Inventory Transfer #${result?.DocNum||result?.DocEntry} posted: ${fromWarehouse} → ${toWarehouse}, ${lines.length} item(s).`, details:{fromWarehouse,toWarehouse,lines}, ip:req.ip, company:co, }).catch(()=>{}); res.json({success:true,data:result}); }catch(err){ const sapMsg=err.message||'Unknown SAP error'; console.error('[INV-TRANSFER] ❌',sapMsg); res.status(400).json({success:false,message:sapMsg}); } }); module.exports = router;