Bernt Christian Egeland 7 месяцев назад
Родитель
Сommit
18675b35e3

+ 102 - 0
src/app/api/desktop/v1/vehicles/[id]/notes/[noteId]/route.ts

@@ -0,0 +1,102 @@
+import { NextResponse } from "next/server";
+import { db } from "@/lib/db";
+import { withDesktopAuth } from "@/lib/with-desktop-auth";
+import { PermissionAction, PermissionSubject } from "@/lib/permissions";
+import { updateNoteSchema } from "@/features/vehicles/Schema/noteSchema";
+
+export async function PUT(
+  request: Request,
+  { params }: { params: Promise<{ id: string; noteId: string }> },
+) {
+  const { id, noteId } = await params;
+
+  return withDesktopAuth(
+    request,
+    async ({ organizationId }) => {
+      const note = await db.note.findFirst({
+        where: { id: noteId, vehicleId: id, vehicle: { organizationId } },
+      });
+      if (!note) {
+        return NextResponse.json({ error: "Note not found" }, { status: 404 });
+      }
+
+      const body = await request.json();
+      const data = updateNoteSchema.parse({ ...body, id: noteId });
+
+      const updated = await db.note.update({
+        where: { id: noteId },
+        data,
+      });
+
+      return NextResponse.json({ note: updated });
+    },
+    {
+      requiredPermissions: [
+        { action: PermissionAction.UPDATE, subject: PermissionSubject.VEHICLES },
+      ],
+    },
+  );
+}
+
+export async function DELETE(
+  request: Request,
+  { params }: { params: Promise<{ id: string; noteId: string }> },
+) {
+  const { noteId } = await params;
+
+  return withDesktopAuth(
+    request,
+    async ({ organizationId }) => {
+      const note = await db.note.findFirst({
+        where: { id: noteId, vehicle: { organizationId } },
+      });
+      if (!note) {
+        return NextResponse.json({ error: "Note not found" }, { status: 404 });
+      }
+
+      await db.note.delete({ where: { id: noteId } });
+      return new NextResponse(null, { status: 204 });
+    },
+    {
+      requiredPermissions: [
+        { action: PermissionAction.UPDATE, subject: PermissionSubject.VEHICLES },
+      ],
+    },
+  );
+}
+
+export async function PATCH(
+  request: Request,
+  { params }: { params: Promise<{ id: string; noteId: string }> },
+) {
+  const { noteId } = await params;
+
+  return withDesktopAuth(
+    request,
+    async ({ organizationId }) => {
+      const note = await db.note.findFirst({
+        where: { id: noteId, vehicle: { organizationId } },
+      });
+      if (!note) {
+        return NextResponse.json({ error: "Note not found" }, { status: 404 });
+      }
+
+      const body = await request.json();
+
+      if (body.action === "togglePin") {
+        await db.note.update({
+          where: { id: noteId },
+          data: { isPinned: !note.isPinned },
+        });
+        return NextResponse.json({ success: true });
+      }
+
+      return NextResponse.json({ error: "Invalid action" }, { status: 400 });
+    },
+    {
+      requiredPermissions: [
+        { action: PermissionAction.UPDATE, subject: PermissionSubject.VEHICLES },
+      ],
+    },
+  );
+}

+ 84 - 0
src/app/api/desktop/v1/vehicles/[id]/notes/route.ts

