diff --git a/backend/app/payroll/bonus/router.py b/backend/app/payroll/bonus/router.py index 01611e1..5a7237d 100644 --- a/backend/app/payroll/bonus/router.py +++ b/backend/app/payroll/bonus/router.py @@ -38,12 +38,14 @@ def calculate_bonus(request: schemas.BonusCalculationRequest): employee_id, bonus_year, bonus_month, bonus_type, payment_date, status, base_bonus, performance_bonus, other_bonus, total_bonus, health_insurance, care_insurance, pension_insurance, employment_insurance, + child_support, income_tax, other_deduction, total_deduction, net_bonus, calculated_at, calculated_by ) VALUES ( %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["health_insurance"], calc_result["care_insurance"], calc_result["pension_insurance"], calc_result["employment_insurance"], + calc_result["child_support"], calc_result["income_tax"], calc_result["other_deduction"], calc_result["total_deduction"], 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 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="賞与が見つかりません、または削除できません") + if row["status"] == "approved": + raise HTTPException(status_code=400, detail="承認済みの賞与は削除できません") + cur.execute("DELETE FROM bonus_payments WHERE bonus_id = %s", (bonus_id,)) conn.commit() diff --git a/backend/app/payroll/bonus/service.py b/backend/app/payroll/bonus/service.py index 13a3f09..c2330c2 100644 --- a/backend/app/payroll/bonus/service.py +++ b/backend/app/payroll/bonus/service.py @@ -139,12 +139,12 @@ class BonusCalculationService: # 総支給額 total_bonus = base_bonus + performance_bonus + other_bonus - # 社会保険料上限を取得 + # 社会保険料上限を取得(賞与専用の標準賞与額上限を使用) health_insurance_limit = BonusCalculationService.get_insurance_limit( - "健康保険標準報酬月額上限", payment_date + "健康保険標準賞与額上限", payment_date ) 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"])) ).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 dependents_count = PayrollCalculationService.get_active_dependents_count(employee_id, payment_date) @@ -214,7 +242,8 @@ class BonusCalculationService: health_insurance - care_insurance - pension_insurance - - employment_insurance + employment_insurance - + child_support ) income_tax = BonusCalculationService.calculate_bonus_income_tax( @@ -229,6 +258,7 @@ class BonusCalculationService: care_insurance + pension_insurance + employment_insurance + + child_support + income_tax + other_deduction ) @@ -255,6 +285,7 @@ class BonusCalculationService: "care_insurance": care_insurance, "pension_insurance": pension_insurance, "employment_insurance": employment_insurance, + "child_support": child_support, "income_tax": income_tax, "other_deduction": other_deduction, "total_deduction": total_deduction, diff --git a/backend/app/payroll/calculation/router.py b/backend/app/payroll/calculation/router.py index 700aa62..f23ac6c 100644 --- a/backend/app/payroll/calculation/router.py +++ b/backend/app/payroll/calculation/router.py @@ -67,7 +67,8 @@ def calculate_payroll(request: schemas.PayrollCalculationRequest): employee_id, payroll_year, payroll_month, payment_date, status, working_days, working_hours, overtime_hours, holiday_hours, late_hours, absent_days, 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, net_payment, calculated_at, calculated_by ) 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 ) @@ -89,6 +91,7 @@ def calculate_payroll(request: schemas.PayrollCalculationRequest): calc_result["commute_allowance"], calc_result["other_allowance"], calc_result["total_payment"], calc_result["health_insurance"], calc_result["care_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["total_deduction"], calc_result["net_payment"], datetime.now(), calc_result["calculated_by"] diff --git a/backend/app/payroll/calculation/service.py b/backend/app/payroll/calculation/service.py index e93ea4f..1692dbd 100644 --- a/backend/app/payroll/calculation/service.py +++ b/backend/app/payroll/calculation/service.py @@ -334,9 +334,11 @@ class PayrollCalculationService: if result: 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: 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 = 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 @@ -422,12 +433,13 @@ class PayrollCalculationService: elif not employment_insurance_rate and calc_social_insurance: errors.append(f"雇用保険料率が見つかりません(年度: {payment_date.year})") - # 社会保険計算スキップ時は全て。0にする + # 社会保険計算スキップ時は全て0にする if not calc_social_insurance: health_insurance = Decimal("0") care_insurance = Decimal("0") pension_insurance = Decimal("0") employment_insurance = Decimal("0") + child_support = Decimal("0") # エラーがある場合は、エラーメッセージを返す if errors: @@ -447,7 +459,8 @@ class PayrollCalculationService: health_insurance - care_insurance - pension_insurance - - employment_insurance + employment_insurance - + child_support ) if calc_income_tax: @@ -465,6 +478,7 @@ class PayrollCalculationService: care_insurance + pension_insurance + employment_insurance + + child_support + income_tax + resident_tax + other_deduction @@ -501,6 +515,7 @@ class PayrollCalculationService: "care_insurance": care_insurance, "pension_insurance": pension_insurance, "employment_insurance": employment_insurance, + "child_support": child_support, "income_tax": income_tax, "resident_tax": resident_tax, "other_deduction": other_deduction, diff --git a/backend/app/payroll/settings/standard_remuneration_router.py b/backend/app/payroll/settings/standard_remuneration_router.py index bd045cf..a136391 100644 --- a/backend/app/payroll/settings/standard_remuneration_router.py +++ b/backend/app/payroll/settings/standard_remuneration_router.py @@ -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) diff --git a/backend/app/static/payroll-calculation.html b/backend/app/static/payroll-calculation.html index edc6548..116e09a 100644 --- a/backend/app/static/payroll-calculation.html +++ b/backend/app/static/payroll-calculation.html @@ -2,10 +2,16 @@ + 給与管理 - 月次給与計算
-

