563 lines
20 KiB
PHP
563 lines
20 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
namespace App\Services;
|
||
|
|
|
||
|
|
use App\Models\Tenants\Payroll;
|
||
|
|
use App\Models\Tenants\PayrollSetting;
|
||
|
|
use App\Models\Tenants\Withdrawal;
|
||
|
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||
|
|
use Illuminate\Database\Eloquent\Collection;
|
||
|
|
use Illuminate\Support\Facades\DB;
|
||
|
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||
|
|
|
||
|
|
class PayrollService extends Service
|
||
|
|
{
|
||
|
|
// =========================================================================
|
||
|
|
// 급여 목록/상세
|
||
|
|
// =========================================================================
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 급여 목록
|
||
|
|
*/
|
||
|
|
public function index(array $params): LengthAwarePaginator
|
||
|
|
{
|
||
|
|
$tenantId = $this->tenantId();
|
||
|
|
|
||
|
|
$query = Payroll::query()
|
||
|
|
->where('tenant_id', $tenantId)
|
||
|
|
->with(['user:id,name,email', 'creator:id,name']);
|
||
|
|
|
||
|
|
// 연도 필터
|
||
|
|
if (! empty($params['year'])) {
|
||
|
|
$query->where('pay_year', $params['year']);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 월 필터
|
||
|
|
if (! empty($params['month'])) {
|
||
|
|
$query->where('pay_month', $params['month']);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 사용자 필터
|
||
|
|
if (! empty($params['user_id'])) {
|
||
|
|
$query->where('user_id', $params['user_id']);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 상태 필터
|
||
|
|
if (! empty($params['status'])) {
|
||
|
|
$query->where('status', $params['status']);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 검색 (사용자명)
|
||
|
|
if (! empty($params['search'])) {
|
||
|
|
$query->whereHas('user', function ($q) use ($params) {
|
||
|
|
$q->where('name', 'like', "%{$params['search']}%");
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// 정렬
|
||
|
|
$sortBy = $params['sort_by'] ?? 'pay_year';
|
||
|
|
$sortDir = $params['sort_dir'] ?? 'desc';
|
||
|
|
|
||
|
|
if ($sortBy === 'period') {
|
||
|
|
$query->orderBy('pay_year', $sortDir)->orderBy('pay_month', $sortDir);
|
||
|
|
} else {
|
||
|
|
$query->orderBy($sortBy, $sortDir);
|
||
|
|
}
|
||
|
|
|
||
|
|
$perPage = $params['per_page'] ?? 20;
|
||
|
|
|
||
|
|
return $query->paginate($perPage);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 특정 연월 급여 요약
|
||
|
|
*/
|
||
|
|
public function summary(int $year, int $month): array
|
||
|
|
{
|
||
|
|
$tenantId = $this->tenantId();
|
||
|
|
|
||
|
|
$stats = Payroll::query()
|
||
|
|
->where('tenant_id', $tenantId)
|
||
|
|
->where('pay_year', $year)
|
||
|
|
->where('pay_month', $month)
|
||
|
|
->selectRaw('
|
||
|
|
COUNT(*) as total_count,
|
||
|
|
SUM(CASE WHEN status = ? THEN 1 ELSE 0 END) as draft_count,
|
||
|
|
SUM(CASE WHEN status = ? THEN 1 ELSE 0 END) as confirmed_count,
|
||
|
|
SUM(CASE WHEN status = ? THEN 1 ELSE 0 END) as paid_count,
|
||
|
|
SUM(gross_salary) as total_gross,
|
||
|
|
SUM(total_deductions) as total_deductions,
|
||
|
|
SUM(net_salary) as total_net
|
||
|
|
', [Payroll::STATUS_DRAFT, Payroll::STATUS_CONFIRMED, Payroll::STATUS_PAID])
|
||
|
|
->first();
|
||
|
|
|
||
|
|
return [
|
||
|
|
'year' => $year,
|
||
|
|
'month' => $month,
|
||
|
|
'total_count' => (int) $stats->total_count,
|
||
|
|
'draft_count' => (int) $stats->draft_count,
|
||
|
|
'confirmed_count' => (int) $stats->confirmed_count,
|
||
|
|
'paid_count' => (int) $stats->paid_count,
|
||
|
|
'total_gross' => (float) $stats->total_gross,
|
||
|
|
'total_deductions' => (float) $stats->total_deductions,
|
||
|
|
'total_net' => (float) $stats->total_net,
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 급여 상세
|
||
|
|
*/
|
||
|
|
public function show(int $id): Payroll
|
||
|
|
{
|
||
|
|
$tenantId = $this->tenantId();
|
||
|
|
|
||
|
|
return Payroll::query()
|
||
|
|
->where('tenant_id', $tenantId)
|
||
|
|
->with([
|
||
|
|
'user:id,name,email',
|
||
|
|
'confirmer:id,name',
|
||
|
|
'withdrawal',
|
||
|
|
'creator:id,name',
|
||
|
|
])
|
||
|
|
->findOrFail($id);
|
||
|
|
}
|
||
|
|
|
||
|
|
// =========================================================================
|
||
|
|
// 급여 생성/수정/삭제
|
||
|
|
// =========================================================================
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 급여 생성
|
||
|
|
*/
|
||
|
|
public function store(array $data): Payroll
|
||
|
|
{
|
||
|
|
$tenantId = $this->tenantId();
|
||
|
|
$userId = $this->apiUserId();
|
||
|
|
|
||
|
|
// 중복 확인
|
||
|
|
$exists = Payroll::query()
|
||
|
|
->where('tenant_id', $tenantId)
|
||
|
|
->where('user_id', $data['user_id'])
|
||
|
|
->where('pay_year', $data['pay_year'])
|
||
|
|
->where('pay_month', $data['pay_month'])
|
||
|
|
->exists();
|
||
|
|
|
||
|
|
if ($exists) {
|
||
|
|
throw new BadRequestHttpException(__('error.payroll.already_exists'));
|
||
|
|
}
|
||
|
|
|
||
|
|
// 금액 계산
|
||
|
|
$grossSalary = $this->calculateGross($data);
|
||
|
|
$totalDeductions = $this->calculateDeductions($data);
|
||
|
|
$netSalary = $grossSalary - $totalDeductions;
|
||
|
|
|
||
|
|
return Payroll::create([
|
||
|
|
'tenant_id' => $tenantId,
|
||
|
|
'user_id' => $data['user_id'],
|
||
|
|
'pay_year' => $data['pay_year'],
|
||
|
|
'pay_month' => $data['pay_month'],
|
||
|
|
'base_salary' => $data['base_salary'] ?? 0,
|
||
|
|
'overtime_pay' => $data['overtime_pay'] ?? 0,
|
||
|
|
'bonus' => $data['bonus'] ?? 0,
|
||
|
|
'allowances' => $data['allowances'] ?? null,
|
||
|
|
'gross_salary' => $grossSalary,
|
||
|
|
'income_tax' => $data['income_tax'] ?? 0,
|
||
|
|
'resident_tax' => $data['resident_tax'] ?? 0,
|
||
|
|
'health_insurance' => $data['health_insurance'] ?? 0,
|
||
|
|
'pension' => $data['pension'] ?? 0,
|
||
|
|
'employment_insurance' => $data['employment_insurance'] ?? 0,
|
||
|
|
'deductions' => $data['deductions'] ?? null,
|
||
|
|
'total_deductions' => $totalDeductions,
|
||
|
|
'net_salary' => $netSalary,
|
||
|
|
'status' => Payroll::STATUS_DRAFT,
|
||
|
|
'note' => $data['note'] ?? null,
|
||
|
|
'created_by' => $userId,
|
||
|
|
'updated_by' => $userId,
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 급여 수정
|
||
|
|
*/
|
||
|
|
public function update(int $id, array $data): Payroll
|
||
|
|
{
|
||
|
|
$tenantId = $this->tenantId();
|
||
|
|
$userId = $this->apiUserId();
|
||
|
|
|
||
|
|
$payroll = Payroll::query()
|
||
|
|
->where('tenant_id', $tenantId)
|
||
|
|
->findOrFail($id);
|
||
|
|
|
||
|
|
if (! $payroll->isEditable()) {
|
||
|
|
throw new BadRequestHttpException(__('error.payroll.not_editable'));
|
||
|
|
}
|
||
|
|
|
||
|
|
// 연월 변경 시 중복 확인
|
||
|
|
$newYear = $data['pay_year'] ?? $payroll->pay_year;
|
||
|
|
$newMonth = $data['pay_month'] ?? $payroll->pay_month;
|
||
|
|
$newUserId = $data['user_id'] ?? $payroll->user_id;
|
||
|
|
|
||
|
|
if ($newYear != $payroll->pay_year || $newMonth != $payroll->pay_month || $newUserId != $payroll->user_id) {
|
||
|
|
$exists = Payroll::query()
|
||
|
|
->where('tenant_id', $tenantId)
|
||
|
|
->where('user_id', $newUserId)
|
||
|
|
->where('pay_year', $newYear)
|
||
|
|
->where('pay_month', $newMonth)
|
||
|
|
->where('id', '!=', $id)
|
||
|
|
->exists();
|
||
|
|
|
||
|
|
if ($exists) {
|
||
|
|
throw new BadRequestHttpException(__('error.payroll.already_exists'));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 금액 업데이트
|
||
|
|
$updateData = array_merge($payroll->toArray(), $data);
|
||
|
|
$grossSalary = $this->calculateGross($updateData);
|
||
|
|
$totalDeductions = $this->calculateDeductions($updateData);
|
||
|
|
$netSalary = $grossSalary - $totalDeductions;
|
||
|
|
|
||
|
|
$payroll->fill([
|
||
|
|
'user_id' => $data['user_id'] ?? $payroll->user_id,
|
||
|
|
'pay_year' => $data['pay_year'] ?? $payroll->pay_year,
|
||
|
|
'pay_month' => $data['pay_month'] ?? $payroll->pay_month,
|
||
|
|
'base_salary' => $data['base_salary'] ?? $payroll->base_salary,
|
||
|
|
'overtime_pay' => $data['overtime_pay'] ?? $payroll->overtime_pay,
|
||
|
|
'bonus' => $data['bonus'] ?? $payroll->bonus,
|
||
|
|
'allowances' => $data['allowances'] ?? $payroll->allowances,
|
||
|
|
'gross_salary' => $grossSalary,
|
||
|
|
'income_tax' => $data['income_tax'] ?? $payroll->income_tax,
|
||
|
|
'resident_tax' => $data['resident_tax'] ?? $payroll->resident_tax,
|
||
|
|
'health_insurance' => $data['health_insurance'] ?? $payroll->health_insurance,
|
||
|
|
'pension' => $data['pension'] ?? $payroll->pension,
|
||
|
|
'employment_insurance' => $data['employment_insurance'] ?? $payroll->employment_insurance,
|
||
|
|
'deductions' => $data['deductions'] ?? $payroll->deductions,
|
||
|
|
'total_deductions' => $totalDeductions,
|
||
|
|
'net_salary' => $netSalary,
|
||
|
|
'note' => $data['note'] ?? $payroll->note,
|
||
|
|
'updated_by' => $userId,
|
||
|
|
]);
|
||
|
|
|
||
|
|
$payroll->save();
|
||
|
|
|
||
|
|
return $payroll->fresh(['user:id,name,email', 'creator:id,name']);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 급여 삭제
|
||
|
|
*/
|
||
|
|
public function destroy(int $id): bool
|
||
|
|
{
|
||
|
|
$tenantId = $this->tenantId();
|
||
|
|
$userId = $this->apiUserId();
|
||
|
|
|
||
|
|
$payroll = Payroll::query()
|
||
|
|
->where('tenant_id', $tenantId)
|
||
|
|
->findOrFail($id);
|
||
|
|
|
||
|
|
if (! $payroll->isDeletable()) {
|
||
|
|
throw new BadRequestHttpException(__('error.payroll.not_deletable'));
|
||
|
|
}
|
||
|
|
|
||
|
|
$payroll->deleted_by = $userId;
|
||
|
|
$payroll->save();
|
||
|
|
$payroll->delete();
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// =========================================================================
|
||
|
|
// 급여 확정/지급
|
||
|
|
// =========================================================================
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 급여 확정
|
||
|
|
*/
|
||
|
|
public function confirm(int $id): Payroll
|
||
|
|
{
|
||
|
|
$tenantId = $this->tenantId();
|
||
|
|
$userId = $this->apiUserId();
|
||
|
|
|
||
|
|
$payroll = Payroll::query()
|
||
|
|
->where('tenant_id', $tenantId)
|
||
|
|
->findOrFail($id);
|
||
|
|
|
||
|
|
if (! $payroll->isConfirmable()) {
|
||
|
|
throw new BadRequestHttpException(__('error.payroll.not_confirmable'));
|
||
|
|
}
|
||
|
|
|
||
|
|
$payroll->status = Payroll::STATUS_CONFIRMED;
|
||
|
|
$payroll->confirmed_at = now();
|
||
|
|
$payroll->confirmed_by = $userId;
|
||
|
|
$payroll->updated_by = $userId;
|
||
|
|
$payroll->save();
|
||
|
|
|
||
|
|
return $payroll->fresh(['user:id,name,email', 'confirmer:id,name']);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 급여 지급 처리
|
||
|
|
*/
|
||
|
|
public function pay(int $id, ?int $withdrawalId = null): Payroll
|
||
|
|
{
|
||
|
|
$tenantId = $this->tenantId();
|
||
|
|
$userId = $this->apiUserId();
|
||
|
|
|
||
|
|
return DB::transaction(function () use ($id, $withdrawalId, $tenantId, $userId) {
|
||
|
|
$payroll = Payroll::query()
|
||
|
|
->where('tenant_id', $tenantId)
|
||
|
|
->findOrFail($id);
|
||
|
|
|
||
|
|
if (! $payroll->isPayable()) {
|
||
|
|
throw new BadRequestHttpException(__('error.payroll.not_payable'));
|
||
|
|
}
|
||
|
|
|
||
|
|
// 출금 내역 연결 검증
|
||
|
|
if ($withdrawalId) {
|
||
|
|
$withdrawal = Withdrawal::query()
|
||
|
|
->where('tenant_id', $tenantId)
|
||
|
|
->where('id', $withdrawalId)
|
||
|
|
->first();
|
||
|
|
|
||
|
|
if (! $withdrawal) {
|
||
|
|
throw new BadRequestHttpException(__('error.payroll.invalid_withdrawal'));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$payroll->status = Payroll::STATUS_PAID;
|
||
|
|
$payroll->paid_at = now();
|
||
|
|
$payroll->withdrawal_id = $withdrawalId;
|
||
|
|
$payroll->updated_by = $userId;
|
||
|
|
$payroll->save();
|
||
|
|
|
||
|
|
return $payroll->fresh(['user:id,name,email', 'withdrawal']);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 일괄 확정
|
||
|
|
*/
|
||
|
|
public function bulkConfirm(int $year, int $month): int
|
||
|
|
{
|
||
|
|
$tenantId = $this->tenantId();
|
||
|
|
$userId = $this->apiUserId();
|
||
|
|
|
||
|
|
return Payroll::query()
|
||
|
|
->where('tenant_id', $tenantId)
|
||
|
|
->where('pay_year', $year)
|
||
|
|
->where('pay_month', $month)
|
||
|
|
->where('status', Payroll::STATUS_DRAFT)
|
||
|
|
->update([
|
||
|
|
'status' => Payroll::STATUS_CONFIRMED,
|
||
|
|
'confirmed_at' => now(),
|
||
|
|
'confirmed_by' => $userId,
|
||
|
|
'updated_by' => $userId,
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
// =========================================================================
|
||
|
|
// 급여명세서
|
||
|
|
// =========================================================================
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 급여명세서 데이터
|
||
|
|
*/
|
||
|
|
public function payslip(int $id): array
|
||
|
|
{
|
||
|
|
$payroll = $this->show($id);
|
||
|
|
|
||
|
|
// 수당 목록
|
||
|
|
$allowances = collect($payroll->allowances ?? [])->map(function ($item) {
|
||
|
|
return [
|
||
|
|
'name' => $item['name'] ?? '',
|
||
|
|
'amount' => (float) ($item['amount'] ?? 0),
|
||
|
|
];
|
||
|
|
})->toArray();
|
||
|
|
|
||
|
|
// 공제 목록
|
||
|
|
$deductions = collect($payroll->deductions ?? [])->map(function ($item) {
|
||
|
|
return [
|
||
|
|
'name' => $item['name'] ?? '',
|
||
|
|
'amount' => (float) ($item['amount'] ?? 0),
|
||
|
|
];
|
||
|
|
})->toArray();
|
||
|
|
|
||
|
|
return [
|
||
|
|
'payroll' => $payroll,
|
||
|
|
'period' => $payroll->period_label,
|
||
|
|
'employee' => [
|
||
|
|
'id' => $payroll->user->id,
|
||
|
|
'name' => $payroll->user->name,
|
||
|
|
'email' => $payroll->user->email,
|
||
|
|
],
|
||
|
|
'earnings' => [
|
||
|
|
'base_salary' => (float) $payroll->base_salary,
|
||
|
|
'overtime_pay' => (float) $payroll->overtime_pay,
|
||
|
|
'bonus' => (float) $payroll->bonus,
|
||
|
|
'allowances' => $allowances,
|
||
|
|
'allowances_total' => (float) $payroll->allowances_total,
|
||
|
|
'gross_total' => (float) $payroll->gross_salary,
|
||
|
|
],
|
||
|
|
'deductions' => [
|
||
|
|
'income_tax' => (float) $payroll->income_tax,
|
||
|
|
'resident_tax' => (float) $payroll->resident_tax,
|
||
|
|
'health_insurance' => (float) $payroll->health_insurance,
|
||
|
|
'pension' => (float) $payroll->pension,
|
||
|
|
'employment_insurance' => (float) $payroll->employment_insurance,
|
||
|
|
'other_deductions' => $deductions,
|
||
|
|
'other_total' => (float) $payroll->deductions_total,
|
||
|
|
'total' => (float) $payroll->total_deductions,
|
||
|
|
],
|
||
|
|
'net_salary' => (float) $payroll->net_salary,
|
||
|
|
'status' => $payroll->status,
|
||
|
|
'status_label' => $payroll->status_label,
|
||
|
|
'paid_at' => $payroll->paid_at?->toIso8601String(),
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
// =========================================================================
|
||
|
|
// 급여 일괄 계산
|
||
|
|
// =========================================================================
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 급여 일괄 계산 (생성 또는 업데이트)
|
||
|
|
*/
|
||
|
|
public function calculate(int $year, int $month, ?array $userIds = null): Collection
|
||
|
|
{
|
||
|
|
$tenantId = $this->tenantId();
|
||
|
|
$userId = $this->apiUserId();
|
||
|
|
|
||
|
|
// 급여 설정 가져오기
|
||
|
|
$settings = PayrollSetting::getOrCreate($tenantId);
|
||
|
|
|
||
|
|
// 대상 사용자 조회
|
||
|
|
// TODO: 실제로는 직원 목록에서 급여 대상자를 조회해야 함
|
||
|
|
// 여기서는 기존 급여 데이터만 업데이트
|
||
|
|
|
||
|
|
return DB::transaction(function () use ($year, $month, $userIds, $tenantId, $userId, $settings) {
|
||
|
|
$query = Payroll::query()
|
||
|
|
->where('tenant_id', $tenantId)
|
||
|
|
->where('pay_year', $year)
|
||
|
|
->where('pay_month', $month)
|
||
|
|
->where('status', Payroll::STATUS_DRAFT);
|
||
|
|
|
||
|
|
if ($userIds) {
|
||
|
|
$query->whereIn('user_id', $userIds);
|
||
|
|
}
|
||
|
|
|
||
|
|
$payrolls = $query->get();
|
||
|
|
|
||
|
|
foreach ($payrolls as $payroll) {
|
||
|
|
// 4대보험 재계산
|
||
|
|
$baseSalary = (float) $payroll->base_salary;
|
||
|
|
|
||
|
|
$healthInsurance = $settings->calculateHealthInsurance($baseSalary);
|
||
|
|
$longTermCare = $settings->calculateLongTermCare($healthInsurance);
|
||
|
|
$pension = $settings->calculatePension($baseSalary);
|
||
|
|
$employmentInsurance = $settings->calculateEmploymentInsurance($baseSalary);
|
||
|
|
|
||
|
|
// 건강보험에 장기요양보험 포함
|
||
|
|
$totalHealthInsurance = $healthInsurance + $longTermCare;
|
||
|
|
|
||
|
|
$payroll->health_insurance = $totalHealthInsurance;
|
||
|
|
$payroll->pension = $pension;
|
||
|
|
$payroll->employment_insurance = $employmentInsurance;
|
||
|
|
|
||
|
|
// 주민세 재계산
|
||
|
|
$payroll->resident_tax = $settings->calculateResidentTax($payroll->income_tax);
|
||
|
|
|
||
|
|
// 총액 재계산
|
||
|
|
$payroll->total_deductions = $payroll->calculateTotalDeductions();
|
||
|
|
$payroll->net_salary = $payroll->calculateNetSalary();
|
||
|
|
$payroll->updated_by = $userId;
|
||
|
|
$payroll->save();
|
||
|
|
}
|
||
|
|
|
||
|
|
return $payrolls->fresh(['user:id,name,email']);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// =========================================================================
|
||
|
|
// 급여 설정
|
||
|
|
// =========================================================================
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 급여 설정 조회
|
||
|
|
*/
|
||
|
|
public function getSettings(): PayrollSetting
|
||
|
|
{
|
||
|
|
$tenantId = $this->tenantId();
|
||
|
|
|
||
|
|
return PayrollSetting::getOrCreate($tenantId);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 급여 설정 수정
|
||
|
|
*/
|
||
|
|
public function updateSettings(array $data): PayrollSetting
|
||
|
|
{
|
||
|
|
$tenantId = $this->tenantId();
|
||
|
|
|
||
|
|
$settings = PayrollSetting::getOrCreate($tenantId);
|
||
|
|
|
||
|
|
$settings->fill([
|
||
|
|
'income_tax_rate' => $data['income_tax_rate'] ?? $settings->income_tax_rate,
|
||
|
|
'resident_tax_rate' => $data['resident_tax_rate'] ?? $settings->resident_tax_rate,
|
||
|
|
'health_insurance_rate' => $data['health_insurance_rate'] ?? $settings->health_insurance_rate,
|
||
|
|
'long_term_care_rate' => $data['long_term_care_rate'] ?? $settings->long_term_care_rate,
|
||
|
|
'pension_rate' => $data['pension_rate'] ?? $settings->pension_rate,
|
||
|
|
'employment_insurance_rate' => $data['employment_insurance_rate'] ?? $settings->employment_insurance_rate,
|
||
|
|
'pension_max_salary' => $data['pension_max_salary'] ?? $settings->pension_max_salary,
|
||
|
|
'pension_min_salary' => $data['pension_min_salary'] ?? $settings->pension_min_salary,
|
||
|
|
'pay_day' => $data['pay_day'] ?? $settings->pay_day,
|
||
|
|
'auto_calculate' => $data['auto_calculate'] ?? $settings->auto_calculate,
|
||
|
|
'allowance_types' => $data['allowance_types'] ?? $settings->allowance_types,
|
||
|
|
'deduction_types' => $data['deduction_types'] ?? $settings->deduction_types,
|
||
|
|
]);
|
||
|
|
|
||
|
|
$settings->save();
|
||
|
|
|
||
|
|
return $settings;
|
||
|
|
}
|
||
|
|
|
||
|
|
// =========================================================================
|
||
|
|
// 헬퍼 메서드
|
||
|
|
// =========================================================================
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 총지급액 계산
|
||
|
|
*/
|
||
|
|
private function calculateGross(array $data): float
|
||
|
|
{
|
||
|
|
$baseSalary = (float) ($data['base_salary'] ?? 0);
|
||
|
|
$overtimePay = (float) ($data['overtime_pay'] ?? 0);
|
||
|
|
$bonus = (float) ($data['bonus'] ?? 0);
|
||
|
|
|
||
|
|
$allowancesTotal = 0;
|
||
|
|
if (! empty($data['allowances'])) {
|
||
|
|
$allowancesTotal = collect($data['allowances'])->sum('amount');
|
||
|
|
}
|
||
|
|
|
||
|
|
return $baseSalary + $overtimePay + $bonus + $allowancesTotal;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 총공제액 계산
|
||
|
|
*/
|
||
|
|
private function calculateDeductions(array $data): float
|
||
|
|
{
|
||
|
|
$incomeTax = (float) ($data['income_tax'] ?? 0);
|
||
|
|
$residentTax = (float) ($data['resident_tax'] ?? 0);
|
||
|
|
$healthInsurance = (float) ($data['health_insurance'] ?? 0);
|
||
|
|
$pension = (float) ($data['pension'] ?? 0);
|
||
|
|
$employmentInsurance = (float) ($data['employment_insurance'] ?? 0);
|
||
|
|
|
||
|
|
$deductionsTotal = 0;
|
||
|
|
if (! empty($data['deductions'])) {
|
||
|
|
$deductionsTotal = collect($data['deductions'])->sum('amount');
|
||
|
|
}
|
||
|
|
|
||
|
|
return $incomeTax + $residentTax + $healthInsurance + $pension + $employmentInsurance + $deductionsTotal;
|
||
|
|
}
|
||
|
|
}
|