103 lines
5.0 KiB
JavaScript
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;
|