ultimatepos/database/migrations/2026_06_30_200004_cmms_asset_lifecycle.php

265 lines
16 KiB
PHP

<?php
use App\Support\LegacyForeignKeys;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('maintenance_production_lines', function (Blueprint $table) {
$table->id();
LegacyForeignKeys::business($table);
LegacyForeignKeys::project($table);
$table->string('code');
$table->string('name');
$table->text('description')->nullable();
$table->string('location')->nullable();
$table->string('status')->default('active');
$table->timestamps();
$table->softDeletes();
$table->unique(['business_id', 'code'], 'mnt_line_biz_code_uq');
$table->index(['business_id', 'project_id'], 'mnt_line_biz_proj_idx');
});
Schema::create('maintenance_part_catalogs', function (Blueprint $table) {
$table->id();
LegacyForeignKeys::business($table);
$table->string('catalog_code');
$table->string('name');
$table->string('manufacturer')->nullable();
$table->string('manufacturer_part_number')->nullable();
$table->string('part_type')->default('mechanical'); // mechanical, electronic, consumable, hydraulic, pneumatic
$table->boolean('is_consumable')->default(false);
$table->text('description')->nullable();
$table->json('specifications')->nullable();
$table->unsignedInteger('default_replacement_interval_days')->nullable();
$table->boolean('is_oem')->default(true);
$table->json('equivalent_parts')->nullable();
$table->string('status')->default('active');
$table->timestamps();
$table->softDeletes();
$table->unique(['business_id', 'catalog_code'], 'mnt_cat_biz_code_uq');
});
Schema::create('maintenance_service_providers', function (Blueprint $table) {
$table->id();
LegacyForeignKeys::business($table);
$table->string('name');
$table->string('provider_type')->default('external'); // internal, external
$table->string('capability')->default('both'); // repair, commissioning, both
$table->json('specialties')->nullable();
$table->string('phone')->nullable();
$table->string('email')->nullable();
LegacyForeignKeys::user($table, 'linked_user_id');
$table->text('notes')->nullable();
$table->boolean('is_active')->default(true);
$table->timestamps();
$table->softDeletes();
});
Schema::table('maintenance_equipment', function (Blueprint $table) {
if (! Schema::hasColumn('maintenance_equipment', 'production_line_id')) {
$table->foreignId('production_line_id')->nullable()->after('category_id')
->constrained('maintenance_production_lines')->nullOnDelete();
}
});
Schema::table('maintenance_equipment_parts', function (Blueprint $table) {
if (! Schema::hasColumn('maintenance_equipment_parts', 'parent_id')) {
$table->foreignId('parent_id')->nullable()->after('equipment_id')
->constrained('maintenance_equipment_parts')->nullOnDelete();
}
if (! Schema::hasColumn('maintenance_equipment_parts', 'part_catalog_id')) {
$table->foreignId('part_catalog_id')->nullable()->after('spare_part_id')
->constrained('maintenance_part_catalogs')->nullOnDelete();
}
$cols = [
'part_type' => fn ($t) => $t->string('part_type')->default('mechanical')->after('unit'),
'is_consumable' => fn ($t) => $t->boolean('is_consumable')->default(false)->after('part_type'),
'responsibility' => fn ($t) => $t->string('responsibility')->default('ours')->after('is_consumable'),
'responsibility_party_name' => fn ($t) => $t->string('responsibility_party_name')->nullable()->after('responsibility'),
'warranty_status' => fn ($t) => $t->string('warranty_status')->default('none')->after('responsibility_party_name'),
'warranty_provider' => fn ($t) => $t->string('warranty_provider')->nullable()->after('warranty_status'),
'warranty_expires_at' => fn ($t) => $t->date('warranty_expires_at')->nullable()->after('warranty_provider'),
'installed_by' => fn ($t) => $t->string('installed_by')->nullable()->after('warranty_expires_at'),
'installer_name' => fn ($t) => $t->string('installer_name')->nullable()->after('installed_by'),
'expected_replacement_date' => fn ($t) => $t->date('expected_replacement_date')->nullable()->after('installer_name'),
'replacement_interval_days' => fn ($t) => $t->unsignedInteger('replacement_interval_days')->nullable()->after('expected_replacement_date'),
'last_replaced_at' => fn ($t) => $t->date('last_replaced_at')->nullable()->after('replacement_interval_days'),
'replacement_quality_grade' => fn ($t) => $t->string('replacement_quality_grade')->nullable()->after('last_replaced_at'),
'damage_action' => fn ($t) => $t->string('damage_action')->nullable()->after('replacement_quality_grade'),
'damage_service_list' => fn ($t) => $t->json('damage_service_list')->nullable()->after('damage_action'),
'multi_year_service_plan' => fn ($t) => $t->json('multi_year_service_plan')->nullable()->after('damage_service_list'),
'health_percentage' => fn ($t) => $t->decimal('health_percentage', 5, 2)->nullable()->after('multi_year_service_plan'),
];
foreach ($cols as $col => $callback) {
if (! Schema::hasColumn('maintenance_equipment_parts', $col)) {
Schema::table('maintenance_equipment_parts', $callback);
}
}
});
Schema::create('maintenance_asset_documents', function (Blueprint $table) {
$table->id();
LegacyForeignKeys::business($table);
$table->string('documentable_type');
$table->unsignedBigInteger('documentable_id');
$table->string('doc_type'); // datasheet, catalog, installation_manual, etc.
$table->string('title');
$table->string('file_path')->nullable();
$table->string('file_url')->nullable();
$table->string('mime_type')->nullable();
$table->string('language')->default('fa');
$table->string('version')->nullable();
$table->date('effective_date')->nullable();
LegacyForeignKeys::user($table, 'uploaded_by');
$table->text('notes')->nullable();
$table->unsignedInteger('sort_order')->default(0);
$table->timestamps();
$table->index(['documentable_type', 'documentable_id'], 'mnt_doc_morph_idx');
$table->index(['business_id', 'doc_type'], 'mnt_doc_biz_type_idx');
});
Schema::create('maintenance_approvals', function (Blueprint $table) {
$table->id();
LegacyForeignKeys::business($table);
$table->string('approvable_type');
$table->unsignedBigInteger('approvable_id');
$table->string('approval_type'); // signature_canvas, signature_photo, stamp_photo
$table->longText('signature_data')->nullable();
$table->string('photo_path')->nullable();
LegacyForeignKeys::user($table, 'approver_id');
$table->string('approver_name')->nullable();
$table->string('approver_role')->nullable();
$table->timestamp('approved_at')->nullable();
$table->text('notes')->nullable();
$table->timestamps();
$table->index(['approvable_type', 'approvable_id'], 'mnt_appr_morph_idx');
});
Schema::create('maintenance_inspection_visits', function (Blueprint $table) {
$table->id();
LegacyForeignKeys::business($table);
$table->foreignId('equipment_id')->constrained('maintenance_equipment')->cascadeOnDelete();
$table->foreignId('production_line_id')->nullable()->constrained('maintenance_production_lines')->nullOnDelete();
$table->timestamp('visit_date');
$table->decimal('health_percentage', 5, 2)->nullable();
$table->decimal('working_hours_at_visit', 12, 2)->nullable();
$table->string('visit_type')->default('routine');
$table->foreignId('checklist_response_id')->nullable()->constrained('maintenance_checklist_responses')->nullOnDelete();
LegacyForeignKeys::user($table, 'inspector_id');
$table->foreignId('service_provider_id')->nullable()->constrained('maintenance_service_providers')->nullOnDelete();
$table->text('summary')->nullable();
$table->json('attachments')->nullable();
$table->foreignId('approval_id')->nullable()->constrained('maintenance_approvals')->nullOnDelete();
$table->string('status')->default('draft'); // draft, completed, approved
$table->timestamps();
$table->index(['business_id', 'equipment_id'], 'mnt_insp_biz_eq_idx');
});
Schema::create('maintenance_inspection_visit_items', function (Blueprint $table) {
$table->id();
$table->foreignId('inspection_visit_id')->constrained('maintenance_inspection_visits')->cascadeOnDelete();
$table->foreignId('equipment_part_id')->nullable()->constrained('maintenance_equipment_parts')->nullOnDelete();
$table->string('part_label');
$table->decimal('health_percentage', 5, 2)->nullable();
$table->string('condition')->nullable(); // good, fair, poor, critical
$table->text('notes')->nullable();
$table->json('photos')->nullable();
$table->boolean('needs_replacement')->default(false);
$table->date('expected_replacement_date')->nullable();
$table->timestamps();
});
Schema::create('maintenance_service_records', function (Blueprint $table) {
$table->id();
LegacyForeignKeys::business($table);
$table->foreignId('equipment_id')->constrained('maintenance_equipment')->cascadeOnDelete();
$table->foreignId('work_order_id')->nullable()->constrained('maintenance_work_orders')->nullOnDelete();
$table->foreignId('overhaul_id')->nullable()->constrained('maintenance_overhauls')->nullOnDelete();
$table->string('service_type'); // minor, major, overhaul
$table->string('title');
$table->text('description')->nullable();
$table->timestamp('started_at')->nullable();
$table->timestamp('completed_at')->nullable();
$table->decimal('labor_hours', 8, 2)->nullable();
$table->unsignedBigInteger('cost')->default(0);
LegacyForeignKeys::user($table, 'performed_by');
$table->foreignId('repair_provider_id')->nullable()->constrained('maintenance_service_providers')->nullOnDelete();
$table->foreignId('commissioning_provider_id')->nullable()->constrained('maintenance_service_providers')->nullOnDelete();
$table->decimal('health_before', 5, 2)->nullable();
$table->decimal('health_after', 5, 2)->nullable();
$table->foreignId('approval_id')->nullable()->constrained('maintenance_approvals')->nullOnDelete();
$table->string('status')->default('draft');
$table->timestamps();
$table->index(['business_id', 'equipment_id'], 'mnt_svc_biz_eq_idx');
});
Schema::create('maintenance_service_record_items', function (Blueprint $table) {
$table->id();
$table->foreignId('service_record_id')->constrained('maintenance_service_records')->cascadeOnDelete();
$table->foreignId('equipment_part_id')->nullable()->constrained('maintenance_equipment_parts')->nullOnDelete();
$table->string('part_label')->nullable();
$table->string('action'); // replaced, repaired, adjusted, cleaned, inspected
$table->decimal('quantity', 12, 3)->default(1);
$table->string('replacement_quality_grade')->nullable();
$table->text('notes')->nullable();
$table->timestamps();
});
Schema::create('maintenance_part_replacements', function (Blueprint $table) {
$table->id();
LegacyForeignKeys::business($table);
$table->foreignId('equipment_id')->constrained('maintenance_equipment')->cascadeOnDelete();
$table->foreignId('equipment_part_id')->nullable()->constrained('maintenance_equipment_parts')->nullOnDelete();
$table->foreignId('spare_part_id')->nullable()->constrained('maintenance_spare_parts')->nullOnDelete();
$table->date('replaced_at');
$table->decimal('quantity', 12, 3)->default(1);
$table->string('action')->default('replaced');
$table->decimal('health_before', 5, 2)->nullable();
$table->decimal('health_after', 5, 2)->nullable();
$table->string('replacement_quality_grade')->nullable();
LegacyForeignKeys::user($table, 'performed_by');
$table->foreignId('service_record_id')->nullable()->constrained('maintenance_service_records')->nullOnDelete();
$table->foreignId('approval_id')->nullable()->constrained('maintenance_approvals')->nullOnDelete();
$table->text('notes')->nullable();
$table->timestamps();
$table->index(['equipment_id', 'equipment_part_id'], 'mnt_repl_eq_part_idx');
});
}
public function down(): void
{
Schema::dropIfExists('maintenance_part_replacements');
Schema::dropIfExists('maintenance_service_record_items');
Schema::dropIfExists('maintenance_service_records');
Schema::dropIfExists('maintenance_inspection_visit_items');
Schema::dropIfExists('maintenance_inspection_visits');
Schema::dropIfExists('maintenance_approvals');
Schema::dropIfExists('maintenance_asset_documents');
Schema::table('maintenance_equipment_parts', function (Blueprint $table) {
foreach (['parent_id', 'part_catalog_id', 'part_type', 'is_consumable', 'responsibility', 'responsibility_party_name', 'warranty_status', 'warranty_provider', 'warranty_expires_at', 'installed_by', 'installer_name', 'expected_replacement_date', 'replacement_interval_days', 'last_replaced_at', 'replacement_quality_grade', 'damage_action', 'damage_service_list', 'multi_year_service_plan', 'health_percentage'] as $col) {
if (Schema::hasColumn('maintenance_equipment_parts', $col)) {
if (in_array($col, ['parent_id', 'part_catalog_id'])) {
$table->dropConstrainedForeignId($col);
} else {
$table->dropColumn($col);
}
}
}
});
Schema::table('maintenance_equipment', function (Blueprint $table) {
if (Schema::hasColumn('maintenance_equipment', 'production_line_id')) {
$table->dropConstrainedForeignId('production_line_id');
}
});
Schema::dropIfExists('maintenance_service_providers');
Schema::dropIfExists('maintenance_asset_documents');
Schema::dropIfExists('maintenance_part_catalogs');
Schema::dropIfExists('maintenance_production_lines');
}
};