import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common'; 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) {} async createAgentBooking(dto: CreateAgentBookingDto) { const agent = await this.prisma.agent.findUnique({ where: { id: dto.agentId }, include: { user: { include: { passenger: true } } } }); if (!agent || !agent.active) throw new NotFoundException('Agent not found or inactive'); if (!agent.user.passenger) throw new BadRequestException('Agent must have passenger account'); const trip = await this.prisma.trip.findUnique({ where: { id: dto.tripId } }); if (!trip) throw new NotFoundException('Trip 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: agent.user.passenger.id, tripId: dto.tripId, 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 }); } }