給与管理 - 月次給与計算

+ -
- - - - + +
-
- - -
-

給与計算

-
-
- - + +
+ + + +
-
-
- - -
-
- - -
-
- - -
-
+
+ + +
+

給与計算

+ +
+ + +
-
-

勤怠情報

- - -
-
- +
- + + +
+
+
-
-
- - -
-
- - -
-
- - -
-
-
-
-

その他

+
+

勤怠情報

+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+

その他

+
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+

計算オプション

+
+ + +
+
+ + + + +
+ + + +
+ + +
+
+ + + +
+ +
+ + +
+

賞与計算

+
+
+ + +
+
- + + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+
+
+ +
+
+ + +
-
- - - + + + +
+ + +
- + +
+
+

出力条件の設定

+
+ + +
+

期間選択

+
+
+ +
+ + + + +
+
+
+ +
+ + + + +
+
+
+ +
+
+
+ + +
+

従業員選択

+
+ + + 選択数: 0 +
+
+

従業員を読み込み中...

+
+
+ + +
+

支払タイプ

+
+ + + +
+
+ + +
+ + + + +
+ + + +
+ + + +
+ style=" + background: white; + padding: 30px; + border-radius: 10px; + max-width: 500px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); + " + > +

⚠️ データがありません

+

+ +

