64 lines
3.3 KiB
JavaScript
64 lines
3.3 KiB
JavaScript
// 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;
|