Files
sam-api/app/Services/WorkResultService.php
kent 84cce6742e feat: G-2 작업실적 관리 API 구현
- WorkResult 모델 생성 (Production 네임스페이스)
- WorkResultService 서비스 구현 (CRUD + 통계 + 토글)
- WorkResultController 컨트롤러 생성 (8개 엔드포인트)
- FormRequest 검증 클래스 (Store/Update)
- Swagger 문서 작성 (WorkResultApi.php)
- 라우트 추가 (/api/v1/work-results)
- i18n 메시지 추가 (work_result 키)

API Endpoints:
- GET /work-results - 목록 조회 (페이징, 필터링)
- GET /work-results/stats - 통계 조회
- GET /work-results/{id} - 상세 조회
- POST /work-results - 등록
- PUT /work-results/{id} - 수정
- DELETE /work-results/{id} - 삭제
- PATCH /work-results/{id}/inspection - 검사 상태 토글
- PATCH /work-results/{id}/packaging - 포장 상태 토글

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-26 15:18:08 +09:00

280 lines
8.1 KiB
PHP

<?php
namespace App\Services;
use App\Models\Production\WorkOrder;
use App\Models\Production\WorkResult;
use Illuminate\Support\Facades\DB;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class WorkResultService extends Service
{
/**
* 목록 조회 (검색/필터링/페이징)
*/
public function index(array $params)
{
$tenantId = $this->tenantId();
$page = (int) ($params['page'] ?? 1);
$size = (int) ($params['size'] ?? 20);
$q = trim((string) ($params['q'] ?? ''));
$processType = $params['process_type'] ?? null;
$workOrderId = $params['work_order_id'] ?? null;
$workerId = $params['worker_id'] ?? null;
$workDateFrom = $params['work_date_from'] ?? null;
$workDateTo = $params['work_date_to'] ?? null;
$isInspected = isset($params['is_inspected']) ? filter_var($params['is_inspected'], FILTER_VALIDATE_BOOLEAN) : null;
$isPackaged = isset($params['is_packaged']) ? filter_var($params['is_packaged'], FILTER_VALIDATE_BOOLEAN) : null;
$query = WorkResult::query()
->where('tenant_id', $tenantId)
->with([
'workOrder:id,work_order_no',
'worker:id,name',
]);
// 검색어
if ($q !== '') {
$query->where(function ($qq) use ($q) {
$qq->where('lot_no', 'like', "%{$q}%")
->orWhere('product_name', 'like', "%{$q}%")
->orWhereHas('workOrder', fn ($wo) => $wo->where('work_order_no', 'like', "%{$q}%"));
});
}
// 공정유형 필터
if ($processType !== null) {
$query->where('process_type', $processType);
}
// 작업지시 필터
if ($workOrderId !== null) {
$query->where('work_order_id', $workOrderId);
}
// 작업자 필터
if ($workerId !== null) {
$query->where('worker_id', $workerId);
}
// 작업일 범위
if ($workDateFrom !== null) {
$query->where('work_date', '>=', $workDateFrom);
}
if ($workDateTo !== null) {
$query->where('work_date', '<=', $workDateTo);
}
// 검사 완료 필터
if ($isInspected !== null) {
$query->where('is_inspected', $isInspected);
}
// 포장 완료 필터
if ($isPackaged !== null) {
$query->where('is_packaged', $isPackaged);
}
$query->orderByDesc('work_date')->orderByDesc('created_at');
return $query->paginate($size, ['*'], 'page', $page);
}
/**
* 통계 조회
*/
public function stats(array $params = []): array
{
$tenantId = $this->tenantId();
$workDateFrom = $params['work_date_from'] ?? null;
$workDateTo = $params['work_date_to'] ?? null;
$processType = $params['process_type'] ?? null;
$query = WorkResult::where('tenant_id', $tenantId);
// 작업일 범위
if ($workDateFrom !== null) {
$query->where('work_date', '>=', $workDateFrom);
}
if ($workDateTo !== null) {
$query->where('work_date', '<=', $workDateTo);
}
// 공정유형 필터
if ($processType !== null) {
$query->where('process_type', $processType);
}
$totals = $query->select([
DB::raw('SUM(production_qty) as total_production'),
DB::raw('SUM(good_qty) as total_good'),
DB::raw('SUM(defect_qty) as total_defect'),
])->first();
$totalProduction = (int) ($totals->total_production ?? 0);
$totalGood = (int) ($totals->total_good ?? 0);
$totalDefect = (int) ($totals->total_defect ?? 0);
$defectRate = $totalProduction > 0
? round(($totalDefect / $totalProduction) * 100, 1)
: 0;
return [
'total_production' => $totalProduction,
'total_good' => $totalGood,
'total_defect' => $totalDefect,
'defect_rate' => $defectRate,
];
}
/**
* 단건 조회
*/
public function show(int $id)
{
$tenantId = $this->tenantId();
$workResult = WorkResult::where('tenant_id', $tenantId)
->with([
'workOrder:id,work_order_no,project_name,status',
'workOrderItem:id,item_name,specification,quantity',
'worker:id,name',
])
->find($id);
if (! $workResult) {
throw new NotFoundHttpException(__('error.not_found'));
}
return $workResult;
}
/**
* 생성
*/
public function store(array $data)
{
$tenantId = $this->tenantId();
$userId = $this->apiUserId();
return DB::transaction(function () use ($data, $tenantId, $userId) {
$data['tenant_id'] = $tenantId;
$data['created_by'] = $userId;
$data['updated_by'] = $userId;
// 양품수량 자동 계산 (입력 안 된 경우)
if (! isset($data['good_qty'])) {
$data['good_qty'] = max(0, ($data['production_qty'] ?? 0) - ($data['defect_qty'] ?? 0));
}
// 작업지시 정보로 자동 채움
if (! empty($data['work_order_id'])) {
$workOrder = WorkOrder::find($data['work_order_id']);
if ($workOrder) {
$data['process_type'] = $data['process_type'] ?? $workOrder->process_type;
}
}
return WorkResult::create($data);
});
}
/**
* 수정
*/
public function update(int $id, array $data)
{
$tenantId = $this->tenantId();
$userId = $this->apiUserId();
$workResult = WorkResult::where('tenant_id', $tenantId)->find($id);
if (! $workResult) {
throw new NotFoundHttpException(__('error.not_found'));
}
return DB::transaction(function () use ($workResult, $data, $userId) {
$data['updated_by'] = $userId;
// 양품수량 재계산 (생산수량 또는 불량수량 변경 시)
if (isset($data['production_qty']) || isset($data['defect_qty'])) {
$productionQty = $data['production_qty'] ?? $workResult->production_qty;
$defectQty = $data['defect_qty'] ?? $workResult->defect_qty;
if (! isset($data['good_qty'])) {
$data['good_qty'] = max(0, $productionQty - $defectQty);
}
}
$workResult->update($data);
return $workResult->fresh([
'workOrder:id,work_order_no',
'worker:id,name',
]);
});
}
/**
* 삭제
*/
public function destroy(int $id): void
{
$tenantId = $this->tenantId();
$workResult = WorkResult::where('tenant_id', $tenantId)->find($id);
if (! $workResult) {
throw new NotFoundHttpException(__('error.not_found'));
}
$workResult->delete();
}
/**
* 검사 상태 토글
*/
public function toggleInspection(int $id)
{
$tenantId = $this->tenantId();
$userId = $this->apiUserId();
$workResult = WorkResult::where('tenant_id', $tenantId)->find($id);
if (! $workResult) {
throw new NotFoundHttpException(__('error.not_found'));
}
$workResult->update([
'is_inspected' => ! $workResult->is_inspected,
'updated_by' => $userId,
]);
return $workResult->fresh();
}
/**
* 포장 상태 토글
*/
public function togglePackaging(int $id)
{
$tenantId = $this->tenantId();
$userId = $this->apiUserId();
$workResult = WorkResult::where('tenant_id', $tenantId)->find($id);
if (! $workResult) {
throw new NotFoundHttpException(__('error.not_found'));
}
$workResult->update([
'is_packaged' => ! $workResult->is_packaged,
'updated_by' => $userId,
]);
return $workResult->fresh();
}
}