feat: Items API BOM 데이터 확장 기능 추가

- GET /items/{id} 응답에 BOM 확장 데이터 포함
  - child_item_code, child_item_name, unit, specification 필드 추가
- expandBomData() 메서드 구현 (ItemsService)
- Product 모델 bom 캐스팅 추가
This commit is contained in:
2025-12-11 23:17:52 +09:00
parent cc3feb1927
commit 84ff9d7fd8
2 changed files with 157 additions and 1 deletions

View File

@@ -136,6 +136,147 @@ private function processDynamicOptions(array &$data, string $sourceTable): void
}
}
/**
* BOM 데이터에서 child_item_id, child_item_type, quantity만 추출
*
* @param array|null $bomData BOM 데이터 배열
* @return array|null [{child_item_id, child_item_type, quantity}, ...]
*/
private function extractBomData(?array $bomData): ?array
{
if (empty($bomData)) {
return null;
}
$extracted = [];
foreach ($bomData as $item) {
if (! is_array($item)) {
continue;
}
$childItemId = $item['child_item_id'] ?? null;
$childItemType = $item['child_item_type'] ?? $item['ref_type'] ?? 'PRODUCT';
$quantity = $item['quantity'] ?? null;
if ($childItemId === null) {
continue;
}
// child_item_type 정규화 (PRODUCT/MATERIAL)
$childItemType = strtoupper($childItemType);
if (! in_array($childItemType, ['PRODUCT', 'MATERIAL'])) {
$childItemType = 'PRODUCT';
}
$extracted[] = [
'child_item_id' => (int) $childItemId,
'child_item_type' => $childItemType,
'quantity' => $quantity !== null ? (float) $quantity : 1,
];
}
return empty($extracted) ? null : $extracted;
}
/**
* BOM 데이터 확장 (child_item 상세 정보 포함)
*
* DB에 저장된 [{child_item_id, child_item_type, quantity}] 형태를
* [{child_item_id, child_item_type, child_item_code, child_item_name, quantity, unit, specification?}]
* 형태로 확장하여 반환
*
* @param array $bomData BOM 데이터 배열 [{child_item_id, child_item_type, quantity}, ...]
* @param int $tenantId 테넌트 ID
* @return array 확장된 BOM 데이터
*/
private function expandBomData(array $bomData, int $tenantId): array
{
if (empty($bomData)) {
return [];
}
// child_item_type별로 ID 분리
$productIds = [];
$materialIds = [];
foreach ($bomData as $item) {
$childId = $item['child_item_id'] ?? null;
$childType = strtoupper($item['child_item_type'] ?? 'PRODUCT');
if ($childId === null) {
continue;
}
if ($childType === 'MATERIAL') {
$materialIds[] = $childId;
} else {
$productIds[] = $childId;
}
}
// Products에서 조회 (FG, PT)
$products = collect([]);
if (! empty($productIds)) {
$products = Product::query()
->where('tenant_id', $tenantId)
->whereIn('id', $productIds)
->get(['id', 'code', 'name', 'unit'])
->keyBy('id');
}
// Materials에서 조회 (SM, RM, CS)
$materials = collect([]);
if (! empty($materialIds)) {
$materials = Material::query()
->where('tenant_id', $tenantId)
->whereIn('id', $materialIds)
->get(['id', 'material_code', 'name', 'unit', 'specification'])
->keyBy('id');
}
// BOM 데이터 확장
$expanded = [];
foreach ($bomData as $item) {
$childId = $item['child_item_id'] ?? null;
$childType = strtoupper($item['child_item_type'] ?? 'PRODUCT');
if ($childId === null) {
continue;
}
$result = [
'child_item_id' => (int) $childId,
'child_item_type' => $childType,
'quantity' => $item['quantity'] ?? 1,
];
// child_item_type에 따라 조회
if ($childType === 'MATERIAL' && isset($materials[$childId])) {
$material = $materials[$childId];
$result['child_item_code'] = $material->material_code;
$result['child_item_name'] = $material->name;
$result['unit'] = $material->unit;
if ($material->specification) {
$result['specification'] = $material->specification;
}
} elseif ($childType === 'PRODUCT' && isset($products[$childId])) {
$product = $products[$childId];
$result['child_item_code'] = $product->code;
$result['child_item_name'] = $product->name;
$result['unit'] = $product->unit;
} else {
// 해당하는 품목이 없으면 null 처리
$result['child_item_code'] = null;
$result['child_item_name'] = null;
$result['unit'] = null;
}
$expanded[] = $result;
}
return $expanded;
}
/**
* 통합 품목 조회 (materials + products UNION)
*
@@ -360,6 +501,11 @@ public function getItem(
$data['item_type'] = $itemType;
$data['type_code'] = $product->product_type;
// BOM 데이터 확장 (child_item 상세 정보 포함)
if (! empty($data['bom'])) {
$data['bom'] = $this->expandBomData($data['bom'], $tenantId);
}
// 가격 정보 추가
if ($includePrice) {
$data['prices'] = $this->fetchPrices('PRODUCT', $id, $clientId, $priceDate);
@@ -463,6 +609,9 @@ private function createProduct(array $data, int $tenantId, int $userId): Product
// 동적 필드를 options에 병합
$this->processDynamicOptions($data, 'products');
// BOM 데이터 처리 (child_item_id, quantity만 추출)
$bomData = $this->extractBomData($data['bom'] ?? null);
$payload = $data;
$payload['tenant_id'] = $tenantId;
$payload['created_by'] = $userId;
@@ -470,6 +619,7 @@ private function createProduct(array $data, int $tenantId, int $userId): Product
$payload['is_sellable'] = $payload['is_sellable'] ?? true;
$payload['is_purchasable'] = $payload['is_purchasable'] ?? false;
$payload['is_producible'] = $payload['is_producible'] ?? false;
$payload['bom'] = $bomData;
return Product::create($payload);
}
@@ -564,6 +714,11 @@ private function updateProduct(int $id, array $data): Product
// 동적 필드를 options에 병합
$this->processDynamicOptions($data, 'products');
// BOM 데이터 처리 (bom 키가 있을 때만)
if (array_key_exists('bom', $data)) {
$data['bom'] = $this->extractBomData($data['bom']);
}
// item_type은 DB 필드가 아니므로 제거
unset($data['item_type']);
$data['updated_by'] = $userId;