Merge pull request 'Fixed resend blame and claim sms start' (#92) from s.yahyaee/yara724-api:main into main

Reviewed-on: Yara724/api#92
This commit is contained in:
2026-05-30 16:18:49 +03:30
5 changed files with 87 additions and 45 deletions

View File

@@ -51,11 +51,14 @@ export function cloneResendUploadedDocuments(
} }
/** 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";
export function getResendItemInputKind(item: string): ResendItemInputKind { export function getResendItemInputKind(item: string): ResendItemInputKind {
if (item === ResendItemType.VOICE) return "voice"; if (item === ResendItemType.VOICE) return "voice";
if (item === ResendItemType.VIDEO) return "video";
if (item === ResendItemType.DESCRIPTION) return "text"; if (item === ResendItemType.DESCRIPTION) return "text";
return "document_camera"; return "document_camera";
} }
@@ -80,13 +83,15 @@ export function isResendPartyItemSatisfied(
): boolean { ): boolean {
const uploaded = row.uploadedDocuments || {}; const uploaded = row.uploadedDocuments || {};
if (item === ResendItemType.DESCRIPTION) { if (item === ResendItemType.DESCRIPTION) {
return !!(row.userTextDescription && String(row.userTextDescription).trim()); return !!(
row.userTextDescription && String(row.userTextDescription).trim()
);
} }
if (item === ResendItemType.VOICE) { if (item === ResendItemType.VOICE) {
return !!row.resendVoiceId; return !!row.resendVoiceId;
} }
if (item === ResendItemType.VIDEO) { // if (item === ResendItemType.VIDEO) {
return !!row.resendVideoId; // return !!row.resendVideoId;
} // }
return !!uploaded[item]; return !!uploaded[item];
} }

View File

@@ -5,11 +5,10 @@ export enum ResendItemType {
CAR_CERTIFICATE = "carCertificate", CAR_CERTIFICATE = "carCertificate",
DRIVING_LICENSE = "drivingLicense", DRIVING_LICENSE = "drivingLicense",
CAR_GREEN_CARD = "carGreenCard", CAR_GREEN_CARD = "carGreenCard",
PLATE = "plate",
CAR_PLATE = "carPlate", CAR_PLATE = "carPlate",
CHASSIS_NUMBER = "chassisNumber",
// Media evidence // Media evidence
VOICE = "voice", VOICE = "voice",
VIDEO = "video",
/** Written / text party description (maps to FIRST_DESCRIPTION / SECOND_DESCRIPTION workflow steps) */ /** Written / text party description (maps to FIRST_DESCRIPTION / SECOND_DESCRIPTION workflow steps) */
DESCRIPTION = "description", DESCRIPTION = "description",

View File

@@ -30,7 +30,12 @@ export const DEFAULT_CAR_BODY_ACCIDENT_MAX_AGE_DAYS = 7;
* default window (see {@link DEFAULT_MEDIA_LIMITS}) and an optional * default window (see {@link DEFAULT_MEDIA_LIMITS}) and an optional
* per-client override under `settings.media.<kind>`. * per-client override under `settings.media.<kind>`.
*/ */
export type MediaKind = "video" | "image" | "voice"; export type MediaKind =
| "video"
| "image"
| "voice"
| "chassisNumber"
| "carPlate";
export interface MediaLimits { export interface MediaLimits {
/** Inclusive lower bound. `0` allows any size from zero up. */ /** Inclusive lower bound. `0` allows any size from zero up. */
@@ -63,6 +68,14 @@ export const DEFAULT_MEDIA_LIMITS: Record<MediaKind, MediaLimits> = {
minBytes: DEFAULT_MEDIA_MIN_BYTES, minBytes: DEFAULT_MEDIA_MIN_BYTES,
maxBytes: DEFAULT_MEDIA_MAX_BYTES, maxBytes: DEFAULT_MEDIA_MAX_BYTES,
}, },
carPlate: {
minBytes: DEFAULT_MEDIA_MIN_BYTES,
maxBytes: DEFAULT_MEDIA_MAX_BYTES,
},
chassisNumber: {
minBytes: DEFAULT_MEDIA_MIN_BYTES,
maxBytes: DEFAULT_MEDIA_MAX_BYTES,
},
}; };
/** Highest multer `fileSize` used on any route for each kind (policy cannot exceed this). */ /** Highest multer `fileSize` used on any route for each kind (policy cannot exceed this). */
@@ -70,6 +83,8 @@ export const MEDIA_ROUTE_MAX_BYTES: Record<MediaKind, number> = {
video: DEFAULT_MEDIA_MAX_BYTES, video: DEFAULT_MEDIA_MAX_BYTES,
image: DEFAULT_MEDIA_MAX_BYTES, image: DEFAULT_MEDIA_MAX_BYTES,
voice: DEFAULT_MEDIA_MAX_BYTES, voice: DEFAULT_MEDIA_MAX_BYTES,
carPlate: DEFAULT_MEDIA_MAX_BYTES,
chassisNumber: DEFAULT_MEDIA_MAX_BYTES,
}; };
export const CAR_BODY_ACCIDENT_MAX_AGE_DAYS_MIN = 1; export const CAR_BODY_ACCIDENT_MAX_AGE_DAYS_MIN = 1;

