forked from Yara724/api
user owner guidence added + status and steps fixed
This commit is contained in:
@@ -65,6 +65,9 @@ import {
|
||||
enrichBlamePartiesForAgreementView,
|
||||
} from "src/helpers/blame-party-agreement-decision";
|
||||
import { resendRequestHasPayload } from "src/helpers/claim-expert-resend";
|
||||
import {
|
||||
classifyV2ExpertPricingParts,
|
||||
} from "src/helpers/claim-v2-expert-reply-workflow";
|
||||
import {
|
||||
getClaimCarAngleCaptureBlob,
|
||||
getDamagedPartCaptureBlob,
|
||||
@@ -1907,8 +1910,8 @@ export class ExpertClaimService {
|
||||
/**
|
||||
* V2 (ClaimCase): validate user-uploaded repair factors on the active expert reply.
|
||||
* Preconditions: all `factorNeeded` parts have `factorLink`; case is UNDER_REVIEW at EXPERT_COST_EVALUATION.
|
||||
* — All approved → same as non-factor path: claimStatus APPROVED, step INSURER_REVIEW.
|
||||
* — Any rejected (expert repriced) → case COMPLETED with expert amounts (damage-expert finalization).
|
||||
* — All approved → claimStatus APPROVED, INSURER_REVIEW: owner accepts/rejects to close.
|
||||
* — Any rejected (expert repriced) → WAITING_FOR_INSURER_APPROVAL, APPROVED, INSURER_REVIEW until owner accepts/rejects.
|
||||
*/
|
||||
async validateClaimFactorsV2(
|
||||
claimRequestId: string,
|
||||
@@ -2072,14 +2075,14 @@ export class ExpertClaimService {
|
||||
if (anyRejected) {
|
||||
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, {
|
||||
$set: {
|
||||
status: ClaimCaseStatus.COMPLETED,
|
||||
status: ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL,
|
||||
claimStatus: ClaimStatus.APPROVED,
|
||||
"workflow.currentStep": ClaimWorkflowStep.CLAIM_COMPLETED,
|
||||
"workflow.currentStep": ClaimWorkflowStep.INSURER_REVIEW,
|
||||
"workflow.nextStep": ClaimWorkflowStep.CLAIM_COMPLETED,
|
||||
},
|
||||
$push: {
|
||||
history: {
|
||||
type: "FACTORS_VALIDATED_REJECTED_EXPERT_FINALIZED",
|
||||
type: "FACTORS_VALIDATED_REJECTED_EXPERT_REPRICED",
|
||||
actor: historyActor,
|
||||
timestamp: new Date(),
|
||||
metadata: { replyField },
|
||||
@@ -2087,13 +2090,26 @@ export class ExpertClaimService {
|
||||
},
|
||||
});
|
||||
|
||||
const ownerPhoneRejected = await this.resolveClaimOwnerPhone(claim);
|
||||
if (ownerPhoneRejected) {
|
||||
const expertLastName =
|
||||
actor?.fullName?.trim()?.split(/\s+/).pop() || "کارشناس";
|
||||
await this.smsOrchestrationService.sendSignatureReviewNotice({
|
||||
receptor: ownerPhoneRejected,
|
||||
fileKind: "claim",
|
||||
publicId: claim.publicId,
|
||||
expertLastName,
|
||||
link: this.smsOrchestrationService.buildClaimLink(String(claim._id)),
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
message:
|
||||
"Factors reviewed. Rejected parts were repriced by the expert; the claim is completed.",
|
||||
"Factors reviewed with repricing. Owner must accept or reject via owner-insurer-approval/sign.",
|
||||
claimRequestId,
|
||||
claimStatus: ClaimStatus.APPROVED,
|
||||
caseStatus: ClaimCaseStatus.COMPLETED,
|
||||
outcome: "REJECTED_REPRICED_COMPLETED",
|
||||
caseStatus: ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL,
|
||||
outcome: "REJECTED_REPRICED_OWNER_SIGN",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2350,6 +2366,12 @@ export class ExpertClaimService {
|
||||
);
|
||||
}
|
||||
|
||||
if (existingResend?.fulfilledAt) {
|
||||
throw new BadRequestException(
|
||||
"The owner has already fulfilled a damage-expert resend for this claim. You cannot request another resend.",
|
||||
);
|
||||
}
|
||||
|
||||
const allowedDocTypes = new Set<string>(
|
||||
Object.values(ClaimRequiredDocumentType),
|
||||
);
|
||||
@@ -2458,10 +2480,11 @@ export class ExpertClaimService {
|
||||
* - Each part must include `daghi` (option + conditional price/branchId) like V1
|
||||
*
|
||||
* On success:
|
||||
* - Stores reply in evaluation.damageExpertReply
|
||||
* - Stores reply; clears owner signature fields (`ownerInsurerApproval`, `ownerPricedPartsApproval`)
|
||||
* - Unlocks the workflow
|
||||
* - If any part has factorNeeded=true → status = WAITING_FOR_INSURER_APPROVAL, step = EXPERT_COST_EVALUATION
|
||||
* - Otherwise → status = WAITING_FOR_INSURER_APPROVAL, step = INSURER_REVIEW, claimStatus = APPROVED
|
||||
* - Pricing-only (`factorNeeded=false` everywhere): WAITING_FOR_INSURER_APPROVAL, APPROVED, INSURER_REVIEW → owner final sign/reject.
|
||||
* - All lines `factorNeeded`: NEEDS_REVISION, OWNER_UPLOAD_FACTOR_DOCUMENTS → uploads → UNDER_REVIEW, EXPERT_COST_EVALUATION (validate factors).
|
||||
* - Mixed priced + factor: NEEDS_REVISION, INSURER_REVIEW with next OWNER_UPLOAD_FACTOR_DOCUMENTS → owner signs priced lines first → upload factors → expert validates → final sign.
|
||||
*/
|
||||
async submitExpertReplyV2(
|
||||
claimRequestId: string,
|
||||
@@ -2514,7 +2537,11 @@ export class ExpertClaimService {
|
||||
? this.validateAndNormalizeDaghiForExpertReplyV2(reply.parts)
|
||||
: [];
|
||||
|
||||
const needsFactorUpload = reply.parts?.some((p) => p.factorNeeded === true) ?? false;
|
||||
const { mixedFactorAndPrice, allFactorLines } = classifyV2ExpertPricingParts(
|
||||
processedParts,
|
||||
);
|
||||
const needsFactorUpload =
|
||||
reply.parts?.some((p) => p.factorNeeded === true) ?? false;
|
||||
|
||||
const objectionSubmitted = !!claim.evaluation?.objection?.submittedAt;
|
||||
const hasFinalReply = !!claim.evaluation?.damageExpertReplyFinal;
|
||||
@@ -2546,14 +2573,22 @@ export class ExpertClaimService {
|
||||
...(expertProfileSnapshot && { expertProfileSnapshot }),
|
||||
};
|
||||
|
||||
const nextStep = needsFactorUpload
|
||||
? ClaimWorkflowStep.INSURER_REVIEW
|
||||
: ClaimWorkflowStep.CLAIM_COMPLETED;
|
||||
let currentStep = ClaimWorkflowStep.INSURER_REVIEW;
|
||||
let nextWorkflowStep = ClaimWorkflowStep.CLAIM_COMPLETED;
|
||||
|
||||
const nextCaseStatus = ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL;
|
||||
const nextClaimStatus = needsFactorUpload
|
||||
? ClaimStatus.NEEDS_REVISION
|
||||
: ClaimStatus.APPROVED;
|
||||
let nextClaimStatus = ClaimStatus.APPROVED;
|
||||
|
||||
if (needsFactorUpload) {
|
||||
nextClaimStatus = ClaimStatus.NEEDS_REVISION;
|
||||
if (mixedFactorAndPrice) {
|
||||
currentStep = ClaimWorkflowStep.INSURER_REVIEW;
|
||||
nextWorkflowStep = ClaimWorkflowStep.OWNER_UPLOAD_FACTOR_DOCUMENTS;
|
||||
} else {
|
||||
currentStep = ClaimWorkflowStep.OWNER_UPLOAD_FACTOR_DOCUMENTS;
|
||||
nextWorkflowStep = ClaimWorkflowStep.EXPERT_COST_EVALUATION;
|
||||
}
|
||||
}
|
||||
|
||||
const updatePayload: Record<string, unknown> = {
|
||||
status: nextCaseStatus,
|
||||
@@ -2564,11 +2599,11 @@ export class ExpertClaimService {
|
||||
'workflow.expiredAt': '',
|
||||
'workflow.lockedBy': '',
|
||||
'workflow.preLockQueueSnapshot': '',
|
||||
'evaluation.ownerInsurerApproval': "",
|
||||
'evaluation.ownerPricedPartsApproval': "",
|
||||
},
|
||||
'workflow.currentStep': nextStep,
|
||||
'workflow.nextStep': needsFactorUpload
|
||||
? ClaimWorkflowStep.EXPERT_COST_EVALUATION
|
||||
: ClaimWorkflowStep.CLAIM_COMPLETED,
|
||||
'workflow.currentStep': currentStep,
|
||||
'workflow.nextStep': needsFactorUpload ? nextWorkflowStep : ClaimWorkflowStep.CLAIM_COMPLETED,
|
||||
[`evaluation.${replyField}`]: replyPayload,
|
||||
$push: {
|
||||
'workflow.completedSteps': completedStep,
|
||||
@@ -2584,6 +2619,8 @@ export class ExpertClaimService {
|
||||
timestamp: new Date(),
|
||||
metadata: {
|
||||
factorNeeded: needsFactorUpload,
|
||||
mixedFactorAndPrice,
|
||||
allFactorLines,
|
||||
partsCount: processedParts.length,
|
||||
isFinalReplyAfterObjection,
|
||||
replyField,
|
||||
@@ -2602,30 +2639,28 @@ export class ExpertClaimService {
|
||||
idempotencyKey: `claim:${claimRequestId}:handled:${actor.sub}`,
|
||||
});
|
||||
|
||||
// Same effective step as WAITING_FOR_INSURER_APPROVAL @ INSURER_REVIEW: owner reviews/signs (yara-signature).
|
||||
if (!needsFactorUpload) {
|
||||
const ownerPhone = await this.resolveClaimOwnerPhone(claim);
|
||||
if (ownerPhone) {
|
||||
const expertLastName =
|
||||
actor?.fullName?.trim()?.split(/\s+/).pop() || "کارشناس";
|
||||
await this.smsOrchestrationService.sendSignatureReviewNotice({
|
||||
receptor: ownerPhone,
|
||||
fileKind: "claim",
|
||||
publicId: claim.publicId,
|
||||
expertLastName,
|
||||
link: this.smsOrchestrationService.buildClaimLink(
|
||||
String(claim._id),
|
||||
),
|
||||
});
|
||||
}
|
||||
const ownerPhoneNotify = await this.resolveClaimOwnerPhone(claim);
|
||||
if (ownerPhoneNotify && !needsFactorUpload) {
|
||||
const expertLastName =
|
||||
actor?.fullName?.trim()?.split(/\s+/).pop() || "کارشناس";
|
||||
await this.smsOrchestrationService.sendSignatureReviewNotice({
|
||||
receptor: ownerPhoneNotify,
|
||||
fileKind: "claim",
|
||||
publicId: claim.publicId,
|
||||
expertLastName,
|
||||
link: this.smsOrchestrationService.buildClaimLink(String(claim._id)),
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
claimRequestId,
|
||||
status: nextCaseStatus,
|
||||
claimStatus: nextClaimStatus,
|
||||
currentStep: nextStep,
|
||||
currentStep,
|
||||
workflowNextStep: needsFactorUpload ? nextWorkflowStep : ClaimWorkflowStep.CLAIM_COMPLETED,
|
||||
factorNeeded: needsFactorUpload,
|
||||
mixedPricingAndFactors: mixedFactorAndPrice,
|
||||
allPartsFactorNeeded: !!needsFactorUpload && !!allFactorLines,
|
||||
isFinalReplyAfterObjection,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user