- 급여 관리 API 추가 (SalaryController, SalaryService, Salary 모델) - 급여 목록/상세/등록/수정/삭제 - 상태 변경 (scheduled/completed) - 일괄 상태 변경 - 통계 조회 - 더미 시더 확장 - 근태, 휴가, 부서, 사용자 시더 추가 - 기존 시더 tenant_id/created_by/updated_by 필드 추가 - 부서 서비스 개선 (tree 조회 기능 추가) - 카드 서비스 수정 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
277 lines
8.9 KiB
PHP
277 lines
8.9 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,employee_number', 'employee.department', 'employee.position', 'employee.rank']);
|
|
|
|
// 검색 필터 (직원명)
|
|
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,employee_number', 'employee.department', 'employee.position', 'employee.rank'])
|
|
->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,employee_number', 'employee.department', 'employee.position', 'employee.rank']);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 급여 삭제
|
|
*/
|
|
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,employee_number', 'employee.department', 'employee.position', 'employee.rank']);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 급여 일괄 상태 변경
|
|
*/
|
|
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(),
|
|
];
|
|
}
|
|
} |