feat(WEB): 절곡 자재투입 LOT 매핑 파이프라인 구현
- 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>
This commit is contained in:
101
app/DTOs/Production/DynamicBomEntry.php
Normal file
101
app/DTOs/Production/DynamicBomEntry.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace App\DTOs\Production;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* dynamic_bom JSON 항목 DTO
|
||||
*
|
||||
* work_order_items.options.dynamic_bom 배열의 각 엔트리를 표현
|
||||
*/
|
||||
class DynamicBomEntry
|
||||
{
|
||||
public function __construct(
|
||||
public readonly int $child_item_id,
|
||||
public readonly string $child_item_code,
|
||||
public readonly string $lot_prefix,
|
||||
public readonly string $part_type,
|
||||
public readonly string $category,
|
||||
public readonly string $material_type,
|
||||
public readonly int $length_mm,
|
||||
public readonly int|float $qty,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 배열에서 DTO 생성
|
||||
*/
|
||||
public static function fromArray(array $data): self
|
||||
{
|
||||
self::validate($data);
|
||||
|
||||
return new self(
|
||||
child_item_id: (int) $data['child_item_id'],
|
||||
child_item_code: (string) $data['child_item_code'],
|
||||
lot_prefix: (string) $data['lot_prefix'],
|
||||
part_type: (string) $data['part_type'],
|
||||
category: (string) $data['category'],
|
||||
material_type: (string) $data['material_type'],
|
||||
length_mm: (int) $data['length_mm'],
|
||||
qty: $data['qty'],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* DTO → 배열 변환 (JSON 저장용)
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'child_item_id' => $this->child_item_id,
|
||||
'child_item_code' => $this->child_item_code,
|
||||
'lot_prefix' => $this->lot_prefix,
|
||||
'part_type' => $this->part_type,
|
||||
'category' => $this->category,
|
||||
'material_type' => $this->material_type,
|
||||
'length_mm' => $this->length_mm,
|
||||
'qty' => $this->qty,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 필수 필드 검증
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function validate(array $data): bool
|
||||
{
|
||||
$required = ['child_item_id', 'child_item_code', 'lot_prefix', 'part_type', 'category', 'material_type', 'length_mm', 'qty'];
|
||||
|
||||
foreach ($required as $field) {
|
||||
if (! array_key_exists($field, $data) || $data[$field] === null) {
|
||||
throw new InvalidArgumentException("DynamicBomEntry: '{$field}' is required");
|
||||
}
|
||||
}
|
||||
|
||||
if ((int) $data['child_item_id'] <= 0) {
|
||||
throw new InvalidArgumentException('DynamicBomEntry: child_item_id must be positive');
|
||||
}
|
||||
|
||||
$validCategories = ['guideRail', 'bottomBar', 'shutterBox', 'smokeBarrier'];
|
||||
if (! in_array($data['category'], $validCategories, true)) {
|
||||
throw new InvalidArgumentException('DynamicBomEntry: category must be one of: '.implode(', ', $validCategories));
|
||||
}
|
||||
|
||||
if ($data['qty'] <= 0) {
|
||||
throw new InvalidArgumentException('DynamicBomEntry: qty must be positive');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* DynamicBomEntry 배열 → JSON 저장용 배열 변환
|
||||
*
|
||||
* @param DynamicBomEntry[] $entries
|
||||
*/
|
||||
public static function toArrayList(array $entries): array
|
||||
{
|
||||
return array_map(fn (self $e) => $e->toArray(), $entries);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user