@@ -0,0 +1,84 @@
+import { NextResponse } from "next/server";
+import { db } from "@/lib/db";
+import { withDesktopAuth } from "@/lib/with-desktop-auth";
+import { PermissionAction, PermissionSubject } from "@/lib/permissions";
+import { createNoteSchema } from "@/features/vehicles/Schema/noteSchema";
+
+export async function GET(
+  request: Request,
+  { params }: { params: Promise<{ id: string }> },
+) {
+  const { id } = await params;
+
+  return withDesktopAuth(
+    request,
+    async ({ organizationId }) => {
+      const vehicle = await db.vehicle.findFirst({
+        where: { id, organizationId },
+        select: { id: true },
+      });
+      if (!vehicle) {
+        return NextResponse.json({ error: "Vehicle not found" }, { status: 404 });
+      }
+
+      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") || "10", 10)));
+      const skip = (page - 1) * pageSize;
+
+      const [records, total] = await Promise.all([
+        db.note.findMany({
+          where: { vehicleId: id },
+          orderBy: [{ isPinned: "desc" }, { createdAt: "desc" }],
+          skip,
+          take: pageSize,
+        }),
+        db.note.count({ where: { vehicleId: id } }),
+      ]);
+
+      return NextResponse.json({
+        records,
+        total,
+        page,
+        pageSize,
+        totalPages: Math.ceil(total / pageSize),
+      });
+    },
+    {
+      requiredPermissions: [
+        { action: PermissionAction.READ, subject: PermissionSubject.VEHICLES },
+      ],
+    },
+  );
+}
+
+export async function POST(
+  request: Request,
+  { params }: { params: Promise<{ id: string }> },
+) {
+  const { id } = await params;
+
+  return withDesktopAuth(
+    request,
+    async ({ organizationId }) => {
+      const vehicle = await db.vehicle.findFirst({
+        where: { id, organizationId },
+        select: { id: true },
+      });
+      if (!vehicle) {
+        return NextResponse.json({ error: "Vehicle not found" }, { status: 404 });
+      }
+
+      const body = await request.json();
+      const data = createNoteSchema.parse({ ...body, vehicleId: id });
+
+      const note = await db.note.create({ data });
+      return NextResponse.json({ note }, { status: 201 });
+    },
+    {
+      requiredPermissions: [
+        { action: PermissionAction.UPDATE, subject: PermissionSubject.VEHICLES },
+      ],
+    },
+  );
+}

+ 107 - 0
src/app/api/desktop/v1/vehicles/[id]/reminders/[reminderId]/route.ts

@@ -0,0 +1,107 @@
+import { NextResponse } from "next/server";
+import { db } from "@/lib/db";
+import { withDesktopAuth } from "@/lib/with-desktop-auth";
+import { PermissionAction, PermissionSubject } from "@/lib/permissions";
+import { updateReminderSchema } from "@/features/vehicles/Schema/reminderSchema";
+
+export async function PUT(
+  request: Request,
+  { params }: { params: Promise<{ id: string; reminderId: string }> },
+) {
+  const { reminderId } = await params;
+
+  return withDesktopAuth(
+    request,
+    async ({ organizationId }) => {
+      const reminder = await db.reminder.findFirst({
+        where: { id: reminderId, vehicle: { organizationId } },
+      });
+      if (!reminder) {
+        return NextResponse.json({ error: "Reminder not found" }, { status: 404 });
+      }
+
+      const body = await request.json();
+      const { id: _id, ...data } = updateReminderSchema.parse({ ...body, id: reminderId });
+
+      const updated = await db.reminder.update({
+        where: { id: reminderId },
+        data: {
+          ...data,
+          description: data.description !== undefined ? (data.description || null) : undefined,
+          dueDate: data.dueDate !== undefined ? (data.dueDate ? new Date(data.dueDate) : null) : undefined,
+          dueMileage: data.dueMileage !== undefined ? (data.dueMileage ?? null) : undefined,
+        },
+      });
+
+      return NextResponse.json({ reminder: updated });
+    },
+    {
+      requiredPermissions: [
+        { action: PermissionAction.UPDATE, subject: PermissionSubject.VEHICLES },
+      ],
+    },
+  );
+}
+
+export async function DELETE(
+  request: Request,
+  { params }: { params: Promise<{ id: string; reminderId: string }> },
+) {
+  const { reminderId } = await params;
+
+  return withDesktopAuth(
+    request,
+    async ({ organizationId }) => {
+      const reminder = await db.reminder.findFirst({
+        where: { id: reminderId, vehicle: { organizationId } },
+      });
+      if (!reminder) {
+        return NextResponse.json({ error: "Reminder not found" }, { status: 404 });
+      }
+
+      await db.reminder.delete({ where: { id: reminderId } });
+      return new NextResponse(null, { status: 204 });
+    },
+    {
+      requiredPermissions: [
+        { action: PermissionAction.UPDATE, subject: PermissionSubject.VEHICLES },
+      ],
+    },
+  );
+}
+
+export async function PATCH(
+  request: Request,
+  { params }: { params: Promise<{ id: string; reminderId: string }> },
+) {
+  const { reminderId } = await params;
+
+  return withDesktopAuth(
+    request,
+    async ({ organizationId }) => {
+      const reminder = await db.reminder.findFirst({
+        where: { id: reminderId, vehicle: { organizationId } },
+      });
+      if (!reminder) {
+        return NextResponse.json({ error: "Reminder not found" }, { status: 404 });
+      }
+
+      const body = await request.json();
+
+      if (body.action === "toggle") {
+        await db.reminder.update({
+          where: { id: reminderId },
+          data: { isCompleted: !reminder.isCompleted },
+        });
+        return NextResponse.json({ success: true });
+      }
+
+      return NextResponse.json({ error: "Invalid action" }, { status: 400 });
+    },
+    {
+      requiredPermissions: [
+        { action: PermissionAction.UPDATE, subject: PermissionSubject.VEHICLES },
+      ],
+    },
+  );
+}

