import { Injectable, NotFoundException, BadRequestException, ConflictException, Logger, } from "@nestjs/common"; import { WorkflowStepDbService } from "./entities/db-service/workflow-step.db.service"; import { CreateWorkflowStepDto } from "./dto/create-workflow-step.dto"; import { UpdateWorkflowStepDto } from "./dto/update-workflow-step.dto"; import { WorkflowStep } from "src/Types&Enums/blame-request-management/blameWorkflow-steps.enum"; import { ClaimWorkflowStep } from "src/Types&Enums/claim-request-management/claim-workflow-steps.enum"; import { WorkflowStepModel } from "./entities/schema/workflow-step.schema"; // Type alias for combined workflow steps type AnyWorkflowStep = WorkflowStep | ClaimWorkflowStep; @Injectable() export class WorkflowStepManagementService { private readonly logger = new Logger(WorkflowStepManagementService.name); constructor(private readonly workflowStepDbService: WorkflowStepDbService) {} async onModuleInit() { try { this.logger.log("Seeding workflow steps..."); const result = await this.initializeDefaultSteps(); this.logger.log( `Workflow steps seeded — upserted: ${result?.upsertedCount ?? 0}, modified: ${result?.modifiedCount ?? 0}, matched: ${result?.matchedCount ?? 0}`, ); } catch (err) { this.logger.error( "Failed to seed workflow steps", err instanceof Error ? err.stack : String(err), ); } } /** * Create a new workflow step */ async create(createDto: CreateWorkflowStepDto): Promise { // Check if step already exists const exists = await this.workflowStepDbService.exists(createDto.stepKey); if (exists) { throw new ConflictException( `Workflow step with key ${createDto.stepKey} already exists`, ); } return await this.workflowStepDbService.create(createDto); } /** * Get all active workflow steps */ async findAll(): Promise { return await this.workflowStepDbService.findAll(); } /** * Get all workflow steps including inactive ones */ async findAllIncludingInactive(): Promise { return await this.workflowStepDbService.findAllIncludingInactive(); } /** * Find a workflow step by ID */ async findById(id: string): Promise { const step = await this.workflowStepDbService.findById(id); if (!step) { throw new NotFoundException(`Workflow step with ID ${id} not found`); } return step; } /** * Find a workflow step by step key (supports both blame and claim workflows) */ async findByStepKey(stepKey: AnyWorkflowStep): Promise { const step = await this.workflowStepDbService.findByStepKey(stepKey as any); if (!step) { throw new NotFoundException( `Workflow step with key ${stepKey} not found`, ); } return step; } /** * Find a workflow step by stepKey or stepNumber (intelligent lookup) */ async findByStepKeyOrNumber( identifier: string | number, ): Promise { // If identifier is a number or can be parsed as a number, search by stepNumber const stepNumber = typeof identifier === "number" ? identifier : Number(identifier); if (!isNaN(stepNumber)) { // Search by step number const step = await this.workflowStepDbService.findByStepNumber(stepNumber); if (step) { return step; } } // Otherwise, search by stepKey (supports both blame and claim) const step = await this.workflowStepDbService.findByStepKey( identifier as any, ); if (!step) { throw new NotFoundException( `Workflow step with identifier ${identifier} not found`, ); } return step; } /** * Find steps by category */ async findByCategory(category: string): Promise { return await this.workflowStepDbService.findByCategory(category); } /** * Update a workflow step by ID */ async update( id: string, updateDto: UpdateWorkflowStepDto, ): Promise { const updated = await this.workflowStepDbService.update(id, updateDto); if (!updated) { throw new NotFoundException(`Workflow step with ID ${id} not found`); } return updated; } /** * Update a workflow step by step key (supports both blame and claim) */ async updateByStepKey( stepKey: AnyWorkflowStep, updateDto: UpdateWorkflowStepDto, ): Promise { const updated = await this.workflowStepDbService.updateByStepKey( stepKey as any, updateDto, ); if (!updated) { throw new NotFoundException( `Workflow step with key ${stepKey} not found`, ); } return updated; } /** * Soft delete a workflow step */ async remove(id: string): Promise { const removed = await this.workflowStepDbService.remove(id); if (!removed) { throw new NotFoundException(`Workflow step with ID ${id} not found`); } return removed; } /** * Get the next possible steps from a given step (supports both blame and claim) */ async getNextSteps(stepKey: AnyWorkflowStep): Promise { const currentStep = await this.findByStepKey(stepKey); if ( !currentStep.nextPossibleSteps || currentStep.nextPossibleSteps.length === 0 ) { return []; } const nextSteps: WorkflowStepModel[] = []; for (const nextStepKey of currentStep.nextPossibleSteps) { const step = await this.workflowStepDbService.findByStepKey( nextStepKey as any, ); if (step && step.isActive) { nextSteps.push(step); } } return nextSteps; } /** * Validate if a step can be accessed based on completed steps (supports both blame and claim) */ async validateStepAccess( stepKey: AnyWorkflowStep, completedSteps: AnyWorkflowStep[], ): Promise<{ canAccess: boolean; missingSteps: AnyWorkflowStep[] }> { const step = await this.findByStepKey(stepKey); if ( !step.requiredPreviousSteps || step.requiredPreviousSteps.length === 0 ) { return { canAccess: true, missingSteps: [] }; } const missingSteps = step.requiredPreviousSteps.filter( (requiredStep) => !completedSteps.includes(requiredStep as any), ); return { canAccess: missingSteps.length === 0, missingSteps, }; } /** * Get the fields for a specific step (for dynamic form generation) */ async getStepFields(stepKey: AnyWorkflowStep): Promise { const step = await this.findByStepKey(stepKey); return { stepKey: step.stepKey, stepName_fa: step.stepName_fa, stepName_en: step.stepName_en, description_fa: step.description_fa, description_en: step.description_en, fields: step.fields || [], }; } /** * Bulk upsert workflow steps */ async bulkUpsert(steps: CreateWorkflowStepDto[]): Promise { return await this.workflowStepDbService.bulkUpsert(steps); } /** * Initialize default workflow steps */ async initializeDefaultSteps(): Promise { const defaultSteps: CreateWorkflowStepDto[] = [ { stepKey: WorkflowStep.CREATED, type: "THIRD_PARTY", stepNumber: 1, isActive: true, stepName_fa: "ایجاد نوع درخواست", stepName_en: "request_type", description_fa: "انتخاب نوع درخواست توسط کاربر (شخص ثالث یا بدنه)", description_en: "Select request type by user (THIRD_PARTY or CAR_BODY)", category: "INITIAL", requiredPreviousSteps: [], nextPossibleSteps: [WorkflowStep.FIRST_BLAME_CONFESSION], allowedRoles: ["user"], estimatedDuration: 1, fields: [ { id: "507f1f77bcf86cd799439011" as any, name_fa: "نوع درخواست", name_en: "requestType", label_fa: "نوع درخواست", label_en: "Request Type", placeholder_fa: "نوع درخواست خود را انتخاب کنید", placeholder_en: "Select your request type", value: [ { label_fa: "شخص ثالث", label_en: "Third Party", value: "THIRD_PARTY", }, { label_fa: "بدنه", label_en: "Car Body", value: "CAR_BODY" }, ], dataType: "select", required: true, order: 1, isActive: true, validation: { enum: ["THIRD_PARTY", "CAR_BODY"], }, }, ], metadata: { icon: "clipboard-list", color: "#8B5CF6", }, }, { stepKey: WorkflowStep.FIRST_BLAME_CONFESSION, type: "THIRD_PARTY", stepNumber: 2, isActive: true, stepName_fa: "اعتراف طرف اول", stepName_en: "First Party Blame Confession", description_fa: "طرف اول وضعیت تقصیر و اعتراف خود را مشخص می‌کند", description_en: "First party declares blame status and confession", category: "FIRST_PARTY", requiredPreviousSteps: [WorkflowStep.CREATED], nextPossibleSteps: [WorkflowStep.FIRST_VIDEO], allowedRoles: ["user"], estimatedDuration: 2, fields: [ { id: "507f1f77bcf86cd799439012" as any, name_fa: "وضعیت تقصیر", name_en: "blameStatus", label_fa: "وضعیت تقصیر", label_en: "Blame Status", placeholder_fa: "وضعیت تقصیر را انتخاب کنید", placeholder_en: "Select blame status", value: [ { label_fa: "موافق", label_en: "Agreed", value: "AGREED" }, { label_fa: "مخالف", label_en: "Disagreement", value: "DISAGREEMENT", }, ], dataType: "select", required: true, order: 1, isActive: true, validation: { enum: ["AGREED", "DISAGREEMENT"], }, }, { id: "507f1f77bcf86cd799439013" as any, name_fa: "اعتراف", name_en: "confess", label_fa: "اعتراف", label_en: "Confession", placeholder_fa: "نوع اعتراف خود را انتخاب کنید", placeholder_en: "Select your confession type", value: [ { label_fa: "مقصر", label_en: "Guilty", value: "guilty" }, { label_fa: "آسیب دیده", label_en: "Damaged", value: "damaged" }, ], dataType: "select", required: true, order: 2, isActive: true, validation: { enum: ["guilty", "damaged"], }, }, ], metadata: { icon: "balance-scale", color: "#F59E0B", requiresAuth: true, }, }, { stepKey: WorkflowStep.FIRST_VIDEO, type: "THIRD_PARTY", stepNumber: 3, isActive: true, stepName_fa: "ضبط ویدیو طرف اول", stepName_en: "First Party Video Capture", description_fa: "ضبط ویدیو مستقیم از دوربین توسط طرف اول (گالری مجاز نیست)", description_en: "Live video capture from camera by first party (gallery not allowed)", category: "FIRST_PARTY", requiredPreviousSteps: [WorkflowStep.FIRST_BLAME_CONFESSION], nextPossibleSteps: [WorkflowStep.FIRST_INITIAL_FORM], allowedRoles: ["user"], estimatedDuration: 3, fields: [ { id: "507f1f77bcf86cd799439014" as any, name_fa: "فایل ویدیو", name_en: "file", label_fa: "ضبط ویدیو", label_en: "Video Recording", placeholder_fa: "ویدیو خود را از دوربین ضبط کنید", placeholder_en: "Record your video from camera", value: null, dataType: "video-capture", required: true, order: 1, isActive: true, validation: { maxSize: 52428800, maxDuration: 180, allowedTypes: ["video/mp4", "video/webm"], captureMode: "camera-only", allowGallery: false, }, }, ], metadata: { icon: "video-camera", color: "#EF4444", captureType: "live-camera", allowGallerySelection: false, requiresCamera: true, }, }, { stepKey: WorkflowStep.FIRST_INITIAL_FORM, type: "THIRD_PARTY", stepNumber: 4, isActive: true, stepName_fa: "فرم اصلی اطلاعات راننده و بیمه‌گذار", stepName_en: "Main Form - Driver & Insurer Information", description_fa: "وارد کردن اطلاعات کامل راننده، بیمه‌گذار و خودرو", description_en: "Enter complete driver, insurer and vehicle information", category: "FIRST_PARTY", requiredPreviousSteps: [WorkflowStep.FIRST_VIDEO], nextPossibleSteps: [WorkflowStep.FIRST_LOCATION], allowedRoles: ["user"], estimatedDuration: 5, fields: [ { id: "507f1f77bcf86cd799439015" as any, name_fa: "کد ملی مالک", name_en: "nationalCodeOfOwner", label_fa: "کد ملی مالک", label_en: "National Code of Owner", placeholder_fa: "کد ملی ۱۰ رقمی مالک را وارد کنید", placeholder_en: "Enter 10-digit national code of insurer", value: null, dataType: "string", required: true, order: 1, isActive: true, validation: { pattern: "^[0-9]{10}$", minLength: 10, maxLength: 10, }, }, { id: "507f1f77bcf86cd799439016" as any, name_fa: "کد ملی راننده", name_en: "nationalCodeOfDriver", label_fa: "کد ملی راننده", label_en: "National Code of Driver", placeholder_fa: "کد ملی ۱۰ رقمی راننده را وارد کنید", placeholder_en: "Enter 10-digit national code of driver", value: null, dataType: "string", required: true, order: 2, isActive: true, validation: { pattern: "^[0-9]{10}$", minLength: 10, maxLength: 10, }, }, { id: "507f1f77bcf86cd799439017" as any, name_fa: "شماره گواهینامه بیمه‌گذار", name_en: "insurerLicense", label_fa: "شماره گواهینامه بیمه‌گذار", label_en: "Insurer License Number", placeholder_fa: "شماره گواهینامه بیمه‌گذار را وارد کنید", placeholder_en: "Enter insurer license number", value: null, dataType: "string", required: true, order: 3, isActive: true, validation: { minLength: 5, maxLength: 20, }, }, { id: "507f1f77bcf86cd799439018" as any, name_fa: "شماره گواهینامه راننده", name_en: "driverLicense", label_fa: "شماره گواهینامه راننده", label_en: "Driver License Number", placeholder_fa: "شماره گواهینامه راننده را وارد کنید", placeholder_en: "Enter driver license number", value: null, dataType: "string", required: true, order: 4, isActive: true, validation: { minLength: 5, maxLength: 20, }, }, { id: "507f1f77bcf86cd799439019" as any, name_fa: "پلاک خودرو", name_en: "plate", label_fa: "پلاک خودرو", label_en: "Vehicle Plate", placeholder_fa: "اطلاعات پلاک خودرو را وارد کنید", placeholder_en: "Enter vehicle plate information", value: null, dataType: "object", required: true, order: 5, isActive: true, validation: { type: "object", properties: { leftDigits: { type: "number", min: 10, max: 99 }, centerAlphabet: { type: "string", pattern: "^[آ-ی]{1}$" }, centerDigits: { type: "number", min: 100, max: 999 }, ir: { type: "number", min: 10, max: 99 }, }, }, }, { id: "507f1f77bcf86cd79943901a" as any, name_fa: "راننده همان بیمه‌گذار است", name_en: "driverIsInsurer", label_fa: "راننده همان بیمه‌گذار است", label_en: "Driver is Insurer", placeholder_fa: "", placeholder_en: "", value: null, dataType: "boolean", required: true, order: 6, isActive: true, validation: { type: "boolean", }, }, { id: "507f1f77bcf86cd79943901b" as any, name_fa: "خودرو صفر است", name_en: "isNewCar", label_fa: "خودرو صفر است", label_en: "Is New Car", placeholder_fa: "", placeholder_en: "", value: null, dataType: "boolean", required: true, order: 7, isActive: true, validation: { type: "boolean", }, }, { id: "507f1f77bcf86cd79943901c" as any, name_fa: "بدون گواهی", name_en: "userNoCertificate", label_fa: "کاربر گواهی ندارد", label_en: "User Has No Certificate", placeholder_fa: "", placeholder_en: "", value: null, dataType: "boolean", required: true, order: 8, isActive: true, validation: { type: "boolean", }, }, { id: "507f1f77bcf86cd79943901d" as any, name_fa: "تاریخ تولد بیمه‌گذار", name_en: "insurerBirthday", label_fa: "تاریخ تولد بیمه‌گذار", label_en: "Insurer Birthday", placeholder_fa: "تاریخ تولد بیمه‌گذار (timestamp)", placeholder_en: "Insurer birthday (timestamp)", value: null, dataType: "number", required: true, order: 9, isActive: true, validation: { type: "number", min: 0, }, }, { id: "507f1f77bcf86cd79943901e" as any, name_fa: "تاریخ تولد راننده", name_en: "driverBirthday", label_fa: "تاریخ تولد راننده", label_en: "Driver Birthday", placeholder_fa: "تاریخ تولد راننده را وارد کنید", placeholder_en: "Enter driver birthday", value: null, dataType: "string", required: true, order: 10, isActive: true, validation: { format: "date", }, }, ], metadata: { icon: "clipboard-list", color: "#8B5CF6", formType: "detailed", hasComplexValidation: true, }, }, { stepKey: WorkflowStep.FIRST_LOCATION, type: "THIRD_PARTY", stepNumber: 5, isActive: true, stepName_fa: "موقعیت مکانی حادثه", stepName_en: "Accident Location", description_fa: "ثبت موقعیت جغرافیایی محل وقوع حادثه", description_en: "Record geographical location of accident scene", category: "FIRST_PARTY", requiredPreviousSteps: [WorkflowStep.FIRST_INITIAL_FORM], nextPossibleSteps: [WorkflowStep.FIRST_DESCRIPTION], allowedRoles: ["user"], estimatedDuration: 2, fields: [ { id: "507f1f77bcf86cd79943901f" as any, name_fa: "عرض جغرافیایی", name_en: "lat", label_fa: "عرض جغرافیایی", label_en: "Latitude", placeholder_fa: "عرض جغرافیایی را وارد کنید", placeholder_en: "Enter latitude", value: null, dataType: "number", required: true, order: 1, isActive: true, validation: { type: "number", min: -90, max: 90, }, }, { id: "507f1f77bcf86cd799439020" as any, name_fa: "طول جغرافیایی", name_en: "lon", label_fa: "طول جغرافیایی", label_en: "Longitude", placeholder_fa: "طول جغرافیایی را وارد کنید", placeholder_en: "Enter longitude", value: null, dataType: "number", required: true, order: 2, isActive: true, validation: { type: "number", min: -180, max: 180, }, }, ], metadata: { icon: "map-pin", color: "#10B981", requiresGPS: true, autoDetectLocation: true, }, }, { stepKey: WorkflowStep.FIRST_VOICE, type: "THIRD_PARTY", stepNumber: 6, isActive: true, stepName_fa: "ضبط صدای طرف اول", stepName_en: "First Party Voice Recording", description_fa: "ضبط توضیحات صوتی توسط طرف اول", description_en: "Voice recording of explanations by first party", category: "FIRST_PARTY", requiredPreviousSteps: [WorkflowStep.FIRST_LOCATION], nextPossibleSteps: [WorkflowStep.FIRST_DESCRIPTION], allowedRoles: ["user"], estimatedDuration: 3, fields: [ { id: "507f1f77bcf86cd799439021" as any, name_fa: "فایل صوتی", name_en: "file", label_fa: "ضبط صدا", label_en: "Voice Recording", placeholder_fa: "توضیحات صوتی خود را ضبط کنید", placeholder_en: "Record your voice explanation", value: null, dataType: "audio-file", required: true, order: 1, isActive: true, validation: { maxSize: 10485760, maxDuration: 300, allowedTypes: [ "audio/mp3", "audio/mpeg", "audio/wav", "audio/m4a", "audio/webm", ], }, }, ], metadata: { icon: "microphone", color: "#F59E0B", recordingType: "voice", requiresMicrophone: true, }, }, { stepKey: WorkflowStep.FIRST_DESCRIPTION, type: "THIRD_PARTY", stepNumber: 7, isActive: true, stepName_fa: "توضیحات طرف اول", stepName_en: "First Party Description", description_fa: "ثبت توضیحات و جزئیات حادثه توسط طرف اول", description_en: "Record accident description and details by first party", category: "FIRST_PARTY", requiredPreviousSteps: [WorkflowStep.FIRST_VOICE], nextPossibleSteps: [WorkflowStep.FIRST_INVITE_SECOND], allowedRoles: ["user"], estimatedDuration: 4, fields: [ { id: "507f1f77bcf86cd799439022" as any, name_fa: "توضیحات", name_en: "desc", label_fa: "توضیحات حادثه", label_en: "Accident Description", placeholder_fa: "توضیحات کامل حادثه را وارد کنید", placeholder_en: "Enter complete accident description", value: null, dataType: "textarea", required: true, order: 1, isActive: true, validation: { minLength: 10, maxLength: 2000, }, }, { id: "507f1f77bcf86cd799439023" as any, name_fa: "تاریخ حادثه", name_en: "accidentDate", label_fa: "تاریخ حادثه", label_en: "Accident Date", placeholder_fa: "تاریخ وقوع حادثه را وارد کنید", placeholder_en: "Enter accident date", value: null, dataType: "date", required: true, order: 2, isActive: true, validation: { format: "YYYY-MM-DD", }, }, { id: "507f1f77bcf86cd799439024" as any, name_fa: "زمان حادثه", name_en: "accidentTime", label_fa: "زمان حادثه", label_en: "Accident Time", placeholder_fa: "زمان وقوع حادثه را وارد کنید", placeholder_en: "Enter accident time", value: null, dataType: "time", required: true, order: 3, isActive: true, validation: { format: "HH:mm", }, }, ], metadata: { icon: "file-text", color: "#6366F1", }, }, { stepKey: WorkflowStep.FIRST_INVITE_SECOND, type: "THIRD_PARTY", stepNumber: 8, isActive: true, stepName_fa: "دعوت طرف دوم", stepName_en: "Invite Second Party", description_fa: "ارسال دعوتنامه به طرف دوم برای ثبت اطلاعات", description_en: "Send invitation to second party to register information", category: "FIRST_PARTY", requiredPreviousSteps: [WorkflowStep.FIRST_DESCRIPTION], nextPossibleSteps: [WorkflowStep.SECOND_INITIAL_FORM], allowedRoles: ["user"], estimatedDuration: 1, fields: [], metadata: { icon: "user-plus", color: "#8B5CF6", requiresPhoneNumber: true, actionType: "invitation", }, }, { stepKey: WorkflowStep.SECOND_INITIAL_FORM, type: "THIRD_PARTY", stepNumber: 9, isActive: true, stepName_fa: "فرم اصلی اطلاعات طرف دوم", stepName_en: "Second Party Main Form", description_fa: "وارد کردن اطلاعات کامل راننده، بیمه‌گذار و خودرو طرف دوم", description_en: "Enter complete driver, insurer and vehicle information for second party", category: "SECOND_PARTY", requiredPreviousSteps: [WorkflowStep.FIRST_INVITE_SECOND], nextPossibleSteps: [WorkflowStep.SECOND_LOCATION], allowedRoles: ["user"], estimatedDuration: 5, fields: [ { id: "507f1f77bcf86cd799439025" as any, name_fa: "کد ملی مالک", name_en: "nationalCodeOfOwner", label_fa: "کد ملی مالک", label_en: "National Code of Owner", placeholder_fa: "کد ملی ۱۰ رقمی مالک را وارد کنید", placeholder_en: "Enter 10-digit national code of insurer", value: null, dataType: "string", required: true, order: 1, isActive: true, validation: { pattern: "^[0-9]{10}$", minLength: 10, maxLength: 10, }, }, { id: "507f1f77bcf86cd799439026" as any, name_fa: "کد ملی راننده", name_en: "nationalCodeOfDriver", label_fa: "کد ملی راننده", label_en: "National Code of Driver", placeholder_fa: "کد ملی ۱۰ رقمی راننده را وارد کنید", placeholder_en: "Enter 10-digit national code of driver", value: null, dataType: "string", required: true, order: 2, isActive: true, validation: { pattern: "^[0-9]{10}$", minLength: 10, maxLength: 10, }, }, { id: "507f1f77bcf86cd799439027" as any, name_fa: "شماره گواهینامه بیمه‌گذار", name_en: "insurerLicense", label_fa: "شماره گواهینامه بیمه‌گذار", label_en: "Insurer License Number", placeholder_fa: "شماره گواهینامه بیمه‌گذار را وارد کنید", placeholder_en: "Enter insurer license number", value: null, dataType: "string", required: true, order: 3, isActive: true, validation: { minLength: 5, maxLength: 20, }, }, { id: "507f1f77bcf86cd799439028" as any, name_fa: "شماره گواهینامه راننده", name_en: "driverLicense", label_fa: "شماره گواهینامه راننده", label_en: "Driver License Number", placeholder_fa: "شماره گواهینامه راننده را وارد کنید", placeholder_en: "Enter driver license number", value: null, dataType: "string", required: true, order: 4, isActive: true, validation: { minLength: 5, maxLength: 20, }, }, { id: "507f1f77bcf86cd799439029" as any, name_fa: "پلاک خودرو", name_en: "plate", label_fa: "پلاک خودرو", label_en: "Vehicle Plate", placeholder_fa: "اطلاعات پلاک خودرو را وارد کنید", placeholder_en: "Enter vehicle plate information", value: null, dataType: "object", required: true, order: 5, isActive: true, validation: { type: "object", properties: { leftDigits: { type: "number", min: 10, max: 99 }, centerAlphabet: { type: "string", pattern: "^[آ-ی]{1}$" }, centerDigits: { type: "number", min: 100, max: 999 }, ir: { type: "number", min: 10, max: 99 }, }, }, }, { id: "507f1f77bcf86cd79943902a" as any, name_fa: "راننده همان بیمه‌گذار است", name_en: "driverIsInsurer", label_fa: "راننده همان بیمه‌گذار است", label_en: "Driver is Insurer", value: null, dataType: "boolean", required: true, order: 6, isActive: true, }, { id: "507f1f77bcf86cd79943902b" as any, name_fa: "خودرو صفر است", name_en: "isNewCar", label_fa: "خودرو صفر است", label_en: "Is New Car", value: null, dataType: "boolean", required: true, order: 7, isActive: true, }, { id: "507f1f77bcf86cd79943902c" as any, name_fa: "بدون گواهی", name_en: "userNoCertificate", label_fa: "کاربر گواهی ندارد", label_en: "User Has No Certificate", value: null, dataType: "boolean", required: true, order: 8, isActive: true, }, { id: "507f1f77bcf86cd79943902d" as any, name_fa: "تاریخ تولد بیمه‌گذار", name_en: "insurerBirthday", label_fa: "تاریخ تولد بیمه‌گذار", label_en: "Insurer Birthday", placeholder_fa: "تاریخ تولد بیمه‌گذار (timestamp)", placeholder_en: "Insurer birthday (timestamp)", value: null, dataType: "number", required: true, order: 9, isActive: true, validation: { type: "number", min: 0, }, }, { id: "507f1f77bcf86cd79943902e" as any, name_fa: "تاریخ تولد راننده", name_en: "driverBirthday", label_fa: "تاریخ تولد راننده", label_en: "Driver Birthday", placeholder_fa: "تاریخ تولد راننده را وارد کنید", placeholder_en: "Enter driver birthday", value: null, dataType: "string", required: true, order: 10, isActive: true, validation: { format: "date", }, }, ], metadata: { icon: "clipboard-list", color: "#8B5CF6", formType: "detailed", hasComplexValidation: true, party: "second", }, }, { stepKey: WorkflowStep.SECOND_LOCATION, type: "THIRD_PARTY", stepNumber: 10, isActive: true, stepName_fa: "موقعیت مکانی طرف دوم", stepName_en: "Second Party Location", description_fa: "ثبت موقعیت جغرافیایی طرف دوم", description_en: "Record second party geographical location", category: "SECOND_PARTY", requiredPreviousSteps: [WorkflowStep.SECOND_INITIAL_FORM], nextPossibleSteps: [WorkflowStep.SECOND_VOICE], allowedRoles: ["user"], estimatedDuration: 2, fields: [ { id: "507f1f77bcf86cd79943902f" as any, name_fa: "عرض جغرافیایی", name_en: "lat", label_fa: "عرض جغرافیایی", label_en: "Latitude", placeholder_fa: "عرض جغرافیایی را وارد کنید", placeholder_en: "Enter latitude", value: null, dataType: "number", required: true, order: 1, isActive: true, validation: { type: "number", min: -90, max: 90, }, }, { id: "507f1f77bcf86cd799439030" as any, name_fa: "طول جغرافیایی", name_en: "lon", label_fa: "طول جغرافیایی", label_en: "Longitude", placeholder_fa: "طول جغرافیایی را وارد کنید", placeholder_en: "Enter longitude", value: null, dataType: "number", required: true, order: 2, isActive: true, validation: { type: "number", min: -180, max: 180, }, }, ], metadata: { icon: "map-pin", color: "#10B981", requiresGPS: true, autoDetectLocation: true, party: "second", }, }, { stepKey: WorkflowStep.SECOND_VOICE, type: "THIRD_PARTY", stepNumber: 11, isActive: true, stepName_fa: "ضبط صدای طرف دوم", stepName_en: "Second Party Voice Recording", description_fa: "ضبط توضیحات صوتی توسط طرف دوم", description_en: "Voice recording of explanations by second party", category: "SECOND_PARTY", requiredPreviousSteps: [WorkflowStep.SECOND_LOCATION], nextPossibleSteps: [WorkflowStep.SECOND_DESCRIPTION], allowedRoles: ["user"], estimatedDuration: 3, fields: [ { id: "507f1f77bcf86cd799439031" as any, name_fa: "فایل صوتی", name_en: "file", label_fa: "ضبط صدا", label_en: "Voice Recording", placeholder_fa: "توضیحات صوتی خود را ضبط کنید", placeholder_en: "Record your voice explanation", value: null, dataType: "audio-file", required: true, order: 1, isActive: true, validation: { maxSize: 10485760, maxDuration: 300, allowedTypes: [ "audio/mp3", "audio/mpeg", "audio/wav", "audio/m4a", "audio/webm", ], }, }, ], metadata: { icon: "microphone", color: "#F59E0B", recordingType: "voice", requiresMicrophone: true, party: "second", }, }, { stepKey: WorkflowStep.SECOND_DESCRIPTION, type: "THIRD_PARTY", stepNumber: 12, isActive: true, stepName_fa: "توضیحات طرف دوم", stepName_en: "Second Party Description", description_fa: "ثبت توضیحات و جزئیات حادثه توسط طرف دوم", description_en: "Record accident description and details by second party", category: "SECOND_PARTY", requiredPreviousSteps: [WorkflowStep.SECOND_VOICE], nextPossibleSteps: [WorkflowStep.WAITING_FOR_GUILT_DECISION], allowedRoles: ["user"], estimatedDuration: 4, fields: [ { id: "507f1f77bcf86cd799439032" as any, name_fa: "توضیحات", name_en: "desc", label_fa: "توضیحات حادثه", label_en: "Accident Description", placeholder_fa: "توضیحات کامل حادثه را وارد کنید", placeholder_en: "Enter complete accident description", value: null, dataType: "textarea", required: true, order: 1, isActive: true, validation: { minLength: 10, maxLength: 2000, }, }, { id: "507f1f77bcf86cd799439033" as any, name_fa: "تاریخ حادثه", name_en: "accidentDate", label_fa: "تاریخ حادثه", label_en: "Accident Date", placeholder_fa: "تاریخ وقوع حادثه را وارد کنید", placeholder_en: "Enter accident date", value: null, dataType: "date", required: true, order: 2, isActive: true, validation: { format: "YYYY-MM-DD", }, }, { id: "507f1f77bcf86cd799439034" as any, name_fa: "زمان حادثه", name_en: "accidentTime", label_fa: "زمان حادثه", label_en: "Accident Time", placeholder_fa: "زمان وقوع حادثه را وارد کنید", placeholder_en: "Enter accident time", value: null, dataType: "time", required: true, order: 3, isActive: true, validation: { format: "HH:mm", }, }, ], metadata: { icon: "file-text", color: "#6366F1", party: "second", }, }, // CAR_BODY workflow: accident type form (replaces confession for CAR_BODY) { stepKey: WorkflowStep.CAR_BODY_ACCIDENT_TYPE, type: "CAR_BODY", stepNumber: 2, isActive: true, stepName_fa: "نوع حادثه بدنه", stepName_en: "Car Body Accident Type", description_fa: "انتخاب اینکه حادثه با خودرو بوده یا با شیء", description_en: "Select whether accident was with another car or with an object", category: "FIRST_PARTY", requiredPreviousSteps: [WorkflowStep.CREATED], nextPossibleSteps: [WorkflowStep.FIRST_VIDEO], allowedRoles: ["user"], estimatedDuration: 1, fields: [ { id: "507f1f77bcf86cd7994390cb" as any, name_fa: "نوع حادثه", name_en: "accidentType", label_fa: "نوع حادثه", label_en: "Accident Type", placeholder_fa: "حادثه با خودرو یا شیء؟", placeholder_en: "Accident with car or object?", value: [ { label_fa: "با خودرو", label_en: "With Car", value: "car" }, { label_fa: "با شیء", label_en: "With Object", value: "object" }, ], dataType: "select", required: true, order: 1, isActive: true, validation: { enum: ["car", "object"] }, }, ], metadata: { icon: "car", color: "#10B981" }, }, // Add more default steps as needed... ]; return await this.bulkUpsert(defaultSteps); } }