+
+ +
+
diff --git a/backend/app/static/payroll-settings.html b/backend/app/static/payroll-settings.html index 7f29300..ea25dea 100644 --- a/backend/app/static/payroll-settings.html +++ b/backend/app/static/payroll-settings.html @@ -2,6 +2,7 @@ + 給与管理 - 設定 @@ -11,6 +12,7 @@ gap: 10px; margin-bottom: 20px; border-bottom: 2px solid #ddd; + flex-wrap: wrap; } .nav-tab { padding: 10px 20px; @@ -38,11 +40,23 @@ font-weight: bold; } .form-group input, - .form-group select { + .form-group select, + .form-group textarea { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; + box-sizing: border-box; + } + .form-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 15px; + } + .form-row-3 { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 15px; } .btn { padding: 10px 20px; @@ -59,6 +73,14 @@ background: #6c757d; color: white; } + .btn-danger { + background: #dc3545; + color: white; + } + .btn-small { + padding: 5px 10px; + font-size: 12px; + } table { width: 100%; border-collapse: collapse; @@ -73,81 +95,275 @@ table th { background: #f0f0f0; } + /* Add alternating row background for readability */ + table tbody tr:nth-child(odd) { + background: #ffffff; + } + table tbody tr:nth-child(even) { + background: #f8f9fb; + } + .filter-section { + background: #f9f9f9; + padding: 15px; + border-radius: 5px; + margin-bottom: 20px; + } + .modal-form { + display: none; + margin-top: 20px; + padding: 20px; + border: 2px solid #007bff; + border-radius: 5px; + background: white; + } + .info-box { + background: #e7f3ff; + padding: 15px; + border-left: 4px solid #007bff; + margin-bottom: 20px; + } + .year-item { + padding: 15px; + margin: 10px 0; + background: #f9f9f9; + border: 1px solid #ddd; + border-radius: 5px; + cursor: pointer; + transition: background 0.2s; + } + .year-item:hover { + background: #e8f4ff; + } + .year-item.expanded { + background: #e8f4ff; + border-color: #007bff; + } + .year-header { + font-size: 18px; + font-weight: bold; + color: #333; + } + .year-data { + display: none; + margin-top: 15px; + } + .year-item.expanded .year-data { + display: block; + } + .filter-controls { + margin-bottom: 15px; + padding: 10px; + background: #fff; + border: 1px solid #ddd; + border-radius: 4px; + } + .back-link { + display: inline-block; + margin-bottom: 20px; + color: #007bff; + text-decoration: none; + font-size: 14px; + } + .back-link:hover { + text-decoration: underline; + }
+ +

給与管理 - 設定

社会保険料率設定

+ +
+ 設定のポイント: +
    +
  • 年度ごとに設定します(複数の料率を入力可能)
  • +
  • 給与計算と賞与計算で異なる料率を設定できます
  • +
  • 事業所所在地(都道府県)ごとに設定できます
  • +
  • 介護保険の適用年齢は設定可能です(デフォルト: 40歳以上)
  • +
+
+ + +
+
+
+ + + ※空欄で全て表示 +
+
+ + +
+
+ + +
+
+
+
-
-

保険料率の登録

-
-
- - + + +
+

子ども・子育て拠出金率設定

+ +
+ 子ども・子育て拠出金について: +
    +
  • 事業主のみが負担する拠出金です(従業員負担なし)
  • +
  • 標準報酬月額の上限額を設定できます
  • +
  • 年度ごとに料率を設定します
  • +
+
+ + +
+ + +
+ + +
+

社会保険料上限設定

+ +
+ 上限設定について: +
    +
  • 健康保険と厚生年金の標準報酬月額上限を設定します
  • +
  • 通勤手当の非課税限度額を設定します
  • +
  • 年度ごとに変更される場合があります
  • +
+
+ + +
+ + +
+
-

所得税率表

-
- - -
+

所得税率表(年度1月~12月)

-
+
+
+
+ + +
+

標準報酬月額表(年度3月~翌年2月)

+
+ 標準報酬月額表について: +
    +
  • 健康保険・厚生年金保険の保険料額表をインポートします
  • +
  • 都道府県ごとの料率表に対応しています
  • +
  • 年度をクリックして展開してください
  • +
