365 lines
11 KiB
TypeScript
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' },
|
|
});
|
|
} |