This commit is contained in:
pradeepkumar
2026-03-31 22:39:10 +05:30
parent 475bc9e251
commit e5e1888615
3 changed files with 176 additions and 1 deletions

View File

@@ -1,6 +1,7 @@
import {
Controller,
Get,
Post,
Put,
Delete,
Param,
@@ -9,6 +10,8 @@ import {
Query,
UseGuards,
NotFoundException,
HttpCode,
HttpStatus,
} from '@nestjs/common';
import {
ApiTags,
@@ -190,6 +193,19 @@ export class UsersController {
return this.usersService.deleteAccount(user.id);
}
// ==========================================
// AGENT VERIFICATION SUBMISSION
// ==========================================
@Post('me/verification/submit')
@Roles(UserRole.AGENT)
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Agent: Submit or resubmit profile for verification' })
@ApiResponse({ status: 200, description: 'Verification submitted' })
async submitForVerification(@CurrentUser('id') userId: string) {
return this.usersService.submitForVerification(userId);
}
// =============================================
// ADMIN ENDPOINTS
// =============================================

View File

@@ -425,13 +425,15 @@ export class UsersService {
},
});
// Save verification history
// Save verification history with profile snapshot
const profileSnapshot = await this.captureProfileSnapshot(user.agentProfile.id);
await this.prisma.verificationHistory.create({
data: {
agentProfileId: user.agentProfile.id,
status: data.status,
note: data.note || null,
adminId,
submittedData: profileSnapshot ?? undefined,
},
});
@@ -466,6 +468,162 @@ export class UsersService {
});
}
// Submit or resubmit for verification (agent-facing)
async submitForVerification(userId: string) {
const user = await this.prisma.user.findUnique({
where: { id: userId },
include: { agentProfile: true },
});
if (!user) throw new NotFoundException('User not found');
if (user.role !== UserRole.AGENT) throw new BadRequestException('User is not an agent');
if (!user.agentProfile) throw new NotFoundException('Agent profile not found');
const currentStatus = user.agentProfile.verificationStatus;
if (currentStatus === VerificationStatus.PENDING_REVIEW) {
throw new BadRequestException('Verification is already pending review');
}
if (currentStatus === VerificationStatus.APPROVED) {
throw new BadRequestException('Profile is already verified');
}
// Validate required fields are filled
const missingFields = await this.getRequiredFieldsMissing(user.agentProfile.id, user.agentProfile.agentTypeId);
if (missingFields.length > 0) {
throw new BadRequestException({
message: 'Please fill in all required fields before submitting',
missingFields,
});
}
// Capture snapshot of current profile data + documents
const profileSnapshot = await this.captureProfileSnapshot(user.agentProfile.id);
// Update status to PENDING_REVIEW
await this.prisma.agentProfile.update({
where: { id: user.agentProfile.id },
data: {
verificationStatus: VerificationStatus.PENDING_REVIEW,
verificationNote: null,
},
});
// Create history entry with submitted data snapshot
await this.prisma.verificationHistory.create({
data: {
agentProfileId: user.agentProfile.id,
status: VerificationStatus.PENDING_REVIEW,
note: currentStatus === VerificationStatus.REJECTED ? 'Resubmitted after rejection' : 'Initial submission',
submittedData: profileSnapshot ?? undefined,
},
});
return { message: 'Verification submitted successfully', status: 'PENDING_REVIEW' };
}
// Capture a snapshot of agent's profile data and documents for history
private async captureProfileSnapshot(agentProfileId: string) {
const profile = await this.prisma.agentProfile.findUnique({
where: { id: agentProfileId },
include: {
user: { select: { email: true } },
agentType: { select: { id: true, name: true } },
fieldValues: {
include: {
field: {
select: { slug: true, name: true, fieldType: true, section: { select: { slug: true, name: true } } },
},
},
},
},
});
if (!profile) return null;
// Organize field values by section
const sections: Record<string, Record<string, any>> = {};
for (const fv of profile.fieldValues) {
const sectionSlug = fv.field.section.slug;
if (!sections[sectionSlug]) {
sections[sectionSlug] = { _name: fv.field.section.name };
}
const value = fv.textValue ?? fv.numberValue ?? fv.booleanValue ?? fv.jsonValue ?? fv.dateValue;
sections[sectionSlug][fv.field.slug] = {
name: fv.field.name,
type: fv.field.fieldType,
value,
};
}
return {
firstName: profile.firstName,
lastName: profile.lastName,
email: profile.user?.email,
phone: profile.phone,
avatar: profile.avatar,
bio: profile.bio,
agentType: profile.agentType?.name,
sections,
capturedAt: new Date().toISOString(),
};
}
// Check which required fields are missing for an agent
private async getRequiredFieldsMissing(agentProfileId: string, agentTypeId: string | null) {
// Get all sections for this agent type (or global sections)
const sectionFilter: any = { isActive: true };
if (agentTypeId) {
sectionFilter.OR = [
{ isGlobal: true },
{ agentTypeSections: { some: { agentTypeId } } },
];
} else {
sectionFilter.isGlobal = true;
}
// Get all required fields from applicable sections
const requiredFields = await this.prisma.profileField.findMany({
where: {
isRequired: true,
isActive: true,
section: sectionFilter,
},
select: { id: true, name: true, slug: true, section: { select: { name: true, slug: true } } },
});
if (requiredFields.length === 0) return [];
// Get agent's filled field values
const filledValues = await this.prisma.agentProfileFieldValue.findMany({
where: {
agentProfileId,
fieldId: { in: requiredFields.map((f) => f.id) },
},
select: { fieldId: true, textValue: true, numberValue: true, booleanValue: true, jsonValue: true, dateValue: true },
});
const filledMap = new Map(filledValues.map((fv) => [fv.fieldId, fv]));
// Find missing or empty required fields
const missing: { fieldName: string; sectionName: string }[] = [];
for (const field of requiredFields) {
const value = filledMap.get(field.id);
if (!value) {
missing.push({ fieldName: field.name, sectionName: field.section.name });
continue;
}
// Check if value is actually empty
const hasValue = value.textValue || value.numberValue !== null || value.booleanValue !== null ||
(value.jsonValue && (Array.isArray(value.jsonValue) ? (value.jsonValue as any[]).length > 0 : true)) ||
value.dateValue;
if (!hasValue) {
missing.push({ fieldName: field.name, sectionName: field.section.name });
}
}
return missing;
}
// =============================================
// NOTIFICATION PREFERENCES
// =============================================