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",
|
EXPERT = "expert",
|
||||||
DAMAGE_EXPERT = "damage_expert",
|
DAMAGE_EXPERT = "damage_expert",
|
||||||
FIELD_EXPERT = "field_expert",
|
FIELD_EXPERT = "field_expert",
|
||||||
|
REGISTRAR = "registrar",
|
||||||
COMPANY = "company",
|
COMPANY = "company",
|
||||||
ADMIN = "admin",
|
ADMIN = "admin",
|
||||||
USER = "user",
|
USER = "user",
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
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 { CreateFieldExpertDto } from "src/auth/dto/actor/create-field-expert.actor.dto";
|
||||||
|
import { CreateRegistrarDto } from "src/auth/dto/actor/create-registrar.actor.dto";
|
||||||
import {
|
import {
|
||||||
GenuineRegisterDto,
|
GenuineRegisterDto,
|
||||||
InsurerRegisterDto,
|
InsurerRegisterDto,
|
||||||
@@ -64,6 +65,14 @@ export class ActorAuthController {
|
|||||||
return await this.actorAuthService.createFieldExpertMock(body);
|
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)
|
@UseGuards(LocalActorAuthGuard)
|
||||||
@Post("login")
|
@Post("login")
|
||||||
@Roles()
|
@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 { 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 { 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 { 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";
|
||||||
@@ -46,6 +47,7 @@ export class ActorAuthService {
|
|||||||
private readonly expertDbService: ExpertDbService,
|
private readonly expertDbService: ExpertDbService,
|
||||||
private readonly damageExpertDbService: DamageExpertDbService,
|
private readonly damageExpertDbService: DamageExpertDbService,
|
||||||
private readonly fieldExpertDbService: FieldExpertDbService,
|
private readonly fieldExpertDbService: FieldExpertDbService,
|
||||||
|
private readonly registrarDbService: RegistrarDbService,
|
||||||
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,
|
||||||
@@ -79,6 +81,13 @@ export class ActorAuthService {
|
|||||||
else
|
else
|
||||||
res = await this.fieldExpertDbService.findOne({ email: username });
|
res = await this.fieldExpertDbService.findOne({ email: username });
|
||||||
break;
|
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:
|
case RoleEnum.COMPANY:
|
||||||
res = await this.insurerExpertDbService.findOne({ email: username });
|
res = await this.insurerExpertDbService.findOne({ email: username });
|
||||||
break;
|
break;
|
||||||
@@ -273,6 +282,10 @@ export class ActorAuthService {
|
|||||||
actor = await this.fieldExpertDbService.findOne(filter);
|
actor = await this.fieldExpertDbService.findOne(filter);
|
||||||
dbServiceToUpdate = this.fieldExpertDbService as any;
|
dbServiceToUpdate = this.fieldExpertDbService as any;
|
||||||
}
|
}
|
||||||
|
if (!actor) {
|
||||||
|
actor = await this.registrarDbService.findOne(filter);
|
||||||
|
dbServiceToUpdate = this.registrarDbService as any;
|
||||||
|
}
|
||||||
|
|
||||||
if (!actor) {
|
if (!actor) {
|
||||||
throw new NotFoundException("actor not found");
|
throw new NotFoundException("actor not found");
|
||||||
@@ -309,6 +322,10 @@ export class ActorAuthService {
|
|||||||
userExist = await this.fieldExpertDbService.findOne({ email });
|
userExist = await this.fieldExpertDbService.findOne({ email });
|
||||||
dbServiceToUpdate = this.fieldExpertDbService;
|
dbServiceToUpdate = this.fieldExpertDbService;
|
||||||
}
|
}
|
||||||
|
if (!userExist) {
|
||||||
|
userExist = await this.registrarDbService.findOne({ email });
|
||||||
|
dbServiceToUpdate = this.registrarDbService as any;
|
||||||
|
}
|
||||||
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");
|
||||||
@@ -383,6 +400,7 @@ export class ActorAuthService {
|
|||||||
"phone",
|
"phone",
|
||||||
"mobile",
|
"mobile",
|
||||||
],
|
],
|
||||||
|
registrar: ["email"],
|
||||||
};
|
};
|
||||||
|
|
||||||
const allowedFields = allowedFieldsByRole[role];
|
const allowedFields = allowedFieldsByRole[role];
|
||||||
@@ -498,4 +516,34 @@ export class ActorAuthService {
|
|||||||
throw er;
|
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.DAMAGE_EXPERT,
|
||||||
RoleEnum.COMPANY,
|
RoleEnum.COMPANY,
|
||||||
RoleEnum.FIELD_EXPERT,
|
RoleEnum.FIELD_EXPERT,
|
||||||
|
RoleEnum.REGISTRAR,
|
||||||
].includes(payload.role)
|
].includes(payload.role)
|
||||||
) {
|
) {
|
||||||
throw new UnauthorizedException("User role is not authorized");
|
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 { UsersModule } from "src/users/users.module";
|
||||||
import { ClaimRequestManagementController } from "./claim-request-management.controller";
|
import { ClaimRequestManagementController } from "./claim-request-management.controller";
|
||||||
import { ClaimRequestManagementV2Controller } from "./claim-request-management.v2.controller";
|
import { ClaimRequestManagementV2Controller } from "./claim-request-management.v2.controller";
|
||||||
|
import { RegistrarClaimV1Controller } from "./registrar-claim.v1.controller";
|
||||||
import { ClaimRequestManagementService } from "./claim-request-management.service";
|
import { ClaimRequestManagementService } from "./claim-request-management.service";
|
||||||
import { CarGreenCardDbService } from "./entites/db-service/car-green-card.db.service";
|
import { CarGreenCardDbService } from "./entites/db-service/car-green-card.db.service";
|
||||||
import { ClaimRequestManagementDbService } from "./entites/db-service/claim-request-management.db.service";
|
import { ClaimRequestManagementDbService } from "./entites/db-service/claim-request-management.db.service";
|
||||||
@@ -85,7 +86,11 @@ import { JwtModule } from "@nestjs/jwt";
|
|||||||
ClaimRequiredDocumentDbService,
|
ClaimRequiredDocumentDbService,
|
||||||
ClaimAccessGuard,
|
ClaimAccessGuard,
|
||||||
],
|
],
|
||||||
controllers: [ClaimRequestManagementController, ClaimRequestManagementV2Controller],
|
controllers: [
|
||||||
|
ClaimRequestManagementController,
|
||||||
|
ClaimRequestManagementV2Controller,
|
||||||
|
RegistrarClaimV1Controller,
|
||||||
|
],
|
||||||
exports: [
|
exports: [
|
||||||
ClaimRequestManagementService,
|
ClaimRequestManagementService,
|
||||||
ClaimRequestManagementDbService,
|
ClaimRequestManagementDbService,
|
||||||
|
|||||||
@@ -2996,25 +2996,130 @@ export class ClaimRequestManagementService {
|
|||||||
currentUserId: string,
|
currentUserId: string,
|
||||||
actorRole?: string,
|
actorRole?: string,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
if (actorRole !== RoleEnum.FIELD_EXPERT || !claimCase?.blameRequestId) {
|
if (
|
||||||
|
![RoleEnum.FIELD_EXPERT, RoleEnum.REGISTRAR].includes(actorRole as any) ||
|
||||||
|
!claimCase?.blameRequestId
|
||||||
|
) {
|
||||||
return currentUserId;
|
return currentUserId;
|
||||||
}
|
}
|
||||||
const blameRequest = await this.blameRequestDbService.findById(
|
const blameRequest = await this.blameRequestDbService.findById(
|
||||||
claimCase.blameRequestId.toString(),
|
claimCase.blameRequestId.toString(),
|
||||||
);
|
);
|
||||||
if (
|
if (!blameRequest || blameRequest.creationMethod !== "IN_PERSON") return currentUserId;
|
||||||
!blameRequest?.expertInitiated ||
|
if (actorRole === RoleEnum.FIELD_EXPERT) {
|
||||||
blameRequest.creationMethod !== "IN_PERSON" ||
|
if (
|
||||||
!blameRequest.initiatedByFieldExpertId
|
!blameRequest.expertInitiated ||
|
||||||
) {
|
!blameRequest.initiatedByFieldExpertId ||
|
||||||
return currentUserId;
|
String(blameRequest.initiatedByFieldExpertId) !== currentUserId
|
||||||
}
|
) {
|
||||||
if (String(blameRequest.initiatedByFieldExpertId) !== currentUserId) {
|
return currentUserId;
|
||||||
return currentUserId;
|
}
|
||||||
|
} else {
|
||||||
|
if (
|
||||||
|
!blameRequest.registrarInitiated ||
|
||||||
|
!blameRequest.initiatedByRegistrarId ||
|
||||||
|
String(blameRequest.initiatedByRegistrarId) !== currentUserId
|
||||||
|
) {
|
||||||
|
return currentUserId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return claimCase.owner?.userId ? String(claimCase.owner.userId) : 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)
|
* 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: [] })
|
@Prop({ type: [HistoryEventSchema], default: [] })
|
||||||
history?: HistoryEvent[];
|
history?: HistoryEvent[];
|
||||||
|
|
||||||
|
@Prop({ type: Types.ObjectId, index: true })
|
||||||
|
createdByRegistrarId?: Types.ObjectId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Legacy fields kept optional to simplify progressive migration.
|
* Legacy fields kept optional to simplify progressive migration.
|
||||||
* If you choose to migrate later, we can remove these.
|
* 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 })
|
@Prop({ type: Types.ObjectId })
|
||||||
initiatedByFieldExpertId?: 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. */
|
/** LINK = expert sends link to user(s); IN_PERSON = expert fills form on-site. */
|
||||||
@Prop({ type: String, enum: CreationMethod })
|
@Prop({ type: String, enum: CreationMethod })
|
||||||
creationMethod?: CreationMethod;
|
creationMethod?: CreationMethod;
|
||||||
|
|||||||
@@ -282,6 +282,7 @@ export enum CreationMethod {
|
|||||||
export enum FilledBy {
|
export enum FilledBy {
|
||||||
CUSTOMER = "customer",
|
CUSTOMER = "customer",
|
||||||
EXPERT = "expert",
|
EXPERT = "expert",
|
||||||
|
REGISTRAR = "registrar",
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ExpertLinkInfo {
|
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 { RequestManagementV2Controller } from "./request-management.v2.controller";
|
||||||
import { ExpertInitiatedController } from "./expert-initiated.controller";
|
import { ExpertInitiatedController } from "./expert-initiated.controller";
|
||||||
import { ExpertInitiatedV2Controller } from "./expert-initiated.v2.controller";
|
import { ExpertInitiatedV2Controller } from "./expert-initiated.v2.controller";
|
||||||
|
import { RegistrarInitiatedController } from "./registrar-initiated.controller";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -69,6 +70,7 @@ import { ExpertInitiatedV2Controller } from "./expert-initiated.v2.controller";
|
|||||||
RequestManagementV2Controller,
|
RequestManagementV2Controller,
|
||||||
ExpertInitiatedController,
|
ExpertInitiatedController,
|
||||||
ExpertInitiatedV2Controller,
|
ExpertInitiatedV2Controller,
|
||||||
|
RegistrarInitiatedController,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
RequestManagementService,
|
RequestManagementService,
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ export class RequestManagementService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async advanceWorkflowToNext(req: any, submittedStepKey: WorkflowStep) {
|
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 (
|
if (
|
||||||
req.type === BlameRequestType.CAR_BODY &&
|
req.type === BlameRequestType.CAR_BODY &&
|
||||||
submittedStepKey === WorkflowStep.FIRST_DESCRIPTION
|
submittedStepKey === WorkflowStep.FIRST_DESCRIPTION
|
||||||
@@ -113,11 +113,11 @@ export class RequestManagementService {
|
|||||||
: [];
|
: [];
|
||||||
if (!completed.includes(submittedStepKey)) completed.push(submittedStepKey);
|
if (!completed.includes(submittedStepKey)) completed.push(submittedStepKey);
|
||||||
req.workflow.completedSteps = completed;
|
req.workflow.completedSteps = completed;
|
||||||
req.workflow.currentStep = WorkflowStep.COMPLETED;
|
req.workflow.currentStep = WorkflowStep.WAITING_FOR_SIGNATURES;
|
||||||
req.workflow.nextStep = undefined;
|
req.workflow.nextStep = undefined;
|
||||||
req.status = CaseStatus.COMPLETED;
|
req.status = CaseStatus.WAITING_FOR_SIGNATURES;
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
"[WORKFLOW] CAR_BODY: advanced to COMPLETED after FIRST_DESCRIPTION",
|
"[WORKFLOW] CAR_BODY: advanced to WAITING_FOR_SIGNATURES after FIRST_DESCRIPTION",
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -3234,16 +3234,86 @@ export class RequestManagementService {
|
|||||||
* Verify the current user (field expert) can access this BlameRequest (v2).
|
* Verify the current user (field expert) can access this BlameRequest (v2).
|
||||||
*/
|
*/
|
||||||
private verifyExpertAccessForBlameV2(req: any, expert: any): void {
|
private verifyExpertAccessForBlameV2(req: any, expert: any): void {
|
||||||
if (!req?.expertInitiated || !req?.initiatedByFieldExpertId) {
|
if (req?.expertInitiated && req?.initiatedByFieldExpertId) {
|
||||||
throw new ForbiddenException(
|
if (String(req.initiatedByFieldExpertId) !== String(expert?.sub)) {
|
||||||
"This file is not expert-initiated or has no initiating expert",
|
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)) {
|
const nextStepFromManager = firstStep.nextPossibleSteps?.[0];
|
||||||
throw new ForbiddenException(
|
const nextStep =
|
||||||
"You can only access files that you have initiated",
|
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 }> {
|
): Promise<{ sent: boolean; message: string }> {
|
||||||
const req = await this.blameRequestDbService.findById(requestId);
|
const req = await this.blameRequestDbService.findById(requestId);
|
||||||
if (!req) throw new NotFoundException("Request not found");
|
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(
|
throw new BadRequestException(
|
||||||
"This endpoint is only for expert-initiated IN_PERSON blame files.",
|
"This endpoint is only for expert-initiated IN_PERSON blame files.",
|
||||||
);
|
);
|
||||||
@@ -3657,7 +3730,10 @@ export class RequestManagementService {
|
|||||||
): Promise<{ verified: boolean; message: string }> {
|
): Promise<{ verified: boolean; message: string }> {
|
||||||
const req = await this.blameRequestDbService.findById(requestId);
|
const req = await this.blameRequestDbService.findById(requestId);
|
||||||
if (!req) throw new NotFoundException("Request not found");
|
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(
|
throw new BadRequestException(
|
||||||
"This endpoint is only for expert-initiated IN_PERSON blame files.",
|
"This endpoint is only for expert-initiated IN_PERSON blame files.",
|
||||||
);
|
);
|
||||||
@@ -3712,7 +3788,7 @@ export class RequestManagementService {
|
|||||||
actor: {
|
actor: {
|
||||||
actorId: new Types.ObjectId(expert.sub),
|
actorId: new Types.ObjectId(expert.sub),
|
||||||
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
||||||
actorType: "field_expert",
|
actorType: expert?.role === RoleEnum.REGISTRAR ? "registrar" : "field_expert",
|
||||||
},
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
firstPartyVerified: true,
|
firstPartyVerified: true,
|
||||||
@@ -3759,8 +3835,10 @@ export class RequestManagementService {
|
|||||||
async getMyExpertInitiatedFilesV2(expert: any): Promise<any[]> {
|
async getMyExpertInitiatedFilesV2(expert: any): Promise<any[]> {
|
||||||
const expertId = new Types.ObjectId(expert.sub);
|
const expertId = new Types.ObjectId(expert.sub);
|
||||||
const files = await this.blameRequestDbService.find({
|
const files = await this.blameRequestDbService.find({
|
||||||
expertInitiated: true,
|
$or: [
|
||||||
initiatedByFieldExpertId: expertId,
|
{ expertInitiated: true, initiatedByFieldExpertId: expertId },
|
||||||
|
{ registrarInitiated: true, initiatedByRegistrarId: expertId },
|
||||||
|
],
|
||||||
});
|
});
|
||||||
return (files || []).map((f: any) => ({
|
return (files || []).map((f: any) => ({
|
||||||
_id: f._id,
|
_id: f._id,
|
||||||
@@ -3790,16 +3868,20 @@ export class RequestManagementService {
|
|||||||
req.expertInitiated &&
|
req.expertInitiated &&
|
||||||
req.initiatedByFieldExpertId &&
|
req.initiatedByFieldExpertId &&
|
||||||
String(req.initiatedByFieldExpertId) === String(user?.sub);
|
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 isParty = Array.isArray(req.parties) && req.parties.some((p: any) => {
|
||||||
const pid = p?.person?.userId ? String(p.person.userId) : null;
|
const pid = p?.person?.userId ? String(p.person.userId) : null;
|
||||||
const phone = p?.person?.phoneNumber;
|
const phone = p?.person?.phoneNumber;
|
||||||
return (pid && pid === String(user?.sub)) || (phone && phone === user?.username);
|
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");
|
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)
|
// 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;
|
let updated = false;
|
||||||
for (const p of req.parties || []) {
|
for (const p of req.parties || []) {
|
||||||
if (
|
if (
|
||||||
@@ -3862,7 +3944,10 @@ export class RequestManagementService {
|
|||||||
if (!req) {
|
if (!req) {
|
||||||
throw new NotFoundException("Request not found");
|
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(
|
throw new BadRequestException(
|
||||||
"This endpoint is only for expert-initiated IN_PERSON BlameRequest files.",
|
"This endpoint is only for expert-initiated IN_PERSON BlameRequest files.",
|
||||||
);
|
);
|
||||||
@@ -3890,7 +3975,10 @@ export class RequestManagementService {
|
|||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
const req = await this.blameRequestDbService.findById(requestId);
|
const req = await this.blameRequestDbService.findById(requestId);
|
||||||
if (!req) throw new NotFoundException("Request not found");
|
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.");
|
throw new BadRequestException("Only expert-initiated IN_PERSON CAR_BODY files.");
|
||||||
}
|
}
|
||||||
if (req.type !== BlameRequestType.CAR_BODY) {
|
if (req.type !== BlameRequestType.CAR_BODY) {
|
||||||
@@ -4193,7 +4281,7 @@ export class RequestManagementService {
|
|||||||
|
|
||||||
const req = await this.blameRequestDbService.findById(requestId);
|
const req = await this.blameRequestDbService.findById(requestId);
|
||||||
if (!req) throw new NotFoundException("Request not found");
|
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.");
|
throw new BadRequestException("This endpoint is only for expert-initiated files.");
|
||||||
}
|
}
|
||||||
this.verifyExpertAccessForBlameV2(req, expert);
|
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 { ExpertDbService } from "./entities/db-service/expert.db.service";
|
||||||
import { FieldExpertDbService } from "./entities/db-service/field-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 { RegistrarDbService } from "./entities/db-service/registrar.db.service";
|
||||||
import { UserDbService } from "./entities/db-service/user.db.service";
|
import { UserDbService } from "./entities/db-service/user.db.service";
|
||||||
import {
|
import {
|
||||||
DamageExpertDbSchema,
|
DamageExpertDbSchema,
|
||||||
@@ -21,6 +22,10 @@ import {
|
|||||||
InsurerExpertDbSchema,
|
InsurerExpertDbSchema,
|
||||||
InsurerExpertModel,
|
InsurerExpertModel,
|
||||||
} from "./entities/schema/insurer-expert.schema";
|
} from "./entities/schema/insurer-expert.schema";
|
||||||
|
import {
|
||||||
|
RegistrarDbSchema,
|
||||||
|
RegistrarModel,
|
||||||
|
} from "./entities/schema/registrar.schema";
|
||||||
import { UserModel, UserDbSchema } from "./entities/schema/user.schema";
|
import { UserModel, UserDbSchema } from "./entities/schema/user.schema";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
@@ -31,6 +36,7 @@ import { UserModel, UserDbSchema } from "./entities/schema/user.schema";
|
|||||||
{ name: ExpertModel.name, schema: ExpertDbSchema },
|
{ name: ExpertModel.name, schema: ExpertDbSchema },
|
||||||
{ name: FieldExpertModel.name, schema: FieldExpertDbSchema },
|
{ name: FieldExpertModel.name, schema: FieldExpertDbSchema },
|
||||||
{ name: InsurerExpertModel.name, schema: InsurerExpertDbSchema },
|
{ name: InsurerExpertModel.name, schema: InsurerExpertDbSchema },
|
||||||
|
{ name: RegistrarModel.name, schema: RegistrarDbSchema },
|
||||||
]),
|
]),
|
||||||
OtpModule,
|
OtpModule,
|
||||||
HashModule,
|
HashModule,
|
||||||
@@ -42,6 +48,7 @@ import { UserModel, UserDbSchema } from "./entities/schema/user.schema";
|
|||||||
DamageExpertDbService,
|
DamageExpertDbService,
|
||||||
FieldExpertDbService,
|
FieldExpertDbService,
|
||||||
InsurerExpertDbService,
|
InsurerExpertDbService,
|
||||||
|
RegistrarDbService,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
UserDbService,
|
UserDbService,
|
||||||
@@ -49,6 +56,7 @@ import { UserModel, UserDbSchema } from "./entities/schema/user.schema";
|
|||||||
DamageExpertDbService,
|
DamageExpertDbService,
|
||||||
FieldExpertDbService,
|
FieldExpertDbService,
|
||||||
InsurerExpertDbService,
|
InsurerExpertDbService,
|
||||||
|
RegistrarDbService,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class UsersModule {}
|
export class UsersModule {}
|
||||||
|
|||||||
Reference in New Issue
Block a user