diff --git a/tests/Feature/Production/WorkOrderApiTest.php b/tests/Feature/Production/WorkOrderApiTest.php new file mode 100644 index 0000000..4380014 --- /dev/null +++ b/tests/Feature/Production/WorkOrderApiTest.php @@ -0,0 +1,299 @@ +setUpAuthenticatedUser(); + + // 공정 + $this->process = Process::create([ + 'tenant_id' => $this->tenant->id, + 'process_code' => 'PRC-'.uniqid(), + 'process_name' => '스크린 조립', + 'process_type' => 'assembly', + 'is_active' => true, + 'created_by' => $this->user->id, + 'updated_by' => $this->user->id, + ]); + + // 수주 (작업지시의 부모) + $client = Client::create([ + 'tenant_id' => $this->tenant->id, + 'name' => '테스트 거래처', + 'client_code' => 'CLI'.uniqid(), + 'is_active' => true, + 'created_by' => $this->user->id, + ]); + + $this->order = Order::create([ + 'tenant_id' => $this->tenant->id, + 'order_no' => 'ORD-'.uniqid(), + 'order_type_code' => Order::TYPE_ORDER, + 'status_code' => Order::STATUS_CONFIRMED, + 'client_id' => $client->id, + 'client_name' => $client->name, + 'quantity' => 10, + 'supply_amount' => 1000000, + 'tax_amount' => 100000, + 'total_amount' => 1100000, + 'created_by' => $this->user->id, + 'updated_by' => $this->user->id, + ]); + } + + // ==================== 목록/통계 조회 ==================== + + public function test_작업지시_목록_조회(): void + { + $this->createWorkOrder(); + + $response = $this->api('get', '/api/v1/work-orders'); + + $this->assertApiPaginated($response); + } + + public function test_작업지시_통계_조회(): void + { + $this->createWorkOrder(); + + $response = $this->api('get', '/api/v1/work-orders/stats'); + + $response->assertStatus(200) + ->assertJsonStructure(['success', 'data']); + } + + // ==================== CRUD ==================== + + public function test_작업지시_생성_성공(): void + { + $response = $this->api('post', '/api/v1/work-orders', [ + 'process_id' => $this->process->id, + 'sales_order_id' => $this->order->id, + 'assignee_ids' => [$this->user->id], + 'priority' => 5, + 'scheduled_date' => now()->addDays(7)->format('Y-m-d'), + 'memo' => '테스트 작업지시', + ]); + + $response->assertStatus(200); + $this->assertTrue($response->json('success')); + + $this->assertDatabaseHas('work_orders', [ + 'process_id' => $this->process->id, + 'sales_order_id' => $this->order->id, + 'tenant_id' => $this->tenant->id, + ]); + } + + public function test_작업지시_상세_조회(): void + { + $wo = $this->createWorkOrder(); + + $response = $this->api('get', "/api/v1/work-orders/{$wo->id}"); + + $this->assertApiSuccess($response); + + $data = $response->json('data'); + $this->assertEquals($wo->id, $data['id']); + } + + public function test_작업지시_수정(): void + { + $wo = $this->createWorkOrder(); + + $response = $this->api('put', "/api/v1/work-orders/{$wo->id}", [ + 'memo' => '수정된 메모', + 'priority' => 1, + ]); + + $response->assertStatus(200); + + $this->assertDatabaseHas('work_orders', [ + 'id' => $wo->id, + 'memo' => '수정된 메모', + ]); + } + + public function test_작업지시_삭제(): void + { + $wo = $this->createWorkOrder(); + + $response = $this->api('delete', "/api/v1/work-orders/{$wo->id}"); + + $response->assertStatus(200); + + $this->assertSoftDeleted('work_orders', ['id' => $wo->id]); + } + + public function test_존재하지_않는_작업지시_조회시_404(): void + { + $response = $this->api('get', '/api/v1/work-orders/999999'); + + $response->assertStatus(404); + } + + // ==================== 상태 전이 ==================== + + public function test_상태_미배정에서_대기로_변경(): void + { + $wo = $this->createWorkOrder(WorkOrder::STATUS_UNASSIGNED); + + $response = $this->api('patch', "/api/v1/work-orders/{$wo->id}/status", [ + 'status' => WorkOrder::STATUS_PENDING, + ]); + + $response->assertStatus(200); + + $this->assertDatabaseHas('work_orders', [ + 'id' => $wo->id, + 'status' => WorkOrder::STATUS_PENDING, + ]); + } + + public function test_상태_대기에서_준비중으로_변경(): void + { + $wo = $this->createWorkOrder(WorkOrder::STATUS_PENDING); + + $response = $this->api('patch', "/api/v1/work-orders/{$wo->id}/status", [ + 'status' => WorkOrder::STATUS_WAITING, + ]); + + $response->assertStatus(200); + + $this->assertDatabaseHas('work_orders', [ + 'id' => $wo->id, + 'status' => WorkOrder::STATUS_WAITING, + ]); + } + + public function test_상태_준비중에서_진행중으로_변경(): void + { + $wo = $this->createWorkOrder(WorkOrder::STATUS_WAITING); + + $response = $this->api('patch', "/api/v1/work-orders/{$wo->id}/status", [ + 'status' => WorkOrder::STATUS_IN_PROGRESS, + ]); + + $response->assertStatus(200); + + $this->assertDatabaseHas('work_orders', [ + 'id' => $wo->id, + 'status' => WorkOrder::STATUS_IN_PROGRESS, + ]); + } + + public function test_상태_진행중에서_완료로_변경(): void + { + $wo = $this->createWorkOrder(WorkOrder::STATUS_IN_PROGRESS); + + $response = $this->api('patch', "/api/v1/work-orders/{$wo->id}/status", [ + 'status' => WorkOrder::STATUS_COMPLETED, + ]); + + $response->assertStatus(200); + + $this->assertDatabaseHas('work_orders', [ + 'id' => $wo->id, + 'status' => WorkOrder::STATUS_COMPLETED, + ]); + } + + // ==================== 담당자 배정 ==================== + + public function test_담당자_배정(): void + { + $wo = $this->createWorkOrder(WorkOrder::STATUS_UNASSIGNED); + + $response = $this->api('patch', "/api/v1/work-orders/{$wo->id}/assign", [ + 'assignee_id' => $this->user->id, + ]); + + $response->assertStatus(200); + + $this->assertDatabaseHas('work_orders', [ + 'id' => $wo->id, + 'assignee_id' => $this->user->id, + ]); + } + + // ==================== 공정 단계 ==================== + + public function test_공정_단계_진행_조회(): void + { + $wo = $this->createWorkOrder(); + + $response = $this->api('get', "/api/v1/work-orders/{$wo->id}/step-progress"); + + $response->assertStatus(200) + ->assertJsonStructure(['success', 'data']); + } + + // ==================== 자재 ==================== + + public function test_자재_목록_조회(): void + { + $wo = $this->createWorkOrder(); + + $response = $this->api('get', "/api/v1/work-orders/{$wo->id}/materials"); + + $response->assertStatus(200) + ->assertJsonStructure(['success', 'data']); + } + + public function test_자재_투입_이력_조회(): void + { + $wo = $this->createWorkOrder(); + + $response = $this->api('get', "/api/v1/work-orders/{$wo->id}/material-input-history"); + + $response->assertStatus(200) + ->assertJsonStructure(['success', 'data']); + } + + // ==================== 인증 ==================== + + public function test_미인증_요청시_401(): void + { + $response = $this->withHeaders([ + 'X-API-KEY' => $this->apiKey, + 'Accept' => 'application/json', + ])->getJson('/api/v1/work-orders'); + + $response->assertStatus(401); + } + + // ==================== 헬퍼 ==================== + + private function createWorkOrder(string $status = WorkOrder::STATUS_UNASSIGNED): WorkOrder + { + return WorkOrder::create([ + 'tenant_id' => $this->tenant->id, + 'work_order_no' => 'WO-'.uniqid(), + 'sales_order_id' => $this->order->id, + 'process_id' => $this->process->id, + 'project_name' => '테스트 프로젝트', + 'status' => $status, + 'priority' => 5, + 'scheduled_date' => now()->addDays(7), + 'memo' => '테스트 작업지시', + 'is_active' => true, + 'created_by' => $this->user->id, + 'updated_by' => $this->user->id, + ]); + } +}