feat(nas): archive issued-order PDFs to NAS_ORDERS; split NAS_FINANCIALS_PATH into NAS_INVOICES + NAS_ORDERS
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +15,10 @@ import { invalidateSettingsCache } from "../../services/system-settings";
|
||||
import os from "os";
|
||||
import { config } from "../../config/env";
|
||||
import { NasFileManager } from "../../services/nas-file-manager";
|
||||
import { nasFinancialsManager } from "../../services/nas-financials-manager";
|
||||
import {
|
||||
nasInvoicesManager,
|
||||
nasOrdersManager,
|
||||
} from "../../services/nas-financials-manager";
|
||||
import { nasOffersManager } from "../../services/nas-offers-manager";
|
||||
|
||||
/** Encode custom_fields + supplier_field_order into a single JSON blob (matching PHP format) */
|
||||
@@ -420,8 +423,11 @@ export default async function companySettingsRoutes(
|
||||
projects: {
|
||||
configured: projectNas.isConfigured(),
|
||||
},
|
||||
financials: {
|
||||
configured: nasFinancialsManager.isConfigured(),
|
||||
invoices: {
|
||||
configured: nasInvoicesManager.isConfigured(),
|
||||
},
|
||||
orders: {
|
||||
configured: nasOrdersManager.isConfigured(),
|
||||
},
|
||||
offers: {
|
||||
configured: nasOffersManager.isConfigured(),
|
||||
|
||||
@@ -3,7 +3,7 @@ import QRCode from "qrcode";
|
||||
import prisma from "../../config/database";
|
||||
import { requirePermission } from "../../middleware/auth";
|
||||
import { localDateCzStr } from "../../utils/date";
|
||||
import { nasFinancialsManager } from "../../services/nas-financials-manager";
|
||||
import { nasInvoicesManager } from "../../services/nas-financials-manager";
|
||||
import { htmlToPdf } from "../../utils/html-to-pdf";
|
||||
import { getRate } from "../../services/exchange-rates";
|
||||
import { localDateStr } from "../../utils/date";
|
||||
@@ -1067,15 +1067,15 @@ ${indentCSS}
|
||||
// Save PDF to NAS
|
||||
if (
|
||||
saveMode &&
|
||||
nasFinancialsManager.isConfigured() &&
|
||||
nasInvoicesManager.isConfigured() &&
|
||||
invoice.invoice_number
|
||||
) {
|
||||
const issueDate = invoice.issue_date
|
||||
? new Date(invoice.issue_date)
|
||||
: new Date();
|
||||
nasFinancialsManager.cleanIssuedInvoice(invoice.invoice_number!);
|
||||
nasInvoicesManager.cleanIssued(invoice.invoice_number!);
|
||||
const pdfBuffer = await htmlToPdf(html);
|
||||
nasFinancialsManager.saveIssuedInvoicePdf(
|
||||
nasInvoicesManager.saveIssuedPdf(
|
||||
invoice.invoice_number!,
|
||||
issueDate.getFullYear(),
|
||||
issueDate.getMonth() + 1,
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
updateInvoice,
|
||||
deleteInvoice,
|
||||
} from "../../services/invoices.service";
|
||||
import { nasFinancialsManager } from "../../services/nas-financials-manager";
|
||||
import { nasInvoicesManager } from "../../services/nas-financials-manager";
|
||||
|
||||
export default async function invoicesRoutes(
|
||||
fastify: FastifyInstance,
|
||||
@@ -202,7 +202,7 @@ export default async function invoicesRoutes(
|
||||
|
||||
// Delete PDF from NAS
|
||||
if (existing.invoice_number) {
|
||||
await nasFinancialsManager.cleanIssuedInvoice(existing.invoice_number);
|
||||
await nasInvoicesManager.cleanIssued(existing.invoice_number);
|
||||
}
|
||||
|
||||
await logAudit({
|
||||
@@ -233,7 +233,7 @@ export default async function invoicesRoutes(
|
||||
|
||||
const d = new Date(invoice.issue_date);
|
||||
const relPath = `Vydané/${d.getFullYear()}/${String(d.getMonth() + 1).padStart(2, "0")}/${invoice.invoice_number}.pdf`;
|
||||
const file = nasFinancialsManager.readIssuedInvoice(relPath);
|
||||
const file = nasInvoicesManager.readIssued(relPath);
|
||||
if (!file) return error(reply, "PDF soubor nenalezen", 404);
|
||||
|
||||
const safeName = invoice.invoice_number.replace(/[\r\n"]/g, "");
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { FastifyInstance } from "fastify";
|
||||
import prisma from "../../config/database";
|
||||
import { requirePermission } from "../../middleware/auth";
|
||||
import { parseId } from "../../utils/response";
|
||||
import { parseId, success } from "../../utils/response";
|
||||
import { localDateCzStr } from "../../utils/date";
|
||||
import { htmlToPdf } from "../../utils/html-to-pdf";
|
||||
import { nasOrdersManager } from "../../services/nas-financials-manager";
|
||||
import createDOMPurify from "dompurify";
|
||||
import { JSDOM } from "jsdom";
|
||||
|
||||
@@ -856,6 +857,25 @@ export default async function issuedOrdersPdfRoutes(fastify: FastifyInstance) {
|
||||
settings,
|
||||
lang,
|
||||
);
|
||||
|
||||
// ?save=1 → archive the PDF to NAS instead of returning it (mirrors
|
||||
// invoices-pdf). A draft has no po_number → the guard skips it.
|
||||
const saveMode = query.save === "1";
|
||||
if (saveMode && nasOrdersManager.isConfigured() && order.po_number) {
|
||||
const baseDate = order.order_date
|
||||
? new Date(order.order_date)
|
||||
: new Date();
|
||||
nasOrdersManager.cleanIssued(order.po_number);
|
||||
const pdfBuffer = await htmlToPdf(html);
|
||||
nasOrdersManager.saveIssuedPdf(
|
||||
order.po_number,
|
||||
baseDate.getFullYear(),
|
||||
baseDate.getMonth() + 1,
|
||||
pdfBuffer,
|
||||
);
|
||||
return success(reply, null, 200, "PDF uloženo");
|
||||
}
|
||||
|
||||
const pdf = await htmlToPdf(html);
|
||||
const filename = `Objednavka-${order.po_number || String(id)}.pdf`;
|
||||
return reply
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
CreateReceivedInvoiceSchema,
|
||||
UpdateReceivedInvoiceSchema,
|
||||
} from "../../schemas/received-invoices.schema";
|
||||
import { nasFinancialsManager } from "../../services/nas-financials-manager";
|
||||
import { nasInvoicesManager } from "../../services/nas-financials-manager";
|
||||
import { toCzk } from "../../services/exchange-rates";
|
||||
import path from "path";
|
||||
|
||||
@@ -203,12 +203,12 @@ export default async function receivedInvoicesRoutes(
|
||||
});
|
||||
if (!invoice?.file_name) return error(reply, "Soubor nenalezen", 404);
|
||||
|
||||
const relPath = nasFinancialsManager.buildReceivedPath(
|
||||
const relPath = nasInvoicesManager.buildReceivedPath(
|
||||
invoice.file_name,
|
||||
invoice.year,
|
||||
invoice.month,
|
||||
);
|
||||
const nasFile = nasFinancialsManager.readReceivedInvoice(relPath);
|
||||
const nasFile = nasInvoicesManager.readReceived(relPath);
|
||||
if (!nasFile) return error(reply, "Soubor na NAS nenalezen", 404);
|
||||
|
||||
const mime = invoice.file_mime || "application/pdf";
|
||||
@@ -295,7 +295,7 @@ export default async function receivedInvoicesRoutes(
|
||||
const now = new Date();
|
||||
const createdIds: number[] = [];
|
||||
|
||||
const useNas = nasFinancialsManager.isConfigured();
|
||||
const useNas = nasInvoicesManager.isConfigured();
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
@@ -319,7 +319,7 @@ export default async function receivedInvoicesRoutes(
|
||||
return error(reply, "NAS úložiště není nakonfigurováno", 503);
|
||||
}
|
||||
|
||||
const nasResult = nasFinancialsManager.saveReceivedInvoice(
|
||||
const nasResult = nasInvoicesManager.saveReceived(
|
||||
file.name,
|
||||
invoiceYear,
|
||||
invoiceMonth,
|
||||
@@ -584,12 +584,12 @@ export default async function receivedInvoicesRoutes(
|
||||
await prisma.received_invoices.delete({ where: { id } });
|
||||
|
||||
if (existing.file_name) {
|
||||
const relPath = nasFinancialsManager.buildReceivedPath(
|
||||
const relPath = nasInvoicesManager.buildReceivedPath(
|
||||
existing.file_name,
|
||||
existing.year,
|
||||
existing.month,
|
||||
);
|
||||
nasFinancialsManager.deleteReceivedInvoice(relPath);
|
||||
nasInvoicesManager.deleteReceived(relPath);
|
||||
}
|
||||
await logAudit({
|
||||
request,
|
||||
|
||||
@@ -7,7 +7,7 @@ import { logAudit } from "../../services/audit";
|
||||
import { success, error, parseId, paginated } from "../../utils/response";
|
||||
import { parsePagination, buildPaginationMeta } from "../../utils/pagination";
|
||||
import { parseBody } from "../../schemas/common";
|
||||
import { nasFinancialsManager } from "../../services/nas-financials-manager";
|
||||
import { nasInvoicesManager } from "../../services/nas-financials-manager";
|
||||
import {
|
||||
getItemAvailableQty,
|
||||
getItemTotalStock,
|
||||
@@ -1315,11 +1315,11 @@ export default async function warehouseRoutes(
|
||||
const year = now.getFullYear();
|
||||
const month = now.getMonth() + 1;
|
||||
|
||||
if (!nasFinancialsManager.isConfigured()) {
|
||||
if (!nasInvoicesManager.isConfigured()) {
|
||||
return error(reply, "Úložiště souborů není dostupné", 503);
|
||||
}
|
||||
|
||||
const result = nasFinancialsManager.saveReceivedInvoice(
|
||||
const result = nasInvoicesManager.saveReceived(
|
||||
part.filename,
|
||||
year,
|
||||
month,
|
||||
@@ -1344,9 +1344,7 @@ export default async function warehouseRoutes(
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
const removed = nasFinancialsManager.deleteReceivedInvoice(
|
||||
result.filePath,
|
||||
);
|
||||
const removed = nasInvoicesManager.deleteReceived(result.filePath);
|
||||
request.log.error(
|
||||
{ err: e, filePath: result.filePath, orphanRemoved: removed },
|
||||
"warehouse attachment DB insert failed after NAS write; rolled back NAS file",
|
||||
@@ -1392,7 +1390,7 @@ export default async function warehouseRoutes(
|
||||
// Delete file from NAS. A failure here is non-fatal (we still remove the
|
||||
// DB row) but must not be silent — log it so an undeleted NAS file is
|
||||
// visible rather than leaking quietly.
|
||||
const nasDeleted = nasFinancialsManager.deleteReceivedInvoice(
|
||||
const nasDeleted = nasInvoicesManager.deleteReceived(
|
||||
attachment.file_path,
|
||||
);
|
||||
if (!nasDeleted) {
|
||||
|
||||
Reference in New Issue
Block a user