Bernt Christian Egeland hai 7 meses
pai
achega
db79270d48

+ 141 - 0
src/app/api/desktop/v1/work-orders/[id]/route.ts

@@ -0,0 +1,141 @@
+import { NextResponse } from "next/server";
+import { db } from "@/lib/db";
+import { withDesktopAuth } from "@/lib/with-desktop-auth";
+import { PermissionAction, PermissionSubject } from "@/lib/permissions";
+import { updateServiceSchema } from "@/features/vehicles/Schema/serviceSchema";
+import { recordDeletion } from "@/lib/sync-deletion";
+
+export async function GET(
+  request: Request,
+  { params }: { params: Promise<{ id: string }> },
+) {
+  const { id } = await params;
+
+  return withDesktopAuth(
+    request,
+    async ({ organizationId }) => {
+      const workOrder = await db.serviceRecord.findFirst({
+        where: { id, vehicle: { customer: { organizationId } } },
+        include: {
+          partItems: true,
+          laborItems: true,
+          attachments: true,
+          payments: true,
+          vehicle: { include: { customer: true } },
+        },
+      });
+
+      if (!workOrder) {
+        return NextResponse.json({ error: "Work order not found" }, { status: 404 });
+      }
+
+      return NextResponse.json({ workOrder });
+    },
+    {
+      requiredPermissions: [
+        { action: PermissionAction.READ, subject: PermissionSubject.SERVICES },
+      ],
+    },
+  );
+}
+
+export async function PUT(
+  request: Request,
+  { params }: { params: Promise<{ id: string }> },
+) {
+  const { id } = await params;
+
+  return withDesktopAuth(
+    request,
+    async ({ organizationId }) => {
+      const body = await request.json();
+      const { id: _schemaId, partItems, laborItems, ...recordData } = updateServiceSchema.parse({ ...body, id });
+
+      const existing = await db.serviceRecord.findFirst({
+        where: { id, vehicle: { customer: { organizationId } } },
+      });
+      if (!existing) {
+        return NextResponse.json({ error: "Work order not found" }, { status: 404 });
+      }
+
+      const workOrder = await db.$transaction(async (tx) => {
+        const updated = await tx.serviceRecord.update({
+          where: { id },
+          data: {
+            ...recordData,
+            description: recordData.description !== undefined ? (recordData.description || null) : undefined,
+            techName: recordData.techName !== undefined ? (recordData.techName || null) : undefined,
+            diagnosticNotes: recordData.diagnosticNotes !== undefined ? (recordData.diagnosticNotes || null) : undefined,
+            invoiceNotes: recordData.invoiceNotes !== undefined ? (recordData.invoiceNotes || null) : undefined,
+            invoiceNumber: recordData.invoiceNumber !== undefined ? (recordData.invoiceNumber || null) : undefined,
+            mileage: recordData.mileage !== undefined ? (recordData.mileage ?? null) : undefined,
+            serviceDate: recordData.serviceDate ? new Date(recordData.serviceDate) : undefined,
+          },
+        });
+
+        if (partItems !== undefined) {
+          await tx.servicePart.deleteMany({ where: { serviceRecordId: id } });
+          if (partItems.length > 0) {
+            await tx.servicePart.createMany({
+              data: partItems.map((p) => {
+                // eslint-disable-next-line @typescript-eslint/no-unused-vars
+                const { inventoryPartId: _inv, ...partData } = p;
+                return { ...partData, serviceRecordId: id };
+              }),
+            });
+          }
+        }
+
+        if (laborItems !== undefined) {
+          await tx.serviceLabor.deleteMany({ where: { serviceRecordId: id } });
+          if (laborItems.length > 0) {
+            await tx.serviceLabor.createMany({
+              data: laborItems.map((l) => ({
+                ...l,
+                serviceRecordId: id,
+              })),
+            });
+          }
+        }
+
+        return updated;
+      });
+
+      return NextResponse.json({ workOrder });
+    },
+    {
+      requiredPermissions: [
+        { action: PermissionAction.UPDATE, subject: PermissionSubject.SERVICES },
+      ],
+    },
+  );
+}
+
+export async function DELETE(
+  request: Request,
+  { params }: { params: Promise<{ id: string }> },
+) {
+  const { id } = await params;
+
+  return withDesktopAuth(
+    request,
+    async ({ organizationId }) => {
+      const existing = await db.serviceRecord.findFirst({
+        where: { id, vehicle: { customer: { organizationId } } },
+      });
+      if (!existing) {
+        return NextResponse.json({ error: "Work order not found" }, { status: 404 });
+      }
+
+      await recordDeletion("serviceRecord", id, organizationId);
+      await db.serviceRecord.delete({ where: { id } });
+
+      return new NextResponse(null, { status: 204 });
+    },
+    {
+      requiredPermissions: [
+        { action: PermissionAction.DELETE, subject: PermissionSubject.SERVICES },
+      ],
+    },
+  );
+}

