fix(nas): issued-order PDFs overwrite in place - no more _1 duplicate copies

saveIssuedPdf ran through uniquePath, so whenever two archive calls raced (the fire-and-forget ?save=1 after a save vs. the /file fallback, or two quick saves) the second write landed as <number>_1.pdf - and those copies were invisible to the reader and cleaner (exact-name match), accumulating forever. Now the issued archive writes to the deterministic Vydane/YYYY/MM/<number>.pdf and overwrites (same model as the offers manager), and cleanIssued also sweeps stale <number>_N.pdf duplicates so existing junk self-heals on the next save/delete. Document numbers are digit-only (never contain _), so the suffix match cannot hit a different document. uniquePath remains for received-invoice uploads where name collisions are legitimate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-06-10 17:55:27 +02:00
parent 268a947c85
commit dd8c699420
2 changed files with 48 additions and 7 deletions

View File

@@ -52,10 +52,20 @@ export class NasDocumentManager {
// ── Created (issued) documents ───────────────────────────────────
/** Remove any existing PDF for this document number across all year/month folders */
/**
* Remove any existing PDF for this document number across all year/month
* folders — including stale `<number>_N.pdf` duplicates that the old
* uniquePath-based save left behind when two archive calls raced (the
* save-after-edit fire-and-forget vs. the /file fallback). Document
* numbers are digit-only and never contain "_", so the suffix match
* cannot hit a different document.
*/
cleanIssued(documentNumber: string): void {
if (!this.basePath) return;
const safeName = this.sanitizeFilename(documentNumber) + ".pdf";
const safeBase = this.sanitizeFilename(documentNumber);
const stalePattern = new RegExp(
`^${safeBase.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(_\\d+)?\\.pdf$`,
);
const issuedDir = path.join(this.basePath, DIR_ISSUED);
try {
if (!fs.existsSync(issuedDir)) return;
@@ -74,9 +84,10 @@ export class NasDocumentManager {
);
continue;
}
const filePath = path.join(monthPath, safeName);
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
for (const file of fs.readdirSync(monthPath)) {
if (stalePattern.test(file)) {
fs.unlinkSync(path.join(monthPath, file));
}
}
}
}
@@ -103,12 +114,17 @@ export class NasDocumentManager {
const dir = this.ensureDir(DIR_ISSUED, year, month);
if (!dir) return null;
// Deterministic path, OVERWRITE in place (same model as the offers
// manager): the archive of a numbered document has exactly one canonical
// file. uniquePath here used to spawn `<number>_1.pdf` duplicates
// whenever two archive calls raced — those are user-visible junk on the
// NAS and invisible to the reader/cleaner, which match the exact name.
const safeName = this.sanitizeFilename(documentNumber) + ".pdf";
const fullPath = this.uniquePath(dir, safeName);
const fullPath = path.join(dir, safeName);
try {
fs.writeFileSync(fullPath, pdfBuffer);
return `${DIR_ISSUED}/${year}/${this.pad(month)}/${path.basename(fullPath)}`;
return `${DIR_ISSUED}/${year}/${this.pad(month)}/${safeName}`;
} catch (err) {
console.error(
"[nas-financials-manager] write failed for issued document:",