368 lines
12 KiB
TypeScript
368 lines
12 KiB
TypeScript
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
|
|
} |