1
0
forked from Yara724/api

Merge branch 'main' into integrate-my-work

This commit is contained in:
2026-02-24 12:21:31 +03:30
13 changed files with 512 additions and 22 deletions

View File

@@ -1,6 +1,7 @@
export enum RoleEnum { export enum RoleEnum {
EXPERT = "expert", EXPERT = "expert",
DAMAGE_EXPERT = "damage_expert", DAMAGE_EXPERT = "damage_expert",
FIELD_EXPERT = "field_expert",
COMPANY = "company", COMPANY = "company",
ADMIN = "admin", ADMIN = "admin",
USER = "user", USER = "user",

View File

@@ -22,6 +22,7 @@ import {
} from "src/auth/dto/actor/forget-password.actor.dto"; } from "src/auth/dto/actor/forget-password.actor.dto";
import { LoginActorDto } from "src/auth/dto/actor/login.actor.dto"; import { LoginActorDto } from "src/auth/dto/actor/login.actor.dto";
import { ActorEditUserProfileDto } from "src/auth/dto/actor/profile.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 { import {
GenuineRegisterDto, GenuineRegisterDto,
InsurerRegisterDto, InsurerRegisterDto,
@@ -55,6 +56,14 @@ export class ActorAuthController {
return await this.actorAuthService.insurerRegister(body); 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) @UseGuards(LocalActorAuthGuard)
@Post("login") @Post("login")
@Roles() @Roles()

View File

@@ -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 { InsurerExpertDbService } from "src/users/entities/db-service/insurer-expert.db.service";
import { DamageExpertDbService } from "src/users/entities/db-service/damage-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 { 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 { HashService } from "src/utils/hash/hash.service";
// import { MailService } from "src/utils/mail/mail.service"; // import { MailService } from "src/utils/mail/mail.service";
import { OtpService } from "src/utils/otp/otp.service"; import { OtpService } from "src/utils/otp/otp.service";
@@ -44,6 +45,7 @@ export class ActorAuthService {
private readonly hashService: HashService, private readonly hashService: HashService,
private readonly expertDbService: ExpertDbService, private readonly expertDbService: ExpertDbService,
private readonly damageExpertDbService: DamageExpertDbService, private readonly damageExpertDbService: DamageExpertDbService,
private readonly fieldExpertDbService: FieldExpertDbService,
private readonly insurerExpertDbService: InsurerExpertDbService, private readonly insurerExpertDbService: InsurerExpertDbService,
// private readonly mailService: MailService, // Mailer disabled not used // private readonly mailService: MailService, // Mailer disabled not used
private readonly clientDbService: ClientDbService, private readonly clientDbService: ClientDbService,
@@ -69,6 +71,14 @@ export class ActorAuthService {
else else
res = await this.damageExpertDbService.findOne({ email: username }); res = await this.damageExpertDbService.findOne({ email: username });
break; 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: case RoleEnum.COMPANY:
res = await this.insurerExpertDbService.findOne({ email: username }); res = await this.insurerExpertDbService.findOne({ email: username });
break; break;
@@ -259,6 +269,11 @@ export class ActorAuthService {
dbServiceToUpdate = this.insurerExpertDbService as any; dbServiceToUpdate = this.insurerExpertDbService as any;
} }
if (!actor) {
actor = await this.fieldExpertDbService.findOne(filter);
dbServiceToUpdate = this.fieldExpertDbService as any;
}
if (!actor) { if (!actor) {
throw new NotFoundException("actor not found"); throw new NotFoundException("actor not found");
} }
@@ -280,18 +295,29 @@ export class ActorAuthService {
} }
async forgetPasswordVerify(email, otp, newPassword) { 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"); if (!userExist) throw new NotFoundException("user not found");
const decodeOtp = await this.hashService.compare(otp, userExist.otp); const decodeOtp = await this.hashService.compare(otp, userExist.otp);
if (!decodeOtp) throw new UnauthorizedException("otp invalid"); if (!decodeOtp) throw new UnauthorizedException("otp invalid");
if (decodeOtp) { const hashNewPassword = await this.hashService.hash(newPassword);
const hashNewPassword = await this.hashService.hash(newPassword); await dbServiceToUpdate.findOneAndUpdate(
await this.expertDbService.findOneAndUpdate( { email },
{ email }, { password: hashNewPassword },
{ password: hashNewPassword }, );
); return new ForgetPasswordVerifyCodeDtoRs(userExist, "update password");
return new ForgetPasswordVerifyCodeDtoRs(userExist, "update password");
}
} }
async getProfiles(currentUser) { async getProfiles(currentUser) {
@@ -349,7 +375,14 @@ export class ActorAuthService {
"city", "city",
"state", "state",
"address", "address",
] ],
field_expert: [
"firstName",
"lastName",
"email",
"phone",
"mobile",
],
}; };
const allowedFields = allowedFieldsByRole[role]; const allowedFields = allowedFieldsByRole[role];
@@ -409,6 +442,13 @@ export class ActorAuthService {
); );
break; break;
case "field_expert":
await this.fieldExpertDbService.updateOne(
{ _id: new Types.ObjectId(userId) },
{ $set: sanitized },
);
break;
default: default:
throw new ForbiddenException("Unsupported role for update"); throw new ForbiddenException("Unsupported role for update");
} }
@@ -419,4 +459,43 @@ export class ActorAuthService {
throw new InternalServerErrorException("Failed to update profile"); 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;
}
}
} }

