feat: [수주서] 절곡품 이미지 연동 — bending_items files 기반

- groupBendingParts에서 item_code prefix → bending_items 이미지 매칭
- getBendingItemImages: BD prefix 기반 files 테이블 사전 조회
- 각 bending_parts item에 image_url 포함하여 응답
This commit is contained in:
김보곤
2026-03-22 15:21:01 +09:00
parent a8b04e15c3
commit d4b10452f5

View File

@@ -573,6 +573,8 @@ private function getOrderDetail(int $id): array
*/
private function groupBendingParts($steelItems, int $shutterCount = 0): array
{
$tenantId = $this->tenantId();
$groups = [
'가이드레일' => [],
'케이스' => [],
@@ -581,6 +583,10 @@ private function groupBendingParts($steelItems, int $shutterCount = 0): array
'기타' => [],
];
// item_code별 bending_item 이미지 사전 조회
$itemCodes = $steelItems->pluck('item_code')->filter()->unique()->values()->toArray();
$imageMap = $this->getBendingItemImages($itemCodes, $tenantId);
foreach ($steelItems->groupBy('item_name') as $name => $group) {
$item = $group->first();
$totalQty = $group->sum('quantity');
@@ -591,16 +597,31 @@ private function groupBendingParts($steelItems, int $shutterCount = 0): array
$isCase = str_contains($name, '케이스') && ! $isMaguri;
if ($shutterCount > 0 && ($isGuideRail || $isCase || $isMaguri)) {
// 가이드레일, 마구리: 2 × 셔터수량 / 케이스: 셔터수량
$qty = ($isGuideRail || $isMaguri) ? 2 * $shutterCount : $shutterCount;
} else {
$qty = $totalQty;
}
// 이미지 URL: item_code(BD-XX-NN) 기반 → bending_item prefix 매칭
$itemCode = $item['item_code'] ?? null;
$imageUrl = $itemCode ? ($imageMap[$itemCode] ?? null) : null;
// item_code로 직접 매칭 안 되면 prefix(BD-XX)로 매칭 시도
if (! $imageUrl && $itemCode && preg_match('/^(BD-[A-Z]{2})/', $itemCode, $m)) {
$prefix = $m[1];
foreach ($imageMap as $code => $url) {
if (str_starts_with($code, $prefix)) {
$imageUrl = $url;
break;
}
}
}
$row = [
'name' => $item['item_name'],
'spec' => $item['specification'] ?? '-',
'qty' => (int) $qty,
'image_url' => $imageUrl,
];
if (str_contains($name, '연기차단재')) {
@@ -629,6 +650,70 @@ private function groupBendingParts($steelItems, int $shutterCount = 0): array
return $result;
}
/**
* bending_items의 이미지 URL 사전 조회
*
* item_code(BD-XX-NN) → bending_items.code(BD-XX.NNN) prefix 매칭 → files 테이블에서 이미지 조회
*
* @return array<string, string> item_code → image_url 매핑
*/
private function getBendingItemImages(array $itemCodes, int $tenantId): array
{
if (empty($itemCodes)) {
return [];
}
// item_code에서 prefix 추출 (BD-RM-24 → BD-RM)
$prefixes = [];
foreach ($itemCodes as $code) {
if (preg_match('/^(BD-[A-Z]{2})/', $code, $m)) {
$prefixes[$m[1]] = true;
}
}
if (empty($prefixes)) {
return [];
}
// bending_items에서 해당 prefix 매칭 + files 조인
$bendingItems = \App\Models\BendingItem::where('tenant_id', $tenantId)
->where(function ($q) use ($prefixes) {
foreach (array_keys($prefixes) as $prefix) {
$q->orWhere('code', 'LIKE', "{$prefix}%");
}
})
->whereHas('files')
->with(['files' => fn ($q) => $q->orderBy('id', 'desc')->limit(1)])
->get();
// prefix → file_id 매핑 (첫 번째 매칭만)
$prefixImageMap = [];
foreach ($bendingItems as $bi) {
$file = $bi->files->first();
if (! $file) {
continue;
}
// BD-RM.001 → BD-RM prefix 추출
$biPrefix = substr($bi->code, 0, 5); // "BD-RM"
if (! isset($prefixImageMap[$biPrefix])) {
$prefixImageMap[$biPrefix] = url("/api/v1/files/{$file->id}/view");
}
}
// item_code → image_url 매핑
$result = [];
foreach ($itemCodes as $code) {
if (preg_match('/^(BD-[A-Z]{2})/', $code, $m)) {
$prefix = $m[1];
if (isset($prefixImageMap[$prefix])) {
$result[$code] = $prefixImageMap[$prefix];
}
}
}
return $result;
}
private function getWorkOrderLogDetail(int $id): array
{
$workOrder = WorkOrder::with('process')->findOrFail($id);
@@ -797,27 +882,27 @@ private function formatFqcTemplate($template): ?array
}
return [
'id' => $s->id,
'name' => $s->name,
'title' => $s->title,
'description' => $s->description,
'image_path' => $s->image_path,
'file_id' => $s->file_id,
'image_url' => $imageUrl,
'sort_order' => $s->sort_order,
'items' => $s->items->map(fn ($i) => [
'id' => $i->id,
'section_id' => $i->section_id,
'item_name' => $i->item ?? '',
'standard' => $i->standard,
'tolerance' => $i->tolerance,
'measurement_type' => $i->measurement_type,
'frequency' => $i->frequency,
'sort_order' => $i->sort_order,
'category' => $i->category,
'method' => $i->method,
])->all(),
];
'id' => $s->id,
'name' => $s->name,
'title' => $s->title,
'description' => $s->description,
'image_path' => $s->image_path,
'file_id' => $s->file_id,
'image_url' => $imageUrl,
'sort_order' => $s->sort_order,
'items' => $s->items->map(fn ($i) => [
'id' => $i->id,
'section_id' => $i->section_id,
'item_name' => $i->item ?? '',
'standard' => $i->standard,
'tolerance' => $i->tolerance,
'measurement_type' => $i->measurement_type,
'frequency' => $i->frequency,
'sort_order' => $i->sort_order,
'category' => $i->category,
'method' => $i->method,
])->all(),
];
})->all(),
'columns' => $template->columns->map(fn ($c) => [
'id' => $c->id,