Files
edr-platform/apps/edr-passenger-api/src/modules/agents/agents.service.ts

214 lines
7.4 KiB
TypeScript

import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';
import { PrismaService } from '../../common/prisma.service';
import { CreateAgentBookingDto, OpenShiftDto, CloseShiftDto } from './agents.dto';
import { IdDocumentType } from '@prisma/client';
function generateRef(): string {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
return Array.from({ length: 6 }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
}
@Injectable()
export class AgentsService {
constructor(
private prisma: PrismaService,
@InjectDataSource() private readonly dataSource: DataSource,
) {}
async findAll(filters: { search?: string; active?: string }) {
const where: any = {};
if (filters.active !== undefined && filters.active !== '') {
where.active = filters.active === 'true';
}
const agents = await this.prisma.agent.findMany({
where,
orderBy: { createdAt: 'desc' },
});
// Enrich with IAM user data
const iamUserIds = agents.map(a => a.iamUserId).filter(Boolean) as string[];
type IamRow = { id: string; email: string; name: any; phone_number: string | null };
const iamRows: IamRow[] = iamUserIds.length > 0
? await this.dataSource.query<IamRow[]>(
`SELECT id, email, name, phone_number FROM iam.users WHERE id = ANY($1)`,
[iamUserIds],
).catch(() => [])
: [];
const iamMap = new Map(iamRows.map(r => [r.id, r]));
const items = agents
.map(a => {
const iam = a.iamUserId ? iamMap.get(a.iamUserId) ?? null : null;
const fullName = iam?.name?.en ?? iam?.name?.am ?? null;
if (filters.search) {
const q = filters.search.toLowerCase();
const matches = a.agentCode.toLowerCase().includes(q)
|| (iam?.email ?? '').toLowerCase().includes(q)
|| (fullName ?? '').toLowerCase().includes(q);
if (!matches) return null;
}
return {
...a,
user: iam ? { fullName, email: iam.email, phone: iam.phone_number } : null,
};
})
.filter(Boolean);
return { items, total: items.length };
}
async createAgentBooking(dto: CreateAgentBookingDto) {
const agent = await this.prisma.agent.findUnique({ where: { id: dto.agentId } });
if (!agent || !agent.active) throw new NotFoundException('Agent not found or inactive');
const passenger = agent.iamUserId
? await this.prisma.passenger.findUnique({ where: { iamUserId: agent.iamUserId } })
: null;
if (!passenger) throw new BadRequestException('Agent must have a linked passenger account');
const schedule = await this.prisma.trainSchedule.findUnique({ where: { id: dto.scheduleId } });
if (!schedule) throw new NotFoundException('Schedule not found');
const seatIds = dto.passengers.map(p => p.seatId);
const seats = await this.prisma.seat.findMany({ where: { id: { in: seatIds } } });
if (seats.length !== seatIds.length) throw new BadRequestException('Invalid seat selection');
const baseFare = 45000 * dto.passengers.length;
const totalMinor = baseFare;
const booking = await this.prisma.booking.create({
data: {
bookingRef: generateRef(),
passengerId: passenger.id,
scheduleId: dto.scheduleId,
status: dto.paymentMethod === 'CASH' ? 'CONFIRMED' : 'PENDING_PAYMENT',
totalMinor,
seats: {
create: dto.passengers.map(p => ({
seat: { connect: { id: p.seatId } },
passengerName: p.fullName,
idDocumentType: p.idDocumentType as IdDocumentType | undefined,
idDocumentNumber: p.idDocumentNumber
}))
}
},
include: { seats: true }
});
await this.prisma.seat.updateMany({
where: { id: { in: seatIds } },
data: { status: 'BOOKED' }
});
const changeGiven = dto.cashReceived ? dto.cashReceived - totalMinor : 0;
await this.prisma.agentBooking.create({
data: {
agentId: dto.agentId,
bookingId: booking.id,
paymentMethod: dto.paymentMethod,
cashReceived: dto.cashReceived,
changeGiven,
paperTicket: dto.paperTicket ?? false
}
});
const commissionAmount = Math.floor(totalMinor * agent.commissionRate / 100);
await this.prisma.agentCommission.create({
data: {
agentId: dto.agentId,
bookingId: booking.id,
amountMinor: commissionAmount,
rate: agent.commissionRate
}
});
return { booking, commission: commissionAmount };
}
async openShift(dto: OpenShiftDto) {
const agent = await this.prisma.agent.findUnique({ where: { id: dto.agentId } });
if (!agent) throw new NotFoundException('Agent not found');
const openShift = await this.prisma.agentShift.findFirst({
where: { agentId: dto.agentId, closedAt: null }
});
if (openShift) throw new BadRequestException('Shift already open');
return this.prisma.agentShift.create({
data: {
agentId: dto.agentId,
openingBalance: dto.openingBalance ?? 0
}
});
}
async closeShift(dto: CloseShiftDto) {
const shift = await this.prisma.agentShift.findUnique({ where: { id: dto.shiftId } });
if (!shift) throw new NotFoundException('Shift not found');
if (shift.closedAt) throw new BadRequestException('Shift already closed');
return this.prisma.agentShift.update({
where: { id: dto.shiftId },
data: {
closedAt: new Date(),
closingBalance: dto.closingBalance,
notes: dto.notes,
reconciled: true
}
});
}
async getCommissions(agentId: string, dateFrom?: Date, dateTo?: Date) {
return this.prisma.agentCommission.findMany({
where: {
agentId,
createdAt: {
gte: dateFrom,
lte: dateTo
}
},
orderBy: { createdAt: 'desc' }
});
}
async getShifts(agentId: string) {
return this.prisma.agentShift.findMany({
where: { agentId },
orderBy: { openedAt: 'desc' },
take: 20
});
}
async getMe(iamUserId: string) {
const agent = await this.prisma.agent.findUnique({ where: { iamUserId } });
if (!agent) throw new NotFoundException('No agent profile found for this user');
return agent;
}
async createAgent(dto: { iamUserId: string; agentCode?: string; commissionRate?: number }) {
const existing = await this.prisma.agent.findUnique({ where: { iamUserId: dto.iamUserId } });
if (existing) throw new BadRequestException('An agent profile already exists for this user');
const agentCode = dto.agentCode || `AG${String(Date.now()).slice(-4)}`;
return this.prisma.agent.create({
data: {
iamUserId: dto.iamUserId,
agentCode,
commissionRate: dto.commissionRate ?? 5,
active: true,
},
});
}
async updateAgent(id: string, dto: { agentCode?: string; commissionRate?: number; active?: boolean }) {
const agent = await this.prisma.agent.findUnique({ where: { id } });
if (!agent) throw new NotFoundException('Agent not found');
return this.prisma.agent.update({
where: { id },
data: {
...(dto.agentCode !== undefined && { agentCode: dto.agentCode }),
...(dto.commissionRate !== undefined && { commissionRate: dto.commissionRate }),
...(dto.active !== undefined && { active: dto.active }),
},
});
}
}