View File

@@ -6682,19 +6682,19 @@ export class RequestManagementService {
] = (voiceDoc as any)._id; ] = (voiceDoc as any)._id;
mergedRow.resendVoiceId = (voiceDoc as any)._id; mergedRow.resendVoiceId = (voiceDoc as any)._id;
uploadedItems.push(canonKey); uploadedItems.push(canonKey);
} else if (canonKey === ResendItemType.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),
fileName: file.filename, // fileName: file.filename,
}); // });
updatePayload.$set[`parties.${partyIndex}.evidence.videoId`] = String( // updatePayload.$set[`parties.${partyIndex}.evidence.videoId`] = String(
(videoDoc as any)._id, // (videoDoc as any)._id,
); // );
updatePayload.$set[ // updatePayload.$set[
`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(canonKey); uploadedItems.push(canonKey);
} else { } else {
const docType = canonKey as BlameDocumentType; const docType = canonKey as BlameDocumentType;
@@ -6932,7 +6932,12 @@ export class RequestManagementService {
if (targetPhone) { if (targetPhone) {
const publicId = String(updatedRequest.publicId); const publicId = String(updatedRequest.publicId);
const link = this.smsOrchestrationService.buildClaimLink(requestId); const link = this.smsOrchestrationService.buildBlamePartyLink(
requestId,
targetPhone === request?.parties[0]?.person.phoneNumber
? "FIRST"
: "SECOND",
);
await this.smsOrchestrationService.sendThirdPartyDamagedPartyClaimLinkNotice( await this.smsOrchestrationService.sendThirdPartyDamagedPartyClaimLinkNotice(
{ {
receptor: targetPhone, receptor: targetPhone,

View File

@@ -22,7 +22,10 @@ import {
ApiParam, ApiParam,
ApiTags, ApiTags,
} from "@nestjs/swagger"; } from "@nestjs/swagger";
import { FileFieldsInterceptor, FileInterceptor } from "@nestjs/platform-express"; import {
FileFieldsInterceptor,
FileInterceptor,
} from "@nestjs/platform-express";
import { diskStorage } from "multer"; import { diskStorage } from "multer";
import { Request } from "express"; import { Request } from "express";
import { GlobalGuard } from "src/auth/guards/global.guard"; import { GlobalGuard } from "src/auth/guards/global.guard";
@@ -114,7 +117,11 @@ export class RequestManagementV2Controller {
@Body() body: BlameConfessionDtoV2, @Body() body: BlameConfessionDtoV2,
@CurrentUser() user, @CurrentUser() user,
) { ) {
return this.requestManagementService.blameConfessionV2(requestId, body, user); return this.requestManagementService.blameConfessionV2(
requestId,
body,
user,
);
} }
/** CAR_BODY only: submit accident type (car vs object). Call this when nextStep is CAR_BODY_ACCIDENT_TYPE. */ /** CAR_BODY only: submit accident type (car vs object). Call this when nextStep is CAR_BODY_ACCIDENT_TYPE. */
@@ -127,7 +134,11 @@ export class RequestManagementV2Controller {
@Body() body: CarBodyFormDto, @Body() body: CarBodyFormDto,
@CurrentUser() user, @CurrentUser() user,
) { ) {
return this.requestManagementService.carBodyAccidentTypeFormV2(requestId, body, user); return this.requestManagementService.carBodyAccidentTypeFormV2(
requestId,
body,
user,
);
} }
@ApiBody({ @ApiBody({
@@ -195,7 +206,11 @@ export class RequestManagementV2Controller {
@Body() body: LocationDto, @Body() body: LocationDto,
@CurrentUser() user, @CurrentUser() user,
) { ) {
return this.requestManagementService.addDetailLocationV2(requestId, body, user); return this.requestManagementService.addDetailLocationV2(
requestId,
body,
user,
);
} }
@ApiBody({ @ApiBody({
@@ -269,7 +284,11 @@ export class RequestManagementV2Controller {
@Body() body: DescriptionDto, @Body() body: DescriptionDto,
@CurrentUser() user, @CurrentUser() user,
) { ) {
return this.requestManagementService.addDescriptionV2(requestId, body, user); return this.requestManagementService.addDescriptionV2(
requestId,
body,
user,
);
} }
@Post("add-second-party/:phoneNumber/:requestId/:frontendRoute") @Post("add-second-party/:phoneNumber/:requestId/:frontendRoute")
@@ -355,9 +374,8 @@ export class RequestManagementV2Controller {
await this.mediaPolicyService.assertForBlame(sign, requestId, "image"); await this.mediaPolicyService.assertForBlame(sign, requestId, "image");
// Convert string "true"/"false" to boolean // Convert string "true"/"false" to boolean
const acceptDecision = typeof isAccept === "string" const acceptDecision =
? isAccept === "true" typeof isAccept === "string" ? isAccept === "true" : Boolean(isAccept);
: Boolean(isAccept);
return this.requestManagementService.userSignDecisionV2( return this.requestManagementService.userSignDecisionV2(
requestId, requestId,
@@ -405,17 +423,15 @@ export class RequestManagementV2Controller {
format: "binary", format: "binary",
description: "Voice recording file (if requested)", description: "Voice recording file (if requested)",
}, },
video: { carPlate: {
type: "string", type: "string",
format: "binary", description: "car plate",
description: "Video file (if requested)", },
chassisNumber: {
type: "string",
description: "chassis number شماره شاسی",
}, },
description: { description: {
type: "string",
description:
"Text description when expert requested ResendItemType.description",
},
textDescription: {
type: "string", type: "string",
description: description:
"Alias for `description` (same semantics); either field may be used.", "Alias for `description` (same semantics); either field may be used.",
@@ -431,7 +447,8 @@ export class RequestManagementV2Controller {
{ name: "nationalCertificate", maxCount: 1 }, { name: "nationalCertificate", maxCount: 1 },
{ name: "carGreenCard", maxCount: 1 }, { name: "carGreenCard", maxCount: 1 },
{ name: "voice", maxCount: 1 }, { name: "voice", maxCount: 1 },
{ name: "video", maxCount: 1 }, { name: "carPlate", maxCount: 1 },
{ name: "chassisNumber", maxCount: 1 },
], ],
{ {
storage: diskStorage({ storage: diskStorage({
@@ -460,7 +477,8 @@ export class RequestManagementV2Controller {
nationalCertificate?: Express.Multer.File[]; nationalCertificate?: Express.Multer.File[];
carGreenCard?: Express.Multer.File[]; carGreenCard?: Express.Multer.File[];
voice?: Express.Multer.File[]; voice?: Express.Multer.File[];
video?: Express.Multer.File[]; carPlate?: Express.Multer.File[];
chassisNumber?: Express.Multer.File[];
}, },
@Body("description") description?: string, @Body("description") description?: string,
@Body("textDescription") textDescription?: string, @Body("textDescription") textDescription?: string,
@@ -474,7 +492,8 @@ export class RequestManagementV2Controller {
nationalCertificate: "image", nationalCertificate: "image",
carGreenCard: "image", carGreenCard: "image",
voice: "voice", voice: "voice",
video: "video", carPlate: "carPlate",
chassisNumber: "chassisNumber",
}); });
const descCandidates = [description, textDescription] const descCandidates = [description, textDescription]
@@ -490,4 +509,3 @@ export class RequestManagementV2Controller {
); );
} }
} }