@@ -0,0 +1,102 @@
|
||||
// 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;
|
||||
Reference in New Issue
Block a user