feat: [품질검사] 수주 선택 필터링 + 개소 상세 + 검사 상태 개선

- availableOrders: client_id/item_id 필터 파라미터 지원
- availableOrders: 응답에 client_id, client_name, item_id, item_name, locations(개소 상세) 추가
- show: 개소별 데이터에 거래처/모델 정보 포함
- DocumentService: fqcStatus rootNodes 기반으로 변경

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-07 01:19:04 +09:00
parent 2231c9a48f
commit 3ac64d5b76
3 changed files with 73 additions and 21 deletions

View File

@@ -1,6 +1,6 @@
# 논리적 데이터베이스 관계 문서
> **자동 생성**: 2026-03-06 15:12:45
> **자동 생성**: 2026-03-06 21:25:05
> **소스**: Eloquent 모델 관계 분석
## 📊 모델별 관계 현황

View File

@@ -663,20 +663,32 @@ public function fqcStatus(int $orderId, int $templateId): array
$tenantId = $this->tenantId();
$order = \App\Models\Orders\Order::where('tenant_id', $tenantId)
->with('items')
->with(['rootNodes.items' => fn ($q) => $q->orderBy('sort_order')])
->findOrFail($orderId);
// 해당 수주의 FQC 문서 조회
// 개소별 대표 OrderItem ID 수집
$representativeItemIds = $order->rootNodes
->map(fn ($node) => $node->items->first()?->id)
->filter()
->values();
// 해당 대표 품목의 FQC 문서 조회
$documents = Document::where('tenant_id', $tenantId)
->where('template_id', $templateId)
->where('linkable_type', \App\Models\Orders\OrderItem::class)
->whereIn('linkable_id', $order->items->pluck('id'))
->whereIn('linkable_id', $representativeItemIds)
->with('data')
->get()
->keyBy('linkable_id');
$items = $order->items->map(function ($orderItem) use ($documents) {
$doc = $documents->get($orderItem->id);
// 개소(root node)별 진행현황
$items = $order->rootNodes->map(function ($node) use ($documents) {
$representativeItem = $node->items->first();
if (! $representativeItem) {
return null;
}
$doc = $documents->get($representativeItem->id);
// 종합판정 값 추출
$judgement = null;
@@ -686,17 +698,17 @@ public function fqcStatus(int $orderId, int $templateId): array
}
return [
'order_item_id' => $orderItem->id,
'floor_code' => $orderItem->floor_code,
'symbol_code' => $orderItem->symbol_code,
'specification' => $orderItem->specification,
'item_name' => $orderItem->item_name,
'order_item_id' => $representativeItem->id,
'floor_code' => $representativeItem->floor_code,
'symbol_code' => $representativeItem->symbol_code,
'specification' => $representativeItem->specification,
'item_name' => $representativeItem->item_name,
'document_id' => $doc?->id,
'document_no' => $doc?->document_no,
'status' => $doc?->status ?? 'NONE',
'judgement' => $judgement,
];
});
})->filter()->values();
// 통계
$total = $items->count();
@@ -889,6 +901,7 @@ public function upsert(array $data): Document
if (isset($data['rendered_html'])) {
$updatePayload['rendered_html'] = $data['rendered_html'];
}
return $this->update($existingDocument->id, $updatePayload);
}
@@ -904,6 +917,7 @@ public function upsert(array $data): Document
if (isset($data['rendered_html'])) {
$createPayload['rendered_html'] = $data['rendered_html'];
}
return $this->create($createPayload);
});
}

View File

