src/transporters/transporters.service.ts
Methods |
constructor(prisma: PrismaService)
|
||||||
|
Defined in src/transporters/transporters.service.ts:21
|
||||||
|
Parameters :
|
| Async bulkUploadFromFile | |||||||||
bulkUploadFromFile(organizationId: string, file: Express.Multer.File)
|
|||||||||
|
Defined in src/transporters/transporters.service.ts:128
|
|||||||||
|
Parameters :
Returns :
Promise<BulkUploadResult>
|
| Async create | ||||||||||||
create(organizationId: string, dto: CreateTransporterDto, userId?: string)
|
||||||||||||
|
Defined in src/transporters/transporters.service.ts:25
|
||||||||||||
|
Parameters :
Returns :
unknown
|
| Async findAll | |||||||||
findAll(organizationId: string, params: PaginationParams)
|
|||||||||
|
Defined in src/transporters/transporters.service.ts:46
|
|||||||||
|
Parameters :
Returns :
unknown
|
| Async findOne |
findOne(organizationId: string, id: string)
|
|
Defined in src/transporters/transporters.service.ts:72
|
|
Org-scoped lookup — cross-tenant ID guessing impossible.
Returns :
unknown
|
| Async remove |
remove(organizationId: string, id: string)
|
|
Defined in src/transporters/transporters.service.ts:109
|
|
Returns :
unknown
|
| Async update | |||||||||||||||
update(organizationId: string, id: string, dto: UpdateTransporterDto, userId?: string)
|
|||||||||||||||
|
Defined in src/transporters/transporters.service.ts:86
|
|||||||||||||||
|
Parameters :
Returns :
unknown
|
import {
BadRequestException,
ConflictException,
Injectable,
Logger,
NotFoundException,
} from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateTransporterDto } from './dto/create-transporter.dto';
import { UpdateTransporterDto } from './dto/update-transporter.dto';
import {
buildPaginationQuery,
buildPaginationMeta,
PaginationParams,
} from '../common/utils/pagination.util';
import { BulkUploadService, TRANSPORTER_MAPPINGS, BulkUploadResult } from '../common/services/bulk-upload.service';
@Injectable()
export class TransportersService {
private readonly logger = new Logger(TransportersService.name);
private readonly bulkUpload = new BulkUploadService();
constructor(private prisma: PrismaService) {}
async create(organizationId: string, dto: CreateTransporterDto, userId?: string) {
const existing = await this.prisma.transporter.findFirst({
where: { organizationId, code: dto.code },
});
if (existing) {
throw new ConflictException(`A carrier with code "${dto.code}" already exists in this organization`);
}
return this.prisma.transporter.create({
data: {
organizationId,
name: dto.name,
code: dto.code,
contactName: dto.contactName,
contactEmail: dto.contactEmail,
contactPhone: dto.contactPhone,
createdById: userId,
},
});
}
async findAll(organizationId: string, params: PaginationParams) {
const { skip, take, orderBy, page, limit } = buildPaginationQuery(params);
const where: any = { organizationId };
if (params.search) {
where.OR = [
{ name: { contains: params.search, mode: 'insensitive' } },
{ code: { contains: params.search, mode: 'insensitive' } },
];
}
const [items, total] = await Promise.all([
this.prisma.transporter.findMany({
where,
skip,
take,
orderBy,
include: { _count: { select: { vehicles: true } } },
}),
this.prisma.transporter.count({ where }),
]);
return { data: items, meta: buildPaginationMeta(total, page, limit) };
}
/** Org-scoped lookup — cross-tenant ID guessing impossible. */
async findOne(organizationId: string, id: string) {
const transporter = await this.prisma.transporter.findFirst({
where: { id, organizationId },
include: {
vehicles: {
select: { id: true, unitNumber: true, type: true, status: true },
},
_count: { select: { vehicles: true } },
},
});
if (!transporter) throw new NotFoundException('Carrier not found');
return transporter;
}
async update(
organizationId: string,
id: string,
dto: UpdateTransporterDto,
userId?: string,
) {
await this.findOne(organizationId, id);
if (dto.code) {
const existing = await this.prisma.transporter.findFirst({
where: { organizationId, code: dto.code, NOT: { id } },
});
if (existing) {
throw new ConflictException(`Another carrier with code "${dto.code}" already exists`);
}
}
return this.prisma.transporter.update({
where: { id },
data: { ...dto, updatedById: userId },
});
}
async remove(organizationId: string, id: string) {
const transporter = await this.findOne(organizationId, id);
// ABSOLUTELY block deletion if any vehicles are still parented to
// this carrier. The previous behaviour cascade-deleted vehicles AND
// their entire trip history — catastrophic.
const vehicleCount = transporter._count?.vehicles ?? 0;
if (vehicleCount > 0) {
throw new BadRequestException(
`Carrier owns ${vehicleCount} vehicle${
vehicleCount === 1 ? '' : 's'
}. Reassign or remove the vehicles first.`,
);
}
await this.prisma.transporter.delete({ where: { id } });
return { deleted: true };
}
async bulkUploadFromFile(organizationId: string, file: Express.Multer.File): Promise<BulkUploadResult> {
const { rows, errors } = this.bulkUpload.parseFile(file.buffer, file.originalname, TRANSPORTER_MAPPINGS);
const created: Record<string, unknown>[] = [];
let skipped = 0;
for (const row of rows) {
const code = String(row.code || '').trim().toUpperCase();
if (!code || !/^[A-Z0-9_-]{2,20}$/.test(code)) {
skipped++;
continue;
}
const existing = await this.prisma.transporter.findFirst({
where: { organizationId, code },
});
if (existing) {
skipped++;
continue;
}
const transporter = await this.prisma.transporter.create({
data: {
organizationId,
name: String(row.name || '').trim(),
code,
contactName: (row.contactName as string) || undefined,
contactEmail: (row.contactEmail as string) || undefined,
contactPhone: (row.contactPhone as string) || undefined,
isActive: true,
},
});
created.push(transporter);
}
this.logger.log(`Bulk transporter upload: ${created.length} created, ${skipped} skipped, ${errors.length} errors`);
return { created: created.length, skipped, errors, records: created, totalRows: rows.length };
}
}