1
0
forked from Yara724/api

fanavaran stage by stage implemented i am so bored to send smart commit sorry MR sina

This commit is contained in:
2026-07-01 15:13:22 +03:30
parent 99c819caeb
commit 67471fb9ce
16 changed files with 2366 additions and 146 deletions

View File

@@ -54,11 +54,13 @@ import { JwtModule } from "@nestjs/jwt";
import { MediaPolicyModule } from "src/media-policy/media-policy.module";
import { HttpModule } from "@nestjs/axios";
import { FanavaranAuditModule } from "src/fanavaran/fanavaran-audit.module";
import { FanavaranLookupModule } from "src/fanavaran/fanavaran-lookup.module";
@Module({
imports: [
HttpModule,
FanavaranAuditModule,
FanavaranLookupModule,
PublicIdModule,
UsersModule,
RequestManagementModule,

View File

@@ -1,34 +1,34 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
import { IsEnum, IsNotEmpty, IsString } from "class-validator";
/**
* V2 DTO for capturing car angle or damaged part
*/
export class CapturePartV2Dto {
@ApiProperty({
description: 'Type of capture: angle or part',
example: 'angle',
enum: ['angle', 'part'],
description: "Type of capture: angle or part",
example: "angle",
enum: ["angle", "part"],
})
@IsNotEmpty({ message: 'Capture type is required' })
@IsEnum(['angle', 'part'], {
@IsNotEmpty({ message: "Capture type is required" })
@IsEnum(["angle", "part"], {
message: 'Capture type must be either "angle" or "part"',
})
captureType: 'angle' | 'part';
captureType: "angle" | "part";
@ApiProperty({
description:
'When captureType is angle: front | back | left | right. When part: catalog id as string (e.g. "101"), 0-based index (e.g. "0"), or full catalog key (e.g. left_backfender). Prefer id or index for parts.',
example: 'front',
example: "front",
})
@IsNotEmpty({ message: 'Capture key is required' })
@IsString({ message: 'Capture key must be a string' })
@IsNotEmpty({ message: "Capture key is required" })
@IsString({ message: "Capture key must be a string" })
captureKey: string;
@ApiProperty({
type: 'string',
format: 'binary',
description: 'Image file (JPG, PNG)',
type: "string",
format: "binary",
description: "Image file (JPG, PNG)",
})
file: Express.Multer.File;
}
@@ -38,51 +38,58 @@ export class CapturePartV2Dto {
*/
export class CapturePartV2ResponseDto {
@ApiProperty({
description: 'Claim request ID',
example: '507f1f77bcf86cd799439011',
description: "Claim request ID",
example: "507f1f77bcf86cd799439011",
})
claimRequestId: string;
@ApiProperty({
description: 'Type of capture',
example: 'angle',
description: "Type of capture",
example: "angle",
})
captureType: string;
@ApiProperty({
description: 'Key of what was captured',
example: 'front',
description: "Key of what was captured",
example: "front",
})
captureKey: string;
@ApiProperty({
description: 'File URL',
example: 'http://localhost:3000/files/captures/front-1234567890.jpg',
description: "File URL",
example: "http://localhost:3000/files/captures/front-1234567890.jpg",
})
fileUrl: string;
@ApiProperty({
description: 'Whether all captures are now complete',
description: "Whether all captures are now complete",
example: false,
})
allCapturesComplete: boolean;
@ApiProperty({
description: 'Current workflow step',
example: 'CAPTURE_PART_DAMAGES',
description: "Current workflow step",
example: "CAPTURE_PART_DAMAGES",
})
currentStep: string;
@ApiProperty({
description: 'Success message',
example: 'Angle captured successfully. 6 captures remaining.',
description: "Success message",
example: "Angle captured successfully. 6 captures remaining.",
})
message: string;
@ApiPropertyOptional({
description: 'True when expert-requested part resends are complete and the claim returned to the expert queue.',
description:
"True when expert-requested part resends are complete and the claim returned to the expert queue.",
})
expertResendComplete?: boolean;
@ApiPropertyOptional({
description:
"Best-effort Fanavaran attachment upload result. Local capture still succeeds when this contains a warning.",
})
fanavaranAttachment?: unknown;
}
/**
@@ -90,20 +97,20 @@ export class CapturePartV2ResponseDto {
*/
export class VideoCaptureV2ResponseDto {
@ApiProperty({
description: 'Claim case ID',
example: '507f1f77bcf86cd799439011',
description: "Claim case ID",
example: "507f1f77bcf86cd799439011",
})
claimRequestId: string;
@ApiProperty({
description: 'ID of the stored video document (claim-video-capture)',
example: '507f1f77bcf86cd799439012',
description: "ID of the stored video document (claim-video-capture)",
example: "507f1f77bcf86cd799439012",
})
videoId: string;
@ApiProperty({
description: 'Success message',
example: 'Video capture uploaded successfully.',
description: "Success message",
example: "Video capture uploaded successfully.",
})
message: string;
}

View File

@@ -1,4 +1,4 @@
import { ApiProperty } from "@nestjs/swagger";
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
export class CreateClaimFromBlameResponseDto {
@ApiProperty({
@@ -23,4 +23,10 @@ export class CreateClaimFromBlameResponseDto {
example: "Claim request created successfully",
})
message: string;
@ApiPropertyOptional({
description:
"Best-effort Fanavaran early submit result. Claim creation still succeeds when this contains a warning.",
})
fanavaran?: unknown;
}

View File

@@ -1,5 +1,13 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsArray, IsEnum, IsNotEmpty, ArrayMinSize, ArrayUnique, IsOptional, IsInt } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
import {
IsArray,
IsEnum,
IsNotEmpty,
ArrayMinSize,
ArrayUnique,
IsOptional,
IsInt,
} from "class-validator";
import {
ClaimVehicleTypeV2,
OuterPartSideV2,
@@ -12,27 +20,27 @@ import { DamageSelectedPartV2BodyDto } from "./damage-selected-part-v2.dto";
*/
export enum OuterCarPart {
// Hood
HOOD = 'hood',
HOOD = "hood",
// Doors
FRONT_RIGHT_DOOR = 'front_right_door',
FRONT_LEFT_DOOR = 'front_left_door',
REAR_RIGHT_DOOR = 'rear_right_door',
REAR_LEFT_DOOR = 'rear_left_door',
FRONT_RIGHT_DOOR = "front_right_door",
FRONT_LEFT_DOOR = "front_left_door",
REAR_RIGHT_DOOR = "rear_right_door",
REAR_LEFT_DOOR = "rear_left_door",
// Bumpers
FRONT_BUMPER = 'front_bumper',
REAR_BUMPER = 'rear_bumper',
FRONT_BUMPER = "front_bumper",
REAR_BUMPER = "rear_bumper",
// Fenders
FRONT_RIGHT_FENDER = 'front_right_fender',
FRONT_LEFT_FENDER = 'front_left_fender',
REAR_RIGHT_FENDER = 'rear_right_fender',
REAR_LEFT_FENDER = 'rear_left_fender',
FRONT_RIGHT_FENDER = "front_right_fender",
FRONT_LEFT_FENDER = "front_left_fender",
REAR_RIGHT_FENDER = "rear_right_fender",
REAR_LEFT_FENDER = "rear_left_fender",
// Trunk & Roof
TRUNK = 'trunk',
ROOF = 'roof',
TRUNK = "trunk",
ROOF = "roof",
}
/**
@@ -41,38 +49,38 @@ export enum OuterCarPart {
*/
export class SelectOuterPartsV2Dto {
@ApiProperty({
description: 'Array of selected damaged outer car parts',
example: ['hood', 'front_right_door', 'rear_bumper', 'roof'],
description: "Array of selected damaged outer car parts",
example: ["hood", "front_right_door", "rear_bumper", "roof"],
enum: OuterCarPart,
isArray: true,
minItems: 1,
maxItems: 13,
})
@IsOptional()
@IsArray({ message: 'selectedParts must be an array' })
@ArrayMinSize(1, { message: 'At least one damaged part must be selected' })
@ArrayUnique({ message: 'Duplicate parts are not allowed' })
@IsArray({ message: "selectedParts must be an array" })
@ArrayMinSize(1, { message: "At least one damaged part must be selected" })
@ArrayUnique({ message: "Duplicate parts are not allowed" })
@IsEnum(OuterCarPart, {
each: true,
message: 'Invalid part name. Must be one of the valid outer car parts',
message: "Invalid part name. Must be one of the valid outer car parts",
})
selectedParts?: OuterCarPart[];
@ApiProperty({
description: 'Selected outer part IDs from catalog',
description: "Selected outer part IDs from catalog",
example: [9, 10, 4],
type: [Number],
required: false,
})
@IsOptional()
@IsArray({ message: 'selectedPartIds must be an array' })
@ArrayMinSize(1, { message: 'At least one part ID must be selected' })
@ArrayUnique({ message: 'Duplicate part IDs are not allowed' })
@IsInt({ each: true, message: 'Each selected part ID must be an integer' })
@IsArray({ message: "selectedPartIds must be an array" })
@ArrayMinSize(1, { message: "At least one part ID must be selected" })
@ArrayUnique({ message: "Duplicate part IDs are not allowed" })
@IsInt({ each: true, message: "Each selected part ID must be an integer" })
selectedPartIds?: number[];
@ApiProperty({
description: 'Vehicle type for validating available outer parts',
description: "Vehicle type for validating available outer parts",
enum: ClaimVehicleTypeV2,
required: true,
})
@@ -86,14 +94,14 @@ export class SelectOuterPartsV2Dto {
*/
export class SelectOuterPartsV2ResponseDto {
@ApiProperty({
description: 'Claim request ID',
example: '507f1f77bcf86cd799439011',
description: "Claim request ID",
example: "507f1f77bcf86cd799439011",
})
claimRequestId: string;
@ApiProperty({
description: 'Public ID shared across blame and claim',
example: 'A14235',
description: "Public ID shared across blame and claim",
example: "A14235",
})
publicId: string;
@@ -105,29 +113,36 @@ export class SelectOuterPartsV2ResponseDto {
selectedParts: DamageSelectedPartV2BodyDto[];
@ApiProperty({
description: 'Selected part IDs',
description: "Selected part IDs",
example: [9, 7, 11],
type: [Number],
})
selectedPartIds: number[];
@ApiProperty({
description: 'Current workflow step',
example: 'SELECT_OUTER_PARTS',
description: "Current workflow step",
example: "SELECT_OUTER_PARTS",
})
currentStep: string;
@ApiProperty({
description: 'Next possible workflow step',
example: 'SELECT_OTHER_PARTS',
description: "Next possible workflow step",
example: "SELECT_OTHER_PARTS",
})
nextStep: string;
@ApiProperty({
description: 'Success message',
example: 'Outer parts selected successfully. Please proceed to select other parts.',
description: "Success message",
example:
"Outer parts selected successfully. Please proceed to select other parts.",
})
message: string;
@ApiPropertyOptional({
description:
"Best-effort Fanavaran damage-case submit result. Selecting parts still succeeds when this contains a warning.",
})
fanavaranDamageCase?: unknown;
}
export class SetClaimVehicleTypeV2Dto {

View File

@@ -1,26 +1,26 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
import { ClaimRequiredDocumentType } from 'src/Types&Enums/claim-request-management/required-document-type.enum';
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
import { IsEnum, IsNotEmpty, IsString } from "class-validator";
import { ClaimRequiredDocumentType } from "src/Types&Enums/claim-request-management/required-document-type.enum";
/**
* V2 DTO for uploading required document
*/
export class UploadRequiredDocumentV2Dto {
@ApiProperty({
description: 'Document type/key',
example: 'car_green_card',
description: "Document type/key",
example: "car_green_card",
enum: ClaimRequiredDocumentType,
})
@IsNotEmpty({ message: 'Document key is required' })
@IsNotEmpty({ message: "Document key is required" })
@IsEnum(ClaimRequiredDocumentType, {
message: 'Invalid document type',
message: "Invalid document type",
})
documentKey: ClaimRequiredDocumentType;
@ApiProperty({
type: 'string',
format: 'binary',
description: 'Image file (JPG, PNG, PDF)',
type: "string",
format: "binary",
description: "Image file (JPG, PNG, PDF)",
})
file: Express.Multer.File;
}
@@ -30,43 +30,51 @@ export class UploadRequiredDocumentV2Dto {
*/
export class UploadRequiredDocumentV2ResponseDto {
@ApiProperty({
description: 'Claim request ID',
example: '507f1f77bcf86cd799439011',
description: "Claim request ID",
example: "507f1f77bcf86cd799439011",
})
claimRequestId: string;
@ApiProperty({
description: 'Document key that was uploaded',
example: 'car_green_card',
description: "Document key that was uploaded",
example: "car_green_card",
})
documentKey: string;
@ApiProperty({
description: 'File URL',
example: 'http://localhost:3000/files/documents/car-green-card-1234567890.jpg',
description: "File URL",
example:
"http://localhost:3000/files/documents/car-green-card-1234567890.jpg",
})
fileUrl: string;
@ApiProperty({
description: 'Whether all required documents are now uploaded',
description: "Whether all required documents are now uploaded",
example: false,
})
allDocumentsUploaded: boolean;
@ApiProperty({
description: 'Current workflow step',
example: 'UPLOAD_REQUIRED_DOCUMENTS',
description: "Current workflow step",
example: "UPLOAD_REQUIRED_DOCUMENTS",
})
currentStep: string;
@ApiProperty({
description: 'Success message',
example: 'Document uploaded successfully. 12 documents remaining.',
description: "Success message",
example: "Document uploaded successfully. 12 documents remaining.",
})
message: string;
@ApiPropertyOptional({
description: 'True when the owner finished every damage-expert resend requirement and the claim is back in the expert queue.',
description:
"True when the owner finished every damage-expert resend requirement and the claim is back in the expert queue.",
})
expertResendComplete?: boolean;
@ApiPropertyOptional({
description:
"Best-effort Fanavaran attachment upload result. Local document upload still succeeds when this contains a warning.",
})
fanavaranAttachment?: unknown;
}

View File

@@ -58,6 +58,55 @@ export class RequiredDocumentRef {
export const RequiredDocumentRefSchema =
SchemaFactory.createForClass(RequiredDocumentRef);
@Schema({ _id: false })
export class FanavaranSyncStage {
@Prop({ type: String })
status?: "pending" | "success" | "failed" | "skipped";
@Prop({ type: Date })
lastTriedAt?: Date;
@Prop({ type: String })
lastError?: string;
@Prop({ type: Number })
claimId?: number;
@Prop({ type: Number })
claimNo?: number;
@Prop({ type: Number })
dmgCaseId?: number;
@Prop({ type: Number })
expertiseId?: number;
@Prop({ type: [MongooseSchema.Types.Mixed], default: [] })
files?: unknown[];
@Prop({ type: MongooseSchema.Types.Mixed })
response?: unknown;
}
export const FanavaranSyncStageSchema =
SchemaFactory.createForClass(FanavaranSyncStage);
@Schema({ _id: false })
export class FanavaranSyncState {
@Prop({ type: FanavaranSyncStageSchema })
baseClaim?: FanavaranSyncStage;
@Prop({ type: FanavaranSyncStageSchema })
damageCase?: FanavaranSyncStage;
@Prop({ type: FanavaranSyncStageSchema })
attachments?: FanavaranSyncStage;
@Prop({ type: FanavaranSyncStageSchema })
expertise?: FanavaranSyncStage;
}
export const FanavaranSyncStateSchema =
SchemaFactory.createForClass(FanavaranSyncState);
@Schema({
collection: "claimCases",
timestamps: true,
@@ -142,6 +191,15 @@ export class ClaimCase {
@Prop({ type: Number })
claimId?: number;
@Prop({ type: Number })
dmgCaseId?: number;
@Prop({ type: Number })
expertiseId?: number;
@Prop({ type: FanavaranSyncStateSchema, default: () => ({}) })
fanavaranSync?: FanavaranSyncState;
@Prop({ type: ClaimDamageSelectionSchema, default: () => ({}) })
damage?: ClaimDamageSelection;