+ 42 - 0
src/app/api/desktop/v1/vehicles/[id]/reminders/route.ts

@@ -0,0 +1,42 @@
+import { NextResponse } from "next/server";
+import { db } from "@/lib/db";
+import { withDesktopAuth } from "@/lib/with-desktop-auth";
+import { PermissionAction, PermissionSubject } from "@/lib/permissions";
+import { createReminderSchema } from "@/features/vehicles/Schema/reminderSchema";
+
+export async function POST(
+  request: Request,
+  { params }: { params: Promise<{ id: string }> },
+) {
+  const { id } = await params;
+
+  return withDesktopAuth(
+    request,
+    async ({ organizationId }) => {
+      const vehicle = await db.vehicle.findFirst({
+        where: { id, organizationId },
+        select: { id: true },
+      });
+      if (!vehicle) {
+        return NextResponse.json({ error: "Vehicle not found" }, { status: 404 });
+      }
+
+      const body = await request.json();
+      const data = createReminderSchema.parse({ ...body, vehicleId: id });
+
+      const reminder = await db.reminder.create({
+        data: {
+          ...data,
+          dueDate: data.dueDate ? new Date(data.dueDate) : null,
+        },
+      });
+
+      return NextResponse.json({ reminder }, { status: 201 });
+    },
+    {
+      requiredPermissions: [
+        { action: PermissionAction.UPDATE, subject: PermissionSubject.VEHICLES },
+      ],
+    },
+  );
+}

+ 67 - 1
src/app/api/desktop/v1/vehicles/[id]/route.ts

@@ -17,7 +17,29 @@ export async function GET(
         where: { id, organizationId },
         include: {
           customer: { select: { id: true, name: true, company: true, email: true, phone: true } },
-          _count: { select: { serviceRecords: true } },
+          serviceRecords: {
+            orderBy: { serviceDate: "desc" },
+            select: { id: true, cost: true, totalAmount: true },
+          },
+          reminders: {
+            orderBy: [{ isCompleted: "asc" }, { dueDate: "asc" }],
+            select: {
+              id: true,
+              title: true,
+              description: true,
+              dueDate: true,
+              dueMileage: true,
+              isCompleted: true,
+              createdAt: true,
+            },
+          },
+          _count: {
+            select: {
+              serviceRecords: true,
+              notes: true,
+              reminders: true,
+            },
+          },
         },
       });
 
@@ -80,6 +102,50 @@ export async function PUT(
   );
 }
 
