src/loading-bays/bay-conformance.service.ts
Methods |
|
constructor(prisma: PrismaService)
|
||||||
|
Parameters :
|
| Async getConformanceStats |
getConformanceStats(orgId: string, from?: string, to?: string)
|
|
Returns :
unknown
|
| Async listEvents |
listEvents(orgId: string, bayId?: string, limit: number)
|
|
Returns :
unknown
|
| Async recordEvent | |||||||||
recordEvent(orgId: string, data: literal type)
|
|||||||||
|
Parameters :
Returns :
unknown
|
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
@Injectable()
export class BayConformanceService {
constructor(private prisma: PrismaService) {}
// Record a bay event (truck arrival/departure)
async recordEvent(
orgId: string,
data: {
loadingBayId: string;
vehicleId?: string;
tripId?: string;
eventType: string;
scheduledAt?: Date;
actualAt?: Date;
notes?: string;
},
) {
const actual = data.actualAt || new Date();
let varianceMin = 0;
let status = 'ON_TIME';
if (data.scheduledAt) {
varianceMin = Math.round(
(actual.getTime() - new Date(data.scheduledAt).getTime()) / 60000,
);
if (varianceMin > 15) status = 'LATE';
else if (varianceMin < -15) status = 'EARLY';
}
return this.prisma.bayEvent.create({
data: {
organizationId: orgId,
loadingBayId: data.loadingBayId,
vehicleId: data.vehicleId,
tripId: data.tripId,
eventType: data.eventType,
scheduledAt: data.scheduledAt ? new Date(data.scheduledAt) : null,
actualAt: actual,
varianceMin,
status,
notes: data.notes,
},
});
}
// Get conformance stats
async getConformanceStats(orgId: string, from?: string, to?: string) {
const where: Record<string, unknown> = { organizationId: orgId };
if (from || to) {
const actualAt: Record<string, Date> = {};
if (from) actualAt.gte = new Date(from);
if (to) actualAt.lte = new Date(to);
where.actualAt = actualAt;
}
const events = await this.prisma.bayEvent.findMany({ where });
const total = events.length;
const onTime = events.filter((e) => e.status === 'ON_TIME').length;
const late = events.filter((e) => e.status === 'LATE').length;
const early = events.filter((e) => e.status === 'EARLY').length;
const avgVariance =
total > 0
? Math.round(
events.reduce((s, e) => s + (e.varianceMin || 0), 0) / total,
)
: 0;
const conformanceRate =
total > 0 ? Math.round((onTime / total) * 100) : 0;
// Per-bay breakdown
const byBay = new Map<
string,
{ total: number; onTime: number; late: number; early: number }
>();
for (const e of events) {
const bay = byBay.get(e.loadingBayId) || {
total: 0,
onTime: 0,
late: 0,
early: 0,
};
bay.total++;
if (e.status === 'ON_TIME') bay.onTime++;
if (e.status === 'LATE') bay.late++;
if (e.status === 'EARLY') bay.early++;
byBay.set(e.loadingBayId, bay);
}
return {
total,
onTime,
late,
early,
avgVariance,
conformanceRate,
byBay: Object.fromEntries(byBay),
};
}
// List recent bay events
async listEvents(orgId: string, bayId?: string, limit = 50) {
return this.prisma.bayEvent.findMany({
where: {
organizationId: orgId,
...(bayId ? { loadingBayId: bayId } : {}),
},
orderBy: { actualAt: 'desc' },
take: limit,
include: { loadingBay: { select: { name: true, code: true } } },
});
}
}