mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 17:45:42 +00:00
165 lines
6.5 KiB
TypeScript
165 lines
6.5 KiB
TypeScript
import { Injectable, NotFoundException } from '@nestjs/common';
|
|
import { DataSource } from 'typeorm';
|
|
|
|
import { FilesService } from '../files/files.service';
|
|
import { LastMileService } from '../last-mile/last-mile.service';
|
|
import { CreateInspectionReportDto } from './dto/create-inspection-report.dto';
|
|
import { UpdateInspectionReportDto } from './dto/update-inspection-report.dto';
|
|
import { WarehouseInspectionReport } from './entities/warehouse-inspection-report.entity';
|
|
import { WarehouseInventory } from './entities/warehouse-inventory.entity';
|
|
import { WarehouseInspectionRepository } from './warehouse-inspection.repository';
|
|
|
|
const INSPECTION_RESOURCE = 'warehouse-inspection-report';
|
|
|
|
@Injectable()
|
|
export class WarehouseInspectionService {
|
|
constructor(
|
|
private readonly dataSource: DataSource,
|
|
private readonly inspectionRepository: WarehouseInspectionRepository,
|
|
private readonly filesService: FilesService,
|
|
private readonly lastMileService: LastMileService,
|
|
) {}
|
|
|
|
/** Create or update the inspection report for an inventory item and sync its inspectionStatus. */
|
|
async create(inventoryId: string, dto: CreateInspectionReportDto): Promise<WarehouseInspectionReport> {
|
|
const inventoryRepo = this.dataSource.getRepository(WarehouseInventory);
|
|
const inventory = await inventoryRepo.findOne({ where: { id: inventoryId } });
|
|
if (!inventory) {
|
|
throw new NotFoundException(`Inventory item ${inventoryId} not found`);
|
|
}
|
|
|
|
const expected = dto.expectedWeight ?? null;
|
|
const actual = dto.actualWeight ?? null;
|
|
const weightLoss = expected !== null && actual !== null ? Math.max(0, expected - actual) : null;
|
|
const inspectedAt = new Date();
|
|
|
|
const payload = {
|
|
inventoryId,
|
|
bookingId: inventory.bookingId ?? null,
|
|
reportType: dto.reportType,
|
|
inspectionStatus: dto.inspectionStatus,
|
|
hasDamage: dto.hasDamage ?? false,
|
|
damageDescription: dto.damageDescription ?? null,
|
|
hasWeightLoss: dto.hasWeightLoss ?? false,
|
|
expectedWeight: expected,
|
|
actualWeight: actual,
|
|
weightLoss,
|
|
weightLossUnit: weightLoss !== null ? 'kg' : null,
|
|
hasMissingItems: dto.hasMissingItems ?? false,
|
|
missingItemsDescription: dto.missingItemsDescription ?? null,
|
|
remarks: dto.remarks ?? null,
|
|
inspectedById: dto.inspectedById ?? null,
|
|
inspectedAt,
|
|
};
|
|
|
|
const [existingReport] = await this.inspectionRepository.findAll({
|
|
where: { inventoryId },
|
|
order: { createdAt: 'DESC' },
|
|
take: 1,
|
|
});
|
|
|
|
let report: WarehouseInspectionReport;
|
|
if (existingReport) {
|
|
await this.inspectionRepository.update(existingReport.id, payload);
|
|
report = await this.findById(existingReport.id);
|
|
} else {
|
|
report = await this.inspectionRepository.create(payload);
|
|
}
|
|
|
|
// Mirror the latest outcome onto the inventory item so loading rules can read it.
|
|
await inventoryRepo.update(inventoryId, {
|
|
inspectionStatus: dto.inspectionStatus,
|
|
inspectedAt,
|
|
});
|
|
|
|
if (dto.inspectionStatus === 'PASSED') {
|
|
await this.markImportPickupReadyAndAcceptLastMile(inventoryId);
|
|
}
|
|
|
|
return report;
|
|
}
|
|
|
|
private async markImportPickupReadyAndAcceptLastMile(inventoryId: string): Promise<void> {
|
|
const [row] = await this.dataSource.query(
|
|
`SELECT inv.booking_id AS "bookingId",
|
|
b.reference AS "bookingReference",
|
|
b.trade_direction AS "tradeDirection",
|
|
b.last_mile_delivery_address AS "lastMileDeliveryAddress"
|
|
FROM freight.warehouse_inventory inv
|
|
LEFT JOIN freight.bookings b ON b.id = inv.booking_id
|
|
WHERE inv.id = $1 AND inv.deleted_at IS NULL
|
|
LIMIT 1`,
|
|
[inventoryId],
|
|
);
|
|
if ((row?.tradeDirection ?? '').toUpperCase() !== 'IMPORT') return;
|
|
|
|
await this.dataSource.getRepository(WarehouseInventory).update(inventoryId, {
|
|
status: 'READY_FOR_PICKUP',
|
|
readyForPickupAt: new Date(),
|
|
});
|
|
|
|
if (row.bookingReference && row.lastMileDeliveryAddress) {
|
|
await this.lastMileService.acceptBooking(row.bookingReference);
|
|
}
|
|
}
|
|
|
|
async findByInventory(inventoryId: string): Promise<WarehouseInspectionReport[]> {
|
|
return this.inspectionRepository.findAll({
|
|
where: { inventoryId },
|
|
order: { createdAt: 'DESC' },
|
|
});
|
|
}
|
|
|
|
async findById(id: string): Promise<WarehouseInspectionReport> {
|
|
const report = await this.inspectionRepository.findById(id);
|
|
if (!report) {
|
|
throw new NotFoundException(`Inspection report ${id} not found`);
|
|
}
|
|
return report;
|
|
}
|
|
|
|
async update(id: string, dto: UpdateInspectionReportDto): Promise<WarehouseInspectionReport> {
|
|
const report = await this.findById(id);
|
|
|
|
const expected = dto.expectedWeight ?? report.expectedWeight ?? null;
|
|
const actual = dto.actualWeight ?? report.actualWeight ?? null;
|
|
const weightLoss = expected !== null && actual !== null ? Math.max(0, expected - actual) : report.weightLoss ?? null;
|
|
|
|
await this.inspectionRepository.update(id, {
|
|
...(dto.reportType ? { reportType: dto.reportType } : {}),
|
|
...(dto.inspectionStatus ? { inspectionStatus: dto.inspectionStatus } : {}),
|
|
...(dto.hasDamage !== undefined ? { hasDamage: dto.hasDamage } : {}),
|
|
...(dto.damageDescription !== undefined ? { damageDescription: dto.damageDescription } : {}),
|
|
...(dto.hasWeightLoss !== undefined ? { hasWeightLoss: dto.hasWeightLoss } : {}),
|
|
expectedWeight: expected,
|
|
actualWeight: actual,
|
|
weightLoss,
|
|
...(dto.hasMissingItems !== undefined ? { hasMissingItems: dto.hasMissingItems } : {}),
|
|
...(dto.missingItemsDescription !== undefined ? { missingItemsDescription: dto.missingItemsDescription } : {}),
|
|
...(dto.remarks !== undefined ? { remarks: dto.remarks } : {}),
|
|
});
|
|
|
|
if (dto.inspectionStatus) {
|
|
await this.dataSource
|
|
.getRepository(WarehouseInventory)
|
|
.update(report.inventoryId, { inspectionStatus: dto.inspectionStatus });
|
|
if (dto.inspectionStatus === 'PASSED') {
|
|
await this.markImportPickupReadyAndAcceptLastMile(report.inventoryId);
|
|
}
|
|
}
|
|
|
|
return this.findById(id);
|
|
}
|
|
|
|
/** Attach uploaded images/documents to a report, reusing the shared Files (MinIO) module. */
|
|
async addAttachments(reportId: string, files: Express.Multer.File[]) {
|
|
await this.findById(reportId);
|
|
if (!files?.length) return [];
|
|
return this.filesService.uploadMany(reportId, INSPECTION_RESOURCE, files);
|
|
}
|
|
|
|
listAttachments(reportId: string) {
|
|
return this.filesService.findByResource(reportId, INSPECTION_RESOURCE);
|
|
}
|
|
}
|