修正
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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 = (
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
# ========================================
|
||||
# 社会保険料率管理
|
||||
# ========================================
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user