sale order
SAP-ERP Portal CI/CD / build (push) Successful in 3m57s

This commit is contained in:
John
2026-10-05 18:45:17 +05:30
parent e725a6571b
commit eead8f5ffd
129 changed files with 14252 additions and 452 deletions
+63
View File
@@ -0,0 +1,63 @@
// routes/salesExt.js — machine-to-machine API for the Sales Order module (/api/sales-ext)
// msale received invoice PDFs from an external job (POST api/invoice_details/
// upload_invoice). That job must now be pointed here instead. Auth: header
// MS-API-KEY = .env SALES_EXT_API_KEY (the endpoint is disabled while unset).
// Invoices themselves, dispatch details and COA certificates are read live
// from SAP, so they need no push at all any more.
'use strict';
const express = require('express');
const path = require('path');
const fs = require('fs');
const crypto = require('crypto');
const { query } = require('../services/sales/db');
const masters = require('../services/sales/masters');
const { getPool } = require('../services/sqlPool');
const { DOC_DIR } = require('./sales');
const router = express.Router();
const INV_DIR = path.join(DOC_DIR, 'invoices');
fs.mkdirSync(INV_DIR, { recursive: true });
router.use((req, res, next) => {
const key = process.env.SALES_EXT_API_KEY || '';
const got = String(req.headers['ms-api-key'] || '');
const ok = key && got.length === key.length && crypto.timingSafeEqual(Buffer.from(got), Buffer.from(key));
if (!ok) return res.status(401).json({ success: false, message: 'Invalid API key' });
req.auditActor = 'sales-ext-api';
next();
});
// Invoice numbers of web orders (last N days, default 120) with no PDF yet.
router.get('/invoices-without-pdf', async (req, res) => {
try {
const s = await masters.getSettings();
const days = Math.min(parseInt(req.query.days) || 120, 730);
const pool = await getPool(s.company);
const r = await pool.request().input('d', days).query(`SELECT DocNum, DocDate, CardCode, U_WEB_SO_NO FROM OINV
WHERE CANCELED='N' AND U_WEB_SO_NO IS NOT NULL AND DocDate>=DATEADD(day,-@d,CAST(GETDATE() AS date))`);
const have = new Set((await query(`SELECT DISTINCT INVOICE_NO FROM dbo.ZSO_INVOICE_FILES`)).map(x => String(x.INVOICE_NO)));
res.json({ success: true, data: r.recordset.filter(x => !have.has(String(x.DocNum))).map(x => ({ invoice_no: x.DocNum, invoice_date: x.DocDate, card_code: x.CardCode, web_so_no: x.U_WEB_SO_NO })) });
} catch (e) { res.status(500).json({ success: false, message: e.message }); }
});
// Body: [{invoice_no, invoice_file_name, invoice_file_data(base64)}] — also
// accepts msale's index-keyed object form ({"0":{...},"1":{...}}).
router.post('/invoice-pdf', async (req, res) => {
const list = Array.isArray(req.body) ? req.body : Object.values(req.body || {});
const result = { success: [], error: [] };
for (const v of list) {
try {
const no = String(v.invoice_no || '').trim();
if (!/^\d+$/.test(no) || !v.invoice_file_data) throw new Error('invoice_no and invoice_file_data are required');
const buf = Buffer.from(String(v.invoice_file_data), 'base64');
if (buf.slice(0, 4).toString() !== '%PDF') throw new Error('file is not a PDF');
const name = `INV_${no}_${Date.now()}.pdf`;
fs.writeFileSync(path.join(INV_DIR, name), buf);
await query(`INSERT INTO dbo.ZSO_INVOICE_FILES (INVOICE_NO, FILE_NAME) VALUES (?,?)`, [no, name]);
result.success.push({ invoice_no: no, invoice_file_name: v.invoice_file_name || name });
} catch (e) { result.error.push({ invoice_no: v && v.invoice_no, message: e.message }); }
}
res.json({ status: 'ok', ...result });
});
module.exports = router;