Files
sam-api/app/Services/SalaryService.php
kent 0fef26f42a feat: HR 모델 userProfile 관계 추가 및 서비스 개선
## 모델 개선
- Leave: userProfile relation 추가
- Salary: userProfile relation 추가
- TenantUserProfile: department, position 관계 및 label accessor 추가

## 서비스 개선
- LeaveService: userProfile eager loading 추가
- SalaryService: 사원 정보 조회 개선
- CardService: 관계 정리 및 개선
- AttendanceService: 조회 기능 개선

## 시더
- DummySalarySeeder 추가
- DummyCardSeeder 멀티테넌트 지원 개선
- DummyDataSeeder에 급여 시더 등록

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-26 01:32:07 +09:00

293 lines
9.3 KiB
PHP

<?php
namespace App\Services;
use App\Models\Tenants\Salary;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
class SalaryService extends Service
{
/**
* 급여 목록 조회
*/
public function index(array $params): LengthAwarePaginator
{
$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);
}
// 페이지네이션
$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(),
]);
});
}
/**
* 급여 통계 조회
*/
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(),
];
}
}