From 1044b57e15e2750d1e3ef159cfc0b69c8aff623b Mon Sep 17 00:00:00 2001 From: kent Date: Tue, 13 Jan 2026 19:49:23 +0900 Subject: [PATCH] =?UTF-8?q?chore(API):=20=EB=A7=88=EC=9D=B4=EA=B7=B8?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=85=98=20=ED=8C=8C=EC=9D=BC=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC=20=EB=B0=8F=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존 마이그레이션 파일 정리 - handover_reports 테이블 마이그레이션 추가 - site_briefings 테이블 마이그레이션 추가 - work_orders process_id 마이그레이션 추가 Co-Authored-By: Claude --- ...work_orders_process_type_to_process_id.php | 193 ++++++++++++++++++ ...222428_alter_payments_paid_at_nullable.php | 2 +- ..._cancel_columns_to_subscriptions_table.php | 2 +- ...ve_bad_debt_columns_from_clients_table.php | 2 +- ...025_12_25_004032_create_salaries_table.php | 2 +- ..._000001_create_expected_expenses_table.php | 2 +- ..._12_26_100000_create_work_orders_table.php | 2 +- ...reate_work_order_bending_details_table.php | 2 +- ..._100300_create_work_order_issues_table.php | 2 +- ..._table_and_drop_legacy_material_tables.php | 2 +- ...25_12_30_091821_create_positions_table.php | 2 +- ...1822_add_position_type_to_common_codes.php | 2 +- ...2_30_131009_add_key_to_positions_table.php | 2 +- ..._add_price_fields_to_order_items_table.php | 2 +- ...41500_add_quote_fields_to_orders_table.php | 2 +- ...05_142000_add_order_id_to_quotes_table.php | 2 +- ...26_01_08_000001_create_contracts_table.php | 2 +- ...1_08_180607_create_process_items_table.php | 2 +- ...9_000001_create_handover_reports_table.php | 88 ++++++++ ..._create_handover_report_managers_table.php | 53 +++++ ...003_create_handover_report_items_table.php | 53 +++++ ..._100000_create_structure_reviews_table.php | 2 +- ...171700_add_order_codes_to_common_codes.php | 2 +- ..._12_100000_create_site_briefings_table.php | 78 +++++++ ...odify_site_briefings_attendees_to_json.php | 33 +++ ...0141_add_site_briefing_to_quotes_table.php | 55 +++++ 26 files changed, 572 insertions(+), 19 deletions(-) create mode 100644 database/migrations/2025_01_09_100000_change_work_orders_process_type_to_process_id.php create mode 100644 database/migrations/2026_01_09_000001_create_handover_reports_table.php create mode 100644 database/migrations/2026_01_09_000002_create_handover_report_managers_table.php create mode 100644 database/migrations/2026_01_09_000003_create_handover_report_items_table.php create mode 100644 database/migrations/2026_01_12_100000_create_site_briefings_table.php create mode 100644 database/migrations/2026_01_12_110000_modify_site_briefings_attendees_to_json.php create mode 100644 database/migrations/2026_01_12_180141_add_site_briefing_to_quotes_table.php diff --git a/database/migrations/2025_01_09_100000_change_work_orders_process_type_to_process_id.php b/database/migrations/2025_01_09_100000_change_work_orders_process_type_to_process_id.php new file mode 100644 index 0000000..14ede0c --- /dev/null +++ b/database/migrations/2025_01_09_100000_change_work_orders_process_type_to_process_id.php @@ -0,0 +1,193 @@ +ensureBendingProcessExists(); + + // 2. process_id 컬럼 추가 (nullable로 먼저 생성) + Schema::table('work_orders', function (Blueprint $table) { + $table->unsignedBigInteger('process_id') + ->nullable() + ->after('sales_order_id') + ->comment('공정 ID (FK → processes.id)'); + }); + + // 3. 기존 process_type 데이터를 process_id로 마이그레이션 + $this->migrateProcessTypeToProcessId(); + + // 4. process_id에 FK 제약 추가 및 NOT NULL로 변경 + Schema::table('work_orders', function (Blueprint $table) { + $table->foreign('process_id') + ->references('id') + ->on('processes') + ->onDelete('restrict'); + }); + + // 5. 기존 process_type 컬럼 제거 + Schema::table('work_orders', function (Blueprint $table) { + $table->dropColumn('process_type'); + }); + } + + public function down(): void + { + // 1. process_type 컬럼 복원 + Schema::table('work_orders', function (Blueprint $table) { + $table->string('process_type', 30) + ->default('screen') + ->after('sales_order_id') + ->comment('공정유형: screen/slat/bending'); + }); + + // 2. process_id에서 process_type으로 데이터 복원 + $this->migrateProcessIdToProcessType(); + + // 3. FK 및 process_id 컬럼 제거 + Schema::table('work_orders', function (Blueprint $table) { + $table->dropForeign(['process_id']); + $table->dropColumn('process_id'); + }); + } + + /** + * 절곡 공정이 없는 테넌트에 절곡 공정 생성 + */ + private function ensureBendingProcessExists(): void + { + // 작업지시에서 bending을 사용하는 테넌트 조회 + $tenantIds = DB::table('work_orders') + ->where('process_type', 'bending') + ->distinct() + ->pluck('tenant_id'); + + foreach ($tenantIds as $tenantId) { + // 해당 테넌트에 절곡 공정이 있는지 확인 + $exists = DB::table('processes') + ->where('tenant_id', $tenantId) + ->where('process_name', '절곡') + ->exists(); + + if (! $exists) { + // 마지막 공정코드 조회 + $lastCode = DB::table('processes') + ->where('tenant_id', $tenantId) + ->orderByDesc('process_code') + ->value('process_code'); + + // 새 공정코드 생성 (P-003 형식) + $newCodeNum = 1; + if ($lastCode && preg_match('/P-(\d+)/', $lastCode, $matches)) { + $newCodeNum = (int) $matches[1] + 1; + } + $newCode = sprintf('P-%03d', $newCodeNum); + + // 절곡 공정 생성 + DB::table('processes')->insert([ + 'tenant_id' => $tenantId, + 'process_code' => $newCode, + 'process_name' => '절곡', + 'process_type' => '생산', + 'description' => '절곡 공정 (마이그레이션에 의해 자동 생성)', + 'is_active' => true, + 'created_at' => now(), + 'updated_at' => now(), + ]); + } + } + } + + /** + * process_type → process_id 데이터 마이그레이션 + */ + private function migrateProcessTypeToProcessId(): void + { + // 공정명 매핑 (process_type → process_name) + $mappings = [ + 'screen' => '스크린', + 'slat' => '슬랫', + 'bending' => '절곡', + ]; + + foreach ($mappings as $processType => $processName) { + // 각 테넌트별로 해당 공정 ID 조회 후 업데이트 + $workOrders = DB::table('work_orders') + ->where('process_type', $processType) + ->whereNull('process_id') + ->get(['id', 'tenant_id']); + + foreach ($workOrders as $workOrder) { + $processId = DB::table('processes') + ->where('tenant_id', $workOrder->tenant_id) + ->where('process_name', $processName) + ->value('id'); + + if ($processId) { + DB::table('work_orders') + ->where('id', $workOrder->id) + ->update(['process_id' => $processId]); + } + } + } + + // process_id가 설정되지 않은 레코드 확인 (데이터 무결성) + $orphanCount = DB::table('work_orders') + ->whereNull('process_id') + ->count(); + + if ($orphanCount > 0) { + throw new \RuntimeException( + "마이그레이션 실패: {$orphanCount}개의 작업지시에 대응하는 공정을 찾을 수 없습니다. ". + 'processes 테이블에 스크린, 슬랫, 절곡 공정이 등록되어 있는지 확인하세요.' + ); + } + } + + /** + * process_id → process_type 데이터 복원 (롤백용) + */ + private function migrateProcessIdToProcessType(): void + { + // 공정명 → process_type 역매핑 + $mappings = [ + '스크린' => 'screen', + '슬랫' => 'slat', + '절곡' => 'bending', + ]; + + foreach ($mappings as $processName => $processType) { + DB::table('work_orders') + ->whereIn('process_id', function ($query) use ($processName) { + $query->select('id') + ->from('processes') + ->where('process_name', $processName); + }) + ->update(['process_type' => $processType]); + } + + // 매핑되지 않은 공정은 기본값 screen으로 설정 + DB::table('work_orders') + ->where('process_type', '') + ->orWhereNull('process_type') + ->update(['process_type' => 'screen']); + } +}; diff --git a/database/migrations/2025_12_22_222428_alter_payments_paid_at_nullable.php b/database/migrations/2025_12_22_222428_alter_payments_paid_at_nullable.php index abdf65c..56068b1 100644 --- a/database/migrations/2025_12_22_222428_alter_payments_paid_at_nullable.php +++ b/database/migrations/2025_12_22_222428_alter_payments_paid_at_nullable.php @@ -27,4 +27,4 @@ public function down(): void $table->datetime('paid_at')->nullable(false)->change(); }); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2025_12_22_222722_add_cancel_columns_to_subscriptions_table.php b/database/migrations/2025_12_22_222722_add_cancel_columns_to_subscriptions_table.php index ddd452b..00b912b 100644 --- a/database/migrations/2025_12_22_222722_add_cancel_columns_to_subscriptions_table.php +++ b/database/migrations/2025_12_22_222722_add_cancel_columns_to_subscriptions_table.php @@ -28,4 +28,4 @@ public function down(): void $table->dropColumn(['cancelled_at', 'cancel_reason']); }); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2025_12_23_222356_remove_bad_debt_columns_from_clients_table.php b/database/migrations/2025_12_23_222356_remove_bad_debt_columns_from_clients_table.php index 3eaa33b..710d8c8 100644 --- a/database/migrations/2025_12_23_222356_remove_bad_debt_columns_from_clients_table.php +++ b/database/migrations/2025_12_23_222356_remove_bad_debt_columns_from_clients_table.php @@ -41,4 +41,4 @@ public function down(): void $table->string('bad_debt_progress', 20)->nullable()->after('bad_debt_end_date')->comment('악성채권 진행상황'); }); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2025_12_25_004032_create_salaries_table.php b/database/migrations/2025_12_25_004032_create_salaries_table.php index 881d4dc..66b1a67 100644 --- a/database/migrations/2025_12_25_004032_create_salaries_table.php +++ b/database/migrations/2025_12_25_004032_create_salaries_table.php @@ -57,4 +57,4 @@ public function down(): void { Schema::dropIfExists('salaries'); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2025_12_26_000001_create_expected_expenses_table.php b/database/migrations/2025_12_26_000001_create_expected_expenses_table.php index 004b20d..f4c5a85 100644 --- a/database/migrations/2025_12_26_000001_create_expected_expenses_table.php +++ b/database/migrations/2025_12_26_000001_create_expected_expenses_table.php @@ -39,4 +39,4 @@ public function down(): void { Schema::dropIfExists('expected_expenses'); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2025_12_26_100000_create_work_orders_table.php b/database/migrations/2025_12_26_100000_create_work_orders_table.php index a82f762..3474893 100644 --- a/database/migrations/2025_12_26_100000_create_work_orders_table.php +++ b/database/migrations/2025_12_26_100000_create_work_orders_table.php @@ -46,4 +46,4 @@ public function down(): void { Schema::dropIfExists('work_orders'); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2025_12_26_100200_create_work_order_bending_details_table.php b/database/migrations/2025_12_26_100200_create_work_order_bending_details_table.php index 4fcd97a..0bfd6ae 100644 --- a/database/migrations/2025_12_26_100200_create_work_order_bending_details_table.php +++ b/database/migrations/2025_12_26_100200_create_work_order_bending_details_table.php @@ -43,4 +43,4 @@ public function down(): void { Schema::dropIfExists('work_order_bending_details'); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2025_12_26_100300_create_work_order_issues_table.php b/database/migrations/2025_12_26_100300_create_work_order_issues_table.php index 579f8f5..0538295 100644 --- a/database/migrations/2025_12_26_100300_create_work_order_issues_table.php +++ b/database/migrations/2025_12_26_100300_create_work_order_issues_table.php @@ -39,4 +39,4 @@ public function down(): void { Schema::dropIfExists('work_order_issues'); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2025_12_29_175820_create_inspections_table_and_drop_legacy_material_tables.php b/database/migrations/2025_12_29_175820_create_inspections_table_and_drop_legacy_material_tables.php index 42878de..560a7ee 100644 --- a/database/migrations/2025_12_29_175820_create_inspections_table_and_drop_legacy_material_tables.php +++ b/database/migrations/2025_12_29_175820_create_inspections_table_and_drop_legacy_material_tables.php @@ -113,4 +113,4 @@ public function down(): void $table->index('inspection_id'); }); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2025_12_30_091821_create_positions_table.php b/database/migrations/2025_12_30_091821_create_positions_table.php index 6cc0f4e..66eaf0b 100644 --- a/database/migrations/2025_12_30_091821_create_positions_table.php +++ b/database/migrations/2025_12_30_091821_create_positions_table.php @@ -27,4 +27,4 @@ public function down(): void { Schema::dropIfExists('positions'); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2025_12_30_091822_add_position_type_to_common_codes.php b/database/migrations/2025_12_30_091822_add_position_type_to_common_codes.php index 76197a1..8f7e29b 100644 --- a/database/migrations/2025_12_30_091822_add_position_type_to_common_codes.php +++ b/database/migrations/2025_12_30_091822_add_position_type_to_common_codes.php @@ -35,4 +35,4 @@ public function down(): void { DB::table('common_codes')->where('code_group', 'position_type')->delete(); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2025_12_30_131009_add_key_to_positions_table.php b/database/migrations/2025_12_30_131009_add_key_to_positions_table.php index 8f869bf..82b19e2 100644 --- a/database/migrations/2025_12_30_131009_add_key_to_positions_table.php +++ b/database/migrations/2025_12_30_131009_add_key_to_positions_table.php @@ -27,4 +27,4 @@ public function down(): void $table->dropColumn('key'); }); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2026_01_05_140111_add_price_fields_to_order_items_table.php b/database/migrations/2026_01_05_140111_add_price_fields_to_order_items_table.php index c976070..0fc88d8 100644 --- a/database/migrations/2026_01_05_140111_add_price_fields_to_order_items_table.php +++ b/database/migrations/2026_01_05_140111_add_price_fields_to_order_items_table.php @@ -75,4 +75,4 @@ public function down(): void ]); }); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2026_01_05_141500_add_quote_fields_to_orders_table.php b/database/migrations/2026_01_05_141500_add_quote_fields_to_orders_table.php index 50c13c8..e80a524 100644 --- a/database/migrations/2026_01_05_141500_add_quote_fields_to_orders_table.php +++ b/database/migrations/2026_01_05_141500_add_quote_fields_to_orders_table.php @@ -63,4 +63,4 @@ public function down(): void ]); }); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2026_01_05_142000_add_order_id_to_quotes_table.php b/database/migrations/2026_01_05_142000_add_order_id_to_quotes_table.php index 8169061..d86d39c 100644 --- a/database/migrations/2026_01_05_142000_add_order_id_to_quotes_table.php +++ b/database/migrations/2026_01_05_142000_add_order_id_to_quotes_table.php @@ -30,4 +30,4 @@ public function down(): void $table->dropColumn('order_id'); }); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2026_01_08_000001_create_contracts_table.php b/database/migrations/2026_01_08_000001_create_contracts_table.php index be3d519..c5d1390 100644 --- a/database/migrations/2026_01_08_000001_create_contracts_table.php +++ b/database/migrations/2026_01_08_000001_create_contracts_table.php @@ -74,4 +74,4 @@ public function down(): void { Schema::dropIfExists('contracts'); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2026_01_08_180607_create_process_items_table.php b/database/migrations/2026_01_08_180607_create_process_items_table.php index af3d505..f4c98c5 100644 --- a/database/migrations/2026_01_08_180607_create_process_items_table.php +++ b/database/migrations/2026_01_08_180607_create_process_items_table.php @@ -41,4 +41,4 @@ public function down(): void { Schema::dropIfExists('process_items'); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2026_01_09_000001_create_handover_reports_table.php b/database/migrations/2026_01_09_000001_create_handover_reports_table.php new file mode 100644 index 0000000..05b5cb9 --- /dev/null +++ b/database/migrations/2026_01_09_000001_create_handover_reports_table.php @@ -0,0 +1,88 @@ +id(); + $table->unsignedBigInteger('tenant_id')->comment('테넌트 ID'); + + // 보고서 기본 정보 + $table->string('report_number', 50)->comment('보고서번호'); + $table->unsignedBigInteger('contract_id')->nullable()->comment('연결 계약 ID'); + $table->string('site_name')->comment('현장명'); + + // 거래처 정보 + $table->unsignedBigInteger('partner_id')->nullable()->comment('거래처 ID'); + $table->string('partner_name')->nullable()->comment('거래처명'); + + // 담당자 정보 + $table->unsignedBigInteger('contract_manager_id')->nullable()->comment('계약담당자 ID'); + $table->string('contract_manager_name')->nullable()->comment('계약담당자명'); + $table->unsignedBigInteger('construction_pm_id')->nullable()->comment('공사PM ID'); + $table->string('construction_pm_name')->nullable()->comment('공사PM명'); + + // 계약 상세 + $table->integer('total_sites')->default(0)->comment('총 개소'); + $table->decimal('contract_amount', 15, 2)->default(0)->comment('계약금액'); + $table->date('contract_date')->nullable()->comment('계약일자'); + $table->date('contract_start_date')->nullable()->comment('계약시작일'); + $table->date('contract_end_date')->nullable()->comment('계약종료일'); + $table->date('completion_date')->nullable()->comment('준공일'); + + // 상태 정보 + $table->string('status', 20)->default('pending')->comment('인수인계상태: pending, completed'); + + // 2차 배관 + $table->boolean('has_secondary_piping')->default(false)->comment('2차 배관 유무'); + $table->decimal('secondary_piping_amount', 15, 2)->default(0)->comment('2차 배관 금액'); + $table->text('secondary_piping_note')->nullable()->comment('2차 배관 비고'); + + // 도장 & 코킹 + $table->boolean('has_coating')->default(false)->comment('도장 & 코킹 유무'); + $table->decimal('coating_amount', 15, 2)->default(0)->comment('도장 & 코킹 금액'); + $table->text('coating_note')->nullable()->comment('도장 & 코킹 비고'); + + // 장비 외 실행금액 (JSON) + $table->json('external_equipment_cost')->nullable()->comment('장비 외 실행금액: shipping_cost, high_altitude_work, public_expense'); + + // 특이사항 + $table->text('special_notes')->nullable()->comment('특이사항'); + + // 활성화 상태 + $table->boolean('is_active')->default(true)->comment('활성화 여부'); + + // 감사 컬럼 + $table->unsignedBigInteger('created_by')->nullable()->comment('생성자 ID'); + $table->unsignedBigInteger('updated_by')->nullable()->comment('수정자 ID'); + $table->unsignedBigInteger('deleted_by')->nullable()->comment('삭제자 ID'); + + // 타임스탬프 + $table->timestamps(); + $table->softDeletes(); + + // 인덱스 + $table->index('tenant_id'); + $table->index('contract_id'); + $table->index('partner_id'); + $table->index('status'); + $table->unique(['tenant_id', 'report_number']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('handover_reports'); + } +}; diff --git a/database/migrations/2026_01_09_000002_create_handover_report_managers_table.php b/database/migrations/2026_01_09_000002_create_handover_report_managers_table.php new file mode 100644 index 0000000..0dc4f9f --- /dev/null +++ b/database/migrations/2026_01_09_000002_create_handover_report_managers_table.php @@ -0,0 +1,53 @@ +id(); + $table->unsignedBigInteger('tenant_id')->comment('테넌트 ID'); + $table->unsignedBigInteger('handover_report_id')->comment('인수인계보고서 ID'); + + // 담당자 정보 + $table->string('name')->comment('담당자명'); + $table->text('non_performance_reason')->nullable()->comment('미이행 사유'); + $table->text('signature')->nullable()->comment('서명 (Base64 또는 URL)'); + + // 순서 + $table->integer('sort_order')->default(0)->comment('정렬 순서'); + + // 감사 컬럼 + $table->unsignedBigInteger('created_by')->nullable()->comment('생성자 ID'); + $table->unsignedBigInteger('updated_by')->nullable()->comment('수정자 ID'); + + // 타임스탬프 + $table->timestamps(); + + // 인덱스 + $table->index('tenant_id'); + $table->index('handover_report_id'); + + // 외래키 + $table->foreign('handover_report_id') + ->references('id') + ->on('handover_reports') + ->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('handover_report_managers'); + } +}; diff --git a/database/migrations/2026_01_09_000003_create_handover_report_items_table.php b/database/migrations/2026_01_09_000003_create_handover_report_items_table.php new file mode 100644 index 0000000..a180aed --- /dev/null +++ b/database/migrations/2026_01_09_000003_create_handover_report_items_table.php @@ -0,0 +1,53 @@ +id(); + $table->unsignedBigInteger('tenant_id')->comment('테넌트 ID'); + $table->unsignedBigInteger('handover_report_id')->comment('인수인계보고서 ID'); + + // ITEM 정보 + $table->integer('item_no')->default(0)->comment('순번'); + $table->string('name')->comment('명칭'); + $table->string('product')->nullable()->comment('제품'); + $table->integer('quantity')->default(0)->comment('수량'); + $table->text('remark')->nullable()->comment('비고'); + + // 감사 컬럼 + $table->unsignedBigInteger('created_by')->nullable()->comment('생성자 ID'); + $table->unsignedBigInteger('updated_by')->nullable()->comment('수정자 ID'); + + // 타임스탬프 + $table->timestamps(); + + // 인덱스 + $table->index('tenant_id'); + $table->index('handover_report_id'); + $table->index(['handover_report_id', 'item_no']); + + // 외래키 + $table->foreign('handover_report_id') + ->references('id') + ->on('handover_reports') + ->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('handover_report_items'); + } +}; diff --git a/database/migrations/2026_01_09_100000_create_structure_reviews_table.php b/database/migrations/2026_01_09_100000_create_structure_reviews_table.php index 2596c2b..d9fb9e0 100644 --- a/database/migrations/2026_01_09_100000_create_structure_reviews_table.php +++ b/database/migrations/2026_01_09_100000_create_structure_reviews_table.php @@ -51,4 +51,4 @@ public function down(): void { Schema::dropIfExists('structure_reviews'); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2026_01_09_171700_add_order_codes_to_common_codes.php b/database/migrations/2026_01_09_171700_add_order_codes_to_common_codes.php index 625ff7f..ff69302 100644 --- a/database/migrations/2026_01_09_171700_add_order_codes_to_common_codes.php +++ b/database/migrations/2026_01_09_171700_add_order_codes_to_common_codes.php @@ -66,4 +66,4 @@ public function down(): void DB::table('common_codes')->where('code_group', 'order_status')->delete(); DB::table('common_codes')->where('code_group', 'order_type')->delete(); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2026_01_12_100000_create_site_briefings_table.php b/database/migrations/2026_01_12_100000_create_site_briefings_table.php new file mode 100644 index 0000000..c444188 --- /dev/null +++ b/database/migrations/2026_01_12_100000_create_site_briefings_table.php @@ -0,0 +1,78 @@ +id(); + $table->foreignId('tenant_id')->comment('테넌트 ID'); + + // 기본 정보 + $table->string('briefing_code', 50)->nullable()->comment('현설번호 (자동생성)'); + $table->string('title', 200)->comment('현장설명회명/현장명'); + $table->text('description')->nullable()->comment('설명/업무보고'); + + // 거래처/현장 연결 + $table->foreignId('partner_id')->nullable()->comment('거래처 ID'); + $table->foreignId('site_id')->nullable()->comment('현장 ID'); + + // 일정 정보 + $table->date('briefing_date')->comment('현장설명회 일자'); + $table->string('briefing_time', 10)->nullable()->comment('현장설명회 시간 (HH:MM)'); + $table->enum('briefing_type', ['online', 'offline'])->default('offline')->comment('구분: online|offline'); + $table->string('location', 200)->nullable()->comment('현장설명회 장소'); + $table->string('address', 255)->nullable()->comment('주소'); + + // 상태 정보 + $table->enum('status', ['scheduled', 'ongoing', 'completed', 'cancelled', 'postponed']) + ->default('scheduled')->comment('상태: scheduled|ongoing|completed|cancelled|postponed'); + $table->enum('bid_status', ['pending', 'bidding', 'closed', 'failed', 'awarded']) + ->default('pending')->comment('입찰상태: pending|bidding|closed|failed|awarded'); + $table->date('bid_date')->nullable()->comment('입찰일자'); + + // 참석자 정보 + $table->string('attendee', 100)->nullable()->comment('담당 참석자'); + $table->enum('attendance_status', ['scheduled', 'attended', 'absent']) + ->default('scheduled')->comment('참석상태: scheduled|attended|absent'); + $table->integer('attendee_count')->default(0)->comment('총 참석자 수'); + + // 공사 정보 + $table->integer('site_count')->default(0)->comment('개소 수'); + $table->date('construction_start_date')->nullable()->comment('공사기간 시작'); + $table->date('construction_end_date')->nullable()->comment('공사기간 종료'); + $table->enum('vat_type', ['excluded', 'included'])->default('excluded')->comment('부가세: excluded|included'); + + // 감사 필드 + $table->foreignId('created_by')->nullable()->comment('생성자'); + $table->foreignId('updated_by')->nullable()->comment('수정자'); + $table->foreignId('deleted_by')->nullable()->comment('삭제자'); + $table->softDeletes(); + $table->timestamps(); + + // 인덱스 + $table->index('tenant_id'); + $table->index('partner_id'); + $table->index('site_id'); + $table->index('briefing_date'); + $table->index('status'); + $table->index('bid_status'); + $table->unique(['tenant_id', 'briefing_code']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('site_briefings'); + } +}; diff --git a/database/migrations/2026_01_12_110000_modify_site_briefings_attendees_to_json.php b/database/migrations/2026_01_12_110000_modify_site_briefings_attendees_to_json.php new file mode 100644 index 0000000..5e5f0d5 --- /dev/null +++ b/database/migrations/2026_01_12_110000_modify_site_briefings_attendees_to_json.php @@ -0,0 +1,33 @@ +dropColumn('attendee'); + }); + + Schema::table('site_briefings', function (Blueprint $table) { + // JSON 컬럼으로 참석자 목록 저장 + // 구조: [{"user_id": 1, "name": "홍길동", "type": "internal"}, {"name": "외부인", "company": "업체명", "phone": "010-1234-5678", "type": "external"}] + $table->json('attendees')->nullable()->after('bid_date')->comment('참석자 목록 (JSON)'); + }); + } + + public function down(): void + { + Schema::table('site_briefings', function (Blueprint $table) { + $table->dropColumn('attendees'); + }); + + Schema::table('site_briefings', function (Blueprint $table) { + $table->string('attendee', 100)->nullable()->after('bid_date')->comment('참석자 (단일)'); + }); + } +}; diff --git a/database/migrations/2026_01_12_180141_add_site_briefing_to_quotes_table.php b/database/migrations/2026_01_12_180141_add_site_briefing_to_quotes_table.php new file mode 100644 index 0000000..f762e58 --- /dev/null +++ b/database/migrations/2026_01_12_180141_add_site_briefing_to_quotes_table.php @@ -0,0 +1,55 @@ +foreignId('site_briefing_id') + ->nullable() + ->after('order_id') + ->comment('현장설명회 ID (자동생성 시)'); + + // 인덱스 추가 + $table->index('site_briefing_id', 'idx_quotes_site_briefing_id'); + }); + + // product_category를 nullable enum으로 변경 + // MySQL에서 enum 변경은 raw SQL 필요 + DB::statement("ALTER TABLE quotes MODIFY product_category ENUM('SCREEN', 'STEEL') NULL COMMENT '제품 카테고리'"); + + // status에 'pending' 추가 + DB::statement("ALTER TABLE quotes MODIFY status ENUM('pending', 'draft', 'sent', 'approved', 'rejected', 'finalized', 'converted') DEFAULT 'draft' COMMENT '상태'"); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('quotes', function (Blueprint $table) { + $table->dropIndex('idx_quotes_site_briefing_id'); + $table->dropColumn('site_briefing_id'); + }); + + // product_category를 NOT NULL로 복원 + DB::statement("ALTER TABLE quotes MODIFY product_category ENUM('SCREEN', 'STEEL') NOT NULL COMMENT '제품 카테고리'"); + + // status에서 'pending' 제거 + DB::statement("ALTER TABLE quotes MODIFY status ENUM('draft', 'sent', 'approved', 'rejected', 'finalized', 'converted') DEFAULT 'draft' COMMENT '상태'"); + } +};