feat: 子ども・子育て支援金を給与・賞与計算に追加(令和8年4月以降)
This commit is contained in:
@@ -38,12 +38,14 @@ def calculate_bonus(request: schemas.BonusCalculationRequest):
|
|||||||
employee_id, bonus_year, bonus_month, bonus_type, payment_date, status,
|
employee_id, bonus_year, bonus_month, bonus_type, payment_date, status,
|
||||||
base_bonus, performance_bonus, other_bonus, total_bonus,
|
base_bonus, performance_bonus, other_bonus, total_bonus,
|
||||||
health_insurance, care_insurance, pension_insurance, employment_insurance,
|
health_insurance, care_insurance, pension_insurance, employment_insurance,
|
||||||
|
child_support,
|
||||||
income_tax, other_deduction, total_deduction,
|
income_tax, other_deduction, total_deduction,
|
||||||
net_bonus, calculated_at, calculated_by
|
net_bonus, calculated_at, calculated_by
|
||||||
) VALUES (
|
) VALUES (
|
||||||
%s, %s, %s, %s, %s, %s,
|
%s, %s, %s, %s, %s, %s,
|
||||||
%s, %s, %s, %s,
|
%s, %s, %s, %s,
|
||||||
%s, %s, %s, %s,
|
%s, %s, %s, %s,
|
||||||
|
%s,
|
||||||
%s, %s, %s,
|
%s, %s, %s,
|
||||||
%s, %s, %s
|
%s, %s, %s
|
||||||
)
|
)
|
||||||
@@ -56,6 +58,7 @@ def calculate_bonus(request: schemas.BonusCalculationRequest):
|
|||||||
calc_result["other_bonus"], calc_result["total_bonus"],
|
calc_result["other_bonus"], calc_result["total_bonus"],
|
||||||
calc_result["health_insurance"], calc_result["care_insurance"],
|
calc_result["health_insurance"], calc_result["care_insurance"],
|
||||||
calc_result["pension_insurance"], calc_result["employment_insurance"],
|
calc_result["pension_insurance"], calc_result["employment_insurance"],
|
||||||
|
calc_result["child_support"],
|
||||||
calc_result["income_tax"], calc_result["other_deduction"],
|
calc_result["income_tax"], calc_result["other_deduction"],
|
||||||
calc_result["total_deduction"],
|
calc_result["total_deduction"],
|
||||||
calc_result["net_bonus"], datetime.now(), calc_result["calculated_by"]
|
calc_result["net_bonus"], datetime.now(), calc_result["calculated_by"]
|
||||||
@@ -196,7 +199,12 @@ def delete_bonus(bonus_id: int):
|
|||||||
"""賞与を削除"""
|
"""賞与を削除"""
|
||||||
with get_connection() as conn:
|
with get_connection() as conn:
|
||||||
with conn.cursor() as cur:
|
with conn.cursor() as cur:
|
||||||
cur.execute("DELETE FROM bonus_payments WHERE bonus_id = %s AND status = 'draft'", (bonus_id,))
|
# 承認済みは削除不可
|
||||||
if cur.rowcount == 0:
|
cur.execute("SELECT status FROM bonus_payments WHERE bonus_id = %s", (bonus_id,))
|
||||||
|
row = cur.fetchone()
|
||||||
|
if not row:
|
||||||
raise HTTPException(status_code=404, detail="賞与が見つかりません、または削除できません")
|
raise HTTPException(status_code=404, detail="賞与が見つかりません、または削除できません")
|
||||||
|
if row["status"] == "approved":
|
||||||
|
raise HTTPException(status_code=400, detail="承認済みの賞与は削除できません")
|
||||||
|
cur.execute("DELETE FROM bonus_payments WHERE bonus_id = %s", (bonus_id,))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|||||||
@@ -139,12 +139,12 @@ class BonusCalculationService:
|
|||||||
# 総支給額
|
# 総支給額
|
||||||
total_bonus = base_bonus + performance_bonus + other_bonus
|
total_bonus = base_bonus + performance_bonus + other_bonus
|
||||||
|
|
||||||
# 社会保険料上限を取得
|
# 社会保険料上限を取得(賞与専用の標準賞与額上限を使用)
|
||||||
health_insurance_limit = BonusCalculationService.get_insurance_limit(
|
health_insurance_limit = BonusCalculationService.get_insurance_limit(
|
||||||
"健康保険標準報酬月額上限", payment_date
|
"健康保険標準賞与額上限", payment_date
|
||||||
)
|
)
|
||||||
pension_insurance_limit = BonusCalculationService.get_insurance_limit(
|
pension_insurance_limit = BonusCalculationService.get_insurance_limit(
|
||||||
"厚生年金標準報酬月額上限", payment_date
|
"厚生年金標準賞与額上限", payment_date
|
||||||
)
|
)
|
||||||
|
|
||||||
# 賞与の標準賞与額(上限適用)
|
# 賞与の標準賞与額(上限適用)
|
||||||
@@ -203,6 +203,34 @@ class BonusCalculationService:
|
|||||||
total_bonus * Decimal(str(employment_insurance_rate["employee_rate"]))
|
total_bonus * Decimal(str(employment_insurance_rate["employee_rate"]))
|
||||||
).quantize(Decimal("1"))
|
).quantize(Decimal("1"))
|
||||||
|
|
||||||
|
# 子ども・子育て支援金(令和8年4月分以降のみ適用)
|
||||||
|
# 賞与の場合: 標準報酬月額表の淨化率を使い、total_bonus × 子育て率 / 2
|
||||||
|
child_support = Decimal("0")
|
||||||
|
is_child_support_applicable = (
|
||||||
|
(bonus_year == 2026 and bonus_month >= 4) or bonus_year >= 2027
|
||||||
|
)
|
||||||
|
if is_child_support_applicable:
|
||||||
|
with get_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
# 標準報酬月額表から子育て率を取得(最上位等級rowの child_support/monthly_amountで率を計算)
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT child_support, monthly_amount
|
||||||
|
FROM standard_remuneration
|
||||||
|
WHERE rate_year = %s
|
||||||
|
AND monthly_amount > 0 AND child_support > 0
|
||||||
|
ORDER BY monthly_amount DESC
|
||||||
|
LIMIT 1
|
||||||
|
""",
|
||||||
|
(payment_date.year,)
|
||||||
|
)
|
||||||
|
sr = cur.fetchone()
|
||||||
|
if sr and sr["monthly_amount"] and sr["monthly_amount"] > 0:
|
||||||
|
cs_rate = Decimal(str(sr["child_support"])) / Decimal(str(sr["monthly_amount"]))
|
||||||
|
# 標準賞与額に封上限なし(法定上賞与には標準賞与額上限なし)
|
||||||
|
child_support = (total_bonus * cs_rate / Decimal("2")).quantize(Decimal("1"))
|
||||||
|
print(f"[DEBUG] 賞与子育て支援金: 率={cs_rate}, 賞与総額={total_bonus} → 従業員負担={child_support}")
|
||||||
|
|
||||||
# 所得税の計算(賞与特有の計算)
|
# 所得税の計算(賞与特有の計算)
|
||||||
from ..calculation.service import PayrollCalculationService
|
from ..calculation.service import PayrollCalculationService
|
||||||
dependents_count = PayrollCalculationService.get_active_dependents_count(employee_id, payment_date)
|
dependents_count = PayrollCalculationService.get_active_dependents_count(employee_id, payment_date)
|
||||||
@@ -214,7 +242,8 @@ class BonusCalculationService:
|
|||||||
health_insurance -
|
health_insurance -
|
||||||
care_insurance -
|
care_insurance -
|
||||||
pension_insurance -
|
pension_insurance -
|
||||||
employment_insurance
|
employment_insurance -
|
||||||
|
child_support
|
||||||
)
|
)
|
||||||
|
|
||||||
income_tax = BonusCalculationService.calculate_bonus_income_tax(
|
income_tax = BonusCalculationService.calculate_bonus_income_tax(
|
||||||
@@ -229,6 +258,7 @@ class BonusCalculationService:
|
|||||||
care_insurance +
|
care_insurance +
|
||||||
pension_insurance +
|
pension_insurance +
|
||||||
employment_insurance +
|
employment_insurance +
|
||||||
|
child_support +
|
||||||
income_tax +
|
income_tax +
|
||||||
other_deduction
|
other_deduction
|
||||||
)
|
)
|
||||||
@@ -255,6 +285,7 @@ class BonusCalculationService:
|
|||||||
"care_insurance": care_insurance,
|
"care_insurance": care_insurance,
|
||||||
"pension_insurance": pension_insurance,
|
"pension_insurance": pension_insurance,
|
||||||
"employment_insurance": employment_insurance,
|
"employment_insurance": employment_insurance,
|
||||||
|
"child_support": child_support,
|
||||||
"income_tax": income_tax,
|
"income_tax": income_tax,
|
||||||
"other_deduction": other_deduction,
|
"other_deduction": other_deduction,
|
||||||
"total_deduction": total_deduction,
|
"total_deduction": total_deduction,
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ def calculate_payroll(request: schemas.PayrollCalculationRequest):
|
|||||||
working_days, working_hours, overtime_hours, holiday_hours, late_hours, absent_days,
|
working_days, working_hours, overtime_hours, holiday_hours, late_hours, absent_days,
|
||||||
base_salary, overtime_pay, holiday_pay, commute_allowance, other_allowance, total_payment,
|
base_salary, overtime_pay, holiday_pay, commute_allowance, other_allowance, total_payment,
|
||||||
health_insurance, care_insurance, pension_insurance, employment_insurance,
|
health_insurance, care_insurance, pension_insurance, employment_insurance,
|
||||||
|
child_support,
|
||||||
income_tax, resident_tax, other_deduction, total_deduction,
|
income_tax, resident_tax, other_deduction, total_deduction,
|
||||||
net_payment, calculated_at, calculated_by
|
net_payment, calculated_at, calculated_by
|
||||||
) VALUES (
|
) VALUES (
|
||||||
@@ -75,6 +76,7 @@ def calculate_payroll(request: schemas.PayrollCalculationRequest):
|
|||||||
%s, %s, %s, %s, %s, %s,
|
%s, %s, %s, %s, %s, %s,
|
||||||
%s, %s, %s, %s, %s, %s,
|
%s, %s, %s, %s, %s, %s,
|
||||||
%s, %s, %s, %s,
|
%s, %s, %s, %s,
|
||||||
|
%s,
|
||||||
%s, %s, %s, %s,
|
%s, %s, %s, %s,
|
||||||
%s, %s, %s
|
%s, %s, %s
|
||||||
)
|
)
|
||||||
@@ -89,6 +91,7 @@ def calculate_payroll(request: schemas.PayrollCalculationRequest):
|
|||||||
calc_result["commute_allowance"], calc_result["other_allowance"], calc_result["total_payment"],
|
calc_result["commute_allowance"], calc_result["other_allowance"], calc_result["total_payment"],
|
||||||
calc_result["health_insurance"], calc_result["care_insurance"],
|
calc_result["health_insurance"], calc_result["care_insurance"],
|
||||||
calc_result["pension_insurance"], calc_result["employment_insurance"],
|
calc_result["pension_insurance"], calc_result["employment_insurance"],
|
||||||
|
calc_result["child_support"],
|
||||||
calc_result["income_tax"], calc_result["resident_tax"], calc_result["other_deduction"],
|
calc_result["income_tax"], calc_result["resident_tax"], calc_result["other_deduction"],
|
||||||
calc_result["total_deduction"],
|
calc_result["total_deduction"],
|
||||||
calc_result["net_payment"], datetime.now(), calc_result["calculated_by"]
|
calc_result["net_payment"], datetime.now(), calc_result["calculated_by"]
|
||||||
|
|||||||
@@ -334,9 +334,11 @@ class PayrollCalculationService:
|
|||||||
|
|
||||||
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}")
|
child_support_full = Decimal(str(result["child_support"])) if result["child_support"] is not None else Decimal("0")
|
||||||
|
print(f"[DEBUG] 標準報酬月額表検索: 総支給額={total_payment} → 標準報酬月額={standard_monthly_salary}, 子育て支援金全額={child_support_full}")
|
||||||
else:
|
else:
|
||||||
print(f"[DEBUG] 標準報酬月額表検索: 該当等級なし(総支給額={total_payment})")
|
print(f"[DEBUG] 標準報酬月額表検索: 該当等級なし(総支給額={total_payment})")
|
||||||
|
child_support_full = Decimal("0")
|
||||||
|
|
||||||
|
|
||||||
# 社会保険料上限を取得
|
# 社会保険料上限を取得
|
||||||
@@ -410,6 +412,15 @@ class PayrollCalculationService:
|
|||||||
employment_insurance_rate = PayrollCalculationService.get_insurance_rate("雇用保険", payment_date)
|
employment_insurance_rate = PayrollCalculationService.get_insurance_rate("雇用保険", payment_date)
|
||||||
employment_insurance = Decimal("0")
|
employment_insurance = Decimal("0")
|
||||||
|
|
||||||
|
# 子ども・子育て支援金(令和8年4月分以降のみ適用)
|
||||||
|
child_support = Decimal("0")
|
||||||
|
is_child_support_applicable = (
|
||||||
|
(payroll_year == 2026 and payroll_month >= 4) or payroll_year >= 2027
|
||||||
|
)
|
||||||
|
if is_child_support_applicable and calc_social_insurance:
|
||||||
|
child_support = (child_support_full / Decimal("2")).quantize(Decimal("0.01"))
|
||||||
|
print(f"[DEBUG] 子育て支援金: 全額={child_support_full} → 折半額(従業員負担)={child_support}")
|
||||||
|
|
||||||
# 雇用保険加入対象かどうか確認
|
# 雇用保険加入対象かどうか確認
|
||||||
is_employment_insurance_eligible = salary_setting.get("employment_insurance_eligible", True) if salary_setting else True
|
is_employment_insurance_eligible = salary_setting.get("employment_insurance_eligible", True) if salary_setting else True
|
||||||
|
|
||||||
@@ -422,12 +433,13 @@ class PayrollCalculationService:
|
|||||||
elif not employment_insurance_rate and calc_social_insurance:
|
elif not employment_insurance_rate and calc_social_insurance:
|
||||||
errors.append(f"雇用保険料率が見つかりません(年度: {payment_date.year})")
|
errors.append(f"雇用保険料率が見つかりません(年度: {payment_date.year})")
|
||||||
|
|
||||||
# 社会保険計算スキップ時は全て。0にする
|
# 社会保険計算スキップ時は全て0にする
|
||||||
if not calc_social_insurance:
|
if not calc_social_insurance:
|
||||||
health_insurance = Decimal("0")
|
health_insurance = Decimal("0")
|
||||||
care_insurance = Decimal("0")
|
care_insurance = Decimal("0")
|
||||||
pension_insurance = Decimal("0")
|
pension_insurance = Decimal("0")
|
||||||
employment_insurance = Decimal("0")
|
employment_insurance = Decimal("0")
|
||||||
|
child_support = Decimal("0")
|
||||||
|
|
||||||
# エラーがある場合は、エラーメッセージを返す
|
# エラーがある場合は、エラーメッセージを返す
|
||||||
if errors:
|
if errors:
|
||||||
@@ -447,7 +459,8 @@ class PayrollCalculationService:
|
|||||||
health_insurance -
|
health_insurance -
|
||||||
care_insurance -
|
care_insurance -
|
||||||
pension_insurance -
|
pension_insurance -
|
||||||
employment_insurance
|
employment_insurance -
|
||||||
|
child_support
|
||||||
)
|
)
|
||||||
|
|
||||||
if calc_income_tax:
|
if calc_income_tax:
|
||||||
@@ -465,6 +478,7 @@ class PayrollCalculationService:
|
|||||||
care_insurance +
|
care_insurance +
|
||||||
pension_insurance +
|
pension_insurance +
|
||||||
employment_insurance +
|
employment_insurance +
|
||||||
|
child_support +
|
||||||
income_tax +
|
income_tax +
|
||||||
resident_tax +
|
resident_tax +
|
||||||
other_deduction
|
other_deduction
|
||||||
@@ -501,6 +515,7 @@ class PayrollCalculationService:
|
|||||||
"care_insurance": care_insurance,
|
"care_insurance": care_insurance,
|
||||||
"pension_insurance": pension_insurance,
|
"pension_insurance": pension_insurance,
|
||||||
"employment_insurance": employment_insurance,
|
"employment_insurance": employment_insurance,
|
||||||
|
"child_support": child_support,
|
||||||
"income_tax": income_tax,
|
"income_tax": income_tax,
|
||||||
"resident_tax": resident_tax,
|
"resident_tax": resident_tax,
|
||||||
"other_deduction": other_deduction,
|
"other_deduction": other_deduction,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ class StandardRemunerationRow(BaseModel):
|
|||||||
salary_to: Optional[Decimal]
|
salary_to: Optional[Decimal]
|
||||||
health_insurance_no_care: Decimal
|
health_insurance_no_care: Decimal
|
||||||
health_insurance_with_care: Decimal
|
health_insurance_with_care: Decimal
|
||||||
|
child_support: Decimal
|
||||||
pension_insurance: Decimal
|
pension_insurance: Decimal
|
||||||
|
|
||||||
|
|
||||||
@@ -43,7 +44,7 @@ async def get_standard_remuneration(year: int, prefecture: Optional[str] = None)
|
|||||||
if prefecture:
|
if prefecture:
|
||||||
query = """
|
query = """
|
||||||
SELECT rate_year, prefecture, grade, monthly_amount, salary_from, salary_to,
|
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
|
FROM standard_remuneration
|
||||||
WHERE rate_year = %s AND prefecture = %s
|
WHERE rate_year = %s AND prefecture = %s
|
||||||
ORDER BY prefecture,
|
ORDER BY prefecture,
|
||||||
@@ -54,7 +55,7 @@ async def get_standard_remuneration(year: int, prefecture: Optional[str] = None)
|
|||||||
else:
|
else:
|
||||||
query = """
|
query = """
|
||||||
SELECT rate_year, prefecture, grade, monthly_amount, salary_from, salary_to,
|
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
|
FROM standard_remuneration
|
||||||
WHERE rate_year = %s
|
WHERE rate_year = %s
|
||||||
ORDER BY prefecture,
|
ORDER BY prefecture,
|
||||||
@@ -76,6 +77,7 @@ async def get_standard_remuneration(year: int, prefecture: Optional[str] = None)
|
|||||||
"salary_to": row["salary_to"],
|
"salary_to": row["salary_to"],
|
||||||
"health_insurance_no_care": row["health_insurance_no_care"],
|
"health_insurance_no_care": row["health_insurance_no_care"],
|
||||||
"health_insurance_with_care": row["health_insurance_with_care"],
|
"health_insurance_with_care": row["health_insurance_with_care"],
|
||||||
|
"child_support": row["child_support"],
|
||||||
"pension_insurance": row["pension_insurance"]
|
"pension_insurance": row["pension_insurance"]
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -127,33 +129,38 @@ async def import_standard_remuneration(
|
|||||||
detail=f"A1セルから年度を抽出できません: {a1_value}"
|
detail=f"A1セルから年度を抽出できません: {a1_value}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 第9行からメタデータ(保険料率)を抽出
|
# 第8行からメタデータ(保険料率)を抽出
|
||||||
|
# 列配置: F8(idx5)=健康保険(介護なし), H8(idx7)=健康保険(介護あり), J8(idx9)=子育て支援金, L8(idx11)=厚生年金
|
||||||
|
# 値は0-1スケール(例: 0.0985)なので×100してパーセント表示
|
||||||
metadata = {}
|
metadata = {}
|
||||||
try:
|
try:
|
||||||
row9 = list(ws.iter_rows(min_row=9, max_row=9, values_only=True))[0]
|
row8 = list(ws.iter_rows(min_row=8, max_row=8, values_only=True))[0]
|
||||||
# F9, H9, J9 (インデックス 5, 7, 9) から値を取得
|
health_no_care_rate = row8[5] if len(row8) > 5 else None # F8
|
||||||
health_no_care_rate = row9[5] if len(row9) > 5 else None # F9
|
health_with_care_rate = row8[7] if len(row8) > 7 else None # H8
|
||||||
health_with_care_rate = row9[7] if len(row9) > 7 else None # H9
|
child_support_rate = row8[9] if len(row8) > 9 else None # J8
|
||||||
pension_rate = row9[9] if len(row9) > 9 else None # J9
|
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):
|
def clean_percentage(val):
|
||||||
if val is None:
|
if val is None:
|
||||||
return None
|
return None
|
||||||
val_str = str(val).strip()
|
if isinstance(val, float):
|
||||||
val_str = val_str.replace('%', '').replace('%', '')
|
return round(val * 100, 4) # 0.0985 → 9.85
|
||||||
|
val_str = str(val).strip().replace('%', '').replace('%', '')
|
||||||
try:
|
try:
|
||||||
return float(val_str)
|
f = float(val_str)
|
||||||
|
# 1未満なら0-1スケールと判断して×100
|
||||||
|
return round(f * 100, 4) if f < 1 else f
|
||||||
except:
|
except:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
metadata = {
|
metadata = {
|
||||||
'health_no_care_rate': clean_percentage(health_no_care_rate),
|
'health_no_care_rate': clean_percentage(health_no_care_rate),
|
||||||
'health_with_care_rate': clean_percentage(health_with_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),
|
'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}")
|
print(f"[メタデータ クリーニング後] {metadata}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -165,16 +172,15 @@ async def import_standard_remuneration(
|
|||||||
errors = []
|
errors = []
|
||||||
rows_read = []
|
rows_read = []
|
||||||
|
|
||||||
# まずヘッダー行を確認(11行目)
|
# まずヘッダー行を確認(10行目)
|
||||||
header_row = list(ws.iter_rows(min_row=11, max_row=11, values_only=True))[0]
|
header_row = list(ws.iter_rows(min_row=10, max_row=10, values_only=True))[0]
|
||||||
# ヘッダー行の全列を表示
|
header_debug = f"ヘッダー(10行目): {list(header_row)}"
|
||||||
header_debug = f"ヘッダー(11行目): {list(header_row)}"
|
|
||||||
errors.append(header_debug)
|
errors.append(header_debug)
|
||||||
|
|
||||||
# min_row=12で12行目から読み取りを開始
|
# min_row=11で11行目(等級1)から読み取りを開始
|
||||||
# enumerate()はイテレータの要素番号なので、start=0にして、実際の行番号は手動で計算
|
# enumerate()はイテレータの要素番号なので、start=0にして、実際の行番号は手動で計算
|
||||||
for idx, row in enumerate(ws.iter_rows(min_row=12, values_only=True)):
|
for idx, row in enumerate(ws.iter_rows(min_row=11, values_only=True)):
|
||||||
row_idx = 12 + idx # 実際のExcel行番号
|
row_idx = 11 + idx # 実際のExcel行番号
|
||||||
# 最初の1行だけ全列を記録(デバッグ用)
|
# 最初の1行だけ全列を記録(デバッグ用)
|
||||||
if idx == 0:
|
if idx == 0:
|
||||||
row_debug = f"Row {row_idx} (最初のデータ行): {list(row)}"
|
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, ...]
|
# パターン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, ...]
|
# パターン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 ('~', '〜', '-'):
|
if col2_cleaned in ('~', '〜', '-'):
|
||||||
# パターンA: 列2=「~」, 列3=上限値
|
# パターンA: 列C=「~」, 列D=上限値(古い形式用フォールバック)
|
||||||
salary_from = 0 # または前の行から取得
|
salary_from = 0
|
||||||
salary_to = clean_value(row[3], 'salary_to')
|
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_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')
|
health_with_care = clean_value(row[6] if len(row) > 6 else None, 'health_with_care')
|
||||||
|
child_support = clean_value(row[8] if len(row) > 8 else None, 'child_support')
|
||||||
# デバッグ: row全体の長さとrow[8]の値を記録
|
pension = clean_value(row[10] if len(row) > 10 else None, 'pension')
|
||||||
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)}")
|
|
||||||
|
|
||||||
elif col3_cleaned in ('~', '〜', '-'):
|
elif col3_cleaned in ('~', '〜', '-'):
|
||||||
# パターンB: 列2=下限, 列3=「~」, 列4=上限
|
# パターンB: 列C=下限(またはNone), 列D=「~」, 列E=上限
|
||||||
|
# r8ippan3.xlsx の標準形式
|
||||||
salary_from = clean_value(row[2], 'salary_from')
|
salary_from = clean_value(row[2], 'salary_from')
|
||||||
salary_to = clean_value(row[4] if len(row) > 4 else None, 'salary_to')
|
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_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')
|
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:
|
else:
|
||||||
# パターンC: 列2=下限、列3=上限の数値パターン
|
# パターンC: 列C=下限、列D=上限の数値パターン
|
||||||
salary_from = clean_value(row[2], 'salary_from')
|
salary_from = clean_value(row[2], 'salary_from')
|
||||||
salary_to = clean_value(row[3], 'salary_to')
|
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_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')
|
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 = []
|
missing_fields = []
|
||||||
@@ -342,7 +348,7 @@ async def import_standard_remuneration(
|
|||||||
|
|
||||||
data_rows.append((
|
data_rows.append((
|
||||||
rate_year, prefecture, grade, monthly_amount, salary_from, salary_to,
|
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:
|
except Exception as e:
|
||||||
@@ -370,8 +376,8 @@ async def import_standard_remuneration(
|
|||||||
insert_query = """
|
insert_query = """
|
||||||
INSERT INTO standard_remuneration
|
INSERT INTO standard_remuneration
|
||||||
(rate_year, prefecture, grade, monthly_amount, salary_from, salary_to,
|
(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)
|
||||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
cur.executemany(insert_query, data_rows)
|
cur.executemany(insert_query, data_rows)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -432,12 +432,14 @@
|
|||||||
<div id="tab-bonus" class="tab-content-calc">
|
<div id="tab-bonus" class="tab-content-calc">
|
||||||
<div class="filters">
|
<div class="filters">
|
||||||
<label>
|
<label>
|
||||||
対象年月:
|
対象年:
|
||||||
<input
|
<input
|
||||||
type="month"
|
type="number"
|
||||||
id="filterBonusYearMonth"
|
id="filterBonusYear"
|
||||||
style="width: 160px"
|
style="width: 90px"
|
||||||
placeholder="YYYY-MM"
|
value="2026"
|
||||||
|
min="2000"
|
||||||
|
max="2099"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<button class="btn btn-primary" onclick="loadBonus()">検索</button>
|
<button class="btn btn-primary" onclick="loadBonus()">検索</button>
|
||||||
@@ -1480,13 +1482,14 @@
|
|||||||
now.getFullYear() + "-" + String(now.getMonth() + 1).padStart(2, "0");
|
now.getFullYear() + "-" + String(now.getMonth() + 1).padStart(2, "0");
|
||||||
const filterYearEl = document.getElementById("filterYear");
|
const filterYearEl = document.getElementById("filterYear");
|
||||||
if (filterYearEl) filterYearEl.value = now.getFullYear();
|
if (filterYearEl) filterYearEl.value = now.getFullYear();
|
||||||
document.getElementById("filterBonusYearMonth").value = ym;
|
const filterBonusYearEl = document.getElementById("filterBonusYear");
|
||||||
|
if (filterBonusYearEl) filterBonusYearEl.value = now.getFullYear();
|
||||||
loadPayrolls();
|
loadPayrolls();
|
||||||
|
|
||||||
// =====================================================
|
// =====================================================
|
||||||
// タブ切り替え機能
|
// タブ切り替え機能
|
||||||
// =====================================================
|
// =====================================================
|
||||||
let currentTab = "salary";
|
var currentTab = "salary";
|
||||||
|
|
||||||
function switchTab(tab) {
|
function switchTab(tab) {
|
||||||
currentTab = tab;
|
currentTab = tab;
|
||||||
@@ -1526,20 +1529,10 @@
|
|||||||
// 賞与計算関連
|
// 賞与計算関連
|
||||||
// =====================================================
|
// =====================================================
|
||||||
async function loadBonus() {
|
async function loadBonus() {
|
||||||
const ym = document.getElementById("filterBonusYearMonth").value;
|
const year = document.getElementById("filterBonusYear").value;
|
||||||
let year = null;
|
|
||||||
let month = null;
|
|
||||||
if (ym) {
|
|
||||||
const parts = ym.split("-");
|
|
||||||
if (parts.length === 2) {
|
|
||||||
year = Number(parts[0]);
|
|
||||||
month = Number(parts[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (year) params.append("bonus_year", year);
|
if (year) params.append("bonus_year", Number(year));
|
||||||
if (month) params.append("bonus_month", month);
|
|
||||||
|
|
||||||
// employeeMapが空なら読み込む
|
// employeeMapが空なら読み込む
|
||||||
if (!employeeMap || Object.keys(employeeMap).length === 0) {
|
if (!employeeMap || Object.keys(employeeMap).length === 0) {
|
||||||
@@ -2129,16 +2122,16 @@
|
|||||||
<td class="value">¥${Number(salary.employment_insurance).toLocaleString()}</td>
|
<td class="value">¥${Number(salary.employment_insurance).toLocaleString()}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
<td class="label">子ども・子育て支援金</td>
|
||||||
|
<td class="value">¥${Number(salary.child_support || 0).toLocaleString()}</td>
|
||||||
<td class="label">所得税</td>
|
<td class="label">所得税</td>
|
||||||
<td class="value">¥${Number(salary.income_tax).toLocaleString()}</td>
|
<td class="value">¥${Number(salary.income_tax).toLocaleString()}</td>
|
||||||
<td class="label">住民税</td>
|
|
||||||
<td class="value">¥${Number(salary.resident_tax).toLocaleString()}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
<td class="label">住民税</td>
|
||||||
|
<td class="value">¥${Number(salary.resident_tax).toLocaleString()}</td>
|
||||||
<td class="label">その他控除</td>
|
<td class="label">その他控除</td>
|
||||||
<td class="value">¥${Number(salary.other_deduction).toLocaleString()}</td>
|
<td class="value">¥${Number(salary.other_deduction).toLocaleString()}</td>
|
||||||
<td class="label"></td>
|
|
||||||
<td class="value"></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="total-row">
|
<tr class="total-row">
|
||||||
<td class="label">総控除額</td>
|
<td class="label">総控除額</td>
|
||||||
@@ -2215,10 +2208,16 @@
|
|||||||
<td class="value">¥${Number(bonus.employment_insurance).toLocaleString()}</td>
|
<td class="value">¥${Number(bonus.employment_insurance).toLocaleString()}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
<td class="label">子ども・子育て支援金</td>
|
||||||
|
<td class="value">¥${Number(bonus.child_support || 0).toLocaleString()}</td>
|
||||||
<td class="label">所得税</td>
|
<td class="label">所得税</td>
|
||||||
<td class="value">¥${Number(bonus.income_tax).toLocaleString()}</td>
|
<td class="value">¥${Number(bonus.income_tax).toLocaleString()}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
<td class="label">その他控除</td>
|
<td class="label">その他控除</td>
|
||||||
<td class="value">¥${Number(bonus.other_deduction).toLocaleString()}</td>
|
<td class="value">¥${Number(bonus.other_deduction).toLocaleString()}</td>
|
||||||
|
<td class="label"></td>
|
||||||
|
<td class="value"></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="total-row">
|
<tr class="total-row">
|
||||||
<td class="label">総控除額</td>
|
<td class="label">総控除額</td>
|
||||||
|
|||||||
@@ -300,6 +300,9 @@
|
|||||||
<option value="厚生年金">厚生年金</option>
|
<option value="厚生年金">厚生年金</option>
|
||||||
<option value="雇用保険">雇用保険</option>
|
<option value="雇用保険">雇用保険</option>
|
||||||
<option value="労災保険">労災保険</option>
|
<option value="労災保険">労災保険</option>
|
||||||
|
<option value="子ども・子育て支援金">
|
||||||
|
子ども・子育て支援金
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -795,6 +798,7 @@
|
|||||||
const rates = {
|
const rates = {
|
||||||
healthNoCareRate: metadata.health_no_care_rate,
|
healthNoCareRate: metadata.health_no_care_rate,
|
||||||
healthWithCareRate: metadata.health_with_care_rate,
|
healthWithCareRate: metadata.health_with_care_rate,
|
||||||
|
childSupportRate: metadata.child_support_rate,
|
||||||
pensionRate: metadata.pension_rate,
|
pensionRate: metadata.pension_rate,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -885,26 +889,34 @@
|
|||||||
<th rowspan="3">報酬月額 未満</th>
|
<th rowspan="3">報酬月額 未満</th>
|
||||||
<th colspan="2">健康保険料(介護保険なし)</th>
|
<th colspan="2">健康保険料(介護保険なし)</th>
|
||||||
<th colspan="2">健康保険料(介護保険あり)</th>
|
<th colspan="2">健康保険料(介護保険あり)</th>
|
||||||
|
<th colspan="2">子ども・子育て支援金</th>
|
||||||
<th colspan="2">厚生年金保険料</th>
|
<th colspan="2">厚生年金保険料</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr style="background-color: #f0f0f0;">
|
<tr style="background-color: #f0f0f0;">
|
||||||
<th colspan="2" style="text-align: center; font-weight: bold; color: #007bff;">
|
<th colspan="2" style="text-align: center; font-weight: bold; color: #007bff;">
|
||||||
${
|
${
|
||||||
rates.healthNoCareRate !== undefined
|
rates.healthNoCareRate != null
|
||||||
? rates.healthNoCareRate.toFixed(2) + "%"
|
? rates.healthNoCareRate.toFixed(2) + "%"
|
||||||
: "-"
|
: "-"
|
||||||
}
|
}
|
||||||
</th>
|
</th>
|
||||||
<th colspan="2" style="text-align: center; font-weight: bold; color: #007bff;">
|
<th colspan="2" style="text-align: center; font-weight: bold; color: #007bff;">
|
||||||
${
|
${
|
||||||
rates.healthWithCareRate !== undefined
|
rates.healthWithCareRate != null
|
||||||
? rates.healthWithCareRate.toFixed(2) + "%"
|
? rates.healthWithCareRate.toFixed(2) + "%"
|
||||||
: "-"
|
: "-"
|
||||||
}
|
}
|
||||||
</th>
|
</th>
|
||||||
<th colspan="2" style="text-align: center; font-weight: bold; color: #007bff;">
|
<th colspan="2" style="text-align: center; font-weight: bold; color: #007bff;">
|
||||||
${
|
${
|
||||||
rates.pensionRate !== undefined
|
rates.childSupportRate != null
|
||||||
|
? rates.childSupportRate.toFixed(4) + "%"
|
||||||
|
: "-"
|
||||||
|
}
|
||||||
|
</th>
|
||||||
|
<th colspan="2" style="text-align: center; font-weight: bold; color: #007bff;">
|
||||||
|
${
|
||||||
|
rates.pensionRate != null
|
||||||
? rates.pensionRate.toFixed(2) + "%"
|
? rates.pensionRate.toFixed(2) + "%"
|
||||||
: "-"
|
: "-"
|
||||||
}
|
}
|
||||||
@@ -917,6 +929,8 @@
|
|||||||
<th>折半額</th>
|
<th>折半額</th>
|
||||||
<th>全額</th>
|
<th>全額</th>
|
||||||
<th>折半額</th>
|
<th>折半額</th>
|
||||||
|
<th>全額</th>
|
||||||
|
<th>折半額</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
`;
|
`;
|
||||||
@@ -924,13 +938,16 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
${adjustedData
|
${adjustedData
|
||||||
.map((row) => {
|
.map((row) => {
|
||||||
// 保険料を2で割って折半額を計算
|
// 全額を2で割って折半額を計算
|
||||||
const healthNoCareHalf = (
|
const healthNoCareHalf = (
|
||||||
parseFloat(row.health_insurance_no_care) / 2
|
parseFloat(row.health_insurance_no_care) / 2
|
||||||
).toFixed(2);
|
).toFixed(2);
|
||||||
const healthWithCareHalf = (
|
const healthWithCareHalf = (
|
||||||
parseFloat(row.health_insurance_with_care) / 2
|
parseFloat(row.health_insurance_with_care) / 2
|
||||||
).toFixed(2);
|
).toFixed(2);
|
||||||
|
const childSupportHalf = (
|
||||||
|
parseFloat(row.child_support ?? 0) / 2
|
||||||
|
).toFixed(2);
|
||||||
const pensionHalf = (
|
const pensionHalf = (
|
||||||
parseFloat(row.pension_insurance) / 2
|
parseFloat(row.pension_insurance) / 2
|
||||||
).toFixed(2);
|
).toFixed(2);
|
||||||
@@ -949,6 +966,8 @@
|
|||||||
row.health_insurance_with_care ?? 0,
|
row.health_insurance_with_care ?? 0,
|
||||||
).toLocaleString()}</td>
|
).toLocaleString()}</td>
|
||||||
<td>${Number(healthWithCareHalf ?? 0).toLocaleString()}</td>
|
<td>${Number(healthWithCareHalf ?? 0).toLocaleString()}</td>
|
||||||
|
<td>${Number(row.child_support ?? 0).toLocaleString()}</td>
|
||||||
|
<td>${Number(childSupportHalf).toLocaleString()}</td>
|
||||||
<td>${Number(
|
<td>${Number(
|
||||||
row.pension_insurance ?? 0,
|
row.pension_insurance ?? 0,
|
||||||
).toLocaleString()}</td>
|
).toLocaleString()}</td>
|
||||||
|
|||||||
Reference in New Issue
Block a user