diff --git a/app/Services/QmsLotAuditService.php b/app/Services/QmsLotAuditService.php index 1f08ca25..282b0cd1 100644 --- a/app/Services/QmsLotAuditService.php +++ b/app/Services/QmsLotAuditService.php @@ -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 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,