File

src/loading-bays/bay-conformance.service.ts

Index

Methods

Constructor

constructor(prisma: PrismaService)
Parameters :
Name Type Optional
prisma PrismaService No

Methods

Async getConformanceStats
getConformanceStats(orgId: string, from?: string, to?: string)
Parameters :
Name Type Optional
orgId string No
from string Yes
to string Yes
Returns : unknown
Async listEvents
listEvents(orgId: string, bayId?: string, limit: number)
Parameters :
Name Type Optional Default value
orgId string No
bayId string Yes
limit number No 50
Returns : unknown
Async recordEvent
recordEvent(orgId: string, data: literal type)
Parameters :
Name Type Optional
orgId string No
data literal type No
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 } } },
    });
  }
}

results matching ""

    No results matching ""