feat: 子ども・子育て支援金を給与・賞与計算に追加(令和8年4月以降)

This commit is contained in:
admin
2026-04-27 01:04:54 +09:00
parent 31a64d5b59
commit a646a81719
9 changed files with 3988 additions and 426 deletions

View File

@@ -17,6 +17,7 @@ class StandardRemunerationRow(BaseModel):
salary_to: Optional[Decimal]
health_insurance_no_care: Decimal
health_insurance_with_care: Decimal
child_support: Decimal
pension_insurance: Decimal
@@ -43,7 +44,7 @@ async def get_standard_remuneration(year: int, prefecture: Optional[str] = None)
if prefecture:
query = """
SELECT rate_year, prefecture, grade, monthly_amount, salary_from, salary_to,
health_insurance_no_care, health_insurance_with_care, pension_insurance
health_insurance_no_care, health_insurance_with_care, child_support, pension_insurance
FROM standard_remuneration
WHERE rate_year = %s AND prefecture = %s
ORDER BY prefecture,
@@ -54,7 +55,7 @@ async def get_standard_remuneration(year: int, prefecture: Optional[str] = None)
else:
query = """
SELECT rate_year, prefecture, grade, monthly_amount, salary_from, salary_to,
health_insurance_no_care, health_insurance_with_care, pension_insurance
health_insurance_no_care, health_insurance_with_care, child_support, pension_insurance
FROM standard_remuneration
WHERE rate_year = %s
ORDER BY prefecture,
@@ -76,6 +77,7 @@ async def get_standard_remuneration(year: int, prefecture: Optional[str] = None)
"salary_to": row["salary_to"],
"health_insurance_no_care": row["health_insurance_no_care"],
"health_insurance_with_care": row["health_insurance_with_care"],
"child_support": row["child_support"],
"pension_insurance": row["pension_insurance"]
})
@@ -127,33 +129,38 @@ async def import_standard_remuneration(
detail=f"A1セルから年度を抽出できません: {a1_value}"
)
# 第9行からメタデータ(保険料率)を抽出
# 第8行からメタデータ(保険料率)を抽出
# 列配置: F8(idx5)=健康保険(介護なし), H8(idx7)=健康保険(介護あり), J8(idx9)=子育て支援金, L8(idx11)=厚生年金
# 値は0-1スケール例: 0.0985なので×100してパーセント表示
metadata = {}
try:
row9 = list(ws.iter_rows(min_row=9, max_row=9, values_only=True))[0]
# F9, H9, J9 (インデックス 5, 7, 9) から値を取得
health_no_care_rate = row9[5] if len(row9) > 5 else None # F9
health_with_care_rate = row9[7] if len(row9) > 7 else None # H9
pension_rate = row9[9] if len(row9) > 9 else None # J9
row8 = list(ws.iter_rows(min_row=8, max_row=8, values_only=True))[0]
health_no_care_rate = row8[5] if len(row8) > 5 else None # F8
health_with_care_rate = row8[7] if len(row8) > 7 else None # H8
child_support_rate = row8[9] if len(row8) > 9 else None # J8
pension_rate = row8[11] if len(row8) > 11 else None # L8
print(f"[メタデータ] 第9行: F9={health_no_care_rate}, H9={health_with_care_rate}, J9={pension_rate}")
print(f"[メタデータ] 第8行: F8={health_no_care_rate}, H8={health_with_care_rate}, J8={child_support_rate}, L8={pension_rate}")
# パーセンテージをクリーニング(文字列から%を削除)
def clean_percentage(val):
if val is None:
return None
val_str = str(val).strip()
val_str = val_str.replace('%', '').replace('', '')
if isinstance(val, float):
return round(val * 100, 4) # 0.0985 → 9.85
val_str = str(val).strip().replace('%', '').replace('', '')
try:
return float(val_str)
f = float(val_str)
# 1未満なら0-1スケールと判断して×100
return round(f * 100, 4) if f < 1 else f
except:
return None
metadata = {
'health_no_care_rate': clean_percentage(health_no_care_rate),
'health_with_care_rate': clean_percentage(health_with_care_rate),
'child_support_rate': clean_percentage(child_support_rate),
'pension_rate': clean_percentage(pension_rate),
'source': str(a1_value) if a1_value else None # A1セルの内容を追加
'source': str(a1_value) if a1_value else None
}
print(f"[メタデータ クリーニング後] {metadata}")
except Exception as e:
@@ -165,16 +172,15 @@ async def import_standard_remuneration(
errors = []
rows_read = []
# まずヘッダー行を確認11行目)
header_row = list(ws.iter_rows(min_row=11, max_row=11, values_only=True))[0]
# ヘッダー行の全列を表示
header_debug = f"ヘッダー(11行目): {list(header_row)}"
# まずヘッダー行を確認10行目)
header_row = list(ws.iter_rows(min_row=10, max_row=10, values_only=True))[0]
header_debug = f"ヘッダー(10行目): {list(header_row)}"
errors.append(header_debug)
# min_row=12で12行目から読み取りを開始
# min_row=11で11行目等級1から読み取りを開始
# enumerate()はイテレータの要素番号なので、start=0にして、実際の行番号は手動で計算
for idx, row in enumerate(ws.iter_rows(min_row=12, values_only=True)):
row_idx = 12 + idx # 実際のExcel行番号
for idx, row in enumerate(ws.iter_rows(min_row=11, values_only=True)):
row_idx = 11 + idx # 実際のExcel行番号
# 最初の1行だけ全列を記録デバッグ用
if idx == 0:
row_debug = f"Row {row_idx} (最初のデータ行): {list(row)}"
@@ -251,40 +257,40 @@ async def import_standard_remuneration(
# パターンB: col2 = 数値, col3 = '' → [grade, monthly, range_from, ~, range_to, health_no_full, health_no_half, health_with_full, health_with_half, ...]
# パターンC: col2 = 数値, col3 = 数値 → [grade, monthly, range_from, range_to, health_no_full, health_no_half, health_with_full, health_with_half, ...]
# r8ippan3.xlsx形式:
# col A(0)=等級, col B(1)=月額, col C(2)=円以上, col D(3)='', col E(4)=円未満
# col F(5)=健保介護なし全額, col G(6)=折半, col H(7)=健保介護あり全額, col I(8)=折半
# col J(9)=子育て支援金全額, col K(10)=折半, col L(11)=厚生年金全額, col M(12)=折半
# ※等級1のみcol C(2)が空でcol D(3)=''
# ※等級1〜3は厚生年金col L(11)が空最低標準報酬月額88,000円のため
if col2_cleaned in ('', '', '-'):
# パターンA: 列2=「~」, 列3=上限値
salary_from = 0 # または前の行から取得
# パターンA: 列C=「~」, 列D=上限値(古い形式用フォールバック)
salary_from = 0
salary_to = clean_value(row[3], 'salary_to')
# 保険料は列4以降
health_no_care = clean_value(row[4] if len(row) > 4 else None, 'health_no_care')
health_with_care = clean_value(row[6] if len(row) > 6 else None, 'health_with_care')
# デバッグ: row全体の長さとrow[8]の値を記録
print(f"[デバッグ パターンA] 行{row_idx}: len(row)={len(row)}, row全体={list(row)}")
print(f" row[8]={row[8] if len(row) > 8 else 'N/A (行が短い)'}")
pension = clean_value(row[8] if len(row) > 8 else None, 'pension')
if pension == 0:
print(f" ⚠️ 警告: ペンション値が0です。row[8]の元データ = {row[8] if len(row) > 8 else 'N/A'}")
print(f" clean_value後: pension={pension}, type={type(pension)}")
child_support = clean_value(row[8] if len(row) > 8 else None, 'child_support')
pension = clean_value(row[10] if len(row) > 10 else None, 'pension')
elif col3_cleaned in ('', '', '-'):
# パターンB: 列2=下限, 列3=「~」, 列4=上限
# パターンB: 列C=下限(またはNone), 列D=「~」, 列E=上限
# r8ippan3.xlsx の標準形式
salary_from = clean_value(row[2], 'salary_from')
salary_to = clean_value(row[4] if len(row) > 4 else None, 'salary_to')
# 保険料は列5以降
health_no_care = clean_value(row[5] if len(row) > 5 else None, 'health_no_care')
health_with_care = clean_value(row[7] if len(row) > 7 else None, 'health_with_care')
pension = clean_value(row[9] if len(row) > 9 else None, 'pension')
child_support = clean_value(row[9] if len(row) > 9 else None, 'child_support')
pension = clean_value(row[11] if len(row) > 11 else None, 'pension')
else:
# パターンC: 列2=下限、列3=上限の数値パターン
# パターンC: 列C=下限、列D=上限の数値パターン
salary_from = clean_value(row[2], 'salary_from')
salary_to = clean_value(row[3], 'salary_to')
# 保険料は列4以降
health_no_care = clean_value(row[4] if len(row) > 4 else None, 'health_no_care')
health_with_care = clean_value(row[6] if len(row) > 6 else None, 'health_with_care')
pension = clean_value(row[8] if len(row) > 8 else None, 'pension')
child_support = clean_value(row[8] if len(row) > 8 else None, 'child_support')
pension = clean_value(row[10] if len(row) > 10 else None, 'pension')
# 必須フィールドのチェック(詳細なエラーメッセージ)
missing_fields = []
@@ -342,7 +348,7 @@ async def import_standard_remuneration(
data_rows.append((
rate_year, prefecture, grade, monthly_amount, salary_from, salary_to,
health_no_care, health_with_care, pension
health_no_care, health_with_care, child_support, pension
))
except Exception as e:
@@ -370,8 +376,8 @@ async def import_standard_remuneration(
insert_query = """
INSERT INTO standard_remuneration
(rate_year, prefecture, grade, monthly_amount, salary_from, salary_to,
health_insurance_no_care, health_insurance_with_care, pension_insurance)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
health_insurance_no_care, health_insurance_with_care, child_support, pension_insurance)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
"""
cur.executemany(insert_query, data_rows)