import { Injectable, NotFoundException, BadRequestException, ConflictException } 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 { constructor( private readonly workflowStepDbService: WorkflowStepDbService, ) {} /** * 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); } }