- PrefixResolver: 제품코드×마감재질→LOT prefix 결정 + BD-XX-NN 코드 생성 - DynamicBomEntry DTO: dynamic_bom JSON 항목 타입 안전 관리 - BendingInfoBuilder 확장: build() 리턴 변경 + buildDynamicBomForItem() 추가 - OrderService: 작업지시 생성 시 per-item dynamic_bom 자동 저장 - WorkOrderService.getMaterials(): dynamic_bom 우선 체크 + N+1 배치 최적화 - WorkOrderService.registerMaterialInput(): work_order_item_id 분기 라우팅 통일 - 단위 테스트 58개 + 통합 테스트 6개 (64 tests / 293 assertions) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
174 lines
6.5 KiB
PHP
174 lines
6.5 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\Production;
|
|
|
|
use App\DTOs\Production\DynamicBomEntry;
|
|
use InvalidArgumentException;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
class DynamicBomEntryTest extends TestCase
|
|
{
|
|
private function validData(): array
|
|
{
|
|
return [
|
|
'child_item_id' => 15812,
|
|
'child_item_code' => 'BD-RS-43',
|
|
'lot_prefix' => 'RS',
|
|
'part_type' => '마감재',
|
|
'category' => 'guideRail',
|
|
'material_type' => 'SUS',
|
|
'length_mm' => 4300,
|
|
'qty' => 2,
|
|
];
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────
|
|
// fromArray + toArray 라운드트립
|
|
// ─────────────────────────────────────────────────
|
|
|
|
public function test_from_array_creates_dto(): void
|
|
{
|
|
$entry = DynamicBomEntry::fromArray($this->validData());
|
|
|
|
$this->assertEquals(15812, $entry->child_item_id);
|
|
$this->assertEquals('BD-RS-43', $entry->child_item_code);
|
|
$this->assertEquals('RS', $entry->lot_prefix);
|
|
$this->assertEquals('마감재', $entry->part_type);
|
|
$this->assertEquals('guideRail', $entry->category);
|
|
$this->assertEquals('SUS', $entry->material_type);
|
|
$this->assertEquals(4300, $entry->length_mm);
|
|
$this->assertEquals(2, $entry->qty);
|
|
}
|
|
|
|
public function test_to_array_round_trip(): void
|
|
{
|
|
$data = $this->validData();
|
|
$entry = DynamicBomEntry::fromArray($data);
|
|
$this->assertEquals($data, $entry->toArray());
|
|
}
|
|
|
|
public function test_to_array_list(): void
|
|
{
|
|
$entries = [
|
|
DynamicBomEntry::fromArray($this->validData()),
|
|
DynamicBomEntry::fromArray(array_merge($this->validData(), [
|
|
'child_item_id' => 15813,
|
|
'child_item_code' => 'BD-RM-43',
|
|
'lot_prefix' => 'RM',
|
|
'part_type' => '본체',
|
|
])),
|
|
];
|
|
|
|
$list = DynamicBomEntry::toArrayList($entries);
|
|
$this->assertCount(2, $list);
|
|
$this->assertEquals('BD-RS-43', $list[0]['child_item_code']);
|
|
$this->assertEquals('BD-RM-43', $list[1]['child_item_code']);
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────
|
|
// 유효한 카테고리
|
|
// ─────────────────────────────────────────────────
|
|
|
|
/**
|
|
* @dataProvider validCategoryProvider
|
|
*/
|
|
public function test_valid_categories(string $category): void
|
|
{
|
|
$data = array_merge($this->validData(), ['category' => $category]);
|
|
$entry = DynamicBomEntry::fromArray($data);
|
|
$this->assertEquals($category, $entry->category);
|
|
}
|
|
|
|
public static function validCategoryProvider(): array
|
|
{
|
|
return [
|
|
'guideRail' => ['guideRail'],
|
|
'bottomBar' => ['bottomBar'],
|
|
'shutterBox' => ['shutterBox'],
|
|
'smokeBarrier' => ['smokeBarrier'],
|
|
];
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────
|
|
// 필수 필드 누락 검증
|
|
// ─────────────────────────────────────────────────
|
|
|
|
/**
|
|
* @dataProvider requiredFieldProvider
|
|
*/
|
|
public function test_missing_required_field_throws(string $field): void
|
|
{
|
|
$data = $this->validData();
|
|
unset($data[$field]);
|
|
|
|
$this->expectException(InvalidArgumentException::class);
|
|
$this->expectExceptionMessage("'{$field}' is required");
|
|
DynamicBomEntry::fromArray($data);
|
|
}
|
|
|
|
public static function requiredFieldProvider(): array
|
|
{
|
|
return [
|
|
'child_item_id' => ['child_item_id'],
|
|
'child_item_code' => ['child_item_code'],
|
|
'lot_prefix' => ['lot_prefix'],
|
|
'part_type' => ['part_type'],
|
|
'category' => ['category'],
|
|
'material_type' => ['material_type'],
|
|
'length_mm' => ['length_mm'],
|
|
'qty' => ['qty'],
|
|
];
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────
|
|
// 값 제약 검증
|
|
// ─────────────────────────────────────────────────
|
|
|
|
public function test_invalid_child_item_id_throws(): void
|
|
{
|
|
$data = array_merge($this->validData(), ['child_item_id' => 0]);
|
|
|
|
$this->expectException(InvalidArgumentException::class);
|
|
$this->expectExceptionMessage('child_item_id must be positive');
|
|
DynamicBomEntry::fromArray($data);
|
|
}
|
|
|
|
public function test_invalid_category_throws(): void
|
|
{
|
|
$data = array_merge($this->validData(), ['category' => 'invalidCategory']);
|
|
|
|
$this->expectException(InvalidArgumentException::class);
|
|
$this->expectExceptionMessage('category must be one of');
|
|
DynamicBomEntry::fromArray($data);
|
|
}
|
|
|
|
public function test_zero_qty_throws(): void
|
|
{
|
|
$data = array_merge($this->validData(), ['qty' => 0]);
|
|
|
|
$this->expectException(InvalidArgumentException::class);
|
|
$this->expectExceptionMessage('qty must be positive');
|
|
DynamicBomEntry::fromArray($data);
|
|
}
|
|
|
|
public function test_negative_qty_throws(): void
|
|
{
|
|
$data = array_merge($this->validData(), ['qty' => -1]);
|
|
|
|
$this->expectException(InvalidArgumentException::class);
|
|
$this->expectExceptionMessage('qty must be positive');
|
|
DynamicBomEntry::fromArray($data);
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────
|
|
// float qty 허용
|
|
// ─────────────────────────────────────────────────
|
|
|
|
public function test_float_qty_allowed(): void
|
|
{
|
|
$data = array_merge($this->validData(), ['qty' => 1.5]);
|
|
$entry = DynamicBomEntry::fromArray($data);
|
|
$this->assertEquals(1.5, $entry->qty);
|
|
}
|
|
}
|