This commit is contained in:
admin
2026-03-04 23:20:42 +09:00
parent 99d16d8219
commit 31a64d5b59
11 changed files with 674 additions and 201 deletions

View File

@@ -51,7 +51,9 @@ def calculate_payroll(request: schemas.PayrollCalculationRequest):
payment_date=request.payment_date,
other_allowance=request.other_allowance,
resident_tax=request.resident_tax,
other_deduction=request.other_deduction
other_deduction=request.other_deduction,
calc_income_tax=request.calc_income_tax,
calc_social_insurance=request.calc_social_insurance
)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@@ -174,8 +176,10 @@ def update_payroll(payroll_id: int, request: schemas.MonthlyPayrollUpdate):
@router.post("/{payroll_id}/recalculate", response_model=schemas.MonthlyPayroll)
def recalculate_payroll(payroll_id: int):
def recalculate_payroll(payroll_id: int, request: schemas.RecalculateRequest = None):
"""給与を再計算"""
if request is None:
request = schemas.RecalculateRequest()
with get_connection() as conn:
with conn.cursor() as cur:
# 既存データを取得
@@ -204,7 +208,9 @@ def recalculate_payroll(payroll_id: int):
other_allowance=existing["other_allowance"],
resident_tax=existing["resident_tax"],
other_deduction=existing["other_deduction"],
calculated_by="recalculated"
calculated_by="recalculated",
calc_income_tax=request.calc_income_tax,
calc_social_insurance=request.calc_social_insurance
)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))

View File

@@ -123,6 +123,14 @@ class PayrollCalculationRequest(BaseModel):
other_allowance: Decimal = Decimal("0")
resident_tax: Decimal = Decimal("0")
other_deduction: Decimal = Decimal("0")
calc_income_tax: bool = True
calc_social_insurance: bool = True
class RecalculateRequest(BaseModel):
"""再計算リクエスト"""
calc_income_tax: bool = True
calc_social_insurance: bool = True
class PayrollApprovalRequest(BaseModel):

View File

