1
0
forked from Yara724/api

script updated , inquiry field added to blame case and claim case

This commit is contained in:
2026-06-02 12:32:49 +03:30
parent 8730e9af62
commit fe82455562
6 changed files with 580 additions and 108 deletions

View File

@@ -3973,6 +3973,7 @@ export class ClaimRequestManagementService {
blameRequestNo: blameRequest.requestNo,
status: ClaimCaseStatus.SELECTING_OUTER_PARTS,
claimStatus: ClaimStatus.PENDING,
inquiries: (blameRequest as any).inquiries ?? {},
workflow: {
currentStep: ClaimWorkflowStep.SELECT_OUTER_PARTS,
nextStep: ClaimWorkflowStep.SELECT_OTHER_PARTS,
@@ -4108,6 +4109,7 @@ export class ClaimRequestManagementService {
blameRequestNo: blameRequest.requestNo,
status: ClaimCaseStatus.SELECTING_OUTER_PARTS,
claimStatus: ClaimStatus.PENDING,
inquiries: (blameRequest as any).inquiries ?? {},
workflow: {
currentStep: ClaimWorkflowStep.SELECT_OUTER_PARTS,
nextStep: ClaimWorkflowStep.SELECT_OTHER_PARTS,
@@ -4258,6 +4260,7 @@ export class ClaimRequestManagementService {
blameRequestNo: blameRequest.requestNo,
status: ClaimCaseStatus.SELECTING_OUTER_PARTS,
claimStatus: ClaimStatus.PENDING,
inquiries: (blameRequest as any).inquiries ?? {},
workflow: {
currentStep: ClaimWorkflowStep.SELECT_OUTER_PARTS,
nextStep: ClaimWorkflowStep.SELECT_OTHER_PARTS,

View File

@@ -3,7 +3,10 @@ import { HydratedDocument, Schema as MongooseSchema, Types } from "mongoose";
import { ClaimRequiredDocumentType } from "src/Types&Enums/claim-request-management/required-document-type.enum";
import { ClaimStatus } from "src/Types&Enums/claim-request-management/claimStatus.enum";
import { ClaimCaseStatus } from "src/Types&Enums/claim-request-management/claim-case-status.enum";
import { HistoryEvent, HistoryEventSchema } from "src/request-management/entities/schema/historyEvent.type";
import {
HistoryEvent,
HistoryEventSchema,
} from "src/request-management/entities/schema/historyEvent.type";
import {
ClaimDamageSelection,
ClaimDamageSelectionSchema,
@@ -25,8 +28,15 @@ import {
ClaimCaseSnapshot,
ClaimCaseSnapshotSchema,
} from "./claim-case.snapshot.schema";
import { ClaimWorkflow, ClaimWorkflowSchema } from "./claim-case.workflow.schema";
import {
ClaimWorkflow,
ClaimWorkflowSchema,
} from "./claim-case.workflow.schema";
import { UserClaimRating } from "./claim-request-management.schema";
import {
CaseInquiries,
CaseInquiriesSchema,
} from "src/common/schema/case-inquiries.schema";
@Schema({ _id: false })
export class RequiredDocumentRef {
@@ -45,7 +55,8 @@ export class RequiredDocumentRef {
@Prop({ type: Date })
uploadedAt?: Date;
}
export const RequiredDocumentRefSchema = SchemaFactory.createForClass(RequiredDocumentRef);
export const RequiredDocumentRefSchema =
SchemaFactory.createForClass(RequiredDocumentRef);
@Schema({
collection: "claimCases",
@@ -67,7 +78,12 @@ export class ClaimCase {
/**
* Overall case status (user flow + expert flow progression)
*/
@Prop({ required: true, type: String, enum: ClaimCaseStatus, default: ClaimCaseStatus.CREATED })
@Prop({
required: true,
type: String,
enum: ClaimCaseStatus,
default: ClaimCaseStatus.CREATED,
})
status: ClaimCaseStatus;
/**
@@ -119,6 +135,9 @@ export class ClaimCase {
@Prop({ type: ClaimEvaluationSchema, default: () => ({}) })
evaluation?: ClaimEvaluation;
@Prop({ type: CaseInquiriesSchema, default: () => ({}) })
inquiries?: CaseInquiries;
/**
* Optional “read-optimized” copy of fields from blame/request side.
* Source of truth remains `blameRequestId`.
@@ -160,4 +179,3 @@ export class ClaimCase {
export type ClaimCaseDocument = HydratedDocument<ClaimCase>;
export const ClaimCaseSchema = SchemaFactory.createForClass(ClaimCase);
ClaimCaseSchema.index({ status: 1, "workflow.locked": 1 });

View File

@@ -0,0 +1,34 @@
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { Schema as MongooseSchema } from "mongoose";
@Schema({ _id: false })
export class InquiryStatus {
@Prop({ type: Boolean, default: false })
has?: boolean;
@Prop({ type: MongooseSchema.Types.Mixed, default: () => ({}) })
data?: any;
@Prop({ type: MongooseSchema.Types.Mixed })
error?: any;
@Prop({ type: Date })
updatedAt?: Date;
}
export const InquiryStatusSchema = SchemaFactory.createForClass(InquiryStatus);
@Schema({ _id: false, strict: false })
export class CaseInquiries {
@Prop({ type: InquiryStatusSchema })
thirdParty?: InquiryStatus;
@Prop({ type: InquiryStatusSchema })
carBody?: InquiryStatus;
@Prop({ type: InquiryStatusSchema })
person?: InquiryStatus;
@Prop({ type: InquiryStatusSchema })
drivingLicence?: InquiryStatus;
}
export const CaseInquiriesSchema = SchemaFactory.createForClass(CaseInquiries);

View File

@@ -8,6 +8,10 @@ import { HistoryEvent, HistoryEventSchema } from "./historyEvent.type";
import { CaseStatus } from "src/Types&Enums/blame-request-management/caseStatus.enum";
import { BlameStatus } from "src/Types&Enums/blame-request-management/blameStatus.enum";
import { CreationMethod, FilledBy } from "./request-management.schema";
import {
CaseInquiries,
CaseInquiriesSchema,
} from "src/common/schema/case-inquiries.schema";
/** CAR_BODY only: mocked insurance inquiry result (legacy top-level; prefer parties[].insurance.carBodyInsurance) */
@Schema({ _id: false })
@@ -18,86 +22,95 @@ export class CarBodyInsuranceDetail {
@Prop() insurerCompany?: string;
@Prop({ type: [String] }) coverages?: string[];
}
export const CarBodyInsuranceDetailSchema = SchemaFactory.createForClass(CarBodyInsuranceDetail);
export const CarBodyInsuranceDetailSchema = SchemaFactory.createForClass(
CarBodyInsuranceDetail,
);
@Schema({ _id: false })
export class BlameRequestSnapshot {
@Prop({ type: [PartySchema], default: [] })
parties?: Party[];
@Prop({ type: [PartySchema], default: [] })
parties?: Party[];
}
export const BlameRequestSnapshotSchema =
SchemaFactory.createForClass(BlameRequestSnapshot);
@Schema({ collection: "blameCases", timestamps: true , id:true })
@Schema({ collection: "blameCases", timestamps: true, id: true })
export class BlameRequest {
@Prop({ required: true, unique: true, index: true, trim: true })
requestNo: string;
@Prop({ required: true, unique: true, index: true, trim: true })
requestNo: string;
/**
* Human-friendly shared id across blame+claim (e.g. A14235).
* Generated once at creation time and reused by claim.
*/
@Prop({ required: true, unique: true, index: true, trim: true })
publicId: string;
/**
* Human-friendly shared id across blame+claim (e.g. A14235).
* Generated once at creation time and reused by claim.
*/
@Prop({ required: true, unique: true, index: true, trim: true })
publicId: string;
@Prop({ required: true, type: String, enum: BlameRequestType })
type: BlameRequestType;
@Prop({ required: true, type: String, enum: BlameRequestType })
type: BlameRequestType;
@Prop({ required: true, type: String, enum: CaseStatus, default: CaseStatus.OPEN })
status: CaseStatus;
@Prop({
required: true,
type: String,
enum: CaseStatus,
default: CaseStatus.OPEN,
})
status: CaseStatus;
@Prop({ type: String, enum: BlameStatus, default: BlameStatus.UNKNOWN })
blameStatus: BlameStatus;
@Prop({ type: String, enum: BlameStatus, default: BlameStatus.UNKNOWN })
blameStatus: BlameStatus;
@Prop({ type: WorkflowSchema })
workflow?: Workflow;
@Prop({ type: WorkflowSchema })
workflow?: Workflow;
@Prop({ type: [PartySchema], default: [] })
parties: Party[];
@Prop({ type: [PartySchema], default: [] })
parties: Party[];
@Prop({ type: ExpertSectionSchema })
expert?: ExpertSection;
@Prop({ type: CaseInquiriesSchema, default: () => ({}) })
inquiries?: CaseInquiries;
@Prop({ type: [HistoryEventSchema], default: [] })
history: HistoryEvent[];
@Prop({ type: ExpertSectionSchema })
expert?: ExpertSection;
/** CAR_BODY only: first party car-body insurance (mocked inquiry) legacy; prefer parties[0].insurance.carBodyInsurance */
@Prop({ type: CarBodyInsuranceDetailSchema })
carBodyInsuranceDetail?: CarBodyInsuranceDetail;
@Prop({ type: [HistoryEventSchema], default: [] })
history: HistoryEvent[];
/**
* Optional snapshot structure (kept for future use / read-optimized copies).
* Source of truth remains the top-level fields of this document.
*/
@Prop({ type: BlameRequestSnapshotSchema, required: false })
snapshot?: BlameRequestSnapshot;
/** CAR_BODY only: first party car-body insurance (mocked inquiry) legacy; prefer parties[0].insurance.carBodyInsurance */
@Prop({ type: CarBodyInsuranceDetailSchema })
carBodyInsuranceDetail?: CarBodyInsuranceDetail;
/** True when this blame was created by a field expert (independent expert); only that expert can see/review it. */
@Prop({ default: false })
expertInitiated?: boolean;
/**
* Optional snapshot structure (kept for future use / read-optimized copies).
* Source of truth remains the top-level fields of this document.
*/
@Prop({ type: BlameRequestSnapshotSchema, required: false })
snapshot?: BlameRequestSnapshot;
/** Field expert who created this file (for expert-initiated flows). */
@Prop({ type: Types.ObjectId })
initiatedByFieldExpertId?: Types.ObjectId;
/** True when this blame was created by a field expert (independent expert); only that expert can see/review it. */
@Prop({ default: false })
expertInitiated?: boolean;
/** True when this blame was created by a registrar. */
@Prop({ default: false })
registrarInitiated?: boolean;
/** Field expert who created this file (for expert-initiated flows). */
@Prop({ type: Types.ObjectId })
initiatedByFieldExpertId?: Types.ObjectId;
/** Registrar who created this file (for registrar-initiated flows). */
@Prop({ type: Types.ObjectId })
initiatedByRegistrarId?: Types.ObjectId;
/** True when this blame was created by a registrar. */
@Prop({ default: false })
registrarInitiated?: boolean;
/** LINK = expert sends link to user(s); IN_PERSON = expert fills form on-site. */
@Prop({ type: String, enum: CreationMethod })
creationMethod?: CreationMethod;
/** Registrar who created this file (for registrar-initiated flows). */
@Prop({ type: Types.ObjectId })
initiatedByRegistrarId?: Types.ObjectId;
/** Who fills the blame data: CUSTOMER (LINK) or EXPERT (IN_PERSON). */
@Prop({ type: String, enum: FilledBy })
filledBy?: FilledBy;
/** LINK = expert sends link to user(s); IN_PERSON = expert fills form on-site. */
@Prop({ type: String, enum: CreationMethod })
creationMethod?: CreationMethod;
/** Who fills the blame data: CUSTOMER (LINK) or EXPERT (IN_PERSON). */
@Prop({ type: String, enum: FilledBy })
filledBy?: FilledBy;
}
export type BlameRequestDocument = HydratedDocument<BlameRequest>;
export const BlameRequestSchema = SchemaFactory.createForClass(BlameRequest);
BlameRequestSchema.index({ status: 1, "workflow.locked": 1 });
BlameRequestSchema.index({ status: 1, "workflow.locked": 1 });

View File

@@ -123,6 +123,64 @@ export class RequestManagementService {
: -1;
}
private normalizeInquiryError(err: any): any {
if (!err) return undefined;
return {
message: err?.message || String(err),
status: err?.response?.status,
data: err?.response?.data,
};
}
private recordCaseInquiryStatus(
req: any,
key: "thirdParty" | "carBody" | "person" | "drivingLicence" | string,
has: boolean,
data: any = {},
error?: any,
): void {
const existingInquiries =
req.inquiries?.toObject?.() ??
(req.inquiries && typeof req.inquiries === "object" ? req.inquiries : {});
req.inquiries = {
...existingInquiries,
[key]: {
has,
data: data ?? {},
...(error ? { error: this.normalizeInquiryError(error) } : {}),
updatedAt: new Date(),
},
};
}
private recordPartyCaseInquiryStatus(
req: any,
key: "thirdParty" | "carBody" | "person" | "drivingLicence" | string,
role: PartyRole,
has: boolean,
data: any = {},
error?: any,
): void {
const existingData =
req?.inquiries?.[key]?.data?.toObject?.() ?? req?.inquiries?.[key]?.data;
const dataByRole =
existingData &&
typeof existingData === "object" &&
!Array.isArray(existingData)
? existingData
: {};
this.recordCaseInquiryStatus(
req,
key,
has,
{
...dataByRole,
[role]: data ?? {},
},
error,
);
}
private assertPartyOwner(party: any, user: any, errMsg: string) {
const partyUserId = party?.person?.userId
? String(party.person.userId)
@@ -967,6 +1025,11 @@ export class RequestManagementService {
this.logger.log(
`[TEJARAT] block inquiry mapped for request=${req._id}: ${JSON.stringify(inquiryMapped)}`,
);
this.recordPartyCaseInquiryStatus(req, "thirdParty", role, true, {
source: "TEJARAT_BLOCK_INQUIRY",
raw: inquiryRaw,
mapped: inquiryMapped,
});
} catch (err: any) {
this.logger.error(
`[TEJARAT] block inquiry failed for request=${req._id}: ${err?.message || err}`,
@@ -978,6 +1041,15 @@ export class RequestManagementService {
}, data=${JSON.stringify(err.response.data)}`,
);
}
this.recordPartyCaseInquiryStatus(
req,
"thirdParty",
role,
false,
{},
err,
);
await (req as any).save();
throw new HttpException("Inquiry failed", HttpStatus.BAD_REQUEST);
}
@@ -987,6 +1059,12 @@ export class RequestManagementService {
inquiryMapped.Error,
)}`,
);
this.recordPartyCaseInquiryStatus(req, "thirdParty", role, false, {
source: "TEJARAT_BLOCK_INQUIRY",
raw: inquiryRaw,
mapped: inquiryMapped,
});
await (req as any).save();
throw new HttpException(
inquiryMapped.Error.Message || "Inquiry returned error",
HttpStatus.BAD_REQUEST,
@@ -1020,6 +1098,13 @@ export class RequestManagementService {
personalNationalCode,
personalBirthDate,
);
this.recordPartyCaseInquiryStatus(
req,
"person",
role,
true,
personalInquiry,
);
this.logger.log(
`[SANDHUB] personal inquiry success request=${req._id} nationalCode=${personalNationalCode}: ${JSON.stringify(
personalInquiry,
@@ -1029,6 +1114,8 @@ export class RequestManagementService {
this.logger.error(
`[SANDHUB] personal inquiry failed request=${req._id} nationalCode=${personalNationalCode}: ${err?.message || err}`,
);
this.recordPartyCaseInquiryStatus(req, "person", role, false, {}, err);
await (req as any).save();
throw new HttpException(
"Personal identity inquiry failed",
HttpStatus.BAD_REQUEST,
@@ -1120,10 +1207,28 @@ export class RequestManagementService {
// CAR BODY EXTERNAL API INQUIRY
if (req.type === BlameRequestType.CAR_BODY && role === PartyRole.FIRST) {
const carBodyInfo = await this.sandHubService.getTejaratCarBodyInquiry({
nationalCodeOfInsurer: body.nationalCodeOfInsurer,
plate: body.plate,
});
let carBodyInfo: any;
try {
carBodyInfo = await this.sandHubService.getTejaratCarBodyInquiry({
nationalCodeOfInsurer: body.nationalCodeOfInsurer,
plate: body.plate,
});
this.recordPartyCaseInquiryStatus(req, "carBody", role, true, {
source: "TEJARAT_CAR_BODY_INQUIRY",
raw: carBodyInfo.raw,
mapped: carBodyInfo.mapped,
});
} catch (err: any) {
this.logger.error(
`[TEJARAT] car body inquiry failed for request=${req._id}: ${err?.message || err}`,
);
this.recordPartyCaseInquiryStatus(req, "carBody", role, false, {}, err);
await (req as any).save();
throw new HttpException(
"Car body inquiry failed",
HttpStatus.BAD_REQUEST,
);
}
// Raw + mapped stored under vehicle (same pattern as third-party inquiry)
party.vehicle.inquiry = {
@@ -4829,8 +4934,24 @@ export class RequestManagementService {
nationalCodeOfInsurer: firstPartyPlate.nationalCodeOfInsurer,
});
sandHubReport = sandHubResponse["_doc"] || sandHubResponse;
this.recordPartyCaseInquiryStatus(
req,
"thirdParty",
PartyRole.FIRST,
true,
sandHubReport,
);
} catch (e) {
this.logger.error("SandHub error in expertCompleteCarBodyFormV2:", e);
this.recordPartyCaseInquiryStatus(
req,
"thirdParty",
PartyRole.FIRST,
false,
{},
e,
);
await (req as any).save();
throw new InternalServerErrorException(
"Failed to process plate information.",
);
@@ -4864,6 +4985,13 @@ export class RequestManagementService {
firstPartyPlate.plate,
firstPartyPlate.nationalCodeOfInsurer,
);
this.recordPartyCaseInquiryStatus(
req,
"carBody",
PartyRole.FIRST,
true,
carBodyInfo,
);
const firstParty: any = {
role: PartyRole.FIRST,
@@ -5010,11 +5138,32 @@ export class RequestManagementService {
desc: string,
role: PartyRole,
) => {
const sandHubResponse = await this.sandHubService.getSandHubResponse({
plate: plateDto.plate,
nationalCodeOfInsurer: plateDto.nationalCodeOfInsurer,
});
let sandHubResponse: any;
try {
sandHubResponse = await this.sandHubService.getSandHubResponse({
plate: plateDto.plate,
nationalCodeOfInsurer: plateDto.nationalCodeOfInsurer,
});
} catch (err: any) {
this.recordPartyCaseInquiryStatus(
req,
"thirdParty",
role,
false,
{},
err,
);
await (req as any).save();
throw err;
}
const sandHubReport = (sandHubResponse["_doc"] || sandHubResponse) as any;
this.recordPartyCaseInquiryStatus(
req,
"thirdParty",
role,
true,
sandHubReport,
);
const clientName =
sandHubReport?.CompanyName || sandHubReport?.LastCompanyName;
const companyCode = sandHubReport?.CompanyCode;