forked from Yara724/api
Fixed insurer
This commit is contained in:
@@ -23,6 +23,7 @@ import { UsersModule } from "./users/users.module";
|
|||||||
import { applyIranFaTimestampPlugin } from "./helpers/mongoose-fa-timestamps.plugin";
|
import { applyIranFaTimestampPlugin } from "./helpers/mongoose-fa-timestamps.plugin";
|
||||||
import { CronModule } from "./utils/cron/cron.module";
|
import { CronModule } from "./utils/cron/cron.module";
|
||||||
import { WorkflowStepManagementModule } from "./workflow-step-management/workflow-step-management.module";
|
import { WorkflowStepManagementModule } from "./workflow-step-management/workflow-step-management.module";
|
||||||
|
import { ExpertInitiatedModule } from "./expert-initiated/expert-initiated.module";
|
||||||
import * as Joi from "joi";
|
import * as Joi from "joi";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
@@ -112,6 +113,7 @@ import * as Joi from "joi";
|
|||||||
ExpertInsurerModule,
|
ExpertInsurerModule,
|
||||||
LookupsModule,
|
LookupsModule,
|
||||||
WorkflowStepManagementModule,
|
WorkflowStepManagementModule,
|
||||||
|
// ExpertInitiatedModule,
|
||||||
],
|
],
|
||||||
controllers: [],
|
controllers: [],
|
||||||
providers: [
|
providers: [
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ export class UserAuthService {
|
|||||||
|
|
||||||
if (!userExist) {
|
if (!userExist) {
|
||||||
await this.smsSender(otp, canonicalMobile);
|
await this.smsSender(otp, canonicalMobile);
|
||||||
|
// console.log(`OTP for ${canonicalMobile}: ${otp}`);
|
||||||
const newUser = await this.userDbService.createUser({
|
const newUser = await this.userDbService.createUser({
|
||||||
mobile: canonicalMobile,
|
mobile: canonicalMobile,
|
||||||
username: canonicalMobile,
|
username: canonicalMobile,
|
||||||
@@ -144,6 +145,7 @@ export class UserAuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.smsSender(otp, canonicalMobile);
|
await this.smsSender(otp, canonicalMobile);
|
||||||
|
// console.log(`OTP for ${canonicalMobile}: ${otp}`);
|
||||||
await this.userDbService.findOneAndUpdate(
|
await this.userDbService.findOneAndUpdate(
|
||||||
buildUserLookupByPhone(canonicalMobile),
|
buildUserLookupByPhone(canonicalMobile),
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5156,7 +5156,6 @@ export class ClaimRequestManagementService {
|
|||||||
): Promise<UploadRequiredDocumentV2ResponseDto> {
|
): Promise<UploadRequiredDocumentV2ResponseDto> {
|
||||||
try {
|
try {
|
||||||
const claimCase = await this.claimCaseDbService.findById(claimRequestId);
|
const claimCase = await this.claimCaseDbService.findById(claimRequestId);
|
||||||
|
|
||||||
if (!claimCase) {
|
if (!claimCase) {
|
||||||
throw new NotFoundException(
|
throw new NotFoundException(
|
||||||
`Claim case with ID ${claimRequestId} not found`,
|
`Claim case with ID ${claimRequestId} not found`,
|
||||||
@@ -5214,6 +5213,7 @@ export class ClaimRequestManagementService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isResendUpload) {
|
if (isResendUpload) {
|
||||||
if (claimCase.status !== ClaimCaseStatus.WAITING_FOR_USER_RESEND) {
|
if (claimCase.status !== ClaimCaseStatus.WAITING_FOR_USER_RESEND) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
@@ -5262,7 +5262,6 @@ export class ClaimRequestManagementService {
|
|||||||
|
|
||||||
const fileUrl = buildFileLink(file.path);
|
const fileUrl = buildFileLink(file.path);
|
||||||
|
|
||||||
// Create document reference in claim-required-documents collection
|
|
||||||
const docRef = await this.claimRequiredDocumentDbService.create({
|
const docRef = await this.claimRequiredDocumentDbService.create({
|
||||||
path: file.path,
|
path: file.path,
|
||||||
fileName: file.filename,
|
fileName: file.filename,
|
||||||
@@ -5271,8 +5270,24 @@ export class ClaimRequestManagementService {
|
|||||||
uploadedAt: new Date(),
|
uploadedAt: new Date(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update claim case
|
// Base history entry for this upload
|
||||||
const updateData: any = {
|
const uploadHistoryEntry = {
|
||||||
|
type: "DOCUMENT_UPLOADED",
|
||||||
|
actor: {
|
||||||
|
actorId: new Types.ObjectId(currentUserId),
|
||||||
|
actorName: claimCase.owner?.fullName || "User",
|
||||||
|
actorType: "user",
|
||||||
|
},
|
||||||
|
timestamp: new Date(),
|
||||||
|
metadata: {
|
||||||
|
documentKey: body.documentKey,
|
||||||
|
fileName: file.filename,
|
||||||
|
fileId: docRef._id.toString(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Base $set — always applied
|
||||||
|
const $set: Record<string, unknown> = {
|
||||||
[`requiredDocuments.${body.documentKey}`]: {
|
[`requiredDocuments.${body.documentKey}`]: {
|
||||||
fileId: docRef._id,
|
fileId: docRef._id,
|
||||||
filePath: file.path,
|
filePath: file.path,
|
||||||
@@ -5280,33 +5295,16 @@ export class ClaimRequestManagementService {
|
|||||||
uploaded: true,
|
uploaded: true,
|
||||||
uploadedAt: new Date(),
|
uploadedAt: new Date(),
|
||||||
},
|
},
|
||||||
$push: {
|
|
||||||
history: {
|
|
||||||
type: "DOCUMENT_UPLOADED",
|
|
||||||
actor: {
|
|
||||||
actorId: new Types.ObjectId(currentUserId),
|
|
||||||
actorName: claimCase.owner?.fullName || "User",
|
|
||||||
actorType: "user",
|
|
||||||
},
|
|
||||||
timestamp: new Date(),
|
|
||||||
metadata: {
|
|
||||||
documentKey: body.documentKey,
|
|
||||||
fileName: file.filename,
|
|
||||||
fileId: docRef._id.toString(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// History entries to push — always at least the upload entry
|
||||||
|
const historyEntries: unknown[] = [uploadHistoryEntry];
|
||||||
|
|
||||||
|
// completedSteps entries to push
|
||||||
|
const completedStepsEntries: string[] = [];
|
||||||
|
|
||||||
let allDocumentsUploaded = false;
|
let allDocumentsUploaded = false;
|
||||||
let remaining = 0;
|
let remaining = 0;
|
||||||
|
|
||||||
// Will be true when this upload also completes capture-phase docs AND
|
|
||||||
// every angle + every selected damaged part has already been captured.
|
|
||||||
// In that case we advance the workflow exactly like `capturePartV2` does
|
|
||||||
// when the user finishes the last capture — otherwise the user would be
|
|
||||||
// stuck in CAPTURE_PART_DAMAGES with all captures done but no more
|
|
||||||
// captures to upload to trigger the transition.
|
|
||||||
let captureStepCompletedOnThisUpload = false;
|
let captureStepCompletedOnThisUpload = false;
|
||||||
|
|
||||||
if (!isResendUpload) {
|
if (!isResendUpload) {
|
||||||
@@ -5317,7 +5315,6 @@ export class ClaimRequestManagementService {
|
|||||||
remaining = CAPTURE_PHASE_DAMAGED_PARTY_DOC_KEYS.filter(
|
remaining = CAPTURE_PHASE_DAMAGED_PARTY_DOC_KEYS.filter(
|
||||||
(k) => !afterThis(k),
|
(k) => !afterThis(k),
|
||||||
).length;
|
).length;
|
||||||
allDocumentsUploaded = false;
|
|
||||||
|
|
||||||
if (remaining === 0) {
|
if (remaining === 0) {
|
||||||
const progressAfterDoc = getClaimCaptureProgress(claimCase, {
|
const progressAfterDoc = getClaimCaptureProgress(claimCase, {
|
||||||
@@ -5330,31 +5327,29 @@ export class ClaimRequestManagementService {
|
|||||||
progressAfterDoc.capturePhaseDocsComplete
|
progressAfterDoc.capturePhaseDocsComplete
|
||||||
) {
|
) {
|
||||||
captureStepCompletedOnThisUpload = true;
|
captureStepCompletedOnThisUpload = true;
|
||||||
updateData["status"] =
|
$set["status"] = ClaimCaseStatus.UPLOADING_REQUIRED_DOCUMENTS;
|
||||||
ClaimCaseStatus.UPLOADING_REQUIRED_DOCUMENTS;
|
$set["workflow.currentStep"] =
|
||||||
updateData["workflow.currentStep"] =
|
|
||||||
ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS;
|
ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS;
|
||||||
updateData["workflow.nextStep"] =
|
$set["workflow.nextStep"] =
|
||||||
ClaimWorkflowStep.USER_SUBMISSION_COMPLETE;
|
ClaimWorkflowStep.USER_SUBMISSION_COMPLETE;
|
||||||
updateData.$push.history = [
|
|
||||||
updateData.$push.history,
|
completedStepsEntries.push(
|
||||||
{
|
ClaimWorkflowStep.CAPTURE_PART_DAMAGES,
|
||||||
type: "STEP_COMPLETED",
|
);
|
||||||
actor: {
|
historyEntries.push({
|
||||||
actorId: new Types.ObjectId(currentUserId),
|
type: "STEP_COMPLETED",
|
||||||
actorName: claimCase.owner?.fullName || "User",
|
actor: {
|
||||||
actorType: "user",
|
actorId: new Types.ObjectId(currentUserId),
|
||||||
},
|
actorName: claimCase.owner?.fullName || "User",
|
||||||
timestamp: new Date(),
|
actorType: "user",
|
||||||
metadata: {
|
|
||||||
stepKey: ClaimWorkflowStep.CAPTURE_PART_DAMAGES,
|
|
||||||
description:
|
|
||||||
"Angles, damaged parts, and capture-phase vehicle evidence (chassis, engine, metal plate) are complete. Please upload remaining required documents.",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
timestamp: new Date(),
|
||||||
updateData.$push["workflow.completedSteps"] =
|
metadata: {
|
||||||
ClaimWorkflowStep.CAPTURE_PART_DAMAGES;
|
stepKey: ClaimWorkflowStep.CAPTURE_PART_DAMAGES,
|
||||||
|
description:
|
||||||
|
"Angles, damaged parts, and capture-phase vehicle evidence (chassis, engine, metal plate) are complete. Please upload remaining required documents.",
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -5370,14 +5365,17 @@ export class ClaimRequestManagementService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (allDocumentsUploaded) {
|
if (allDocumentsUploaded) {
|
||||||
updateData["status"] = ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT;
|
$set["status"] = ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT;
|
||||||
updateData["claimStatus"] = ClaimStatus.PENDING;
|
$set["claimStatus"] = ClaimStatus.PENDING;
|
||||||
updateData["workflow.currentStep"] =
|
$set["workflow.currentStep"] =
|
||||||
ClaimWorkflowStep.USER_SUBMISSION_COMPLETE;
|
ClaimWorkflowStep.USER_SUBMISSION_COMPLETE;
|
||||||
updateData["workflow.nextStep"] =
|
$set["workflow.nextStep"] =
|
||||||
ClaimWorkflowStep.EXPERT_DAMAGE_ASSESSMENT;
|
ClaimWorkflowStep.EXPERT_DAMAGE_ASSESSMENT;
|
||||||
updateData.$push.history = [
|
|
||||||
updateData.$push.history,
|
completedStepsEntries.push(
|
||||||
|
ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
|
||||||
|
);
|
||||||
|
historyEntries.push(
|
||||||
{
|
{
|
||||||
type: "STEP_COMPLETED",
|
type: "STEP_COMPLETED",
|
||||||
actor: {
|
actor: {
|
||||||
@@ -5405,16 +5403,30 @@ export class ClaimRequestManagementService {
|
|||||||
"User submission complete. Claim ready for damage expert review.",
|
"User submission complete. Claim ready for damage expert review.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
);
|
||||||
updateData.$push["workflow.completedSteps"] =
|
|
||||||
ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build the final MongoDB update — all operators explicit and clean
|
||||||
|
const updatePayload: Record<string, unknown> = { $set };
|
||||||
|
|
||||||
|
const $push: Record<string, unknown> = {
|
||||||
|
history:
|
||||||
|
historyEntries.length === 1
|
||||||
|
? historyEntries[0]
|
||||||
|
: { $each: historyEntries },
|
||||||
|
};
|
||||||
|
if (completedStepsEntries.length === 1) {
|
||||||
|
$push["workflow.completedSteps"] = completedStepsEntries[0];
|
||||||
|
} else if (completedStepsEntries.length > 1) {
|
||||||
|
$push["workflow.completedSteps"] = { $each: completedStepsEntries };
|
||||||
|
}
|
||||||
|
updatePayload.$push = $push;
|
||||||
|
|
||||||
await this.claimCaseDbService.findByIdAndUpdate(
|
await this.claimCaseDbService.findByIdAndUpdate(
|
||||||
claimRequestId,
|
claimRequestId,
|
||||||
updateData,
|
updatePayload,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isResendUpload) {
|
if (isResendUpload) {
|
||||||
|
|||||||
@@ -92,7 +92,10 @@ import {
|
|||||||
buildMutualAgreementExpertDecision,
|
buildMutualAgreementExpertDecision,
|
||||||
enrichBlamePartiesForAgreementView,
|
enrichBlamePartiesForAgreementView,
|
||||||
} from "src/helpers/blame-party-agreement-decision";
|
} from "src/helpers/blame-party-agreement-decision";
|
||||||
import { resendRequestHasPayload } from "src/helpers/claim-expert-resend";
|
import {
|
||||||
|
normalizeResendDocumentKeys,
|
||||||
|
resendRequestHasPayload,
|
||||||
|
} from "src/helpers/claim-expert-resend";
|
||||||
import {
|
import {
|
||||||
claimCaseStatusAfterExpertReplyV2,
|
claimCaseStatusAfterExpertReplyV2,
|
||||||
classifyV2ExpertPricingParts,
|
classifyV2ExpertPricingParts,
|
||||||
@@ -133,6 +136,7 @@ import { CLAIM_V2_TOTAL_PAYMENT_CAP_TOMAN } from "src/constants/repair-amount-li
|
|||||||
import { ListQueryV2Dto } from "src/common/dto/list-query-v2.dto";
|
import { ListQueryV2Dto } from "src/common/dto/list-query-v2.dto";
|
||||||
import { applyListQueryV2 } from "src/helpers/list-query-v2";
|
import { applyListQueryV2 } from "src/helpers/list-query-v2";
|
||||||
import { buildEnrichedDamagedParts } from "./dto/claim-damaged-part.enricher";
|
import { buildEnrichedDamagedParts } from "./dto/claim-damaged-part.enricher";
|
||||||
|
import { canonicalizeResendDocumentKey } from "src/helpers/claim-resend-document-keys";
|
||||||
|
|
||||||
const CLAIM_V2_TOTAL_PAYMENT_CAP = CLAIM_V2_TOTAL_PAYMENT_CAP_TOMAN;
|
const CLAIM_V2_TOTAL_PAYMENT_CAP = CLAIM_V2_TOTAL_PAYMENT_CAP_TOMAN;
|
||||||
|
|
||||||
@@ -3842,29 +3846,29 @@ export class ExpertClaimService {
|
|||||||
const isResendPending =
|
const isResendPending =
|
||||||
claim.status === ClaimCaseStatus.WAITING_FOR_USER_RESEND;
|
claim.status === ClaimCaseStatus.WAITING_FOR_USER_RESEND;
|
||||||
|
|
||||||
|
const assignedForReviewById = String(
|
||||||
|
(claim.workflow as any)?.assignedForReviewBy?.actorId ?? "",
|
||||||
|
);
|
||||||
|
const lockEnforced = this.isClaimV2WorkflowLockCurrentlyEnforced(claim);
|
||||||
|
const lockedById = String(claim.workflow?.lockedBy?.actorId ?? "");
|
||||||
|
const isAssignedToMe =
|
||||||
|
!!assignedForReviewById && assignedForReviewById === actorId;
|
||||||
|
|
||||||
|
// Gate: must satisfy at least one bucket
|
||||||
if (
|
if (
|
||||||
!isDamageExpertPhase &&
|
!isDamageExpertPhase &&
|
||||||
!isFactorValidationPending &&
|
!isFactorValidationPending &&
|
||||||
!isResendPending
|
!isResendPending &&
|
||||||
|
!isAssignedToMe
|
||||||
) {
|
) {
|
||||||
throw new ForbiddenException(
|
throw new ForbiddenException(
|
||||||
`This claim is not available for expert review. Current status: ${claim.status}`,
|
`This claim is not available for expert review. Current status: ${claim.status}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ownership access control — same 4-bucket logic as list
|
|
||||||
const assignedForReviewById = String(
|
|
||||||
(claim.workflow as any)?.assignedForReviewBy?.actorId ?? "",
|
|
||||||
);
|
|
||||||
const lockEnforced = this.isClaimV2WorkflowLockCurrentlyEnforced(claim);
|
|
||||||
const lockedById = String(claim.workflow?.lockedBy?.actorId ?? "");
|
|
||||||
|
|
||||||
const isAvailable =
|
const isAvailable =
|
||||||
isDamageExpertPhase && !assignedForReviewById && !lockEnforced;
|
isDamageExpertPhase && !assignedForReviewById && !lockEnforced;
|
||||||
const isAssignedToMe =
|
|
||||||
!!assignedForReviewById && assignedForReviewById === actorId;
|
|
||||||
const isLockedByMe = lockEnforced && lockedById === actorId;
|
const isLockedByMe = lockEnforced && lockedById === actorId;
|
||||||
// Factor validation is open to all tenant experts (no individual ownership)
|
|
||||||
const isFactorValidationOpen = isFactorValidationPending;
|
const isFactorValidationOpen = isFactorValidationPending;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -3888,12 +3892,19 @@ export class ExpertClaimService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasCapture = (data: any, key: string) =>
|
|
||||||
data && (data instanceof Map ? data.get(key) : data[key]);
|
|
||||||
|
|
||||||
// Build requiredDocuments map
|
// Build requiredDocuments map
|
||||||
const requiredDocs = claim.requiredDocuments as any;
|
const requiredDocs = claim.requiredDocuments as any;
|
||||||
const requiredDocumentsStatus: Record<string, any> = {};
|
const requiredDocumentsStatus: Record<string, any> = {};
|
||||||
|
|
||||||
|
// Resend documents requested by expert — normalize to canonical keys for comparison
|
||||||
|
const resendDocumentKeys = new Set(
|
||||||
|
normalizeResendDocumentKeys(
|
||||||
|
(claim as any).evaluation?.damageExpertResend?.resendDocuments,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const resendFulfilled = !!(claim as any).evaluation?.damageExpertResend
|
||||||
|
?.fulfilledAt;
|
||||||
|
|
||||||
if (requiredDocs) {
|
if (requiredDocs) {
|
||||||
const keys =
|
const keys =
|
||||||
requiredDocs instanceof Map
|
requiredDocs instanceof Map
|
||||||
@@ -3902,11 +3913,18 @@ export class ExpertClaimService {
|
|||||||
for (const k of keys as string[]) {
|
for (const k of keys as string[]) {
|
||||||
const doc =
|
const doc =
|
||||||
requiredDocs instanceof Map ? requiredDocs.get(k) : requiredDocs[k];
|
requiredDocs instanceof Map ? requiredDocs.get(k) : requiredDocs[k];
|
||||||
|
const canonicalKey = canonicalizeResendDocumentKey(k) ?? k;
|
||||||
|
const wasResendRequested = resendDocumentKeys.has(canonicalKey);
|
||||||
|
|
||||||
requiredDocumentsStatus[k] = {
|
requiredDocumentsStatus[k] = {
|
||||||
uploaded: !!doc?.uploaded,
|
uploaded: !!doc?.uploaded,
|
||||||
fileId: doc?.fileId?.toString(),
|
fileId: doc?.fileId?.toString(),
|
||||||
fileName: doc?.fileName,
|
fileName: doc?.fileName,
|
||||||
filePath: doc?.filePath ? buildFileLink(doc.filePath) : undefined,
|
filePath: doc?.filePath ? buildFileLink(doc.filePath) : undefined,
|
||||||
|
resend: {
|
||||||
|
wasRequested: wasResendRequested,
|
||||||
|
isFulfilled: wasResendRequested && resendFulfilled,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3927,7 +3945,7 @@ export class ExpertClaimService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build damaged parts list (aligned with normalized selected parts and enriched with evaluation context)
|
// Build enriched damaged parts
|
||||||
const carTypeExpert = claim.vehicle?.carType as
|
const carTypeExpert = claim.vehicle?.carType as
|
||||||
| ClaimVehicleTypeV2
|
| ClaimVehicleTypeV2
|
||||||
| undefined;
|
| undefined;
|
||||||
@@ -3938,18 +3956,10 @@ export class ExpertClaimService {
|
|||||||
);
|
);
|
||||||
const damagedPartsDataExpert = claim.media?.damagedParts as any;
|
const damagedPartsDataExpert = claim.media?.damagedParts as any;
|
||||||
|
|
||||||
// Extract optimization lookups from the DB document safely
|
|
||||||
const evaluationBlock = (claim as any).evaluation;
|
|
||||||
const priceDropLines = evaluationBlock?.priceDrop?.partLines || [];
|
|
||||||
const objectionParts = evaluationBlock?.objection?.objectionParts || [];
|
|
||||||
const newObjectionParts = evaluationBlock?.objection?.newParts || [];
|
|
||||||
|
|
||||||
// Inside getClaimDetailV2, replace the damagedParts block:
|
|
||||||
|
|
||||||
const damagedParts = buildEnrichedDamagedParts({
|
const damagedParts = buildEnrichedDamagedParts({
|
||||||
selectedParts: selectedNormExpert,
|
selectedParts: selectedNormExpert,
|
||||||
damagedPartsData: damagedPartsDataExpert,
|
damagedPartsData: damagedPartsDataExpert,
|
||||||
evaluationBlock: (claim as any).evaluation, // damageExpertResend lives here already
|
evaluationBlock: (claim as any).evaluation,
|
||||||
expertAddedParts: (claim.damage as any)?.expertAddedParts ?? [],
|
expertAddedParts: (claim.damage as any)?.expertAddedParts ?? [],
|
||||||
getDamagedPartCaptureBlob,
|
getDamagedPartCaptureBlob,
|
||||||
hasDamagedPartCapture,
|
hasDamagedPartCapture,
|
||||||
@@ -3957,7 +3967,7 @@ export class ExpertClaimService {
|
|||||||
buildFileLink,
|
buildFileLink,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 1. Vehicle payload logic from "upstream"
|
// Vehicle payload — fall back to blame inquiry if claim vehicle is sparse
|
||||||
let vehiclePayload = claim.vehicle as any;
|
let vehiclePayload = claim.vehicle as any;
|
||||||
const hasClaimVehicle =
|
const hasClaimVehicle =
|
||||||
vehiclePayload &&
|
vehiclePayload &&
|
||||||
@@ -3968,7 +3978,6 @@ export class ExpertClaimService {
|
|||||||
|
|
||||||
let blameLean: any = null;
|
let blameLean: any = null;
|
||||||
|
|
||||||
// 2. Video capture and blame case db queries from "stashed"
|
|
||||||
const videoCaptureIdRaw = (claim.media as any)?.videoCaptureId;
|
const videoCaptureIdRaw = (claim.media as any)?.videoCaptureId;
|
||||||
const blameRequestIdStr = claim.blameRequestId?.toString();
|
const blameRequestIdStr = claim.blameRequestId?.toString();
|
||||||
|
|
||||||
@@ -3991,18 +4000,15 @@ export class ExpertClaimService {
|
|||||||
blameLean = blameLeanRows[0] ?? null;
|
blameLean = blameLeanRows[0] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If claim vehicle not found and blameLean exists, replace vehiclePayload
|
|
||||||
if (!hasClaimVehicle && blameLean) {
|
if (!hasClaimVehicle && blameLean) {
|
||||||
const fromBlame = this.damagedPartyVehicleFromBlame(blameLean, claim);
|
const fromBlame = this.damagedPartyVehicleFromBlame(blameLean, claim);
|
||||||
if (fromBlame) vehiclePayload = fromBlame;
|
if (fromBlame) vehiclePayload = fromBlame;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Blame file context logic from "upstream"
|
|
||||||
const blameFileContext = blameLean
|
const blameFileContext = blameLean
|
||||||
? this.blameFileContextForExpert(blameLean)
|
? this.blameFileContextForExpert(blameLean)
|
||||||
: {};
|
: {};
|
||||||
|
|
||||||
// 4. videoCapture logic from "stashed"
|
|
||||||
let videoCapture: ClaimDetailV2ResponseDto["videoCapture"] = undefined;
|
let videoCapture: ClaimDetailV2ResponseDto["videoCapture"] = undefined;
|
||||||
if (videoCaptureRow) {
|
if (videoCaptureRow) {
|
||||||
const vc = videoCaptureRow as any;
|
const vc = videoCaptureRow as any;
|
||||||
@@ -4036,28 +4042,12 @@ export class ExpertClaimService {
|
|||||||
? ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT
|
? ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT
|
||||||
: claim.status;
|
: claim.status;
|
||||||
|
|
||||||
const objection = (claim as any).evaluation?.objection
|
|
||||||
? {
|
|
||||||
objectionParts:
|
|
||||||
(claim as any).evaluation.objection.objectionParts ?? [],
|
|
||||||
newParts: (claim as any).evaluation.objection.newParts ?? [],
|
|
||||||
submittedAt: (claim as any).evaluation.objection.submittedAt,
|
|
||||||
}
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const blameCaseForApi = blameCase
|
const blameCaseForApi = blameCase
|
||||||
? this.sanitizeBlameCaseForClaimDetailApi(
|
? this.sanitizeBlameCaseForClaimDetailApi(
|
||||||
blameCase as Record<string, unknown>,
|
blameCase as Record<string, unknown>,
|
||||||
)
|
)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
// Enrich the full evaluation block with `signLink`s for every place a user
|
|
||||||
// signature is referenced, then assemble the response slice.
|
|
||||||
//
|
|
||||||
// We always expose owner approval blocks (when present in the DB) because
|
|
||||||
// the signature URL is needed across all phases of the claim. The expert
|
|
||||||
// reply payloads are still gated on `isFactorValidationPending` to match
|
|
||||||
// the historical contract of this endpoint.
|
|
||||||
const claimEvaluationRaw = (claim as any).evaluation as
|
const claimEvaluationRaw = (claim as any).evaluation as
|
||||||
| Record<string, unknown>
|
| Record<string, unknown>
|
||||||
| undefined;
|
| undefined;
|
||||||
@@ -4122,7 +4112,6 @@ export class ExpertClaimService {
|
|||||||
blameRequestId: claim.blameRequestId?.toString(),
|
blameRequestId: claim.blameRequestId?.toString(),
|
||||||
blameRequestNo: claim.blameRequestNo,
|
blameRequestNo: claim.blameRequestNo,
|
||||||
money: moneyPayload,
|
money: moneyPayload,
|
||||||
// selectedParts: selectedNormExpert,
|
|
||||||
otherParts: claim.damage?.otherParts,
|
otherParts: claim.damage?.otherParts,
|
||||||
requiredDocuments:
|
requiredDocuments:
|
||||||
Object.keys(requiredDocumentsStatus).length > 0
|
Object.keys(requiredDocumentsStatus).length > 0
|
||||||
@@ -4131,10 +4120,9 @@ export class ExpertClaimService {
|
|||||||
carAngles,
|
carAngles,
|
||||||
damagedParts,
|
damagedParts,
|
||||||
awaitingFactorValidation: isFactorValidationPending,
|
awaitingFactorValidation: isFactorValidationPending,
|
||||||
evaluation: enrichedEvaluation as
|
evaluation: evaluationForApi as
|
||||||
| ClaimDetailV2ResponseDto["evaluation"]
|
| ClaimDetailV2ResponseDto["evaluation"]
|
||||||
| undefined,
|
| undefined,
|
||||||
// objection,
|
|
||||||
videoCapture,
|
videoCapture,
|
||||||
blameCase: blameCaseForApi,
|
blameCase: blameCaseForApi,
|
||||||
blameExpertDecision,
|
blameExpertDecision,
|
||||||
|
|||||||
@@ -1099,6 +1099,8 @@ export class ExpertInsurerService {
|
|||||||
|
|
||||||
async retrieveAllFilesOfClient(insurerId: string) {
|
async retrieveAllFilesOfClient(insurerId: string) {
|
||||||
const id = this.getClientId(insurerId);
|
const id = this.getClientId(insurerId);
|
||||||
|
const idStr = String(id);
|
||||||
|
|
||||||
const [blameFiles, claimFiles] = await Promise.all([
|
const [blameFiles, claimFiles] = await Promise.all([
|
||||||
this.getClientBlameFiles(id),
|
this.getClientBlameFiles(id),
|
||||||
this.getClientClaimFiles(id),
|
this.getClientClaimFiles(id),
|
||||||
@@ -1112,11 +1114,7 @@ export class ExpertInsurerService {
|
|||||||
parties?: Array<{
|
parties?: Array<{
|
||||||
role?: string;
|
role?: string;
|
||||||
fullName?: string;
|
fullName?: string;
|
||||||
vehicle?: {
|
vehicle?: { carName?: string; carModel?: string; plate?: string };
|
||||||
carName?: string;
|
|
||||||
carModel?: string;
|
|
||||||
plate?: string;
|
|
||||||
};
|
|
||||||
}>;
|
}>;
|
||||||
blame?: {
|
blame?: {
|
||||||
requestId?: string;
|
requestId?: string;
|
||||||
@@ -1125,11 +1123,7 @@ export class ExpertInsurerService {
|
|||||||
parties?: Array<{
|
parties?: Array<{
|
||||||
role?: string;
|
role?: string;
|
||||||
fullName?: string;
|
fullName?: string;
|
||||||
vehicle?: {
|
vehicle?: { carName?: string; carModel?: string; plate?: string };
|
||||||
carName?: string;
|
|
||||||
carModel?: string;
|
|
||||||
plate?: string;
|
|
||||||
};
|
|
||||||
}>;
|
}>;
|
||||||
expertNames?: string[];
|
expertNames?: string[];
|
||||||
createdAt?: Date | string;
|
createdAt?: Date | string;
|
||||||
@@ -1150,6 +1144,7 @@ export class ExpertInsurerService {
|
|||||||
}
|
}
|
||||||
>();
|
>();
|
||||||
|
|
||||||
|
// Index blame files first
|
||||||
for (const b of blameFiles) {
|
for (const b of blameFiles) {
|
||||||
const key = String((b as any).publicId || (b as any)._id || "");
|
const key = String((b as any).publicId || (b as any)._id || "");
|
||||||
if (!key) continue;
|
if (!key) continue;
|
||||||
@@ -1163,7 +1158,7 @@ export class ExpertInsurerService {
|
|||||||
(b as any).requestNo || String((b as any).requestNumber || ""),
|
(b as any).requestNo || String((b as any).requestNumber || ""),
|
||||||
status: (b as any).status,
|
status: (b as any).status,
|
||||||
parties: partiesPreview,
|
parties: partiesPreview,
|
||||||
expertNames: extractExpertNamesFromBlame(b), // ← add
|
expertNames: extractExpertNamesFromBlame(b),
|
||||||
createdAt: (b as any).createdAt,
|
createdAt: (b as any).createdAt,
|
||||||
updatedAt: (b as any).updatedAt,
|
updatedAt: (b as any).updatedAt,
|
||||||
};
|
};
|
||||||
@@ -1173,10 +1168,61 @@ export class ExpertInsurerService {
|
|||||||
byPublicId.set(key, prev);
|
byPublicId.set(key, prev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For claim files whose blame wasn't in blameFiles (different insurer's blame),
|
||||||
|
// fetch the linked blame directly so we can get vehicle + parties data
|
||||||
|
const claimOnlyBlameIds = claimFiles
|
||||||
|
.filter((c) => {
|
||||||
|
const key = String((c as any).publicId || (c as any)._id || "");
|
||||||
|
return key && !byPublicId.has(key) && (c as any).blameRequestId;
|
||||||
|
})
|
||||||
|
.map((c) => String((c as any).blameRequestId));
|
||||||
|
|
||||||
|
const uniqueBlameIds = [...new Set(claimOnlyBlameIds)].filter(Boolean);
|
||||||
|
|
||||||
|
const linkedBlames =
|
||||||
|
uniqueBlameIds.length > 0
|
||||||
|
? ((await this.blameRequestDbService.find(
|
||||||
|
{
|
||||||
|
_id: {
|
||||||
|
$in: uniqueBlameIds.map((id) => new Types.ObjectId(id)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ lean: true },
|
||||||
|
)) as any[])
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const linkedBlameByPublicId = new Map<string, any>(
|
||||||
|
linkedBlames.map((b) => [String(b.publicId), b]),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Index claim files
|
||||||
for (const c of claimFiles) {
|
for (const c of claimFiles) {
|
||||||
const key = String((c as any).publicId || (c as any)._id || "");
|
const key = String((c as any).publicId || (c as any)._id || "");
|
||||||
if (!key) continue;
|
if (!key) continue;
|
||||||
const prev = byPublicId.get(key) ?? { publicId: key };
|
const prev = byPublicId.get(key) ?? { publicId: key };
|
||||||
|
|
||||||
|
// If no blame entry yet, use the linked blame for parties/vehicle/fileType
|
||||||
|
if (!prev.blame) {
|
||||||
|
const linkedBlame = linkedBlameByPublicId.get(key);
|
||||||
|
if (linkedBlame) {
|
||||||
|
const partiesFromBlame = this.buildPartiesPreview(
|
||||||
|
linkedBlame.parties,
|
||||||
|
);
|
||||||
|
prev.fileType = prev.fileType ?? linkedBlame.type;
|
||||||
|
prev.blame = {
|
||||||
|
requestId: String(linkedBlame._id),
|
||||||
|
requestNo: linkedBlame.requestNo || "",
|
||||||
|
status: linkedBlame.status,
|
||||||
|
parties: partiesFromBlame,
|
||||||
|
expertNames: extractExpertNamesFromBlame(linkedBlame),
|
||||||
|
createdAt: linkedBlame.createdAt,
|
||||||
|
updatedAt: linkedBlame.updatedAt,
|
||||||
|
};
|
||||||
|
prev.parties = prev.parties ?? partiesFromBlame;
|
||||||
|
prev.createdAt = prev.createdAt ?? linkedBlame.createdAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const claimPartiesPreview = this.buildPartiesPreview(
|
const claimPartiesPreview = this.buildPartiesPreview(
|
||||||
(c as any)?.snapshot?.parties,
|
(c as any)?.snapshot?.parties,
|
||||||
);
|
);
|
||||||
@@ -1189,7 +1235,7 @@ export class ExpertInsurerService {
|
|||||||
status: (c as any).status,
|
status: (c as any).status,
|
||||||
claimStatus: (c as any).claimStatus,
|
claimStatus: (c as any).claimStatus,
|
||||||
currentStep: (c as any).currentStep,
|
currentStep: (c as any).currentStep,
|
||||||
expertNames: extractExpertNamesFromClaim(c), // ← add
|
expertNames: extractExpertNamesFromClaim(c),
|
||||||
createdAt: (c as any).createdAt,
|
createdAt: (c as any).createdAt,
|
||||||
updatedAt: (c as any).updatedAt,
|
updatedAt: (c as any).updatedAt,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -260,6 +260,19 @@ export function canFinalizeExpertResend(claim: any): boolean {
|
|||||||
claim?.damage?.selectedParts,
|
claim?.damage?.selectedParts,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log("[canFinalizeExpertResend]", {
|
||||||
|
docKeys,
|
||||||
|
partKeys,
|
||||||
|
docResults: docKeys.map((k) => ({
|
||||||
|
k,
|
||||||
|
uploaded: isRequiredDocumentUploaded(claim, k),
|
||||||
|
})),
|
||||||
|
partResults: partKeys.map((k) => ({
|
||||||
|
k,
|
||||||
|
captured: hasDamagedPartCapture(claim, k),
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
|
||||||
if (docKeys.length === 0 && partKeys.length === 0) {
|
if (docKeys.length === 0 && partKeys.length === 0) {
|
||||||
return String(r.resendDescription || "").trim() !== "";
|
return String(r.resendDescription || "").trim() !== "";
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user