feat: [payroll] MNG 급여관리 계산 엔진 및 일괄 처리 API 구현

- IncomeTaxBracket 모델 추가 (2024 간이세액표 DB 조회)
- PayrollService 전면 개편: 4대보험 + 소득세 자동 계산 엔진
- 10,000천원 초과 고소득 구간 공식 계산 지원
- 과세표준 = 총지급액 - 식대(비과세), 10원 단위 절삭
- 일괄 생성(bulkGenerate), 전월 복사(copyFromPrevious) 기능
- 확정취소(unconfirm), 지급취소(unpay) 상태 관리
- 계산 미리보기(calculatePreview) 엔드포인트 추가
- 공제항목 수동 오버라이드(deduction_overrides) 지원
- Payroll 모델에 long_term_care, options 필드 추가
This commit is contained in:
김보곤
2026-03-11 19:18:27 +09:00
parent 18a6f3e7aa
commit 82621a6045
11 changed files with 850 additions and 249 deletions

View File

@@ -4,7 +4,9 @@
use App\Helpers\ApiResponse;
use App\Http\Controllers\Controller;
use App\Http\Requests\V1\Payroll\BulkGeneratePayrollRequest;
use App\Http\Requests\V1\Payroll\CalculatePayrollRequest;
use App\Http\Requests\V1\Payroll\CopyFromPreviousPayrollRequest;
use App\Http\Requests\V1\Payroll\PayPayrollRequest;
use App\Http\Requests\V1\Payroll\StorePayrollRequest;
use App\Http\Requests\V1\Payroll\UpdatePayrollRequest;
@@ -28,6 +30,7 @@ public function index(Request $request)
'month',
'user_id',
'status',
'department_id',
'search',
'sort_by',
'sort_dir',
@@ -103,6 +106,16 @@ public function confirm(int $id)
return ApiResponse::success($payroll, __('message.payroll.confirmed'));
}
/**
* 급여 확정 취소
*/
public function unconfirm(int $id)
{
$payroll = $this->service->unconfirm($id);
return ApiResponse::success($payroll, __('message.payroll.unconfirmed'));
}
/**
* 급여 지급 처리
*/
@@ -113,6 +126,16 @@ public function pay(int $id, PayPayrollRequest $request)
return ApiResponse::success($payroll, __('message.payroll.paid'));
}
/**
* 급여 지급 취소 (슈퍼관리자)
*/
public function unpay(int $id)
{
$payroll = $this->service->unpay($id);
return ApiResponse::success($payroll, __('message.payroll.unpaid'));
}
/**
* 일괄 확정
*/
@@ -127,13 +150,29 @@ public function bulkConfirm(Request $request)
}
/**
* 급여명세서 조회
* 재직사원 일괄 생성
*/
public function payslip(int $id)
public function bulkGenerate(BulkGeneratePayrollRequest $request)
{
$payslip = $this->service->payslip($id);
$year = (int) $request->input('year');
$month = (int) $request->input('month');
return ApiResponse::success($payslip, __('message.fetched'));
$result = $this->service->bulkGenerate($year, $month);
return ApiResponse::success($result, __('message.payroll.bulk_generated'));
}
/**
* 전월 급여 복사
*/
public function copyFromPrevious(CopyFromPreviousPayrollRequest $request)
{
$year = (int) $request->input('year');
$month = (int) $request->input('month');
$result = $this->service->copyFromPreviousMonth($year, $month);
return ApiResponse::success($result, __('message.payroll.copied'));
}
/**
@@ -150,6 +189,35 @@ public function calculate(CalculatePayrollRequest $request)
return ApiResponse::success($payrolls, __('message.payroll.calculated'));
}
/**
* 급여 계산 미리보기
*/
public function calculatePreview(Request $request)
{
$data = $request->only([
'user_id',
'base_salary',
'overtime_pay',
'bonus',
'allowances',
'deductions',
]);
$result = $this->service->calculatePreview($data);
return ApiResponse::success($result, __('message.calculated'));
}
/**
* 급여명세서 조회
*/
public function payslip(int $id)
{
$payslip = $this->service->payslip($id);
return ApiResponse::success($payslip, __('message.fetched'));
}
/**
* 급여 설정 조회
*/