@@ -2,6 +2,9 @@
namespace App\Services;
use App\Models\Documents\Document;
use App\Models\Documents\DocumentData;
use App\Models\Documents\DocumentTemplate;
use App\Models\Orders\Order;
use App\Models\Orders\OrderItem;
use App\Models\Orders\OrderNode;
@@ -9,9 +12,6 @@
use App\Models\Qualitys\QualityDocument;
use App\Models\Qualitys\QualityDocumentLocation;
use App\Models\Qualitys\QualityDocumentOrder;
use App\Models\Documents\Document;
use App\Models\Documents\DocumentData;
use App\Models\Documents\DocumentTemplate;
use App\Services\Audit\AuditLogger;
use Illuminate\Support\Facades\DB;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
@@ -377,6 +377,8 @@ public function availableOrders(array $params): array
{
$tenantId = $this->tenantId();
$q = trim((string) ($params['q'] ?? ''));
$clientId = $params['client_id'] ?? null;
$itemId = $params['item_id'] ?? null;
// 이미 연결된 수주 ID 목록
$linkedOrderIds = QualityDocumentOrder::whereHas('qualityDocument', function ($query) use ($tenantId) {
@@ -385,6 +387,12 @@ public function availableOrders(array $params): array
$query = Order::where('tenant_id', $tenantId)
->whereNotIn('id', $linkedOrderIds)
->with(['item:id,name', 'nodes' => function ($q) {
$q->whereNull('parent_id')->orderBy('sort_order')
->with(['items' => function ($q2) {
$q2->orderBy('sort_order')->limit(1);
}]);
}])
->withCount(['nodes as location_count' => function ($q) {
$q->whereNull('parent_id');
}]);
@@ -396,6 +404,16 @@ public function availableOrders(array $params): array
});
}
// 같은 거래처(발주처) 필터
if ($clientId) {
$query->where('client_id', $clientId);
}
// 같은 모델(품목) 필터
if ($itemId) {
$query->where('item_id', $itemId);
}
return $query->orderByDesc('id')
->limit(50)
->get()
@@ -403,9 +421,24 @@ public function availableOrders(array $params): array
'id' => $order->id,
'order_number' => $order->order_no,
'site_name' => $order->site_name ?? '',
'client_id' => $order->client_id,
'client_name' => $order->client_name ?? '',
'item_id' => $order->item_id,
'item_name' => $order->item?->name ?? '',
'delivery_date' => $order->delivery_date?->format('Y-m-d') ?? '',
'location_count' => $order->location_count,
'locations' => $order->nodes->where('parent_id', null)->map(function ($node) {
$item = $node->items->first();
$options = $node->options ?? [];
return [
'node_id' => $node->id,
'floor' => $item?->floor_code ?? $node->code ?? '',
'symbol' => $item?->symbol_code ?? '',
'order_width' => $options['width'] ?? 0,
'order_height' => $options['height'] ?? 0,
];
})->values()->toArray(),
])
->toArray();
}
@@ -701,6 +734,10 @@ private function transformToFrontend(QualityDocument $doc, bool $detail = false)
'order_id' => $order?->id,
'order_number' => $order?->order_no ?? '',
'site_name' => $order?->site_name ?? '',
'client_id' => $order?->client_id,
'client_name' => $order?->client_name ?? '',
'item_id' => $order?->item_id,
'item_name' => $order?->item?->name ?? '',
'delivery_date' => $order?->delivery_date ? $order->delivery_date->format('Y-m-d') : '',
'floor' => $orderItem?->floor_code ?? '',
'symbol' => $orderItem?->symbol_code ?? '',
@@ -710,6 +747,7 @@ private function transformToFrontend(QualityDocument $doc, bool $detail = false)
'construction_height' => $loc->post_height ?? 0,
'change_reason' => $loc->change_reason ?? '',
'inspection_data' => $loc->inspection_data,
'document_id' => $loc->document_id,
];
})->toArray();
}
@@ -729,10 +767,6 @@ public function inspectLocation(int $docId, int $locId, array $data)
throw new NotFoundHttpException(__('error.not_found'));
}
if ($doc->isCompleted()) {
throw new BadRequestHttpException(__('error.quality.cannot_modify_completed'));
}
$location = QualityDocumentLocation::where('quality_document_id', $docId)->find($locId);
if (! $location) {
throw new NotFoundHttpException(__('error.not_found'));
@@ -753,6 +787,9 @@ public function inspectLocation(int $docId, int $locId, array $data)
if (isset($data['inspection_status'])) {
$updateData['inspection_status'] = $data['inspection_status'];
}
if (array_key_exists('inspection_data', $data)) {
$updateData['inspection_data'] = $data['inspection_data'];
}
if (! empty($updateData)) {
$location->update($updateData);
@@ -931,7 +968,7 @@ private function syncRequestDocument(QualityDocument $doc): void
'tenant_id' => $tenantId,
'template_id' => self::REQUEST_TEMPLATE_ID,
'document_no' => $documentNo,
'title' => '제품검사 요청서 - ' . ($doc->site_name ?? $doc->quality_doc_number),
'title' => '제품검사 요청서 - '.($doc->site_name ?? $doc->quality_doc_number),
'status' => Document::STATUS_DRAFT,
'linkable_type' => QualityDocument::class,
'linkable_id' => $doc->id,
@@ -1029,6 +1066,7 @@ private function syncRequestDocument(QualityDocument $doc): void
DocumentData::insert(array_map(function ($d) {
$d['created_at'] = now();
$d['updated_at'] = now();
return $d;
}, $eavData));
}
@@ -1052,7 +1090,7 @@ private function buildBasicFieldMapping(QualityDocument $doc, array $options): a
'manager_contact' => $manager['phone'] ?? '',
'site_name' => $doc->site_name ?? '',
'delivery_date' => $order?->delivery_date?->format('Y-m-d') ?? '',
'site_address' => trim(($siteAddress['address'] ?? '') . ' ' . ($siteAddress['detail'] ?? '')),
'site_address' => trim(($siteAddress['address'] ?? '').' '.($siteAddress['detail'] ?? '')),
'total_locations' => (string) ($doc->locations?->count() ?? 0),
'receipt_date' => $doc->received_date?->format('Y-m-d') ?? '',
'inspection_request_date' => $inspection['request_date'] ?? '',