feat: [qms] 수주로트 감사 상세 정보 확장
- 수주 상세: 개소별 제품, 모터, 절곡물, 부자재 정보 추가 - 출하 상세: 배차정보, 제품 그룹별 품목 분류 추가 - 확인 로직: documentOrders 기준 수주로트 카운트로 변경 - locations relation 경로 수정 (documentOrders.locations) - 품질관리서 파일 정보 routeDocuments에 포함 - Shipment Client 모델 네임스페이스 수정 - DocumentService data relation null 처리 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -147,7 +147,7 @@ public function vehicleDispatches(): HasMany
|
||||
*/
|
||||
public function client(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Clients\Client::class);
|
||||
return $this->belongsTo(\App\Models\Orders\Client::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1034,7 +1034,7 @@ private function formatDocumentForReact(Document $document): array
|
||||
'submitted_at' => $document->submitted_at?->toIso8601String(),
|
||||
'completed_at' => $document->completed_at?->toIso8601String(),
|
||||
'created_at' => $document->created_at?->toIso8601String(),
|
||||
'data' => $document->data->map(fn ($d) => [
|
||||
'data' => ($document->getRelation('data') ?? collect())->map(fn ($d) => [
|
||||
'section_id' => $d->section_id,
|
||||
'column_id' => $d->column_id,
|
||||
'row_index' => $d->row_index,
|
||||
|
||||
@@ -24,7 +24,7 @@ public function index(array $params): array
|
||||
{
|
||||
$query = QualityDocument::with([
|
||||
'documentOrders.order.item',
|
||||
'locations',
|
||||
'documentOrders.locations',
|
||||
'performanceReport',
|
||||
])
|
||||
->where('status', QualityDocument::STATUS_COMPLETED);
|
||||
@@ -142,8 +142,18 @@ public function routeDocuments(int $qualityDocumentOrderId): array
|
||||
);
|
||||
$documents[] = $this->formatDocument('product', '제품검사 성적서', $locationsWithInspection);
|
||||
|
||||
// 8. 품질관리서
|
||||
$documents[] = $this->formatDocument('quality', '품질관리서', collect([$qualityDoc]));
|
||||
// 8. 품질관리서 (파일 정보 포함)
|
||||
$qualityDoc->loadMissing('file');
|
||||
$qualityDocFormatted = $this->formatDocument('quality', '품질관리서', collect([$qualityDoc]));
|
||||
|
||||
// 파일 정보 추가
|
||||
if ($qualityDoc->file) {
|
||||
$qualityDocFormatted['file_id'] = $qualityDoc->file->id;
|
||||
$qualityDocFormatted['file_name'] = $qualityDoc->file->display_name ?? $qualityDoc->file->original_name;
|
||||
$qualityDocFormatted['file_size'] = $qualityDoc->file->file_size;
|
||||
}
|
||||
|
||||
$documents[] = $qualityDocFormatted;
|
||||
|
||||
return $documents;
|
||||
}
|
||||
@@ -200,8 +210,18 @@ public function confirm(int $locationId, array $data): array
|
||||
private function transformReportToFrontend(QualityDocument $doc): array
|
||||
{
|
||||
$performanceReport = $doc->performanceReport;
|
||||
$confirmedCount = $doc->locations->filter(function ($loc) {
|
||||
return data_get($loc->options, 'lot_audit_confirmed', false);
|
||||
|
||||
// 수주로트 건수 = documentOrders 수
|
||||
$totalRoutes = $doc->documentOrders->count();
|
||||
|
||||
// 확인 완료 수주로트 = 해당 주문의 모든 개소가 확인된 건수
|
||||
$confirmedRoutes = $doc->documentOrders->filter(function ($docOrder) {
|
||||
$locations = $docOrder->locations;
|
||||
if ($locations->isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $locations->every(fn ($loc) => data_get($loc->options, 'lot_audit_confirmed', false));
|
||||
})->count();
|
||||
|
||||
return [
|
||||
@@ -209,8 +229,8 @@ private function transformReportToFrontend(QualityDocument $doc): array
|
||||
'code' => $doc->quality_doc_number,
|
||||
'site_name' => $doc->site_name,
|
||||
'item' => $this->getFgProductName($doc),
|
||||
'route_count' => $confirmedCount,
|
||||
'total_routes' => $doc->locations->count(),
|
||||
'route_count' => $confirmedRoutes,
|
||||
'total_routes' => $totalRoutes,
|
||||
'quarter' => $performanceReport
|
||||
? $performanceReport->year.'년 '.$performanceReport->quarter.'분기'
|
||||
: '',
|
||||
@@ -415,21 +435,175 @@ private function getInspectionDetail(int $id, string $type): array
|
||||
|
||||
private function getOrderDetail(int $id): array
|
||||
{
|
||||
$order = Order::with(['nodes' => fn ($q) => $q->whereNull('parent_id')])->findOrFail($id);
|
||||
$order = Order::with([
|
||||
'client',
|
||||
'nodes' => fn ($q) => $q->whereNull('parent_id')->orderBy('id'),
|
||||
])->findOrFail($id);
|
||||
|
||||
$rootNodes = $order->nodes;
|
||||
$options = $order->options ?? [];
|
||||
|
||||
// 개소별 제품 정보
|
||||
$products = $rootNodes->map(function ($node, $index) {
|
||||
$opts = $node->options ?? [];
|
||||
$vars = data_get($opts, 'bom_result.variables', []);
|
||||
|
||||
return [
|
||||
'no' => $index + 1,
|
||||
'floor' => $opts['floor'] ?? '-',
|
||||
'symbol' => $opts['symbol'] ?? '-',
|
||||
'product_name' => $opts['product_name'] ?? '-',
|
||||
'product_code' => $opts['product_code'] ?? null,
|
||||
'open_width' => $opts['open_width'] ?? null,
|
||||
'open_height' => $opts['open_height'] ?? null,
|
||||
'made_width' => $opts['width'] ?? null,
|
||||
'made_height' => $opts['height'] ?? null,
|
||||
'guide_rail' => $vars['installation_type'] ?? '-',
|
||||
'shaft' => $vars['bracket_inch'] ?? '-',
|
||||
'case_inch' => $vars['bracket_inch'] ?? '-',
|
||||
'bracket' => $vars['BRACKET_SIZE'] ?? '-',
|
||||
'capacity' => $vars['MOTOR_CAPACITY'] ?? '-',
|
||||
'finish' => $vars['finishing_type'] ?? '-',
|
||||
'product_type' => $vars['product_type'] ?? null,
|
||||
'joint_bar' => null, // 철재 전용 — 아래에서 bom_items에서 보강
|
||||
];
|
||||
})->values()->toArray();
|
||||
|
||||
// BOM items 집계 (모든 노드에서)
|
||||
$allBomItems = $rootNodes->flatMap(function ($node) {
|
||||
return collect(data_get($node->options, 'bom_result.items', []));
|
||||
});
|
||||
|
||||
// 철재 제품의 조인트바 수량 보강
|
||||
foreach ($rootNodes->values() as $index => $node) {
|
||||
$bomItems = collect(data_get($node->options, 'bom_result.items', []));
|
||||
$jointBar = $bomItems->first(fn ($i) => str_contains($i['item_name'] ?? '', '조인트바'));
|
||||
if ($jointBar) {
|
||||
$products[$index]['joint_bar'] = $jointBar['quantity'] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
// 모터 정보 (category: motor, controller)
|
||||
$motorItems = $allBomItems->filter(fn ($i) => in_array($i['item_category'] ?? '', ['motor', 'controller']));
|
||||
$motorLeft = [];
|
||||
$motorRight = [];
|
||||
foreach ($motorItems->groupBy('item_name') as $name => $group) {
|
||||
$item = $group->first();
|
||||
$totalQty = $group->sum('quantity');
|
||||
$row = [
|
||||
'item' => $item['item_name'],
|
||||
'type' => $item['specification'] ?? '-',
|
||||
'spec' => $item['item_code'] ?? '-',
|
||||
'qty' => $totalQty,
|
||||
];
|
||||
// 모터/브라켓 → 좌, 제어기/전동개폐기 → 우
|
||||
if (in_array($item['item_category'], ['controller'])) {
|
||||
$motorRight[] = $row;
|
||||
} else {
|
||||
$motorLeft[] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
// 절곡물 (category: steel)
|
||||
$steelItems = $allBomItems->filter(fn ($i) => ($i['item_category'] ?? '') === 'steel');
|
||||
$bendingParts = $this->groupBendingParts($steelItems);
|
||||
|
||||
// 부자재 (category: parts)
|
||||
$partItems = $allBomItems->filter(fn ($i) => ($i['item_category'] ?? '') === 'parts');
|
||||
$subsidiaryParts = [];
|
||||
foreach ($partItems->groupBy('item_name') as $name => $group) {
|
||||
$item = $group->first();
|
||||
$subsidiaryParts[] = [
|
||||
'name' => $item['item_name'],
|
||||
'spec' => $item['specification'] ?? '-',
|
||||
'qty' => $group->sum('quantity'),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => 'order',
|
||||
'data' => [
|
||||
'id' => $order->id,
|
||||
'order_no' => $order->order_no,
|
||||
'status' => $order->status,
|
||||
'status_code' => $order->status_code,
|
||||
'category_code' => $order->category_code,
|
||||
'received_at' => $order->received_at?->toDateString(),
|
||||
'delivery_date' => $order->delivery_date?->toDateString(),
|
||||
'delivery_method_code' => $order->delivery_method_code,
|
||||
'site_name' => $order->site_name,
|
||||
'nodes_count' => $order->nodes->count(),
|
||||
'client_name' => $order->client_name ?? $order->client?->name,
|
||||
'client_contact' => $order->client_contact,
|
||||
'manager_name' => $options['manager_name'] ?? null,
|
||||
'receiver' => $options['receiver'] ?? null,
|
||||
'receiver_contact' => $options['receiver_contact'] ?? null,
|
||||
'shipping_address' => $options['shipping_address'] ?? null,
|
||||
'shipping_address_detail' => $options['shipping_address_detail'] ?? null,
|
||||
'shipping_cost_code' => $options['shipping_cost_code'] ?? null,
|
||||
'quantity' => $order->quantity,
|
||||
'supply_amount' => $order->supply_amount,
|
||||
'tax_amount' => $order->tax_amount,
|
||||
'total_amount' => $order->total_amount,
|
||||
'remarks' => $order->remarks,
|
||||
'nodes_count' => $rootNodes->count(),
|
||||
'products' => $products,
|
||||
'motors' => [
|
||||
'left' => $motorLeft,
|
||||
'right' => $motorRight,
|
||||
],
|
||||
'bending_parts' => $bendingParts,
|
||||
'subsidiary_parts' => $subsidiaryParts,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 절곡물 BOM items를 그룹별로 분류
|
||||
*/
|
||||
private function groupBendingParts($steelItems): array
|
||||
{
|
||||
$groups = [
|
||||
'가이드레일' => [],
|
||||
'케이스' => [],
|
||||
'하단마감' => [],
|
||||
'연기차단재' => [],
|
||||
'기타' => [],
|
||||
];
|
||||
|
||||
foreach ($steelItems->groupBy('item_name') as $name => $group) {
|
||||
$item = $group->first();
|
||||
$totalQty = $group->sum('quantity');
|
||||
$row = [
|
||||
'name' => $item['item_name'],
|
||||
'spec' => $item['specification'] ?? '-',
|
||||
'qty' => $totalQty,
|
||||
];
|
||||
|
||||
if (str_contains($name, '연기차단재')) {
|
||||
$groups['연기차단재'][] = $row;
|
||||
} elseif (str_contains($name, '가이드레일')) {
|
||||
$groups['가이드레일'][] = $row;
|
||||
} elseif (str_contains($name, '케이스') || str_contains($name, '마구리')) {
|
||||
$groups['케이스'][] = $row;
|
||||
} elseif (str_contains($name, '하장바') || str_contains($name, 'L-BAR') || str_contains($name, '보강평철')) {
|
||||
$groups['하단마감'][] = $row;
|
||||
} else {
|
||||
$groups['기타'][] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach ($groups as $groupName => $items) {
|
||||
if (! empty($items)) {
|
||||
$result[] = [
|
||||
'group' => $groupName,
|
||||
'items' => $items,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function getWorkOrderLogDetail(int $id): array
|
||||
{
|
||||
$workOrder = WorkOrder::with('process')->findOrFail($id);
|
||||
@@ -449,22 +623,65 @@ private function getWorkOrderLogDetail(int $id): array
|
||||
|
||||
private function getShipmentDetail(int $id): array
|
||||
{
|
||||
$shipment = Shipment::findOrFail($id);
|
||||
$shipment = Shipment::with([
|
||||
'vehicleDispatches',
|
||||
'items',
|
||||
'order.nodes' => fn ($q) => $q->whereNull('parent_id'),
|
||||
])->findOrFail($id);
|
||||
|
||||
// 배차정보
|
||||
$vehicleDispatches = $shipment->vehicleDispatches->map(fn ($d) => [
|
||||
'logistics_company' => $d->logistics_company,
|
||||
'arrival_datetime' => $d->arrival_datetime,
|
||||
'tonnage' => $d->tonnage,
|
||||
'vehicle_no' => $d->vehicle_no,
|
||||
'driver_contact' => $d->driver_contact,
|
||||
'remarks' => $d->remarks,
|
||||
])->values()->toArray();
|
||||
|
||||
// 출하 품목 → 제품 그룹별 분류
|
||||
$productGroups = [];
|
||||
$otherParts = [];
|
||||
foreach ($shipment->items as $item) {
|
||||
$row = [
|
||||
'item_name' => $item->item_name,
|
||||
'specification' => $item->specification,
|
||||
'quantity' => $item->quantity,
|
||||
'unit' => $item->unit,
|
||||
'lot_no' => $item->lot_no,
|
||||
'floor_unit' => $item->floor_unit,
|
||||
];
|
||||
// floor_unit가 있으면 해당 제품 그룹에, 없으면 기타 부품
|
||||
if ($item->floor_unit) {
|
||||
$productGroups[$item->floor_unit][] = $row;
|
||||
} else {
|
||||
$otherParts[] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => 'shipping',
|
||||
'data' => [
|
||||
'id' => $shipment->id,
|
||||
'shipment_no' => $shipment->shipment_no,
|
||||
'lot_no' => $shipment->lot_no,
|
||||
'status' => $shipment->status,
|
||||
'scheduled_date' => $shipment->scheduled_date?->toDateString(),
|
||||
'customer_name' => $shipment->customer_name,
|
||||
'customer_grade' => $shipment->customer_grade,
|
||||
'site_name' => $shipment->site_name,
|
||||
'delivery_address' => $shipment->delivery_address,
|
||||
'delivery_method' => $shipment->delivery_method,
|
||||
'shipping_cost' => $shipment->shipping_cost,
|
||||
'receiver' => $shipment->receiver,
|
||||
'receiver_contact' => $shipment->receiver_contact,
|
||||
'vehicle_no' => $shipment->vehicle_no,
|
||||
'driver_name' => $shipment->driver_name,
|
||||
'driver_contact' => $shipment->driver_contact,
|
||||
'remarks' => $shipment->remarks,
|
||||
'vehicle_dispatches' => $vehicleDispatches,
|
||||
'product_groups' => $productGroups,
|
||||
'other_parts' => $otherParts,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user