forked from Yara724/api
Fix workflow steps
This commit is contained in:
@@ -1,5 +1,55 @@
|
|||||||
import { ResendItemType } from "./resendItemType.enum";
|
import { ResendItemType } from "./resendItemType.enum";
|
||||||
|
|
||||||
|
const RESEND_ITEM_VALUES = new Set<string>(Object.values(ResendItemType));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map multipart / client field names and DB typos to canonical {@link ResendItemType} values.
|
||||||
|
*/
|
||||||
|
export function normalizeResendRequestedItemKey(raw: string): string | null {
|
||||||
|
const t = String(raw ?? "").trim();
|
||||||
|
if (!t) return null;
|
||||||
|
if (RESEND_ITEM_VALUES.has(t)) return t;
|
||||||
|
const lower = t.toLowerCase();
|
||||||
|
for (const v of RESEND_ITEM_VALUES) {
|
||||||
|
if (v.toLowerCase() === lower) return v;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Deduplicated list of valid requested item keys. */
|
||||||
|
export function normalizeResendRequestedItemsList(
|
||||||
|
items: string[] | undefined | null,
|
||||||
|
): string[] {
|
||||||
|
const out: string[] = [];
|
||||||
|
const seen = new Set<string>();
|
||||||
|
for (const raw of items || []) {
|
||||||
|
const c = normalizeResendRequestedItemKey(String(raw));
|
||||||
|
if (c && !seen.has(c)) {
|
||||||
|
seen.add(c);
|
||||||
|
out.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clone `uploadedDocuments` from a Mongoose subdoc (plain object or Map) into a plain object
|
||||||
|
* so merges and {@link isResendPartyItemSatisfied} see existing keys.
|
||||||
|
*/
|
||||||
|
export function cloneResendUploadedDocuments(
|
||||||
|
raw: unknown,
|
||||||
|
): Record<string, unknown> {
|
||||||
|
if (raw == null || typeof raw !== "object") return {};
|
||||||
|
if (raw instanceof Map) {
|
||||||
|
const o: Record<string, unknown> = {};
|
||||||
|
for (const [k, v] of raw.entries()) {
|
||||||
|
o[String(k)] = v;
|
||||||
|
}
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
return { ...(raw as Record<string, unknown>) };
|
||||||
|
}
|
||||||
|
|
||||||
/** How the mobile/web client should collect each resend item (no workflow-step manager). */
|
/** How the mobile/web client should collect each resend item (no workflow-step manager). */
|
||||||
export type ResendItemInputKind = "document_camera" | "voice" | "video" | "text";
|
export type ResendItemInputKind = "document_camera" | "voice" | "video" | "text";
|
||||||
|
|
||||||
@@ -11,7 +61,8 @@ export function getResendItemInputKind(item: string): ResendItemInputKind {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function buildResendItemsWithUi(requestedItems: string[]) {
|
export function buildResendItemsWithUi(requestedItems: string[]) {
|
||||||
return requestedItems.map((item) => ({
|
const normalized = normalizeResendRequestedItemsList(requestedItems);
|
||||||
|
return normalized.map((item) => ({
|
||||||
item,
|
item,
|
||||||
inputKind: getResendItemInputKind(item),
|
inputKind: getResendItemInputKind(item),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -34,7 +34,10 @@ import { BlameStatus } from "src/Types&Enums/blame-request-management/blameStatu
|
|||||||
import { BlameRequestType } from "src/Types&Enums/blame-request-management/blameRequestType.enum";
|
import { BlameRequestType } from "src/Types&Enums/blame-request-management/blameRequestType.enum";
|
||||||
import { CaseStatus } from "src/Types&Enums/blame-request-management/caseStatus.enum";
|
import { CaseStatus } from "src/Types&Enums/blame-request-management/caseStatus.enum";
|
||||||
import { ResendItemType } from "src/Types&Enums/blame-request-management/resendItemType.enum";
|
import { ResendItemType } from "src/Types&Enums/blame-request-management/resendItemType.enum";
|
||||||
import { buildResendItemsWithUi } from "src/Types&Enums/blame-request-management/resend-item-ui";
|
import {
|
||||||
|
buildResendItemsWithUi,
|
||||||
|
normalizeResendRequestedItemsList,
|
||||||
|
} from "src/Types&Enums/blame-request-management/resend-item-ui";
|
||||||
import { buildFileLink } from "src/helpers/urlCreator";
|
import { buildFileLink } from "src/helpers/urlCreator";
|
||||||
import { toJalaliDateAndTime } from "src/helpers/date-jalali";
|
import { toJalaliDateAndTime } from "src/helpers/date-jalali";
|
||||||
import { ResendRequestDto } from "./dto/resend.dto";
|
import { ResendRequestDto } from "./dto/resend.dto";
|
||||||
@@ -1148,7 +1151,15 @@ export class ExpertBlameService {
|
|||||||
`partyId ${uid} is not a party userId on this blame request.`,
|
`partyId ${uid} is not a party userId on this blame request.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
for (const item of party.requestedItems || []) {
|
const normalizedItems = normalizeResendRequestedItemsList(
|
||||||
|
party.requestedItems as string[],
|
||||||
|
);
|
||||||
|
if (normalizedItems.length === 0) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
`Party ${uid}: requestedItems must include at least one valid resend item (e.g. drivingLicense, voice, description).`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (const item of normalizedItems) {
|
||||||
if (!allowedItems.has(String(item))) {
|
if (!allowedItems.has(String(item))) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
`Invalid requestedItems value: ${item}. Use ResendItemType values.`,
|
`Invalid requestedItems value: ${item}. Use ResendItemType values.`,
|
||||||
@@ -1162,9 +1173,12 @@ export class ExpertBlameService {
|
|||||||
|
|
||||||
const partyResendRequests = resendDto.parties.map((party) => {
|
const partyResendRequests = resendDto.parties.map((party) => {
|
||||||
const uid = String(party.partyId);
|
const uid = String(party.partyId);
|
||||||
|
const normalizedItems = normalizeResendRequestedItemsList(
|
||||||
|
party.requestedItems as string[],
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
partyId: new Types.ObjectId(uid),
|
partyId: new Types.ObjectId(uid),
|
||||||
requestedItems: party.requestedItems,
|
requestedItems: normalizedItems,
|
||||||
description: party.description || "",
|
description: party.description || "",
|
||||||
requestedAt: now,
|
requestedAt: now,
|
||||||
completed: false,
|
completed: false,
|
||||||
@@ -1228,7 +1242,7 @@ export class ExpertBlameService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const partyHasResendPayload = (row: {
|
const partyHasResendPayload = (row: {
|
||||||
requestedItems?: ResendItemType[];
|
requestedItems?: readonly string[];
|
||||||
description?: string;
|
description?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const n = Array.isArray(row.requestedItems)
|
const n = Array.isArray(row.requestedItems)
|
||||||
|
|||||||
@@ -2546,8 +2546,8 @@ export class ExpertClaimService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const nextStep = needsFactorUpload
|
const nextStep = needsFactorUpload
|
||||||
? ClaimWorkflowStep.EXPERT_COST_EVALUATION
|
? ClaimWorkflowStep.INSURER_REVIEW
|
||||||
: ClaimWorkflowStep.INSURER_REVIEW;
|
: ClaimWorkflowStep.CLAIM_COMPLETED;
|
||||||
|
|
||||||
const nextCaseStatus = ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL;
|
const nextCaseStatus = ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL;
|
||||||
const nextClaimStatus = needsFactorUpload
|
const nextClaimStatus = needsFactorUpload
|
||||||
@@ -2566,7 +2566,7 @@ export class ExpertClaimService {
|
|||||||
},
|
},
|
||||||
'workflow.currentStep': nextStep,
|
'workflow.currentStep': nextStep,
|
||||||
'workflow.nextStep': needsFactorUpload
|
'workflow.nextStep': needsFactorUpload
|
||||||
? ClaimWorkflowStep.INSURER_REVIEW
|
? ClaimWorkflowStep.EXPERT_COST_EVALUATION
|
||||||
: ClaimWorkflowStep.CLAIM_COMPLETED,
|
: ClaimWorkflowStep.CLAIM_COMPLETED,
|
||||||
[`evaluation.${replyField}`]: replyPayload,
|
[`evaluation.${replyField}`]: replyPayload,
|
||||||
$push: {
|
$push: {
|
||||||
|
|||||||
@@ -28,7 +28,10 @@ import { UserSignatureResponseDto } from "./dto/user-signature.dto";
|
|||||||
import { ResendItemType } from "src/Types&Enums/blame-request-management/resendItemType.enum";
|
import { ResendItemType } from "src/Types&Enums/blame-request-management/resendItemType.enum";
|
||||||
import {
|
import {
|
||||||
buildResendItemsWithUi,
|
buildResendItemsWithUi,
|
||||||
|
cloneResendUploadedDocuments,
|
||||||
isResendPartyItemSatisfied,
|
isResendPartyItemSatisfied,
|
||||||
|
normalizeResendRequestedItemKey,
|
||||||
|
normalizeResendRequestedItemsList,
|
||||||
} from "src/Types&Enums/blame-request-management/resend-item-ui";
|
} from "src/Types&Enums/blame-request-management/resend-item-ui";
|
||||||
import { ClaimCaseDbService } from "src/claim-request-management/entites/db-service/claim-case.db.service";
|
import { ClaimCaseDbService } from "src/claim-request-management/entites/db-service/claim-case.db.service";
|
||||||
import { ClaimCaseStatus } from "src/Types&Enums/claim-request-management/claim-case-status.enum";
|
import { ClaimCaseStatus } from "src/Types&Enums/claim-request-management/claim-case-status.enum";
|
||||||
@@ -6040,14 +6043,12 @@ export class RequestManagementService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const items = (userResendRequest.requestedItems || []) as string[];
|
const items = normalizeResendRequestedItemsList(
|
||||||
|
userResendRequest.requestedItems as string[],
|
||||||
|
);
|
||||||
const row = userResendRequest as any;
|
const row = userResendRequest as any;
|
||||||
const merged = {
|
const merged = {
|
||||||
uploadedDocuments: {
|
uploadedDocuments: cloneResendUploadedDocuments(row.uploadedDocuments),
|
||||||
...(typeof row.uploadedDocuments === "object" && row.uploadedDocuments
|
|
||||||
? { ...row.uploadedDocuments }
|
|
||||||
: {}),
|
|
||||||
},
|
|
||||||
resendVoiceId: row.resendVoiceId,
|
resendVoiceId: row.resendVoiceId,
|
||||||
resendVideoId: row.resendVideoId,
|
resendVideoId: row.resendVideoId,
|
||||||
userTextDescription: row.userTextDescription,
|
userTextDescription: row.userTextDescription,
|
||||||
@@ -6056,7 +6057,7 @@ export class RequestManagementService {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
requestId: String(request._id),
|
requestId: String(request._id),
|
||||||
requestedItems: userResendRequest.requestedItems,
|
requestedItems: items,
|
||||||
description: userResendRequest.description || "",
|
description: userResendRequest.description || "",
|
||||||
completed: userResendRequest.completed || false,
|
completed: userResendRequest.completed || false,
|
||||||
requestedItemsUi: buildResendItemsWithUi(items),
|
requestedItemsUi: buildResendItemsWithUi(items),
|
||||||
@@ -6122,22 +6123,26 @@ export class RequestManagementService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const userResendRequest = resendParties[resendIndex];
|
const userResendRequest = resendParties[resendIndex];
|
||||||
const requestedItems = userResendRequest.requestedItems || [];
|
const requestedItems = normalizeResendRequestedItemsList(
|
||||||
|
userResendRequest.requestedItems as string[],
|
||||||
|
);
|
||||||
|
if (requestedItems.length === 0) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
"No valid requested items are configured for your party resend. Please contact support.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const row = userResendRequest as any;
|
const row = userResendRequest as any;
|
||||||
const mergedRow = {
|
const mergedRow = {
|
||||||
uploadedDocuments: {
|
uploadedDocuments: cloneResendUploadedDocuments(row.uploadedDocuments),
|
||||||
...(typeof row.uploadedDocuments === "object" && row.uploadedDocuments
|
|
||||||
? { ...row.uploadedDocuments }
|
|
||||||
: {}),
|
|
||||||
},
|
|
||||||
resendVoiceId: row.resendVoiceId,
|
resendVoiceId: row.resendVoiceId,
|
||||||
resendVideoId: row.resendVideoId,
|
resendVideoId: row.resendVideoId,
|
||||||
userTextDescription: row.userTextDescription,
|
userTextDescription: row.userTextDescription,
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasAnyFile = Object.keys(files || {}).some((fieldName) => {
|
const safeFiles = files || {};
|
||||||
const fileArray = files[fieldName as keyof typeof files];
|
const hasAnyFile = Object.keys(safeFiles).some((fieldName) => {
|
||||||
|
const fileArray = safeFiles[fieldName as keyof typeof safeFiles];
|
||||||
return fileArray && fileArray.length > 0 && fileArray[0];
|
return fileArray && fileArray.length > 0 && fileArray[0];
|
||||||
});
|
});
|
||||||
const descTrim =
|
const descTrim =
|
||||||
@@ -6147,7 +6152,7 @@ export class RequestManagementService {
|
|||||||
|
|
||||||
if (!hasAnyFile && !sendingDescription) {
|
if (!hasAnyFile && !sendingDescription) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
"Provide at least one requested file or a text description when description was requested.",
|
"Provide at least one file for a requested item and/or the text description when the expert asked for a description.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6162,19 +6167,20 @@ export class RequestManagementService {
|
|||||||
uploadedItems.push(ResendItemType.DESCRIPTION);
|
uploadedItems.push(ResendItemType.DESCRIPTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const fieldName in files) {
|
for (const fieldName of Object.keys(safeFiles)) {
|
||||||
const fileArray = files[fieldName as keyof typeof files];
|
const fileArray = safeFiles[fieldName as keyof typeof safeFiles];
|
||||||
if (!fileArray || fileArray.length === 0) continue;
|
if (!fileArray || fileArray.length === 0) continue;
|
||||||
|
|
||||||
const file = fileArray[0];
|
const file = fileArray[0];
|
||||||
|
|
||||||
if (!requestedItems.includes(fieldName)) {
|
const canonKey = normalizeResendRequestedItemKey(fieldName);
|
||||||
|
if (!canonKey || !requestedItems.includes(canonKey)) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
`${fieldName} was not requested. Requested items: ${requestedItems.join(", ")}`,
|
`${fieldName} was not requested or is not a valid resend field. Requested items: ${requestedItems.join(", ")}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fieldName === "voice") {
|
if (canonKey === ResendItemType.VOICE) {
|
||||||
const voiceDoc = await this.blameVoiceDbService.create({
|
const voiceDoc = await this.blameVoiceDbService.create({
|
||||||
path: file.path,
|
path: file.path,
|
||||||
requestId: new Types.ObjectId(requestId),
|
requestId: new Types.ObjectId(requestId),
|
||||||
@@ -6188,8 +6194,8 @@ export class RequestManagementService {
|
|||||||
`expert.resend.parties.${resendIndex}.resendVoiceId`
|
`expert.resend.parties.${resendIndex}.resendVoiceId`
|
||||||
] = (voiceDoc as any)._id;
|
] = (voiceDoc as any)._id;
|
||||||
mergedRow.resendVoiceId = (voiceDoc as any)._id;
|
mergedRow.resendVoiceId = (voiceDoc as any)._id;
|
||||||
uploadedItems.push(fieldName);
|
uploadedItems.push(canonKey);
|
||||||
} else if (fieldName === "video") {
|
} else if (canonKey === ResendItemType.VIDEO) {
|
||||||
const videoDoc = await this.blameVideoDbService.create({
|
const videoDoc = await this.blameVideoDbService.create({
|
||||||
path: file.path,
|
path: file.path,
|
||||||
requestId: new Types.ObjectId(requestId),
|
requestId: new Types.ObjectId(requestId),
|
||||||
@@ -6201,9 +6207,9 @@ export class RequestManagementService {
|
|||||||
`expert.resend.parties.${resendIndex}.resendVideoId`
|
`expert.resend.parties.${resendIndex}.resendVideoId`
|
||||||
] = (videoDoc as any)._id;
|
] = (videoDoc as any)._id;
|
||||||
mergedRow.resendVideoId = (videoDoc as any)._id;
|
mergedRow.resendVideoId = (videoDoc as any)._id;
|
||||||
uploadedItems.push(fieldName);
|
uploadedItems.push(canonKey);
|
||||||
} else {
|
} else {
|
||||||
const docType = fieldName as BlameDocumentType;
|
const docType = canonKey as BlameDocumentType;
|
||||||
const doc = await this.blameDocumentDbService.create({
|
const doc = await this.blameDocumentDbService.create({
|
||||||
path: file.path,
|
path: file.path,
|
||||||
requestId: new Types.ObjectId(requestId),
|
requestId: new Types.ObjectId(requestId),
|
||||||
@@ -6211,16 +6217,16 @@ export class RequestManagementService {
|
|||||||
documentType: docType,
|
documentType: docType,
|
||||||
});
|
});
|
||||||
updatePayload.$set[
|
updatePayload.$set[
|
||||||
`expert.resend.parties.${resendIndex}.uploadedDocuments.${fieldName}`
|
`expert.resend.parties.${resendIndex}.uploadedDocuments.${canonKey}`
|
||||||
] = (doc as any)._id;
|
] = (doc as any)._id;
|
||||||
mergedRow.uploadedDocuments[fieldName] = (doc as any)._id;
|
mergedRow.uploadedDocuments[canonKey] = (doc as any)._id;
|
||||||
uploadedItems.push(fieldName);
|
uploadedItems.push(canonKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const allItemsCompleted = (requestedItems as string[]).every((item) =>
|
const allItemsCompleted =
|
||||||
isResendPartyItemSatisfied(item, mergedRow),
|
requestedItems.length > 0 &&
|
||||||
);
|
requestedItems.every((item) => isResendPartyItemSatisfied(item, mergedRow));
|
||||||
|
|
||||||
if (allItemsCompleted) {
|
if (allItemsCompleted) {
|
||||||
updatePayload.$set[`expert.resend.parties.${resendIndex}.completed`] = true;
|
updatePayload.$set[`expert.resend.parties.${resendIndex}.completed`] = true;
|
||||||
@@ -6231,7 +6237,7 @@ export class RequestManagementService {
|
|||||||
await this.blameRequestDbService.findByIdAndUpdate(requestId, updatePayload);
|
await this.blameRequestDbService.findByIdAndUpdate(requestId, updatePayload);
|
||||||
|
|
||||||
const updatedRequest = await this.blameRequestDbService.findById(requestId);
|
const updatedRequest = await this.blameRequestDbService.findById(requestId);
|
||||||
const allPartiesCompleted = updatedRequest.expert?.resend?.parties?.every(
|
const allPartiesCompleted = updatedRequest?.expert?.resend?.parties?.every(
|
||||||
(p) => p.completed,
|
(p) => p.completed,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -399,6 +399,11 @@ export class RequestManagementV2Controller {
|
|||||||
description:
|
description:
|
||||||
"Text description when expert requested ResendItemType.description",
|
"Text description when expert requested ResendItemType.description",
|
||||||
},
|
},
|
||||||
|
textDescription: {
|
||||||
|
type: "string",
|
||||||
|
description:
|
||||||
|
"Alias for `description` (same semantics); either field may be used.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -442,12 +447,18 @@ export class RequestManagementV2Controller {
|
|||||||
video?: Express.Multer.File[];
|
video?: Express.Multer.File[];
|
||||||
},
|
},
|
||||||
@Body("description") description?: string,
|
@Body("description") description?: string,
|
||||||
|
@Body("textDescription") textDescription?: string,
|
||||||
) {
|
) {
|
||||||
|
const descCandidates = [description, textDescription]
|
||||||
|
.map((x) => (x != null ? String(x).trim() : ""))
|
||||||
|
.filter((s) => s.length > 0);
|
||||||
|
const mergedDescription =
|
||||||
|
descCandidates.length > 0 ? descCandidates[0] : undefined;
|
||||||
return this.requestManagementService.uploadResendDocumentsV2(
|
return this.requestManagementService.uploadResendDocumentsV2(
|
||||||
requestId,
|
requestId,
|
||||||
user.sub,
|
user.sub,
|
||||||
files,
|
files,
|
||||||
description,
|
mergedDescription,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user