修正
This commit is contained in:
@@ -32,9 +32,9 @@ async def validation_exception_handler(request: Request, exc: ValidationError):
|
|||||||
# ─────────────────────────
|
# ─────────────────────────
|
||||||
# ルート(稼働確認)
|
# ルート(稼働確認)
|
||||||
# ─────────────────────────
|
# ─────────────────────────
|
||||||
@app.get("/", summary="稼働確認")
|
@app.get("/", include_in_schema=False)
|
||||||
def root():
|
def root():
|
||||||
return {"status": "ok"}
|
return RedirectResponse(url="/index.html")
|
||||||
|
|
||||||
# ─────────────────────────
|
# ─────────────────────────
|
||||||
# ルーター登録(※ 必ず app 定義の後)
|
# ルーター登録(※ 必ず app 定義の後)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class BonusCalculationService:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_insurance_rate(rate_type: str, target_date: date, calculation_target: str = '賞与') -> Dict[str, Any]:
|
def get_insurance_rate(rate_type: str, target_date: date, calculation_target: str = '賞与') -> Dict[str, Any]:
|
||||||
"""保険料率を取得(賞与用)"""
|
"""保険料率を取得(賞与用、年度がない場合は前年度を参照)"""
|
||||||
with get_connection() as conn:
|
with get_connection() as conn:
|
||||||
with conn.cursor() as cur:
|
with conn.cursor() as cur:
|
||||||
# rate_yearで年度を判定
|
# rate_yearで年度を判定
|
||||||
@@ -28,7 +28,28 @@ class BonusCalculationService:
|
|||||||
""",
|
""",
|
||||||
(rate_type, target_year, calculation_target)
|
(rate_type, target_year, calculation_target)
|
||||||
)
|
)
|
||||||
return cur.fetchone()
|
result = cur.fetchone()
|
||||||
|
|
||||||
|
# 現在の年度でデータが見つからない場合は前年度を検索(最大3年遡る)
|
||||||
|
if not result:
|
||||||
|
for year_offset in range(1, 4):
|
||||||
|
previous_year = target_year - year_offset
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT * FROM insurance_rates
|
||||||
|
WHERE rate_type = %s
|
||||||
|
AND rate_year = %s
|
||||||
|
AND calculation_target = %s
|
||||||
|
ORDER BY rate_id DESC
|
||||||
|
LIMIT 1
|
||||||
|
""",
|
||||||
|
(rate_type, previous_year, calculation_target)
|
||||||
|
)
|
||||||
|
result = cur.fetchone()
|
||||||
|
if result:
|
||||||
|
break
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_insurance_limit(setting_type: str, target_date: date) -> Decimal:
|
def get_insurance_limit(setting_type: str, target_date: date) -> Decimal:
|
||||||
|
|||||||
@@ -12,11 +12,13 @@ class PayrollCalculationService:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_active_dependents_count(employee_id: int, target_date: date) -> int:
|
def get_active_dependents_count(employee_id: int, target_date: date) -> int:
|
||||||
"""有効な扶養家族数を取得(源泉税控除対象のみ:19歳以上)"""
|
"""有効な扶養家族数を取得(源泉税控除対象:16歳以上)"""
|
||||||
with get_connection() as conn:
|
with get_connection() as conn:
|
||||||
with conn.cursor() as cur:
|
with conn.cursor() as cur:
|
||||||
# その年の12月31日時点で19歳以上の扶養家族のみをカウント
|
# その年の12月31日時点で16歳以上の扶養家族をカウント
|
||||||
# 16-18歳は住民税のみ対象なので源泉税控除対象外
|
# 源泉所得税の扶養控除対象は16歳以上が対象
|
||||||
|
print(f"[DEBUG] 扶養人数取得: 従業員ID={employee_id}, 対象日={target_date}")
|
||||||
|
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"""
|
"""
|
||||||
SELECT COUNT(*) as count FROM dependents
|
SELECT COUNT(*) as count FROM dependents
|
||||||
@@ -24,12 +26,14 @@ class PayrollCalculationService:
|
|||||||
AND valid_from <= %s
|
AND valid_from <= %s
|
||||||
AND (valid_to IS NULL OR valid_to >= %s)
|
AND (valid_to IS NULL OR valid_to >= %s)
|
||||||
AND birth_date IS NOT NULL
|
AND birth_date IS NOT NULL
|
||||||
AND EXTRACT(YEAR FROM AGE(DATE(EXTRACT(YEAR FROM %s) || '-12-31'), birth_date)) >= 19
|
AND EXTRACT(YEAR FROM AGE(DATE(EXTRACT(YEAR FROM %s) || '-12-31'), birth_date)) >= 16
|
||||||
""",
|
""",
|
||||||
(employee_id, target_date, target_date, target_date)
|
(employee_id, target_date, target_date, target_date)
|
||||||
)
|
)
|
||||||
result = cur.fetchone()
|
result = cur.fetchone()
|
||||||
return result["count"] if result else 0
|
count = result["count"] if result else 0
|
||||||
|
print(f"[DEBUG] 取得した扶養人数: {count}人")
|
||||||
|
return count
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_salary_setting(employee_id: int, target_date: date) -> Dict[str, Any]:
|
def get_salary_setting(employee_id: int, target_date: date) -> Dict[str, Any]:
|
||||||
@@ -51,7 +55,7 @@ class PayrollCalculationService:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_insurance_rate(rate_type: str, target_date: date, calculation_target: str = '給与') -> Dict[str, Any]:
|
def get_insurance_rate(rate_type: str, target_date: date, calculation_target: str = '給与') -> Dict[str, Any]:
|
||||||
"""保険料率を取得"""
|
"""保険料率を取得(年度がない場合は前年度を参照)"""
|
||||||
with get_connection() as conn:
|
with get_connection() as conn:
|
||||||
with conn.cursor() as cur:
|
with conn.cursor() as cur:
|
||||||
# rate_yearで年度を判定
|
# rate_yearで年度を判定
|
||||||
@@ -67,7 +71,28 @@ class PayrollCalculationService:
|
|||||||
""",
|
""",
|
||||||
(rate_type, target_year, calculation_target)
|
(rate_type, target_year, calculation_target)
|
||||||
)
|
)
|
||||||
return cur.fetchone()
|
result = cur.fetchone()
|
||||||
|
|
||||||
|
# 現在の年度でデータが見つからない場合は前年度を検索(最大3年遡る)
|
||||||
|
if not result:
|
||||||
|
for year_offset in range(1, 4):
|
||||||
|
previous_year = target_year - year_offset
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT * FROM insurance_rates
|
||||||
|
WHERE rate_type = %s
|
||||||
|
AND rate_year = %s
|
||||||
|
AND calculation_target = %s
|
||||||
|
ORDER BY rate_id DESC
|
||||||
|
LIMIT 1
|
||||||
|
""",
|
||||||
|
(rate_type, previous_year, calculation_target)
|
||||||
|
)
|
||||||
|
result = cur.fetchone()
|
||||||
|
if result:
|
||||||
|
break
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_insurance_limit(setting_type: str, target_date: date) -> Decimal:
|
def get_insurance_limit(setting_type: str, target_date: date) -> Decimal:
|
||||||
@@ -94,23 +119,101 @@ class PayrollCalculationService:
|
|||||||
dependents_count: int,
|
dependents_count: int,
|
||||||
target_date: date
|
target_date: date
|
||||||
) -> Decimal:
|
) -> Decimal:
|
||||||
"""所得税を計算"""
|
"""所得税を計算(税年度がない場合は前年度の表を参照)"""
|
||||||
with get_connection() as conn:
|
with get_connection() as conn:
|
||||||
with conn.cursor() as cur:
|
with conn.cursor() as cur:
|
||||||
|
# 現在の年度で検索
|
||||||
|
# 月収がmonthly_income_from <= monthly_income < monthly_income_toの範囲に入るか、
|
||||||
|
# または月収がmonthly_income_from <= monthly_income <= monthly_income_toの範囲(Toが同値の場合も含む)
|
||||||
|
print(f"[DEBUG] 所得税検索: 月収={monthly_income}, 扶養人数={dependents_count}, 日付={target_date}")
|
||||||
|
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"""
|
"""
|
||||||
SELECT tax_amount FROM income_tax_table
|
SELECT tax_id, monthly_income_from, monthly_income_to, tax_amount, valid_from
|
||||||
|
FROM income_tax_table
|
||||||
WHERE valid_from <= %s
|
WHERE valid_from <= %s
|
||||||
AND (valid_to IS NULL OR valid_to >= %s)
|
AND (valid_to IS NULL OR valid_to >= %s)
|
||||||
AND dependents_count = %s
|
AND dependents_count = %s
|
||||||
AND monthly_income_from <= %s
|
AND monthly_income_from <= %s
|
||||||
AND monthly_income_to >= %s
|
AND monthly_income_to > %s
|
||||||
ORDER BY valid_from DESC
|
ORDER BY valid_from DESC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
""",
|
""",
|
||||||
(target_date, target_date, dependents_count, monthly_income, monthly_income)
|
(target_date, target_date, dependents_count, monthly_income, monthly_income)
|
||||||
)
|
)
|
||||||
result = cur.fetchone()
|
result = cur.fetchone()
|
||||||
|
|
||||||
|
if result:
|
||||||
|
print(f"[DEBUG] 見つかった(範囲検索): From={result['monthly_income_from']}, To={result['monthly_income_to']}, 税額={result['tax_amount']}")
|
||||||
|
|
||||||
|
# 見つからない場合は、Toが同値のケースも検索(月収がちょうどToの値に達する場合)
|
||||||
|
if not result:
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT tax_id, monthly_income_from, monthly_income_to, tax_amount, valid_from
|
||||||
|
FROM income_tax_table
|
||||||
|
WHERE valid_from <= %s
|
||||||
|
AND (valid_to IS NULL OR valid_to >= %s)
|
||||||
|
AND dependents_count = %s
|
||||||
|
AND monthly_income_from <= %s
|
||||||
|
AND monthly_income_to = %s
|
||||||
|
ORDER BY valid_from DESC
|
||||||
|
LIMIT 1
|
||||||
|
""",
|
||||||
|
(target_date, target_date, dependents_count, monthly_income, monthly_income)
|
||||||
|
)
|
||||||
|
result = cur.fetchone()
|
||||||
|
if result:
|
||||||
|
print(f"[DEBUG] 見つかった(To同値検索): From={result['monthly_income_from']}, To={result['monthly_income_to']}, 税額={result['tax_amount']}")
|
||||||
|
|
||||||
|
# データが見つからない場合は、指定された日付に関係なく前年度以前の表を検索
|
||||||
|
if not result:
|
||||||
|
for year_offset in range(1, 4):
|
||||||
|
previous_date = date(target_date.year - year_offset, target_date.month, target_date.day)
|
||||||
|
print(f"[DEBUG] 前年度検索: {previous_date}")
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT tax_id, monthly_income_from, monthly_income_to, tax_amount, valid_from
|
||||||
|
FROM income_tax_table
|
||||||
|
WHERE valid_from <= %s
|
||||||
|
AND (valid_to IS NULL OR valid_to >= %s)
|
||||||
|
AND dependents_count = %s
|
||||||
|
AND monthly_income_from <= %s
|
||||||
|
AND monthly_income_to > %s
|
||||||
|
ORDER BY valid_from DESC
|
||||||
|
LIMIT 1
|
||||||
|
""",
|
||||||
|
(previous_date, previous_date, dependents_count, monthly_income, monthly_income)
|
||||||
|
)
|
||||||
|
result = cur.fetchone()
|
||||||
|
if result:
|
||||||
|
print(f"[DEBUG] 前年度で見つかった: {previous_date}, 税額={result['tax_amount']}")
|
||||||
|
break
|
||||||
|
|
||||||
|
# Toが同値のケースも検索
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT tax_id, monthly_income_from, monthly_income_to, tax_amount, valid_from
|
||||||
|
FROM income_tax_table
|
||||||
|
WHERE valid_from <= %s
|
||||||
|
AND (valid_to IS NULL OR valid_to >= %s)
|
||||||
|
AND dependents_count = %s
|
||||||
|
AND monthly_income_from <= %s
|
||||||
|
AND monthly_income_to = %s
|
||||||
|
ORDER BY valid_from DESC
|
||||||
|
LIMIT 1
|
||||||
|
""",
|
||||||
|
(previous_date, previous_date, dependents_count, monthly_income, monthly_income)
|
||||||
|
)
|
||||||
|
result = cur.fetchone()
|
||||||
|
if result:
|
||||||
|
print(f"[DEBUG] 前年度で見つかった(To同値): {previous_date}, 税額={result['tax_amount']}")
|
||||||
|
break
|
||||||
|
|
||||||
|
if not result:
|
||||||
|
print(f"[DEBUG] 該当する所得税データなし、デフォルト値 0 を返す")
|
||||||
|
|
||||||
return Decimal(str(result["tax_amount"])) if result else Decimal("0")
|
return Decimal(str(result["tax_amount"])) if result else Decimal("0")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -189,6 +292,10 @@ class PayrollCalculationService:
|
|||||||
with get_connection() as conn:
|
with get_connection() as conn:
|
||||||
with conn.cursor() as cur:
|
with conn.cursor() as cur:
|
||||||
# 給与年月の標準報酬月額表から、総支給額が属する等級を検索
|
# 給与年月の標準報酬月額表から、総支給額が属する等級を検索
|
||||||
|
target_year = payment_date.year
|
||||||
|
result = None
|
||||||
|
|
||||||
|
# まず指定年度の標準報酬月額表を検索
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"""
|
"""
|
||||||
SELECT monthly_amount
|
SELECT monthly_amount
|
||||||
@@ -199,14 +306,36 @@ class PayrollCalculationService:
|
|||||||
ORDER BY rate_year DESC, salary_from DESC
|
ORDER BY rate_year DESC, salary_from DESC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
""",
|
""",
|
||||||
(payment_date.year, total_payment, total_payment)
|
(target_year, total_payment, total_payment)
|
||||||
)
|
)
|
||||||
result = cur.fetchone()
|
result = cur.fetchone()
|
||||||
|
|
||||||
|
# 見つからない場合は前年度以前を検索(最大3年遡る)
|
||||||
|
if not result:
|
||||||
|
for year_offset in range(1, 4):
|
||||||
|
previous_year = target_year - year_offset
|
||||||
|
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
|
||||||
|
""",
|
||||||
|
(previous_year, total_payment, total_payment)
|
||||||
|
)
|
||||||
|
result = cur.fetchone()
|
||||||
|
if result:
|
||||||
|
break
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
standard_monthly_salary = Decimal(str(result["monthly_amount"]))
|
standard_monthly_salary = Decimal(str(result["monthly_amount"]))
|
||||||
print(f"[DEBUG] 標準報酬月額表検索: 総支給額={total_payment} → 標準報酬月額={standard_monthly_salary}")
|
print(f"[DEBUG] 標準報酬月額表検索: 総支給額={total_payment} → 標準報酬月額={standard_monthly_salary}")
|
||||||
else:
|
else:
|
||||||
print(f"[DEBUG] 標準報酬月額表検索: 該当等級なし(総支給額={total_payment})")
|
print(f"[DEBUG] 標準報酬月額表検索: 該当等級なし(総支給額={total_payment})")
|
||||||
|
|
||||||
|
|
||||||
# 社会保険料上限を取得
|
# 社会保険料上限を取得
|
||||||
health_insurance_limit = PayrollCalculationService.get_insurance_limit(
|
health_insurance_limit = PayrollCalculationService.get_insurance_limit(
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
給与設定・税率・保険料管理API (Settings Management API)
|
給与設定・税率・保険料管理API (Settings Management API)
|
||||||
"""
|
"""
|
||||||
from fastapi import APIRouter, HTTPException, status, UploadFile, File
|
from fastapi import APIRouter, HTTPException, status, UploadFile, File
|
||||||
from typing import List
|
from typing import List, Optional
|
||||||
from datetime import date
|
from datetime import date
|
||||||
import csv
|
import csv
|
||||||
import io
|
import io
|
||||||
@@ -17,10 +17,40 @@ from ...core.database import get_connection
|
|||||||
router = APIRouter(prefix="/payroll/settings", tags=["Payroll - Settings"])
|
router = APIRouter(prefix="/payroll/settings", tags=["Payroll - Settings"])
|
||||||
|
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# ヘルパー関数:年度フォールバック処理
|
||||||
|
# ========================================
|
||||||
|
def get_child_support_rate_with_fallback(target_year: int) -> Optional[dict]:
|
||||||
|
"""子ども・子育て拠出金率を取得(指定年度がない場合は前年度を参照、最大3年遡る)"""
|
||||||
|
with get_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
# まず指定年度を検索
|
||||||
|
cur.execute(
|
||||||
|
"SELECT * FROM child_support_contribution_rates WHERE rate_year = %s ORDER BY contribution_id DESC LIMIT 1",
|
||||||
|
(target_year,)
|
||||||
|
)
|
||||||
|
result = cur.fetchone()
|
||||||
|
|
||||||
|
# 見つからない場合は前年度以前を検索
|
||||||
|
if not result:
|
||||||
|
for year_offset in range(1, 4):
|
||||||
|
previous_year = target_year - year_offset
|
||||||
|
cur.execute(
|
||||||
|
"SELECT * FROM child_support_contribution_rates WHERE rate_year = %s ORDER BY contribution_id DESC LIMIT 1",
|
||||||
|
(previous_year,)
|
||||||
|
)
|
||||||
|
result = cur.fetchone()
|
||||||
|
if result:
|
||||||
|
break
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
# ========================================
|
# ========================================
|
||||||
# 給与基本設定
|
# 給与基本設定
|
||||||
# ========================================
|
# ========================================
|
||||||
|
|
||||||
|
|
||||||
@router.post("/salary", response_model=schemas.SalarySetting, status_code=status.HTTP_201_CREATED)
|
@router.post("/salary", response_model=schemas.SalarySetting, status_code=status.HTTP_201_CREATED)
|
||||||
def create_salary_setting(setting: schemas.SalarySettingCreate):
|
def create_salary_setting(setting: schemas.SalarySettingCreate):
|
||||||
"""給与設定を作成"""
|
"""給与設定を作成"""
|
||||||
@@ -744,6 +774,7 @@ def calculate_income_tax(monthly_income: Decimal, dependents_count: int = 0, tar
|
|||||||
|
|
||||||
with get_connection() as conn:
|
with get_connection() as conn:
|
||||||
with conn.cursor() as cur:
|
with conn.cursor() as cur:
|
||||||
|
# 月収がmonthly_income_from <= income < monthly_income_toの範囲に入るか検索
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"""
|
"""
|
||||||
SELECT tax_amount FROM income_tax_table
|
SELECT tax_amount FROM income_tax_table
|
||||||
@@ -751,13 +782,29 @@ def calculate_income_tax(monthly_income: Decimal, dependents_count: int = 0, tar
|
|||||||
AND (valid_to IS NULL OR valid_to >= %s)
|
AND (valid_to IS NULL OR valid_to >= %s)
|
||||||
AND dependents_count = %s
|
AND dependents_count = %s
|
||||||
AND monthly_income_from <= %s
|
AND monthly_income_from <= %s
|
||||||
AND monthly_income_to >= %s
|
AND monthly_income_to > %s
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
""",
|
""",
|
||||||
(target_date, target_date, dependents_count, monthly_income, monthly_income)
|
(target_date, target_date, dependents_count, monthly_income, monthly_income)
|
||||||
)
|
)
|
||||||
result = cur.fetchone()
|
result = cur.fetchone()
|
||||||
|
|
||||||
|
# 見つからない場合は、Toが同値のケースも検索
|
||||||
|
if not result:
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT tax_amount FROM income_tax_table
|
||||||
|
WHERE valid_from <= %s
|
||||||
|
AND (valid_to IS NULL OR valid_to >= %s)
|
||||||
|
AND dependents_count = %s
|
||||||
|
AND monthly_income_from <= %s
|
||||||
|
AND monthly_income_to = %s
|
||||||
|
LIMIT 1
|
||||||
|
""",
|
||||||
|
(target_date, target_date, dependents_count, monthly_income, monthly_income)
|
||||||
|
)
|
||||||
|
result = cur.fetchone()
|
||||||
|
|
||||||
if not result:
|
if not result:
|
||||||
return {"monthly_income": monthly_income, "dependents_count": dependents_count, "tax_amount": 0}
|
return {"monthly_income": monthly_income, "dependents_count": dependents_count, "tax_amount": 0}
|
||||||
|
|
||||||
|
|||||||
88
check_income_tax.py
Normal file
88
check_income_tax.py
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
所得税表の確認スクリプト
|
||||||
|
ユーザーが提供したデータ:
|
||||||
|
- 月収(控除後): 356,600円
|
||||||
|
- 扶養人数: 3人
|
||||||
|
- 期待される所得税: 5,840円
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'backend'))
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
|
from datetime import date
|
||||||
|
from app.core.database import get_connection
|
||||||
|
|
||||||
|
def check_income_tax():
|
||||||
|
"""所得税テーブルを確認"""
|
||||||
|
|
||||||
|
monthly_income = Decimal("356600")
|
||||||
|
dependents_count = 3
|
||||||
|
target_date = date(2025, 1, 22)
|
||||||
|
|
||||||
|
print(f"検索条件:")
|
||||||
|
print(f" 月収: {monthly_income}円")
|
||||||
|
print(f" 扶養人数: {dependents_count}人")
|
||||||
|
print(f" 適用日: {target_date}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
with get_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
# 全てのデータを確認
|
||||||
|
print("=== 3人扶養の全データ ===")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT * FROM income_tax_table
|
||||||
|
WHERE dependents_count = %s
|
||||||
|
ORDER BY monthly_income_from
|
||||||
|
""",
|
||||||
|
(dependents_count,)
|
||||||
|
)
|
||||||
|
|
||||||
|
results = cur.fetchall()
|
||||||
|
print(f"総件数: {len(results)}")
|
||||||
|
|
||||||
|
# 月収356,600円が属する範囲を探す
|
||||||
|
for row in results:
|
||||||
|
if (row["monthly_income_from"] <= monthly_income <= row["monthly_income_to"]):
|
||||||
|
print(f"\n✓ 月収 {monthly_income} が属する範囲:")
|
||||||
|
print(f" From: {row['monthly_income_from']}")
|
||||||
|
print(f" To: {row['monthly_income_to']}")
|
||||||
|
print(f" 税額: {row['tax_amount']}円")
|
||||||
|
print(f" 有効期間: {row['valid_from']} ~ {row['valid_to']}")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print(f"\n✗ 月収 {monthly_income} が属する範囲が見つかりません")
|
||||||
|
print("\n該当の近い範囲:")
|
||||||
|
for row in results[-5:]:
|
||||||
|
print(f" {row['monthly_income_from']} ~ {row['monthly_income_to']}: {row['tax_amount']}円")
|
||||||
|
|
||||||
|
# 現在の年度で検索(元の検索ロジックをシミュレート)
|
||||||
|
print("\n=== 元の検索ロジック(valid_from/toで検索)===")
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT * FROM income_tax_table
|
||||||
|
WHERE valid_from <= %s
|
||||||
|
AND (valid_to IS NULL OR valid_to >= %s)
|
||||||
|
AND dependents_count = %s
|
||||||
|
AND monthly_income_from <= %s
|
||||||
|
AND monthly_income_to >= %s
|
||||||
|
ORDER BY valid_from DESC
|
||||||
|
LIMIT 1
|
||||||
|
""",
|
||||||
|
(target_date, target_date, dependents_count, monthly_income, monthly_income)
|
||||||
|
)
|
||||||
|
result = cur.fetchone()
|
||||||
|
|
||||||
|
if result:
|
||||||
|
print(f"検索結果:")
|
||||||
|
print(f" 月収範囲: {result['monthly_income_from']} ~ {result['monthly_income_to']}")
|
||||||
|
print(f" 税額: {result['tax_amount']}円")
|
||||||
|
print(f" 有効期間: {result['valid_from']} ~ {result['valid_to']}")
|
||||||
|
else:
|
||||||
|
print("検索結果: なし")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
check_income_tax()
|
||||||
121
debug_income_tax.py
Normal file
121
debug_income_tax.py
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
所得税計算のデバッグスクリプト
|
||||||
|
ユーザーが提供したデータ:
|
||||||
|
- 月収(控除後): 356,600円
|
||||||
|
- 扶養人数: 3人
|
||||||
|
- 期待される所得税: 5,840円
|
||||||
|
"""
|
||||||
|
|
||||||
|
# サンプルCSVデータから月収356,600に対応する税額を逆算
|
||||||
|
sample_data_3 = """
|
||||||
|
月収From,月収To,扶養人数,税額
|
||||||
|
0,96000,3,0
|
||||||
|
96000,98000,3,150
|
||||||
|
98000,100000,3,200
|
||||||
|
100000,102000,3,280
|
||||||
|
102000,104000,3,360
|
||||||
|
104000,106000,3,440
|
||||||
|
106000,108000,3,520
|
||||||
|
108000,110000,3,600
|
||||||
|
110000,112000,3,680
|
||||||
|
112000,114000,3,760
|
||||||
|
114000,116000,3,840
|
||||||
|
116000,118000,3,930
|
||||||
|
118000,127000,3,1100
|
||||||
|
127000,129000,3,1230
|
||||||
|
129000,131000,3,1360
|
||||||
|
131000,133000,3,1490
|
||||||
|
133000,135000,3,1620
|
||||||
|
135000,137000,3,1750
|
||||||
|
137000,139000,3,1880
|
||||||
|
139000,141000,3,2010
|
||||||
|
141000,143000,3,2140
|
||||||
|
143000,145000,3,2270
|
||||||
|
145000,147000,3,2400
|
||||||
|
147000,149000,3,2530
|
||||||
|
149000,151000,3,2660
|
||||||
|
151000,153000,3,2790
|
||||||
|
153000,155000,3,2920
|
||||||
|
155000,157000,3,3050
|
||||||
|
157000,159000,3,3180
|
||||||
|
159000,161000,3,3310
|
||||||
|
161000,163000,3,3430
|
||||||
|
163000,165000,3,3560
|
||||||
|
165000,167000,3,3690
|
||||||
|
167000,169000,3,3820
|
||||||
|
169000,171000,3,3950
|
||||||
|
171000,173000,3,4080
|
||||||
|
173000,175000,3,4210
|
||||||
|
175000,177000,3,4340
|
||||||
|
177000,179000,3,4470
|
||||||
|
179000,181000,3,4600
|
||||||
|
181000,183000,3,4730
|
||||||
|
183000,185000,3,4860
|
||||||
|
185000,187000,3,4990
|
||||||
|
187000,189000,3,5120
|
||||||
|
189000,191000,3,5250
|
||||||
|
191000,193000,3,5380
|
||||||
|
193000,195000,3,5510
|
||||||
|
195000,197000,3,5640
|
||||||
|
197000,199000,3,5770
|
||||||
|
199000,201000,3,5900
|
||||||
|
201000,203000,3,6030
|
||||||
|
203000,205000,3,6160
|
||||||
|
205000,207000,3,6290
|
||||||
|
207000,209000,3,6420
|
||||||
|
209000,211000,3,6550
|
||||||
|
211000,213000,3,6680
|
||||||
|
213000,215000,3,6810
|
||||||
|
215000,217000,3,6940
|
||||||
|
217000,219000,3,7070
|
||||||
|
219000,221000,3,7200
|
||||||
|
221000,223000,3,7330
|
||||||
|
223000,225000,3,7460
|
||||||
|
225000,227000,3,7590
|
||||||
|
"""
|
||||||
|
|
||||||
|
# パースして月収356,600に対応する税額を探す
|
||||||
|
lines = sample_data_3.strip().split('\n')[1:] # ヘッダーをスキップ
|
||||||
|
monthly_income = 356600
|
||||||
|
|
||||||
|
print(f"月収 {monthly_income:,} 円に対応する所得税を検索...\n")
|
||||||
|
|
||||||
|
found = False
|
||||||
|
for line in lines:
|
||||||
|
parts = line.split(',')
|
||||||
|
from_val = int(parts[0])
|
||||||
|
to_val = int(parts[1])
|
||||||
|
dependents = int(parts[2])
|
||||||
|
tax = int(parts[3])
|
||||||
|
|
||||||
|
# 月収が範囲内か確認
|
||||||
|
if from_val <= monthly_income < to_val:
|
||||||
|
print(f"✓ 見つかりました!")
|
||||||
|
print(f" 月収範囲: {from_val:,} ~ {to_val:,}")
|
||||||
|
print(f" 扶養人数: {dependents}人")
|
||||||
|
print(f" 所得税: ¥{tax:,}")
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
elif from_val <= monthly_income <= to_val:
|
||||||
|
print(f"✓ 見つかりました(Toが同値)!")
|
||||||
|
print(f" 月収範囲: {from_val:,} ~ {to_val:,}")
|
||||||
|
print(f" 扶養人数: {dependents}人")
|
||||||
|
print(f" 所得税: ¥{tax:,}")
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not found:
|
||||||
|
print(f"✗ 月収 {monthly_income:,} に対応する範囲が見つかりません")
|
||||||
|
print(f"\n最後の10行:")
|
||||||
|
for line in lines[-10:]:
|
||||||
|
parts = line.split(',')
|
||||||
|
from_val = int(parts[0])
|
||||||
|
to_val = int(parts[1])
|
||||||
|
tax = int(parts[3])
|
||||||
|
print(f" {from_val:,} ~ {to_val:,}: ¥{tax:,}")
|
||||||
|
|
||||||
|
print(f"\n=== ユーザーが提供した期待値 ===")
|
||||||
|
print(f" 月収: ¥356,600")
|
||||||
|
print(f" 扶養人数: 3人")
|
||||||
|
print(f" 期待される所得税: ¥5,840")
|
||||||
52
test_dependents_logic.py
Normal file
52
test_dependents_logic.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
扶養人数の検索ロジック検証
|
||||||
|
|
||||||
|
日本の所得税表では、扶養人数が多いほど税額が低くなります。
|
||||||
|
しかし、「3人」という記録が、実際には「3人以上」を意味しているかもしれません。
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 仮想的な所得税テーブルの状態
|
||||||
|
sample_table = [
|
||||||
|
# (From, To, 扶養人数, 税額)
|
||||||
|
(355000, 360000, 0, 7450), # ← もしかしてこれが使われている?
|
||||||
|
(355000, 360000, 1, 6700),
|
||||||
|
(355000, 360000, 2, 6100),
|
||||||
|
(355000, 360000, 3, 5840), # ← これが正しい
|
||||||
|
(355000, 360000, 999, 3500), # または「3以上」という意味で大きい番号を使用?
|
||||||
|
]
|
||||||
|
|
||||||
|
monthly_income = 356600
|
||||||
|
dependents_count = 3
|
||||||
|
|
||||||
|
print("=== 扶養人数の検索ロジック検証 ===\n")
|
||||||
|
print(f"検索条件: 月収 {monthly_income:,}, 扶養人数 {dependents_count}\n")
|
||||||
|
|
||||||
|
# シナリオ1: 完全一致検索
|
||||||
|
print("[シナリオ1] WHERE dependents_count = %s")
|
||||||
|
matches = [tax for from_v, to_v, dep, tax in sample_table
|
||||||
|
if from_v <= monthly_income < to_v and dep == dependents_count]
|
||||||
|
print(f"マッチ数: {len(matches)}")
|
||||||
|
for tax in matches:
|
||||||
|
print(f" → ¥{tax}")
|
||||||
|
|
||||||
|
# シナリオ2: >= 検索(3人以上は3人の行を使う)
|
||||||
|
print("\n[シナリオ2] WHERE dependents_count <= %s (DESC)")
|
||||||
|
matches = [tax for from_v, to_v, dep, tax in sample_table
|
||||||
|
if from_v <= monthly_income < to_v and dep <= dependents_count]
|
||||||
|
matches_sorted = sorted(matches, reverse=True)
|
||||||
|
print(f"マッチ数: {len(matches_sorted)}")
|
||||||
|
for tax in matches_sorted[:3]:
|
||||||
|
print(f" → ¥{tax}")
|
||||||
|
|
||||||
|
# シナリオ3: テーブルに0人の行が検索される場合
|
||||||
|
print("\n[シナリオ3] もし扶養人数3が見つからず、0が返される場合")
|
||||||
|
for from_v, to_v, dep, tax in sample_table:
|
||||||
|
if from_v <= monthly_income < to_v and dep == 0:
|
||||||
|
print(f" → ¥{tax} (0人扶養の税額が使用される)")
|
||||||
|
|
||||||
|
print("\n=== 推察 ===")
|
||||||
|
print("実際に 7,450円 が計算されているということは...")
|
||||||
|
print("1. 扶養人数が 0 人として計算されている可能性")
|
||||||
|
print("2. または、テーブルの月収範囲の定義が異なる可能性")
|
||||||
|
print("3. または、複数の条件で検索されて、意図しない行が使用されている可能性")
|
||||||
67
test_query_logic.py
Normal file
67
test_query_logic.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
所得税テーブルの詳細確認スクリプト
|
||||||
|
月収356,600円で対応する所得税を確認
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'backend'))
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
# テスト用の簡単なクエリシミュレーション
|
||||||
|
sample_rows_3 = [
|
||||||
|
# (From, To, 扶養3人の税額)
|
||||||
|
(350000, 355000, 5600),
|
||||||
|
(355000, 360000, 5840), # ← 356,600はここに該当するはず
|
||||||
|
(360000, 365000, 6080),
|
||||||
|
(365000, 370000, 6320),
|
||||||
|
]
|
||||||
|
|
||||||
|
monthly_income = 356600
|
||||||
|
dependents_count = 3
|
||||||
|
|
||||||
|
print("=== テスト: 月収 356,600円、扶養3人の所得税 ===\n")
|
||||||
|
|
||||||
|
# ケース1: monthly_income_to > monthly_income 条件
|
||||||
|
print("[ケース1] WHERE monthly_income_from <= %s AND monthly_income_to > %s")
|
||||||
|
print(f"条件: {monthly_income} >= From AND {monthly_income} < To\n")
|
||||||
|
|
||||||
|
found = False
|
||||||
|
for from_val, to_val, tax in sample_rows_3:
|
||||||
|
match = from_val <= monthly_income < to_val
|
||||||
|
status = "✓ MATCH" if match else "✗ NO"
|
||||||
|
print(f" {from_val:,} ~ {to_val:,}: {status} (tax={tax})")
|
||||||
|
if match:
|
||||||
|
found = True
|
||||||
|
result = tax
|
||||||
|
|
||||||
|
if found:
|
||||||
|
print(f"\n✓ 見つかりました: ¥{result}")
|
||||||
|
else:
|
||||||
|
print(f"\n✗ 見つかりませんでした")
|
||||||
|
|
||||||
|
# ケース2: monthly_income_to >= monthly_income 条件(元の間違ったロジック)
|
||||||
|
print("\n[ケース2(元のバグ)] WHERE monthly_income_from <= %s AND monthly_income_to >= %s")
|
||||||
|
print(f"条件: {monthly_income} >= From AND {monthly_income} <= To\n")
|
||||||
|
|
||||||
|
matches = []
|
||||||
|
for from_val, to_val, tax in sample_rows_3:
|
||||||
|
match = from_val <= monthly_income <= to_val
|
||||||
|
status = "✓ MATCH" if match else "✗ NO"
|
||||||
|
print(f" {from_val:,} ~ {to_val:,}: {status} (tax={tax})")
|
||||||
|
if match:
|
||||||
|
matches.append(tax)
|
||||||
|
|
||||||
|
if matches:
|
||||||
|
print(f"\n✓ 見つかりました: {len(matches)}件")
|
||||||
|
for i, tax in enumerate(matches, 1):
|
||||||
|
print(f" {i}. ¥{tax}")
|
||||||
|
else:
|
||||||
|
print(f"\n✗ 見つかりませんでした")
|
||||||
|
|
||||||
|
print("\n=== 比較 ===")
|
||||||
|
print("正しいロジック (To > income): 5,840円")
|
||||||
|
print("バグロジック (To >= income): 複数マッチまたはマッチなし")
|
||||||
|
print(f"実際の計算結果: 7,450円 ← これはどこから来ているのか?")
|
||||||
48
verify_calculation.py
Normal file
48
verify_calculation.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
給与計算の流れを再現してみる
|
||||||
|
画像から見える情報:
|
||||||
|
- 基本給: ¥420,000
|
||||||
|
- 総支給額: ¥420,000
|
||||||
|
- 社会保険小計: ¥63,400
|
||||||
|
- 所得税(甲欄3人): ¥7,450
|
||||||
|
|
||||||
|
ユーザーが言っている「控除後の収入356,600」とは何か?
|
||||||
|
"""
|
||||||
|
|
||||||
|
total_payment = 420000
|
||||||
|
social_insurance = 63400
|
||||||
|
income_tax_shown = 7450
|
||||||
|
|
||||||
|
# 課税対象額の計算
|
||||||
|
# 課税対象額 = 総支給額 - 通勤手当(非課税限度額まで) - 社会保険料
|
||||||
|
# 通勤手当がないと仮定
|
||||||
|
taxable_income = total_payment - social_insurance
|
||||||
|
|
||||||
|
print("=== 給与計算の再現 ===")
|
||||||
|
print(f"総支給額: ¥{total_payment:,}")
|
||||||
|
print(f"社会保険小計: ¥{social_insurance:,}")
|
||||||
|
print(f"課税対象額(給与-社保): ¥{taxable_income:,}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# ユーザーが言った「356,600」との比較
|
||||||
|
user_mentioned = 356600
|
||||||
|
print(f"ユーザーが言及した金額: ¥{user_mentioned:,}")
|
||||||
|
print(f"計算された課税対象額: ¥{taxable_income:,}")
|
||||||
|
print(f"差異: ¥{taxable_income - user_mentioned:,}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# 所得税の逆算
|
||||||
|
print("=== 所得税の検証 ===")
|
||||||
|
print(f"画像に表示されている所得税(甲欄3人): ¥{income_tax_shown:,}")
|
||||||
|
print(f"ユーザーが期待する所得税: ¥5,840")
|
||||||
|
print(f"差異: ¥{income_tax_shown - 5840:,}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# 月収356,600に対して5,840円という金額が妥当か確認
|
||||||
|
print("=== 税率の検証 ===")
|
||||||
|
effective_rate_user = (5840 / user_mentioned) * 100
|
||||||
|
effective_rate_actual = (income_tax_shown / taxable_income) * 100
|
||||||
|
|
||||||
|
print(f"356,600円に対して5,840円の税率: {effective_rate_user:.2f}%")
|
||||||
|
print(f"{taxable_income:,}円に対して{income_tax_shown:,}円の税率: {effective_rate_actual:.2f}%")
|
||||||
Reference in New Issue
Block a user