diff --git a/package.json b/package.json index 8eb40b2..9339fb7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "app-ts", - "version": "2.0.3", + "version": "2.0.4", "description": "", "main": "dist/server.js", "scripts": { diff --git a/src/__tests__/nas-project-folder.test.ts b/src/__tests__/nas-project-folder.test.ts index 192c363..c342c25 100644 --- a/src/__tests__/nas-project-folder.test.ts +++ b/src/__tests__/nas-project-folder.test.ts @@ -68,3 +68,39 @@ describe("NasFileManager.createProjectFolder", () => { ); }); }); + +/** + * Folder deletion primitive used by the "delete folder" checkbox on both the + * order delete (orders.service deleteOrder) and project delete (projects.service + * deleteProject) paths. Matches the project by number prefix. + */ +describe("NasFileManager.deleteProjectFolder", () => { + let tmpBase: string; + let nas: NasFileManager; + + beforeEach(() => { + tmpBase = fs.mkdtempSync(path.join(os.tmpdir(), "nas-del-")); + nas = new NasFileManager(tmpBase); + }); + + afterEach(() => { + fs.rmSync(tmpBase, { recursive: true, force: true }); + }); + + it("removes an existing project folder (matched by number prefix)", async () => { + nas.createProjectFolder("26710010", "Hala"); + expect(fs.existsSync(path.join(tmpBase, "26710010_Hala"))).toBe(true); + const ok = await nas.deleteProjectFolder("26710010"); + expect(ok).toBe(true); + expect(fs.existsSync(path.join(tmpBase, "26710010_Hala"))).toBe(false); + }); + + it("is a no-op (returns true) when no folder exists for the number", async () => { + expect(await nas.deleteProjectFolder("26710011")).toBe(true); + }); + + it("returns false when the NAS base is not mounted", async () => { + const missing = new NasFileManager(path.join(tmpBase, "nope")); + expect(await missing.deleteProjectFolder("26710012")).toBe(false); + }); +}); diff --git a/src/routes/admin/orders.ts b/src/routes/admin/orders.ts index c029650..601999d 100644 --- a/src/routes/admin/orders.ts +++ b/src/routes/admin/orders.ts @@ -310,7 +310,9 @@ export default async function ordersRoutes( const id = parseId(request.params.id, reply); if (id === null) return; - const result = await deleteOrder(id); + const body = request.body as Record | undefined; + const deleteFiles = !!body?.delete_files; + const result = await deleteOrder(id, deleteFiles); if ("error" in result) return error(reply, result.error!, result.status!); await logAudit({ diff --git a/src/services/orders.service.ts b/src/services/orders.service.ts index 2afeb74..6d38f29 100644 --- a/src/services/orders.service.ts +++ b/src/services/orders.service.ts @@ -588,15 +588,17 @@ export async function updateOrder(id: number, body: UpdateOrderData) { return { data: { id, order_number: existing.order_number } }; } -export async function deleteOrder(id: number) { +export async function deleteOrder(id: number, deleteFiles = false) { const existing = await prisma.orders.findUnique({ where: { id } }); if (!existing) return { error: "Objednávka nenalezena", status: 404 } as const; - // Fetch linked projects before the transaction for number release later + // Fetch linked projects before the transaction for number release and + // (optional) NAS folder cleanup later. project_number is needed to locate + // the folder on the share. const linkedProjects = await prisma.projects.findMany({ where: { order_id: id }, - select: { id: true, created_at: true }, + select: { id: true, created_at: true, project_number: true }, }); // Guard: projects may have non-cascaded warehouse refs (sklad_issues, @@ -662,6 +664,24 @@ export async function deleteOrder(id: number) { await tx.orders.delete({ where: { id } }); }); + // Best-effort NAS folder cleanup for the order's project(s), outside the + // transaction and only when the user ticked the "delete folder" checkbox in + // the order delete modal. Non-fatal: a NAS error must not undo the + // already-committed DB delete. deleteProjectFolder is idempotent — it returns + // true (no-op) when the folder is already absent. + if (deleteFiles && nasFileManager.isConfigured()) { + for (const p of linkedProjects) { + if (!p.project_number) continue; + const ok = await nasFileManager.deleteProjectFolder(p.project_number); + if (!ok) { + console.error( + "[orders.service] NAS folder not deleted for project", + p.project_number, + ); + } + } + } + const year = existing.created_at ? new Date(existing.created_at).getFullYear() : new Date().getFullYear();