+ 146 - 0
src/app/api/desktop/v1/work-orders/route.ts

@@ -0,0 +1,146 @@
+import { NextResponse } from "next/server";
+import { db } from "@/lib/db";
+import { withDesktopAuth } from "@/lib/with-desktop-auth";
+import { PermissionAction, PermissionSubject } from "@/lib/permissions";
+import { createServiceSchema } from "@/features/vehicles/Schema/serviceSchema";
+
+export async function GET(request: Request) {
+  return withDesktopAuth(
+    request,
+    async ({ organizationId }) => {
+      const url = new URL(request.url);
+      const page = Math.max(1, parseInt(url.searchParams.get("page") || "1", 10));
+      const pageSize = Math.min(100, Math.max(1, parseInt(url.searchParams.get("pageSize") || "20", 10)));
+      const search = url.searchParams.get("search") || "";
+      const status = url.searchParams.get("status") || "";
+      const type = url.searchParams.get("type") || "";
+      const skip = (page - 1) * pageSize;
+
+      // eslint-disable-next-line @typescript-eslint/no-explicit-any
+      const where: any = {
+        vehicle: { customer: { organizationId } },
+      };
+
+      if (search) {
+        where.OR = [
+          { title: { contains: search, mode: "insensitive" } },
+          { description: { contains: search, mode: "insensitive" } },
+          { invoiceNumber: { contains: search, mode: "insensitive" } },
+          { techName: { contains: search, mode: "insensitive" } },
+          { vehicle: { make: { contains: search, mode: "insensitive" } } },
+          { vehicle: { model: { contains: search, mode: "insensitive" } } },
+          { vehicle: { licensePlate: { contains: search, mode: "insensitive" } } },
+        ];
+      }
+
+      if (status && status !== "all") {
+        where.status = status;
+      }
+
+      if (type && type !== "all") {
+        where.type = type;
+      }
+
+      const [workOrders, total] = await Promise.all([
+        db.serviceRecord.findMany({
+          where,
+          include: {
+            vehicle: {
+              select: {
+                id: true,
+                make: true,
+                model: true,
+                year: true,
+                licensePlate: true,
+                customer: { select: { id: true, name: true, company: true } },
+              },
+            },
+            _count: { select: { partItems: true, laborItems: true, attachments: true } },
+          },
+          orderBy: { updatedAt: "desc" },
+          skip,
+          take: pageSize,
+        }),
+        db.serviceRecord.count({ where }),
+      ]);
+
+      return NextResponse.json({
+        workOrders,
+        total,
+        page,
+        pageSize,
+        totalPages: Math.ceil(total / pageSize),
+      });
+    },
+    {
+      requiredPermissions: [
+        { action: PermissionAction.READ, subject: PermissionSubject.SERVICES },
+      ],
+    },
+  );
+}
+
+export async function POST(request: Request) {
+  return withDesktopAuth(
+    request,
+    async ({ organizationId }) => {
+      const body = await request.json();
+      const data = createServiceSchema.parse(body);
+
+      const vehicle = await db.vehicle.findFirst({
+        where: { id: data.vehicleId, customer: { organizationId } },
+      });
+      if (!vehicle) {
+        return NextResponse.json({ error: "Vehicle not found" }, { status: 404 });
+      }
+
+      const { partItems, laborItems, attachments, ...recordData } = data;
+
+      const workOrder = await db.$transaction(async (tx) => {
+        const created = await tx.serviceRecord.create({
+          data: {
+            ...recordData,
+            serviceDate: new Date(recordData.serviceDate),
+          },
+        });
+
+        if (partItems && partItems.length > 0) {
+          await tx.servicePart.createMany({
+            data: partItems.map((p) => {
+              // eslint-disable-next-line @typescript-eslint/no-unused-vars
+              const { inventoryPartId: _inv, ...partData } = p;
+              return { ...partData, serviceRecordId: created.id };
+            }),
+          });
+        }
+
+        if (laborItems && laborItems.length > 0) {
+          await tx.serviceLabor.createMany({
+            data: laborItems.map((l) => ({
+              ...l,
+              serviceRecordId: created.id,
+            })),
+          });
+        }
+
+        if (attachments && attachments.length > 0) {
+          await tx.serviceAttachment.createMany({
+            data: attachments.map((a) => ({
+              ...a,
+              serviceRecordId: created.id,
+            })),
+          });
+        }
+
+        return created;
+      });
+
+      return NextResponse.json({ workOrder }, { status: 201 });
+    },
+    {
+      requiredPermissions: [
+        { action: PermissionAction.CREATE, subject: PermissionSubject.SERVICES },
+      ],
+    },
+  );
+}