tenantId(); $query = Salary::query() ->where('tenant_id', $tenantId) ->with([ 'employee:id,name,user_id,email', 'employeeProfile' => fn ($q) => $q->where('tenant_id', $tenantId), 'employeeProfile.department:id,name', ]); // 검색 필터 (직원명) if (! empty($params['search'])) { $search = $params['search']; $query->whereHas('employee', function ($q) use ($search) { $q->where('name', 'like', "%{$search}%"); }); } // 연도 필터 if (! empty($params['year'])) { $query->where('year', $params['year']); } // 월 필터 if (! empty($params['month'])) { $query->where('month', $params['month']); } // 상태 필터 if (! empty($params['status'])) { $query->where('status', $params['status']); } // 기간 필터 if (! empty($params['start_date']) && ! empty($params['end_date'])) { $query->whereBetween('payment_date', [$params['start_date'], $params['end_date']]); } // 직원 ID 필터 if (! empty($params['employee_id'])) { $query->where('employee_id', $params['employee_id']); } // 정렬 $sortBy = $params['sort_by'] ?? 'year'; $sortDir = $params['sort_dir'] ?? 'desc'; if ($sortBy === 'year') { $query->orderBy('year', $sortDir) ->orderBy('month', $sortDir); } else { $query->orderBy($sortBy, $sortDir); } // 페이지네이션 $perPage = $params['per_page'] ?? 20; return $query->paginate($perPage); } /** * 급여 상세 조회 */ public function show(int $id): Salary { $tenantId = $this->tenantId(); return Salary::query() ->where('tenant_id', $tenantId) ->with([ 'employee:id,name,user_id,email', 'employeeProfile' => fn ($q) => $q->where('tenant_id', $tenantId), 'employeeProfile.department:id,name', ]) ->findOrFail($id); } /** * 급여 등록 */ public function store(array $data): Salary { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); return DB::transaction(function () use ($data, $tenantId, $userId) { $salary = new Salary; $salary->tenant_id = $tenantId; $salary->employee_id = $data['employee_id']; $salary->year = $data['year']; $salary->month = $data['month']; $salary->base_salary = $data['base_salary'] ?? 0; $salary->total_allowance = $data['total_allowance'] ?? 0; $salary->total_overtime = $data['total_overtime'] ?? 0; $salary->total_bonus = $data['total_bonus'] ?? 0; $salary->total_deduction = $data['total_deduction'] ?? 0; $salary->allowance_details = $data['allowance_details'] ?? null; $salary->deduction_details = $data['deduction_details'] ?? null; $salary->payment_date = $data['payment_date'] ?? null; $salary->status = $data['status'] ?? 'scheduled'; $salary->created_by = $userId; $salary->updated_by = $userId; // 실지급액 계산 $salary->net_payment = $salary->calculateNetPayment(); $salary->save(); return $salary->load('employee:id,name,email'); }); } /** * 급여 수정 */ public function update(int $id, array $data): Salary { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); return DB::transaction(function () use ($id, $data, $tenantId, $userId) { $salary = Salary::query() ->where('tenant_id', $tenantId) ->findOrFail($id); if (isset($data['employee_id'])) { $salary->employee_id = $data['employee_id']; } if (isset($data['year'])) { $salary->year = $data['year']; } if (isset($data['month'])) { $salary->month = $data['month']; } if (isset($data['base_salary'])) { $salary->base_salary = $data['base_salary']; } if (isset($data['total_allowance'])) { $salary->total_allowance = $data['total_allowance']; } if (isset($data['total_overtime'])) { $salary->total_overtime = $data['total_overtime']; } if (isset($data['total_bonus'])) { $salary->total_bonus = $data['total_bonus']; } if (isset($data['total_deduction'])) { $salary->total_deduction = $data['total_deduction']; } if (array_key_exists('allowance_details', $data)) { $salary->allowance_details = $data['allowance_details']; } if (array_key_exists('deduction_details', $data)) { $salary->deduction_details = $data['deduction_details']; } if (array_key_exists('payment_date', $data)) { $salary->payment_date = $data['payment_date']; } if (isset($data['status'])) { $salary->status = $data['status']; } // 실지급액 재계산 $salary->net_payment = $salary->calculateNetPayment(); $salary->updated_by = $userId; $salary->save(); return $salary->fresh()->load([ 'employee:id,name,user_id,email', 'employeeProfile' => fn ($q) => $q->where('tenant_id', $tenantId), 'employeeProfile.department:id,name', ]); }); } /** * 급여 삭제 */ public function destroy(int $id): bool { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); return DB::transaction(function () use ($id, $tenantId, $userId) { $salary = Salary::query() ->where('tenant_id', $tenantId) ->findOrFail($id); $salary->deleted_by = $userId; $salary->save(); $salary->delete(); return true; }); } /** * 급여 상태 변경 (지급완료/지급예정) */ public function updateStatus(int $id, string $status): Salary { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); return DB::transaction(function () use ($id, $status, $tenantId, $userId) { $salary = Salary::query() ->where('tenant_id', $tenantId) ->findOrFail($id); $salary->status = $status; $salary->updated_by = $userId; $salary->save(); return $salary->load([ 'employee:id,name,user_id,email', 'employeeProfile' => fn ($q) => $q->where('tenant_id', $tenantId), 'employeeProfile.department:id,name', ]); }); } /** * 급여 일괄 상태 변경 */ public function bulkUpdateStatus(array $ids, string $status): int { $tenantId = $this->tenantId(); $userId = $this->apiUserId(); return DB::transaction(function () use ($ids, $status, $tenantId, $userId) { return Salary::query() ->where('tenant_id', $tenantId) ->whereIn('id', $ids) ->update([ 'status' => $status, 'updated_by' => $userId, 'updated_at' => now(), ]); }); } /** * 엑셀 내보내기용 데이터 조회 * * @return array{data: array>, headings: array} */ public function getExportData(array $params): array { $tenantId = $this->tenantId(); $query = Salary::query() ->where('tenant_id', $tenantId) ->with([ 'employee:id,name,user_id,email', 'employeeProfile' => fn ($q) => $q->where('tenant_id', $tenantId), 'employeeProfile.department:id,name', ]); // 검색 필터 (직원명) if (! empty($params['search'])) { $search = $params['search']; $query->whereHas('employee', function ($q) use ($search) { $q->where('name', 'like', "%{$search}%"); }); } // 연도 필터 if (! empty($params['year'])) { $query->where('year', $params['year']); } // 월 필터 if (! empty($params['month'])) { $query->where('month', $params['month']); } // 상태 필터 if (! empty($params['status'])) { $query->where('status', $params['status']); } // 기간 필터 if (! empty($params['start_date']) && ! empty($params['end_date'])) { $query->whereBetween('payment_date', [$params['start_date'], $params['end_date']]); } // 직원 ID 필터 if (! empty($params['employee_id'])) { $query->where('employee_id', $params['employee_id']); } // 정렬 $sortBy = $params['sort_by'] ?? 'year'; $sortDir = $params['sort_dir'] ?? 'desc'; if ($sortBy === 'year') { $query->orderBy('year', $sortDir) ->orderBy('month', $sortDir); } else { $query->orderBy($sortBy, $sortDir); } $salaries = $query->get(); // 상태 레이블 매핑 $statusLabels = [ 'scheduled' => '지급예정', 'completed' => '지급완료', 'pending' => '보류', ]; // 엑셀 데이터 변환 $data = $salaries->map(function ($salary) use ($statusLabels) { return [ $salary->year.'년 '.$salary->month.'월', $salary->employee?->name ?? '-', $salary->employeeProfile?->department?->name ?? '-', number_format($salary->base_salary), number_format($salary->total_allowance), number_format($salary->total_overtime), number_format($salary->total_bonus), number_format($salary->total_deduction), number_format($salary->net_payment), $statusLabels[$salary->status] ?? $salary->status, $salary->payment_date ?? '-', ]; })->toArray(); $headings = [ '급여월', '직원명', '부서', '기본급', '수당', '야근수당', '상여금', '공제액', '실지급액', '상태', '지급일', ]; return [ 'data' => $data, 'headings' => $headings, ]; } /** * 급여 통계 조회 */ public function getStatistics(array $params): array { $tenantId = $this->tenantId(); $query = Salary::query() ->where('tenant_id', $tenantId); // 연도/월 필터 if (! empty($params['year'])) { $query->where('year', $params['year']); } if (! empty($params['month'])) { $query->where('month', $params['month']); } // 기간 필터 if (! empty($params['start_date']) && ! empty($params['end_date'])) { $query->whereBetween('payment_date', [$params['start_date'], $params['end_date']]); } return [ 'total_net_payment' => (float) $query->sum('net_payment'), 'total_base_salary' => (float) $query->sum('base_salary'), 'total_allowance' => (float) $query->sum('total_allowance'), 'total_overtime' => (float) $query->sum('total_overtime'), 'total_bonus' => (float) $query->sum('total_bonus'), 'total_deduction' => (float) $query->sum('total_deduction'), 'count' => $query->count(), 'completed_count' => (clone $query)->where('status', 'completed')->count(), 'scheduled_count' => (clone $query)->where('status', 'scheduled')->count(), ]; } }