+export async function PATCH(
+  request: Request,
+  { params }: { params: Promise<{ id: string }> },
+) {
+  const { id } = await params;
+
+  return withDesktopAuth(
+    request,
+    async ({ organizationId }) => {
+      const body = await request.json();
+      const { action } = body;
+
+      if (action === "archive") {
+        const result = await db.vehicle.updateMany({
+          where: { id, organizationId },
+          data: { isArchived: true, archiveReason: body.reason || null },
+        });
+        if (result.count === 0) {
+          return NextResponse.json({ error: "Vehicle not found" }, { status: 404 });
+        }
+        return NextResponse.json({ success: true });
+      }
+
+      if (action === "unarchive") {
+        const result = await db.vehicle.updateMany({
+          where: { id, organizationId },
+          data: { isArchived: false, archiveReason: null },
+        });
+        if (result.count === 0) {
+          return NextResponse.json({ error: "Vehicle not found" }, { status: 404 });
+        }
+        return NextResponse.json({ success: true });
+      }
+
+      return NextResponse.json({ error: "Invalid action" }, { status: 400 });
+    },
+    {
+      requiredPermissions: [
+        { action: PermissionAction.UPDATE, subject: PermissionSubject.VEHICLES },
+      ],
+    },
+  );
+}
+
 export async function DELETE(
   request: Request,
   { params }: { params: Promise<{ id: string }> },

+ 74 - 0
src/app/api/desktop/v1/vehicles/[id]/services/route.ts

@@ -0,0 +1,74 @@
+import { NextResponse } from "next/server";
+import { db } from "@/lib/db";
+import { withDesktopAuth } from "@/lib/with-desktop-auth";
+import { PermissionAction, PermissionSubject } from "@/lib/permissions";
+
+export async function GET(
+  request: Request,
+  { params }: { params: Promise<{ id: string }> },
+) {
+  const { id } = await params;
+
+  return withDesktopAuth(
+    request,
+    async ({ organizationId }) => {
+      const vehicle = await db.vehicle.findFirst({
+        where: { id, organizationId },
+        select: { id: true },
+      });
+      if (!vehicle) {
+        return NextResponse.json({ error: "Vehicle not found" }, { status: 404 });
+      }
+
+      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") || "10", 10)));
+      const search = url.searchParams.get("search") || "";
+      const type = url.searchParams.get("type") || "all";
+      const skip = (page - 1) * pageSize;
+
+      // eslint-disable-next-line @typescript-eslint/no-explicit-any
+      const where: any = { vehicleId: id };
+
+      if (search) {
+        where.OR = [
+          { title: { contains: search, mode: "insensitive" } },
+          { description: { contains: search, mode: "insensitive" } },
+          { diagnosticNotes: { contains: search, mode: "insensitive" } },
+          { techName: { contains: search, mode: "insensitive" } },
+          { shopName: { contains: search, mode: "insensitive" } },
+        ];
+      }
+
+      if (type && type !== "all") {
+        where.type = type;
+      }
+
+      const [records, total] = await Promise.all([
+        db.serviceRecord.findMany({
+          where,
+          include: {
+            _count: { select: { partItems: true, laborItems: true, attachments: true } },
+          },
+          orderBy: { serviceDate: "desc" },
+          skip,
+          take: pageSize,
+        }),
+        db.serviceRecord.count({ where }),
+      ]);
+
+      return NextResponse.json({
+        records,
+        total,
+        page,
+        pageSize,
+        totalPages: Math.ceil(total / pageSize),
+      });
+    },
+    {
+      requiredPermissions: [
+        { action: PermissionAction.READ, subject: PermissionSubject.VEHICLES },
+      ],
+    },
+  );
+}

+ 3 - 1
src/app/api/desktop/v1/vehicles/route.ts

@@ -31,7 +31,7 @@ export async function GET(request: Request) {
         }
       }
 
-      const [vehicles, total] = await Promise.all([
+      const [vehicles, total, archivedCount] = await Promise.all([
         db.vehicle.findMany({
           where,
           include: {
@@ -43,6 +43,7 @@ export async function GET(request: Request) {
           take: pageSize,
         }),
         db.vehicle.count({ where }),
+        db.vehicle.count({ where: { organizationId, isArchived: true } }),
       ]);
 
       return NextResponse.json({
@@ -51,6 +52,7 @@ export async function GET(request: Request) {
         page,
         pageSize,
         totalPages: Math.ceil(total / pageSize),
+        archivedCount,
       });
     },
     {