uup
This commit is contained in:
368
app/lib/vehicle-management.server.ts
Normal file
368
app/lib/vehicle-management.server.ts
Normal file
@@ -0,0 +1,368 @@
|
||||
import { prisma } from "./db.server";
|
||||
import type { Vehicle } from "@prisma/client";
|
||||
import type { CreateVehicleData, UpdateVehicleData, VehicleWithOwner, VehicleWithRelations } from "~/types/database";
|
||||
|
||||
// Get all vehicles with search and pagination
|
||||
export async function getVehicles(
|
||||
searchQuery?: string,
|
||||
page: number = 1,
|
||||
limit: number = 10,
|
||||
ownerId?: number,
|
||||
plateNumber?: string
|
||||
): Promise<{
|
||||
vehicles: VehicleWithOwner[];
|
||||
total: number;
|
||||
totalPages: number;
|
||||
}> {
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
// Build where clause for search
|
||||
const whereClause: any = {};
|
||||
|
||||
if (ownerId) {
|
||||
whereClause.ownerId = ownerId;
|
||||
}
|
||||
|
||||
if (plateNumber) {
|
||||
whereClause.plateNumber = { contains: plateNumber.toLowerCase() };
|
||||
}
|
||||
|
||||
if (searchQuery) {
|
||||
const searchLower = searchQuery.toLowerCase();
|
||||
whereClause.OR = [
|
||||
{ plateNumber: { contains: searchLower } },
|
||||
{ manufacturer: { contains: searchLower } },
|
||||
{ model: { contains: searchLower } },
|
||||
{ bodyType: { contains: searchLower } },
|
||||
{ owner: { name: { contains: searchLower } } },
|
||||
];
|
||||
}
|
||||
|
||||
const [vehicles, total] = await Promise.all([
|
||||
prisma.vehicle.findMany({
|
||||
where: whereClause,
|
||||
include: {
|
||||
owner: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
phone: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { createdDate: 'desc' },
|
||||
skip: offset,
|
||||
take: limit,
|
||||
}),
|
||||
prisma.vehicle.count({ where: whereClause }),
|
||||
]);
|
||||
|
||||
return {
|
||||
vehicles,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
};
|
||||
}
|
||||
|
||||
// Get vehicle by ID with full relationships
|
||||
export async function getVehicleById(id: number): Promise<VehicleWithRelations | null> {
|
||||
return await prisma.vehicle.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
owner: true,
|
||||
maintenanceVisits: {
|
||||
orderBy: { visitDate: 'desc' },
|
||||
take: 10,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Create new vehicle
|
||||
export async function createVehicle(
|
||||
vehicleData: CreateVehicleData
|
||||
): Promise<{ success: boolean; vehicle?: Vehicle; error?: string }> {
|
||||
try {
|
||||
// Check if vehicle with same plate number already exists
|
||||
const existingVehicle = await prisma.vehicle.findUnique({
|
||||
where: { plateNumber: vehicleData.plateNumber },
|
||||
});
|
||||
|
||||
if (existingVehicle) {
|
||||
return { success: false, error: "رقم اللوحة موجود بالفعل" };
|
||||
}
|
||||
|
||||
// Check if owner exists
|
||||
const owner = await prisma.customer.findUnique({
|
||||
where: { id: vehicleData.ownerId },
|
||||
});
|
||||
|
||||
if (!owner) {
|
||||
return { success: false, error: "المالك غير موجود" };
|
||||
}
|
||||
|
||||
// Create vehicle
|
||||
const vehicle = await prisma.vehicle.create({
|
||||
data: {
|
||||
plateNumber: vehicleData.plateNumber.trim(),
|
||||
bodyType: vehicleData.bodyType.trim(),
|
||||
manufacturer: vehicleData.manufacturer.trim(),
|
||||
model: vehicleData.model.trim(),
|
||||
trim: vehicleData.trim?.trim() || null,
|
||||
year: vehicleData.year,
|
||||
transmission: vehicleData.transmission,
|
||||
fuel: vehicleData.fuel,
|
||||
cylinders: vehicleData.cylinders || null,
|
||||
engineDisplacement: vehicleData.engineDisplacement || null,
|
||||
useType: vehicleData.useType,
|
||||
ownerId: vehicleData.ownerId,
|
||||
},
|
||||
});
|
||||
|
||||
return { success: true, vehicle };
|
||||
} catch (error) {
|
||||
console.error("Error creating vehicle:", error);
|
||||
return { success: false, error: "حدث خطأ أثناء إنشاء المركبة" };
|
||||
}
|
||||
}
|
||||
|
||||
// Update vehicle
|
||||
export async function updateVehicle(
|
||||
id: number,
|
||||
vehicleData: UpdateVehicleData
|
||||
): Promise<{ success: boolean; vehicle?: Vehicle; error?: string }> {
|
||||
try {
|
||||
// Check if vehicle exists
|
||||
const existingVehicle = await prisma.vehicle.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!existingVehicle) {
|
||||
return { success: false, error: "المركبة غير موجودة" };
|
||||
}
|
||||
|
||||
// Check for plate number conflicts with other vehicles
|
||||
if (vehicleData.plateNumber) {
|
||||
const conflictVehicle = await prisma.vehicle.findFirst({
|
||||
where: {
|
||||
AND: [
|
||||
{ id: { not: id } },
|
||||
{ plateNumber: vehicleData.plateNumber },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
if (conflictVehicle) {
|
||||
return { success: false, error: "رقم اللوحة موجود بالفعل" };
|
||||
}
|
||||
}
|
||||
|
||||
// Check if new owner exists (if changing owner)
|
||||
if (vehicleData.ownerId && vehicleData.ownerId !== existingVehicle.ownerId) {
|
||||
const owner = await prisma.customer.findUnique({
|
||||
where: { id: vehicleData.ownerId },
|
||||
});
|
||||
|
||||
if (!owner) {
|
||||
return { success: false, error: "المالك الجديد غير موجود" };
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare update data
|
||||
const updateData: any = {};
|
||||
if (vehicleData.plateNumber !== undefined) updateData.plateNumber = vehicleData.plateNumber.trim();
|
||||
if (vehicleData.bodyType !== undefined) updateData.bodyType = vehicleData.bodyType.trim();
|
||||
if (vehicleData.manufacturer !== undefined) updateData.manufacturer = vehicleData.manufacturer.trim();
|
||||
if (vehicleData.model !== undefined) updateData.model = vehicleData.model.trim();
|
||||
if (vehicleData.trim !== undefined) updateData.trim = vehicleData.trim?.trim() || null;
|
||||
if (vehicleData.year !== undefined) updateData.year = vehicleData.year;
|
||||
if (vehicleData.transmission !== undefined) updateData.transmission = vehicleData.transmission;
|
||||
if (vehicleData.fuel !== undefined) updateData.fuel = vehicleData.fuel;
|
||||
if (vehicleData.cylinders !== undefined) updateData.cylinders = vehicleData.cylinders || null;
|
||||
if (vehicleData.engineDisplacement !== undefined) updateData.engineDisplacement = vehicleData.engineDisplacement || null;
|
||||
if (vehicleData.useType !== undefined) updateData.useType = vehicleData.useType;
|
||||
if (vehicleData.ownerId !== undefined) updateData.ownerId = vehicleData.ownerId;
|
||||
if (vehicleData.lastVisitDate !== undefined) updateData.lastVisitDate = vehicleData.lastVisitDate;
|
||||
if (vehicleData.suggestedNextVisitDate !== undefined) updateData.suggestedNextVisitDate = vehicleData.suggestedNextVisitDate;
|
||||
|
||||
// Update vehicle
|
||||
const vehicle = await prisma.vehicle.update({
|
||||
where: { id },
|
||||
data: updateData,
|
||||
});
|
||||
|
||||
return { success: true, vehicle };
|
||||
} catch (error) {
|
||||
console.error("Error updating vehicle:", error);
|
||||
return { success: false, error: "حدث خطأ أثناء تحديث المركبة" };
|
||||
}
|
||||
}
|
||||
|
||||
// Delete vehicle with relationship handling
|
||||
export async function deleteVehicle(
|
||||
id: number
|
||||
): Promise<{ success: boolean; error?: string }> {
|
||||
try {
|
||||
// Check if vehicle exists
|
||||
const existingVehicle = await prisma.vehicle.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
maintenanceVisits: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingVehicle) {
|
||||
return { success: false, error: "المركبة غير موجودة" };
|
||||
}
|
||||
|
||||
// Check if vehicle has maintenance visits
|
||||
if (existingVehicle.maintenanceVisits.length > 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: `لا يمكن حذف المركبة لأنها تحتوي على ${existingVehicle.maintenanceVisits.length} زيارة صيانة. يرجى حذف الزيارات أولاً`
|
||||
};
|
||||
}
|
||||
|
||||
// Delete vehicle
|
||||
await prisma.vehicle.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error("Error deleting vehicle:", error);
|
||||
return { success: false, error: "حدث خطأ أثناء حذف المركبة" };
|
||||
}
|
||||
}
|
||||
|
||||
// Get vehicles for dropdown/select options
|
||||
export async function getVehiclesForSelect(ownerId?: number): Promise<{
|
||||
id: number;
|
||||
plateNumber: string;
|
||||
manufacturer: string;
|
||||
model: string;
|
||||
year: number;
|
||||
}[]> {
|
||||
const whereClause = ownerId ? { ownerId } : {};
|
||||
|
||||
return await prisma.vehicle.findMany({
|
||||
where: whereClause,
|
||||
select: {
|
||||
id: true,
|
||||
plateNumber: true,
|
||||
manufacturer: true,
|
||||
model: true,
|
||||
year: true,
|
||||
},
|
||||
orderBy: { plateNumber: 'asc' },
|
||||
});
|
||||
}
|
||||
|
||||
// Get vehicle statistics
|
||||
export async function getVehicleStats(vehicleId: number): Promise<{
|
||||
totalVisits: number;
|
||||
totalSpent: number;
|
||||
lastVisitDate?: Date;
|
||||
nextSuggestedVisitDate?: Date;
|
||||
averageVisitCost: number;
|
||||
} | null> {
|
||||
const vehicle = await prisma.vehicle.findUnique({
|
||||
where: { id: vehicleId },
|
||||
include: {
|
||||
maintenanceVisits: {
|
||||
select: {
|
||||
cost: true,
|
||||
visitDate: true,
|
||||
},
|
||||
orderBy: { visitDate: 'desc' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!vehicle) return null;
|
||||
|
||||
const totalSpent = vehicle.maintenanceVisits.reduce((sum, visit) => sum + visit.cost, 0);
|
||||
const averageVisitCost = vehicle.maintenanceVisits.length > 0
|
||||
? totalSpent / vehicle.maintenanceVisits.length
|
||||
: 0;
|
||||
const lastVisitDate = vehicle.maintenanceVisits.length > 0
|
||||
? vehicle.maintenanceVisits[0].visitDate
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
totalVisits: vehicle.maintenanceVisits.length,
|
||||
totalSpent,
|
||||
lastVisitDate,
|
||||
nextSuggestedVisitDate: vehicle.suggestedNextVisitDate || undefined,
|
||||
averageVisitCost,
|
||||
};
|
||||
}
|
||||
|
||||
// Search vehicles by plate number or manufacturer (for autocomplete)
|
||||
export async function searchVehicles(query: string, limit: number = 10): Promise<{
|
||||
id: number;
|
||||
plateNumber: string;
|
||||
manufacturer: string;
|
||||
model: string;
|
||||
year: number;
|
||||
owner: { id: number; name: string; };
|
||||
}[]> {
|
||||
if (!query || query.trim().length < 2) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const searchLower = query.toLowerCase();
|
||||
|
||||
return await prisma.vehicle.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ plateNumber: { contains: searchLower } },
|
||||
{ manufacturer: { contains: searchLower } },
|
||||
{ model: { contains: searchLower } },
|
||||
],
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
plateNumber: true,
|
||||
manufacturer: true,
|
||||
model: true,
|
||||
year: true,
|
||||
owner: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { plateNumber: 'asc' },
|
||||
take: limit,
|
||||
});
|
||||
}
|
||||
|
||||
// Get vehicles by owner ID
|
||||
export async function getVehiclesByOwner(ownerId: number): Promise<VehicleWithOwner[]> {
|
||||
return await prisma.vehicle.findMany({
|
||||
where: { ownerId },
|
||||
include: {
|
||||
owner: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
phone: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { createdDate: 'desc' },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export async function getMaintenanceVisitsByVehicle(vehicleId: number) {
|
||||
const visits = await prisma.maintenanceVisit.findMany({
|
||||
where: { vehicleId: vehicleId },
|
||||
orderBy: { createdDate: 'desc' },
|
||||
});
|
||||
return visits
|
||||
}
|
||||
Reference in New Issue
Block a user