feat(projects): auto-create NAS folder for order-spawned projects
Manual project creation already creates the 02_PROJEKTY/<number>_<name> folder (projects.service.createProject). Projects created from an order — both from an accepted offer (createOrderFromQuotation) and a manual offer-less order (createOrder) — did not, so they had no NAS folder until a user opened the project and clicked "create folder" or uploaded a file. - orders.service: best-effort, non-fatal createProjectFolder after each project-creating transaction commits (mirrors createProject; gated on isConfigured(), logs on failure, never rolls back the order). - projects.service: log when the existing auto-create returns false (was silently discarded — CLAUDE.md known-issue #4). - nas-file-manager: optional constructor basePath seam for tests. - tests: NasFileManager.createProjectFolder coverage — naming, idempotency, Czech diacritics, empty name, not-mounted (152 -> 158 tests). Bump version to 2.0.3. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,11 @@ import {
|
||||
releaseSharedNumber,
|
||||
isOrderNumberTaken,
|
||||
} from "./numbering.service";
|
||||
import { NasFileManager } from "./nas-file-manager";
|
||||
|
||||
// Best-effort NAS folder creation for order-spawned projects, mirroring
|
||||
// projects.service. Stateless singleton; reads NAS_PATH at construction.
|
||||
const nasFileManager = new NasFileManager();
|
||||
|
||||
interface OrderItemInput {
|
||||
description?: string | null;
|
||||
@@ -262,6 +267,23 @@ export async function createOrderFromQuotation(
|
||||
return { order, project, orderNumber };
|
||||
});
|
||||
|
||||
// Best-effort NAS project folder, outside the transaction (mirrors
|
||||
// createProject). A project created from an accepted offer must get the same
|
||||
// 02_PROJEKTY/<number>_<name> folder as a manually-created one. Non-fatal: a
|
||||
// NAS outage must never roll back the order.
|
||||
if (result.project.project_number && nasFileManager.isConfigured()) {
|
||||
const created = nasFileManager.createProjectFolder(
|
||||
result.project.project_number,
|
||||
result.project.name || "",
|
||||
);
|
||||
if (!created) {
|
||||
console.error(
|
||||
"[orders.service] NAS folder not created for project",
|
||||
result.project.project_number,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
data: {
|
||||
order_id: result.order.id,
|
||||
@@ -297,7 +319,7 @@ export async function createOrder(
|
||||
attachmentName?: string | null,
|
||||
) {
|
||||
try {
|
||||
return await prisma.$transaction(async (tx) => {
|
||||
const result = await prisma.$transaction(async (tx) => {
|
||||
const orderNumber =
|
||||
body.order_number !== undefined && body.order_number !== null
|
||||
? String(body.order_number)
|
||||
@@ -362,6 +384,8 @@ export async function createOrder(
|
||||
}
|
||||
|
||||
let projectId: number | undefined;
|
||||
let createdProject: { project_number: string; name: string } | null =
|
||||
null;
|
||||
// Only auto-create a project for genuine offer-less orders; share the
|
||||
// order's number (same shared pool) exactly like the from-quotation flow.
|
||||
if (body.create_project !== false && body.quotation_id == null) {
|
||||
@@ -375,14 +399,42 @@ export async function createOrder(
|
||||
},
|
||||
});
|
||||
projectId = project.id;
|
||||
if (project.project_number) {
|
||||
createdProject = {
|
||||
project_number: project.project_number,
|
||||
name: project.name || "",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: order.id,
|
||||
order_number: order.order_number,
|
||||
project_id: projectId,
|
||||
createdProject,
|
||||
};
|
||||
});
|
||||
|
||||
// Best-effort NAS project folder, outside the transaction (mirrors
|
||||
// createProject). Non-fatal so a NAS outage can't roll back the order.
|
||||
if (result.createdProject && nasFileManager.isConfigured()) {
|
||||
const created = nasFileManager.createProjectFolder(
|
||||
result.createdProject.project_number,
|
||||
result.createdProject.name,
|
||||
);
|
||||
if (!created) {
|
||||
console.error(
|
||||
"[orders.service] NAS folder not created for project",
|
||||
result.createdProject.project_number,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: result.id,
|
||||
order_number: result.order_number,
|
||||
project_id: result.project_id,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof Error && "status" in err) {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user