feat(api): add inquiries, leads, and agents modules for Agent Portal
Build three new DDD modules following existing CQRS patterns: - Inquiries: CRUD endpoints for buyer consultation requests with agent notification support - Leads: Full lead lifecycle management with status state machine and conversion tracking - Agents: Quality score calculation (event-driven on review changes) and dashboard stats API All modules include unit tests (14 test files, all 797 tests pass). Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
export class UpdateLeadStatusCommand {
|
||||
constructor(
|
||||
public readonly leadId: string,
|
||||
public readonly agentUserId: string,
|
||||
public readonly newStatus: string,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Inject, Logger } from '@nestjs/common';
|
||||
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
||||
import { ForbiddenException, NotFoundException, type PrismaService } from '@modules/shared';
|
||||
import { type LeadStatus } from '../../../domain/entities/lead.entity';
|
||||
import { LEAD_REPOSITORY, type ILeadRepository } from '../../../domain/repositories/lead.repository';
|
||||
import { UpdateLeadStatusCommand } from './update-lead-status.command';
|
||||
|
||||
@CommandHandler(UpdateLeadStatusCommand)
|
||||
export class UpdateLeadStatusHandler implements ICommandHandler<UpdateLeadStatusCommand> {
|
||||
private readonly logger = new Logger(UpdateLeadStatusHandler.name);
|
||||
|
||||
constructor(
|
||||
@Inject(LEAD_REPOSITORY) private readonly leadRepo: ILeadRepository,
|
||||
private readonly eventBus: EventBus,
|
||||
private readonly prisma: PrismaService,
|
||||
) {}
|
||||
|
||||
async execute(command: UpdateLeadStatusCommand): Promise<void> {
|
||||
// Look up agent by userId
|
||||
const agent = await this.prisma.agent.findUnique({
|
||||
where: { userId: command.agentUserId },
|
||||
});
|
||||
if (!agent) {
|
||||
throw new NotFoundException('Agent', command.agentUserId);
|
||||
}
|
||||
|
||||
const lead = await this.leadRepo.findById(command.leadId);
|
||||
if (!lead) {
|
||||
throw new NotFoundException('Lead', command.leadId);
|
||||
}
|
||||
|
||||
// Verify agent ownership
|
||||
if (lead.agentId !== agent.id) {
|
||||
throw new ForbiddenException('Bạn chỉ có thể cập nhật lead của chính mình');
|
||||
}
|
||||
|
||||
lead.updateStatus(command.newStatus as LeadStatus);
|
||||
await this.leadRepo.update(lead);
|
||||
|
||||
// Publish domain events
|
||||
const events = lead.clearDomainEvents();
|
||||
for (const event of events) {
|
||||
this.eventBus.publish(event);
|
||||
}
|
||||
|
||||
this.logger.log(`Lead ${command.leadId} status updated to ${command.newStatus} by agent ${agent.id}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user