feat: 子ども・子育て支援金を給与・賞与計算に追加(令和8年4月以降)
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user