This commit is contained in:
admin
2026-01-22 01:16:54 +09:00
parent 86020ada5c
commit 3d91356877
16 changed files with 1862 additions and 494 deletions

View File

@@ -262,7 +262,7 @@ def approve_payroll(payroll_id: int, request: schemas.PayrollApprovalRequest):
return result
@router.delete("/{payroll_id}", status_code=status.HTTP_204_NO_CONTENT)
@router.delete("/{payroll_id}")
def delete_payroll(payroll_id: int):
"""給与データを削除"""
with get_connection() as conn:
@@ -282,3 +282,5 @@ def delete_payroll(payroll_id: int):
cur.execute("DELETE FROM monthly_payroll WHERE payroll_id = %s", (payroll_id,))
conn.commit()
return {"message": "給与データを削除しました", "payroll_id": payroll_id}

View File

@@ -132,22 +132,25 @@ class PayrollCalculationService:
) -> Dict[str, Any]:
"""給与を計算"""
# バリデーションエラーを集約
errors = []
# 給与設定を取得
salary_setting = PayrollCalculationService.get_salary_setting(employee_id, payment_date)
if not salary_setting:
raise ValueError("給与設定が見つかりません")
errors.append("給与設定が見つかりません")
# 基本給の計算
base_salary = Decimal(str(salary_setting["base_salary"]))
base_salary = Decimal(str(salary_setting["base_salary"])) if salary_setting else Decimal("0")
# 時給の場合は勤務時間から計算
if salary_setting["payment_type"] == "時給" and salary_setting["hourly_rate"]:
if salary_setting and salary_setting["payment_type"] == "時給" and salary_setting["hourly_rate"]:
hourly_rate = Decimal(str(salary_setting["hourly_rate"]))
base_salary = hourly_rate * working_hours
# 欠勤控除(日給制の場合)
absence_deduction = Decimal("0")
if absent_days > 0 and salary_setting["payment_type"] == "月給":
if absent_days > 0 and salary_setting and salary_setting["payment_type"] == "月給":
# 月給を営業日数で割って欠勤日数を掛ける(簡易計算)
daily_rate = base_salary / Decimal("22") # 月平均営業日数を22日と仮定
absence_deduction = daily_rate * absent_days
@@ -156,11 +159,11 @@ class PayrollCalculationService:
# 残業手当の計算(時給 × 1.25
overtime_pay = Decimal("0")
if overtime_hours > 0:
if salary_setting["hourly_rate"]:
if salary_setting and salary_setting["hourly_rate"]:
hourly_rate = Decimal(str(salary_setting["hourly_rate"]))
else:
# 月給を時給に換算月平均労働時間を160時間と仮定
hourly_rate = Decimal(str(salary_setting["base_salary"])) / Decimal("160")
hourly_rate = (Decimal(str(salary_setting["base_salary"])) if salary_setting else Decimal("0")) / Decimal("160")
overtime_pay = hourly_rate * overtime_hours * Decimal("1.25")
# 休日手当の計算(時給 × 1.35
@@ -181,8 +184,29 @@ class PayrollCalculationService:
commute_allowance + other_allowance
)
# 社会保険料の計算(標準報酬月額を総支給額と仮定)
# 標準報酬月額表から標準報酬月額を検索
standard_monthly_salary = total_payment
with get_connection() as conn:
with conn.cursor() as cur:
# 給与年月の標準報酬月額表から、総支給額が属する等級を検索
cur.execute(
"""
SELECT monthly_amount
FROM standard_remuneration
WHERE rate_year = %s
AND salary_from <= %s
AND (salary_to IS NULL OR salary_to > %s)
ORDER BY rate_year DESC, salary_from DESC
LIMIT 1
""",
(payment_date.year, total_payment, total_payment)
)
result = cur.fetchone()
if result:
standard_monthly_salary = Decimal(str(result["monthly_amount"]))
print(f"[DEBUG] 標準報酬月額表検索: 総支給額={total_payment} → 標準報酬月額={standard_monthly_salary}")
else:
print(f"[DEBUG] 標準報酬月額表検索: 該当等級なし(総支給額={total_payment}")
# 社会保険料上限を取得
health_insurance_limit = PayrollCalculationService.get_insurance_limit(
@@ -218,22 +242,27 @@ class PayrollCalculationService:
if (payment_date.month, payment_date.day) < (birth_date.month, birth_date.day):
age -= 1
# 健康保険(上限適用後の標準報酬月額で計算)
# 健康保険(標準報酬月額表から検索した月額で計算)
health_insurance_rate = PayrollCalculationService.get_insurance_rate("健康保険", payment_date)
health_insurance = Decimal("0")
if health_insurance_rate:
health_insurance = (
health_standard_salary * Decimal(str(health_insurance_rate["employee_rate"]))
).quantize(Decimal("1"))
).quantize(Decimal("0.01"))
else:
errors.append(f"健康保険料率が見つかりません(年度: {payment_date.year}")
# 介護保険40歳以上65歳未満が対象、健康保険と同じ標準報酬月額)
# 介護保険40歳以上65歳未満が対象、標準報酬月額表から検索した月額で計算
care_insurance = Decimal("0")
if 40 <= age < 65:
care_insurance_rate = PayrollCalculationService.get_insurance_rate("介護保険", payment_date)
if care_insurance_rate:
care_insurance = (
health_standard_salary * Decimal(str(care_insurance_rate["employee_rate"]))
).quantize(Decimal("1"))
).quantize(Decimal("0.01"))
print(f"[DEBUG] 介護保険計算: {health_standard_salary} × {care_insurance_rate['employee_rate']}% = {care_insurance}")
else:
errors.append(f"介護保険料率が見つかりません(年度: {payment_date.year}")
# 厚生年金(上限適用後の標準報酬月額で計算)
pension_insurance_rate = PayrollCalculationService.get_insurance_rate("厚生年金", payment_date)
@@ -241,15 +270,30 @@ class PayrollCalculationService:
if pension_insurance_rate:
pension_insurance = (
pension_standard_salary * Decimal(str(pension_insurance_rate["employee_rate"]))
).quantize(Decimal("1"))
).quantize(Decimal("0.01"))
print(f"[DEBUG] 厚生年金計算: {pension_standard_salary} × {pension_insurance_rate['employee_rate']}% = {pension_insurance}")
else:
errors.append(f"厚生年金保険料率が見つかりません(年度: {payment_date.year}")
# 雇用保険
employment_insurance_rate = PayrollCalculationService.get_insurance_rate("雇用保険", payment_date)
employment_insurance = Decimal("0")
if employment_insurance_rate:
# 雇用保険加入対象かどうか確認
is_employment_insurance_eligible = salary_setting.get("employment_insurance_eligible", True) if salary_setting else True
if is_employment_insurance_eligible and employment_insurance_rate:
employment_insurance = (
total_payment * Decimal(str(employment_insurance_rate["employee_rate"]))
).quantize(Decimal("1"))
).quantize(Decimal("0.01"))
elif not is_employment_insurance_eligible:
print(f"[DEBUG] この従業員は雇用保険非対象です")
elif not employment_insurance_rate:
errors.append(f"雇用保険料率が見つかりません(年度: {payment_date.year}")
# エラーがある場合は、エラーメッセージを返す
if errors:
raise ValueError(f"給与計算に必要なデータが不足しています:\n" + "\n".join(errors))
# 所得税の計算
dependents_count = PayrollCalculationService.get_active_dependents_count(employee_id, payment_date)