259 lines
8.9 KiB
PHP
259 lines
8.9 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
namespace App\Services;
|
||
|
|
|
||
|
|
use App\Models\Qualitys\PerformanceReport;
|
||
|
|
use App\Services\Audit\AuditLogger;
|
||
|
|
use Illuminate\Support\Facades\DB;
|
||
|
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||
|
|
|
||
|
|
class PerformanceReportService extends Service
|
||
|
|
{
|
||
|
|
private const AUDIT_TARGET = 'performance_report';
|
||
|
|
|
||
|
|
public function __construct(
|
||
|
|
private readonly AuditLogger $auditLogger,
|
||
|
|
private readonly QualityDocumentService $qualityDocumentService
|
||
|
|
) {}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 목록 조회
|
||
|
|
*/
|
||
|
|
public function index(array $params)
|
||
|
|
{
|
||
|
|
$tenantId = $this->tenantId();
|
||
|
|
$perPage = (int) ($params['per_page'] ?? 20);
|
||
|
|
$q = trim((string) ($params['q'] ?? ''));
|
||
|
|
$year = $params['year'] ?? null;
|
||
|
|
$quarter = $params['quarter'] ?? null;
|
||
|
|
$confirmStatus = $params['confirm_status'] ?? null;
|
||
|
|
|
||
|
|
$query = PerformanceReport::query()
|
||
|
|
->where('performance_reports.tenant_id', $tenantId)
|
||
|
|
->with(['qualityDocument.client', 'qualityDocument.locations', 'confirmer:id,name']);
|
||
|
|
|
||
|
|
if ($q !== '') {
|
||
|
|
$query->whereHas('qualityDocument', function ($qq) use ($q) {
|
||
|
|
$qq->where('quality_doc_number', 'like', "%{$q}%")
|
||
|
|
->orWhere('site_name', 'like', "%{$q}%");
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($year !== null) {
|
||
|
|
$query->where('year', $year);
|
||
|
|
}
|
||
|
|
if ($quarter !== null) {
|
||
|
|
$query->where('quarter', $quarter);
|
||
|
|
}
|
||
|
|
if ($confirmStatus !== null) {
|
||
|
|
$query->where('confirmation_status', $confirmStatus);
|
||
|
|
}
|
||
|
|
|
||
|
|
$query->orderByDesc('performance_reports.id');
|
||
|
|
$paginated = $query->paginate($perPage);
|
||
|
|
|
||
|
|
$transformedData = $paginated->getCollection()->map(fn ($report) => $this->transformToFrontend($report));
|
||
|
|
|
||
|
|
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 = PerformanceReport::where('performance_reports.tenant_id', $tenantId);
|
||
|
|
|
||
|
|
if (! empty($params['year'])) {
|
||
|
|
$query->where('performance_reports.year', $params['year']);
|
||
|
|
}
|
||
|
|
if (! empty($params['quarter'])) {
|
||
|
|
$query->where('performance_reports.quarter', $params['quarter']);
|
||
|
|
}
|
||
|
|
|
||
|
|
$counts = (clone $query)
|
||
|
|
->select('confirmation_status', DB::raw('count(*) as count'))
|
||
|
|
->groupBy('confirmation_status')
|
||
|
|
->pluck('count', 'confirmation_status')
|
||
|
|
->toArray();
|
||
|
|
|
||
|
|
$totalLocations = (clone $query)
|
||
|
|
->join('quality_documents', 'quality_documents.id', '=', 'performance_reports.quality_document_id')
|
||
|
|
->join('quality_document_locations', 'quality_document_locations.quality_document_id', '=', 'quality_documents.id')
|
||
|
|
->count('quality_document_locations.id');
|
||
|
|
|
||
|
|
return [
|
||
|
|
'total_count' => array_sum($counts),
|
||
|
|
'confirmed_count' => $counts[PerformanceReport::STATUS_CONFIRMED] ?? 0,
|
||
|
|
'unconfirmed_count' => $counts[PerformanceReport::STATUS_UNCONFIRMED] ?? 0,
|
||
|
|
'total_locations' => $totalLocations,
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 일괄 확정
|
||
|
|
*/
|
||
|
|
public function confirm(array $ids)
|
||
|
|
{
|
||
|
|
$tenantId = $this->tenantId();
|
||
|
|
$userId = $this->apiUserId();
|
||
|
|
|
||
|
|
return DB::transaction(function () use ($ids, $tenantId, $userId) {
|
||
|
|
$reports = PerformanceReport::where('tenant_id', $tenantId)
|
||
|
|
->whereIn('id', $ids)
|
||
|
|
->with(['qualityDocument'])
|
||
|
|
->get();
|
||
|
|
|
||
|
|
$errors = [];
|
||
|
|
foreach ($reports as $report) {
|
||
|
|
if ($report->isConfirmed() || $report->isReported()) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 필수정보 검증
|
||
|
|
$requiredInfo = $this->qualityDocumentService->calculateRequiredInfo($report->qualityDocument);
|
||
|
|
if ($requiredInfo !== '완료') {
|
||
|
|
$errors[] = [
|
||
|
|
'id' => $report->id,
|
||
|
|
'quality_doc_number' => $report->qualityDocument->quality_doc_number,
|
||
|
|
'reason' => $requiredInfo,
|
||
|
|
];
|
||
|
|
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
$report->update([
|
||
|
|
'confirmation_status' => PerformanceReport::STATUS_CONFIRMED,
|
||
|
|
'confirmed_date' => now()->toDateString(),
|
||
|
|
'confirmed_by' => $userId,
|
||
|
|
'updated_by' => $userId,
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (! empty($errors)) {
|
||
|
|
throw new BadRequestHttpException(json_encode([
|
||
|
|
'message' => __('error.quality.confirm_failed'),
|
||
|
|
'errors' => $errors,
|
||
|
|
]));
|
||
|
|
}
|
||
|
|
|
||
|
|
return ['confirmed_count' => count($ids) - count($errors)];
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 일괄 확정 해제
|
||
|
|
*/
|
||
|
|
public function unconfirm(array $ids)
|
||
|
|
{
|
||
|
|
$tenantId = $this->tenantId();
|
||
|
|
$userId = $this->apiUserId();
|
||
|
|
|
||
|
|
return DB::transaction(function () use ($ids, $tenantId, $userId) {
|
||
|
|
PerformanceReport::where('tenant_id', $tenantId)
|
||
|
|
->whereIn('id', $ids)
|
||
|
|
->where('confirmation_status', PerformanceReport::STATUS_CONFIRMED)
|
||
|
|
->update([
|
||
|
|
'confirmation_status' => PerformanceReport::STATUS_UNCONFIRMED,
|
||
|
|
'confirmed_date' => null,
|
||
|
|
'confirmed_by' => null,
|
||
|
|
'updated_by' => $userId,
|
||
|
|
]);
|
||
|
|
|
||
|
|
return ['unconfirmed_count' => count($ids)];
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 일괄 메모 업데이트
|
||
|
|
*/
|
||
|
|
public function updateMemo(array $ids, string $memo)
|
||
|
|
{
|
||
|
|
$tenantId = $this->tenantId();
|
||
|
|
$userId = $this->apiUserId();
|
||
|
|
|
||
|
|
PerformanceReport::where('tenant_id', $tenantId)
|
||
|
|
->whereIn('id', $ids)
|
||
|
|
->update([
|
||
|
|
'memo' => $memo,
|
||
|
|
'updated_by' => $userId,
|
||
|
|
]);
|
||
|
|
|
||
|
|
return ['updated_count' => count($ids)];
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 누락체크 (출고완료 but 제품검사 미등록)
|
||
|
|
*/
|
||
|
|
public function missing(array $params): array
|
||
|
|
{
|
||
|
|
$tenantId = $this->tenantId();
|
||
|
|
|
||
|
|
// 품질관리서가 등록된 수주 ID
|
||
|
|
$registeredOrderIds = DB::table('quality_document_orders')
|
||
|
|
->join('quality_documents', 'quality_documents.id', '=', 'quality_document_orders.quality_document_id')
|
||
|
|
->where('quality_documents.tenant_id', $tenantId)
|
||
|
|
->pluck('quality_document_orders.order_id');
|
||
|
|
|
||
|
|
// 출고완료 상태이지만 품질관리서 미등록 수주
|
||
|
|
$query = DB::table('orders')
|
||
|
|
->where('tenant_id', $tenantId)
|
||
|
|
->whereNotIn('id', $registeredOrderIds)
|
||
|
|
->where('status_code', 'SHIPPED'); // TODO: 출고완료 상태 추가 시 상수 확인
|
||
|
|
|
||
|
|
if (! empty($params['year'])) {
|
||
|
|
$query->whereYear('created_at', $params['year']);
|
||
|
|
}
|
||
|
|
if (! empty($params['quarter'])) {
|
||
|
|
$quarter = (int) $params['quarter'];
|
||
|
|
$startMonth = ($quarter - 1) * 3 + 1;
|
||
|
|
$endMonth = $quarter * 3;
|
||
|
|
$query->whereMonth('created_at', '>=', $startMonth)
|
||
|
|
->whereMonth('created_at', '<=', $endMonth);
|
||
|
|
}
|
||
|
|
|
||
|
|
return $query->orderByDesc('id')
|
||
|
|
->limit(100)
|
||
|
|
->get()
|
||
|
|
->map(fn ($order) => [
|
||
|
|
'id' => $order->id,
|
||
|
|
'order_number' => $order->order_no ?? '',
|
||
|
|
'site_name' => $order->site_name ?? '',
|
||
|
|
'client' => '', // 별도 조인 필요
|
||
|
|
'delivery_date' => $order->delivery_date ?? '',
|
||
|
|
])
|
||
|
|
->toArray();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* DB → 프론트엔드 변환
|
||
|
|
*/
|
||
|
|
private function transformToFrontend(PerformanceReport $report): array
|
||
|
|
{
|
||
|
|
$doc = $report->qualityDocument;
|
||
|
|
|
||
|
|
return [
|
||
|
|
'id' => $report->id,
|
||
|
|
'quality_doc_number' => $doc?->quality_doc_number ?? '',
|
||
|
|
'created_date' => $report->created_at?->format('Y-m-d') ?? '',
|
||
|
|
'site_name' => $doc?->site_name ?? '',
|
||
|
|
'client' => $doc?->client?->name ?? '',
|
||
|
|
'location_count' => $doc?->locations?->count() ?? 0,
|
||
|
|
'required_info' => $doc ? $this->qualityDocumentService->calculateRequiredInfo($doc) : '',
|
||
|
|
'confirm_status' => $report->confirmation_status === PerformanceReport::STATUS_CONFIRMED ? 'confirmed' : 'unconfirmed',
|
||
|
|
'confirm_date' => $report->confirmed_date?->format('Y-m-d'),
|
||
|
|
'memo' => $report->memo ?? '',
|
||
|
|
'year' => $report->year,
|
||
|
|
'quarter' => $report->quarter,
|
||
|
|
];
|
||
|
|
}
|
||
|
|
}
|