+
+ +
+ + +
+ +
diff --git a/frontend/payroll-calculation.html b/frontend/payroll-calculation.html index e7e8b61..116e09a 100644 --- a/frontend/payroll-calculation.html +++ b/frontend/payroll-calculation.html @@ -432,12 +432,14 @@
@@ -1480,13 +1482,14 @@ now.getFullYear() + "-" + String(now.getMonth() + 1).padStart(2, "0"); const filterYearEl = document.getElementById("filterYear"); if (filterYearEl) filterYearEl.value = now.getFullYear(); - document.getElementById("filterBonusYearMonth").value = ym; + const filterBonusYearEl = document.getElementById("filterBonusYear"); + if (filterBonusYearEl) filterBonusYearEl.value = now.getFullYear(); loadPayrolls(); // ===================================================== // タブ切り替え機能 // ===================================================== - let currentTab = "salary"; + var currentTab = "salary"; function switchTab(tab) { currentTab = tab; @@ -1526,20 +1529,10 @@ // 賞与計算関連 // ===================================================== async function loadBonus() { - const ym = document.getElementById("filterBonusYearMonth").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 year = document.getElementById("filterBonusYear").value; const params = new URLSearchParams(); - if (year) params.append("bonus_year", year); - if (month) params.append("bonus_month", month); + if (year) params.append("bonus_year", Number(year)); // employeeMapが空なら読み込む if (!employeeMap || Object.keys(employeeMap).length === 0) { @@ -2129,16 +2122,16 @@ ¥${Number(salary.employment_insurance).toLocaleString()} + 子ども・子育て支援金 + ¥${Number(salary.child_support || 0).toLocaleString()} 所得税 ¥${Number(salary.income_tax).toLocaleString()} - 住民税 - ¥${Number(salary.resident_tax).toLocaleString()} + 住民税 + ¥${Number(salary.resident_tax).toLocaleString()} その他控除 ¥${Number(salary.other_deduction).toLocaleString()} - - 総控除額 @@ -2215,10 +2208,16 @@ ¥${Number(bonus.employment_insurance).toLocaleString()} + 子ども・子育て支援金 + ¥${Number(bonus.child_support || 0).toLocaleString()} 所得税 ¥${Number(bonus.income_tax).toLocaleString()} + + その他控除 ¥${Number(bonus.other_deduction).toLocaleString()} + + 総控除額 diff --git a/frontend/payroll-settings.html b/frontend/payroll-settings.html index dea9f67..c9e2bec 100644 --- a/frontend/payroll-settings.html +++ b/frontend/payroll-settings.html @@ -300,6 +300,9 @@ +
@@ -795,6 +798,7 @@ const rates = { healthNoCareRate: metadata.health_no_care_rate, healthWithCareRate: metadata.health_with_care_rate, + childSupportRate: metadata.child_support_rate, pensionRate: metadata.pension_rate, }; @@ -885,26 +889,34 @@ 報酬月額 未満 健康保険料(介護保険なし) 健康保険料(介護保険あり) + 子ども・子育て支援金 厚生年金保険料 ${ - rates.healthNoCareRate !== undefined + rates.healthNoCareRate != null ? rates.healthNoCareRate.toFixed(2) + "%" : "-" } ${ - rates.healthWithCareRate !== undefined + rates.healthWithCareRate != null ? rates.healthWithCareRate.toFixed(2) + "%" : "-" } ${ - rates.pensionRate !== undefined + rates.childSupportRate != null + ? rates.childSupportRate.toFixed(4) + "%" + : "-" + } + + + ${ + rates.pensionRate != null ? rates.pensionRate.toFixed(2) + "%" : "-" } @@ -917,6 +929,8 @@ 折半額 全額 折半額 + 全額 + 折半額 `; @@ -924,13 +938,16 @@ ${adjustedData .map((row) => { - // 保険料を2で割って折半額を計算 + // 全額を2で割って折半額を計算 const healthNoCareHalf = ( parseFloat(row.health_insurance_no_care) / 2 ).toFixed(2); const healthWithCareHalf = ( parseFloat(row.health_insurance_with_care) / 2 ).toFixed(2); + const childSupportHalf = ( + parseFloat(row.child_support ?? 0) / 2 + ).toFixed(2); const pensionHalf = ( parseFloat(row.pension_insurance) / 2 ).toFixed(2); @@ -949,6 +966,8 @@ row.health_insurance_with_care ?? 0, ).toLocaleString()} ${Number(healthWithCareHalf ?? 0).toLocaleString()} + ${Number(row.child_support ?? 0).toLocaleString()} + ${Number(childSupportHalf).toLocaleString()} ${Number( row.pension_insurance ?? 0, ).toLocaleString()}