diff --git a/app/Enums/InspectionCycle.php b/app/Enums/InspectionCycle.php new file mode 100644 index 00000000..4a9e08ba --- /dev/null +++ b/app/Enums/InspectionCycle.php @@ -0,0 +1,221 @@ + '일일', + self::WEEKLY => '주간', + self::MONTHLY => '월간', + self::BIMONTHLY => '2개월', + self::QUARTERLY => '분기', + self::SEMIANNUAL => '반년', + ]; + } + + /** + * 주기별 라벨 반환 + */ + public static function label(string $cycle): string + { + return self::all()[$cycle] ?? $cycle; + } + + /** + * 주기별 기간 필터 타입 반환 + */ + public static function periodType(string $cycle): string + { + return $cycle === self::DAILY ? 'month' : 'year'; + } + + /** + * 주기별 그리드 열 라벨 반환 + * + * @return array [colIndex => label] + */ + public static function columnLabels(string $cycle, ?string $period = null): array + { + return match ($cycle) { + self::DAILY => self::dailyLabels($period), + self::WEEKLY => self::weeklyLabels(), + self::MONTHLY => self::monthlyLabels(), + self::BIMONTHLY => self::bimonthlyLabels(), + self::QUARTERLY => self::quarterlyLabels(), + self::SEMIANNUAL => self::semiannualLabels(), + default => self::dailyLabels($period), + }; + } + + /** + * 열 인덱스 → check_date 변환 + */ + public static function resolveCheckDate(string $cycle, string $period, int $colIndex): string + { + return match ($cycle) { + self::DAILY => self::dailyCheckDate($period, $colIndex), + self::WEEKLY => self::weeklyCheckDate($period, $colIndex), + self::MONTHLY => self::monthlyCheckDate($period, $colIndex), + self::BIMONTHLY => self::bimonthlyCheckDate($period, $colIndex), + self::QUARTERLY => self::quarterlyCheckDate($period, $colIndex), + self::SEMIANNUAL => self::semiannualCheckDate($period, $colIndex), + default => self::dailyCheckDate($period, $colIndex), + }; + } + + /** + * check_date → year_month(period) 역산 + */ + public static function resolvePeriod(string $cycle, string $checkDate): string + { + $date = Carbon::parse($checkDate); + + return match ($cycle) { + self::DAILY => $date->format('Y-m'), + default => $date->format('Y'), + }; + } + + /** + * 주기별 그리드 열 수 + */ + public static function columnCount(string $cycle, ?string $period = null): int + { + return count(self::columnLabels($cycle, $period)); + } + + /** + * 주말 여부 (daily 전용) + */ + public static function isWeekend(string $period, int $colIndex): bool + { + $date = Carbon::createFromFormat('Y-m', $period)->startOfMonth()->addDays($colIndex - 1); + + return in_array($date->dayOfWeek, [0, 6]); + } + + // --- Daily --- + private static function dailyLabels(?string $period): array + { + $date = Carbon::createFromFormat('Y-m', $period ?? now()->format('Y-m')); + $days = $date->daysInMonth; + $labels = []; + for ($d = 1; $d <= $days; $d++) { + $labels[$d] = (string) $d; + } + + return $labels; + } + + private static function dailyCheckDate(string $period, int $colIndex): string + { + return Carbon::createFromFormat('Y-m', $period)->startOfMonth()->addDays($colIndex - 1)->format('Y-m-d'); + } + + // --- Weekly --- + private static function weeklyLabels(): array + { + $labels = []; + for ($w = 1; $w <= 52; $w++) { + $labels[$w] = $w.'주'; + } + + return $labels; + } + + private static function weeklyCheckDate(string $year, int $colIndex): string + { + // ISO 주차의 월요일 + return Carbon::create((int) $year)->setISODate((int) $year, $colIndex, 1)->format('Y-m-d'); + } + + // --- Monthly --- + private static function monthlyLabels(): array + { + $labels = []; + for ($m = 1; $m <= 12; $m++) { + $labels[$m] = $m.'월'; + } + + return $labels; + } + + private static function monthlyCheckDate(string $year, int $colIndex): string + { + return Carbon::create((int) $year, $colIndex, 1)->format('Y-m-d'); + } + + // --- Bimonthly --- + private static function bimonthlyLabels(): array + { + return [ + 1 => '1~2월', + 2 => '3~4월', + 3 => '5~6월', + 4 => '7~8월', + 5 => '9~10월', + 6 => '11~12월', + ]; + } + + private static function bimonthlyCheckDate(string $year, int $colIndex): string + { + $month = ($colIndex - 1) * 2 + 1; + + return Carbon::create((int) $year, $month, 1)->format('Y-m-d'); + } + + // --- Quarterly --- + private static function quarterlyLabels(): array + { + return [ + 1 => '1분기', + 2 => '2분기', + 3 => '3분기', + 4 => '4분기', + ]; + } + + private static function quarterlyCheckDate(string $year, int $colIndex): string + { + $month = ($colIndex - 1) * 3 + 1; + + return Carbon::create((int) $year, $month, 1)->format('Y-m-d'); + } + + // --- Semiannual --- + private static function semiannualLabels(): array + { + return [ + 1 => '상반기', + 2 => '하반기', + ]; + } + + private static function semiannualCheckDate(string $year, int $colIndex): string + { + $month = $colIndex === 1 ? 1 : 7; + + return Carbon::create((int) $year, $month, 1)->format('Y-m-d'); + } +} diff --git a/app/Http/Controllers/Api/Admin/EquipmentInspectionController.php b/app/Http/Controllers/Api/Admin/EquipmentInspectionController.php index 6e831c7d..9892db81 100644 --- a/app/Http/Controllers/Api/Admin/EquipmentInspectionController.php +++ b/app/Http/Controllers/Api/Admin/EquipmentInspectionController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Api\Admin; +use App\Enums\InspectionCycle; use App\Http\Controllers\Controller; use App\Services\EquipmentInspectionService; use Illuminate\Http\JsonResponse; @@ -15,12 +16,20 @@ public function __construct( public function index(Request $request) { - $yearMonth = $request->input('year_month', now()->format('Y-m')); + $cycle = $request->input('cycle', InspectionCycle::DAILY); + $period = $request->input('period') ?? $request->input('year_month', now()->format('Y-m')); + + // daily가 아닌 주기에서 period가 없으면 현재 연도 + if ($cycle !== InspectionCycle::DAILY && ! $request->input('period')) { + $period = now()->format('Y'); + } + $productionLine = $request->input('production_line'); $equipmentId = $request->input('equipment_id'); - $inspections = $this->inspectionService->getMonthlyInspections( - $yearMonth, + $inspections = $this->inspectionService->getInspections( + $cycle, + $period, $productionLine, $equipmentId ? (int) $equipmentId : null ); @@ -28,7 +37,8 @@ public function index(Request $request) if ($request->header('HX-Request')) { return view('equipment.partials.inspection-grid', [ 'inspections' => $inspections, - 'yearMonth' => $yearMonth, + 'cycle' => $cycle, + 'period' => $period, ]); } @@ -44,13 +54,15 @@ public function toggleDetail(Request $request): JsonResponse 'equipment_id' => 'required|integer', 'template_item_id' => 'required|integer', 'check_date' => 'required|date', + 'cycle' => 'nullable|string', ]); try { $result = $this->inspectionService->toggleDetail( $request->input('equipment_id'), $request->input('template_item_id'), - $request->input('check_date') + $request->input('check_date'), + $request->input('cycle', InspectionCycle::DAILY) ); return response()->json([ @@ -58,10 +70,12 @@ public function toggleDetail(Request $request): JsonResponse 'data' => $result, ]); } catch (\Exception $e) { + $status = $e->getMessage() === '점검 권한이 없습니다.' ? 403 : 400; + return response()->json([ 'success' => false, 'message' => $e->getMessage(), - ], 400); + ], $status); } } @@ -70,6 +84,7 @@ public function updateNotes(Request $request): JsonResponse $request->validate([ 'equipment_id' => 'required|integer', 'year_month' => 'required|string', + 'cycle' => 'nullable|string', 'overall_judgment' => 'nullable|in:OK,NG', 'repair_note' => 'nullable|string', 'issue_note' => 'nullable|string', @@ -80,7 +95,8 @@ public function updateNotes(Request $request): JsonResponse $inspection = $this->inspectionService->updateInspectionNotes( $request->input('equipment_id'), $request->input('year_month'), - $request->only(['overall_judgment', 'repair_note', 'issue_note', 'inspector_id']) + $request->only(['overall_judgment', 'repair_note', 'issue_note', 'inspector_id']), + $request->input('cycle', InspectionCycle::DAILY) ); return response()->json([ @@ -100,6 +116,7 @@ public function storeTemplate(Request $request, int $equipmentId): JsonResponse { $request->validate([ 'item_no' => 'required|integer', + 'inspection_cycle' => 'nullable|string', 'check_point' => 'required|string|max:50', 'check_item' => 'required|string|max:100', 'check_timing' => 'nullable|in:operating,stopped', @@ -109,7 +126,12 @@ public function storeTemplate(Request $request, int $equipmentId): JsonResponse ]); try { - $template = $this->inspectionService->saveTemplate($equipmentId, $request->all()); + $data = $request->all(); + if (empty($data['inspection_cycle'])) { + $data['inspection_cycle'] = InspectionCycle::DAILY; + } + + $template = $this->inspectionService->saveTemplate($equipmentId, $data); return response()->json([ 'success' => true, diff --git a/app/Http/Controllers/EquipmentController.php b/app/Http/Controllers/EquipmentController.php index c2221c63..4d81a2de 100644 --- a/app/Http/Controllers/EquipmentController.php +++ b/app/Http/Controllers/EquipmentController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use App\Enums\InspectionCycle; use App\Services\EquipmentInspectionService; use App\Services\EquipmentRepairService; use App\Services\EquipmentService; @@ -72,8 +73,9 @@ public function inspections(Request $request): View|Response } $equipmentList = $this->equipmentService->getEquipmentList(); + $cycles = InspectionCycle::all(); - return view('equipment.inspections.index', compact('equipmentList')); + return view('equipment.inspections.index', compact('equipmentList', 'cycles')); } public function repairs(Request $request): View|Response diff --git a/app/Models/Equipment/Equipment.php b/app/Models/Equipment/Equipment.php index 82210d16..8e58ad7d 100644 --- a/app/Models/Equipment/Equipment.php +++ b/app/Models/Equipment/Equipment.php @@ -34,6 +34,7 @@ class Equipment extends Model 'status', 'disposed_date', 'manager_id', + 'sub_manager_id', 'photo_path', 'memo', 'is_active', @@ -56,6 +57,24 @@ public function manager(): BelongsTo return $this->belongsTo(\App\Models\User::class, 'manager_id'); } + public function subManager(): BelongsTo + { + return $this->belongsTo(\App\Models\User::class, 'sub_manager_id'); + } + + /** + * 해당 유저가 이 설비의 점검 권한이 있는지 확인 + */ + public function canInspect(?int $userId = null): bool + { + $userId = $userId ?? auth()->id(); + if (auth()->user()?->isAdmin()) { + return true; + } + + return $this->manager_id === $userId || $this->sub_manager_id === $userId; + } + public function inspectionTemplates(): HasMany { return $this->hasMany(EquipmentInspectionTemplate::class, 'equipment_id')->orderBy('sort_order'); diff --git a/app/Models/Equipment/EquipmentInspection.php b/app/Models/Equipment/EquipmentInspection.php index b524c3e7..eb71464d 100644 --- a/app/Models/Equipment/EquipmentInspection.php +++ b/app/Models/Equipment/EquipmentInspection.php @@ -14,6 +14,7 @@ class EquipmentInspection extends Model protected $fillable = [ 'tenant_id', 'equipment_id', + 'inspection_cycle', 'year_month', 'overall_judgment', 'inspector_id', diff --git a/app/Models/Equipment/EquipmentInspectionTemplate.php b/app/Models/Equipment/EquipmentInspectionTemplate.php index dd9393f4..e6ce8fe7 100644 --- a/app/Models/Equipment/EquipmentInspectionTemplate.php +++ b/app/Models/Equipment/EquipmentInspectionTemplate.php @@ -13,6 +13,7 @@ class EquipmentInspectionTemplate extends Model protected $fillable = [ 'tenant_id', 'equipment_id', + 'inspection_cycle', 'item_no', 'check_point', 'check_item', @@ -37,6 +38,11 @@ public function scopeActive($query) return $query->where('is_active', true); } + public function scopeByCycle($query, string $cycle) + { + return $query->where('inspection_cycle', $cycle); + } + public function getTimingLabelAttribute(): string { return match ($this->check_timing) { diff --git a/app/Services/EquipmentInspectionService.php b/app/Services/EquipmentInspectionService.php index 3a77aa0b..6d9869b8 100644 --- a/app/Services/EquipmentInspectionService.php +++ b/app/Services/EquipmentInspectionService.php @@ -2,19 +2,25 @@ namespace App\Services; +use App\Enums\InspectionCycle; use App\Models\Equipment\Equipment; use App\Models\Equipment\EquipmentInspection; use App\Models\Equipment\EquipmentInspectionDetail; use App\Models\Equipment\EquipmentInspectionTemplate; -use Carbon\Carbon; class EquipmentInspectionService { - public function getMonthlyInspections(string $yearMonth, ?string $productionLine = null, ?int $equipmentId = null): array + /** + * 주기별 점검 데이터 조회 + * + * @param string $cycle 점검주기 (daily/weekly/monthly/bimonthly/quarterly/semiannual) + * @param string $period 기간 (daily: 2026-02, 그 외: 2026) + */ + public function getInspections(string $cycle, string $period, ?string $productionLine = null, ?int $equipmentId = null): array { - $tenantId = session('selected_tenant_id', 1); - - $equipmentQuery = Equipment::where('is_active', true)->where('status', '!=', 'disposed'); + $equipmentQuery = Equipment::where('is_active', true) + ->where('status', '!=', 'disposed') + ->with(['manager', 'subManager']); if ($productionLine) { $equipmentQuery->where('production_line', $productionLine); @@ -26,13 +32,13 @@ public function getMonthlyInspections(string $yearMonth, ?string $productionLine $equipments = $equipmentQuery->orderBy('sort_order')->orderBy('name')->get(); - $date = Carbon::createFromFormat('Y-m', $yearMonth); - $daysInMonth = $date->daysInMonth; + $labels = InspectionCycle::columnLabels($cycle, $period); $result = []; foreach ($equipments as $equipment) { $templates = EquipmentInspectionTemplate::where('equipment_id', $equipment->id) + ->where('inspection_cycle', $cycle) ->where('is_active', true) ->orderBy('sort_order') ->get(); @@ -42,7 +48,8 @@ public function getMonthlyInspections(string $yearMonth, ?string $productionLine } $inspection = EquipmentInspection::where('equipment_id', $equipment->id) - ->where('year_month', $yearMonth) + ->where('inspection_cycle', $cycle) + ->where('year_month', $period) ->first(); $details = []; @@ -59,23 +66,41 @@ public function getMonthlyInspections(string $yearMonth, ?string $productionLine 'templates' => $templates, 'inspection' => $inspection, 'details' => $details, - 'days_in_month' => $daysInMonth, + 'labels' => $labels, + 'can_inspect' => $equipment->canInspect(), ]; } return $result; } - public function toggleDetail(int $equipmentId, int $templateItemId, string $checkDate): array + /** + * 하위호환: 기존 getMonthlyInspections 유지 + */ + public function getMonthlyInspections(string $yearMonth, ?string $productionLine = null, ?int $equipmentId = null): array { + return $this->getInspections(InspectionCycle::DAILY, $yearMonth, $productionLine, $equipmentId); + } + + /** + * 셀 토글 (점검 결과 순환) + */ + public function toggleDetail(int $equipmentId, int $templateItemId, string $checkDate, string $cycle = 'daily'): array + { + $equipment = Equipment::findOrFail($equipmentId); + if (! $equipment->canInspect()) { + throw new \Exception('점검 권한이 없습니다.'); + } + $tenantId = session('selected_tenant_id', 1); - $yearMonth = Carbon::parse($checkDate)->format('Y-m'); + $period = InspectionCycle::resolvePeriod($cycle, $checkDate); $inspection = EquipmentInspection::firstOrCreate( [ 'tenant_id' => $tenantId, 'equipment_id' => $equipmentId, - 'year_month' => $yearMonth, + 'inspection_cycle' => $cycle, + 'year_month' => $period, ], [ 'created_by' => auth()->id(), @@ -112,7 +137,7 @@ public function toggleDetail(int $equipmentId, int $templateItemId, string $chec ]; } - public function updateInspectionNotes(int $equipmentId, string $yearMonth, array $data): EquipmentInspection + public function updateInspectionNotes(int $equipmentId, string $yearMonth, array $data, string $cycle = 'daily'): EquipmentInspection { $tenantId = session('selected_tenant_id', 1); @@ -120,6 +145,7 @@ public function updateInspectionNotes(int $equipmentId, string $yearMonth, array [ 'tenant_id' => $tenantId, 'equipment_id' => $equipmentId, + 'inspection_cycle' => $cycle, 'year_month' => $yearMonth, ], [ @@ -134,8 +160,6 @@ public function updateInspectionNotes(int $equipmentId, string $yearMonth, array public function getMonthlyStats(string $yearMonth): array { - $tenantId = session('selected_tenant_id', 1); - $totalEquipments = Equipment::where('is_active', true) ->where('status', '!=', 'disposed') ->count(); @@ -175,4 +199,16 @@ public function deleteTemplate(int $id): bool { return EquipmentInspectionTemplate::findOrFail($id)->delete(); } + + /** + * 설비에 등록된 점검주기 목록 반환 (항목이 있는 주기만) + */ + public function getActiveCycles(int $equipmentId): array + { + return EquipmentInspectionTemplate::where('equipment_id', $equipmentId) + ->where('is_active', true) + ->distinct() + ->pluck('inspection_cycle') + ->toArray(); + } } diff --git a/app/Services/EquipmentService.php b/app/Services/EquipmentService.php index e90fb2d1..02764107 100644 --- a/app/Services/EquipmentService.php +++ b/app/Services/EquipmentService.php @@ -9,7 +9,7 @@ class EquipmentService { public function getEquipments(array $filters = [], int $perPage = 20): LengthAwarePaginator { - $query = Equipment::query()->with('manager'); + $query = Equipment::query()->with(['manager', 'subManager']); if (! empty($filters['search'])) { $search = $filters['search']; @@ -40,7 +40,7 @@ public function getEquipments(array $filters = [], int $perPage = 20): LengthAwa public function getEquipmentById(int $id): ?Equipment { - return Equipment::with(['manager', 'inspectionTemplates', 'repairs', 'processes', 'photos'])->find($id); + return Equipment::with(['manager', 'subManager', 'inspectionTemplates', 'repairs', 'processes', 'photos'])->find($id); } public function createEquipment(array $data): Equipment diff --git a/resources/views/equipment/create.blade.php b/resources/views/equipment/create.blade.php index 96f8bc5a..e9da036f 100644 --- a/resources/views/equipment/create.blade.php +++ b/resources/views/equipment/create.blade.php @@ -125,7 +125,7 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none foc

담당자 / 비고

- +
+
+ + +
@@ -142,6 +142,16 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none foc @endforeach
+
+ + +
+ + +
-
+ + + +