Files
sam-api/app/DTOs/Production/DynamicBomEntry.php
권혁성 5a3d6c2243 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>
2026-02-22 04:19:47 +09:00

102 lines
3.1 KiB
PHP

<?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);
}
}