import prisma from "../config/database"; import { generateSharedNumber, previewSharedNumber, releaseSharedNumber, } from "./numbering.service"; import { NasFileManager } from "./nas-file-manager"; const nasFileManager = new NasFileManager(); const ALLOWED_SORT_FIELDS = [ "id", "project_number", "name", "status", "created_at", ]; interface ListProjectsParams { page: number; limit: number; skip: number; sort: string; order: "asc" | "desc"; search: string; status?: string; customer_id?: number; } export async function listProjects(params: ListProjectsParams) { const { page, limit, skip, sort, order, search, status, customer_id } = params; const sortField = ALLOWED_SORT_FIELDS.includes(sort) ? sort : "id"; const where: Record = {}; if (status) where.status = status; if (customer_id) where.customer_id = customer_id; if (search) where.OR = [ { name: { contains: search } }, { project_number: { contains: search } }, ]; const [projects, total] = await Promise.all([ prisma.projects.findMany({ where, skip, take: limit, orderBy: { [sortField]: order }, include: { customers: { select: { id: true, name: true } }, users: { select: { id: true, first_name: true, last_name: true } }, orders: { select: { order_number: true } }, }, }), prisma.projects.count({ where }), ]); const enriched = projects.map((p) => ({ ...p, customer_name: p.customers?.name || null, responsible_user_name: p.users ? `${p.users.first_name} ${p.users.last_name}`.trim() : null, order_number: p.orders?.order_number || null, })); return { data: enriched, total, page, limit }; } export async function getProject(id: number) { const project = await prisma.projects.findUnique({ where: { id }, include: { customers: true, users: true, quotations: true, orders: true, project_notes: { orderBy: { created_at: "desc" } }, }, }); if (!project) return null; const { orders, quotations, customers, users, ...rest } = project; return { ...rest, customer_name: customers?.name ?? null, responsible_user_name: users ? `${users.first_name} ${users.last_name}` : null, order_number: orders?.order_number ?? null, order_status: orders?.status ?? null, quotation_number: quotations?.quotation_number ?? null, has_nas_folder: project.project_number ? nasFileManager.projectFolderExists(project.project_number) : false, }; } export async function createProject(body: Record) { const projectNumber = body.project_number !== undefined && body.project_number !== null ? String(body.project_number) : await generateSharedNumber(); const project = await prisma.projects.create({ data: { project_number: projectNumber, name: body.name ? String(body.name) : null, customer_id: body.customer_id ? Number(body.customer_id) : null, responsible_user_id: body.responsible_user_id ? Number(body.responsible_user_id) : null, quotation_id: body.quotation_id ? Number(body.quotation_id) : null, order_id: body.order_id ? Number(body.order_id) : null, status: body.status ? String(body.status) : "aktivni", start_date: body.start_date ? new Date(String(body.start_date)) : null, end_date: body.end_date ? new Date(String(body.end_date)) : null, notes: body.notes ? String(body.notes) : null, }, }); if (project.project_number && nasFileManager.isConfigured()) { nasFileManager.createProjectFolder( project.project_number, project.name || "", ); } return project; } export async function updateProject(id: number, body: Record) { const existing = await prisma.projects.findUnique({ where: { id } }); if (!existing) return null; if ( body.project_number !== undefined && String(body.project_number) !== existing.project_number ) { return { error: "Číslo projektu nelze změnit", status: 400 }; } const data: Record = { modified_at: new Date() }; const strFields = ["name", "status", "notes"]; for (const f of strFields) if (body[f] !== undefined) data[f] = body[f] ? String(body[f]) : null; if (body.customer_id !== undefined) data.customer_id = body.customer_id ? Number(body.customer_id) : null; if (body.responsible_user_id !== undefined) data.responsible_user_id = body.responsible_user_id ? Number(body.responsible_user_id) : null; if (body.quotation_id !== undefined) data.quotation_id = body.quotation_id ? Number(body.quotation_id) : null; if (body.order_id !== undefined) data.order_id = body.order_id ? Number(body.order_id) : null; if (body.start_date !== undefined) data.start_date = body.start_date ? new Date(String(body.start_date)) : null; if (body.end_date !== undefined) data.end_date = body.end_date ? new Date(String(body.end_date)) : null; await prisma.projects.update({ where: { id }, data }); if ( body.name !== undefined && existing.name !== body.name && existing.project_number && nasFileManager.isConfigured() ) { nasFileManager.renameProjectFolder( existing.project_number, String(body.name || ""), ); } return existing; } export async function deleteProject(id: number, deleteFiles: boolean = false) { const existing = await prisma.projects.findUnique({ where: { id } }); if (!existing) return { error: "not_found" as const }; if (existing.order_id) return { error: "has_order" as const }; if (deleteFiles && existing.project_number && nasFileManager.isConfigured()) { await nasFileManager.deleteProjectFolder(existing.project_number); } await prisma.projects.delete({ where: { id } }); const year = existing.created_at ? new Date(existing.created_at).getFullYear() : new Date().getFullYear(); await releaseSharedNumber(year); return existing; } export async function createProjectNote( projectId: number, data: { userId: number; firstName: string; lastName: string; content?: string; }, ) { const note = await prisma.project_notes.create({ data: { project_id: projectId, user_id: data.userId, user_name: `${data.firstName} ${data.lastName}`, content: data.content ? String(data.content) : null, }, }); return note; } export async function deleteProjectNote(projectId: number, noteId: number) { const note = await prisma.project_notes.findFirst({ where: { id: noteId, project_id: projectId }, }); if (!note) return null; await prisma.project_notes.delete({ where: { id: noteId } }); return note; } export async function getNextProjectNumber() { return previewSharedNumber(); }