forked from Yara724/api
Compare commits
3 Commits
c6f7edabd2
...
194b71cdb2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
194b71cdb2 | ||
|
|
04ba46ed2a | ||
|
|
95bfc095ce |
@@ -4987,14 +4987,14 @@ export class ClaimRequestManagementService {
|
||||
data && (data instanceof Map ? data.get(key) : data[key]);
|
||||
|
||||
const requiredDocs = claim.requiredDocuments as any;
|
||||
const requiredDocumentsStatus: Record<string, { uploaded: boolean; fileId?: string }> = {};
|
||||
const requiredDocumentsStatus: Record<string, { uploaded: boolean; fileUrl?: string }> = {};
|
||||
if (requiredDocs) {
|
||||
const keys = requiredDocs instanceof Map ? Array.from(requiredDocs.keys()) : Object.keys(requiredDocs);
|
||||
for (const k of keys) {
|
||||
const doc = requiredDocs instanceof Map ? requiredDocs.get(k) : requiredDocs[k];
|
||||
requiredDocumentsStatus[k] = {
|
||||
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;
|
||||
const maskNationalCode = (s?: string) =>
|
||||
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 {
|
||||
claimRequestId: claim._id.toString(),
|
||||
@@ -5052,34 +5071,28 @@ export class ClaimRequestManagementService {
|
||||
nextStep: claim.workflow?.nextStep,
|
||||
blameRequestId: claim.blameRequestId?.toString(),
|
||||
blameRequestNo: claim.blameRequestNo,
|
||||
owner: claim.owner
|
||||
? {
|
||||
userId: claim.owner.userId?.toString(),
|
||||
fullName: claim.owner.fullName,
|
||||
}
|
||||
: undefined,
|
||||
...(isExpertViewer ? { owner: ownerData } : {}),
|
||||
vehicle: claim.vehicle,
|
||||
selectedParts: claim.damage?.selectedParts,
|
||||
otherParts: claim.damage?.otherParts,
|
||||
money: claim.money
|
||||
? {
|
||||
sheba: maskSheba(claim.money.sheba),
|
||||
nationalCodeOfOwner: maskNationalCode(claim.money.nationalCodeOfInsurer),
|
||||
}
|
||||
: undefined,
|
||||
money: isExpertViewer ? moneyForExpert : moneyForUser,
|
||||
requiredDocuments: Object.keys(requiredDocumentsStatus).length > 0 ? requiredDocumentsStatus : undefined,
|
||||
carAngles,
|
||||
damagedParts,
|
||||
expertResend,
|
||||
userRating: claim.userRating
|
||||
...(isExpertViewer
|
||||
? {
|
||||
progressSpeed: claim.userRating.progressSpeed,
|
||||
registrationEase: claim.userRating.registrationEase,
|
||||
overallEvaluation: claim.userRating.overallEvaluation,
|
||||
comment: claim.userRating.comment,
|
||||
createdAt: claim.userRating.createdAt,
|
||||
userRating: claim.userRating
|
||||
? {
|
||||
progressSpeed: claim.userRating.progressSpeed,
|
||||
registrationEase: claim.userRating.registrationEase,
|
||||
overallEvaluation: claim.userRating.overallEvaluation,
|
||||
comment: claim.userRating.comment,
|
||||
createdAt: claim.userRating.createdAt,
|
||||
}
|
||||
: undefined,
|
||||
}
|
||||
: undefined,
|
||||
: {}),
|
||||
createdAt: (claim as any).createdAt,
|
||||
updatedAt: (claim as any).updatedAt,
|
||||
};
|
||||
|
||||
@@ -75,7 +75,8 @@ export class ClaimRequestManagementV2Controller {
|
||||
})
|
||||
@ApiOperation({
|
||||
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({
|
||||
status: 200,
|
||||
@@ -560,8 +561,7 @@ Returns status of each item (uploaded/captured or not).
|
||||
description: `
|
||||
**Workflow Step:** UPLOAD_REQUIRED_DOCUMENTS (Step 5 of Claim)
|
||||
|
||||
**Upload one of the required documents** (13 for THIRD_PARTY; CAR_BODY may require fewer — see capture-requirements):
|
||||
- car_green_card
|
||||
**Upload one of the required documents** (12 for THIRD_PARTY; CAR_BODY may require fewer — see capture-requirements):
|
||||
- damaged_driving_license_front/back
|
||||
- damaged_chassis_number, damaged_engine_photo
|
||||
- damaged_car_card_front/back, damaged_metal_plate
|
||||
|
||||
@@ -69,8 +69,8 @@ export class ClaimDetailsV2ResponseDto {
|
||||
nationalCodeOfOwner?: string;
|
||||
};
|
||||
|
||||
@ApiPropertyOptional({ description: 'Required documents status' })
|
||||
requiredDocuments?: Record<string, { uploaded: boolean; fileId?: string }>;
|
||||
@ApiPropertyOptional({ description: 'Required documents status (link instead of id)' })
|
||||
requiredDocuments?: Record<string, { uploaded: boolean; fileUrl?: string }>;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Car angles captured' })
|
||||
carAngles?: Record<string, { captured: boolean; url?: string }>;
|
||||
|
||||
@@ -46,15 +46,24 @@ function writeFaTimestampsOnUpdate(query: Query<any, any>): void {
|
||||
update.$set = {};
|
||||
}
|
||||
|
||||
if (update.$set.createdAtFa == null && update.createdAtFa == null) {
|
||||
update.$set.createdAtFa = nowFa;
|
||||
}
|
||||
update.$set.updatedAtFa = nowFa;
|
||||
|
||||
// Keep createdAtFa immutable on regular updates; set only for upsert inserts.
|
||||
const opts = (query as any).getOptions?.() ?? {};
|
||||
if (opts.upsert) {
|
||||
if (!update.$setOnInsert || typeof update.$setOnInsert !== "object") {
|
||||
update.$setOnInsert = {};
|
||||
}
|
||||
if (update.$setOnInsert.createdAtFa == null) {
|
||||
update.$setOnInsert.createdAtFa = nowFa;
|
||||
}
|
||||
}
|
||||
|
||||
query.setUpdate(update);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds `createdAtFa` / `updatedAtFa` (Iran timezone, UTC+03:30 offset string)
|
||||
* Adds `createdAtFa` / `updatedAtFa` (Iran timezone tuple: [date, time])
|
||||
* to all schemas that use mongoose timestamps.
|
||||
*/
|
||||
export function applyIranFaTimestampPlugin(connection: Connection): void {
|
||||
|
||||
@@ -79,6 +79,7 @@ import {
|
||||
buildMutualAgreementExpertDecision,
|
||||
MUTUAL_AGREEMENT_EXPERT_DECISION_FIELDS,
|
||||
} from "src/helpers/blame-party-agreement-decision";
|
||||
import { buildFileLink } from "src/helpers/urlCreator";
|
||||
|
||||
@Injectable()
|
||||
export class RequestManagementService {
|
||||
@@ -4117,6 +4118,133 @@ export class RequestManagementService {
|
||||
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)
|
||||
* 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"
|
||||
? (req as any).toObject({ versionKey: false })
|
||||
: { ...(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({
|
||||
summary: "Get one blame request (v2)",
|
||||
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" })
|
||||
@Roles(RoleEnum.USER, RoleEnum.FIELD_EXPERT)
|
||||
|
||||
Reference in New Issue
Block a user