car_mms/app/lib/maintenance-visit-management.server.ts
2025-09-11 14:22:27 +03:00

365 lines
11 KiB
TypeScript

import { prisma } from "./db.server";
import type { MaintenanceVisit } from "@prisma/client";
import type {
CreateMaintenanceVisitData,
UpdateMaintenanceVisitData,
MaintenanceVisitWithRelations
} from "~/types/database";
// Get all maintenance visits with search and pagination
export async function getMaintenanceVisits(
searchQuery?: string,
page: number = 1,
limit: number = 10,
vehicleId?: number,
customerId?: number
): Promise<{
visits: MaintenanceVisitWithRelations[];
total: number;
totalPages: number;
}> {
const offset = (page - 1) * limit;
// Build where clause for search and filters
const whereClause: any = {};
if (vehicleId) {
whereClause.vehicleId = vehicleId;
}
if (customerId) {
whereClause.customerId = customerId;
}
if (searchQuery) {
const searchLower = searchQuery.toLowerCase();
whereClause.OR = [
{ maintenanceJobs: { contains: searchLower } },
{ description: { contains: searchLower } },
{ paymentStatus: { contains: searchLower } },
{ vehicle: { plateNumber: { contains: searchLower } } },
{ customer: { name: { contains: searchLower } } },
];
}
const [visits, total] = await Promise.all([
prisma.maintenanceVisit.findMany({
where: whereClause,
include: {
vehicle: {
select: {
id: true,
plateNumber: true,
manufacturer: true,
model: true,
year: true,
},
},
customer: {
select: {
id: true,
name: true,
phone: true,
email: true,
},
},
income: {
select: {
id: true,
amount: true,
incomeDate: true,
},
},
},
orderBy: { visitDate: 'desc' },
skip: offset,
take: limit,
}),
prisma.maintenanceVisit.count({ where: whereClause }),
]);
const totalPages = Math.ceil(total / limit);
return {
visits,
total,
totalPages,
};
}
// Get a single maintenance visit by ID
export async function getMaintenanceVisitById(id: number): Promise<MaintenanceVisitWithRelations | null> {
return prisma.maintenanceVisit.findUnique({
where: { id },
include: {
vehicle: {
select: {
id: true,
plateNumber: true,
manufacturer: true,
model: true,
year: true,
ownerId: true,
},
},
customer: {
select: {
id: true,
name: true,
phone: true,
email: true,
},
},
income: {
select: {
id: true,
amount: true,
incomeDate: true,
},
},
},
});
}
// Create a new maintenance visit
export async function createMaintenanceVisit(data: CreateMaintenanceVisitData): Promise<MaintenanceVisit> {
// Calculate next visit date based on delay
const nextVisitDate = new Date();
nextVisitDate.setMonth(nextVisitDate.getMonth() + data.nextVisitDelay);
// Start a transaction to create visit, update vehicle, and create income
const result = await prisma.$transaction(async (tx) => {
// Create the maintenance visit
const visit = await tx.maintenanceVisit.create({
data: {
vehicleId: data.vehicleId,
customerId: data.customerId,
maintenanceJobs: JSON.stringify(data.maintenanceJobs),
description: data.description,
cost: data.cost,
paymentStatus: data.paymentStatus || "pending",
kilometers: data.kilometers,
visitDate: data.visitDate || new Date(),
nextVisitDelay: data.nextVisitDelay,
},
});
// Update vehicle's last visit date and suggested next visit date
await tx.vehicle.update({
where: { id: data.vehicleId },
data: {
lastVisitDate: visit.visitDate,
suggestedNextVisitDate: nextVisitDate,
},
});
// Create income record
await tx.income.create({
data: {
maintenanceVisitId: visit.id,
amount: data.cost,
incomeDate: visit.visitDate,
},
});
return visit;
});
return result;
}
// Update an existing maintenance visit
export async function updateMaintenanceVisit(
id: number,
data: UpdateMaintenanceVisitData
): Promise<MaintenanceVisit> {
// Calculate next visit date if delay is updated
let nextVisitDate: Date | undefined;
if (data.nextVisitDelay) {
nextVisitDate = new Date(data.visitDate || new Date());
nextVisitDate.setMonth(nextVisitDate.getMonth() + data.nextVisitDelay);
}
// Start a transaction to update visit, vehicle, and income
const result = await prisma.$transaction(async (tx) => {
// Get the current visit to check for changes
const currentVisit = await tx.maintenanceVisit.findUnique({
where: { id },
select: { vehicleId: true, cost: true, visitDate: true },
});
if (!currentVisit) {
throw new Error("Maintenance visit not found");
}
// Update the maintenance visit
const visit = await tx.maintenanceVisit.update({
where: { id },
data: {
maintenanceJobs: data.maintenanceJobs ? JSON.stringify(data.maintenanceJobs) : undefined,
description: data.description,
cost: data.cost,
paymentStatus: data.paymentStatus,
kilometers: data.kilometers,
visitDate: data.visitDate,
nextVisitDelay: data.nextVisitDelay,
},
});
// Update vehicle if visit date or delay changed
if (data.visitDate || data.nextVisitDelay) {
const updateData: any = {};
if (data.visitDate) {
updateData.lastVisitDate = data.visitDate;
}
if (nextVisitDate) {
updateData.suggestedNextVisitDate = nextVisitDate;
}
if (Object.keys(updateData).length > 0) {
await tx.vehicle.update({
where: { id: currentVisit.vehicleId },
data: updateData,
});
}
}
// Update income record if cost or date changed
if (data.cost !== undefined || data.visitDate) {
await tx.income.updateMany({
where: { maintenanceVisitId: id },
data: {
amount: data.cost || currentVisit.cost,
incomeDate: data.visitDate || currentVisit.visitDate,
},
});
}
return visit;
});
return result;
}
// Delete a maintenance visit
export async function deleteMaintenanceVisit(id: number): Promise<void> {
await prisma.$transaction(async (tx) => {
// Get visit details before deletion
const visit = await tx.maintenanceVisit.findUnique({
where: { id },
select: { vehicleId: true },
});
if (!visit) {
throw new Error("Maintenance visit not found");
}
// Delete the maintenance visit (income will be deleted by cascade)
await tx.maintenanceVisit.delete({
where: { id },
});
// Update vehicle's last visit date to the most recent remaining visit
const lastVisit = await tx.maintenanceVisit.findFirst({
where: { vehicleId: visit.vehicleId },
orderBy: { visitDate: 'desc' },
select: { visitDate: true, nextVisitDelay: true },
});
let updateData: any = {};
if (lastVisit) {
updateData.lastVisitDate = lastVisit.visitDate;
// Recalculate next visit date
const nextDate = new Date(lastVisit.visitDate);
nextDate.setMonth(nextDate.getMonth() + lastVisit.nextVisitDelay);
updateData.suggestedNextVisitDate = nextDate;
} else {
// No visits left, clear the dates
updateData.lastVisitDate = null;
updateData.suggestedNextVisitDate = null;
}
await tx.vehicle.update({
where: { id: visit.vehicleId },
data: updateData,
});
});
}
// Get maintenance visits for a specific vehicle
export async function getVehicleMaintenanceHistory(vehicleId: number): Promise<MaintenanceVisitWithRelations[]> {
return prisma.maintenanceVisit.findMany({
where: { vehicleId },
include: {
vehicle: {
select: {
id: true,
plateNumber: true,
manufacturer: true,
model: true,
year: true,
},
},
customer: {
select: {
id: true,
name: true,
phone: true,
email: true,
},
},
income: {
select: {
id: true,
amount: true,
incomeDate: true,
},
},
},
orderBy: { visitDate: 'desc' },
});
}
// Get maintenance visits for a specific customer
export async function getCustomerMaintenanceHistory(customerId: number): Promise<MaintenanceVisitWithRelations[]> {
return prisma.maintenanceVisit.findMany({
where: { customerId },
include: {
vehicle: {
select: {
id: true,
plateNumber: true,
manufacturer: true,
model: true,
year: true,
},
},
customer: {
select: {
id: true,
name: true,
phone: true,
email: true,
},
},
maintenanceType: {
select: {
id: true,
name: true,
description: true,
},
},
income: {
select: {
id: true,
amount: true,
incomeDate: true,
},
},
},
orderBy: { visitDate: 'desc' },
});
}