Files
sap-erp/routes/inventoryTransfer.js
John eead8f5ffd
SAP-ERP Portal CI/CD / build (push) Successful in 3m57s
sale order
2026-10-05 18:45:17 +05:30

103 lines
5.0 KiB
JavaScript

// 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;