File

src/orders/pod/pod.service.ts

Index

Methods

Constructor

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

Methods

Async getPendingPods
getPendingPods(orgId: string)

List all orders pending POD (in-transit but no POD document yet)

Parameters :
Name Type Optional
orgId string No
Returns : unknown
Async getPod
getPod(orderId: string)

Get POD for an order

Parameters :
Name Type Optional
orderId string No
Returns : unknown
Async submitPod
submitPod(orderId: string, data: literal type)

Submit proof of delivery for an order. Called by driver (via API or future mobile app) when delivery is complete.

Parameters :
Name Type Optional
orderId string No
data literal type No
Returns : unknown
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';

@Injectable()
export class PodService {
  private readonly logger = new Logger(PodService.name);

  constructor(private prisma: PrismaService) {}

  /**
   * Submit proof of delivery for an order.
   * Called by driver (via API or future mobile app) when delivery is complete.
   */
  async submitPod(
    orderId: string,
    data: {
      recipientName: string;
      recipientSignature?: string; // base64 signature image
      photos?: string[]; // base64 photo images
      notes?: string;
      deliveredAt?: Date;
      latitude?: number;
      longitude?: number;
    },
  ) {
    const order = await this.prisma.order.findUnique({
      where: { id: orderId },
    });
    if (!order) throw new NotFoundException('Order not found');

    // Build POD metadata
    const podData = {
      recipientName: data.recipientName,
      recipientSignature: data.recipientSignature,
      photos: data.photos || [],
      notes: data.notes,
      deliveredAt: data.deliveredAt || new Date(),
      gpsLat: data.latitude,
      gpsLng: data.longitude,
      submittedAt: new Date(),
    };

    // Create POD document (store POD payload as JSON in fileUrl since
    // the Document model requires it; a real deployment would upload
    // to S3 and store the URL instead)
    const doc = await this.prisma.document.create({
      data: {
        entityType: 'Order',
        entityId: orderId,
        fileName: `POD-${order.orderNumber}.json`,
        fileUrl: `pod://${orderId}/${Date.now()}`,
        mimeType: 'application/json',
        documentType: 'PROOF_OF_DELIVERY',
        fileSize: JSON.stringify(podData).length,
      },
    });

    // Update order status to DELIVERED
    await this.prisma.order.update({
      where: { id: orderId },
      data: {
        status: 'DELIVERED',
        actualDelivery: data.deliveredAt || new Date(),
      },
    });

    // Create order event
    await this.prisma.orderEvent.create({
      data: {
        orderId,
        fromStatus: order.status,
        toStatus: 'DELIVERED',
        reason: `POD submitted by ${data.recipientName}`,
        metadata: { documentId: doc.id, ...podData },
      },
    });

    // Auto-complete trip if all orders now have POD
    let tripCompleted = false;
    if (order.tripId) {
      try {
        const trip = await this.prisma.trip.findUnique({
          where: { id: order.tripId },
          include: { orders: true },
        });

        if (trip) {
          // Get all order IDs on this trip
          const tripOrderIds = trip.orders.map(o => o.id);

          // Check which ones have POD documents
          const podsSubmitted = await this.prisma.document.findMany({
            where: {
              entityType: 'Order',
              documentType: 'PROOF_OF_DELIVERY',
              entityId: { in: tripOrderIds },
            },
            select: { entityId: true },
          });
          const podOrderIds = new Set(podsSubmitted.map(d => d.entityId));

          // Include the current order (we just created its POD above)
          podOrderIds.add(orderId);

          const allOrdersHavePod = tripOrderIds.every(id => podOrderIds.has(id));

          // Mark POD submitted on the trip
          await this.prisma.trip.update({
            where: { id: order.tripId },
            data: { podSubmitted: true },
          });

          // If POD required and all orders have POD → auto-complete the trip
          if (trip.podRequired && allOrdersHavePod) {
            await this.prisma.trip.update({
              where: { id: order.tripId },
              data: { status: 'COMPLETED', endDate: new Date() },
            });

            // Free up the vehicle
            if (trip.vehicleId) {
              await this.prisma.vehicle.update({
                where: { id: trip.vehicleId },
                data: { status: 'AVAILABLE' },
              }).catch(() => {});
            }

            // Mark all remaining orders on trip as delivered
            await this.prisma.order.updateMany({
              where: { tripId: order.tripId, status: { not: 'DELIVERED' } },
              data: { status: 'DELIVERED', actualDelivery: new Date() },
            });

            tripCompleted = true;
            this.logger.log(`Trip ${trip.tripNumber} auto-completed — all PODs submitted`);
          }
        }
      } catch (err) {
        this.logger.warn(`Failed to check/complete trip: ${err}`);
      }
    }

    this.logger.log(`POD submitted for order ${order.orderNumber}`);
    return {
      document: doc,
      order: { id: orderId, status: 'DELIVERED' },
      tripCompleted,
    };
  }

  /**
   * Get POD for an order
   */
  async getPod(orderId: string) {
    const doc = await this.prisma.document.findFirst({
      where: {
        entityType: 'Order',
        entityId: orderId,
        documentType: 'PROOF_OF_DELIVERY',
      },
    });
    if (!doc) throw new NotFoundException('No POD found for this order');
    return doc;
  }

  /**
   * List all orders pending POD (in-transit but no POD document yet)
   */
  async getPendingPods(orgId: string) {
    const deliveredOrders = await this.prisma.order.findMany({
      where: { organizationId: orgId, status: 'IN_TRANSIT' },
      include: { client: { select: { name: true } } },
    });

    // Filter out orders that already have PODs
    const ordersWithPods = await this.prisma.document.findMany({
      where: {
        entityType: 'Order',
        documentType: 'PROOF_OF_DELIVERY',
        entityId: { in: deliveredOrders.map((o) => o.id) },
      },
      select: { entityId: true },
    });
    const podOrderIds = new Set(ordersWithPods.map((d) => d.entityId));

    return deliveredOrders.filter((o) => !podOrderIds.has(o.id));
  }
}

results matching ""

    No results matching ""