forked from Yara724/api
Merge pull request 'Added in-person expert' (#5) from s.yahyaee/yara724-api:main into main
Reviewed-on: Yara724/api#5
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
export enum RoleEnum {
|
||||
EXPERT = "expert",
|
||||
DAMAGE_EXPERT = "damage_expert",
|
||||
FIELD_EXPERT = "field_expert",
|
||||
COMPANY = "company",
|
||||
ADMIN = "admin",
|
||||
USER = "user",
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
} from "src/auth/dto/actor/forget-password.actor.dto";
|
||||
import { LoginActorDto } from "src/auth/dto/actor/login.actor.dto";
|
||||
import { ActorEditUserProfileDto } from "src/auth/dto/actor/profile.actor.dto";
|
||||
import { CreateFieldExpertDto } from "src/auth/dto/actor/create-field-expert.actor.dto";
|
||||
import {
|
||||
GenuineRegisterDto,
|
||||
InsurerRegisterDto,
|
||||
@@ -55,6 +56,14 @@ export class ActorAuthController {
|
||||
return await this.actorAuthService.insurerRegister(body);
|
||||
}
|
||||
|
||||
/** Mock: create a field expert for testing. Make private later. */
|
||||
@Post("create-field-expert")
|
||||
@ApiBody({ type: CreateFieldExpertDto })
|
||||
@ApiAcceptedResponse()
|
||||
async createFieldExpert(@Body() body: CreateFieldExpertDto) {
|
||||
return await this.actorAuthService.createFieldExpertMock(body);
|
||||
}
|
||||
|
||||
@UseGuards(LocalActorAuthGuard)
|
||||
@Post("login")
|
||||
@Roles()
|
||||
|
||||
@@ -24,6 +24,7 @@ import { UserType } from "src/Types&Enums/userType.enum";
|
||||
import { InsurerExpertDbService } from "src/users/entities/db-service/insurer-expert.db.service";
|
||||
import { DamageExpertDbService } from "src/users/entities/db-service/damage-expert.db.service";
|
||||
import { ExpertDbService } from "src/users/entities/db-service/expert.db.service";
|
||||
import { FieldExpertDbService } from "src/users/entities/db-service/field-expert.db.service";
|
||||
import { HashService } from "src/utils/hash/hash.service";
|
||||
import { MailService } from "src/utils/mail/mail.service";
|
||||
import { OtpService } from "src/utils/otp/otp.service";
|
||||
@@ -44,6 +45,7 @@ export class ActorAuthService {
|
||||
private readonly hashService: HashService,
|
||||
private readonly expertDbService: ExpertDbService,
|
||||
private readonly damageExpertDbService: DamageExpertDbService,
|
||||
private readonly fieldExpertDbService: FieldExpertDbService,
|
||||
private readonly insurerExpertDbService: InsurerExpertDbService,
|
||||
private readonly mailService: MailService,
|
||||
private readonly clientDbService: ClientDbService,
|
||||
@@ -69,6 +71,14 @@ export class ActorAuthService {
|
||||
else
|
||||
res = await this.damageExpertDbService.findOne({ email: username });
|
||||
break;
|
||||
case RoleEnum.FIELD_EXPERT:
|
||||
if (username == null && userId)
|
||||
res = await this.fieldExpertDbService.findOne({
|
||||
_id: new Types.ObjectId(userId),
|
||||
});
|
||||
else
|
||||
res = await this.fieldExpertDbService.findOne({ email: username });
|
||||
break;
|
||||
case RoleEnum.COMPANY:
|
||||
res = await this.insurerExpertDbService.findOne({ email: username });
|
||||
break;
|
||||
@@ -259,6 +269,11 @@ export class ActorAuthService {
|
||||
dbServiceToUpdate = this.insurerExpertDbService as any;
|
||||
}
|
||||
|
||||
if (!actor) {
|
||||
actor = await this.fieldExpertDbService.findOne(filter);
|
||||
dbServiceToUpdate = this.fieldExpertDbService as any;
|
||||
}
|
||||
|
||||
if (!actor) {
|
||||
throw new NotFoundException("actor not found");
|
||||
}
|
||||
@@ -278,18 +293,29 @@ export class ActorAuthService {
|
||||
}
|
||||
|
||||
async forgetPasswordVerify(email, otp, newPassword) {
|
||||
const userExist = await this.expertDbService.findOne({ email });
|
||||
let userExist: any = await this.expertDbService.findOne({ email });
|
||||
let dbServiceToUpdate: any = this.expertDbService;
|
||||
if (!userExist) {
|
||||
userExist = await this.damageExpertDbService.findOne({ email });
|
||||
dbServiceToUpdate = this.damageExpertDbService;
|
||||
}
|
||||
if (!userExist) {
|
||||
userExist = await this.insurerExpertDbService.findOne({ email });
|
||||
dbServiceToUpdate = this.insurerExpertDbService;
|
||||
}
|
||||
if (!userExist) {
|
||||
userExist = await this.fieldExpertDbService.findOne({ email });
|
||||
dbServiceToUpdate = this.fieldExpertDbService;
|
||||
}
|
||||
if (!userExist) throw new NotFoundException("user not found");
|
||||
const decodeOtp = await this.hashService.compare(otp, userExist.otp);
|
||||
if (!decodeOtp) throw new UnauthorizedException("otp invalid");
|
||||
if (decodeOtp) {
|
||||
const hashNewPassword = await this.hashService.hash(newPassword);
|
||||
await this.expertDbService.findOneAndUpdate(
|
||||
{ email },
|
||||
{ password: hashNewPassword },
|
||||
);
|
||||
return new ForgetPasswordVerifyCodeDtoRs(userExist, "update password");
|
||||
}
|
||||
const hashNewPassword = await this.hashService.hash(newPassword);
|
||||
await dbServiceToUpdate.findOneAndUpdate(
|
||||
{ email },
|
||||
{ password: hashNewPassword },
|
||||
);
|
||||
return new ForgetPasswordVerifyCodeDtoRs(userExist, "update password");
|
||||
}
|
||||
|
||||
async getProfiles(currentUser) {
|
||||
@@ -347,7 +373,14 @@ export class ActorAuthService {
|
||||
"city",
|
||||
"state",
|
||||
"address",
|
||||
]
|
||||
],
|
||||
field_expert: [
|
||||
"firstName",
|
||||
"lastName",
|
||||
"email",
|
||||
"phone",
|
||||
"mobile",
|
||||
],
|
||||
};
|
||||
|
||||
const allowedFields = allowedFieldsByRole[role];
|
||||
@@ -407,6 +440,13 @@ export class ActorAuthService {
|
||||
);
|
||||
break;
|
||||
|
||||
case "field_expert":
|
||||
await this.fieldExpertDbService.updateOne(
|
||||
{ _id: new Types.ObjectId(userId) },
|
||||
{ $set: sanitized },
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ForbiddenException("Unsupported role for update");
|
||||
}
|
||||
@@ -417,4 +457,43 @@ export class ActorAuthService {
|
||||
throw new InternalServerErrorException("Failed to update profile");
|
||||
}
|
||||
}
|
||||
|
||||
/** Mock endpoint: create a field expert for testing. Make private later. */
|
||||
async createFieldExpertMock(body: {
|
||||
email: string;
|
||||
password: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
mobile: string;
|
||||
phone?: string;
|
||||
}) {
|
||||
const hashPassword = await this.hashService.hash(body.password);
|
||||
const payload = {
|
||||
email: body.email.toLowerCase().trim(),
|
||||
password: hashPassword,
|
||||
firstName: body.firstName,
|
||||
lastName: body.lastName,
|
||||
mobile: body.mobile,
|
||||
phone: body.phone,
|
||||
role: RoleEnum.FIELD_EXPERT,
|
||||
};
|
||||
try {
|
||||
const created = await this.fieldExpertDbService.create(payload);
|
||||
return {
|
||||
id: created._id,
|
||||
email: created.email,
|
||||
firstName: created.firstName,
|
||||
lastName: created.lastName,
|
||||
mobile: created.mobile,
|
||||
role: created.role,
|
||||
};
|
||||
} catch (er) {
|
||||
if (er.code === 11000) {
|
||||
throw new BadRequestException(
|
||||
"A field expert with this email already exists.",
|
||||
);
|
||||
}
|
||||
throw er;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
30
src/auth/dto/actor/create-field-expert.actor.dto.ts
Normal file
30
src/auth/dto/actor/create-field-expert.actor.dto.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsEmail, IsOptional, IsString, MinLength } from "class-validator";
|
||||
|
||||
export class CreateFieldExpertDto {
|
||||
@ApiProperty({ example: "field.expert@example.com" })
|
||||
@IsEmail()
|
||||
email: string;
|
||||
|
||||
@ApiProperty({ example: "securePassword123", minLength: 6 })
|
||||
@IsString()
|
||||
@MinLength(6)
|
||||
password: string;
|
||||
|
||||
@ApiProperty({ example: "علی" })
|
||||
@IsString()
|
||||
firstName: string;
|
||||
|
||||
@ApiProperty({ example: "محمدی" })
|
||||
@IsString()
|
||||
lastName: string;
|
||||
|
||||
@ApiProperty({ example: "09121234567" })
|
||||
@IsString()
|
||||
mobile: string;
|
||||
|
||||
@ApiProperty({ example: "02188776655", required: false })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
phone?: string;
|
||||
}
|
||||
@@ -39,9 +39,12 @@ export class LocalActorAuthGuard extends AuthGuard("actor") {
|
||||
});
|
||||
|
||||
if (
|
||||
![RoleEnum.EXPERT, RoleEnum.DAMAGE_EXPERT, RoleEnum.COMPANY].includes(
|
||||
payload.role,
|
||||
)
|
||||
![
|
||||
RoleEnum.EXPERT,
|
||||
RoleEnum.DAMAGE_EXPERT,
|
||||
RoleEnum.COMPANY,
|
||||
RoleEnum.FIELD_EXPERT,
|
||||
].includes(payload.role)
|
||||
) {
|
||||
throw new UnauthorizedException("User role is not authorized");
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ import { JwtModule } from "@nestjs/jwt";
|
||||
],
|
||||
controllers: [ClaimRequestManagementController],
|
||||
exports: [
|
||||
ClaimRequestManagementService,
|
||||
ClaimRequestManagementDbService,
|
||||
DamageImageDbService,
|
||||
VideoCaptureDbService,
|
||||
|
||||
@@ -118,7 +118,12 @@ export class ClaimRequestManagementService {
|
||||
|
||||
// If actor is an expert, verify they're accessing an IN_PERSON claim file
|
||||
// and return the claim file's userId
|
||||
if (actor.role === RoleEnum.EXPERT || actor.role === RoleEnum.DAMAGE_EXPERT) {
|
||||
const expertRoles = [
|
||||
RoleEnum.EXPERT,
|
||||
RoleEnum.DAMAGE_EXPERT,
|
||||
RoleEnum.FIELD_EXPERT,
|
||||
];
|
||||
if (expertRoles.includes(actor.role)) {
|
||||
const claim = await this.claimDbService.findOne(claimRequestId);
|
||||
if (!claim) {
|
||||
throw new NotFoundException("Claim file not found");
|
||||
@@ -184,11 +189,16 @@ export class ClaimRequestManagementService {
|
||||
|
||||
// For IN_PERSON expert-initiated files, expert creates the claim
|
||||
// For LINK method or regular files, user creates their own claim
|
||||
const expertRoles = [
|
||||
RoleEnum.EXPERT,
|
||||
RoleEnum.DAMAGE_EXPERT,
|
||||
RoleEnum.FIELD_EXPERT,
|
||||
];
|
||||
if (
|
||||
blameRequest.expertInitiated &&
|
||||
blameRequest.creationMethod === "IN_PERSON" &&
|
||||
actor &&
|
||||
(actor.role === "EXPERT" || actor.role === "DAMAGE_EXPERT")
|
||||
expertRoles.includes(actor.role)
|
||||
) {
|
||||
// Expert is creating claim for IN_PERSON file
|
||||
// Verify expert is the initiating expert
|
||||
@@ -343,6 +353,69 @@ export class ClaimRequestManagementService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Field expert completes claim-needed data for expert-initiated IN_PERSON files.
|
||||
* Only the initiating expert can call this. Accepts car part damage and/or sheba, nationalCodeOfInsurer, otherParts.
|
||||
*/
|
||||
async expertCompleteClaimData(
|
||||
claimRequestId: string,
|
||||
expert: any,
|
||||
dto: {
|
||||
carPartDamage?: CarDamagePartDto;
|
||||
sheba?: string;
|
||||
nationalCodeOfInsurer?: string;
|
||||
otherParts?: any[];
|
||||
},
|
||||
): Promise<{ success: boolean; claimRequestId: string }> {
|
||||
const claim = await this.claimDbService.findOne(claimRequestId);
|
||||
if (!claim) {
|
||||
throw new NotFoundException("Claim file not found");
|
||||
}
|
||||
|
||||
const blameFile = claim.blameFile as any;
|
||||
if (!blameFile?.expertInitiated || blameFile?.creationMethod !== "IN_PERSON") {
|
||||
throw new BadRequestException(
|
||||
"This endpoint is only for expert-initiated IN_PERSON claim files.",
|
||||
);
|
||||
}
|
||||
if (String(blameFile.initiatedBy) !== expert.sub) {
|
||||
throw new ForbiddenException(
|
||||
"Only the initiating field expert can complete claim data for this file.",
|
||||
);
|
||||
}
|
||||
|
||||
if (dto.carPartDamage) {
|
||||
if (claim.carPartDamage && (claim.carPartDamage as any[]).length > 0) {
|
||||
throw new BadRequestException("Car part damage is already set.");
|
||||
}
|
||||
const trueItems = this.findTrueItems(dto.carPartDamage);
|
||||
const claimDoc = (await this.claimDbService.findOne(
|
||||
claimRequestId,
|
||||
)) as any;
|
||||
if (claimDoc) {
|
||||
await this.setCarPartDamageAndImage(claimDoc, trueItems);
|
||||
}
|
||||
await this.claimDbService.findAndUpdate(claimRequestId, {
|
||||
currentStep: ClaimStepsEnum.SelectDamagePart,
|
||||
nextStep: ClaimStepsEnum.SelectOtherParts,
|
||||
$push: { steps: ClaimStepsEnum.SelectDamagePart },
|
||||
});
|
||||
}
|
||||
|
||||
const updates: Record<string, any> = {};
|
||||
if (dto.sheba != null) updates.sheba = dto.sheba;
|
||||
if (dto.nationalCodeOfInsurer != null)
|
||||
updates.nationalCodeOfInsurer = dto.nationalCodeOfInsurer;
|
||||
if (dto.otherParts != null) updates.otherParts = dto.otherParts;
|
||||
if (Object.keys(updates).length > 0) {
|
||||
await this.claimDbService.findAndUpdate(claimRequestId, {
|
||||
$set: updates,
|
||||
});
|
||||
}
|
||||
|
||||
return { success: true, claimRequestId };
|
||||
}
|
||||
|
||||
async selectCarOtherPartDamage(
|
||||
requestId: string,
|
||||
body: any, // Use your DTO for better type safety
|
||||
@@ -1186,10 +1259,12 @@ export class ClaimRequestManagementService {
|
||||
}
|
||||
|
||||
// For experts, return IN_PERSON claim files they initiated
|
||||
if (
|
||||
currentUser.role === RoleEnum.EXPERT ||
|
||||
currentUser.role === RoleEnum.DAMAGE_EXPERT
|
||||
) {
|
||||
const expertRoles = [
|
||||
RoleEnum.EXPERT,
|
||||
RoleEnum.DAMAGE_EXPERT,
|
||||
RoleEnum.FIELD_EXPERT,
|
||||
];
|
||||
if (expertRoles.includes(currentUser.role)) {
|
||||
const allClaims = await this.claimDbService.findAllByAnyFilter({});
|
||||
const expertClaims = allClaims.filter((claim) => {
|
||||
const blameFile = claim.blameFile;
|
||||
|
||||
32
src/request-management/dto/expert-complete-claim-data.dto.ts
Normal file
32
src/request-management/dto/expert-complete-claim-data.dto.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { CarDamagePartDto } from "src/claim-request-management/dto/car-part.dto";
|
||||
|
||||
/**
|
||||
* DTO for field expert to submit claim-needed data in one go (IN_PERSON flow).
|
||||
* All fields optional so expert can submit in one or two steps (e.g. car parts first, then sheba/other).
|
||||
*/
|
||||
export class ExpertCompleteClaimDataDto {
|
||||
@ApiPropertyOptional({
|
||||
description: "Car part damage selection (same as selectCarPartDamage). Required for first claim data step.",
|
||||
type: CarDamagePartDto,
|
||||
})
|
||||
carPartDamage?: CarDamagePartDto;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: "Sheba number",
|
||||
example: "IR123456789012345678901234",
|
||||
})
|
||||
sheba?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: "National code of insurer",
|
||||
example: "1234567890",
|
||||
})
|
||||
nationalCodeOfInsurer?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: "Other parts / extra damage items",
|
||||
example: [{ "حسگر درها": true }],
|
||||
})
|
||||
otherParts?: any[];
|
||||
}
|
||||
@@ -34,17 +34,83 @@ import { CreateExpertInitiatedFileDto } from "./dto/expert-initiated.dto";
|
||||
import { ExpertCompleteThirdPartyFormDto } from "./dto/expert-complete-third-party-form.dto";
|
||||
import { ExpertCompleteCarBodyFormDto } from "./dto/expert-complete-car-body-form.dto";
|
||||
import { ExpertAccidentFieldsDto } from "./dto/expert-accident-fields.dto";
|
||||
import { ExpertCompleteClaimDataDto } from "./dto/expert-complete-claim-data.dto";
|
||||
import { ClaimRequestManagementService } from "src/claim-request-management/claim-request-management.service";
|
||||
|
||||
@ApiTags("expert-initiated-blame")
|
||||
@Controller("expert-initiated-blame")
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(LocalActorAuthGuard, RolesGuard)
|
||||
@Roles(RoleEnum.EXPERT, RoleEnum.DAMAGE_EXPERT)
|
||||
@Roles(RoleEnum.FIELD_EXPERT)
|
||||
export class ExpertInitiatedController {
|
||||
constructor(
|
||||
private readonly requestManagementService: RequestManagementService,
|
||||
private readonly claimRequestManagementService: ClaimRequestManagementService,
|
||||
) {}
|
||||
|
||||
@Get("my-files")
|
||||
@ApiOperation({
|
||||
summary: "List my expert-initiated files",
|
||||
description:
|
||||
"Returns all blame files created by the current field expert (their panel).",
|
||||
})
|
||||
@ApiResponse({ status: 200, description: "List of expert-initiated files" })
|
||||
async getMyFiles(@CurrentUser() expert: any) {
|
||||
return await this.requestManagementService.getMyExpertInitiatedFiles(
|
||||
expert,
|
||||
);
|
||||
}
|
||||
|
||||
@Post("complete-blame-data/:requestId")
|
||||
@ApiOperation({
|
||||
summary: "Submit all blame-needed data",
|
||||
description:
|
||||
"Single endpoint to complete blame form: send THIRD_PARTY or CAR_BODY form data according to file type. " +
|
||||
"Video and voice can be uploaded separately.",
|
||||
})
|
||||
@ApiParam({ name: "requestId", description: "ID of the expert-initiated file" })
|
||||
@ApiBody({
|
||||
description:
|
||||
"Send ExpertCompleteThirdPartyFormDto for THIRD_PARTY files or ExpertCompleteCarBodyFormDto for CAR_BODY files (according to file type).",
|
||||
})
|
||||
@ApiResponse({ status: 200, description: "Blame form completed successfully" })
|
||||
async completeBlameData(
|
||||
@CurrentUser() expert: any,
|
||||
@Param("requestId") requestId: string,
|
||||
@Body() formData: any,
|
||||
) {
|
||||
return await this.requestManagementService.completeBlameData(
|
||||
expert,
|
||||
requestId,
|
||||
formData,
|
||||
);
|
||||
}
|
||||
|
||||
@Post("complete-claim-data/:claimRequestId")
|
||||
@ApiOperation({
|
||||
summary: "Submit claim-needed data",
|
||||
description:
|
||||
"Field expert fills claim data for expert-initiated IN_PERSON files (car part damage, sheba, national code, other parts). " +
|
||||
"Only the initiating expert can call this.",
|
||||
})
|
||||
@ApiParam({
|
||||
name: "claimRequestId",
|
||||
description: "ID of the claim file (created from the expert-initiated blame file)",
|
||||
})
|
||||
@ApiBody({ type: ExpertCompleteClaimDataDto })
|
||||
@ApiResponse({ status: 200, description: "Claim data updated successfully" })
|
||||
async completeClaimData(
|
||||
@CurrentUser() expert: any,
|
||||
@Param("claimRequestId") claimRequestId: string,
|
||||
@Body() dto: ExpertCompleteClaimDataDto,
|
||||
) {
|
||||
return await this.claimRequestManagementService.expertCompleteClaimData(
|
||||
claimRequestId,
|
||||
expert,
|
||||
dto,
|
||||
);
|
||||
}
|
||||
|
||||
@Post("create")
|
||||
@ApiOperation({
|
||||
summary: "Create expert-initiated blame file",
|
||||
|
||||
@@ -2005,8 +2005,13 @@ export class RequestManagementService {
|
||||
request: RequestManagementModel,
|
||||
user: any,
|
||||
): Promise<void> {
|
||||
// If user is not an expert, no verification needed (normal flow)
|
||||
if (user.role !== RoleEnum.EXPERT && user.role !== RoleEnum.DAMAGE_EXPERT) {
|
||||
// If user is not an expert type, no verification needed (normal flow)
|
||||
const expertRoles = [
|
||||
RoleEnum.EXPERT,
|
||||
RoleEnum.DAMAGE_EXPERT,
|
||||
RoleEnum.FIELD_EXPERT,
|
||||
];
|
||||
if (!expertRoles.includes(user.role)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2155,6 +2160,60 @@ export class RequestManagementService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* List all expert-initiated blame files for the current field expert (their panel).
|
||||
* Only files where initiatedBy === current user are returned.
|
||||
*/
|
||||
async getMyExpertInitiatedFiles(expert: any): Promise<any[]> {
|
||||
const expertId = new Types.ObjectId(expert.sub);
|
||||
const files = await this.requestManagementDbService.findAll({
|
||||
expertInitiated: true,
|
||||
initiatedBy: expertId,
|
||||
});
|
||||
return (files || []).map((f) => ({
|
||||
_id: f._id,
|
||||
requestNumber: f.requestNumber,
|
||||
type: f.type,
|
||||
creationMethod: f.creationMethod,
|
||||
blameStatus: f.blameStatus,
|
||||
currentStep: f.currentStep,
|
||||
nextStep: f.nextStep,
|
||||
createdAt: f.createdAt,
|
||||
steps: f.steps,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Single endpoint handler: complete all blame-needed data.
|
||||
* Routes to THIRD_PARTY or CAR_BODY completion based on request type.
|
||||
*/
|
||||
async completeBlameData(
|
||||
expert: any,
|
||||
requestId: string,
|
||||
formData: any,
|
||||
): Promise<RequestManagementDtoRs> {
|
||||
const request = await this.requestManagementDbService.findOne(requestId);
|
||||
if (!request) {
|
||||
throw new NotFoundException("Request not found");
|
||||
}
|
||||
if (!request.expertInitiated) {
|
||||
throw new BadRequestException(
|
||||
"This endpoint is only for expert-initiated files",
|
||||
);
|
||||
}
|
||||
await this.verifyExpertAccess(request, expert);
|
||||
|
||||
if (request.type === "THIRD_PARTY") {
|
||||
return this.expertCompleteThirdPartyForm(expert, requestId, formData);
|
||||
}
|
||||
if (request.type === "CAR_BODY") {
|
||||
return this.expertCompleteCarBodyForm(expert, requestId, formData);
|
||||
}
|
||||
throw new BadRequestException(
|
||||
"Unknown file type. Must be THIRD_PARTY or CAR_BODY.",
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expert completes all information for IN_PERSON THIRD_PARTY file at once
|
||||
* This replaces the step-by-step flow for experts filling files on-site
|
||||
|
||||
49
src/users/entities/db-service/field-expert.db.service.ts
Normal file
49
src/users/entities/db-service/field-expert.db.service.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { InjectModel } from "@nestjs/mongoose";
|
||||
import { FilterQuery, Model, Types } from "mongoose";
|
||||
import { FieldExpertModel } from "../schema/field-expert.schema";
|
||||
|
||||
@Injectable()
|
||||
export class FieldExpertDbService {
|
||||
constructor(
|
||||
@InjectModel(FieldExpertModel.name)
|
||||
private readonly fieldExpertModel: Model<FieldExpertModel>,
|
||||
) {}
|
||||
|
||||
async create(
|
||||
user: Partial<FieldExpertModel> & { password: string },
|
||||
): Promise<FieldExpertModel> {
|
||||
return await this.fieldExpertModel.create(user);
|
||||
}
|
||||
|
||||
async findOne(
|
||||
filter: FilterQuery<FieldExpertModel>,
|
||||
): Promise<FieldExpertModel | null> {
|
||||
return await this.fieldExpertModel.findOne(filter);
|
||||
}
|
||||
|
||||
async findById(userId: string): Promise<FieldExpertModel | null> {
|
||||
return await this.fieldExpertModel.findOne({
|
||||
_id: new Types.ObjectId(userId),
|
||||
});
|
||||
}
|
||||
|
||||
async findOneAndUpdate(
|
||||
filter: FilterQuery<FieldExpertModel>,
|
||||
update: FilterQuery<FieldExpertModel>,
|
||||
) {
|
||||
return await this.fieldExpertModel.findOneAndUpdate(filter, update, {
|
||||
new: true,
|
||||
});
|
||||
}
|
||||
|
||||
async findAll(
|
||||
filter: FilterQuery<FieldExpertModel>,
|
||||
): Promise<(FieldExpertModel & { _id: Types.ObjectId })[]> {
|
||||
return await this.fieldExpertModel.find(filter).lean();
|
||||
}
|
||||
|
||||
async updateOne(filter: FilterQuery<FieldExpertModel>, update: any) {
|
||||
return this.fieldExpertModel.updateOne(filter, update);
|
||||
}
|
||||
}
|
||||
78
src/users/entities/schema/field-expert.schema.ts
Normal file
78
src/users/entities/schema/field-expert.schema.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
|
||||
import { Types } from "mongoose";
|
||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
|
||||
@Schema({ _id: false })
|
||||
export class RequestStats {
|
||||
@Prop({ default: 0 })
|
||||
totalHandled: number;
|
||||
|
||||
@Prop({ default: 0 })
|
||||
totalChecked: number;
|
||||
}
|
||||
|
||||
const RequestStatsSchema = SchemaFactory.createForClass(RequestStats);
|
||||
|
||||
@Schema({
|
||||
collection: "field-expert",
|
||||
versionKey: false,
|
||||
timestamps: true,
|
||||
})
|
||||
export class FieldExpertModel {
|
||||
_id?: Types.ObjectId;
|
||||
|
||||
@Prop({ required: true })
|
||||
firstName: string;
|
||||
|
||||
@Prop({ required: true })
|
||||
lastName: string;
|
||||
|
||||
@Prop({ type: "string", unique: true })
|
||||
email: string;
|
||||
|
||||
@Prop({ type: "string" })
|
||||
username: string;
|
||||
|
||||
@Prop({ required: true })
|
||||
password: string;
|
||||
|
||||
@Prop({ required: true })
|
||||
mobile: string;
|
||||
|
||||
@Prop({ required: false })
|
||||
phone?: string;
|
||||
|
||||
@Prop({ default: RoleEnum.FIELD_EXPERT })
|
||||
role: RoleEnum;
|
||||
|
||||
@Prop({ type: "string", default: "" })
|
||||
otp: string;
|
||||
|
||||
@Prop({ type: RequestStatsSchema, default: () => ({}) })
|
||||
requestStats: RequestStats;
|
||||
|
||||
@Prop({
|
||||
type: [{ type: String }],
|
||||
default: [],
|
||||
validate: {
|
||||
validator: (arr: any[]) => arr.every((val) => typeof val === "string"),
|
||||
message: "All countedRequests must be strings",
|
||||
},
|
||||
})
|
||||
countedRequests?: string[];
|
||||
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export const FieldExpertDbSchema =
|
||||
SchemaFactory.createForClass(FieldExpertModel);
|
||||
|
||||
FieldExpertDbSchema.pre("save", function (next) {
|
||||
if (!this.username) {
|
||||
this.username = this.email;
|
||||
}
|
||||
if (!this.requestStats) {
|
||||
this.requestStats = { totalChecked: 0, totalHandled: 0 };
|
||||
}
|
||||
next();
|
||||
});
|
||||
@@ -5,6 +5,7 @@ import { ExpertModel } from "src/users/entities/schema/expert.schema";
|
||||
import { HashModule } from "src/utils/hash/hash.module";
|
||||
import { OtpModule } from "src/utils/otp/otp.module";
|
||||
import { ExpertDbService } from "./entities/db-service/expert.db.service";
|
||||
import { FieldExpertDbService } from "./entities/db-service/field-expert.db.service";
|
||||
import { InsurerExpertDbService } from "./entities/db-service/insurer-expert.db.service";
|
||||
import { UserDbService } from "./entities/db-service/user.db.service";
|
||||
import {
|
||||
@@ -12,6 +13,10 @@ import {
|
||||
DamageExpertModel,
|
||||
} from "./entities/schema/damage-expert.schema";
|
||||
import { ExpertDbSchema } from "./entities/schema/expert.schema";
|
||||
import {
|
||||
FieldExpertDbSchema,
|
||||
FieldExpertModel,
|
||||
} from "./entities/schema/field-expert.schema";
|
||||
import {
|
||||
InsurerExpertDbSchema,
|
||||
InsurerExpertModel,
|
||||
@@ -24,6 +29,7 @@ import { UserModel, UserDbSchema } from "./entities/schema/user.schema";
|
||||
{ name: UserModel.name, schema: UserDbSchema },
|
||||
{ name: DamageExpertModel.name, schema: DamageExpertDbSchema },
|
||||
{ name: ExpertModel.name, schema: ExpertDbSchema },
|
||||
{ name: FieldExpertModel.name, schema: FieldExpertDbSchema },
|
||||
{ name: InsurerExpertModel.name, schema: InsurerExpertDbSchema },
|
||||
]),
|
||||
OtpModule,
|
||||
@@ -34,12 +40,14 @@ import { UserModel, UserDbSchema } from "./entities/schema/user.schema";
|
||||
UserDbService,
|
||||
ExpertDbService,
|
||||
DamageExpertDbService,
|
||||
FieldExpertDbService,
|
||||
InsurerExpertDbService,
|
||||
],
|
||||
exports: [
|
||||
UserDbService,
|
||||
ExpertDbService,
|
||||
DamageExpertDbService,
|
||||
FieldExpertDbService,
|
||||
InsurerExpertDbService,
|
||||
],
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user