Files
njts-accounting-core/backend/app/payroll/bonus/router.py
2026-05-14 22:02:06 +09:00

247 lines
10 KiB
Python

"""
賞与計算API (Bonus Calculation API)
"""
from fastapi import APIRouter, HTTPException, status
from typing import List
from datetime import datetime
from . import schemas
from .service import BonusCalculationService
from ...core.database import get_connection
router = APIRouter(prefix="/payroll/bonus", tags=["Payroll - Bonus"])
@router.post("/calculate", response_model=schemas.BonusPayment, status_code=status.HTTP_201_CREATED)
def calculate_bonus(request: schemas.BonusCalculationRequest):
"""賞与を計算"""
# 賞与を計算
calc_result = BonusCalculationService.calculate_bonus(
employee_id=request.employee_id,
bonus_year=request.bonus_year,
bonus_month=request.bonus_month,
bonus_type=request.bonus_type,
payment_date=request.payment_date,
base_bonus=request.base_bonus,
performance_bonus=request.performance_bonus,
other_bonus=request.other_bonus,
other_deduction=request.other_deduction,
calculated_by=request.calculated_by
)
# データベースに保存
with get_connection() as conn:
with conn.cursor() as cur:
cur.execute(
"""
INSERT INTO bonus_payments (
employee_id, bonus_year, bonus_month, bonus_type, payment_date, status,
base_bonus, performance_bonus, other_bonus, total_bonus,
health_insurance, care_insurance, pension_insurance, employment_insurance,
child_support,
income_tax, other_deduction, total_deduction,
net_bonus, calculated_at, calculated_by
) VALUES (
%s, %s, %s, %s, %s, %s,
%s, %s, %s, %s,
%s, %s, %s, %s,
%s,
%s, %s, %s,
%s, %s, %s
)
RETURNING *
""",
(
calc_result["employee_id"], calc_result["bonus_year"], calc_result["bonus_month"],
calc_result["bonus_type"], calc_result["payment_date"], calc_result["status"],
calc_result["base_bonus"], calc_result["performance_bonus"],
calc_result["other_bonus"], calc_result["total_bonus"],
calc_result["health_insurance"], calc_result["care_insurance"],
calc_result["pension_insurance"], calc_result["employment_insurance"],
calc_result["child_support"],
calc_result["income_tax"], calc_result["other_deduction"],
calc_result["total_deduction"],
calc_result["net_bonus"], datetime.now(), calc_result["calculated_by"]
)
)
result = cur.fetchone()
conn.commit()
return result
@router.get("/", response_model=List[schemas.BonusPayment])
def get_bonuses(bonus_year: int = None, employee_id: int = None):
"""賞与一覧を取得"""
conditions = []
params = []
if bonus_year:
conditions.append("bonus_year = %s")
params.append(bonus_year)
if employee_id:
conditions.append("employee_id = %s")
params.append(employee_id)
where_clause = " AND ".join(conditions) if conditions else "1=1"
with get_connection() as conn:
with conn.cursor() as cur:
cur.execute(
f"""
SELECT * FROM bonus_payments
WHERE {where_clause}
ORDER BY bonus_year DESC, bonus_month DESC
""",
params
)
return cur.fetchall()
@router.get("/{bonus_id}", response_model=schemas.BonusPayment)
def get_bonus(bonus_id: int):
"""賞与詳細を取得"""
with get_connection() as conn:
with conn.cursor() as cur:
cur.execute("SELECT * FROM bonus_payments WHERE bonus_id = %s", (bonus_id,))
result = cur.fetchone()
if not result:
raise HTTPException(status_code=404, detail="賞与が見つかりません")
return result
@router.post("/{bonus_id}/approve", response_model=schemas.BonusPayment)
def approve_bonus(bonus_id: int, approved_by: str):
"""賞与を承認"""
with get_connection() as conn:
with conn.cursor() as cur:
cur.execute(
"""
UPDATE bonus_payments
SET status = 'approved', approved_at = %s, approved_by = %s
WHERE bonus_id = %s AND status = 'calculated'
RETURNING *
""",
(datetime.now(), approved_by, bonus_id)
)
result = cur.fetchone()
if not result:
raise HTTPException(status_code=404, detail="賞与が見つかりません、または既に承認済みです")
conn.commit()
return result
@router.put("/{bonus_id}", response_model=schemas.BonusPayment)
def update_bonus(bonus_id: int, request: schemas.BonusUpdateRequest):
"""賞与の支給項目を更新(再計算は別途実行)"""
with get_connection() as conn:
with conn.cursor() as cur:
cur.execute("SELECT status FROM bonus_payments WHERE bonus_id = %s", (bonus_id,))
row = cur.fetchone()
if not row:
raise HTTPException(status_code=404, detail="賞与が見つかりません")
if row["status"] == "approved":
raise HTTPException(status_code=400, detail="承認済みの賞与は編集できません")
cur.execute(
"""
UPDATE bonus_payments SET
base_bonus = %s, performance_bonus = %s, other_bonus = %s,
other_deduction = %s,
payment_date = COALESCE(%s, payment_date),
bonus_type = COALESCE(%s, bonus_type)
WHERE bonus_id = %s
RETURNING *
""",
(
request.base_bonus, request.performance_bonus, request.other_bonus,
request.other_deduction,
request.payment_date, request.bonus_type,
bonus_id
)
)
result = cur.fetchone()
conn.commit()
return result
@router.post("/{bonus_id}/recalculate", response_model=schemas.BonusPayment)
def recalculate_bonus(bonus_id: int):
"""賞与を再計算"""
with get_connection() as conn:
with conn.cursor() as cur:
# 既存データを取得
cur.execute("SELECT * FROM bonus_payments WHERE bonus_id = %s", (bonus_id,))
existing = cur.fetchone()
if not existing:
raise HTTPException(status_code=404, detail="賞与データが見つかりません")
if existing["status"] == "approved":
raise HTTPException(status_code=400, detail="承認済みの賞与は再計算できません")
# 再計算
try:
calc_result = BonusCalculationService.calculate_bonus(
employee_id=existing["employee_id"],
bonus_year=existing["bonus_year"],
bonus_month=existing["bonus_month"],
bonus_type=existing["bonus_type"],
payment_date=existing["payment_date"],
base_bonus=existing["base_bonus"],
performance_bonus=existing["performance_bonus"],
other_bonus=existing["other_bonus"],
other_deduction=existing["other_deduction"],
calculated_by="recalculated"
)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
# 更新
cur.execute(
"""
UPDATE bonus_payments SET
base_bonus = %s, performance_bonus = %s, other_bonus = %s,
total_bonus = %s,
health_insurance = %s, care_insurance = %s, pension_insurance = %s,
employment_insurance = %s, child_support = %s,
income_tax = %s, other_deduction = %s,
total_deduction = %s, net_bonus = %s,
calculated_at = %s, calculated_by = %s
WHERE bonus_id = %s
RETURNING *
""",
(
calc_result["base_bonus"], calc_result["performance_bonus"],
calc_result["other_bonus"], calc_result["total_bonus"],
calc_result["health_insurance"], calc_result["care_insurance"],
calc_result["pension_insurance"], calc_result["employment_insurance"],
calc_result["child_support"],
calc_result["income_tax"], calc_result["other_deduction"],
calc_result["total_deduction"], calc_result["net_bonus"],
datetime.now(), "recalculated",
bonus_id
)
)
result = cur.fetchone()
if not result:
raise HTTPException(status_code=404, detail="賞与を更新できません")
conn.commit()
return result
@router.delete("/{bonus_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_bonus(bonus_id: int):
"""賞与を削除"""
with get_connection() as conn:
with conn.cursor() as cur:
# 承認済みは削除不可
cur.execute("SELECT status FROM bonus_payments WHERE bonus_id = %s", (bonus_id,))
row = cur.fetchone()
if not row:
raise HTTPException(status_code=404, detail="賞与が見つかりません、または削除できません")
if row["status"] == "approved":
raise HTTPException(status_code=400, detail="承認済みの賞与は削除できません")
cur.execute("DELETE FROM bonus_payments WHERE bonus_id = %s", (bonus_id,))
conn.commit()