import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose"; import { Types } from "mongoose"; import { ResendFirstPartyDto, ResendSecondPartyDto, } from "src/expert-blame/dto/reply.dto"; import { AddPlateDto } from "src/profile/dto/user/AddPlateDto"; import { ReqBlameStatus } from "src/Types&Enums/blame-request-management/status.enum"; import { StepsEnum } from "src/Types&Enums/blame-request-management/steps.enum"; export class InitialForm { @Prop({ required: false, default: false }) expertOpinion?: boolean; @Prop({ required: false, default: false }) imDamaged?: boolean; @Prop({ required: false, default: false }) imGuilty?: boolean; } export class FirstPartyFile { @Prop() descriptions?: string[]; @Prop() voices?: string[]; @Prop() firstPartyVideoId?: string; // CAR_BODY specific accident details @Prop({ type: Date }) accidentDate?: Date; @Prop() accidentTime?: string; @Prop() weatherCondition?: string; @Prop() roadCondition?: string; @Prop() lightCondition?: string; } export class CarDetail { carName?: string; carModel?: string; carType?: string; isNewCar?: boolean; insurerBirthday?: string; driverBirthday?: string | null; } export class InsuranceDetail { insuranceId?: string; insuranceCompany?: string; financialCommitmentCeiling?: string; startDate?: string; endDate?: string; } // CAR_BODY specific insurance details for the damaged party export class CarBodyInsuranceDetail { @Prop() policyNumber?: string; @Prop() startDate?: string; @Prop() endDate?: string; @Prop() insurerCompany?: string; @Prop({ type: [String] }) coverages?: string[]; } export class SecondPartyFile { @Prop() descriptions?: string[]; @Prop() voices?: string[]; @Prop() secondPartyVideoId?: string; } export class FirstPartyClient { clientName?: string; clientId?: Types.ObjectId; } export class SecondPartyClient { clientName?: string; clientId?: Types.ObjectId; } export class FirstPartyDetail { @Prop({ type: String }) firstPartyId?: Types.ObjectId; @Prop({ type: String }) firstPartyPhoneNumber?: string; @Prop({ type: String }) firstPartyClientId?: string; @Prop({ type: AddPlateDto, description: "first add plate and send plate id to this field", }) firstPartyPlate?: AddPlateDto; @Prop({ type: CarDetail }) firstPartyCarDetail?: CarDetail; @Prop({ type: InsuranceDetail }) firstPartyInsuranceDetail?: InsuranceDetail; // Only used for CAR_BODY type requests – car body insurance info @Prop({ type: CarBodyInsuranceDetail }) firstPartyCarBodyInsuranceDetail?: CarBodyInsuranceDetail; @Prop({ type: FirstPartyFile }) firstPartyClient?: FirstPartyClient; @Prop({ type: FirstPartyFile }) firstPartyFile?: FirstPartyFile; @Prop({ type: FirstPartyFile }) firstPartyInitialForm?: InitialForm; @Prop({ type: Boolean }) firstPartyCertificate: boolean | null; } export class SecondPartyDetail { @Prop({ type: String }) secondPartyId?: Types.ObjectId; @Prop({ type: String }) secondPartyPhoneNumber?: string; @Prop({ type: SecondPartyClient }) secondPartyClient?: SecondPartyClient; @Prop({ type: AddPlateDto, description: "secondParty add plate and send plate id to this field", }) secondPartyPlate?: AddPlateDto; @Prop() secondPartyCarDetail?: CarDetail; @Prop({ type: InsuranceDetail }) secondPartyInsuranceDetail?: InsuranceDetail; @Prop() secondPartyPlateId?: string; @Prop({ type: InitialForm }) secondPartyInitialForm: InitialForm; @Prop({ type: SecondPartyFile }) secondPartyFiles?: SecondPartyFile; } export class LocationDto { @Prop({ type: Number, required: true }) lat: number; @Prop({ type: Number, required: true }) lon: number; } export class AccidentWayIF { @Prop({ required: true }) id: string; @Prop({ required: true }) label: string; } export class AccidentReasonIF { @Prop({ required: true }) id: string; @Prop({ required: true }) label: string; @Prop({ required: true }) fanavaran: number; } export class FieldsInterface { @Prop({ required: true, type: AccidentWayIF }) accidentWay: AccidentWayIF; @Prop({ required: true, type: AccidentReasonIF }) accidentReason: AccidentReasonIF; @Prop({ required: true, type: AccidentWayIF }) accidentType: AccidentWayIF; } export class SignDetail { @Prop() fileId: string; @Prop() fileName: string; @Prop() fileUrl: string; } export class PartiesComment { @Prop({ required: true, type: Boolean }) isAccept?: boolean; @Prop({ required: true }) signDetail?: SignDetail; } export class SubmitReply { @Prop({ required: true }) description: string; @Prop({ required: true, type: String }) guiltyUserId: Types.ObjectId; @Prop({ required: true }) fields: FieldsInterface; @Prop({ required: true, default: () => new Date() }) submitTime: Date; @Prop({ type: PartiesComment }) firstPartyComment?: PartiesComment; @Prop({ type: PartiesComment }) secondPartyComment?: PartiesComment; @Prop() systemResponse: string; } export class ExpertResendParties { userId?: Types.ObjectId; description?: string; } export class ExpertResendReply { // TODO change DTOs to dynamic Exmple SubmitReply Parties comment @Prop() firstParty?: ResendFirstPartyDto; @Prop() secondParty?: ResendSecondPartyDto; } export class ActorLockDetails { @Prop({ type: String }) fullName?: string; @Prop({ type: Types.ObjectId }) actorId?: Types.ObjectId; } export enum CreationMethod { NORMAL = "NORMAL", LINK = "LINK", IN_PERSON = "IN_PERSON", } export enum FilledBy { CUSTOMER = "customer", EXPERT = "expert", REGISTRAR = "registrar", } export class ExpertLinkInfo { @Prop({ type: String, required: true }) token: string; @Prop({ type: Date, required: true }) expiresAt: Date; @Prop({ type: Boolean, default: false }) isUsed: boolean; @Prop({ type: Boolean, default: false }) isExpired: boolean; @Prop({ type: Date }) generatedAt: Date; @Prop({ type: String }) sentTo?: string; // Phone number or email @Prop({ type: Number, default: 0 }) openCount: number; @Prop({ type: Date }) lastOpenedAt?: Date; @Prop({ type: Date }) usedAt?: Date; } export class FileHistoryEvent { @Prop({ type: String, required: true }) eventType: string; // "LINK_SENT", "LINK_OPENED", "FILLED_BY_CUSTOMER", "FILLED_BY_EXPERT", "ASSIGNED_TO_EXPERT", "EVALUATION_STARTED", "EVALUATION_COMPLETED", "USER_ACCEPTED", "USER_REJECTED", "SENT_TO_FANAVARAN", "CODE_RECEIVED" @Prop({ type: Types.ObjectId }) actorId?: Types.ObjectId; @Prop({ type: String }) actorName?: string; @Prop({ type: String }) actorType?: string; // "expert", "damage_expert", "customer" @Prop({ type: Date, default: () => new Date() }) timestamp: Date; @Prop({ type: Object }) metadata?: any; // Additional event-specific data } @Schema({ _id: false }) export class FileRating { @Prop({ type: Number, min: 0, max: 5 }) collisionMethodAccuracy: number; @Prop({ type: Number, min: 0, max: 5 }) evaluationTimeliness: number; @Prop({ type: Number, min: 0, max: 5 }) accidentCauseAccuracy: number; @Prop({ type: Number, min: 0, max: 5 }) guiltyVehicleIdentification: number; @Prop({ type: Number, min: 0, max: 5 }) botRating: number; } // TODO clean all sub Object to other files export type RequestManagementDocs = RequestManagementModel & Document; @Schema({ collection: "requests-management", versionKey: false, timestamps: true, autoIndex: true, _id: true, }) export class RequestManagementModel { @Prop({ type: String, required: false }) type: string; @Prop({ type: String, required: true, index: { unique: true } }) requestNumber: string; /** * Human-friendly shared id across blame+claim (e.g. A14235). * Generated once at creation time and reused by claim. * * NOTE: `sparse` keeps existing documents without this field valid. */ @Prop({ type: String, unique: true, index: true, sparse: true, trim: true }) publicId?: string; @Prop() blameStatus: ReqBlameStatus | StepsEnum; @Prop() steps?: StepsEnum[]; @Prop() userComment?: string; @Prop() currentStep: StepsEnum; @Prop() nextStep?: StepsEnum; @Prop({ description: "first step" }) initialForm?: InitialForm; @Prop({ required: false }) firstPartyDetails?: FirstPartyDetail; @Prop({ required: false, type: Types.ObjectId }) sanHubId?: Types.ObjectId; @Prop({ required: false }) secondPartyDetails?: SecondPartyDetail; @Prop({ required: false, description: "third step call" }) firstPartyLocation?: LocationDto; @Prop({ required: false }) secondPartyLocation?: LocationDto; @Prop({ type: Date, default: Date.now }) createdAt?: Date | string; @Prop({ type: Date, default: Date.now }) updatedAt?: Date; @Prop() lockFile?: boolean; @Prop({ type: Date }) lockTime?: Date | string | number; @Prop({ type: Date }) unlockTime?: Date | number; @Prop() actorLocked?: ActorLockDetails; @Prop() actorsChecker?: []; @Prop({ required: false }) expertSubmitReply?: SubmitReply | null; @Prop() expertResendReply?: ExpertResendReply; @Prop() expertSubmitReplyFinal?: ExpertResendReply; @Prop({ type: FileRating, required: false }) rating?: FileRating; @Prop({ type: Date, required: false }) autoCloseTriggerTime?: Date; @Prop({ type: Boolean, default: false }) isHandledStatsUpdated?: boolean; // Expert-initiated file tracking @Prop({ type: Boolean, default: false }) expertInitiated?: boolean; @Prop({ type: Types.ObjectId }) initiatedBy?: Types.ObjectId; // Expert who started the process @Prop({ type: String, enum: FilledBy }) filledBy?: FilledBy; // Who filled the information @Prop({ type: String, enum: CreationMethod, default: CreationMethod.NORMAL }) creationMethod?: CreationMethod; // How the file was created @Prop({ type: ExpertLinkInfo }) expertLink?: ExpertLinkInfo; // Link information if created via link @Prop({ type: [FileHistoryEvent], default: [] }) history?: FileHistoryEvent[]; // Timeline of events @Prop({ type: Types.ObjectId }) assignedExpertId?: Types.ObjectId; // Expert assigned for evaluation (should be same as initiatedBy for expert-initiated files) } export const RequestManagementSchema = SchemaFactory.createForClass( RequestManagementModel, );