From 50e534094dd4b08ffcbb7a3a2031690fbd540ac7 Mon Sep 17 00:00:00 2001 From: admin Date: Thu, 22 Jan 2026 22:10:17 +0900 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/main.py | 4 +- backend/app/payroll/bonus/service.py | 25 +++- backend/app/payroll/calculation/service.py | 151 +++++++++++++++++++-- backend/app/payroll/settings/router.py | 51 ++++++- check_income_tax.py | 88 ++++++++++++ debug_income_tax.py | 121 +++++++++++++++++ test_dependents_logic.py | 52 +++++++ test_query_logic.py | 67 +++++++++ verify_calculation.py | 48 +++++++ 9 files changed, 590 insertions(+), 17 deletions(-) create mode 100644 check_income_tax.py create mode 100644 debug_income_tax.py create mode 100644 test_dependents_logic.py create mode 100644 test_query_logic.py create mode 100644 verify_calculation.py diff --git a/backend/app/main.py b/backend/app/main.py index 853f273..afe990b 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -32,9 +32,9 @@ async def validation_exception_handler(request: Request, exc: ValidationError): # ───────────────────────── # ルート(稼働確認) # ───────────────────────── -@app.get("/", summary="稼働確認") +@app.get("/", include_in_schema=False) def root(): - return {"status": "ok"} + return RedirectResponse(url="/index.html") # ───────────────────────── # ルーター登録(※ 必ず app 定義の後) diff --git a/backend/app/payroll/bonus/service.py b/backend/app/payroll/bonus/service.py index 0e64e2b..13a3f09 100644 --- a/backend/app/payroll/bonus/service.py +++ b/backend/app/payroll/bonus/service.py @@ -12,7 +12,7 @@ class BonusCalculationService: @staticmethod def get_insurance_rate(rate_type: str, target_date: date, calculation_target: str = '賞与') -> Dict[str, Any]: - """保険料率を取得(賞与用)""" + """保険料率を取得(賞与用、年度がない場合は前年度を参照)""" with get_connection() as conn: with conn.cursor() as cur: # rate_yearで年度を判定 @@ -28,7 +28,28 @@ class BonusCalculationService: """, (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 def get_insurance_limit(setting_type: str, target_date: date) -> Decimal: diff --git a/backend/app/payroll/calculation/service.py b/backend/app/payroll/calculation/service.py index acfc790..64d9301 100644 --- a/backend/app/payroll/calculation/service.py +++ b/backend/app/payroll/calculation/service.py @@ -12,11 +12,13 @@ class PayrollCalculationService: @staticmethod def get_active_dependents_count(employee_id: int, target_date: date) -> int: - """有効な扶養家族数を取得(源泉税控除対象のみ:19歳以上)""" + """有効な扶養家族数を取得(源泉税控除対象:16歳以上)""" with get_connection() as conn: with conn.cursor() as cur: - # その年の12月31日時点で19歳以上の扶養家族のみをカウント - # 16-18歳は住民税のみ対象なので源泉税控除対象外 + # その年の12月31日時点で16歳以上の扶養家族をカウント + # 源泉所得税の扶養控除対象は16歳以上が対象 + print(f"[DEBUG] 扶養人数取得: 従業員ID={employee_id}, 対象日={target_date}") + cur.execute( """ SELECT COUNT(*) as count FROM dependents @@ -24,12 +26,14 @@ class PayrollCalculationService: AND valid_from <= %s AND (valid_to IS NULL OR valid_to >= %s) 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) ) result = cur.fetchone() - return result["count"] if result else 0 + count = result["count"] if result else 0 + print(f"[DEBUG] 取得した扶養人数: {count}人") + return count @staticmethod def get_salary_setting(employee_id: int, target_date: date) -> Dict[str, Any]: @@ -51,7 +55,7 @@ class PayrollCalculationService: @staticmethod def get_insurance_rate(rate_type: str, target_date: date, calculation_target: str = '給与') -> Dict[str, Any]: - """保険料率を取得""" + """保険料率を取得(年度がない場合は前年度を参照)""" with get_connection() as conn: with conn.cursor() as cur: # rate_yearで年度を判定 @@ -67,7 +71,28 @@ class PayrollCalculationService: """, (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 def get_insurance_limit(setting_type: str, target_date: date) -> Decimal: @@ -94,23 +119,101 @@ class PayrollCalculationService: dependents_count: int, target_date: date ) -> Decimal: - """所得税を計算""" + """所得税を計算(税年度がない場合は前年度の表を参照)""" with get_connection() as conn: 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( """ - 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 AND (valid_to IS NULL OR valid_to >= %s) AND dependents_count = %s AND monthly_income_from <= %s - AND monthly_income_to >= %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] 見つかった(範囲検索): 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") @staticmethod @@ -189,6 +292,10 @@ class PayrollCalculationService: with get_connection() as conn: with conn.cursor() as cur: # 給与年月の標準報酬月額表から、総支給額が属する等級を検索 + target_year = payment_date.year + result = None + + # まず指定年度の標準報酬月額表を検索 cur.execute( """ SELECT monthly_amount @@ -199,14 +306,36 @@ class PayrollCalculationService: ORDER BY rate_year DESC, salary_from DESC LIMIT 1 """, - (payment_date.year, total_payment, total_payment) + (target_year, total_payment, total_payment) ) 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: 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( diff --git a/backend/app/payroll/settings/router.py b/backend/app/payroll/settings/router.py index e22720a..e223395 100644 --- a/backend/app/payroll/settings/router.py +++ b/backend/app/payroll/settings/router.py @@ -2,7 +2,7 @@ 給与設定・税率・保険料管理API (Settings Management API) """ from fastapi import APIRouter, HTTPException, status, UploadFile, File -from typing import List +from typing import List, Optional from datetime import date import csv import io @@ -17,10 +17,40 @@ from ...core.database import get_connection 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) 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 conn.cursor() as cur: + # 月収がmonthly_income_from <= income < monthly_income_toの範囲に入るか検索 cur.execute( """ 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 dependents_count = %s AND monthly_income_from <= %s - AND monthly_income_to >= %s + AND monthly_income_to > %s LIMIT 1 """, (target_date, target_date, dependents_count, monthly_income, monthly_income) ) 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: return {"monthly_income": monthly_income, "dependents_count": dependents_count, "tax_amount": 0} diff --git a/check_income_tax.py b/check_income_tax.py new file mode 100644 index 0000000..05a0008 --- /dev/null +++ b/check_income_tax.py @@ -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() diff --git a/debug_income_tax.py b/debug_income_tax.py new file mode 100644 index 0000000..e7a7926 --- /dev/null +++ b/debug_income_tax.py @@ -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") diff --git a/test_dependents_logic.py b/test_dependents_logic.py new file mode 100644 index 0000000..3ae5c00 --- /dev/null +++ b/test_dependents_logic.py @@ -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. または、複数の条件で検索されて、意図しない行が使用されている可能性") diff --git a/test_query_logic.py b/test_query_logic.py new file mode 100644 index 0000000..ac9e036 --- /dev/null +++ b/test_query_logic.py @@ -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円 ← これはどこから来ているのか?") diff --git a/verify_calculation.py b/verify_calculation.py new file mode 100644 index 0000000..ffdff51 --- /dev/null +++ b/verify_calculation.py @@ -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}%")