forked from Yara724/api
YARA-821
This commit is contained in:
@@ -4987,14 +4987,14 @@ export class ClaimRequestManagementService {
|
|||||||
data && (data instanceof Map ? data.get(key) : data[key]);
|
data && (data instanceof Map ? data.get(key) : data[key]);
|
||||||
|
|
||||||
const requiredDocs = claim.requiredDocuments as any;
|
const requiredDocs = claim.requiredDocuments as any;
|
||||||
const requiredDocumentsStatus: Record<string, { uploaded: boolean; fileId?: string }> = {};
|
const requiredDocumentsStatus: Record<string, { uploaded: boolean; fileUrl?: string }> = {};
|
||||||
if (requiredDocs) {
|
if (requiredDocs) {
|
||||||
const keys = requiredDocs instanceof Map ? Array.from(requiredDocs.keys()) : Object.keys(requiredDocs);
|
const keys = requiredDocs instanceof Map ? Array.from(requiredDocs.keys()) : Object.keys(requiredDocs);
|
||||||
for (const k of keys) {
|
for (const k of keys) {
|
||||||
const doc = requiredDocs instanceof Map ? requiredDocs.get(k) : requiredDocs[k];
|
const doc = requiredDocs instanceof Map ? requiredDocs.get(k) : requiredDocs[k];
|
||||||
requiredDocumentsStatus[k] = {
|
requiredDocumentsStatus[k] = {
|
||||||
uploaded: !!doc?.uploaded,
|
uploaded: !!doc?.uploaded,
|
||||||
fileId: doc?.fileId?.toString(),
|
fileUrl: doc?.filePath ? buildFileLink(doc.filePath) : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5041,6 +5041,25 @@ export class ClaimRequestManagementService {
|
|||||||
s ? s.replace(/^(.{4})(.*)(.{4})$/, 'IR$1************$3') : undefined;
|
s ? s.replace(/^(.{4})(.*)(.{4})$/, 'IR$1************$3') : undefined;
|
||||||
const maskNationalCode = (s?: string) =>
|
const maskNationalCode = (s?: string) =>
|
||||||
s ? s.replace(/^(.{2})(.*)(.{2})$/, '$1******$3') : undefined;
|
s ? s.replace(/^(.{2})(.*)(.{2})$/, '$1******$3') : undefined;
|
||||||
|
const isExpertViewer = actor?.role === RoleEnum.FIELD_EXPERT;
|
||||||
|
const ownerData = claim.owner
|
||||||
|
? {
|
||||||
|
userId: claim.owner.userId?.toString(),
|
||||||
|
fullName: claim.owner.fullName,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
const moneyForUser = claim.money
|
||||||
|
? {
|
||||||
|
sheba: maskSheba(claim.money.sheba),
|
||||||
|
nationalCodeOfOwner: maskNationalCode(claim.money.nationalCodeOfInsurer),
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
const moneyForExpert = claim.money
|
||||||
|
? {
|
||||||
|
sheba: claim.money.sheba,
|
||||||
|
nationalCodeOfOwner: claim.money.nationalCodeOfInsurer,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
claimRequestId: claim._id.toString(),
|
claimRequestId: claim._id.toString(),
|
||||||
@@ -5052,25 +5071,17 @@ export class ClaimRequestManagementService {
|
|||||||
nextStep: claim.workflow?.nextStep,
|
nextStep: claim.workflow?.nextStep,
|
||||||
blameRequestId: claim.blameRequestId?.toString(),
|
blameRequestId: claim.blameRequestId?.toString(),
|
||||||
blameRequestNo: claim.blameRequestNo,
|
blameRequestNo: claim.blameRequestNo,
|
||||||
owner: claim.owner
|
...(isExpertViewer ? { owner: ownerData } : {}),
|
||||||
? {
|
|
||||||
userId: claim.owner.userId?.toString(),
|
|
||||||
fullName: claim.owner.fullName,
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
vehicle: claim.vehicle,
|
vehicle: claim.vehicle,
|
||||||
selectedParts: claim.damage?.selectedParts,
|
selectedParts: claim.damage?.selectedParts,
|
||||||
otherParts: claim.damage?.otherParts,
|
otherParts: claim.damage?.otherParts,
|
||||||
money: claim.money
|
money: isExpertViewer ? moneyForExpert : moneyForUser,
|
||||||
? {
|
|
||||||
sheba: maskSheba(claim.money.sheba),
|
|
||||||
nationalCodeOfOwner: maskNationalCode(claim.money.nationalCodeOfInsurer),
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
requiredDocuments: Object.keys(requiredDocumentsStatus).length > 0 ? requiredDocumentsStatus : undefined,
|
requiredDocuments: Object.keys(requiredDocumentsStatus).length > 0 ? requiredDocumentsStatus : undefined,
|
||||||
carAngles,
|
carAngles,
|
||||||
damagedParts,
|
damagedParts,
|
||||||
expertResend,
|
expertResend,
|
||||||
|
...(isExpertViewer
|
||||||
|
? {
|
||||||
userRating: claim.userRating
|
userRating: claim.userRating
|
||||||
? {
|
? {
|
||||||
progressSpeed: claim.userRating.progressSpeed,
|
progressSpeed: claim.userRating.progressSpeed,
|
||||||
@@ -5080,6 +5091,8 @@ export class ClaimRequestManagementService {
|
|||||||
createdAt: claim.userRating.createdAt,
|
createdAt: claim.userRating.createdAt,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
createdAt: (claim as any).createdAt,
|
createdAt: (claim as any).createdAt,
|
||||||
updatedAt: (claim as any).updatedAt,
|
updatedAt: (claim as any).updatedAt,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -75,7 +75,8 @@ export class ClaimRequestManagementV2Controller {
|
|||||||
})
|
})
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "Get Claim Details (V2)",
|
summary: "Get Claim Details (V2)",
|
||||||
description: "Get full details of a claim request. Only the claim owner can access.",
|
description:
|
||||||
|
"Get claim details for current actor. USER receives only minimal own-needed payload (no extra owner/security fields). FIELD_EXPERT keeps full view for expert workflows.",
|
||||||
})
|
})
|
||||||
@ApiResponse({
|
@ApiResponse({
|
||||||
status: 200,
|
status: 200,
|
||||||
|
|||||||
@@ -69,8 +69,8 @@ export class ClaimDetailsV2ResponseDto {
|
|||||||
nationalCodeOfOwner?: string;
|
nationalCodeOfOwner?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: 'Required documents status' })
|
@ApiPropertyOptional({ description: 'Required documents status (link instead of id)' })
|
||||||
requiredDocuments?: Record<string, { uploaded: boolean; fileId?: string }>;
|
requiredDocuments?: Record<string, { uploaded: boolean; fileUrl?: string }>;
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: 'Car angles captured' })
|
@ApiPropertyOptional({ description: 'Car angles captured' })
|
||||||
carAngles?: Record<string, { captured: boolean; url?: string }>;
|
carAngles?: Record<string, { captured: boolean; url?: string }>;
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ import {
|
|||||||
buildMutualAgreementExpertDecision,
|
buildMutualAgreementExpertDecision,
|
||||||
MUTUAL_AGREEMENT_EXPERT_DECISION_FIELDS,
|
MUTUAL_AGREEMENT_EXPERT_DECISION_FIELDS,
|
||||||
} from "src/helpers/blame-party-agreement-decision";
|
} from "src/helpers/blame-party-agreement-decision";
|
||||||
|
import { buildFileLink } from "src/helpers/urlCreator";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RequestManagementService {
|
export class RequestManagementService {
|
||||||
@@ -4117,6 +4118,133 @@ export class RequestManagementService {
|
|||||||
return none;
|
return none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Party payload for user blame detail: no national codes, licenses, phone, birthdays, or vehicle inquiry blobs. */
|
||||||
|
private async sanitizePartyForBlameUserView(
|
||||||
|
party: any,
|
||||||
|
): Promise<Record<string, unknown>> {
|
||||||
|
if (!party || typeof party !== "object") {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const person = party.person;
|
||||||
|
const safePerson = person
|
||||||
|
? {
|
||||||
|
userId:
|
||||||
|
person.userId != null ? String(person.userId) : undefined,
|
||||||
|
fullName: person.fullName,
|
||||||
|
clientId:
|
||||||
|
person.clientId != null ? String(person.clientId) : undefined,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
const vehicle = party.vehicle
|
||||||
|
? {
|
||||||
|
plateId: party.vehicle.plateId,
|
||||||
|
name: party.vehicle.name,
|
||||||
|
model: party.vehicle.model,
|
||||||
|
type: party.vehicle.type,
|
||||||
|
isNew: party.vehicle.isNew,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
const evidenceRaw = party.evidence as Record<string, unknown> | undefined;
|
||||||
|
const evidence: Record<string, unknown> | undefined = evidenceRaw
|
||||||
|
? { ...evidenceRaw }
|
||||||
|
: undefined;
|
||||||
|
if (evidence) {
|
||||||
|
if (party.role === PartyRole.FIRST && evidence.videoId) {
|
||||||
|
const videoDoc = await this.blameVideoDbService.findById(
|
||||||
|
String(evidence.videoId),
|
||||||
|
);
|
||||||
|
if (videoDoc?.path) {
|
||||||
|
evidence.videoUrl = buildFileLink(videoDoc.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete evidence.videoId;
|
||||||
|
|
||||||
|
const voiceIds = evidence.voices;
|
||||||
|
if (Array.isArray(voiceIds)) {
|
||||||
|
const voiceUrls: string[] = [];
|
||||||
|
for (const voiceId of voiceIds) {
|
||||||
|
const voiceDoc = await this.blameVoiceDbService.findById(
|
||||||
|
String(voiceId),
|
||||||
|
);
|
||||||
|
if (voiceDoc?.path) {
|
||||||
|
voiceUrls.push(buildFileLink(voiceDoc.path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
evidence.voiceUrls = voiceUrls;
|
||||||
|
}
|
||||||
|
delete evidence.voices;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
role: party.role,
|
||||||
|
person: safePerson,
|
||||||
|
carBodyFirstForm: party.carBodyFirstForm,
|
||||||
|
location: party.location,
|
||||||
|
vehicle,
|
||||||
|
insurance: party.insurance,
|
||||||
|
statement: party.statement,
|
||||||
|
evidence,
|
||||||
|
confirmation: party.confirmation,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async resolveBlameExpertDisplayNameFromPlain(
|
||||||
|
expert: Record<string, unknown>,
|
||||||
|
): Promise<string | undefined> {
|
||||||
|
const decision = expert.decision as Record<string, unknown> | undefined;
|
||||||
|
const decided = decision?.decidedByExpertId;
|
||||||
|
const assigned = expert.assignedExpertId;
|
||||||
|
const raw = decided ?? assigned;
|
||||||
|
if (raw == null || raw === "") return undefined;
|
||||||
|
const sid = String(raw);
|
||||||
|
if (!Types.ObjectId.isValid(sid)) return undefined;
|
||||||
|
const doc = await this.expertDbService.findOne({
|
||||||
|
_id: new Types.ObjectId(sid),
|
||||||
|
});
|
||||||
|
if (!doc) return undefined;
|
||||||
|
return `${doc.firstName || ""} ${doc.lastName || ""}`.trim() || undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Expert subdocument for user view: string ids, trimmed snapshot (name only), plus `expertName`. */
|
||||||
|
private async buildExpertForBlameUserView(
|
||||||
|
expertRaw: any,
|
||||||
|
): Promise<Record<string, unknown> | undefined> {
|
||||||
|
if (!expertRaw || typeof expertRaw !== "object") return undefined;
|
||||||
|
const out = JSON.parse(JSON.stringify(expertRaw)) as Record<string, unknown>;
|
||||||
|
const decision = out.decision as Record<string, unknown> | undefined;
|
||||||
|
let expertName: string | undefined;
|
||||||
|
|
||||||
|
if (decision) {
|
||||||
|
if (decision.guiltyPartyId != null) {
|
||||||
|
decision.guiltyPartyId = String(decision.guiltyPartyId);
|
||||||
|
}
|
||||||
|
if (decision.decidedByExpertId != null) {
|
||||||
|
decision.decidedByExpertId = String(decision.decidedByExpertId);
|
||||||
|
}
|
||||||
|
const snap = decision.expertProfileSnapshot as
|
||||||
|
| Record<string, unknown>
|
||||||
|
| undefined;
|
||||||
|
if (snap) {
|
||||||
|
expertName =
|
||||||
|
`${snap.firstName ?? ""} ${snap.lastName ?? ""}`.trim() || undefined;
|
||||||
|
decision.expertProfileSnapshot = {
|
||||||
|
firstName: snap.firstName,
|
||||||
|
lastName: snap.lastName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out.assignedExpertId != null) {
|
||||||
|
out.assignedExpertId = String(out.assignedExpertId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!expertName) {
|
||||||
|
expertName = await this.resolveBlameExpertDisplayNameFromPlain(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...out, expertName };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* V2: Get one blame request by id. Access allowed if current user is a party (by userId or phone)
|
* V2: Get one blame request by id. Access allowed if current user is a party (by userId or phone)
|
||||||
* or the initiating field expert. For expert-initiated LINK, party access by phone is sufficient.
|
* or the initiating field expert. For expert-initiated LINK, party access by phone is sufficient.
|
||||||
@@ -4165,7 +4293,36 @@ export class RequestManagementService {
|
|||||||
typeof (req as any).toObject === "function"
|
typeof (req as any).toObject === "function"
|
||||||
? (req as any).toObject({ versionKey: false })
|
? (req as any).toObject({ versionKey: false })
|
||||||
: { ...(req as any) };
|
: { ...(req as any) };
|
||||||
return { ...plain, claimCreation };
|
|
||||||
|
const allParties = Array.isArray(plain.parties) ? plain.parties : [];
|
||||||
|
const visibleParties =
|
||||||
|
isFieldExpertOwner || isRegistrarOwner
|
||||||
|
? allParties
|
||||||
|
: allParties.filter((p: any) => {
|
||||||
|
const pid = p?.person?.userId ? String(p.person.userId) : null;
|
||||||
|
const phone = p?.person?.phoneNumber;
|
||||||
|
return (
|
||||||
|
(pid && pid === String(user?.sub)) ||
|
||||||
|
(phone && phone === user?.username)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
const parties = await Promise.all(
|
||||||
|
visibleParties.map((p: any) => this.sanitizePartyForBlameUserView(p)),
|
||||||
|
);
|
||||||
|
const expert = await this.buildExpertForBlameUserView(plain.expert);
|
||||||
|
|
||||||
|
return {
|
||||||
|
requestNo: plain.requestNo,
|
||||||
|
publicId: plain.publicId,
|
||||||
|
type: plain.type,
|
||||||
|
status: plain.status,
|
||||||
|
blameStatus: plain.blameStatus,
|
||||||
|
workflow: plain.workflow,
|
||||||
|
parties,
|
||||||
|
expert,
|
||||||
|
carBodyInsuranceDetail: plain.carBodyInsuranceDetail,
|
||||||
|
claimCreation,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export class RequestManagementV2Controller {
|
|||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "Get one blame request (v2)",
|
summary: "Get one blame request (v2)",
|
||||||
description:
|
description:
|
||||||
"Response is the blame document plus **claimCreation** ({ hasClaim, shouldGuideToCreateClaim }) — computed for the current user so the app can route the damaged party to create-claim when the blame is COMPLETED and no v2 claim exists yet.",
|
"Returns a minimal, user-safe payload: requestNo, publicId, type, status, blameStatus, workflow, parties (PII stripped), expert (with **expertName**), carBodyInsuranceDetail, plus **claimCreation** ({ hasClaim, shouldGuideToCreateClaim }). History and other internal fields are omitted.",
|
||||||
})
|
})
|
||||||
@ApiParam({ name: "requestId", description: "Blame request ID" })
|
@ApiParam({ name: "requestId", description: "Blame request ID" })
|
||||||
@Roles(RoleEnum.USER, RoleEnum.FIELD_EXPERT)
|
@Roles(RoleEnum.USER, RoleEnum.FIELD_EXPERT)
|
||||||
|
|||||||
Reference in New Issue
Block a user