forked from Yara724/api
Compare commits
2 Commits
9c87927e6c
...
05c5b70b4d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05c5b70b4d | ||
|
|
5d2227b00b |
@@ -5009,8 +5009,30 @@ export class ClaimRequestManagementService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getPartLabelFa = (key: string): string => {
|
||||||
|
const labels: Record<string, string> = {
|
||||||
|
hood: 'کاپوت',
|
||||||
|
trunk: 'صندوق عقب',
|
||||||
|
roof: 'سقف',
|
||||||
|
front_right_door: 'درب جلو راست',
|
||||||
|
front_left_door: 'درب جلو چپ',
|
||||||
|
rear_right_door: 'درب عقب راست',
|
||||||
|
rear_left_door: 'درب عقب چپ',
|
||||||
|
front_bumper: 'سپر جلو',
|
||||||
|
rear_bumper: 'سپر عقب',
|
||||||
|
front_right_fender: 'گلگیر جلو راست',
|
||||||
|
front_left_fender: 'گلگیر جلو چپ',
|
||||||
|
rear_right_fender: 'گلگیر عقب راست',
|
||||||
|
rear_left_fender: 'گلگیر عقب چپ',
|
||||||
|
};
|
||||||
|
return labels[key] || key;
|
||||||
|
};
|
||||||
|
|
||||||
const damagedPartsData = claim.media?.damagedParts as any;
|
const damagedPartsData = claim.media?.damagedParts as any;
|
||||||
const damagedParts: Record<string, { captured: boolean; url?: string }> = {};
|
const damagedParts: Record<
|
||||||
|
string,
|
||||||
|
{ label_fa: string; captured: boolean; url?: string }
|
||||||
|
> = {};
|
||||||
const resendPartKeys = normalizeResendPartKeys(
|
const resendPartKeys = normalizeResendPartKeys(
|
||||||
claim.evaluation?.damageExpertResend?.resendCarParts,
|
claim.evaluation?.damageExpertResend?.resendCarParts,
|
||||||
);
|
);
|
||||||
@@ -5021,6 +5043,7 @@ export class ClaimRequestManagementService {
|
|||||||
for (const p of partKeysForDetails) {
|
for (const p of partKeysForDetails) {
|
||||||
const cap = hasCapture(damagedPartsData, p);
|
const cap = hasCapture(damagedPartsData, p);
|
||||||
damagedParts[p] = {
|
damagedParts[p] = {
|
||||||
|
label_fa: getPartLabelFa(p),
|
||||||
captured: !!cap,
|
captured: !!cap,
|
||||||
url: cap?.url || (cap?.path ? buildFileLink(cap.path) : undefined),
|
url: cap?.url || (cap?.path ? buildFileLink(cap.path) : undefined),
|
||||||
};
|
};
|
||||||
@@ -5080,6 +5103,12 @@ export class ClaimRequestManagementService {
|
|||||||
carAngles,
|
carAngles,
|
||||||
damagedParts,
|
damagedParts,
|
||||||
expertResend,
|
expertResend,
|
||||||
|
evaluation: claim.evaluation
|
||||||
|
? {
|
||||||
|
damageExpertReply: claim.evaluation.damageExpertReply,
|
||||||
|
damageExpertReplyFinal: claim.evaluation.damageExpertReplyFinal,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
...(isExpertViewer
|
...(isExpertViewer
|
||||||
? {
|
? {
|
||||||
userRating: claim.userRating
|
userRating: claim.userRating
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export class ClaimDetailsV2ResponseDto {
|
|||||||
carAngles?: Record<string, { captured: boolean; url?: string }>;
|
carAngles?: Record<string, { captured: boolean; url?: string }>;
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: 'Damaged parts captured' })
|
@ApiPropertyOptional({ description: 'Damaged parts captured' })
|
||||||
damagedParts?: Record<string, { captured: boolean; url?: string }>;
|
damagedParts?: Record<string, { label_fa: string; captured: boolean; url?: string }>;
|
||||||
|
|
||||||
@ApiPropertyOptional({
|
@ApiPropertyOptional({
|
||||||
description:
|
description:
|
||||||
@@ -84,6 +84,15 @@ export class ClaimDetailsV2ResponseDto {
|
|||||||
})
|
})
|
||||||
expertResend?: ExpertResendDetailsV2Dto;
|
expertResend?: ExpertResendDetailsV2Dto;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: "Damage expert opinion(s): initial and final (after objection).",
|
||||||
|
type: Object,
|
||||||
|
})
|
||||||
|
evaluation?: {
|
||||||
|
damageExpertReply?: unknown;
|
||||||
|
damageExpertReplyFinal?: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: 'User satisfaction rating (if submitted)' })
|
@ApiPropertyOptional({ description: 'User satisfaction rating (if submitted)' })
|
||||||
userRating?: {
|
userRating?: {
|
||||||
progressSpeed: number;
|
progressSpeed: number;
|
||||||
|
|||||||
@@ -1122,7 +1122,31 @@ export class ExpertBlameService {
|
|||||||
requestId,
|
requestId,
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: Send notifications to parties (SMS/Push) about required documents
|
const requestIdToken = String(request._id);
|
||||||
|
const partyPhoneByUserId = new Map<string, string>();
|
||||||
|
for (const p of request.parties || []) {
|
||||||
|
const uid = p?.person?.userId ? String(p.person.userId) : "";
|
||||||
|
const phone = p?.person?.phoneNumber;
|
||||||
|
if (uid && typeof phone === "string" && phone.length > 0) {
|
||||||
|
partyPhoneByUserId.set(uid, phone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const p of partyResendRequests) {
|
||||||
|
const uid = String((p as any).partyId);
|
||||||
|
const phone = partyPhoneByUserId.get(uid);
|
||||||
|
if (!phone) continue;
|
||||||
|
const role =
|
||||||
|
String(request.parties?.find((x: any) => String(x?.person?.userId) === uid)?.role) ===
|
||||||
|
"SECOND"
|
||||||
|
? "SECOND"
|
||||||
|
: "FIRST";
|
||||||
|
await this.smsOrchestrationService.sendResendDocumentsNotice({
|
||||||
|
receptor: phone,
|
||||||
|
fileKind: "blame",
|
||||||
|
publicId: request.publicId,
|
||||||
|
link: this.smsOrchestrationService.buildBlamePartyLink(requestIdToken, role),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
requestId: String(request._id),
|
requestId: String(request._id),
|
||||||
@@ -1277,6 +1301,27 @@ export class ExpertBlameService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (request.type === BlameRequestType.THIRD_PARTY) {
|
||||||
|
const requestIdToken = String(request._id);
|
||||||
|
const expertLastName =
|
||||||
|
actor?.fullName?.trim()?.split(/\s+/).pop() || "کارشناس";
|
||||||
|
for (const p of request.parties || []) {
|
||||||
|
const phone = p?.person?.phoneNumber;
|
||||||
|
if (!phone || typeof phone !== "string") continue;
|
||||||
|
const role = String(p?.role) === "SECOND" ? "SECOND" : "FIRST";
|
||||||
|
await this.smsOrchestrationService.sendSignatureReviewNotice({
|
||||||
|
receptor: phone,
|
||||||
|
fileKind: "blame",
|
||||||
|
publicId: request.publicId,
|
||||||
|
expertLastName,
|
||||||
|
link: this.smsOrchestrationService.buildBlamePartyLink(
|
||||||
|
requestIdToken,
|
||||||
|
role,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
requestId: String(request._id),
|
requestId: String(request._id),
|
||||||
status: CaseStatus.WAITING_FOR_SIGNATURES,
|
status: CaseStatus.WAITING_FOR_SIGNATURES,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { ClientModule } from "src/client/client.module";
|
|||||||
import { ExpertBlameModule } from "src/expert-blame/expert-blame.module";
|
import { ExpertBlameModule } from "src/expert-blame/expert-blame.module";
|
||||||
import { RequestManagementModule } from "src/request-management/request-management.module";
|
import { RequestManagementModule } from "src/request-management/request-management.module";
|
||||||
import { SandHubModule } from "src/sand-hub/sand-hub.module";
|
import { SandHubModule } from "src/sand-hub/sand-hub.module";
|
||||||
|
import { SmsOrchestrationModule } from "src/sms-orchestration/sms-orchestration.module";
|
||||||
import { UsersModule } from "src/users/users.module";
|
import { UsersModule } from "src/users/users.module";
|
||||||
import { ExpertClaimController } from "./expert-claim.controller";
|
import { ExpertClaimController } from "./expert-claim.controller";
|
||||||
import { ExpertClaimV2Controller } from "./expert-claim.v2.controller";
|
import { ExpertClaimV2Controller } from "./expert-claim.v2.controller";
|
||||||
@@ -31,6 +32,7 @@ import { ExpertClaimService } from "./expert-claim.service";
|
|||||||
ExpertBlameModule,
|
ExpertBlameModule,
|
||||||
AiModule,
|
AiModule,
|
||||||
RequestManagementModule,
|
RequestManagementModule,
|
||||||
|
SmsOrchestrationModule,
|
||||||
UsersModule,
|
UsersModule,
|
||||||
ClientModule,
|
ClientModule,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ import {
|
|||||||
import { resendRequestHasPayload } from "src/helpers/claim-expert-resend";
|
import { resendRequestHasPayload } from "src/helpers/claim-expert-resend";
|
||||||
import { snapshotFromDamageExpert } from "src/helpers/expert-profile-snapshot";
|
import { snapshotFromDamageExpert } from "src/helpers/expert-profile-snapshot";
|
||||||
import { DamageExpertModel } from "src/users/entities/schema/damage-expert.schema";
|
import { DamageExpertModel } from "src/users/entities/schema/damage-expert.schema";
|
||||||
|
import { SmsOrchestrationService } from "src/sms-orchestration/sms-orchestration.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ExpertClaimService {
|
export class ExpertClaimService {
|
||||||
@@ -187,6 +188,7 @@ export class ExpertClaimService {
|
|||||||
private readonly claimFactorsImageDbService: ClaimFactorsImageDbService,
|
private readonly claimFactorsImageDbService: ClaimFactorsImageDbService,
|
||||||
private readonly claimRequiredDocumentDbService: ClaimRequiredDocumentDbService,
|
private readonly claimRequiredDocumentDbService: ClaimRequiredDocumentDbService,
|
||||||
private readonly branchDbService: BranchDbService,
|
private readonly branchDbService: BranchDbService,
|
||||||
|
private readonly smsOrchestrationService: SmsOrchestrationService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
/** Load immutable profile fields from `damage-expert` for the acting expert. */
|
/** Load immutable profile fields from `damage-expert` for the acting expert. */
|
||||||
@@ -282,13 +284,16 @@ export class ExpertClaimService {
|
|||||||
private blameFileContextForExpert(blame: any): {
|
private blameFileContextForExpert(blame: any): {
|
||||||
blameRequestType?: BlameRequestType;
|
blameRequestType?: BlameRequestType;
|
||||||
carBodyFirstForm?: { car?: boolean; object?: boolean };
|
carBodyFirstForm?: { car?: boolean; object?: boolean };
|
||||||
|
blameStatus?: string
|
||||||
} {
|
} {
|
||||||
if (!blame?.type) return {};
|
if (!blame?.type) return {};
|
||||||
const blameRequestType = blame.type as BlameRequestType;
|
const blameRequestType = blame.type as BlameRequestType;
|
||||||
const out: {
|
const out: {
|
||||||
blameRequestType?: BlameRequestType;
|
blameRequestType?: BlameRequestType;
|
||||||
carBodyFirstForm?: { car?: boolean; object?: boolean };
|
carBodyFirstForm?: { car?: boolean; object?: boolean };
|
||||||
|
blameStatus?: string;
|
||||||
} = { blameRequestType };
|
} = { blameRequestType };
|
||||||
|
out.blameStatus = blame.blameStatus;
|
||||||
if (blameRequestType !== BlameRequestType.CAR_BODY) return out;
|
if (blameRequestType !== BlameRequestType.CAR_BODY) return out;
|
||||||
|
|
||||||
const parties = blame.parties;
|
const parties = blame.parties;
|
||||||
@@ -297,6 +302,7 @@ export class ExpertClaimService {
|
|||||||
const first =
|
const first =
|
||||||
parties.find((p: any) => p?.role === PartyRole.FIRST) ?? parties[0];
|
parties.find((p: any) => p?.role === PartyRole.FIRST) ?? parties[0];
|
||||||
const cbf = first?.carBodyFirstForm;
|
const cbf = first?.carBodyFirstForm;
|
||||||
|
delete out.blameStatus
|
||||||
if (cbf && typeof cbf === "object") {
|
if (cbf && typeof cbf === "object") {
|
||||||
out.carBodyFirstForm = {
|
out.carBodyFirstForm = {
|
||||||
...(typeof cbf.car === "boolean" ? { car: cbf.car } : {}),
|
...(typeof cbf.car === "boolean" ? { car: cbf.car } : {}),
|
||||||
@@ -2219,6 +2225,30 @@ export class ExpertClaimService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (claim.blameRequestId) {
|
||||||
|
const blame = await this.blameRequestDbService.findById(
|
||||||
|
claim.blameRequestId.toString(),
|
||||||
|
);
|
||||||
|
if (blame?.type === BlameRequestType.THIRD_PARTY) {
|
||||||
|
const ownerUserId = claim.owner?.userId ? String(claim.owner.userId) : "";
|
||||||
|
const ownerParty = (blame.parties || []).find(
|
||||||
|
(p: any) =>
|
||||||
|
p?.person?.userId && String(p.person.userId) === ownerUserId,
|
||||||
|
);
|
||||||
|
const ownerPhone = ownerParty?.person?.phoneNumber;
|
||||||
|
if (ownerPhone && typeof ownerPhone === "string") {
|
||||||
|
await this.smsOrchestrationService.sendResendDocumentsNotice({
|
||||||
|
receptor: ownerPhone,
|
||||||
|
fileKind: "claim",
|
||||||
|
publicId: claim.publicId,
|
||||||
|
link: this.smsOrchestrationService.buildClaimLink(
|
||||||
|
String(claim._id),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
claimRequestId,
|
claimRequestId,
|
||||||
status: ClaimCaseStatus.WAITING_FOR_USER_RESEND,
|
status: ClaimCaseStatus.WAITING_FOR_USER_RESEND,
|
||||||
@@ -2371,6 +2401,33 @@ export class ExpertClaimService {
|
|||||||
|
|
||||||
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, updatePayload);
|
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, updatePayload);
|
||||||
|
|
||||||
|
if (!needsFactorUpload && claim.blameRequestId) {
|
||||||
|
const blame = await this.blameRequestDbService.findById(
|
||||||
|
claim.blameRequestId.toString(),
|
||||||
|
);
|
||||||
|
if (blame?.type === BlameRequestType.THIRD_PARTY) {
|
||||||
|
const ownerUserId = claim.owner?.userId ? String(claim.owner.userId) : "";
|
||||||
|
const ownerParty = (blame.parties || []).find(
|
||||||
|
(p: any) =>
|
||||||
|
p?.person?.userId && String(p.person.userId) === ownerUserId,
|
||||||
|
);
|
||||||
|
const ownerPhone = ownerParty?.person?.phoneNumber;
|
||||||
|
if (ownerPhone && typeof ownerPhone === "string") {
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
claimRequestId,
|
claimRequestId,
|
||||||
status: nextCaseStatus,
|
status: nextCaseStatus,
|
||||||
@@ -2519,7 +2576,7 @@ export class ExpertClaimService {
|
|||||||
blameIds.length > 0
|
blameIds.length > 0
|
||||||
? ((await this.blameRequestDbService.find(
|
? ((await this.blameRequestDbService.find(
|
||||||
{ _id: { $in: blameIds.map((id) => new Types.ObjectId(id)) } },
|
{ _id: { $in: blameIds.map((id) => new Types.ObjectId(id)) } },
|
||||||
{ lean: true, select: "type parties expert.decision" },
|
{ lean: true, select: "type parties expert.decision blameStatus" },
|
||||||
)) as any[])
|
)) as any[])
|
||||||
: [];
|
: [];
|
||||||
const blameById = new Map<string, any>(
|
const blameById = new Map<string, any>(
|
||||||
|
|||||||
@@ -38,7 +38,12 @@ async function bootstrap() {
|
|||||||
.addBearerAuth()
|
.addBearerAuth()
|
||||||
.build();
|
.build();
|
||||||
const docs = SwaggerModule.createDocument(app, config);
|
const docs = SwaggerModule.createDocument(app, config);
|
||||||
SwaggerModule.setup("/docs", app, docs);
|
SwaggerModule.setup("/docs", app, docs, {
|
||||||
|
swaggerOptions: {
|
||||||
|
persistAuthorization: true,
|
||||||
|
docExpansion: "none",
|
||||||
|
},
|
||||||
|
});
|
||||||
await app.listen(process.env.PORT);
|
await app.listen(process.env.PORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export interface VerifyLookUpMessage {
|
|||||||
token: string;
|
token: string;
|
||||||
token2?: string;
|
token2?: string;
|
||||||
token3?: string;
|
token3?: string;
|
||||||
|
token10?: string;
|
||||||
receptor: string;
|
receptor: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ type TemplateArgs = {
|
|||||||
token: string;
|
token: string;
|
||||||
token2?: string;
|
token2?: string;
|
||||||
token3?: string;
|
token3?: string;
|
||||||
|
token10?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -40,6 +41,15 @@ export class SmsOrchestrationService implements OnModuleInit {
|
|||||||
return `${process.env.URL}/${frontendRoute}?token=${requestId}`;
|
return `${process.env.URL}/${frontendRoute}?token=${requestId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildBlamePartyLink(requestId: string, partyRole: "FIRST" | "SECOND"): string {
|
||||||
|
const route = partyRole === "SECOND" ? "user2" : "user";
|
||||||
|
return `${process.env.URL}/${route}?token=${requestId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
buildClaimLink(claimRequestId: string): string {
|
||||||
|
return `${process.env.URL}/claim?token=${claimRequestId}`;
|
||||||
|
}
|
||||||
|
|
||||||
async sendInviteLink(phoneNumber: string, link: string): Promise<boolean> {
|
async sendInviteLink(phoneNumber: string, link: string): Promise<boolean> {
|
||||||
return this.sendTemplate({
|
return this.sendTemplate({
|
||||||
template: "yara724-invite-link",
|
template: "yara724-invite-link",
|
||||||
@@ -124,6 +134,38 @@ export class SmsOrchestrationService implements OnModuleInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async sendResendDocumentsNotice(params: {
|
||||||
|
receptor: string;
|
||||||
|
fileKind: "blame" | "claim";
|
||||||
|
publicId: string;
|
||||||
|
link: string;
|
||||||
|
}): Promise<boolean> {
|
||||||
|
return this.sendTemplate({
|
||||||
|
template: "yara-resend-documents",
|
||||||
|
receptor: params.receptor,
|
||||||
|
token: params.fileKind === "blame" ? "تصادف" : "خسارت",
|
||||||
|
token2: params.publicId,
|
||||||
|
token3: params.link,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendSignatureReviewNotice(params: {
|
||||||
|
receptor: string;
|
||||||
|
fileKind: "blame" | "claim";
|
||||||
|
publicId: string;
|
||||||
|
expertLastName: string;
|
||||||
|
link: string;
|
||||||
|
}): Promise<boolean> {
|
||||||
|
return this.sendTemplate({
|
||||||
|
template: "yara-signature",
|
||||||
|
receptor: params.receptor,
|
||||||
|
token: params.fileKind === "blame" ? "تصادف" : "خسارت",
|
||||||
|
token2: params.publicId,
|
||||||
|
token3: params.expertLastName || "کارشناس",
|
||||||
|
token10: params.link,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private async sendTemplate(args: TemplateArgs): Promise<boolean> {
|
private async sendTemplate(args: TemplateArgs): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
await this.smsGatewayService.verifyLookUp(args);
|
await this.smsGatewayService.verifyLookUp(args);
|
||||||
|
|||||||
Reference in New Issue
Block a user