diff --git a/app/Http/Controllers/Api/Admin/EquipmentController.php b/app/Http/Controllers/Api/Admin/EquipmentController.php new file mode 100644 index 00000000..5790b4d1 --- /dev/null +++ b/app/Http/Controllers/Api/Admin/EquipmentController.php @@ -0,0 +1,141 @@ +equipmentService->getEquipments( + $request->all(), + $request->input('per_page', 20) + ); + + if ($request->header('HX-Request')) { + return view('equipment.partials.table', compact('equipments')); + } + + return response()->json([ + 'success' => true, + 'data' => $equipments->items(), + 'meta' => [ + 'current_page' => $equipments->currentPage(), + 'total' => $equipments->total(), + 'per_page' => $equipments->perPage(), + 'last_page' => $equipments->lastPage(), + ], + ]); + } + + public function store(StoreEquipmentRequest $request): JsonResponse + { + try { + $equipment = $this->equipmentService->createEquipment($request->validated()); + + return response()->json([ + 'success' => true, + 'message' => '설비가 등록되었습니다.', + 'data' => $equipment, + ], 201); + } catch (\Exception $e) { + return response()->json([ + 'success' => false, + 'message' => $e->getMessage(), + ], 400); + } + } + + public function show(int $id): JsonResponse + { + $equipment = $this->equipmentService->getEquipmentById($id); + + if (! $equipment) { + return response()->json([ + 'success' => false, + 'message' => '설비를 찾을 수 없습니다.', + ], 404); + } + + return response()->json([ + 'success' => true, + 'data' => $equipment, + ]); + } + + public function update(UpdateEquipmentRequest $request, int $id): JsonResponse + { + try { + $equipment = $this->equipmentService->updateEquipment($id, $request->validated()); + + return response()->json([ + 'success' => true, + 'message' => '설비 정보가 수정되었습니다.', + 'data' => $equipment, + ]); + } catch (\Exception $e) { + return response()->json([ + 'success' => false, + 'message' => $e->getMessage(), + ], 400); + } + } + + public function destroy(int $id): JsonResponse + { + try { + $this->equipmentService->deleteEquipment($id); + + return response()->json([ + 'success' => true, + 'message' => '설비가 삭제되었습니다.', + ]); + } catch (\Exception $e) { + return response()->json([ + 'success' => false, + 'message' => $e->getMessage(), + ], 400); + } + } + + public function restore(int $id): JsonResponse + { + try { + $this->equipmentService->restoreEquipment($id); + + return response()->json([ + 'success' => true, + 'message' => '설비가 복원되었습니다.', + ]); + } catch (\Exception $e) { + return response()->json([ + 'success' => false, + 'message' => $e->getMessage(), + ], 400); + } + } + + public function templates(int $id): JsonResponse + { + $equipment = $this->equipmentService->getEquipmentById($id); + + if (! $equipment) { + return response()->json(['success' => false, 'message' => '설비를 찾을 수 없습니다.'], 404); + } + + return response()->json([ + 'success' => true, + 'data' => $equipment->inspectionTemplates, + ]); + } +} diff --git a/app/Http/Controllers/Api/Admin/EquipmentInspectionController.php b/app/Http/Controllers/Api/Admin/EquipmentInspectionController.php new file mode 100644 index 00000000..6e831c7d --- /dev/null +++ b/app/Http/Controllers/Api/Admin/EquipmentInspectionController.php @@ -0,0 +1,161 @@ +input('year_month', now()->format('Y-m')); + $productionLine = $request->input('production_line'); + $equipmentId = $request->input('equipment_id'); + + $inspections = $this->inspectionService->getMonthlyInspections( + $yearMonth, + $productionLine, + $equipmentId ? (int) $equipmentId : null + ); + + if ($request->header('HX-Request')) { + return view('equipment.partials.inspection-grid', [ + 'inspections' => $inspections, + 'yearMonth' => $yearMonth, + ]); + } + + return response()->json([ + 'success' => true, + 'data' => $inspections, + ]); + } + + public function toggleDetail(Request $request): JsonResponse + { + $request->validate([ + 'equipment_id' => 'required|integer', + 'template_item_id' => 'required|integer', + 'check_date' => 'required|date', + ]); + + try { + $result = $this->inspectionService->toggleDetail( + $request->input('equipment_id'), + $request->input('template_item_id'), + $request->input('check_date') + ); + + return response()->json([ + 'success' => true, + 'data' => $result, + ]); + } catch (\Exception $e) { + return response()->json([ + 'success' => false, + 'message' => $e->getMessage(), + ], 400); + } + } + + public function updateNotes(Request $request): JsonResponse + { + $request->validate([ + 'equipment_id' => 'required|integer', + 'year_month' => 'required|string', + 'overall_judgment' => 'nullable|in:OK,NG', + 'repair_note' => 'nullable|string', + 'issue_note' => 'nullable|string', + 'inspector_id' => 'nullable|integer', + ]); + + try { + $inspection = $this->inspectionService->updateInspectionNotes( + $request->input('equipment_id'), + $request->input('year_month'), + $request->only(['overall_judgment', 'repair_note', 'issue_note', 'inspector_id']) + ); + + return response()->json([ + 'success' => true, + 'message' => '점검 정보가 저장되었습니다.', + 'data' => $inspection, + ]); + } catch (\Exception $e) { + return response()->json([ + 'success' => false, + 'message' => $e->getMessage(), + ], 400); + } + } + + public function storeTemplate(Request $request, int $equipmentId): JsonResponse + { + $request->validate([ + 'item_no' => 'required|integer', + 'check_point' => 'required|string|max:50', + 'check_item' => 'required|string|max:100', + 'check_timing' => 'nullable|in:operating,stopped', + 'check_frequency' => 'nullable|string|max:50', + 'check_method' => 'nullable|string', + 'sort_order' => 'nullable|integer', + ]); + + try { + $template = $this->inspectionService->saveTemplate($equipmentId, $request->all()); + + return response()->json([ + 'success' => true, + 'message' => '점검항목이 추가되었습니다.', + 'data' => $template, + ], 201); + } catch (\Exception $e) { + return response()->json([ + 'success' => false, + 'message' => $e->getMessage(), + ], 400); + } + } + + public function updateTemplate(Request $request, int $templateId): JsonResponse + { + try { + $template = $this->inspectionService->updateTemplate($templateId, $request->all()); + + return response()->json([ + 'success' => true, + 'message' => '점검항목이 수정되었습니다.', + 'data' => $template, + ]); + } catch (\Exception $e) { + return response()->json([ + 'success' => false, + 'message' => $e->getMessage(), + ], 400); + } + } + + public function deleteTemplate(int $templateId): JsonResponse + { + try { + $this->inspectionService->deleteTemplate($templateId); + + return response()->json([ + 'success' => true, + 'message' => '점검항목이 삭제되었습니다.', + ]); + } catch (\Exception $e) { + return response()->json([ + 'success' => false, + 'message' => $e->getMessage(), + ], 400); + } + } +} diff --git a/app/Http/Controllers/Api/Admin/EquipmentRepairController.php b/app/Http/Controllers/Api/Admin/EquipmentRepairController.php new file mode 100644 index 00000000..5350d4fd --- /dev/null +++ b/app/Http/Controllers/Api/Admin/EquipmentRepairController.php @@ -0,0 +1,92 @@ +repairService->getRepairs( + $request->all(), + $request->input('per_page', 20) + ); + + if ($request->header('HX-Request')) { + return view('equipment.partials.repair-table', compact('repairs')); + } + + return response()->json([ + 'success' => true, + 'data' => $repairs->items(), + 'meta' => [ + 'current_page' => $repairs->currentPage(), + 'total' => $repairs->total(), + 'per_page' => $repairs->perPage(), + 'last_page' => $repairs->lastPage(), + ], + ]); + } + + public function store(StoreEquipmentRepairRequest $request): JsonResponse + { + try { + $repair = $this->repairService->createRepair($request->validated()); + + return response()->json([ + 'success' => true, + 'message' => '수리이력이 등록되었습니다.', + 'data' => $repair, + ], 201); + } catch (\Exception $e) { + return response()->json([ + 'success' => false, + 'message' => $e->getMessage(), + ], 400); + } + } + + public function update(Request $request, int $id): JsonResponse + { + try { + $repair = $this->repairService->updateRepair($id, $request->all()); + + return response()->json([ + 'success' => true, + 'message' => '수리이력이 수정되었습니다.', + 'data' => $repair, + ]); + } catch (\Exception $e) { + return response()->json([ + 'success' => false, + 'message' => $e->getMessage(), + ], 400); + } + } + + public function destroy(int $id): JsonResponse + { + try { + $this->repairService->deleteRepair($id); + + return response()->json([ + 'success' => true, + 'message' => '수리이력이 삭제되었습니다.', + ]); + } catch (\Exception $e) { + return response()->json([ + 'success' => false, + 'message' => $e->getMessage(), + ], 400); + } + } +} diff --git a/app/Http/Requests/StoreEquipmentInspectionRequest.php b/app/Http/Requests/StoreEquipmentInspectionRequest.php new file mode 100644 index 00000000..cf6f4de2 --- /dev/null +++ b/app/Http/Requests/StoreEquipmentInspectionRequest.php @@ -0,0 +1,31 @@ + 'required|exists:equipments,id', + 'template_item_id' => 'required|exists:equipment_inspection_templates,id', + 'check_date' => 'required|date', + ]; + } + + public function attributes(): array + { + return [ + 'equipment_id' => '설비', + 'template_item_id' => '점검항목', + 'check_date' => '점검일', + ]; + } +} diff --git a/app/Http/Requests/StoreEquipmentRepairRequest.php b/app/Http/Requests/StoreEquipmentRepairRequest.php new file mode 100644 index 00000000..4fcc55cc --- /dev/null +++ b/app/Http/Requests/StoreEquipmentRepairRequest.php @@ -0,0 +1,48 @@ + 'required|exists:equipments,id', + 'repair_date' => 'required|date', + 'repair_type' => 'required|in:internal,external', + 'repair_hours' => 'nullable|numeric|min:0', + 'description' => 'nullable|string', + 'cost' => 'nullable|numeric|min:0', + 'vendor' => 'nullable|string|max:100', + 'repaired_by' => 'nullable|exists:users,id', + 'memo' => 'nullable|string', + ]; + } + + public function attributes(): array + { + return [ + 'equipment_id' => '설비', + 'repair_date' => '수리일', + 'repair_type' => '보전구분', + 'repair_hours' => '수리시간', + 'cost' => '수리비용', + ]; + } + + public function messages(): array + { + return [ + 'equipment_id.required' => '설비를 선택해주세요.', + 'repair_date.required' => '수리일은 필수입니다.', + 'repair_type.required' => '보전구분을 선택해주세요.', + ]; + } +} diff --git a/app/Http/Requests/StoreEquipmentRequest.php b/app/Http/Requests/StoreEquipmentRequest.php new file mode 100644 index 00000000..a878e71b --- /dev/null +++ b/app/Http/Requests/StoreEquipmentRequest.php @@ -0,0 +1,68 @@ + [ + 'required', 'string', 'max:20', + Rule::unique('equipments', 'equipment_code') + ->where('tenant_id', $tenantId), + ], + 'name' => 'required|string|max:100', + 'equipment_type' => 'nullable|string|max:50', + 'specification' => 'nullable|string|max:255', + 'manufacturer' => 'nullable|string|max:100', + 'model_name' => 'nullable|string|max:100', + 'serial_no' => 'nullable|string|max:100', + 'location' => 'nullable|string|max:100', + 'production_line' => 'nullable|string|max:50', + 'purchase_date' => 'nullable|date', + 'install_date' => 'nullable|date', + 'purchase_price' => 'nullable|numeric|min:0', + 'useful_life' => 'nullable|integer|min:0', + 'status' => 'nullable|in:active,idle,disposed', + 'disposed_date' => 'nullable|date', + 'manager_id' => 'nullable|exists:users,id', + 'photo_path' => 'nullable|string|max:500', + 'memo' => 'nullable|string', + 'is_active' => 'nullable|boolean', + 'sort_order' => 'nullable|integer|min:0', + ]; + } + + public function attributes(): array + { + return [ + 'equipment_code' => '설비코드', + 'name' => '설비명', + 'equipment_type' => '설비유형', + 'manufacturer' => '제조사', + 'purchase_date' => '구입일', + 'install_date' => '설치일', + 'purchase_price' => '구입가격', + ]; + } + + public function messages(): array + { + return [ + 'equipment_code.required' => '설비코드는 필수입니다.', + 'equipment_code.unique' => '이미 존재하는 설비코드입니다.', + 'name.required' => '설비명은 필수입니다.', + ]; + } +} diff --git a/app/Http/Requests/UpdateEquipmentRequest.php b/app/Http/Requests/UpdateEquipmentRequest.php new file mode 100644 index 00000000..8a75831d --- /dev/null +++ b/app/Http/Requests/UpdateEquipmentRequest.php @@ -0,0 +1,56 @@ +route('id'); + + return [ + 'equipment_code' => [ + 'required', 'string', 'max:20', + Rule::unique('equipments', 'equipment_code') + ->where('tenant_id', $tenantId) + ->ignore($id), + ], + 'name' => 'required|string|max:100', + 'equipment_type' => 'nullable|string|max:50', + 'specification' => 'nullable|string|max:255', + 'manufacturer' => 'nullable|string|max:100', + 'model_name' => 'nullable|string|max:100', + 'serial_no' => 'nullable|string|max:100', + 'location' => 'nullable|string|max:100', + 'production_line' => 'nullable|string|max:50', + 'purchase_date' => 'nullable|date', + 'install_date' => 'nullable|date', + 'purchase_price' => 'nullable|numeric|min:0', + 'useful_life' => 'nullable|integer|min:0', + 'status' => 'nullable|in:active,idle,disposed', + 'disposed_date' => 'nullable|date', + 'manager_id' => 'nullable|exists:users,id', + 'photo_path' => 'nullable|string|max:500', + 'memo' => 'nullable|string', + 'is_active' => 'nullable|boolean', + 'sort_order' => 'nullable|integer|min:0', + ]; + } + + public function attributes(): array + { + return [ + 'equipment_code' => '설비코드', + 'name' => '설비명', + ]; + } +} diff --git a/app/Models/Equipment/Equipment.php b/app/Models/Equipment/Equipment.php new file mode 100644 index 00000000..3ecd6511 --- /dev/null +++ b/app/Models/Equipment/Equipment.php @@ -0,0 +1,133 @@ + 'date', + 'install_date' => 'date', + 'disposed_date' => 'date', + 'purchase_price' => 'decimal:2', + 'is_active' => 'boolean', + ]; + + public function manager(): BelongsTo + { + return $this->belongsTo(\App\Models\User::class, 'manager_id'); + } + + public function inspectionTemplates(): HasMany + { + return $this->hasMany(EquipmentInspectionTemplate::class, 'equipment_id')->orderBy('sort_order'); + } + + public function inspections(): HasMany + { + return $this->hasMany(EquipmentInspection::class, 'equipment_id'); + } + + public function repairs(): HasMany + { + return $this->hasMany(EquipmentRepair::class, 'equipment_id'); + } + + public function processes(): BelongsToMany + { + return $this->belongsToMany(\App\Models\Process::class, 'equipment_process') + ->withPivot('is_primary') + ->withTimestamps(); + } + + public function scopeActive($query) + { + return $query->where('status', 'active'); + } + + public function scopeByLine($query, string $line) + { + return $query->where('production_line', $line); + } + + public function scopeByType($query, string $type) + { + return $query->where('equipment_type', $type); + } + + public function getStatusLabelAttribute(): string + { + return match ($this->status) { + 'active' => '가동', + 'idle' => '유휴', + 'disposed' => '폐기', + default => $this->status, + }; + } + + public function getStatusColorAttribute(): string + { + return match ($this->status) { + 'active' => 'bg-green-100 text-green-800', + 'idle' => 'bg-yellow-100 text-yellow-800', + 'disposed' => 'bg-gray-100 text-gray-800', + default => 'bg-gray-100 text-gray-800', + }; + } + + public static function getEquipmentTypes(): array + { + return ['포밍기', '미싱기', '샤링기', 'V컷팅기', '절곡기', '프레스', '드릴', '기타']; + } + + public static function getProductionLines(): array + { + return ['스라트', '스크린', '절곡', '기타']; + } + + public static function getStatuses(): array + { + return [ + 'active' => '가동', + 'idle' => '유휴', + 'disposed' => '폐기', + ]; + } +} diff --git a/app/Models/Equipment/EquipmentInspection.php b/app/Models/Equipment/EquipmentInspection.php new file mode 100644 index 00000000..b524c3e7 --- /dev/null +++ b/app/Models/Equipment/EquipmentInspection.php @@ -0,0 +1,58 @@ +belongsTo(Equipment::class, 'equipment_id'); + } + + public function inspector(): BelongsTo + { + return $this->belongsTo(\App\Models\User::class, 'inspector_id'); + } + + public function details(): HasMany + { + return $this->hasMany(EquipmentInspectionDetail::class, 'inspection_id'); + } + + public function getJudgmentLabelAttribute(): string + { + return match ($this->overall_judgment) { + 'OK' => '양호', + 'NG' => '이상', + default => '-', + }; + } + + public function getJudgmentColorAttribute(): string + { + return match ($this->overall_judgment) { + 'OK' => 'bg-green-100 text-green-800', + 'NG' => 'bg-red-100 text-red-800', + default => 'bg-gray-100 text-gray-800', + }; + } +} diff --git a/app/Models/Equipment/EquipmentInspectionDetail.php b/app/Models/Equipment/EquipmentInspectionDetail.php new file mode 100644 index 00000000..75aca5be --- /dev/null +++ b/app/Models/Equipment/EquipmentInspectionDetail.php @@ -0,0 +1,62 @@ + 'date', + ]; + + public function inspection(): BelongsTo + { + return $this->belongsTo(EquipmentInspection::class, 'inspection_id'); + } + + public function templateItem(): BelongsTo + { + return $this->belongsTo(EquipmentInspectionTemplate::class, 'template_item_id'); + } + + public function getResultSymbolAttribute(): string + { + return match ($this->result) { + 'good' => '○', + 'bad' => 'X', + 'repaired' => '△', + default => '', + }; + } + + public function getResultColorAttribute(): string + { + return match ($this->result) { + 'good' => 'text-green-600', + 'bad' => 'text-red-600', + 'repaired' => 'text-yellow-600', + default => 'text-gray-400', + }; + } + + public static function getNextResult(?string $current): ?string + { + return match ($current) { + null, '' => 'good', + 'good' => 'bad', + 'bad' => 'repaired', + 'repaired' => null, + default => 'good', + }; + } +} diff --git a/app/Models/Equipment/EquipmentInspectionTemplate.php b/app/Models/Equipment/EquipmentInspectionTemplate.php new file mode 100644 index 00000000..dd9393f4 --- /dev/null +++ b/app/Models/Equipment/EquipmentInspectionTemplate.php @@ -0,0 +1,48 @@ + 'boolean', + ]; + + public function equipment(): BelongsTo + { + return $this->belongsTo(Equipment::class, 'equipment_id'); + } + + public function scopeActive($query) + { + return $query->where('is_active', true); + } + + public function getTimingLabelAttribute(): string + { + return match ($this->check_timing) { + 'operating' => '가동 중', + 'stopped' => '정지 시', + default => $this->check_timing ?? '-', + }; + } +} diff --git a/app/Models/Equipment/EquipmentProcess.php b/app/Models/Equipment/EquipmentProcess.php new file mode 100644 index 00000000..ca8260f6 --- /dev/null +++ b/app/Models/Equipment/EquipmentProcess.php @@ -0,0 +1,31 @@ + 'boolean', + ]; + + public function equipment(): BelongsTo + { + return $this->belongsTo(Equipment::class, 'equipment_id'); + } + + public function process(): BelongsTo + { + return $this->belongsTo(\App\Models\Process::class, 'process_id'); + } +} diff --git a/app/Models/Equipment/EquipmentRepair.php b/app/Models/Equipment/EquipmentRepair.php new file mode 100644 index 00000000..3b3ffbe8 --- /dev/null +++ b/app/Models/Equipment/EquipmentRepair.php @@ -0,0 +1,62 @@ + 'date', + 'repair_hours' => 'decimal:1', + 'cost' => 'decimal:2', + ]; + + public function equipment(): BelongsTo + { + return $this->belongsTo(Equipment::class, 'equipment_id'); + } + + public function repairer(): BelongsTo + { + return $this->belongsTo(\App\Models\User::class, 'repaired_by'); + } + + public function getRepairTypeLabelAttribute(): string + { + return match ($this->repair_type) { + 'internal' => '사내', + 'external' => '외주', + default => $this->repair_type ?? '-', + }; + } + + public function getFormattedCostAttribute(): string + { + if (! $this->cost) { + return '-'; + } + + return number_format($this->cost).'원'; + } +} diff --git a/app/Services/EquipmentInspectionService.php b/app/Services/EquipmentInspectionService.php new file mode 100644 index 00000000..3a77aa0b --- /dev/null +++ b/app/Services/EquipmentInspectionService.php @@ -0,0 +1,178 @@ +where('status', '!=', 'disposed'); + + if ($productionLine) { + $equipmentQuery->where('production_line', $productionLine); + } + + if ($equipmentId) { + $equipmentQuery->where('id', $equipmentId); + } + + $equipments = $equipmentQuery->orderBy('sort_order')->orderBy('name')->get(); + + $date = Carbon::createFromFormat('Y-m', $yearMonth); + $daysInMonth = $date->daysInMonth; + + $result = []; + + foreach ($equipments as $equipment) { + $templates = EquipmentInspectionTemplate::where('equipment_id', $equipment->id) + ->where('is_active', true) + ->orderBy('sort_order') + ->get(); + + if ($templates->isEmpty()) { + continue; + } + + $inspection = EquipmentInspection::where('equipment_id', $equipment->id) + ->where('year_month', $yearMonth) + ->first(); + + $details = []; + if ($inspection) { + $details = EquipmentInspectionDetail::where('inspection_id', $inspection->id) + ->get() + ->groupBy(function ($d) { + return $d->template_item_id.'_'.$d->check_date->format('Y-m-d'); + }); + } + + $result[] = [ + 'equipment' => $equipment, + 'templates' => $templates, + 'inspection' => $inspection, + 'details' => $details, + 'days_in_month' => $daysInMonth, + ]; + } + + return $result; + } + + public function toggleDetail(int $equipmentId, int $templateItemId, string $checkDate): array + { + $tenantId = session('selected_tenant_id', 1); + $yearMonth = Carbon::parse($checkDate)->format('Y-m'); + + $inspection = EquipmentInspection::firstOrCreate( + [ + 'tenant_id' => $tenantId, + 'equipment_id' => $equipmentId, + 'year_month' => $yearMonth, + ], + [ + 'created_by' => auth()->id(), + ] + ); + + $detail = EquipmentInspectionDetail::where('inspection_id', $inspection->id) + ->where('template_item_id', $templateItemId) + ->where('check_date', $checkDate) + ->first(); + + if ($detail) { + $nextResult = EquipmentInspectionDetail::getNextResult($detail->result); + if ($nextResult === null) { + $detail->delete(); + + return ['result' => null, 'symbol' => '', 'color' => 'text-gray-400']; + } + $detail->update(['result' => $nextResult]); + } else { + $detail = EquipmentInspectionDetail::create([ + 'inspection_id' => $inspection->id, + 'template_item_id' => $templateItemId, + 'check_date' => $checkDate, + 'result' => 'good', + ]); + $nextResult = 'good'; + } + + return [ + 'result' => $nextResult, + 'symbol' => $detail->fresh()->result_symbol, + 'color' => $detail->fresh()->result_color, + ]; + } + + public function updateInspectionNotes(int $equipmentId, string $yearMonth, array $data): EquipmentInspection + { + $tenantId = session('selected_tenant_id', 1); + + $inspection = EquipmentInspection::firstOrCreate( + [ + 'tenant_id' => $tenantId, + 'equipment_id' => $equipmentId, + 'year_month' => $yearMonth, + ], + [ + 'created_by' => auth()->id(), + ] + ); + + $inspection->update(array_merge($data, ['updated_by' => auth()->id()])); + + return $inspection->fresh(); + } + + public function getMonthlyStats(string $yearMonth): array + { + $tenantId = session('selected_tenant_id', 1); + + $totalEquipments = Equipment::where('is_active', true) + ->where('status', '!=', 'disposed') + ->count(); + + $inspected = EquipmentInspection::where('year_month', $yearMonth)->count(); + + $issueCount = EquipmentInspectionDetail::whereHas('inspection', function ($q) use ($yearMonth) { + $q->where('year_month', $yearMonth); + })->where('result', 'bad')->count(); + + return [ + 'total' => $totalEquipments, + 'inspected' => $inspected, + 'issue_count' => $issueCount, + ]; + } + + public function saveTemplate(int $equipmentId, array $data): EquipmentInspectionTemplate + { + $tenantId = session('selected_tenant_id', 1); + + return EquipmentInspectionTemplate::create(array_merge($data, [ + 'tenant_id' => $tenantId, + 'equipment_id' => $equipmentId, + ])); + } + + public function updateTemplate(int $id, array $data): EquipmentInspectionTemplate + { + $template = EquipmentInspectionTemplate::findOrFail($id); + $template->update($data); + + return $template->fresh(); + } + + public function deleteTemplate(int $id): bool + { + return EquipmentInspectionTemplate::findOrFail($id)->delete(); + } +} diff --git a/app/Services/EquipmentRepairService.php b/app/Services/EquipmentRepairService.php new file mode 100644 index 00000000..0f880cc1 --- /dev/null +++ b/app/Services/EquipmentRepairService.php @@ -0,0 +1,80 @@ +with('equipment', 'repairer'); + + if (! empty($filters['equipment_id'])) { + $query->where('equipment_id', $filters['equipment_id']); + } + + if (! empty($filters['repair_type'])) { + $query->where('repair_type', $filters['repair_type']); + } + + if (! empty($filters['date_from'])) { + $query->where('repair_date', '>=', $filters['date_from']); + } + + if (! empty($filters['date_to'])) { + $query->where('repair_date', '<=', $filters['date_to']); + } + + if (! empty($filters['search'])) { + $search = $filters['search']; + $query->where(function ($q) use ($search) { + $q->where('description', 'like', "%{$search}%") + ->orWhereHas('equipment', function ($eq) use ($search) { + $eq->where('name', 'like', "%{$search}%") + ->orWhere('equipment_code', 'like', "%{$search}%"); + }); + }); + } + + return $query->orderBy('repair_date', 'desc')->paginate($perPage); + } + + public function getRepairById(int $id): ?EquipmentRepair + { + return EquipmentRepair::with('equipment', 'repairer')->find($id); + } + + public function createRepair(array $data): EquipmentRepair + { + $data['tenant_id'] = session('selected_tenant_id', 1); + $data['created_by'] = auth()->id(); + + return EquipmentRepair::create($data); + } + + public function updateRepair(int $id, array $data): EquipmentRepair + { + $repair = EquipmentRepair::findOrFail($id); + $data['updated_by'] = auth()->id(); + $repair->update($data); + + return $repair->fresh(); + } + + public function deleteRepair(int $id): bool + { + $repair = EquipmentRepair::findOrFail($id); + + return $repair->delete(); + } + + public function getRecentRepairs(int $limit = 5): \Illuminate\Database\Eloquent\Collection + { + return EquipmentRepair::with('equipment') + ->orderBy('repair_date', 'desc') + ->limit($limit) + ->get(); + } +} diff --git a/app/Services/EquipmentService.php b/app/Services/EquipmentService.php new file mode 100644 index 00000000..2c63a59a --- /dev/null +++ b/app/Services/EquipmentService.php @@ -0,0 +1,105 @@ +with('manager'); + + if (! empty($filters['search'])) { + $search = $filters['search']; + $query->where(function ($q) use ($search) { + $q->where('equipment_code', 'like', "%{$search}%") + ->orWhere('name', 'like', "%{$search}%"); + }); + } + + if (! empty($filters['status'])) { + $query->where('status', $filters['status']); + } + + if (! empty($filters['production_line'])) { + $query->where('production_line', $filters['production_line']); + } + + if (! empty($filters['equipment_type'])) { + $query->where('equipment_type', $filters['equipment_type']); + } + + $sortBy = $filters['sort_by'] ?? 'sort_order'; + $sortDir = $filters['sort_direction'] ?? 'asc'; + $query->orderBy($sortBy, $sortDir); + + return $query->paginate($perPage); + } + + public function getEquipmentById(int $id): ?Equipment + { + return Equipment::with(['manager', 'inspectionTemplates', 'repairs', 'processes'])->find($id); + } + + public function createEquipment(array $data): Equipment + { + $data['tenant_id'] = session('selected_tenant_id', 1); + $data['created_by'] = auth()->id(); + + return Equipment::create($data); + } + + public function updateEquipment(int $id, array $data): Equipment + { + $equipment = Equipment::findOrFail($id); + $data['updated_by'] = auth()->id(); + $equipment->update($data); + + return $equipment->fresh(); + } + + public function deleteEquipment(int $id): bool + { + $equipment = Equipment::findOrFail($id); + $equipment->deleted_by = auth()->id(); + $equipment->save(); + + return $equipment->delete(); + } + + public function restoreEquipment(int $id): bool + { + $equipment = Equipment::onlyTrashed()->findOrFail($id); + + return $equipment->restore(); + } + + public function getDashboardStats(): array + { + $total = Equipment::count(); + $active = Equipment::where('status', 'active')->count(); + $idle = Equipment::where('status', 'idle')->count(); + $disposed = Equipment::where('status', 'disposed')->count(); + + return compact('total', 'active', 'idle', 'disposed'); + } + + public function getTypeStats(): array + { + return Equipment::where('status', '!=', 'disposed') + ->selectRaw('equipment_type, count(*) as count') + ->groupBy('equipment_type') + ->pluck('count', 'equipment_type') + ->toArray(); + } + + public function getEquipmentList(): \Illuminate\Database\Eloquent\Collection + { + return Equipment::where('is_active', true) + ->orderBy('sort_order') + ->orderBy('name') + ->get(['id', 'equipment_code', 'name', 'equipment_type', 'production_line']); + } +} diff --git a/resources/views/equipment/create.blade.php b/resources/views/equipment/create.blade.php new file mode 100644 index 00000000..2041dd7f --- /dev/null +++ b/resources/views/equipment/create.blade.php @@ -0,0 +1,198 @@ +@extends('layouts.app') +@section('title', '설비 등록') +@section('content') + +
{{ now()->format('Y년 m월 d일') }} 기준
+데이터가 없습니다.
+ @endif +| 수리일 | +설비 | +보전구분 | +수리내용 | +비용 | +
|---|---|---|---|---|
| {{ $repair->repair_date->format('m-d') }} | +{{ $repair->equipment?->name ?? '-' }} | ++ + {{ $repair->repair_type_label }} + + | +{{ Str::limit($repair->description, 40) ?? '-' }} | +{{ $repair->formatted_cost }} | +
최근 수리이력이 없습니다.
+ @endif +점검 가능한 설비가 없습니다.
+설비 등록대장에서 점검항목을 추가해주세요.
+| 설비 | +점검항목 | + @for($d = 1; $d <= $daysInMonth; $d++) + @php + $dayDate = $date->copy()->day($d); + $dayOfWeek = $dayDate->dayOfWeek; + $isWeekend = in_array($dayOfWeek, [0, 6]); + @endphp ++ {{ $d }} + | + @endfor +판정 | +
|---|---|---|---|
|
+ {{ $equipment->equipment_code }}
+ {{ Str::limit($equipment->name, 8) }}
+ |
+ @endif
+ + {{ $tmpl->check_point }} + | + + @for($d = 1; $d <= $daysInMonth; $d++) + @php + $checkDate = $date->copy()->day($d)->format('Y-m-d'); + $key = $tmpl->id . '_' . $checkDate; + $detail = isset($details[$key]) ? $details[$key]->first() : null; + $symbol = $detail ? $detail->result_symbol : ''; + $color = $detail ? $detail->result_color : 'text-gray-400'; + $dayDate = $date->copy()->day($d); + $isWeekend = in_array($dayDate->dayOfWeek, [0, 6]); + @endphp ++ {{ $symbol }} + | + @endfor + + @if($idx === 0) ++ @if($inspection && $inspection->overall_judgment) + + {{ $inspection->judgment_label }} + + @else + - + @endif + | + @endif +
| 수리일 | +설비 | +보전구분 | +수리시간 | +수리내용 | +비용 | +외주업체 | +액션 | +
|---|---|---|---|---|---|---|---|
| + {{ $repair->repair_date->format('Y-m-d') }} + | ++ {{ $repair->equipment?->equipment_code }} + {{ $repair->equipment?->name }} + | ++ + {{ $repair->repair_type_label }} + + | ++ {{ $repair->repair_hours ? $repair->repair_hours . 'h' : '-' }} + | ++ {{ Str::limit($repair->description, 40) ?? '-' }} + | ++ {{ $repair->formatted_cost }} + | ++ {{ $repair->vendor ?? '-' }} + | ++ + | +
| + 수리이력이 없습니다. + | +|||||||
| 설비번호 | +설비명 | +유형 | +위치 | +생산라인 | +상태 | +담당자 | +구입일 | +액션 | +
|---|---|---|---|---|---|---|---|---|
| + {{ $eq->equipment_code }} + | ++ {{ $eq->name }} + | ++ {{ $eq->equipment_type ?? '-' }} + | ++ {{ $eq->location ?? '-' }} + | ++ {{ $eq->production_line ?? '-' }} + | ++ + {{ $eq->status_label }} + + | ++ {{ $eq->manager?->name ?? '-' }} + | ++ {{ $eq->purchase_date?->format('Y-m-d') ?? '-' }} + | ++ 수정 + + | +
| + 등록된 설비가 없습니다. + | +||||||||
{{ $equipment->equipment_code }}
+{{ $equipment->name }}
+{{ $equipment->equipment_type ?? '-' }}
+{{ $equipment->specification ?? '-' }}
+{{ $equipment->manufacturer ?? '-' }}
+{{ $equipment->model_name ?? '-' }}
+{{ $equipment->serial_no ?? '-' }}
+{{ $equipment->location ?? '-' }}
+{{ $equipment->production_line ?? '-' }}
+{{ $equipment->purchase_date?->format('Y-m-d') ?? '-' }}
+{{ $equipment->install_date?->format('Y-m-d') ?? '-' }}
+{{ $equipment->purchase_price ? number_format($equipment->purchase_price) . '원' : '-' }}
+{{ $equipment->useful_life ? $equipment->useful_life . '년' : '-' }}
+{{ $equipment->manager?->name ?? '-' }}
+{{ $equipment->memo }}
+| 번호 | +점검개소 | +점검항목 | +시기 | +주기 | +점검방법 | +액션 | +
|---|---|---|---|---|---|---|
| {{ $tmpl->item_no }} | +{{ $tmpl->check_point }} | +{{ $tmpl->check_item }} | +{{ $tmpl->timing_label }} | +{{ $tmpl->check_frequency ?? '-' }} | +{{ $tmpl->check_method ?? '-' }} | ++ + | +
등록된 점검항목이 없습니다.
+ @endif +| 수리일 | +보전구분 | +수리시간 | +수리내용 | +비용 | +외주업체 | +액션 | +
|---|---|---|---|---|---|---|
| {{ $repair->repair_date->format('Y-m-d') }} | ++ + {{ $repair->repair_type_label }} + + | +{{ $repair->repair_hours ? $repair->repair_hours . 'h' : '-' }} | +{{ Str::limit($repair->description, 50) ?? '-' }} | +{{ $repair->formatted_cost }} | +{{ $repair->vendor ?? '-' }} | ++ + | +
수리이력이 없습니다.
+ @endif +{{ $equipment->equipment_code }}
+