src/orders/pod/pod.service.ts
Methods |
|
constructor(prisma: PrismaService)
|
||||||
|
Defined in src/orders/pod/pod.service.ts:6
|
||||||
|
Parameters :
|
| Async getPendingPods | ||||||
getPendingPods(orgId: string)
|
||||||
|
Defined in src/orders/pod/pod.service.ts:169
|
||||||
|
List all orders pending POD (in-transit but no POD document yet)
Parameters :
Returns :
unknown
|
| Async getPod | ||||||
getPod(orderId: string)
|
||||||
|
Defined in src/orders/pod/pod.service.ts:154
|
||||||
|
Get POD for an order
Parameters :
Returns :
unknown
|
| Async submitPod | |||||||||
submitPod(orderId: string, data: literal type)
|
|||||||||
|
Defined in src/orders/pod/pod.service.ts:14
|
|||||||||
|
Submit proof of delivery for an order. Called by driver (via API or future mobile app) when delivery is complete.
Parameters :
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));
}
}