forked from Yara724/api
Added registrar + fixed sign bug in car-body flow
This commit is contained in:
@@ -2,6 +2,7 @@ export enum RoleEnum {
|
||||
EXPERT = "expert",
|
||||
DAMAGE_EXPERT = "damage_expert",
|
||||
FIELD_EXPERT = "field_expert",
|
||||
REGISTRAR = "registrar",
|
||||
COMPANY = "company",
|
||||
ADMIN = "admin",
|
||||
USER = "user",
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
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 { CreateRegistrarDto } from "src/auth/dto/actor/create-registrar.actor.dto";
|
||||
import {
|
||||
GenuineRegisterDto,
|
||||
InsurerRegisterDto,
|
||||
@@ -64,6 +65,14 @@ export class ActorAuthController {
|
||||
return await this.actorAuthService.createFieldExpertMock(body);
|
||||
}
|
||||
|
||||
/** Mock: create a registrar for testing. Make private later. */
|
||||
@Post("create-registrar")
|
||||
@ApiBody({ type: CreateRegistrarDto })
|
||||
@ApiAcceptedResponse()
|
||||
async createRegistrar(@Body() body: CreateRegistrarDto) {
|
||||
return await this.actorAuthService.createRegistrarMock(body);
|
||||
}
|
||||
|
||||
@UseGuards(LocalActorAuthGuard)
|
||||
@Post("login")
|
||||
@Roles()
|
||||
|
||||
@@ -25,6 +25,7 @@ import { InsurerExpertDbService } from "src/users/entities/db-service/insurer-ex
|
||||
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 { RegistrarDbService } from "src/users/entities/db-service/registrar.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";
|
||||
@@ -46,6 +47,7 @@ export class ActorAuthService {
|
||||
private readonly expertDbService: ExpertDbService,
|
||||
private readonly damageExpertDbService: DamageExpertDbService,
|
||||
private readonly fieldExpertDbService: FieldExpertDbService,
|
||||
private readonly registrarDbService: RegistrarDbService,
|
||||
private readonly insurerExpertDbService: InsurerExpertDbService,
|
||||
// private readonly mailService: MailService, // Mailer disabled – not used
|
||||
private readonly clientDbService: ClientDbService,
|
||||
@@ -79,6 +81,13 @@ export class ActorAuthService {
|
||||
else
|
||||
res = await this.fieldExpertDbService.findOne({ email: username });
|
||||
break;
|
||||
case RoleEnum.REGISTRAR:
|
||||
if (username == null && userId)
|
||||
res = await this.registrarDbService.findOne({
|
||||
_id: new Types.ObjectId(userId),
|
||||
});
|
||||
else res = await this.registrarDbService.findOne({ email: username });
|
||||
break;
|
||||
case RoleEnum.COMPANY:
|
||||
res = await this.insurerExpertDbService.findOne({ email: username });
|
||||
break;
|
||||
@@ -273,6 +282,10 @@ export class ActorAuthService {
|
||||
actor = await this.fieldExpertDbService.findOne(filter);
|
||||
dbServiceToUpdate = this.fieldExpertDbService as any;
|
||||
}
|
||||
if (!actor) {
|
||||
actor = await this.registrarDbService.findOne(filter);
|
||||
dbServiceToUpdate = this.registrarDbService as any;
|
||||
}
|
||||
|
||||
if (!actor) {
|
||||
throw new NotFoundException("actor not found");
|
||||
@@ -309,6 +322,10 @@ export class ActorAuthService {
|
||||
userExist = await this.fieldExpertDbService.findOne({ email });
|
||||
dbServiceToUpdate = this.fieldExpertDbService;
|
||||
}
|
||||
if (!userExist) {
|
||||
userExist = await this.registrarDbService.findOne({ email });
|
||||
dbServiceToUpdate = this.registrarDbService as any;
|
||||
}
|
||||
if (!userExist) throw new NotFoundException("user not found");
|
||||
const decodeOtp = await this.hashService.compare(otp, userExist.otp);
|
||||
if (!decodeOtp) throw new UnauthorizedException("otp invalid");
|
||||
@@ -383,6 +400,7 @@ export class ActorAuthService {
|
||||
"phone",
|
||||
"mobile",
|
||||
],
|
||||
registrar: ["email"],
|
||||
};
|
||||
|
||||
const allowedFields = allowedFieldsByRole[role];
|
||||
@@ -498,4 +516,34 @@ export class ActorAuthService {
|
||||
throw er;
|
||||
}
|
||||
}
|
||||
|
||||
async createRegistrarMock(body: {
|
||||
email: string;
|
||||
password: string;
|
||||
clientId: string;
|
||||
}) {
|
||||
const hashPassword = await this.hashService.hash(body.password);
|
||||
const payload = {
|
||||
email: body.email.toLowerCase().trim(),
|
||||
password: hashPassword,
|
||||
clientKey: new Types.ObjectId(body.clientId),
|
||||
role: RoleEnum.REGISTRAR,
|
||||
};
|
||||
try {
|
||||
const created = await this.registrarDbService.create(payload as any);
|
||||
return {
|
||||
id: (created as any)._id,
|
||||
email: (created as any).email,
|
||||
clientKey: (created as any).clientKey,
|
||||
role: (created as any).role,
|
||||
};
|
||||
} catch (er) {
|
||||
if (er.code === 11000) {
|
||||
throw new BadRequestException(
|
||||
"A registrar with this email already exists.",
|
||||
);
|
||||
}
|
||||
throw er;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
18
src/auth/dto/actor/create-registrar.actor.dto.ts
Normal file
18
src/auth/dto/actor/create-registrar.actor.dto.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsEmail, IsMongoId, IsString, MinLength } from "class-validator";
|
||||
|
||||
export class CreateRegistrarDto {
|
||||
@ApiProperty({ example: "registrar@sample.com" })
|
||||
@IsEmail()
|
||||
email: string;
|
||||
|
||||
@ApiProperty({ example: "securePassword123", minLength: 6 })
|
||||
@IsString()
|
||||
@MinLength(6)
|
||||
password: string;
|
||||
|
||||
@ApiProperty({ example: "507f1f77bcf86cd799439011" })
|
||||
@IsMongoId()
|
||||
clientId: string;
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ export class LocalActorAuthGuard extends AuthGuard("actor") {
|
||||
RoleEnum.DAMAGE_EXPERT,
|
||||
RoleEnum.COMPANY,
|
||||
RoleEnum.FIELD_EXPERT,
|
||||
RoleEnum.REGISTRAR,
|
||||
].includes(payload.role)
|
||||
) {
|
||||
throw new UnauthorizedException("User role is not authorized");
|
||||
|
||||
@@ -6,6 +6,7 @@ import { RequestManagementModule } from "src/request-management/request-manageme
|
||||
import { UsersModule } from "src/users/users.module";
|
||||
import { ClaimRequestManagementController } from "./claim-request-management.controller";
|
||||
import { ClaimRequestManagementV2Controller } from "./claim-request-management.v2.controller";
|
||||
import { RegistrarClaimV1Controller } from "./registrar-claim.v1.controller";
|
||||
import { ClaimRequestManagementService } from "./claim-request-management.service";
|
||||
import { CarGreenCardDbService } from "./entites/db-service/car-green-card.db.service";
|
||||
import { ClaimRequestManagementDbService } from "./entites/db-service/claim-request-management.db.service";
|
||||
@@ -85,7 +86,11 @@ import { JwtModule } from "@nestjs/jwt";
|
||||
ClaimRequiredDocumentDbService,
|
||||
ClaimAccessGuard,
|
||||
],
|
||||
controllers: [ClaimRequestManagementController, ClaimRequestManagementV2Controller],
|
||||
controllers: [
|
||||
ClaimRequestManagementController,
|
||||
ClaimRequestManagementV2Controller,
|
||||
RegistrarClaimV1Controller,
|
||||
],
|
||||
exports: [
|
||||
ClaimRequestManagementService,
|
||||
ClaimRequestManagementDbService,
|
||||
|
||||
@@ -2996,25 +2996,130 @@ export class ClaimRequestManagementService {
|
||||
currentUserId: string,
|
||||
actorRole?: string,
|
||||
): Promise<string> {
|
||||
if (actorRole !== RoleEnum.FIELD_EXPERT || !claimCase?.blameRequestId) {
|
||||
if (
|
||||
![RoleEnum.FIELD_EXPERT, RoleEnum.REGISTRAR].includes(actorRole as any) ||
|
||||
!claimCase?.blameRequestId
|
||||
) {
|
||||
return currentUserId;
|
||||
}
|
||||
const blameRequest = await this.blameRequestDbService.findById(
|
||||
claimCase.blameRequestId.toString(),
|
||||
);
|
||||
if (
|
||||
!blameRequest?.expertInitiated ||
|
||||
blameRequest.creationMethod !== "IN_PERSON" ||
|
||||
!blameRequest.initiatedByFieldExpertId
|
||||
) {
|
||||
return currentUserId;
|
||||
}
|
||||
if (String(blameRequest.initiatedByFieldExpertId) !== currentUserId) {
|
||||
return currentUserId;
|
||||
if (!blameRequest || blameRequest.creationMethod !== "IN_PERSON") return currentUserId;
|
||||
if (actorRole === RoleEnum.FIELD_EXPERT) {
|
||||
if (
|
||||
!blameRequest.expertInitiated ||
|
||||
!blameRequest.initiatedByFieldExpertId ||
|
||||
String(blameRequest.initiatedByFieldExpertId) !== currentUserId
|
||||
) {
|
||||
return currentUserId;
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
!blameRequest.registrarInitiated ||
|
||||
!blameRequest.initiatedByRegistrarId ||
|
||||
String(blameRequest.initiatedByRegistrarId) !== currentUserId
|
||||
) {
|
||||
return currentUserId;
|
||||
}
|
||||
}
|
||||
return claimCase.owner?.userId ? String(claimCase.owner.userId) : currentUserId;
|
||||
}
|
||||
|
||||
async createClaimFromBlameForRegistrarV1(
|
||||
blameRequestId: string,
|
||||
registrar: { sub: string; firstName?: string; lastName?: string },
|
||||
): Promise<CreateClaimFromBlameResponseDto> {
|
||||
const blameRequest = await this.blameRequestDbService.findById(blameRequestId);
|
||||
if (!blameRequest) throw new NotFoundException("Blame request not found");
|
||||
if (blameRequest.status !== BlameCaseStatus.COMPLETED) {
|
||||
throw new BadRequestException(
|
||||
`Blame request must be COMPLETED. Current status: ${blameRequest.status}`,
|
||||
);
|
||||
}
|
||||
if (
|
||||
!blameRequest.registrarInitiated ||
|
||||
blameRequest.creationMethod !== "IN_PERSON" ||
|
||||
!blameRequest.initiatedByRegistrarId
|
||||
) {
|
||||
throw new BadRequestException(
|
||||
"This endpoint is only for registrar-initiated IN_PERSON blame files.",
|
||||
);
|
||||
}
|
||||
if (String(blameRequest.initiatedByRegistrarId) !== String(registrar.sub)) {
|
||||
throw new ForbiddenException(
|
||||
"Only the registrar who created this blame file can create the claim.",
|
||||
);
|
||||
}
|
||||
const parties = blameRequest.parties || [];
|
||||
const isCarBody = blameRequest.type === BlameRequestType.CAR_BODY;
|
||||
let damagedUserId: string;
|
||||
if (isCarBody) {
|
||||
const first = parties.find((p) => p.role === "FIRST");
|
||||
if (!first?.person?.userId) throw new BadRequestException("First party has no userId");
|
||||
damagedUserId = String(first.person.userId);
|
||||
} else {
|
||||
const guiltyPartyId = blameRequest.expert?.decision?.guiltyPartyId
|
||||
? String(blameRequest.expert.decision.guiltyPartyId)
|
||||
: null;
|
||||
if (!guiltyPartyId) throw new BadRequestException("Blame request has no guilty party set");
|
||||
const damagedParty = parties.find(
|
||||
(p) => p.person?.userId && String(p.person.userId) !== guiltyPartyId,
|
||||
);
|
||||
if (!damagedParty?.person?.userId) throw new BadRequestException("Could not determine damaged party");
|
||||
damagedUserId = String(damagedParty.person.userId);
|
||||
}
|
||||
const existingClaim = await this.claimCaseDbService.findOne({
|
||||
blameRequestId: new Types.ObjectId(blameRequestId),
|
||||
});
|
||||
if (existingClaim) throw new ConflictException("A claim for this blame case already exists");
|
||||
const claimNo = await this.generateUniqueClaimNumber();
|
||||
const damagedParty = parties.find(
|
||||
(p) => p.person?.userId && String(p.person.userId) === damagedUserId,
|
||||
);
|
||||
const newClaim = await this.claimCaseDbService.create({
|
||||
requestNo: claimNo,
|
||||
publicId: blameRequest.publicId,
|
||||
blameRequestId: new Types.ObjectId(blameRequestId),
|
||||
blameRequestNo: blameRequest.requestNo,
|
||||
status: ClaimCaseStatus.SELECTING_OUTER_PARTS,
|
||||
claimStatus: ClaimStatus.PENDING,
|
||||
workflow: {
|
||||
currentStep: ClaimWorkflowStep.SELECT_OUTER_PARTS,
|
||||
nextStep: ClaimWorkflowStep.SELECT_OTHER_PARTS,
|
||||
completedSteps: [ClaimWorkflowStep.CLAIM_CREATED],
|
||||
locked: false,
|
||||
},
|
||||
owner: {
|
||||
userId: new Types.ObjectId(damagedUserId),
|
||||
userRole: damagedParty?.role as any,
|
||||
},
|
||||
createdByRegistrarId: new Types.ObjectId(registrar.sub),
|
||||
history: [
|
||||
{
|
||||
type: "CLAIM_CREATED",
|
||||
actor: {
|
||||
actorId: new Types.ObjectId(registrar.sub),
|
||||
actorName: `${registrar.firstName || ""} ${registrar.lastName || ""}`.trim(),
|
||||
actorType: "registrar",
|
||||
},
|
||||
timestamp: new Date(),
|
||||
metadata: {
|
||||
blameRequestId,
|
||||
blamePublicId: blameRequest.publicId,
|
||||
createdByRegistrar: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
} as any);
|
||||
return {
|
||||
claimRequestId: String(newClaim._id),
|
||||
publicId: blameRequest.publicId,
|
||||
status: ClaimCaseStatus.SELECTING_OUTER_PARTS,
|
||||
message: "Claim created successfully. Registrar can now fill claim data.",
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 API: Select damaged outer car parts for claim (Step 2 of claim workflow)
|
||||
*
|
||||
|
||||
@@ -133,6 +133,9 @@ export class ClaimCase {
|
||||
@Prop({ type: [HistoryEventSchema], default: [] })
|
||||
history?: HistoryEvent[];
|
||||
|
||||
@Prop({ type: Types.ObjectId, index: true })
|
||||
createdByRegistrarId?: Types.ObjectId;
|
||||
|
||||
/**
|
||||
* Legacy fields kept optional to simplify progressive migration.
|
||||
* If you choose to migrate later, we can remove these.
|
||||
|
||||
135
src/claim-request-management/registrar-claim.v1.controller.ts
Normal file
135
src/claim-request-management/registrar-claim.v1.controller.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import { Body, Controller, Get, Param, Patch, Post, UploadedFile, UseGuards, UseInterceptors } from "@nestjs/common";
|
||||
import { FileInterceptor } from "@nestjs/platform-express";
|
||||
import { diskStorage } from "multer";
|
||||
import { extname } from "path";
|
||||
import { ApiBearerAuth, ApiBody, ApiConsumes, ApiOperation, ApiParam, ApiTags } from "@nestjs/swagger";
|
||||
import { LocalActorAuthGuard } from "src/auth/guards/actor-local.guard";
|
||||
import { RolesGuard } from "src/auth/guards/role.guard";
|
||||
import { Roles } from "src/decorators/roles.decorator";
|
||||
import { CurrentUser } from "src/decorators/user.decorator";
|
||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
import { SelectOuterPartsV2Dto } from "./dto/select-outer-parts-v2.dto";
|
||||
import { SelectOtherPartsV2Dto } from "./dto/select-other-parts-v2.dto";
|
||||
import { UploadRequiredDocumentV2Dto } from "./dto/upload-document-v2.dto";
|
||||
import { CapturePartV2Dto } from "./dto/capture-part-v2.dto";
|
||||
import { ClaimRequestManagementService } from "./claim-request-management.service";
|
||||
|
||||
@ApiTags("registrar-claim (v1)")
|
||||
@Controller("registrar-claim")
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(LocalActorAuthGuard, RolesGuard)
|
||||
@Roles(RoleEnum.REGISTRAR)
|
||||
export class RegistrarClaimV1Controller {
|
||||
constructor(private readonly claimRequestManagementService: ClaimRequestManagementService) {}
|
||||
|
||||
@Post("create-from-blame/:blameRequestId")
|
||||
@ApiParam({ name: "blameRequestId" })
|
||||
createFromBlame(@Param("blameRequestId") blameRequestId: string, @CurrentUser() registrar: any) {
|
||||
return this.claimRequestManagementService.createClaimFromBlameForRegistrarV1(
|
||||
blameRequestId,
|
||||
registrar,
|
||||
);
|
||||
}
|
||||
|
||||
@Get("request/:claimRequestId")
|
||||
getClaim(@Param("claimRequestId") claimRequestId: string, @CurrentUser() registrar: any) {
|
||||
return this.claimRequestManagementService.getClaimDetailsV2(
|
||||
claimRequestId,
|
||||
registrar.sub,
|
||||
registrar,
|
||||
);
|
||||
}
|
||||
|
||||
@Patch("select-outer-parts/:claimRequestId")
|
||||
@ApiBody({ type: SelectOuterPartsV2Dto })
|
||||
selectOuter(
|
||||
@Param("claimRequestId") claimRequestId: string,
|
||||
@Body() body: SelectOuterPartsV2Dto,
|
||||
@CurrentUser() registrar: any,
|
||||
) {
|
||||
return this.claimRequestManagementService.selectOuterPartsV2(
|
||||
claimRequestId,
|
||||
body,
|
||||
registrar.sub,
|
||||
registrar,
|
||||
);
|
||||
}
|
||||
|
||||
@Patch("select-other-parts/:claimRequestId")
|
||||
@ApiBody({ type: SelectOtherPartsV2Dto })
|
||||
selectOther(
|
||||
@Param("claimRequestId") claimRequestId: string,
|
||||
@Body() body: SelectOtherPartsV2Dto,
|
||||
@CurrentUser() registrar: any,
|
||||
) {
|
||||
return this.claimRequestManagementService.selectOtherPartsV2(
|
||||
claimRequestId,
|
||||
body,
|
||||
registrar.sub,
|
||||
registrar,
|
||||
);
|
||||
}
|
||||
|
||||
@Get("capture-requirements/:claimRequestId")
|
||||
getCapture(@Param("claimRequestId") claimRequestId: string, @CurrentUser() registrar: any) {
|
||||
return this.claimRequestManagementService.getCaptureRequirementsV2(
|
||||
claimRequestId,
|
||||
registrar.sub,
|
||||
registrar,
|
||||
);
|
||||
}
|
||||
|
||||
@Post("upload-document/:claimRequestId")
|
||||
@ApiConsumes("multipart/form-data")
|
||||
@UseInterceptors(
|
||||
FileInterceptor("file", {
|
||||
limits: { fileSize: 10 * 1024 * 1024 },
|
||||
storage: diskStorage({
|
||||
destination: "./files/claim-documents",
|
||||
filename: (req, file, cb) => cb(null, `${Date.now()}${extname(file.originalname)}`),
|
||||
}),
|
||||
}),
|
||||
)
|
||||
@ApiOperation({ summary: "Registrar uploads required claim document" })
|
||||
uploadDoc(
|
||||
@Param("claimRequestId") claimRequestId: string,
|
||||
@Body() body: UploadRequiredDocumentV2Dto,
|
||||
@UploadedFile() file: Express.Multer.File,
|
||||
@CurrentUser() registrar: any,
|
||||
) {
|
||||
return this.claimRequestManagementService.uploadRequiredDocumentV2(
|
||||
claimRequestId,
|
||||
body,
|
||||
file,
|
||||
registrar.sub,
|
||||
registrar,
|
||||
);
|
||||
}
|
||||
|
||||
@Post("capture-part/:claimRequestId")
|
||||
@ApiConsumes("multipart/form-data")
|
||||
@UseInterceptors(
|
||||
FileInterceptor("file", {
|
||||
limits: { fileSize: 10 * 1024 * 1024 },
|
||||
storage: diskStorage({
|
||||
destination: "./files/claim-captures",
|
||||
filename: (req, file, cb) => cb(null, `${Date.now()}${extname(file.originalname)}`),
|
||||
}),
|
||||
}),
|
||||
)
|
||||
capturePart(
|
||||
@Param("claimRequestId") claimRequestId: string,
|
||||
@Body() body: CapturePartV2Dto,
|
||||
@UploadedFile() file: Express.Multer.File,
|
||||
@CurrentUser() registrar: any,
|
||||
) {
|
||||
return this.claimRequestManagementService.capturePartV2(
|
||||
claimRequestId,
|
||||
body,
|
||||
file,
|
||||
registrar.sub,
|
||||
registrar,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
10
src/request-management/dto/registrar-initiated.dto.ts
Normal file
10
src/request-management/dto/registrar-initiated.dto.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsIn, IsString } from "class-validator";
|
||||
|
||||
export class CreateRegistrarInitiatedFileDto {
|
||||
@ApiProperty({ enum: ["THIRD_PARTY", "CAR_BODY"], example: "THIRD_PARTY" })
|
||||
@IsString()
|
||||
@IsIn(["THIRD_PARTY", "CAR_BODY"])
|
||||
type: "THIRD_PARTY" | "CAR_BODY";
|
||||
}
|
||||
|
||||
@@ -81,6 +81,14 @@ export class BlameRequest {
|
||||
@Prop({ type: Types.ObjectId })
|
||||
initiatedByFieldExpertId?: Types.ObjectId;
|
||||
|
||||
/** True when this blame was created by a registrar. */
|
||||
@Prop({ default: false })
|
||||
registrarInitiated?: boolean;
|
||||
|
||||
/** Registrar who created this file (for registrar-initiated flows). */
|
||||
@Prop({ type: Types.ObjectId })
|
||||
initiatedByRegistrarId?: Types.ObjectId;
|
||||
|
||||
/** LINK = expert sends link to user(s); IN_PERSON = expert fills form on-site. */
|
||||
@Prop({ type: String, enum: CreationMethod })
|
||||
creationMethod?: CreationMethod;
|
||||
|
||||
@@ -282,6 +282,7 @@ export enum CreationMethod {
|
||||
export enum FilledBy {
|
||||
CUSTOMER = "customer",
|
||||
EXPERT = "expert",
|
||||
REGISTRAR = "registrar",
|
||||
}
|
||||
|
||||
export class ExpertLinkInfo {
|
||||
|
||||
155
src/request-management/registrar-initiated.controller.ts
Normal file
155
src/request-management/registrar-initiated.controller.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
import { extname } from "node:path";
|
||||
import { Body, Controller, Get, Param, Post, UploadedFile, UseGuards, UseInterceptors } from "@nestjs/common";
|
||||
import { FileInterceptor } from "@nestjs/platform-express";
|
||||
import { diskStorage } from "multer";
|
||||
import { ApiBearerAuth, ApiBody, ApiConsumes, ApiOperation, ApiParam, ApiTags } from "@nestjs/swagger";
|
||||
import { LocalActorAuthGuard } from "src/auth/guards/actor-local.guard";
|
||||
import { RolesGuard } from "src/auth/guards/role.guard";
|
||||
import { CurrentUser } from "src/decorators/user.decorator";
|
||||
import { Roles } from "src/decorators/roles.decorator";
|
||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
import { RequestManagementService } from "./request-management.service";
|
||||
import { CreateRegistrarInitiatedFileDto } from "./dto/registrar-initiated.dto";
|
||||
import { SendPartyOtpsDto } from "./dto/send-party-otps.dto";
|
||||
import { VerifyPartyOtpsDto } from "./dto/verify-party-otps.dto";
|
||||
import { ExpertAccidentFieldsDto } from "./dto/expert-accident-fields.dto";
|
||||
import { PartyRole } from "./entities/schema/partyRole.enum";
|
||||
|
||||
@ApiTags("registrar-initiated-blame (v1)")
|
||||
@Controller("registrar-initiated-blame")
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(LocalActorAuthGuard, RolesGuard)
|
||||
@Roles(RoleEnum.REGISTRAR)
|
||||
export class RegistrarInitiatedController {
|
||||
constructor(private readonly requestManagementService: RequestManagementService) {}
|
||||
|
||||
@Post("create")
|
||||
@ApiOperation({ summary: "Registrar creates IN_PERSON blame file" })
|
||||
@ApiBody({ type: CreateRegistrarInitiatedFileDto })
|
||||
create(@CurrentUser() registrar: any, @Body() dto: CreateRegistrarInitiatedFileDto) {
|
||||
return this.requestManagementService.createRegistrarInitiatedBlame(registrar, dto);
|
||||
}
|
||||
|
||||
@Get("my-files")
|
||||
myFiles(@CurrentUser() registrar: any) {
|
||||
return this.requestManagementService.getMyExpertInitiatedFilesV2(registrar);
|
||||
}
|
||||
|
||||
@Get("blame/:requestId")
|
||||
getBlame(@Param("requestId") requestId: string, @CurrentUser() registrar: any) {
|
||||
return this.requestManagementService.getBlameRequestV2(requestId, registrar);
|
||||
}
|
||||
|
||||
@Post("send-party-otps/:requestId")
|
||||
@ApiParam({ name: "requestId" })
|
||||
@ApiBody({ type: SendPartyOtpsDto })
|
||||
sendPartyOtps(
|
||||
@CurrentUser() registrar: any,
|
||||
@Param("requestId") requestId: string,
|
||||
@Body() dto: SendPartyOtpsDto,
|
||||
) {
|
||||
return this.requestManagementService.sendPartyOtpsV2(registrar, requestId, dto);
|
||||
}
|
||||
|
||||
@Post("verify-party-otps/:requestId")
|
||||
@ApiParam({ name: "requestId" })
|
||||
@ApiBody({ type: VerifyPartyOtpsDto })
|
||||
verifyPartyOtps(
|
||||
@CurrentUser() registrar: any,
|
||||
@Param("requestId") requestId: string,
|
||||
@Body() dto: VerifyPartyOtpsDto,
|
||||
) {
|
||||
return this.requestManagementService.verifyPartyOtpsV2(registrar, requestId, dto);
|
||||
}
|
||||
|
||||
@Post("complete-blame-data/:requestId")
|
||||
completeBlameData(
|
||||
@CurrentUser() registrar: any,
|
||||
@Param("requestId") requestId: string,
|
||||
@Body() body: any,
|
||||
) {
|
||||
return this.requestManagementService.expertCompleteBlameDataV2(registrar, requestId, body);
|
||||
}
|
||||
|
||||
@Post("upload-video/:requestId")
|
||||
@ApiConsumes("multipart/form-data")
|
||||
@UseInterceptors(
|
||||
FileInterceptor("file", {
|
||||
limits: { fileSize: 20 * 1024 * 1024 },
|
||||
storage: diskStorage({
|
||||
destination: "./files/video",
|
||||
filename: (req, file, cb) => cb(null, `registrar-${Date.now()}${extname(file.originalname)}`),
|
||||
}),
|
||||
}),
|
||||
)
|
||||
uploadVideo(
|
||||
@CurrentUser() registrar: any,
|
||||
@Param("requestId") requestId: string,
|
||||
@UploadedFile() file?: Express.Multer.File,
|
||||
) {
|
||||
return this.requestManagementService.expertUploadVideoForBlameV2(registrar, requestId, file);
|
||||
}
|
||||
|
||||
@Post("upload-voice/:requestId")
|
||||
@ApiConsumes("multipart/form-data")
|
||||
@UseInterceptors(
|
||||
FileInterceptor("voice", {
|
||||
limits: { fileSize: 10 * 1024 * 1024 },
|
||||
storage: diskStorage({
|
||||
destination: "./files/voice",
|
||||
filename: (req, file, cb) => cb(null, `registrar-${Date.now()}${extname(file.originalname)}`),
|
||||
}),
|
||||
}),
|
||||
)
|
||||
uploadVoice(
|
||||
@CurrentUser() registrar: any,
|
||||
@Param("requestId") requestId: string,
|
||||
@UploadedFile() voice?: Express.Multer.File,
|
||||
) {
|
||||
return this.requestManagementService.expertUploadVoiceForBlameV2(registrar, requestId, voice);
|
||||
}
|
||||
|
||||
@Post("add-accident-fields/:requestId")
|
||||
@ApiBody({ type: ExpertAccidentFieldsDto })
|
||||
addAccidentFields(
|
||||
@CurrentUser() registrar: any,
|
||||
@Param("requestId") requestId: string,
|
||||
@Body() fields: ExpertAccidentFieldsDto,
|
||||
) {
|
||||
return this.requestManagementService.expertAddAccidentFieldsForBlameV2(
|
||||
registrar,
|
||||
requestId,
|
||||
fields,
|
||||
);
|
||||
}
|
||||
|
||||
@Post("upload-party-signature/:requestId")
|
||||
@ApiConsumes("multipart/form-data")
|
||||
@UseInterceptors(
|
||||
FileInterceptor("sign", {
|
||||
limits: { fileSize: 10 * 1024 * 1024 },
|
||||
storage: diskStorage({
|
||||
destination: "./files/signs",
|
||||
filename: (req, file, cb) => cb(null, `registrar-party-${Date.now()}${extname(file.originalname)}`),
|
||||
}),
|
||||
}),
|
||||
)
|
||||
uploadPartySignature(
|
||||
@CurrentUser() registrar: any,
|
||||
@Param("requestId") requestId: string,
|
||||
@Body() body: { partyRole?: string; isAccept?: boolean | string },
|
||||
@UploadedFile() sign?: Express.Multer.File,
|
||||
) {
|
||||
const role =
|
||||
body.partyRole === "SECOND" ? PartyRole.SECOND : PartyRole.FIRST;
|
||||
const isAccept = !(body.isAccept === false || body.isAccept === "false");
|
||||
return this.requestManagementService.expertUploadPartySignatureV2(
|
||||
registrar,
|
||||
requestId,
|
||||
role,
|
||||
isAccept,
|
||||
sign!,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import { RequestManagementController } from "./request-management.controller";
|
||||
import { RequestManagementV2Controller } from "./request-management.v2.controller";
|
||||
import { ExpertInitiatedController } from "./expert-initiated.controller";
|
||||
import { ExpertInitiatedV2Controller } from "./expert-initiated.v2.controller";
|
||||
import { RegistrarInitiatedController } from "./registrar-initiated.controller";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -69,6 +70,7 @@ import { ExpertInitiatedV2Controller } from "./expert-initiated.v2.controller";
|
||||
RequestManagementV2Controller,
|
||||
ExpertInitiatedController,
|
||||
ExpertInitiatedV2Controller,
|
||||
RegistrarInitiatedController,
|
||||
],
|
||||
providers: [
|
||||
RequestManagementService,
|
||||
|
||||
@@ -103,7 +103,7 @@ export class RequestManagementService {
|
||||
}
|
||||
|
||||
private async advanceWorkflowToNext(req: any, submittedStepKey: WorkflowStep) {
|
||||
// CAR_BODY: after first-party description there is no second party or expert; go to COMPLETED
|
||||
// CAR_BODY: after first-party description, move to signature step.
|
||||
if (
|
||||
req.type === BlameRequestType.CAR_BODY &&
|
||||
submittedStepKey === WorkflowStep.FIRST_DESCRIPTION
|
||||
@@ -113,11 +113,11 @@ export class RequestManagementService {
|
||||
: [];
|
||||
if (!completed.includes(submittedStepKey)) completed.push(submittedStepKey);
|
||||
req.workflow.completedSteps = completed;
|
||||
req.workflow.currentStep = WorkflowStep.COMPLETED;
|
||||
req.workflow.currentStep = WorkflowStep.WAITING_FOR_SIGNATURES;
|
||||
req.workflow.nextStep = undefined;
|
||||
req.status = CaseStatus.COMPLETED;
|
||||
req.status = CaseStatus.WAITING_FOR_SIGNATURES;
|
||||
this.logger.debug(
|
||||
"[WORKFLOW] CAR_BODY: advanced to COMPLETED after FIRST_DESCRIPTION",
|
||||
"[WORKFLOW] CAR_BODY: advanced to WAITING_FOR_SIGNATURES after FIRST_DESCRIPTION",
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -3234,16 +3234,86 @@ export class RequestManagementService {
|
||||
* Verify the current user (field expert) can access this BlameRequest (v2).
|
||||
*/
|
||||
private verifyExpertAccessForBlameV2(req: any, expert: any): void {
|
||||
if (!req?.expertInitiated || !req?.initiatedByFieldExpertId) {
|
||||
throw new ForbiddenException(
|
||||
"This file is not expert-initiated or has no initiating expert",
|
||||
if (req?.expertInitiated && req?.initiatedByFieldExpertId) {
|
||||
if (String(req.initiatedByFieldExpertId) !== String(expert?.sub)) {
|
||||
throw new ForbiddenException(
|
||||
"You can only access files that you have initiated",
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (req?.registrarInitiated && req?.initiatedByRegistrarId) {
|
||||
if (String(req.initiatedByRegistrarId) !== String(expert?.sub)) {
|
||||
throw new ForbiddenException(
|
||||
"You can only access files that you have initiated",
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
throw new ForbiddenException(
|
||||
"This file is not initiator-scoped or has no initiating actor",
|
||||
);
|
||||
}
|
||||
|
||||
async createRegistrarInitiatedBlame(
|
||||
registrar: any,
|
||||
dto: { type: "THIRD_PARTY" | "CAR_BODY" },
|
||||
): Promise<{ requestId: string; publicId: string }> {
|
||||
const registrarId = new Types.ObjectId(registrar.sub);
|
||||
const type =
|
||||
dto.type === "CAR_BODY"
|
||||
? BlameRequestType.CAR_BODY
|
||||
: BlameRequestType.THIRD_PARTY;
|
||||
const firstStep = await this.getWorkflowStep({ stepNumber: 1 });
|
||||
if (!firstStep?.stepKey) {
|
||||
throw new InternalServerErrorException(
|
||||
"Workflow stepNumber=1 not configured in step manager",
|
||||
);
|
||||
}
|
||||
if (String(req.initiatedByFieldExpertId) !== String(expert?.sub)) {
|
||||
throw new ForbiddenException(
|
||||
"You can only access files that you have initiated",
|
||||
const nextStepFromManager = firstStep.nextPossibleSteps?.[0];
|
||||
const nextStep =
|
||||
type === BlameRequestType.CAR_BODY
|
||||
? (WorkflowStep.CAR_BODY_ACCIDENT_TYPE as WorkflowStep)
|
||||
: (nextStepFromManager as WorkflowStep);
|
||||
if (!nextStep) {
|
||||
throw new InternalServerErrorException(
|
||||
"Workflow stepNumber=1 has no nextPossibleSteps configured",
|
||||
);
|
||||
}
|
||||
const publicId = await this.publicIdService.generateRequestPublicId();
|
||||
const parties: any[] = [{ role: PartyRole.FIRST, person: {} }];
|
||||
if (type === BlameRequestType.THIRD_PARTY) {
|
||||
parties.push({ role: PartyRole.SECOND, person: {} });
|
||||
}
|
||||
const created = await this.blameRequestDbService.create({
|
||||
publicId,
|
||||
requestNo: publicId,
|
||||
type,
|
||||
parties,
|
||||
workflow: {
|
||||
currentStep: firstStep.stepKey as WorkflowStep,
|
||||
nextStep,
|
||||
completedSteps: [firstStep.stepKey as WorkflowStep],
|
||||
locked: false,
|
||||
},
|
||||
history: [],
|
||||
registrarInitiated: true,
|
||||
initiatedByRegistrarId: registrarId,
|
||||
creationMethod: CreationMethod.IN_PERSON,
|
||||
filledBy: FilledBy.REGISTRAR,
|
||||
} as any);
|
||||
if (!Array.isArray((created as any).history)) (created as any).history = [];
|
||||
(created as any).history.push({
|
||||
type: "FILE_CREATED_BY_REGISTRAR",
|
||||
actor: {
|
||||
actorId: registrarId,
|
||||
actorName: `${registrar.firstName || ""} ${registrar.lastName || ""}`.trim(),
|
||||
actorType: "registrar",
|
||||
},
|
||||
metadata: { creationMethod: CreationMethod.IN_PERSON, type: dto.type },
|
||||
});
|
||||
await (created as any).save();
|
||||
return { requestId: String((created as any)._id), publicId: (created as any).publicId };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3596,7 +3666,10 @@ export class RequestManagementService {
|
||||
): Promise<{ sent: boolean; message: string }> {
|
||||
const req = await this.blameRequestDbService.findById(requestId);
|
||||
if (!req) throw new NotFoundException("Request not found");
|
||||
if (!req.expertInitiated || req.creationMethod !== CreationMethod.IN_PERSON) {
|
||||
if (
|
||||
(!req.expertInitiated && !req.registrarInitiated) ||
|
||||
req.creationMethod !== CreationMethod.IN_PERSON
|
||||
) {
|
||||
throw new BadRequestException(
|
||||
"This endpoint is only for expert-initiated IN_PERSON blame files.",
|
||||
);
|
||||
@@ -3657,7 +3730,10 @@ export class RequestManagementService {
|
||||
): Promise<{ verified: boolean; message: string }> {
|
||||
const req = await this.blameRequestDbService.findById(requestId);
|
||||
if (!req) throw new NotFoundException("Request not found");
|
||||
if (!req.expertInitiated || req.creationMethod !== CreationMethod.IN_PERSON) {
|
||||
if (
|
||||
(!req.expertInitiated && !req.registrarInitiated) ||
|
||||
req.creationMethod !== CreationMethod.IN_PERSON
|
||||
) {
|
||||
throw new BadRequestException(
|
||||
"This endpoint is only for expert-initiated IN_PERSON blame files.",
|
||||
);
|
||||
@@ -3712,7 +3788,7 @@ export class RequestManagementService {
|
||||
actor: {
|
||||
actorId: new Types.ObjectId(expert.sub),
|
||||
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
||||
actorType: "field_expert",
|
||||
actorType: expert?.role === RoleEnum.REGISTRAR ? "registrar" : "field_expert",
|
||||
},
|
||||
metadata: {
|
||||
firstPartyVerified: true,
|
||||
@@ -3759,8 +3835,10 @@ export class RequestManagementService {
|
||||
async getMyExpertInitiatedFilesV2(expert: any): Promise<any[]> {
|
||||
const expertId = new Types.ObjectId(expert.sub);
|
||||
const files = await this.blameRequestDbService.find({
|
||||
expertInitiated: true,
|
||||
initiatedByFieldExpertId: expertId,
|
||||
$or: [
|
||||
{ expertInitiated: true, initiatedByFieldExpertId: expertId },
|
||||
{ registrarInitiated: true, initiatedByRegistrarId: expertId },
|
||||
],
|
||||
});
|
||||
return (files || []).map((f: any) => ({
|
||||
_id: f._id,
|
||||
@@ -3790,16 +3868,20 @@ export class RequestManagementService {
|
||||
req.expertInitiated &&
|
||||
req.initiatedByFieldExpertId &&
|
||||
String(req.initiatedByFieldExpertId) === String(user?.sub);
|
||||
const isRegistrarOwner =
|
||||
req.registrarInitiated &&
|
||||
req.initiatedByRegistrarId &&
|
||||
String(req.initiatedByRegistrarId) === String(user?.sub);
|
||||
const isParty = Array.isArray(req.parties) && req.parties.some((p: any) => {
|
||||
const pid = p?.person?.userId ? String(p.person.userId) : null;
|
||||
const phone = p?.person?.phoneNumber;
|
||||
return (pid && pid === String(user?.sub)) || (phone && phone === user?.username);
|
||||
});
|
||||
if (!isFieldExpertOwner && !isParty) {
|
||||
if (!isFieldExpertOwner && !isRegistrarOwner && !isParty) {
|
||||
throw new ForbiddenException("You do not have access to this request");
|
||||
}
|
||||
// Optionally bind userId to party when user opens via LINK (phone match, no userId yet)
|
||||
if (!isFieldExpertOwner && user?.sub && Types.ObjectId.isValid(user.sub)) {
|
||||
if (!isFieldExpertOwner && !isRegistrarOwner && user?.sub && Types.ObjectId.isValid(user.sub)) {
|
||||
let updated = false;
|
||||
for (const p of req.parties || []) {
|
||||
if (
|
||||
@@ -3862,7 +3944,10 @@ export class RequestManagementService {
|
||||
if (!req) {
|
||||
throw new NotFoundException("Request not found");
|
||||
}
|
||||
if (!req.expertInitiated || req.creationMethod !== CreationMethod.IN_PERSON) {
|
||||
if (
|
||||
(!req.expertInitiated && !req.registrarInitiated) ||
|
||||
req.creationMethod !== CreationMethod.IN_PERSON
|
||||
) {
|
||||
throw new BadRequestException(
|
||||
"This endpoint is only for expert-initiated IN_PERSON BlameRequest files.",
|
||||
);
|
||||
@@ -3890,7 +3975,10 @@ export class RequestManagementService {
|
||||
): Promise<any> {
|
||||
const req = await this.blameRequestDbService.findById(requestId);
|
||||
if (!req) throw new NotFoundException("Request not found");
|
||||
if (!req.expertInitiated || req.creationMethod !== CreationMethod.IN_PERSON) {
|
||||
if (
|
||||
(!req.expertInitiated && !req.registrarInitiated) ||
|
||||
req.creationMethod !== CreationMethod.IN_PERSON
|
||||
) {
|
||||
throw new BadRequestException("Only expert-initiated IN_PERSON CAR_BODY files.");
|
||||
}
|
||||
if (req.type !== BlameRequestType.CAR_BODY) {
|
||||
@@ -4193,7 +4281,7 @@ export class RequestManagementService {
|
||||
|
||||
const req = await this.blameRequestDbService.findById(requestId);
|
||||
if (!req) throw new NotFoundException("Request not found");
|
||||
if (!req.expertInitiated) {
|
||||
if (!req.expertInitiated && !req.registrarInitiated) {
|
||||
throw new BadRequestException("This endpoint is only for expert-initiated files.");
|
||||
}
|
||||
this.verifyExpertAccessForBlameV2(req, expert);
|
||||
|
||||
40
src/users/entities/db-service/registrar.db.service.ts
Normal file
40
src/users/entities/db-service/registrar.db.service.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { InjectModel } from "@nestjs/mongoose";
|
||||
import { FilterQuery, Model, Types } from "mongoose";
|
||||
import { RegistrarModel } from "../schema/registrar.schema";
|
||||
|
||||
@Injectable()
|
||||
export class RegistrarDbService {
|
||||
constructor(
|
||||
@InjectModel(RegistrarModel.name)
|
||||
private readonly registrarModel: Model<RegistrarModel>,
|
||||
) {}
|
||||
|
||||
async create(
|
||||
user: Partial<RegistrarModel> & { password: string },
|
||||
): Promise<RegistrarModel> {
|
||||
return this.registrarModel.create(user);
|
||||
}
|
||||
|
||||
async findOne(
|
||||
filter: FilterQuery<RegistrarModel>,
|
||||
): Promise<RegistrarModel | null> {
|
||||
return this.registrarModel.findOne(filter);
|
||||
}
|
||||
|
||||
async findOneAndUpdate(
|
||||
filter: FilterQuery<RegistrarModel>,
|
||||
update: any,
|
||||
): Promise<RegistrarModel | null> {
|
||||
return this.registrarModel.findOneAndUpdate(filter, update, { new: true });
|
||||
}
|
||||
|
||||
async updateOne(filter: FilterQuery<RegistrarModel>, update: any) {
|
||||
return this.registrarModel.updateOne(filter, update);
|
||||
}
|
||||
|
||||
async findById(userId: string): Promise<RegistrarModel | null> {
|
||||
return this.registrarModel.findOne({ _id: new Types.ObjectId(userId) });
|
||||
}
|
||||
}
|
||||
|
||||
38
src/users/entities/schema/registrar.schema.ts
Normal file
38
src/users/entities/schema/registrar.schema.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
|
||||
import { Types } from "mongoose";
|
||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
|
||||
@Schema({
|
||||
collection: "registrar",
|
||||
versionKey: false,
|
||||
timestamps: true,
|
||||
})
|
||||
export class RegistrarModel {
|
||||
_id?: Types.ObjectId;
|
||||
|
||||
@Prop({ type: "string", unique: true })
|
||||
email: string;
|
||||
|
||||
@Prop({ type: "string" })
|
||||
username: string;
|
||||
|
||||
@Prop({ required: true })
|
||||
password: string;
|
||||
|
||||
@Prop({ type: Types.ObjectId, required: true, index: true })
|
||||
clientKey: Types.ObjectId;
|
||||
|
||||
@Prop({ default: RoleEnum.REGISTRAR })
|
||||
role: RoleEnum;
|
||||
|
||||
@Prop({ type: "string", default: "" })
|
||||
otp: string;
|
||||
}
|
||||
|
||||
export const RegistrarDbSchema = SchemaFactory.createForClass(RegistrarModel);
|
||||
|
||||
RegistrarDbSchema.pre("save", function (next) {
|
||||
if (!this.username) this.username = this.email;
|
||||
next();
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ 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 { RegistrarDbService } from "./entities/db-service/registrar.db.service";
|
||||
import { UserDbService } from "./entities/db-service/user.db.service";
|
||||
import {
|
||||
DamageExpertDbSchema,
|
||||
@@ -21,6 +22,10 @@ import {
|
||||
InsurerExpertDbSchema,
|
||||
InsurerExpertModel,
|
||||
} from "./entities/schema/insurer-expert.schema";
|
||||
import {
|
||||
RegistrarDbSchema,
|
||||
RegistrarModel,
|
||||
} from "./entities/schema/registrar.schema";
|
||||
import { UserModel, UserDbSchema } from "./entities/schema/user.schema";
|
||||
|
||||
@Module({
|
||||
@@ -31,6 +36,7 @@ import { UserModel, UserDbSchema } from "./entities/schema/user.schema";
|
||||
{ name: ExpertModel.name, schema: ExpertDbSchema },
|
||||
{ name: FieldExpertModel.name, schema: FieldExpertDbSchema },
|
||||
{ name: InsurerExpertModel.name, schema: InsurerExpertDbSchema },
|
||||
{ name: RegistrarModel.name, schema: RegistrarDbSchema },
|
||||
]),
|
||||
OtpModule,
|
||||
HashModule,
|
||||
@@ -42,6 +48,7 @@ import { UserModel, UserDbSchema } from "./entities/schema/user.schema";
|
||||
DamageExpertDbService,
|
||||
FieldExpertDbService,
|
||||
InsurerExpertDbService,
|
||||
RegistrarDbService,
|
||||
],
|
||||
exports: [
|
||||
UserDbService,
|
||||
@@ -49,6 +56,7 @@ import { UserModel, UserDbSchema } from "./entities/schema/user.schema";
|
||||
DamageExpertDbService,
|
||||
FieldExpertDbService,
|
||||
InsurerExpertDbService,
|
||||
RegistrarDbService,
|
||||
],
|
||||
})
|
||||
export class UsersModule {}
|
||||
|
||||
Reference in New Issue
Block a user