751 lines
26 KiB
PHP
751 lines
26 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Orders\Order;
|
|
use App\Models\Orders\OrderItem;
|
|
use App\Models\Qualitys\PerformanceReport;
|
|
use App\Models\Qualitys\QualityDocument;
|
|
use App\Models\Qualitys\QualityDocumentLocation;
|
|
use App\Models\Qualitys\QualityDocumentOrder;
|
|
use App\Services\Audit\AuditLogger;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
|
|
|
class QualityDocumentService extends Service
|
|
{
|
|
private const AUDIT_TARGET = 'quality_document';
|
|
|
|
public function __construct(
|
|
private readonly AuditLogger $auditLogger
|
|
) {}
|
|
|
|
/**
|
|
* 목록 조회
|
|
*/
|
|
public function index(array $params)
|
|
{
|
|
$tenantId = $this->tenantId();
|
|
$perPage = (int) ($params['per_page'] ?? 20);
|
|
$q = trim((string) ($params['q'] ?? ''));
|
|
$status = $params['status'] ?? null;
|
|
$dateFrom = $params['date_from'] ?? null;
|
|
$dateTo = $params['date_to'] ?? null;
|
|
|
|
$query = QualityDocument::query()
|
|
->where('tenant_id', $tenantId)
|
|
->with(['client', 'inspector:id,name', 'creator:id,name', 'documentOrders.order', 'locations']);
|
|
|
|
if ($q !== '') {
|
|
$query->where(function ($qq) use ($q) {
|
|
$qq->where('quality_doc_number', 'like', "%{$q}%")
|
|
->orWhere('site_name', 'like', "%{$q}%");
|
|
});
|
|
}
|
|
|
|
if ($status !== null) {
|
|
$dbStatus = QualityDocument::mapStatusFromFrontend($status);
|
|
$query->where('status', $dbStatus);
|
|
}
|
|
|
|
if ($dateFrom !== null) {
|
|
$query->where('received_date', '>=', $dateFrom);
|
|
}
|
|
if ($dateTo !== null) {
|
|
$query->where('received_date', '<=', $dateTo);
|
|
}
|
|
|
|
$query->orderByDesc('id');
|
|
$paginated = $query->paginate($perPage);
|
|
|
|
$transformedData = $paginated->getCollection()->map(fn ($doc) => $this->transformToFrontend($doc));
|
|
|
|
return [
|
|
'items' => $transformedData,
|
|
'current_page' => $paginated->currentPage(),
|
|
'last_page' => $paginated->lastPage(),
|
|
'per_page' => $paginated->perPage(),
|
|
'total' => $paginated->total(),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 통계 조회
|
|
*/
|
|
public function stats(array $params = []): array
|
|
{
|
|
$tenantId = $this->tenantId();
|
|
|
|
$query = QualityDocument::where('tenant_id', $tenantId);
|
|
|
|
if (! empty($params['date_from'])) {
|
|
$query->where('received_date', '>=', $params['date_from']);
|
|
}
|
|
if (! empty($params['date_to'])) {
|
|
$query->where('received_date', '<=', $params['date_to']);
|
|
}
|
|
|
|
$counts = (clone $query)
|
|
->select('status', DB::raw('count(*) as count'))
|
|
->groupBy('status')
|
|
->pluck('count', 'status')
|
|
->toArray();
|
|
|
|
return [
|
|
'reception_count' => $counts[QualityDocument::STATUS_RECEIVED] ?? 0,
|
|
'in_progress_count' => $counts[QualityDocument::STATUS_IN_PROGRESS] ?? 0,
|
|
'completed_count' => $counts[QualityDocument::STATUS_COMPLETED] ?? 0,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 캘린더 스케줄 조회
|
|
*/
|
|
public function calendar(array $params): array
|
|
{
|
|
$tenantId = $this->tenantId();
|
|
$year = (int) ($params['year'] ?? now()->year);
|
|
$month = (int) ($params['month'] ?? now()->month);
|
|
|
|
$startDate = sprintf('%04d-%02d-01', $year, $month);
|
|
$endDate = date('Y-m-t', strtotime($startDate));
|
|
|
|
$query = QualityDocument::query()
|
|
->where('tenant_id', $tenantId)
|
|
->with(['inspector:id,name']);
|
|
|
|
// options JSON 내 inspection.start_date / inspection.end_date 기준 필터링
|
|
// received_date 기준으로 범위 필터 (options JSON은 직접 SQL 필터 어려우므로)
|
|
$query->where(function ($q) use ($startDate, $endDate) {
|
|
$q->whereBetween('received_date', [$startDate, $endDate]);
|
|
});
|
|
|
|
if (! empty($params['status'])) {
|
|
$dbStatus = QualityDocument::mapStatusFromFrontend($params['status']);
|
|
$query->where('status', $dbStatus);
|
|
}
|
|
|
|
return $query->orderBy('received_date')
|
|
->get()
|
|
->map(function (QualityDocument $doc) {
|
|
$options = $doc->options ?? [];
|
|
$inspection = $options['inspection'] ?? [];
|
|
|
|
return [
|
|
'id' => $doc->id,
|
|
'start_date' => $inspection['start_date'] ?? $doc->received_date?->format('Y-m-d'),
|
|
'end_date' => $inspection['end_date'] ?? $doc->received_date?->format('Y-m-d'),
|
|
'inspector' => $doc->inspector?->name ?? '',
|
|
'site_name' => $doc->site_name,
|
|
'status' => QualityDocument::mapStatusToFrontend($doc->status),
|
|
];
|
|
})
|
|
->values()
|
|
->toArray();
|
|
}
|
|
|
|
/**
|
|
* 단건 조회
|
|
*/
|
|
public function show(int $id)
|
|
{
|
|
$tenantId = $this->tenantId();
|
|
|
|
$doc = QualityDocument::where('tenant_id', $tenantId)
|
|
->with([
|
|
'client',
|
|
'inspector:id,name',
|
|
'creator:id,name',
|
|
'documentOrders.order',
|
|
'locations.orderItem.node',
|
|
])
|
|
->find($id);
|
|
|
|
if (! $doc) {
|
|
throw new NotFoundHttpException(__('error.not_found'));
|
|
}
|
|
|
|
return $this->transformToFrontend($doc, true);
|
|
}
|
|
|
|
/**
|
|
* 생성
|
|
*/
|
|
public function store(array $data)
|
|
{
|
|
$tenantId = $this->tenantId();
|
|
$userId = $this->apiUserId();
|
|
|
|
return DB::transaction(function () use ($data, $tenantId, $userId) {
|
|
$data['tenant_id'] = $tenantId;
|
|
$data['quality_doc_number'] = QualityDocument::generateDocNumber($tenantId);
|
|
$data['status'] = QualityDocument::STATUS_RECEIVED;
|
|
$data['created_by'] = $userId;
|
|
|
|
$doc = QualityDocument::create($data);
|
|
|
|
$this->auditLogger->log(
|
|
$tenantId,
|
|
self::AUDIT_TARGET,
|
|
$doc->id,
|
|
'created',
|
|
null,
|
|
$doc->toArray()
|
|
);
|
|
|
|
return $this->transformToFrontend($doc->load(['client', 'inspector:id,name', 'creator:id,name']));
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 수정
|
|
*/
|
|
public function update(int $id, array $data)
|
|
{
|
|
$tenantId = $this->tenantId();
|
|
$userId = $this->apiUserId();
|
|
|
|
$doc = QualityDocument::where('tenant_id', $tenantId)->find($id);
|
|
if (! $doc) {
|
|
throw new NotFoundHttpException(__('error.not_found'));
|
|
}
|
|
|
|
$beforeData = $doc->toArray();
|
|
|
|
return DB::transaction(function () use ($doc, $data, $userId, $beforeData) {
|
|
$data['updated_by'] = $userId;
|
|
|
|
// options는 기존 값과 병합
|
|
if (isset($data['options'])) {
|
|
$existingOptions = $doc->options ?? [];
|
|
$data['options'] = array_replace_recursive($existingOptions, $data['options']);
|
|
}
|
|
|
|
$doc->update($data);
|
|
|
|
$this->auditLogger->log(
|
|
$doc->tenant_id,
|
|
self::AUDIT_TARGET,
|
|
$doc->id,
|
|
'updated',
|
|
$beforeData,
|
|
$doc->fresh()->toArray()
|
|
);
|
|
|
|
return $this->transformToFrontend($doc->load(['client', 'inspector:id,name', 'creator:id,name', 'documentOrders.order', 'locations']));
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 삭제
|
|
*/
|
|
public function destroy(int $id)
|
|
{
|
|
$tenantId = $this->tenantId();
|
|
|
|
$doc = QualityDocument::where('tenant_id', $tenantId)->find($id);
|
|
if (! $doc) {
|
|
throw new NotFoundHttpException(__('error.not_found'));
|
|
}
|
|
|
|
if ($doc->isCompleted()) {
|
|
throw new BadRequestHttpException(__('error.quality.cannot_delete_completed'));
|
|
}
|
|
|
|
$beforeData = $doc->toArray();
|
|
$doc->deleted_by = $this->apiUserId();
|
|
$doc->save();
|
|
$doc->delete();
|
|
|
|
$this->auditLogger->log(
|
|
$tenantId,
|
|
self::AUDIT_TARGET,
|
|
$doc->id,
|
|
'deleted',
|
|
$beforeData,
|
|
null
|
|
);
|
|
|
|
return 'success';
|
|
}
|
|
|
|
/**
|
|
* 검사 완료 처리
|
|
*/
|
|
public function complete(int $id)
|
|
{
|
|
$tenantId = $this->tenantId();
|
|
$userId = $this->apiUserId();
|
|
|
|
$doc = QualityDocument::where('tenant_id', $tenantId)
|
|
->with(['locations'])
|
|
->find($id);
|
|
|
|
if (! $doc) {
|
|
throw new NotFoundHttpException(__('error.not_found'));
|
|
}
|
|
|
|
if ($doc->isCompleted()) {
|
|
throw new BadRequestHttpException(__('error.quality.already_completed'));
|
|
}
|
|
|
|
// 미완료 개소 확인
|
|
$pendingCount = $doc->locations->where('inspection_status', QualityDocumentLocation::STATUS_PENDING)->count();
|
|
if ($pendingCount > 0) {
|
|
throw new BadRequestHttpException(__('error.quality.pending_locations', ['count' => $pendingCount]));
|
|
}
|
|
|
|
$beforeData = $doc->toArray();
|
|
|
|
return DB::transaction(function () use ($doc, $userId, $tenantId, $beforeData) {
|
|
$doc->update([
|
|
'status' => QualityDocument::STATUS_COMPLETED,
|
|
'updated_by' => $userId,
|
|
]);
|
|
|
|
// 실적신고 자동 생성
|
|
$now = now();
|
|
PerformanceReport::firstOrCreate(
|
|
[
|
|
'tenant_id' => $tenantId,
|
|
'quality_document_id' => $doc->id,
|
|
],
|
|
[
|
|
'year' => $now->year,
|
|
'quarter' => (int) ceil($now->month / 3),
|
|
'confirmation_status' => PerformanceReport::STATUS_UNCONFIRMED,
|
|
'created_by' => $userId,
|
|
]
|
|
);
|
|
|
|
$this->auditLogger->log(
|
|
$tenantId,
|
|
self::AUDIT_TARGET,
|
|
$doc->id,
|
|
'completed',
|
|
$beforeData,
|
|
$doc->fresh()->toArray()
|
|
);
|
|
|
|
return $this->transformToFrontend($doc->load(['client', 'inspector:id,name', 'creator:id,name']));
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 검사 미등록 수주 목록
|
|
*/
|
|
public function availableOrders(array $params): array
|
|
{
|
|
$tenantId = $this->tenantId();
|
|
$q = trim((string) ($params['q'] ?? ''));
|
|
|
|
// 이미 연결된 수주 ID 목록
|
|
$linkedOrderIds = QualityDocumentOrder::whereHas('qualityDocument', function ($query) use ($tenantId) {
|
|
$query->where('tenant_id', $tenantId);
|
|
})->pluck('order_id');
|
|
|
|
$query = Order::where('tenant_id', $tenantId)
|
|
->whereNotIn('id', $linkedOrderIds)
|
|
->withCount(['nodes as location_count' => function ($q) {
|
|
$q->whereNull('parent_id');
|
|
}]);
|
|
|
|
if ($q !== '') {
|
|
$query->where(function ($qq) use ($q) {
|
|
$qq->where('order_no', 'like', "%{$q}%")
|
|
->orWhere('site_name', 'like', "%{$q}%");
|
|
});
|
|
}
|
|
|
|
return $query->orderByDesc('id')
|
|
->limit(50)
|
|
->get()
|
|
->map(fn ($order) => [
|
|
'id' => $order->id,
|
|
'order_number' => $order->order_no,
|
|
'site_name' => $order->site_name ?? '',
|
|
'client_name' => $order->client_name ?? '',
|
|
'delivery_date' => $order->delivery_date?->format('Y-m-d') ?? '',
|
|
'location_count' => $order->location_count,
|
|
])
|
|
->toArray();
|
|
}
|
|
|
|
/**
|
|
* 수주 연결
|
|
*/
|
|
public function attachOrders(int $docId, array $orderIds)
|
|
{
|
|
$tenantId = $this->tenantId();
|
|
|
|
$doc = QualityDocument::where('tenant_id', $tenantId)->find($docId);
|
|
if (! $doc) {
|
|
throw new NotFoundHttpException(__('error.not_found'));
|
|
}
|
|
|
|
return DB::transaction(function () use ($doc, $orderIds, $tenantId) {
|
|
foreach ($orderIds as $orderId) {
|
|
$order = Order::where('tenant_id', $tenantId)->find($orderId);
|
|
if (! $order) {
|
|
continue;
|
|
}
|
|
|
|
// 중복 체크
|
|
$docOrder = QualityDocumentOrder::firstOrCreate([
|
|
'quality_document_id' => $doc->id,
|
|
'order_id' => $orderId,
|
|
]);
|
|
|
|
// 수주 연결 시 개소(order_items)를 locations에 자동 생성
|
|
$orderItems = OrderItem::where('order_id', $orderId)->get();
|
|
foreach ($orderItems as $item) {
|
|
QualityDocumentLocation::firstOrCreate([
|
|
'quality_document_id' => $doc->id,
|
|
'quality_document_order_id' => $docOrder->id,
|
|
'order_item_id' => $item->id,
|
|
]);
|
|
}
|
|
}
|
|
|
|
// 상태를 진행중으로 변경 (접수 상태일 때)
|
|
if ($doc->isReceived()) {
|
|
$doc->update(['status' => QualityDocument::STATUS_IN_PROGRESS]);
|
|
}
|
|
|
|
return $this->transformToFrontend(
|
|
$doc->load(['client', 'inspector:id,name', 'creator:id,name', 'documentOrders.order', 'locations.orderItem.node'])
|
|
);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 수주 연결 해제
|
|
*/
|
|
public function detachOrder(int $docId, int $orderId)
|
|
{
|
|
$tenantId = $this->tenantId();
|
|
|
|
$doc = QualityDocument::where('tenant_id', $tenantId)->find($docId);
|
|
if (! $doc) {
|
|
throw new NotFoundHttpException(__('error.not_found'));
|
|
}
|
|
|
|
if ($doc->isCompleted()) {
|
|
throw new BadRequestHttpException(__('error.quality.cannot_modify_completed'));
|
|
}
|
|
|
|
$docOrder = QualityDocumentOrder::where('quality_document_id', $docId)
|
|
->where('order_id', $orderId)
|
|
->first();
|
|
|
|
if ($docOrder) {
|
|
// 해당 수주의 locations 삭제
|
|
QualityDocumentLocation::where('quality_document_order_id', $docOrder->id)->delete();
|
|
$docOrder->delete();
|
|
}
|
|
|
|
return $this->transformToFrontend(
|
|
$doc->load(['client', 'inspector:id,name', 'creator:id,name', 'documentOrders.order', 'locations.orderItem.node'])
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 필수정보 계산
|
|
*/
|
|
public function calculateRequiredInfo(QualityDocument $doc): string
|
|
{
|
|
$options = $doc->options ?? [];
|
|
$missing = 0;
|
|
|
|
$sections = [
|
|
'construction_site' => ['name', 'land_location', 'lot_number'],
|
|
'material_distributor' => ['company', 'address', 'ceo', 'phone'],
|
|
'contractor' => ['company', 'address', 'name', 'phone'],
|
|
'supervisor' => ['office', 'address', 'name', 'phone'],
|
|
];
|
|
|
|
foreach ($sections as $section => $fields) {
|
|
$data = $options[$section] ?? [];
|
|
foreach ($fields as $field) {
|
|
if (empty($data[$field])) {
|
|
$missing++;
|
|
break; // 섹션 단위
|
|
}
|
|
}
|
|
}
|
|
|
|
return $missing === 0 ? '완료' : "{$missing}건 누락";
|
|
}
|
|
|
|
/**
|
|
* DB → 프론트엔드 변환
|
|
*/
|
|
private function transformToFrontend(QualityDocument $doc, bool $detail = false): array
|
|
{
|
|
$options = $doc->options ?? [];
|
|
|
|
$result = [
|
|
'id' => $doc->id,
|
|
'quality_doc_number' => $doc->quality_doc_number,
|
|
'site_name' => $doc->site_name,
|
|
'client' => $doc->client?->name ?? '',
|
|
'location_count' => $doc->locations?->count() ?? 0,
|
|
'required_info' => $this->calculateRequiredInfo($doc),
|
|
'inspection_period' => $this->formatInspectionPeriod($options),
|
|
'inspector' => $doc->inspector?->name ?? '',
|
|
'status' => QualityDocument::mapStatusToFrontend($doc->status),
|
|
'author' => $doc->creator?->name ?? '',
|
|
'reception_date' => $doc->received_date?->format('Y-m-d'),
|
|
'manager' => $options['manager']['name'] ?? '',
|
|
'manager_contact' => $options['manager']['phone'] ?? '',
|
|
];
|
|
|
|
if ($detail) {
|
|
$result['construction_site'] = [
|
|
'site_name' => $options['construction_site']['name'] ?? '',
|
|
'land_location' => $options['construction_site']['land_location'] ?? '',
|
|
'lot_number' => $options['construction_site']['lot_number'] ?? '',
|
|
];
|
|
$result['material_distributor'] = [
|
|
'company_name' => $options['material_distributor']['company'] ?? '',
|
|
'company_address' => $options['material_distributor']['address'] ?? '',
|
|
'representative_name' => $options['material_distributor']['ceo'] ?? '',
|
|
'phone' => $options['material_distributor']['phone'] ?? '',
|
|
];
|
|
$result['constructor_info'] = [
|
|
'company_name' => $options['contractor']['company'] ?? '',
|
|
'company_address' => $options['contractor']['address'] ?? '',
|
|
'name' => $options['contractor']['name'] ?? '',
|
|
'phone' => $options['contractor']['phone'] ?? '',
|
|
];
|
|
$result['supervisor'] = [
|
|
'office_name' => $options['supervisor']['office'] ?? '',
|
|
'office_address' => $options['supervisor']['address'] ?? '',
|
|
'name' => $options['supervisor']['name'] ?? '',
|
|
'phone' => $options['supervisor']['phone'] ?? '',
|
|
];
|
|
$result['schedule_info'] = [
|
|
'visit_request_date' => $options['inspection']['request_date'] ?? '',
|
|
'start_date' => $options['inspection']['start_date'] ?? '',
|
|
'end_date' => $options['inspection']['end_date'] ?? '',
|
|
'inspector' => $doc->inspector?->name ?? '',
|
|
'site_postal_code' => $options['site_address']['postal_code'] ?? '',
|
|
'site_address' => $options['site_address']['address'] ?? '',
|
|
'site_address_detail' => $options['site_address']['detail'] ?? '',
|
|
];
|
|
|
|
// 개소 목록
|
|
$result['order_items'] = $doc->locations->map(function ($loc) {
|
|
$orderItem = $loc->orderItem;
|
|
$node = $orderItem?->node;
|
|
$nodeOptions = $node?->options ?? [];
|
|
$order = $loc->qualityDocumentOrder?->order;
|
|
|
|
return [
|
|
'id' => (string) $loc->id,
|
|
'order_number' => $order?->order_no ?? '',
|
|
'site_name' => $order?->site_name ?? '',
|
|
'delivery_date' => $order?->delivery_date ?? '',
|
|
'floor' => $orderItem?->floor_code ?? '',
|
|
'symbol' => $orderItem?->symbol_code ?? '',
|
|
'order_width' => $nodeOptions['width'] ?? 0,
|
|
'order_height' => $nodeOptions['height'] ?? 0,
|
|
'construction_width' => $loc->post_width ?? 0,
|
|
'construction_height' => $loc->post_height ?? 0,
|
|
'change_reason' => $loc->change_reason ?? '',
|
|
];
|
|
})->toArray();
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* 개소별 검사 저장 (시공후 규격 + 검사 성적서)
|
|
*/
|
|
public function inspectLocation(int $docId, int $locId, array $data)
|
|
{
|
|
$tenantId = $this->tenantId();
|
|
|
|
$doc = QualityDocument::where('tenant_id', $tenantId)->find($docId);
|
|
if (! $doc) {
|
|
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'));
|
|
}
|
|
|
|
return DB::transaction(function () use ($location, $data, $doc) {
|
|
$updateData = [];
|
|
|
|
if (isset($data['post_width'])) {
|
|
$updateData['post_width'] = $data['post_width'];
|
|
}
|
|
if (isset($data['post_height'])) {
|
|
$updateData['post_height'] = $data['post_height'];
|
|
}
|
|
if (isset($data['change_reason'])) {
|
|
$updateData['change_reason'] = $data['change_reason'];
|
|
}
|
|
if (isset($data['inspection_status'])) {
|
|
$updateData['inspection_status'] = $data['inspection_status'];
|
|
}
|
|
|
|
if (! empty($updateData)) {
|
|
$location->update($updateData);
|
|
}
|
|
|
|
// 상태를 진행중으로 변경 (접수 상태일 때)
|
|
if ($doc->isReceived()) {
|
|
$doc->update(['status' => QualityDocument::STATUS_IN_PROGRESS]);
|
|
}
|
|
|
|
return $location->fresh()->toArray();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 검사제품요청서 데이터 (PDF/프린트용)
|
|
*/
|
|
public function requestDocument(int $id): array
|
|
{
|
|
$tenantId = $this->tenantId();
|
|
|
|
$doc = QualityDocument::where('tenant_id', $tenantId)
|
|
->with([
|
|
'client',
|
|
'inspector:id,name',
|
|
'documentOrders.order',
|
|
'locations.orderItem.node',
|
|
])
|
|
->find($id);
|
|
|
|
if (! $doc) {
|
|
throw new NotFoundHttpException(__('error.not_found'));
|
|
}
|
|
|
|
$options = $doc->options ?? [];
|
|
|
|
return [
|
|
'quality_doc_number' => $doc->quality_doc_number,
|
|
'site_name' => $doc->site_name,
|
|
'client' => $doc->client?->name ?? '',
|
|
'received_date' => $doc->received_date?->format('Y-m-d'),
|
|
'inspector' => $doc->inspector?->name ?? '',
|
|
'construction_site' => $options['construction_site'] ?? [],
|
|
'material_distributor' => $options['material_distributor'] ?? [],
|
|
'contractor' => $options['contractor'] ?? [],
|
|
'supervisor' => $options['supervisor'] ?? [],
|
|
'inspection' => $options['inspection'] ?? [],
|
|
'site_address' => $options['site_address'] ?? [],
|
|
'manager' => $options['manager'] ?? [],
|
|
'items' => $doc->locations->map(function ($loc) {
|
|
$orderItem = $loc->orderItem;
|
|
$node = $orderItem?->node;
|
|
$nodeOptions = $node?->options ?? [];
|
|
$order = $loc->qualityDocumentOrder?->order;
|
|
|
|
return [
|
|
'order_number' => $order?->order_no ?? '',
|
|
'floor' => $orderItem?->floor_code ?? '',
|
|
'symbol' => $orderItem?->symbol_code ?? '',
|
|
'item_name' => $orderItem?->item_name ?? '',
|
|
'specification' => $orderItem?->specification ?? '',
|
|
'order_width' => $nodeOptions['width'] ?? 0,
|
|
'order_height' => $nodeOptions['height'] ?? 0,
|
|
'quantity' => $orderItem?->quantity ?? 1,
|
|
];
|
|
})->toArray(),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 제품검사성적서 데이터 (documents EAV 연동)
|
|
*/
|
|
public function resultDocument(int $id): array
|
|
{
|
|
$tenantId = $this->tenantId();
|
|
|
|
$doc = QualityDocument::where('tenant_id', $tenantId)
|
|
->with([
|
|
'client',
|
|
'inspector:id,name',
|
|
'locations.orderItem.node',
|
|
'locations.document.data',
|
|
'locations.document.template',
|
|
])
|
|
->find($id);
|
|
|
|
if (! $doc) {
|
|
throw new NotFoundHttpException(__('error.not_found'));
|
|
}
|
|
|
|
$options = $doc->options ?? [];
|
|
|
|
return [
|
|
'quality_doc_number' => $doc->quality_doc_number,
|
|
'site_name' => $doc->site_name,
|
|
'client' => $doc->client?->name ?? '',
|
|
'inspector' => $doc->inspector?->name ?? '',
|
|
'status' => QualityDocument::mapStatusToFrontend($doc->status),
|
|
'locations' => $doc->locations->map(function ($loc) {
|
|
$orderItem = $loc->orderItem;
|
|
$node = $orderItem?->node;
|
|
$nodeOptions = $node?->options ?? [];
|
|
$document = $loc->document;
|
|
|
|
$result = [
|
|
'id' => $loc->id,
|
|
'floor' => $orderItem?->floor_code ?? '',
|
|
'symbol' => $orderItem?->symbol_code ?? '',
|
|
'order_width' => $nodeOptions['width'] ?? 0,
|
|
'order_height' => $nodeOptions['height'] ?? 0,
|
|
'post_width' => $loc->post_width,
|
|
'post_height' => $loc->post_height,
|
|
'change_reason' => $loc->change_reason,
|
|
'inspection_status' => $loc->inspection_status,
|
|
'document_id' => $loc->document_id,
|
|
];
|
|
|
|
// EAV 문서 데이터가 있으면 포함
|
|
if ($document) {
|
|
$result['document'] = [
|
|
'id' => $document->id,
|
|
'document_no' => $document->document_no,
|
|
'status' => $document->status,
|
|
'template_id' => $document->template_id,
|
|
'data' => $document->data?->map(fn ($d) => [
|
|
'field_key' => $d->field_key,
|
|
'field_value' => $d->field_value,
|
|
'section_id' => $d->section_id,
|
|
'column_id' => $d->column_id,
|
|
])->toArray() ?? [],
|
|
];
|
|
}
|
|
|
|
return $result;
|
|
})->toArray(),
|
|
];
|
|
}
|
|
|
|
private function formatInspectionPeriod(array $options): string
|
|
{
|
|
$inspection = $options['inspection'] ?? [];
|
|
$start = $inspection['start_date'] ?? '';
|
|
$end = $inspection['end_date'] ?? '';
|
|
|
|
if ($start && $end) {
|
|
return "{$start}~{$end}";
|
|
}
|
|
|
|
return $start ?: $end ?: '';
|
|
}
|
|
}
|