fix: [QMS] 인정품목 표시 수정 + 제품검사 성적서 필터 개선

- getFgProductName(): BOM 순회 대신 Order.item_id 직접 참조로 변경
- 제품검사 성적서 필터: document_id만 → document_id || inspection_status=completed
- getLocationDetail(): FQC 문서 데이터 포함 (template + data)
- formatFqcTemplate(): DB item → item_name 매핑 추가
- formatDocumentItem('product'): 개소별 층/기호 코드 표시

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 09:48:22 +09:00
parent 073ad11ecd
commit 3a889b33ef

View File

@@ -23,8 +23,7 @@ class QmsLotAuditService extends Service
public function index(array $params): array
{
$query = QualityDocument::with([
'documentOrders.order.nodes' => fn ($q) => $q->whereNull('parent_id'),
'documentOrders.order.nodes.items.item',
'documentOrders.order.item',
'locations',
'performanceReport',
])
@@ -89,7 +88,7 @@ public function routeDocuments(int $qualityDocumentOrderId): array
{
$docOrder = QualityDocumentOrder::with([
'order.workOrders.process',
'locations',
'locations.orderItem',
'qualityDocument',
])->findOrFail($qualityDocumentOrderId);
@@ -119,17 +118,20 @@ public function routeDocuments(int $qualityDocumentOrderId): array
// 2. 수주서
$documents[] = $this->formatDocument('order', '수주서', collect([$order]));
// 3. 작업일지 (subType: process.process_name 기반)
$documents[] = $this->formatDocumentWithSubType('log', '작업일지', $workOrders);
// 3. 작업일지 (공정별 1개씩 — 같은 공정의 WO는 그룹핑)
$workOrdersByProcess = $workOrders->groupBy('process_id')->map(fn ($group) => $group->first());
$documents[] = $this->formatDocumentWithSubType('log', '작업일지', $workOrdersByProcess);
// 4. 중간검사 성적서 (PQC)
// 4. 중간검사 성적서 (PQC — 공정별 1개씩)
$pqcInspections = Inspection::where('inspection_type', 'PQC')
->whereIn('work_order_id', $workOrders->pluck('id'))
->where('status', 'completed')
->with('workOrder.process')
->get();
$documents[] = $this->formatDocumentWithSubType('report', '중간검사 성적서', $pqcInspections, 'workOrder');
// 공정별 그룹핑 (같은 공정의 PQC는 최신 1개만)
$pqcByProcess = $pqcInspections->groupBy(fn ($insp) => $insp->workOrder?->process_id)
->map(fn ($group) => $group->sortByDesc('inspection_date')->first());
$documents[] = $this->formatDocumentWithSubType('report', '중간검사 성적서', $pqcByProcess, 'workOrder');
// 5. 납품확인서
$shipments = $order->shipments()->get();
@@ -138,9 +140,11 @@ public function routeDocuments(int $qualityDocumentOrderId): array
// 6. 출고증
$documents[] = $this->formatDocument('shipping', '출고증', $shipments);
// 7. 제품검사 성적서
$locationsWithDoc = $docOrder->locations->filter(fn ($loc) => $loc->document_id);
$documents[] = $this->formatDocument('product', '제품검사 성적서', $locationsWithDoc);
// 7. 제품검사 성적서 (FQC 문서 또는 inspection_data 완료건)
$locationsWithInspection = $docOrder->locations->filter(
fn ($loc) => $loc->document_id || $loc->inspection_status === 'completed'
);
$documents[] = $this->formatDocument('product', '제품검사 성적서', $locationsWithInspection);
// 8. 품질관리서
$documents[] = $this->formatDocument('quality', '품질관리서', collect([$qualityDoc]));
@@ -220,30 +224,14 @@ private function transformReportToFrontend(QualityDocument $doc): array
}
/**
* BOM 최상위(FG) 제품명 추출
* Order → root OrderNode(parent_id=null) → 대표 OrderItem → Item(FG).name
* 수주 대표 제품명 추출
* Order.item_id → Item.name
*/
private function getFgProductName(QualityDocument $doc): string
{
$firstDocOrder = $doc->documentOrders->first();
if (! $firstDocOrder) {
return '';
}
$order = $doc->documentOrders->first()?->order;
$order = $firstDocOrder->order;
if (! $order) {
return '';
}
// eager loaded with whereNull('parent_id') filter
$rootNode = $order->nodes->first();
if (! $rootNode) {
return '';
}
$representativeItem = $rootNode->items->first();
return $representativeItem?->item?->name ?? '';
return $order?->item?->name ?? '';
}
private function transformRouteToFrontend(QualityDocumentOrder $docOrder, QualityDocument $qualityDoc): array
@@ -313,11 +301,12 @@ private function formatDocumentWithSubType(string $type, string $title, $collect
'items' => $collection->values()->map(function ($item) use ($type, $workOrderRelation) {
$formatted = $this->formatDocumentItem($type, $item);
// subType: process.process_name 기반
// subType: process.process_name 기반 + work_order_id 전달
$workOrder = $workOrderRelation ? $item->{$workOrderRelation} : $item;
if ($workOrder instanceof WorkOrder) {
$processName = $workOrder->process?->process_name;
$formatted['sub_type'] = $this->mapProcessToSubType($processName);
$formatted['work_order_id'] = $workOrder->id;
}
return $formatted;
@@ -354,7 +343,7 @@ private function formatDocumentItem(string $type, $item): array
],
'product' => [
'id' => (string) $item->id,
'title' => '제품검사 성적서',
'title' => trim(($item->orderItem?->floor_code ?? '').' '.($item->orderItem?->symbol_code ?? '')) ?: '제품검사 성적서',
'date' => $item->updated_at?->toDateString() ?? '',
'code' => '',
],
@@ -479,9 +468,16 @@ private function getShipmentDetail(int $id): array
private function getLocationDetail(int $id): array
{
$location = QualityDocumentLocation::with(['orderItem', 'document'])->findOrFail($id);
$location = QualityDocumentLocation::with([
'orderItem',
'document.template.sections.items',
'document.template.columns',
'document.template.approvalLines',
'document.template.basicFields',
'document.data',
])->findOrFail($id);
return [
$result = [
'type' => 'product',
'data' => [
'id' => $location->id,
@@ -494,6 +490,86 @@ private function getLocationDetail(int $id): array
'document_id' => $location->document_id,
],
];
// FQC 문서가 있으면 template + data 포함
if ($location->document) {
$doc = $location->document;
$result['data']['fqc_document'] = [
'id' => $doc->id,
'template_id' => $doc->template_id,
'document_no' => $doc->document_no,
'title' => $doc->title,
'status' => $doc->status,
'created_at' => $doc->created_at?->toIso8601String(),
'template' => $this->formatFqcTemplate($doc->template),
'data' => $doc->data->map(fn ($d) => [
'section_id' => $d->section_id,
'column_id' => $d->column_id,
'row_index' => $d->row_index,
'field_key' => $d->field_key,
'field_value' => $d->field_value,
])->all(),
];
}
return $result;
}
private function formatFqcTemplate($template): ?array
{
if (! $template) {
return null;
}
return [
'id' => $template->id,
'name' => $template->name,
'category' => $template->category,
'title' => $template->title,
'approval_lines' => $template->approvalLines->map(fn ($a) => [
'id' => $a->id,
'name' => $a->name,
'department' => $a->department,
'sort_order' => $a->sort_order,
])->all(),
'basic_fields' => $template->basicFields->map(fn ($f) => [
'id' => $f->id,
'label' => $f->label,
'field_key' => $f->field_key,
'field_type' => $f->field_type,
'default_value' => $f->default_value,
'is_required' => $f->is_required,
'sort_order' => $f->sort_order,
])->all(),
'sections' => $template->sections->map(fn ($s) => [
'id' => $s->id,
'name' => $s->name,
'title' => $s->title,
'description' => $s->description,
'image_path' => $s->image_path,
'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,
'label' => $c->label,
'column_type' => $c->column_type,
'width' => $c->width,
'group_name' => $c->group_name,
'sort_order' => $c->sort_order,
])->all(),
];
}
private function getQualityDocDetail(int $id): array