feat: [payroll] 전월 급여 복사 등록 기능 추가

- PayrollService에 copyFromPreviousMonth() 메서드 추가
- PayrollController에 copyFromPrevious() 액션 추가
- 전월 지급/공제 금액을 그대로 복사 (요율 재계산 없음)
- 이미 존재하는 사원/연월은 스킵 처리
This commit is contained in:
김보곤
2026-02-27 17:30:06 +09:00
parent df8707776c
commit 57b58a2297
4 changed files with 147 additions and 0 deletions

View File

@@ -271,6 +271,45 @@ public function pay(Request $request, int $id): JsonResponse
}
}
/**
* 전월 급여 복사 등록
*/
public function copyFromPrevious(Request $request): JsonResponse
{
$validated = $request->validate([
'pay_year' => 'required|integer|min:2020|max:2100',
'pay_month' => 'required|integer|min:1|max:12',
]);
try {
$result = $this->payrollService->copyFromPreviousMonth($validated['pay_year'], $validated['pay_month']);
if (! empty($result['no_previous'])) {
$prevYear = $validated['pay_month'] === 1 ? $validated['pay_year'] - 1 : $validated['pay_year'];
$prevMonth = $validated['pay_month'] === 1 ? 12 : $validated['pay_month'] - 1;
return response()->json([
'success' => false,
'message' => "{$prevYear}{$prevMonth}월 급여 데이터가 없습니다.",
], 422);
}
return response()->json([
'success' => true,
'message' => "전월 복사 완료: {$result['created']}건 생성, {$result['skipped']}건 건너뜀 (이미 존재).",
'data' => $result,
]);
} catch (\Throwable $e) {
report($e);
return response()->json([
'success' => false,
'message' => '전월 복사 중 오류가 발생했습니다.',
'error' => config('app.debug') ? $e->getMessage() : null,
], 500);
}
}
/**
* 일괄 생성
*/

View File

@@ -302,6 +302,74 @@ public function payPayroll(int $id): ?Payroll
return $payroll->fresh(['user']);
}
/**
* 전월 급여 복사 등록
*/
public function copyFromPreviousMonth(int $year, int $month): array
{
$tenantId = session('selected_tenant_id', 1);
// 전월 계산 (1월 → 전년 12월)
$prevYear = $month === 1 ? $year - 1 : $year;
$prevMonth = $month === 1 ? 12 : $month - 1;
$previousPayrolls = Payroll::query()
->forTenant($tenantId)
->forPeriod($prevYear, $prevMonth)
->get();
if ($previousPayrolls->isEmpty()) {
return ['created' => 0, 'skipped' => 0, 'no_previous' => true];
}
$created = 0;
$skipped = 0;
DB::transaction(function () use ($previousPayrolls, $tenantId, $year, $month, &$created, &$skipped) {
foreach ($previousPayrolls as $prev) {
$exists = Payroll::query()
->where('tenant_id', $tenantId)
->where('user_id', $prev->user_id)
->forPeriod($year, $month)
->exists();
if ($exists) {
$skipped++;
continue;
}
Payroll::create([
'tenant_id' => $tenantId,
'user_id' => $prev->user_id,
'pay_year' => $year,
'pay_month' => $month,
'base_salary' => $prev->base_salary,
'overtime_pay' => $prev->overtime_pay,
'bonus' => $prev->bonus,
'allowances' => $prev->allowances,
'gross_salary' => $prev->gross_salary,
'income_tax' => $prev->income_tax,
'resident_tax' => $prev->resident_tax,
'health_insurance' => $prev->health_insurance,
'long_term_care' => $prev->long_term_care,
'pension' => $prev->pension,
'employment_insurance' => $prev->employment_insurance,
'deductions' => $prev->deductions,
'total_deductions' => $prev->total_deductions,
'net_salary' => $prev->net_salary,
'status' => 'draft',
'created_by' => auth()->id(),
'updated_by' => auth()->id(),
]);
$created++;
}
});
return ['created' => $created, 'skipped' => $skipped];
}
/**
* 일괄 생성 (재직 사원 전체)
*/