import type { ProjectCustomerOptionDto, ProjectDetailDto, ProjectDocumentOptionDto, ProjectInput, ProjectOwnerOptionDto, ProjectPriority, ProjectShipmentOptionDto, ProjectStatus, ProjectSummaryDto, } from "@mrp/shared"; import { logAuditEvent } from "../../lib/audit.js"; import { prisma } from "../../lib/prisma.js"; const projectModel = (prisma as any).project; type ProjectRecord = { id: string; projectNumber: string; name: string; status: string; priority: string; dueDate: Date | null; notes: string; createdAt: Date; updatedAt: Date; customer: { id: string; name: string; email: string; phone: string; }; owner: { id: string; firstName: string; lastName: string; } | null; salesQuote: { id: string; documentNumber: string; } | null; salesOrder: { id: string; documentNumber: string; } | null; shipment: { id: string; shipmentNumber: string; } | null; }; function getOwnerName(owner: ProjectRecord["owner"]) { return owner ? `${owner.firstName} ${owner.lastName}`.trim() : null; } function mapProjectSummary(record: ProjectRecord): ProjectSummaryDto { return { id: record.id, projectNumber: record.projectNumber, name: record.name, status: record.status as ProjectStatus, priority: record.priority as ProjectPriority, customerId: record.customer.id, customerName: record.customer.name, ownerId: record.owner?.id ?? null, ownerName: getOwnerName(record.owner), dueDate: record.dueDate ? record.dueDate.toISOString() : null, updatedAt: record.updatedAt.toISOString(), }; } function mapProjectDetail(record: ProjectRecord): ProjectDetailDto { return { ...mapProjectSummary(record), notes: record.notes, createdAt: record.createdAt.toISOString(), salesQuoteId: record.salesQuote?.id ?? null, salesQuoteNumber: record.salesQuote?.documentNumber ?? null, salesOrderId: record.salesOrder?.id ?? null, salesOrderNumber: record.salesOrder?.documentNumber ?? null, shipmentId: record.shipment?.id ?? null, shipmentNumber: record.shipment?.shipmentNumber ?? null, customerEmail: record.customer.email, customerPhone: record.customer.phone, }; } function buildInclude() { return { customer: { select: { id: true, name: true, email: true, phone: true, }, }, owner: { select: { id: true, firstName: true, lastName: true, }, }, salesQuote: { select: { id: true, documentNumber: true, }, }, salesOrder: { select: { id: true, documentNumber: true, }, }, shipment: { select: { id: true, shipmentNumber: true, }, }, }; } async function nextProjectNumber() { const next = (await projectModel.count()) + 1; return `PRJ-${String(next).padStart(5, "0")}`; } async function validateProjectInput(payload: ProjectInput) { const customer = await prisma.customer.findUnique({ where: { id: payload.customerId }, select: { id: true }, }); if (!customer) { return { ok: false as const, reason: "Customer was not found." }; } if (payload.ownerId) { const owner = await prisma.user.findUnique({ where: { id: payload.ownerId }, select: { id: true, isActive: true }, }); if (!owner?.isActive) { return { ok: false as const, reason: "Project owner was not found." }; } } if (payload.salesQuoteId) { const quote = await prisma.salesQuote.findUnique({ where: { id: payload.salesQuoteId }, select: { id: true, customerId: true }, }); if (!quote) { return { ok: false as const, reason: "Linked quote was not found." }; } if (quote.customerId !== payload.customerId) { return { ok: false as const, reason: "Linked quote must belong to the selected customer." }; } } if (payload.salesOrderId) { const order = await prisma.salesOrder.findUnique({ where: { id: payload.salesOrderId }, select: { id: true, customerId: true }, }); if (!order) { return { ok: false as const, reason: "Linked sales order was not found." }; } if (order.customerId !== payload.customerId) { return { ok: false as const, reason: "Linked sales order must belong to the selected customer." }; } } if (payload.shipmentId) { const shipment = await prisma.shipment.findUnique({ where: { id: payload.shipmentId }, include: { salesOrder: { select: { customerId: true, }, }, }, }); if (!shipment) { return { ok: false as const, reason: "Linked shipment was not found." }; } if (shipment.salesOrder.customerId !== payload.customerId) { return { ok: false as const, reason: "Linked shipment must belong to the selected customer." }; } } return { ok: true as const }; } export async function listProjectCustomerOptions(): Promise { const customers = await prisma.customer.findMany({ where: { status: { not: "INACTIVE", }, }, select: { id: true, name: true, email: true, }, orderBy: [{ name: "asc" }], }); return customers; } export async function listProjectOwnerOptions(): Promise { const users = await prisma.user.findMany({ where: { isActive: true, }, select: { id: true, firstName: true, lastName: true, email: true, }, orderBy: [{ firstName: "asc" }, { lastName: "asc" }], }); return users.map((user) => ({ id: user.id, fullName: `${user.firstName} ${user.lastName}`.trim(), email: user.email, })); } export async function listProjectQuoteOptions(customerId?: string | null): Promise { const quotes = await prisma.salesQuote.findMany({ where: { ...(customerId ? { customerId } : {}), }, include: { customer: { select: { name: true, }, }, }, orderBy: [{ issueDate: "desc" }, { createdAt: "desc" }], }); return quotes.map((quote) => ({ id: quote.id, documentNumber: quote.documentNumber, customerName: quote.customer.name, status: quote.status, })); } export async function listProjectOrderOptions(customerId?: string | null): Promise { const orders = await prisma.salesOrder.findMany({ where: { ...(customerId ? { customerId } : {}), }, include: { customer: { select: { name: true, }, }, }, orderBy: [{ issueDate: "desc" }, { createdAt: "desc" }], }); return orders.map((order) => ({ id: order.id, documentNumber: order.documentNumber, customerName: order.customer.name, status: order.status, })); } export async function listProjectShipmentOptions(customerId?: string | null): Promise { const shipments = await prisma.shipment.findMany({ where: { ...(customerId ? { salesOrder: { customerId } } : {}), }, include: { salesOrder: { include: { customer: { select: { name: true, }, }, }, }, }, orderBy: [{ createdAt: "desc" }], }); return shipments.map((shipment) => ({ id: shipment.id, shipmentNumber: shipment.shipmentNumber, salesOrderNumber: shipment.salesOrder.documentNumber, customerName: shipment.salesOrder.customer.name, status: shipment.status, })); } export async function listProjects(filters: { q?: string; status?: ProjectStatus; priority?: ProjectPriority; customerId?: string; ownerId?: string; } = {}) { const query = filters.q?.trim(); const projects = await projectModel.findMany({ where: { ...(filters.status ? { status: filters.status } : {}), ...(filters.priority ? { priority: filters.priority } : {}), ...(filters.customerId ? { customerId: filters.customerId } : {}), ...(filters.ownerId ? { ownerId: filters.ownerId } : {}), ...(query ? { OR: [ { projectNumber: { contains: query } }, { name: { contains: query } }, { customer: { name: { contains: query } } }, ], } : {}), }, include: buildInclude(), orderBy: [{ dueDate: "asc" }, { updatedAt: "desc" }], }); return projects.map((project: unknown) => mapProjectSummary(project as ProjectRecord)); } export async function getProjectById(projectId: string) { const project = await projectModel.findUnique({ where: { id: projectId }, include: buildInclude(), }); return project ? mapProjectDetail(project as ProjectRecord) : null; } export async function createProject(payload: ProjectInput, actorId?: string | null) { const validated = await validateProjectInput(payload); if (!validated.ok) { return { ok: false as const, reason: validated.reason }; } const projectNumber = await nextProjectNumber(); const created = await projectModel.create({ data: { projectNumber, name: payload.name.trim(), status: payload.status, priority: payload.priority, customerId: payload.customerId, salesQuoteId: payload.salesQuoteId, salesOrderId: payload.salesOrderId, shipmentId: payload.shipmentId, ownerId: payload.ownerId, dueDate: payload.dueDate ? new Date(payload.dueDate) : null, notes: payload.notes, }, select: { id: true, }, }); const project = await getProjectById(created.id); if (project) { await logAuditEvent({ actorId, entityType: "project", entityId: created.id, action: "created", summary: `Created project ${project.projectNumber}.`, metadata: { projectNumber: project.projectNumber, customerId: project.customerId, status: project.status, priority: project.priority, }, }); } return project ? { ok: true as const, project } : { ok: false as const, reason: "Unable to load saved project." }; } export async function updateProject(projectId: string, payload: ProjectInput, actorId?: string | null) { const existing = await projectModel.findUnique({ where: { id: projectId }, select: { id: true }, }); if (!existing) { return { ok: false as const, reason: "Project was not found." }; } const validated = await validateProjectInput(payload); if (!validated.ok) { return { ok: false as const, reason: validated.reason }; } await projectModel.update({ where: { id: projectId }, data: { name: payload.name.trim(), status: payload.status, priority: payload.priority, customerId: payload.customerId, salesQuoteId: payload.salesQuoteId, salesOrderId: payload.salesOrderId, shipmentId: payload.shipmentId, ownerId: payload.ownerId, dueDate: payload.dueDate ? new Date(payload.dueDate) : null, notes: payload.notes, }, select: { id: true, }, }); const project = await getProjectById(projectId); if (project) { await logAuditEvent({ actorId, entityType: "project", entityId: projectId, action: "updated", summary: `Updated project ${project.projectNumber}.`, metadata: { projectNumber: project.projectNumber, customerId: project.customerId, status: project.status, priority: project.priority, }, }); } return project ? { ok: true as const, project } : { ok: false as const, reason: "Unable to load saved project." }; }