@@ -231,7 +231,9 @@ class PayrollCalculationService:
other_allowance: Decimal = Decimal("0"),
resident_tax: Decimal = Decimal("0"),
other_deduction: Decimal = Decimal("0"),
calculated_by: str = "system"
calculated_by: str = "system",
calc_income_tax: bool = True,
calc_social_insurance: bool = True
) -> Dict[str, Any]:
"""給与を計算"""
@@ -378,7 +380,7 @@ class PayrollCalculationService:
health_insurance = (
health_standard_salary * Decimal(str(health_insurance_rate["employee_rate"]))
).quantize(Decimal("0.01"))
else:
elif calc_social_insurance:
errors.append(f"健康保険料率が見つかりません(年度: {payment_date.year}")
# 介護保険40歳以上65歳未満が対象、標準報酬月額表から検索した月額で計算
@@ -391,7 +393,7 @@ class PayrollCalculationService:
).quantize(Decimal("0.01"))
print(f"[DEBUG] 介護保険計算: {health_standard_salary} × {care_insurance_rate['employee_rate']}% = {care_insurance}")
else:
errors.append(f"介護保険料率が見つかりません(年度: {payment_date.year}")
errors.append(f"介護保険料率が見つかりません(年度: {payment_date.year}") if calc_social_insurance else None
# 厚生年金(上限適用後の標準報酬月額で計算)
pension_insurance_rate = PayrollCalculationService.get_insurance_rate("厚生年金", payment_date)
@@ -401,7 +403,7 @@ class PayrollCalculationService:
pension_standard_salary * Decimal(str(pension_insurance_rate["employee_rate"]))
).quantize(Decimal("0.01"))
print(f"[DEBUG] 厚生年金計算: {pension_standard_salary} × {pension_insurance_rate['employee_rate']}% = {pension_insurance}")
else:
elif calc_social_insurance:
errors.append(f"厚生年金保険料率が見つかりません(年度: {payment_date.year}")
# 雇用保険
@@ -417,9 +419,16 @@ class PayrollCalculationService:
).quantize(Decimal("0.01"))
elif not is_employment_insurance_eligible:
print(f"[DEBUG] この従業員は雇用保険非対象です")
elif not employment_insurance_rate:
elif not employment_insurance_rate and calc_social_insurance:
errors.append(f"雇用保険料率が見つかりません(年度: {payment_date.year}")
# 社会保険計算スキップ時は全て。0にする
if not calc_social_insurance:
health_insurance = Decimal("0")
care_insurance = Decimal("0")
pension_insurance = Decimal("0")
employment_insurance = Decimal("0")
# エラーがある場合は、エラーメッセージを返す
if errors:
raise ValueError(f"給与計算に必要なデータが不足しています:\n" + "\n".join(errors))
@@ -441,11 +450,14 @@ class PayrollCalculationService:
employment_insurance
)
income_tax = PayrollCalculationService.calculate_income_tax(
taxable_income,
dependents_count,
payment_date
)
if calc_income_tax:
income_tax = PayrollCalculationService.calculate_income_tax(
taxable_income,
dependents_count,
payment_date
)
else:
income_tax = Decimal("0")
# 総控除額
total_deduction = (

View File

@@ -12,6 +12,7 @@ import openpyxl
import xlrd
from . import schemas
from ...core.database import get_connection
from psycopg.errors import UniqueViolation
router = APIRouter(prefix="/payroll/settings", tags=["Payroll - Settings"])
@@ -67,45 +68,51 @@ def create_salary_setting(setting: schemas.SalarySettingCreate):
)
has_employment_insurance_column = cur.fetchone()
if has_employment_insurance_column:
# カラムが存在する場合
cur.execute(
"""
INSERT INTO salary_settings (
employee_id, base_salary, hourly_rate, employment_type,
payment_type, commute_allowance, other_allowance,
employment_insurance_eligible,
valid_from, valid_to
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
RETURNING *
""",
(
setting.employee_id, setting.base_salary, setting.hourly_rate,
setting.employment_type, setting.payment_type,
setting.commute_allowance, setting.other_allowance,
setting.employment_insurance_eligible,
setting.valid_from, setting.valid_to
),
try:
if has_employment_insurance_column:
# カラムが存在する場合
cur.execute(
"""
INSERT INTO salary_settings (
employee_id, base_salary, hourly_rate, employment_type,
payment_type, commute_allowance, other_allowance,
employment_insurance_eligible,
valid_from, valid_to
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
RETURNING *
""",
(
setting.employee_id, setting.base_salary, setting.hourly_rate,
setting.employment_type, setting.payment_type,
setting.commute_allowance, setting.other_allowance,
setting.employment_insurance_eligible,
setting.valid_from, setting.valid_to
),
)
else:
# カラムが存在しない場合(互換性維持)
cur.execute(
"""
INSERT INTO salary_settings (
employee_id, base_salary, hourly_rate, employment_type,
payment_type, commute_allowance, other_allowance,
valid_from, valid_to
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
RETURNING *
""",
(
setting.employee_id, setting.base_salary, setting.hourly_rate,
setting.employment_type, setting.payment_type,
setting.commute_allowance, setting.other_allowance,
setting.valid_from, setting.valid_to
),
)
print("[DEBUG] employment_insurance_eligible カラムがまだ存在しないため、除外しました")
except UniqueViolation:
raise HTTPException(
status_code=409,
detail="この適用開始日はすでに登録されています。別の日付を指定してください。"
)
else:
# カラムが存在しない場合(互換性維持)
cur.execute(
"""
INSERT INTO salary_settings (
employee_id, base_salary, hourly_rate, employment_type,
payment_type, commute_allowance, other_allowance,
valid_from, valid_to
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
RETURNING *
""",
(
setting.employee_id, setting.base_salary, setting.hourly_rate,
setting.employment_type, setting.payment_type,
setting.commute_allowance, setting.other_allowance,
setting.valid_from, setting.valid_to
),
)
print("[DEBUG] employment_insurance_eligible カラムがまだ存在しないため、除外しました")
result = cur.fetchone()
conn.commit()
@@ -184,10 +191,16 @@ def update_salary_setting(setting_id: int, setting: schemas.SalarySettingUpdate)
with get_connection() as conn:
with conn.cursor() as cur:
cur.execute(
f"UPDATE salary_settings SET {set_clause} WHERE setting_id = %s RETURNING *",
values,
)
try:
cur.execute(
f"UPDATE salary_settings SET {set_clause} WHERE setting_id = %s RETURNING *",
values,
)
except UniqueViolation:
raise HTTPException(
status_code=409,
detail="この適用開始日はすでに登録されています。別の日付を指定してください。"
)
result = cur.fetchone()
if not result:
raise HTTPException(status_code=404, detail="給与設定が見つかりません")
@@ -195,6 +208,21 @@ def update_salary_setting(setting_id: int, setting: schemas.SalarySettingUpdate)
return result
@router.delete("/salary/{setting_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_salary_setting(setting_id: int):
"""給与設定を削除"""
with get_connection() as conn:
with conn.cursor() as cur:
cur.execute(
"DELETE FROM salary_settings WHERE setting_id = %s RETURNING setting_id",
(setting_id,),
)
result = cur.fetchone()
if not result:
raise HTTPException(status_code=404, detail="給与設定が見つかりません")
conn.commit()
# ========================================
# 社会保険料率管理
# ========================================

View File

@@ -32,6 +32,7 @@ class VoucherDataSummary(BaseModel):
employee_id: int
employee_code: str
employee_name: str
employment_type: Optional[str] = None
has_salary_data: bool
has_bonus_data: bool
salary_count: int = 0

View File

@@ -310,6 +310,17 @@ def build_voucher_summary(
emp_name = emp_row.get("name")
else:
emp_code, emp_name = emp_row
# 雇用形態を最新の給与設定から取得
cur.execute(
"SELECT employment_type FROM salary_settings WHERE employee_id = %s ORDER BY valid_from DESC LIMIT 1",
(emp_id,)
)
ss_row = cur.fetchone()
if ss_row:
employment_type = ss_row.get("employment_type") if isinstance(ss_row, dict) else ss_row[0]
else:
employment_type = None
except Exception as e:
logger.error(f"従業員情報取得エラー: {str(e)}")
continue
@@ -333,6 +344,7 @@ def build_voucher_summary(
employee_id=emp_id,
employee_code=emp_code,
employee_name=emp_name,
employment_type=employment_type,
has_salary_data=len(salary_list) > 0,
has_bonus_data=len(bonus_list) > 0,
salary_count=len(salary_list),