View 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;
}

View File

@@ -39,9 +39,12 @@ export class LocalActorAuthGuard extends AuthGuard("actor") {
}); });
if ( 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"); throw new UnauthorizedException("User role is not authorized");
} }

View File

@@ -87,6 +87,7 @@ import { JwtModule } from "@nestjs/jwt";
], ],
controllers: [ClaimRequestManagementController, ClaimRequestManagementV2Controller], controllers: [ClaimRequestManagementController, ClaimRequestManagementV2Controller],
exports: [ exports: [
ClaimRequestManagementService,
ClaimRequestManagementDbService, ClaimRequestManagementDbService,
ClaimCaseDbService, ClaimCaseDbService,
DamageImageDbService, DamageImageDbService,

View File

@@ -137,7 +137,12 @@ export class ClaimRequestManagementService {
// If actor is an expert, verify they're accessing an IN_PERSON claim file // If actor is an expert, verify they're accessing an IN_PERSON claim file
// and return the claim file's userId // 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); const claim = await this.claimDbService.findOne(claimRequestId);
if (!claim) { if (!claim) {
throw new NotFoundException("Claim file not found"); throw new NotFoundException("Claim file not found");
@@ -217,11 +222,16 @@ export class ClaimRequestManagementService {
// For IN_PERSON expert-initiated files, expert creates the claim // For IN_PERSON expert-initiated files, expert creates the claim
// For LINK method or regular files, user creates their own claim // For LINK method or regular files, user creates their own claim
const expertRoles = [
RoleEnum.EXPERT,
RoleEnum.DAMAGE_EXPERT,
RoleEnum.FIELD_EXPERT,
];
if ( if (
blameRequest.expertInitiated && blameRequest.expertInitiated &&
blameRequest.creationMethod === "IN_PERSON" && blameRequest.creationMethod === "IN_PERSON" &&
actor && actor &&
(actor.role === "EXPERT" || actor.role === "DAMAGE_EXPERT") expertRoles.includes(actor.role)
) { ) {
// Expert is creating claim for IN_PERSON file // Expert is creating claim for IN_PERSON file
// Verify expert is the initiating expert // Verify expert is the initiating expert
@@ -376,6 +386,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( async selectCarOtherPartDamage(
requestId: string, requestId: string,
body: any, // Use your DTO for better type safety body: any, // Use your DTO for better type safety
@@ -1219,10 +1292,12 @@ export class ClaimRequestManagementService {
} }
// For experts, return IN_PERSON claim files they initiated // For experts, return IN_PERSON claim files they initiated
if ( const expertRoles = [
currentUser.role === RoleEnum.EXPERT || RoleEnum.EXPERT,
currentUser.role === RoleEnum.DAMAGE_EXPERT RoleEnum.DAMAGE_EXPERT,
) { RoleEnum.FIELD_EXPERT,
];
if (expertRoles.includes(currentUser.role)) {
const allClaims = await this.claimDbService.findAllByAnyFilter({}); const allClaims = await this.claimDbService.findAllByAnyFilter({});
const expertClaims = allClaims.filter((claim) => { const expertClaims = allClaims.filter((claim) => {
const blameFile = claim.blameFile; const blameFile = claim.blameFile;

View 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[];
}

View File

@@ -34,17 +34,83 @@ import { CreateExpertInitiatedFileDto } from "./dto/expert-initiated.dto";
import { ExpertCompleteThirdPartyFormDto } from "./dto/expert-complete-third-party-form.dto"; import { ExpertCompleteThirdPartyFormDto } from "./dto/expert-complete-third-party-form.dto";
import { ExpertCompleteCarBodyFormDto } from "./dto/expert-complete-car-body-form.dto"; import { ExpertCompleteCarBodyFormDto } from "./dto/expert-complete-car-body-form.dto";
import { ExpertAccidentFieldsDto } from "./dto/expert-accident-fields.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") @ApiTags("expert-initiated-blame")
@Controller("expert-initiated-blame") @Controller("expert-initiated-blame")
@ApiBearerAuth() @ApiBearerAuth()
@UseGuards(LocalActorAuthGuard, RolesGuard) @UseGuards(LocalActorAuthGuard, RolesGuard)
@Roles(RoleEnum.EXPERT, RoleEnum.DAMAGE_EXPERT) @Roles(RoleEnum.FIELD_EXPERT)
export class ExpertInitiatedController { export class ExpertInitiatedController {
constructor( constructor(
private readonly requestManagementService: RequestManagementService, 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") @Post("create")
@ApiOperation({ @ApiOperation({
summary: "Create expert-initiated blame file", summary: "Create expert-initiated blame file",

View File

@@ -2963,8 +2963,13 @@ export class RequestManagementService {
request: RequestManagementModel, request: RequestManagementModel,
user: any, user: any,
): Promise<void> { ): Promise<void> {
// If user is not an expert, no verification needed (normal flow) // If user is not an expert type, no verification needed (normal flow)
if (user.role !== RoleEnum.EXPERT && user.role !== RoleEnum.DAMAGE_EXPERT) { const expertRoles = [
RoleEnum.EXPERT,
RoleEnum.DAMAGE_EXPERT,
RoleEnum.FIELD_EXPERT,
];
if (!expertRoles.includes(user.role)) {
return; return;
} }
@@ -3115,6 +3120,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 * 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 * This replaces the step-by-step flow for experts filling files on-site

View 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);
}
}

View 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();
});

View File

@@ -5,6 +5,7 @@ import { ExpertModel } from "src/users/entities/schema/expert.schema";
import { HashModule } from "src/utils/hash/hash.module"; import { HashModule } from "src/utils/hash/hash.module";
import { OtpModule } from "src/utils/otp/otp.module"; import { OtpModule } from "src/utils/otp/otp.module";
import { ExpertDbService } from "./entities/db-service/expert.db.service"; 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 { InsurerExpertDbService } from "./entities/db-service/insurer-expert.db.service";
import { UserDbService } from "./entities/db-service/user.db.service"; import { UserDbService } from "./entities/db-service/user.db.service";
import { import {
@@ -12,6 +13,10 @@ import {
DamageExpertModel, DamageExpertModel,
} from "./entities/schema/damage-expert.schema"; } from "./entities/schema/damage-expert.schema";
import { ExpertDbSchema } from "./entities/schema/expert.schema"; import { ExpertDbSchema } from "./entities/schema/expert.schema";
import {
FieldExpertDbSchema,
FieldExpertModel,
} from "./entities/schema/field-expert.schema";
import { import {
InsurerExpertDbSchema, InsurerExpertDbSchema,
InsurerExpertModel, InsurerExpertModel,
@@ -24,6 +29,7 @@ import { UserModel, UserDbSchema } from "./entities/schema/user.schema";
{ name: UserModel.name, schema: UserDbSchema }, { name: UserModel.name, schema: UserDbSchema },
{ name: DamageExpertModel.name, schema: DamageExpertDbSchema }, { name: DamageExpertModel.name, schema: DamageExpertDbSchema },
{ name: ExpertModel.name, schema: ExpertDbSchema }, { name: ExpertModel.name, schema: ExpertDbSchema },
{ name: FieldExpertModel.name, schema: FieldExpertDbSchema },
{ name: InsurerExpertModel.name, schema: InsurerExpertDbSchema }, { name: InsurerExpertModel.name, schema: InsurerExpertDbSchema },
]), ]),
OtpModule, OtpModule,
@@ -34,12 +40,14 @@ import { UserModel, UserDbSchema } from "./entities/schema/user.schema";
UserDbService, UserDbService,
ExpertDbService, ExpertDbService,
DamageExpertDbService, DamageExpertDbService,
FieldExpertDbService,
InsurerExpertDbService, InsurerExpertDbService,
], ],
exports: [ exports: [
UserDbService, UserDbService,
ExpertDbService, ExpertDbService,
DamageExpertDbService, DamageExpertDbService,
FieldExpertDbService,
InsurerExpertDbService, InsurerExpertDbService,
], ],
}) })