diff --git a/backend/app/db_auto_migration.py b/backend/app/db_auto_migration.py
index 789e18c..92741d8 100644
--- a/backend/app/db_auto_migration.py
+++ b/backend/app/db_auto_migration.py
@@ -138,7 +138,14 @@ def run_auto_migration():
print("=" * 80 + "\n")
conn.close()
-
+
+ # ── ねんきんポータル テーブル作成 ──
+ try:
+ from app.modules.nenkin_portal.migration import create_nenkin_tables
+ create_nenkin_tables()
+ except Exception as e:
+ print(f"⚠️ ねんきんポータル テーブル作成エラー(無視): {e}")
+
except Exception as e:
print(f"\n❌ 無法連接到數據庫或執行遷移: {e}")
print("\n提示:")
diff --git a/backend/app/main.py b/backend/app/main.py
index 4fa8b61..b9564d5 100644
--- a/backend/app/main.py
+++ b/backend/app/main.py
@@ -124,6 +124,8 @@ app.include_router(payroll_vouchers_router)
from app.payroll.withholding_slip.router import router as withholding_slip_router
app.include_router(withholding_slip_router)
+from app.modules.nenkin_portal.router import router as nenkin_portal_router
+app.include_router(nenkin_portal_router)
from fastapi.staticfiles import StaticFiles
import os
diff --git a/backend/app/modules/nenkin_portal/__init__.py b/backend/app/modules/nenkin_portal/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/backend/app/modules/nenkin_portal/migration.py b/backend/app/modules/nenkin_portal/migration.py
new file mode 100644
index 0000000..0ad0c19
--- /dev/null
+++ b/backend/app/modules/nenkin_portal/migration.py
@@ -0,0 +1,119 @@
+"""
+ねんきんポータル DB テーブル作成マイグレーション
+"""
+import os
+from psycopg import connect
+
+
+def create_nenkin_tables():
+ conn = connect(
+ host=os.getenv("DB_HOST"),
+ port=int(os.getenv("DB_PORT", 5432)),
+ dbname=os.getenv("DB_NAME"),
+ user=os.getenv("DB_USER"),
+ password=os.getenv("DB_PASSWORD"),
+ )
+
+ with conn.cursor() as cur:
+ # ──────────────────────────────────
+ # 文書ヘッダーマスター(全種別共通)
+ # ──────────────────────────────────
+ cur.execute("""
+ CREATE TABLE IF NOT EXISTS nenkin_documents (
+ id SERIAL PRIMARY KEY,
+ document_no VARCHAR(50) UNIQUE NOT NULL,
+ document_type VARCHAR(10) NOT NULL,
+ document_type_name VARCHAR(100),
+ zip_filename VARCHAR(255),
+ office_name VARCHAR(200),
+ office_number VARCHAR(20),
+ office_kigo VARCHAR(50),
+ nenkin_jimusho VARCHAR(100),
+ purpose_year INTEGER,
+ purpose_month INTEGER,
+ issue_date DATE,
+ uploaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+ )
+ """)
+
+ # ──────────────────────────────────
+ # 社会保険料額情報 (SHAKAI)
+ # ──────────────────────────────────
+ cur.execute("""
+ CREATE TABLE IF NOT EXISTS nenkin_shakai_hokenryo (
+ id SERIAL PRIMARY KEY,
+ document_id INTEGER REFERENCES nenkin_documents(id) ON DELETE CASCADE,
+ kenkou_hokenryo NUMERIC,
+ kousei_nenkin_hokenryo NUMERIC,
+ kodomo_kyoshutsukin NUMERIC,
+ total NUMERIC,
+ nofu_kigen_date DATE,
+ office_postcode VARCHAR(10),
+ office_address VARCHAR(300)
+ )
+ """)
+
+ # ──────────────────────────────────
+ # 保険料納入告知額・領収済額通知書 (NOUNYU)
+ # ──────────────────────────────────
+ cur.execute("""
+ CREATE TABLE IF NOT EXISTS nenkin_nounyu_tsuchi (
+ id SERIAL PRIMARY KEY,
+ document_id INTEGER REFERENCES nenkin_documents(id) ON DELETE CASCADE,
+ kokuchi_kenkou NUMERIC,
+ kokuchi_kounen NUMERIC,
+ kokuchi_kodomo NUMERIC,
+ kokuchi_total NUMERIC,
+ kokuchi_nofu_kigen DATE,
+ ryoshu_kenkou NUMERIC,
+ ryoshu_kounen NUMERIC,
+ ryoshu_kodomo NUMERIC,
+ ryoshu_total NUMERIC,
+ ryoshu_date DATE,
+ ryoshu_nofu_year INTEGER,
+ ryoshu_nofu_month INTEGER,
+ office_postcode VARCHAR(10),
+ office_address VARCHAR(300),
+ office_name_line1 VARCHAR(200),
+ office_name_line2 VARCHAR(200)
+ )
+ """)
+
+ # ──────────────────────────────────
+ # 賞与保険料算出内訳書 ヘッダー (SHOYO)
+ # ──────────────────────────────────
+ cur.execute("""
+ CREATE TABLE IF NOT EXISTS nenkin_shoyo_header (
+ id SERIAL PRIMARY KEY,
+ document_id INTEGER REFERENCES nenkin_documents(id) ON DELETE CASCADE,
+ jin_in_num INTEGER,
+ menjyo_hokenryo_ritsu VARCHAR(50),
+ page_num INTEGER
+ )
+ """)
+
+ # ──────────────────────────────────
+ # 賞与保険料算出内訳書 明細 (SHOYO)
+ # ──────────────────────────────────
+ cur.execute("""
+ CREATE TABLE IF NOT EXISTS nenkin_shoyo_detail (
+ id SERIAL PRIMARY KEY,
+ document_id INTEGER REFERENCES nenkin_documents(id) ON DELETE CASCADE,
+ header_id INTEGER REFERENCES nenkin_shoyo_header(id) ON DELETE CASCADE,
+ seiri_num VARCHAR(20),
+ shimei VARCHAR(100),
+ shori_ymd VARCHAR(20),
+ hassei_ymd VARCHAR(20),
+ hyojun_shoyo_kenpo NUMERIC,
+ hyojun_shoyo_kounen NUMERIC,
+ kenpo_hongetsu NUMERIC,
+ kenpo_zengetsu NUMERIC,
+ kounen_hongetsu NUMERIC,
+ kounen_zengetsu NUMERIC,
+ page_num INTEGER
+ )
+ """)
+
+ conn.commit()
+ conn.close()
+ print("✓ ねんきんポータル テーブル作成完了")
diff --git a/backend/app/modules/nenkin_portal/router.py b/backend/app/modules/nenkin_portal/router.py
new file mode 100644
index 0000000..349b5b6
--- /dev/null
+++ b/backend/app/modules/nenkin_portal/router.py
@@ -0,0 +1,281 @@
+"""
+ねんきんポータル API ルーター
+"""
+from fastapi import APIRouter, UploadFile, File, HTTPException
+from app.core.database import get_connection
+from app.modules.nenkin_portal.service import parse_zip
+
+router = APIRouter(prefix="/nenkin", tags=["ねんきんポータル"])
+
+
+# ──────────────────────────────────────
+# POST /nenkin/upload
+# ──────────────────────────────────────
+
+@router.post("/upload", summary="社会保険電子文書 ZIPアップロード")
+async def upload_nenkin_zip(file: UploadFile = File(...)):
+ if not file.filename.lower().endswith('.zip'):
+ raise HTTPException(status_code=400, detail="ZIPファイルを選択してください")
+
+ data = await file.read()
+
+ try:
+ parsed = parse_zip(data, file.filename)
+ except ValueError as e:
+ raise HTTPException(status_code=400, detail=str(e))
+ except Exception as e:
+ raise HTTPException(status_code=400, detail=f"ZIPの解析に失敗しました: {e}")
+
+ doc_no = parsed['document_no']
+ if not doc_no:
+ raise HTTPException(status_code=400, detail="文書番号が取得できませんでした")
+
+ with get_connection() as conn, conn.cursor() as cur:
+ # 重複チェック
+ cur.execute(
+ "SELECT id FROM nenkin_documents WHERE document_no = %s",
+ (doc_no,)
+ )
+ if cur.fetchone():
+ raise HTTPException(
+ status_code=409,
+ detail=f"文書番号 {doc_no} は既に登録されています"
+ )
+
+ # nenkin_documents に挿入
+ cur.execute("""
+ INSERT INTO nenkin_documents
+ (document_no, document_type, document_type_name, zip_filename,
+ office_name, office_number, office_kigo, nenkin_jimusho,
+ purpose_year, purpose_month, issue_date)
+ VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
+ RETURNING id
+ """, (
+ parsed['document_no'],
+ parsed['document_type'],
+ parsed['document_type_name'],
+ parsed['zip_filename'],
+ parsed['office_name'],
+ parsed['office_number'],
+ parsed['office_kigo'],
+ parsed['nenkin_jimusho'],
+ parsed['purpose_year'],
+ parsed['purpose_month'],
+ parsed['issue_date'],
+ ))
+ doc_id = cur.fetchone()['id']
+
+ detail = parsed['detail']
+
+ if parsed['document_type'] == 'SHAKAI':
+ cur.execute("""
+ INSERT INTO nenkin_shakai_hokenryo
+ (document_id, kenkou_hokenryo, kousei_nenkin_hokenryo,
+ kodomo_kyoshutsukin, total, nofu_kigen_date,
+ office_postcode, office_address)
+ VALUES (%s,%s,%s,%s,%s,%s,%s,%s)
+ """, (
+ doc_id,
+ detail['kenkou_hokenryo'],
+ detail['kousei_nenkin_hokenryo'],
+ detail['kodomo_kyoshutsukin'],
+ detail['total'],
+ detail['nofu_kigen_date'],
+ detail['office_postcode'],
+ detail['office_address'],
+ ))
+
+ elif parsed['document_type'] == 'NOUNYU':
+ cur.execute("""
+ INSERT INTO nenkin_nounyu_tsuchi
+ (document_id,
+ kokuchi_kenkou, kokuchi_kounen, kokuchi_kodomo, kokuchi_total, kokuchi_nofu_kigen,
+ ryoshu_kenkou, ryoshu_kounen, ryoshu_kodomo, ryoshu_total, ryoshu_date,
+ ryoshu_nofu_year, ryoshu_nofu_month,
+ office_postcode, office_address, office_name_line1, office_name_line2)
+ VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
+ """, (
+ doc_id,
+ detail['kokuchi_kenkou'], detail['kokuchi_kounen'],
+ detail['kokuchi_kodomo'], detail['kokuchi_total'],
+ detail['kokuchi_nofu_kigen'],
+ detail['ryoshu_kenkou'], detail['ryoshu_kounen'],
+ detail['ryoshu_kodomo'], detail['ryoshu_total'],
+ detail['ryoshu_date'],
+ detail['ryoshu_nofu_year'], detail['ryoshu_nofu_month'],
+ detail['office_postcode'], detail['office_address'],
+ detail['office_name_line1'], detail['office_name_line2'],
+ ))
+
+ elif parsed['document_type'] == 'SHOYO':
+ cur.execute("""
+ INSERT INTO nenkin_shoyo_header
+ (document_id, jin_in_num, menjyo_hokenryo_ritsu, page_num)
+ VALUES (%s,%s,%s,%s)
+ RETURNING id
+ """, (
+ doc_id,
+ detail['jin_in_num'],
+ detail['menjyo_hokenryo_ritsu'],
+ detail['page_num'],
+ ))
+ header_id = cur.fetchone()['id']
+
+ for item in detail['items']:
+ cur.execute("""
+ INSERT INTO nenkin_shoyo_detail
+ (document_id, header_id, seiri_num, shimei, shori_ymd, hassei_ymd,
+ hyojun_shoyo_kenpo, hyojun_shoyo_kounen,
+ kenpo_hongetsu, kenpo_zengetsu,
+ kounen_hongetsu, kounen_zengetsu, page_num)
+ VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
+ """, (
+ doc_id, header_id,
+ item['seiri_num'], item['shimei'],
+ item['shori_ymd'], item['hassei_ymd'],
+ item['hyojun_shoyo_kenpo'], item['hyojun_shoyo_kounen'],
+ item['kenpo_hongetsu'], item['kenpo_zengetsu'],
+ item['kounen_hongetsu'], item['kounen_zengetsu'],
+ item['page_num'],
+ ))
+
+ conn.commit()
+
+ return {
+ 'id': doc_id,
+ 'document_no': parsed['document_no'],
+ 'document_type': parsed['document_type'],
+ 'document_type_name': parsed['document_type_name'],
+ 'message': f"{parsed['document_type_name']} を登録しました",
+ }
+
+
+# ──────────────────────────────────────
+# GET /nenkin/documents
+# ──────────────────────────────────────
+
+@router.get("/documents", summary="文書一覧取得")
+def list_documents(doc_type: str = None, year: int = None, month: int = None):
+ conditions = []
+ params = []
+
+ if doc_type:
+ conditions.append("document_type = %s")
+ params.append(doc_type)
+ if year:
+ conditions.append("purpose_year = %s")
+ params.append(year)
+ if month:
+ conditions.append("purpose_month = %s")
+ params.append(month)
+
+ where = ("WHERE " + " AND ".join(conditions)) if conditions else ""
+
+ with get_connection() as conn, conn.cursor() as cur:
+ cur.execute(f"""
+ SELECT id, document_no, document_type, document_type_name,
+ zip_filename, office_name, office_number, office_kigo,
+ nenkin_jimusho, purpose_year, purpose_month,
+ issue_date, uploaded_at
+ FROM nenkin_documents
+ {where}
+ ORDER BY purpose_year DESC, purpose_month DESC, document_type, uploaded_at DESC
+ """, params)
+ rows = cur.fetchall()
+
+ result = []
+ for r in rows:
+ item = dict(r)
+ if item.get('issue_date'):
+ item['issue_date'] = item['issue_date'].isoformat()
+ if item.get('uploaded_at'):
+ item['uploaded_at'] = item['uploaded_at'].isoformat()
+ result.append(item)
+ return result
+
+
+# ──────────────────────────────────────
+# GET /nenkin/documents/{doc_id}
+# ──────────────────────────────────────
+
+@router.get("/documents/{doc_id}", summary="文書詳細取得")
+def get_document(doc_id: int):
+ with get_connection() as conn, conn.cursor() as cur:
+ cur.execute("SELECT * FROM nenkin_documents WHERE id = %s", (doc_id,))
+ doc = cur.fetchone()
+ if not doc:
+ raise HTTPException(status_code=404, detail="文書が見つかりません")
+
+ doc = dict(doc)
+ if doc.get('issue_date'):
+ doc['issue_date'] = doc['issue_date'].isoformat()
+ if doc.get('uploaded_at'):
+ doc['uploaded_at'] = doc['uploaded_at'].isoformat()
+
+ detail = {}
+ dtype = doc['document_type']
+
+ if dtype == 'SHAKAI':
+ cur.execute(
+ "SELECT * FROM nenkin_shakai_hokenryo WHERE document_id = %s",
+ (doc_id,)
+ )
+ row = cur.fetchone()
+ if row:
+ detail = dict(row)
+ if detail.get('nofu_kigen_date'):
+ detail['nofu_kigen_date'] = detail['nofu_kigen_date'].isoformat()
+
+ elif dtype == 'NOUNYU':
+ cur.execute(
+ "SELECT * FROM nenkin_nounyu_tsuchi WHERE document_id = %s",
+ (doc_id,)
+ )
+ row = cur.fetchone()
+ if row:
+ detail = dict(row)
+ for df in ('kokuchi_nofu_kigen', 'ryoshu_date'):
+ if detail.get(df):
+ detail[df] = detail[df].isoformat()
+
+ elif dtype == 'SHOYO':
+ cur.execute(
+ "SELECT * FROM nenkin_shoyo_header WHERE document_id = %s",
+ (doc_id,)
+ )
+ hdr = cur.fetchone()
+ if hdr:
+ detail = dict(hdr)
+ cur.execute("""
+ SELECT * FROM nenkin_shoyo_detail
+ WHERE document_id = %s
+ ORDER BY CAST(NULLIF(TRIM(seiri_num), '') AS INTEGER) NULLS LAST
+ """, (doc_id,))
+ detail['items'] = [dict(r) for r in cur.fetchall()]
+
+ return {'document': doc, 'detail': detail}
+
+
+# ──────────────────────────────────────
+# GET /nenkin/filter-options
+# ──────────────────────────────────────
+
+@router.get("/filter-options", summary="フィルター選択肢取得")
+def get_filter_options():
+ with get_connection() as conn, conn.cursor() as cur:
+ cur.execute("""
+ SELECT DISTINCT purpose_year, purpose_month
+ FROM nenkin_documents
+ WHERE purpose_year IS NOT NULL
+ ORDER BY purpose_year DESC, purpose_month DESC
+ """)
+ periods = [dict(r) for r in cur.fetchall()]
+
+ cur.execute("""
+ SELECT DISTINCT document_type, document_type_name
+ FROM nenkin_documents
+ ORDER BY document_type
+ """)
+ types = [dict(r) for r in cur.fetchall()]
+
+ return {'periods': periods, 'types': types}
diff --git a/backend/app/modules/nenkin_portal/service.py b/backend/app/modules/nenkin_portal/service.py
new file mode 100644
index 0000000..2f163fc
--- /dev/null
+++ b/backend/app/modules/nenkin_portal/service.py
@@ -0,0 +1,351 @@
+"""
+ねんきんポータル ZIPパーサー
+対応文書種別:
+ SHAKAI - 社会保険料額情報
+ NOUNYU - 保険料納入告知額・領収済額通知書
+ SHOYO - 賞与保険料算出内訳書
+"""
+import zipfile
+import io
+import re
+import xml.etree.ElementTree as ET
+from datetime import date
+from typing import Optional, Dict, Any
+
+# ──────────────────────────────────────
+# 元号オフセット
+# ──────────────────────────────────────
+GENGOU_OFFSETS = {
+ '令和': 2018, 'R': 2018,
+ '平成': 1988, 'H': 1988,
+ '昭和': 1925, 'S': 1925,
+ '大正': 1911, 'T': 1911,
+ '明治': 1867, 'M': 1867,
+}
+
+# XSLファイル名 → 文書種別
+XSL_TYPE_MAP = {
+ 'yoshiki_04_shakai_003.xsl': 'SHAKAI',
+ 'yoshiki_29_zumitsu_001.xsl': 'NOUNYU',
+ 'yoshiki_03_shoyo_002.xsl': 'SHOYO',
+}
+
+TYPE_NAMES = {
+ 'SHAKAI': '社会保険料額情報',
+ 'NOUNYU': '保険料納入告知額・領収済額通知書',
+ 'SHOYO': '賞与保険料算出内訳書',
+ 'HIHOKEN': '被保険者データ',
+ 'ZOGENS': '保険料増減内訳書',
+}
+
+
+# ──────────────────────────────────────
+# ユーティリティ
+# ──────────────────────────────────────
+
+def gengou_to_western(gengou: str, year_str) -> Optional[int]:
+ try:
+ offset = GENGOU_OFFSETS.get(str(gengou), 0)
+ return int(str(year_str)) + offset if offset else None
+ except Exception:
+ return None
+
+
+def make_date(gengou: str, year_str, month_str, day_str='1') -> Optional[date]:
+ try:
+ western = gengou_to_western(gengou, year_str)
+ if not western:
+ return None
+ return date(western, int(month_str), int(day_str or 1))
+ except Exception:
+ return None
+
+
+def parse_reki_date(s: str) -> Optional[date]:
+ """R080430 → date(2026,4,30)"""
+ if not s or len(s) < 7:
+ return None
+ prefix = s[0].upper()
+ try:
+ yy = int(s[1:3])
+ mm = int(s[3:5])
+ dd = int(s[5:7])
+ offset = GENGOU_OFFSETS.get(prefix, 0)
+ if not offset:
+ return None
+ return date(yy + offset, mm, dd)
+ except Exception:
+ return None
+
+
+def parse_amount(s) -> Optional[float]:
+ """'94,054' / '\\247,066' → float"""
+ if s is None:
+ return None
+ cleaned = str(s).replace(',', '').replace('\\', '').replace('¥', '').replace('¥', '').strip()
+ if not cleaned:
+ return None
+ try:
+ return float(cleaned)
+ except Exception:
+ return None
+
+
+def get_text(elem, tag: str) -> str:
+ if elem is None:
+ return ''
+ child = elem.find(tag)
+ return child.text.strip() if child is not None and child.text else ''
+
+
+# ──────────────────────────────────────
+# 文書種別の自動判定
+# ──────────────────────────────────────
+
+def detect_doc_type(names: list) -> str:
+ for name in names:
+ basename = name.split('/')[-1]
+ if basename in XSL_TYPE_MAP:
+ return XSL_TYPE_MAP[basename]
+ return 'UNKNOWN'
+
+
+def get_document_no(zf, names: list) -> str:
+ """封筒XML から文書番号を取得"""
+ envelope_name = next(
+ (n for n in names if re.match(r'.*/\d+\.xml$', n)),
+ None
+ )
+ if not envelope_name:
+ return ''
+ with zf.open(envelope_name) as f:
+ etree = ET.parse(f)
+ elem = etree.getroot().find('.//DOCNO')
+ return elem.text.strip() if elem is not None and elem.text else ''
+
+
+# ──────────────────────────────────────
+# メインエントリー
+# ──────────────────────────────────────
+
+def parse_zip(zip_bytes: bytes, zip_filename: str) -> Dict[str, Any]:
+ zf = zipfile.ZipFile(io.BytesIO(zip_bytes))
+ names = zf.namelist()
+ doc_type = detect_doc_type(names)
+
+ if doc_type == 'SHAKAI':
+ return _parse_shakai(zf, names, zip_filename)
+ elif doc_type == 'NOUNYU':
+ return _parse_nounyu(zf, names, zip_filename)
+ elif doc_type == 'SHOYO':
+ return _parse_shoyo(zf, names, zip_filename)
+ else:
+ raise ValueError(f"未対応の文書種別です(XSLが不明): {[n.split('/')[-1] for n in names]}")
+
+
+# ──────────────────────────────────────
+# 社会保険料額情報 (SHAKAI)
+# ──────────────────────────────────────
+
+def _parse_shakai(zf, names: list, zip_filename: str) -> Dict[str, Any]:
+ xml_name = next(
+ n for n in names
+ if '社会保険料額情報' in n.split('/')[-1] and n.endswith('.xml')
+ )
+ with zf.open(xml_name) as f:
+ root = ET.parse(f).getroot()
+
+ lst = root.find('shakaiHokenRyougakuList')
+ header = lst.find('header')
+
+ jigyosho_name = get_text(header, 'jigyoshoName').replace('
', ' ')
+ jigyosho_num = get_text(header, 'jigyoshoNum')
+ jigyosho_kigo = get_text(header, 'jigyoshoSeiriKigo')
+ nenkin_jimusho = get_text(header, 'nenkinJimusho1')
+ nofu_gengou = get_text(header, 'nouhuMokutekiYearGengou')
+ nofu_year = get_text(header, 'nouhuMokutekiYear')
+ nofu_month = get_text(header, 'nouhuMokutekiMonth')
+
+ hakkou_gengou = get_text(lst, 'hakkouYmdGengou')
+ hakkou_year = get_text(lst, 'hakkouYmdYear')
+ hakkou_month = get_text(lst, 'hakkouYmdMonth')
+ hakkou_day = get_text(lst, 'hakkouYmdDate')
+
+ nofu_kigen_gengou = get_text(lst, 'nouhuKigenGengou')
+ nofu_kigen_year = get_text(lst, 'nouhuKigenYear')
+ nofu_kigen_month = get_text(lst, 'nouhuKigenMonth')
+ nofu_kigen_day = get_text(lst, 'nouhuKigenDate')
+
+ return {
+ 'document_no': get_document_no(zf, names),
+ 'document_type': 'SHAKAI',
+ 'document_type_name': TYPE_NAMES['SHAKAI'],
+ 'zip_filename': zip_filename,
+ 'office_name': jigyosho_name,
+ 'office_number': jigyosho_num,
+ 'office_kigo': jigyosho_kigo,
+ 'nenkin_jimusho': nenkin_jimusho,
+ 'purpose_year': gengou_to_western(nofu_gengou, nofu_year),
+ 'purpose_month': int(nofu_month) if nofu_month else None,
+ 'issue_date': make_date(hakkou_gengou, hakkou_year, hakkou_month, hakkou_day),
+ 'detail': {
+ 'kenkou_hokenryo': parse_amount(get_text(lst, 'kenkouHokenRyou')),
+ 'kousei_nenkin_hokenryo': parse_amount(get_text(lst, 'kouseiNenkinHokenRyou')),
+ 'kodomo_kyoshutsukin': parse_amount(get_text(lst, 'kodomoKosodateKyoshutsuKin')),
+ 'total': parse_amount(get_text(lst, 'total')),
+ 'nofu_kigen_date': make_date(nofu_kigen_gengou, nofu_kigen_year, nofu_kigen_month, nofu_kigen_day),
+ 'office_postcode': get_text(lst, 'jigyoushoPostCode'),
+ 'office_address': get_text(lst, 'jigyoushoShozaichi').replace('
', ' '),
+ }
+ }
+
+
+# ──────────────────────────────────────
+# 保険料納入告知額・領収済額通知書 (NOUNYU)
+# ──────────────────────────────────────
+
+def _parse_nounyu(zf, names: list, zip_filename: str) -> Dict[str, Any]:
+ xml_name = next(
+ n for n in names
+ if '保険料納入告知額' in n.split('/')[-1] and n.endswith('.xml')
+ )
+ with zf.open(xml_name) as f:
+ root = ET.parse(f).getroot()
+
+ lst = root.find('hokenRyouRyoshuzumigakutsuchiShoList')
+ header = lst.find('header')
+
+ jigyosho_num = get_text(header, 'jigyoshoNum')
+ jigyosho_kigo = get_text(header, 'jigyoshoSeiriKigo')
+ nenkin_jimusho = get_text(header, 'nenkinJimusho1')
+ nofu_gengou = get_text(header, 'nouhuMokutekiYearGengou')
+ nofu_year = get_text(header, 'nouhuMokutekiYear')
+ nofu_month = get_text(header, 'nouhuMokutekiMonth')
+
+ hakkou_gengou = get_text(lst, 'hakkouYmdGengou')
+ hakkou_year = get_text(lst, 'hakkouYmdYear')
+ hakkou_month = get_text(lst, 'hakkouYmdMonth')
+ hakkou_day = get_text(lst, 'hakkouYmdDate')
+
+ nofu_kigen_gengou = get_text(lst, 'nouhuKigenGengou')
+ nofu_kigen_year = get_text(lst, 'nouhuKigenYear')
+ nofu_kigen_month = get_text(lst, 'nouhuKigenMonth')
+ nofu_kigen_day = get_text(lst, 'nouhuKigenDate')
+
+ ryoshu_gengou = get_text(lst, 'ryoshuDtGengo')
+ ryoshu_year = get_text(lst, 'ryoshuDtYear')
+ ryoshu_month = get_text(lst, 'ryoshuDtMonth')
+ ryoshu_day = get_text(lst, 'ryoshuDtDate')
+
+ ryoshu_nofu_gengou = get_text(lst, 'ryoshuNouhuMokutekiYmGengou')
+ ryoshu_nofu_year = get_text(lst, 'ryoshuNouhuMokutekiYmYear')
+ ryoshu_nofu_month = get_text(lst, 'ryoshuNouhuMokutekiYmMonth')
+
+ name1 = get_text(lst, 'jigyoshoNameLine1')
+ name2 = get_text(lst, 'jigyoshoNameLine2')
+ office_name = (name1 + ' ' + name2).strip()
+
+ return {
+ 'document_no': get_document_no(zf, names),
+ 'document_type': 'NOUNYU',
+ 'document_type_name': TYPE_NAMES['NOUNYU'],
+ 'zip_filename': zip_filename,
+ 'office_name': office_name,
+ 'office_number': jigyosho_num,
+ 'office_kigo': jigyosho_kigo,
+ 'nenkin_jimusho': nenkin_jimusho,
+ 'purpose_year': gengou_to_western(nofu_gengou, nofu_year),
+ 'purpose_month': int(nofu_month) if nofu_month else None,
+ 'issue_date': make_date(hakkou_gengou, hakkou_year, hakkou_month, hakkou_day),
+ 'detail': {
+ 'kokuchi_kenkou': parse_amount(get_text(lst, 'kenkouHokenRyou')),
+ 'kokuchi_kounen': parse_amount(get_text(lst, 'kouseiNenkinHokenRyou')),
+ 'kokuchi_kodomo': parse_amount(get_text(lst, 'kodomoKosodateKyoshutsuKin')),
+ 'kokuchi_total': parse_amount(get_text(lst, 'total')),
+ 'kokuchi_nofu_kigen': make_date(nofu_kigen_gengou, nofu_kigen_year, nofu_kigen_month, nofu_kigen_day),
+ 'ryoshu_kenkou': parse_amount(get_text(lst, 'ryoshuKenkouHokenRyou')),
+ 'ryoshu_kounen': parse_amount(get_text(lst, 'ryoshuKouseiNenkinHokenRyou')),
+ 'ryoshu_kodomo': parse_amount(get_text(lst, 'ryoshuKodomoKosodateKyoshutsuKin')),
+ 'ryoshu_total': parse_amount(get_text(lst, 'ryoshuTotal')),
+ 'ryoshu_date': make_date(ryoshu_gengou, ryoshu_year, ryoshu_month, ryoshu_day),
+ 'ryoshu_nofu_year': gengou_to_western(ryoshu_nofu_gengou, ryoshu_nofu_year),
+ 'ryoshu_nofu_month': int(ryoshu_nofu_month) if ryoshu_nofu_month else None,
+ 'office_postcode': get_text(lst, 'jigyoushoPostCode'),
+ 'office_address': (get_text(lst, 'jigyoushoShozaichiLine1') + ' ' + get_text(lst, 'jigyoushoShozaichiLine2')).strip(),
+ 'office_name_line1': name1,
+ 'office_name_line2': name2,
+ }
+ }
+
+
+# ──────────────────────────────────────
+# 賞与保険料算出内訳書 (SHOYO)
+# ──────────────────────────────────────
+
+def _parse_shoyo(zf, names: list, zip_filename: str) -> Dict[str, Any]:
+ # メインXML(.xmlかつ「賞与保険料算出内訳書」を含むもの)
+ xml_name = next(
+ n for n in names
+ if '賞与保険料算出内訳書' in n.split('/')[-1]
+ and n.endswith('.xml')
+ )
+ with zf.open(xml_name) as f:
+ root = ET.parse(f).getroot()
+
+ shoyo_list = root.find('shouyoList')
+ header = shoyo_list.find('header')
+
+ jigyosho_name = get_text(header, 'jigyoshoName')
+ jigyosho_num = get_text(header, 'jigyoshoNum')
+ jigyosho_kigo = get_text(header, 'jigyoshoSeiriKigo')
+ nenkin_jimusho = get_text(header, 'nenkinJimusho2').replace('\u3000', ' ').strip()
+ nofu_gengou = get_text(header, 'nouhuMokutekiYearGengou')
+ nofu_year = get_text(header, 'nouhuMokutekiYear')
+ nofu_month = get_text(header, 'nouhuMokutekiMonth')
+ jin_in = get_text(header, 'jinInNum')
+ menjyo = get_text(header, 'menjyoHokenRyouRitu')
+
+ # 各被保険者の明細
+ items = []
+ for uchiwake in shoyo_list.findall('uchiwake'):
+ hyojun = uchiwake.find('hyoujyunShouyoGaku')
+ kenpo_ryou = uchiwake.find('kenKouHokenRyou')
+ kounen_ryou = uchiwake.find('kouseiNenkinHokenRyou')
+
+ hassei_ymd = get_text(hyojun, 'hasseiYMD') if hyojun is not None else ''
+ parsed_hassei = parse_reki_date(hassei_ymd)
+
+ items.append({
+ 'seiri_num': get_text(uchiwake, 'seiriNum').strip(),
+ 'shimei': get_text(uchiwake, 'shimei'),
+ 'shori_ymd': get_text(uchiwake, 'shoriYMD'),
+ 'hassei_ymd': hassei_ymd,
+ 'hassei_date_iso': parsed_hassei.isoformat() if parsed_hassei else None,
+ 'hyojun_shoyo_kenpo': parse_amount(get_text(hyojun, 'getsuGakuKenpo') if hyojun is not None else ''),
+ 'hyojun_shoyo_kounen': parse_amount(get_text(hyojun, 'getsuGakuKounen') if hyojun is not None else ''),
+ 'kenpo_hongetsu': parse_amount(get_text(kenpo_ryou, 'hongetsuGaku') if kenpo_ryou is not None else ''),
+ 'kenpo_zengetsu': parse_amount(get_text(kenpo_ryou, 'zengetsuIzenMonth') if kenpo_ryou is not None else ''),
+ 'kounen_hongetsu': parse_amount(get_text(kounen_ryou, 'hongetsuGaku') if kounen_ryou is not None else ''),
+ 'kounen_zengetsu': parse_amount(get_text(kounen_ryou, 'zengetsuIzenMonth') if kounen_ryou is not None else ''),
+ 'page_num': int(get_text(uchiwake, 'pageNum') or 0),
+ })
+
+ return {
+ 'document_no': get_document_no(zf, names),
+ 'document_type': 'SHOYO',
+ 'document_type_name': TYPE_NAMES['SHOYO'],
+ 'zip_filename': zip_filename,
+ 'office_name': jigyosho_name,
+ 'office_number': jigyosho_num,
+ 'office_kigo': jigyosho_kigo,
+ 'nenkin_jimusho': nenkin_jimusho,
+ 'purpose_year': gengou_to_western(nofu_gengou, nofu_year),
+ 'purpose_month': int(nofu_month) if nofu_month else None,
+ 'issue_date': None,
+ 'detail': {
+ 'jin_in_num': int(jin_in) if jin_in else 0,
+ 'menjyo_hokenryo_ritsu': menjyo,
+ 'page_num': int(get_text(shoyo_list, 'pageNum') or 1),
+ 'items': items,
+ }
+ }
diff --git a/docs/1381260411286271_20260520233507.zip b/docs/1381260411286271_20260520233507.zip
new file mode 100644
index 0000000..cc94750
Binary files /dev/null and b/docs/1381260411286271_20260520233507.zip differ
diff --git a/docs/1381260411410876_20260520233324.zip b/docs/1381260411410876_20260520233324.zip
new file mode 100644
index 0000000..8d0be07
Binary files /dev/null and b/docs/1381260411410876_20260520233324.zip differ
diff --git a/docs/1381260511867049_20260520233223.zip b/docs/1381260511867049_20260520233223.zip
new file mode 100644
index 0000000..da17389
Binary files /dev/null and b/docs/1381260511867049_20260520233223.zip differ
diff --git a/docs/1381260511954054/202605200027318947.xml b/docs/1381260511954054/202605200027318947.xml
new file mode 100644
index 0000000..c55c199
--- /dev/null
+++ b/docs/1381260511954054/202605200027318947.xml
@@ -0,0 +1,87 @@
+
+
+ 親展
+ 要
+
+
+ kagami.xsl
+ 202605200027318947
+ 令和8年5月20日
+
+
+
+
+
+ 日本年金機構理事長
+ 日本年金機構
+
+ 日本年金機構からのお知らせ
+
+ 電子送付希望をいただいた賞与保険料算出内訳書を送付します。以下のファイルをご確認ください。
+ なお、ファイルにはPDF形式が含まれる場合があります。
+
+
+ 賞与保険料算出内訳書_令和8年4月分(202605200027318947).xml
+ 賞与保険料算出内訳書_令和8年4月分.xml
+
+
+ CSV形式のファイルを合わせて送付しますので、必要に応じてご活用ください。
+
+
+ 賞与保険料算出内訳書_令和8年4月分_1_ヘッダー(202605200027318947).csv
+ 賞与保険料算出内訳書_令和8年4月分_1_ヘッダー.csv
+
+
+ 賞与保険料算出内訳書_令和8年4月分_2_内訳(202605200027318947).csv
+ 賞与保険料算出内訳書_令和8年4月分_2_内訳.csv
+
+
+ 【事業主の皆さまへのご案内】<br/><br/>オンライン事業所年金情報サービスでは、社会保険料を口座振替で納付している事業主の方に「保険料納入告知額・領収済額通知書」の電子送付をしていますので、ぜひこの機会に電子送付の希望登録をお願いします。<br/><br/>また、オンライン事業所年金情報サービスを利用した被保険者データの受け取りは、これまで算定月・賞与支払予定月のみ可能でしたが、令和7年1月から希望する月で被保険者データを受け取ることができるようになりました。<br/><br/>詳細については、日本年金機構ホームページをご覧ください。<br/><a href="https://www.nenkin.go.jp/tokusetsu/online_jigyousho.html">https://www.nenkin.go.jp/tokusetsu/online_jigyousho.html</a><br/><br/>※上記は、既に保険料納入告知額・領収済額通知書の電子送付や被保険者データの送付希望月の登録を行っていただいている事業主の方にもご案内しています。
+
+
+kz6HKaSL017rn++5J8seilL/xWG8cAPeePZkufBJwuQ=kIv90NbjStXFCBW8Xch7MB/B/u7ryi8JyNGlCWTxqpQ=Nb2dzuuBriFQwaOFg1d4/MYPi25u944Wv2M2gVhuhpk=YEkZRD1HOwh5VgACxlE/WVJiDHjeOn90ow9QRUEf4uc=PEzU/pDPTyMB6nn245NqWLWp6sBoLTkk3Q8ZwQ1uPIw=qEcnefe23+rBZ+MpPw3qeEvePnoOJMtQyGJL/Bwjtaz+WPXVCdO6y+qPZJbjWetUo6iwi1TLrIwO
+SUkctAOYG+tshpaDkMVhsP/LWKrLIuZS9aVHB9+XDr5wUUQzs/rcTEiVMf/v9RuZ3lFZ91uObHQW
+RZuv3s27LMLgJm/3iJUWyLVjQV0jfHeG/qSIfJTihY68eId8zEVNdLu6FS/SeFX81UHJbhNOaFJH
+9wveJyLcd8Sdqtw09V31cvgHo1O4mMyYCB+97OfIPk6eRUaw6groo1dGJIX+rhkq7NWC0SOoDRmT
+hKRhC8epW/URlHa+uIvEtKomszXf2eCiJk1QQA==MIIFKTCCBBGgAwIBAgIPODUzNDEzNDYzOTk4NzA5MA0GCSqGSIb3DQEBCwUAMEYxCzAJBgNVBAYT
+AkpQMRwwGgYDVQQKDBNKYXBhbmVzZSBHb3Zlcm5tZW50MRkwFwYDVQQLDBBPZmZpY2lhbFN0YXR1
+c0NBMB4XDTIyMDUzMTE1MDAwMFoXDTI3MDUzMTE0NTk1OVowgYkxCzAJBgNVBAYTAkpQMRwwGgYD
+VQQKDBNKYXBhbmVzZSBHb3Zlcm5tZW50MS8wLQYDVQQLDCZNaW5pc3RyeSBvZiBIZWFsdGgsIExh
+Ym91ciBhbmQgV2VsZmFyZTErMCkGA1UEAwwiUHJlc2lkZW50IG9mIEphcGFuIFBlbnNpb24gU2Vy
+dmljZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALFcx67Yj+R7aDJLXozKT+D6ajGM
+eSLL46Zu/KLrcADNR2/4/kvznDy+cC6ekizUtR3j9JPC0UHt1ECmUAvmyX/KKQToNfFRguqkH30X
+nCm0LgE0yzoocreCLHb4Q5HL82ThM0hICUQRhE5/nKn6BPVEMVIi4WvkrF56LIhsxh9yiqbQzcMh
+2gbKJ9qwbXlf2t57vR5HrXuQxVmzhwh2miI/8tGBDRte6xBmQQ9iMStx/M7pC2FWuULAThA9zIop
+S7G8M2c9Quz3da7rfSWQX+Cr29G2geym2wGwBiZSLOr9Haua6kyT/38i6kWnjFz+V7f90ETGGIzP
+0s4mEBboG/8CAwEAAaOCAc4wggHKMB8GA1UdIwQYMBaAFFIm8Y7mfjbpZKQk94zYbSJcqFWZMB0G
+A1UdDgQWBBTtZSAS2Of86iDljw/HbL63ufMtezAOBgNVHQ8BAf8EBAMCBsAwVwYDVR0gAQH/BE0w
+SzBJBgsCgziGjjEIAwEBbjA6MDgGCCsGAQUFBwIBFixodHRwczovL3d3dy5ncGtpLmdvLmpwL29z
+Y2EvY3BjcHMvaW5kZXguaHRtbDB0BgNVHREEbTBrpGkwZzELMAkGA1UEBhMCSlAxGDAWBgNVBAoM
+D+aXpeacrOWbveaUv+W6nDEYMBYGA1UECwwP5Y6a55Sf5Yq05YON55yBMSQwIgYDVQQDDBvml6Xm
+nKzlubTph5HmqZ/mp4vnkIbkuovplbcwTgYDVR0SBEcwRaRDMEExCzAJBgNVBAYTAkpQMRgwFgYD
+VQQKDA/ml6XmnKzlm73mlL/lupwxGDAWBgNVBAsMD+WumOiBt+iqjeiovOWxgDBZBgNVHR8EUjBQ
+ME6gTKBKpEgwRjELMAkGA1UEBhMCSlAxHDAaBgNVBAoME0phcGFuZXNlIEdvdmVybm1lbnQxGTAX
+BgNVBAsMEE9mZmljaWFsU3RhdHVzQ0EwDQYJKoZIhvcNAQELBQADggEBAJapGHTU0z30nXPccxUf
+JHriV0vVs8RZaSm5gwXZKJDdsEG/pfjDYze28aNVzL496zqmCZED0XIdZlI5vfWR2QY0xUwzldYG
+phx0pyG1OzlQN8W247S5XsJ3eYXqcgc0qO8399vtKSHVCa4OjvFH3kEn/IfmsxWBT+10roe4HyDK
+ddkT1eMGu8ccCc8WCd2SoEwQmCERtV85+Jbf0AQ95opqGUQWxAT+0n6x1Gyi9ggKyU7ifonxGUOY
+nvAEIb2U+rebwvNwRN8V9G2W/LNIkiV57/Hs6H8P8ugfoNGWeAU1pzD4MfC4zuJg8veLELs+G6Xh
+cG+ewK5GiwhySdvKexs=MIIEBDCCAuygAwIBAgIPNzI4MDQyMzYwNDg2MzczMA0GCSqGSIb3DQEBCwUAMEYxCzAJBgNVBAYT
+AkpQMRwwGgYDVQQKDBNKYXBhbmVzZSBHb3Zlcm5tZW50MRkwFwYDVQQLDBBPZmZpY2lhbFN0YXR1
+c0NBMB4XDTE5MDgyMzE1MDAwMFoXDTI5MDgyMzE0NTk1OVowRjELMAkGA1UEBhMCSlAxHDAaBgNV
+BAoME0phcGFuZXNlIEdvdmVybm1lbnQxGTAXBgNVBAsMEE9mZmljaWFsU3RhdHVzQ0EwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkaCKUqxDTGx50NrhjVYKvvAYTlrmidWexGpna7JXx
+SQFi0umtGe904ZbZOwSld8gxXqRJCxLx64ZUtPG25hHgYnCfxhkIA+p3nomgkwGrauSHsdBU9co9
+ioKUAHPWpsp1CR2EFhE13NBBX2anxnqxil1KvInGOOl/Hf9mJ5Jz85oLgZHWxMDtRrKL2fPgXZ72
++YHv5QsyqZwFy3J9qPnQueUM7lSoYNidlyiIyhbtC8/68OH513nnksv+w65qEuXJh/LNj/y0ruAB
+V3EyjHbd4oWyf7HHD4JO8ITa5ABMt2kSc91IxaSeJDBvD0rUbf4bOSnshTyQaN/gJL7Wc4xZAgMB
+AAGjge4wgeswHQYDVR0OBBYEFFIm8Y7mfjbpZKQk94zYbSJcqFWZMA4GA1UdDwEB/wQEAwIBBjBO
+BgNVHREERzBFpEMwQTELMAkGA1UEBhMCSlAxGDAWBgNVBAoMD+aXpeacrOWbveaUv+W6nDEYMBYG
+A1UECwwP5a6Y6IG36KqN6Ki85bGAMFkGA1UdHwRSMFAwTqBMoEqkSDBGMQswCQYDVQQGEwJKUDEc
+MBoGA1UECgwTSmFwYW5lc2UgR292ZXJubWVudDEZMBcGA1UECwwQT2ZmaWNpYWxTdGF0dXNDQTAP
+BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCjy1OYTCLtF1mViU3uLdA2BtbOH+qa
+RqQkhJTT5Mbqblwhp+I1TaHwO7kT0Tnw74f4e2nkUd5ByMyodojFk3+/Xj1CMG56dYjDkC8GBR8+
+QdrEzDF4o6nui9xqMfzyevMFasaRdMSyWpeSjCquMJjkgc5l691Rwn2ixoEUol+tfhwmKVkQttUq
+Bwu5ofnwTF814RTMh4IJFQL4UloXRkRBduNh+xLvWf7sBjirI7FrLfkvkkz7YAS1ToGkEGSb+CU3
+8GHXwH3oul8/5SUZriNXUexKwLqzCjjqUsca2bDDP8A7myX7zdMNKWMhweSbOpedzaR+gF2Lrsx4
+wU0AP+AY
\ No newline at end of file
diff --git a/docs/1381260511954054/kagami.xsl b/docs/1381260511954054/kagami.xsl
new file mode 100644
index 0000000..7813c31
--- /dev/null
+++ b/docs/1381260511954054/kagami.xsl
@@ -0,0 +1,148 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ _blank
+
+
+
+
+
+
+
+
+ font-weight:bold; text-decoration:underline;
+
+
+
+
+
diff --git a/docs/1381260511954054/yoshiki_03_shoyo_002.xsl b/docs/1381260511954054/yoshiki_03_shoyo_002.xsl
new file mode 100644
index 0000000..a629e92
--- /dev/null
+++ b/docs/1381260511954054/yoshiki_03_shoyo_002.xsl
@@ -0,0 +1,733 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ 頁 |
+ |
+
+
+
+
+
+
+ |
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+ 健康保険
+ |
+ |
+
+ 賞与保険料算出内訳書
+ |
+ |
+
+
+ |
+
+ 厚生年金保険
+ |
+ |
+
+
+
+
+
+
+
+
+ | 処理年月日 |
+ 整 理 番 号 |
+ 表 示 |
+ 氏 名 |
+ 発生年月日 (賞与支払年月日) |
+ 標 準 賞 与 額 |
+ 健 康 保 険 料 |
+ 厚 生 年 金 保 険 料 |
+
+
+ | 健保[千円] |
+ 厚年[千円] |
+ 本 月 額[円] |
+ 前 月 以 前 額[円] |
+ 本 月 額[円] |
+ 前 月 以 前 額[円] |
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+
+
+
+
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+ 健康保険
+ |
+ |
+
+ 賞与保険料算出内訳書のみかた
+ |
+ |
+
+
+ |
+
+ 厚生年金保険
+ |
+
+
+
+
+
+
+
+
+ 1.表題の下には、年度、年月分、事業所名称、年金事務所名、事業所整理記号、事業所番号、人員及び頁を記入してあります。
+ なお、事業所名称が長い時は25字まで記入してあります。また人員には保険料計算時の現存被保険者数(健保法118条該当者、厚年法適用除外者、育児休業取得者、
+ 産前産後休業取得者を含む。)を記入してあります。
+ |
+
+
+
+ 2.賞与保険料算出内訳書には、健康保険料と厚生年金保険料別に被保険者個人ごとの被保険者賞与支払届等をもとに算出した賞与保険料を記入してあります。
+ なお、「二以上事業所勤務被保険者賞与保険料登録票」で登録された賞与保険料については出力されません。
+ |
+
+
+
+
+
+
+
+
+
+ | (1)処理年月日 |
+ 「処理年月日」欄は年金事務所で事務処理上使用する欄です。 |
+
+
+ | (2)整理番号 |
+ 被保険者整理番号を記入してあります。 |
+
+
+ | (3)表示 |
+
+ ①#:「随時保険料」の対象となった事を示してあります。
+ ②2: 厚生年金保険法適用除外者を示してあります。
+ ③3: 健康保険法適用除外者を示してあります。
+ ④4: 介護保険料徴収者を示してあります。
+ |
+
+
+ | (4)資格 |
+
+ ①発生年月日 (賞与支払年月日):賞与が支払われた年月日を記入してあります。
+ ②標準賞与額:発生年月日現在における標準賞与額を健康保険と厚生年金保険別に記入してあります。
+ |
+
+
+ | (5)「健康保険料」及び「厚生年金保険料」の欄 |
+
+ ①本 月 額:発生年月日(賞与支払年月日)が当月の賞与保険料(標準賞与額×保険料率)を、被保険者賞与支払届は符号なしで、被保険者
+ 賞与支払届(取消)は「-」を付して記入してあります。
+ ②前月以前額:発生年月日(賞与支払年月日)が前月以前の賞与保険料(標準賞与額×保険料率)を、被保険者賞与支払届は符号なしで、被保
+ 険者賞与支払届(取消)は「-」を付して記入してあります。 |
+
+
+ |
+
+
+ |
+ 3.記載内容について、わからないことがあるときは管轄の年金事務所に照会して下さい。
+ |
+
+
+
+
+
+
+
+
+
+ 賞与保険料算出内訳書
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/1381260511954054/賞与保険料算出内訳書_令和8年4月分(202605200027318947).xml b/docs/1381260511954054/賞与保険料算出内訳書_令和8年4月分(202605200027318947).xml
new file mode 100644
index 0000000..de4f246
--- /dev/null
+++ b/docs/1381260511954054/賞与保険料算出内訳書_令和8年4月分(202605200027318947).xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+ 新日本テクノソリューションズ 株式会社
+ 23090
+ 41-シサメ
+ 2
+ 墨田 年金事務所
+ 4
+ 8
+ 令和
+ 賞与保険料算出内訳書の概要や見方については日本年金機構ホームページをご確認ください。<br/>【概要・見方】<br/><a href="https://www.nenkin.go.jp/denshibenri/online_jigyousho/denshidata/tsuchisho.html#cms04">https://www.nenkin.go.jp/denshibenri/online_jigyousho/denshidata/tsuchisho.html#cms04</a>
+ 1
+ 8
+ 令和
+
+ 1
+
+ 4
+
+ 4300
+ 1500
+ R080430
+
+
+ 503100.0
+
+
+
+ 274500.00
+
+
+ 1
+ 2
+ 張 翔鶴
+ R080501
+
+ false
+
+ true
+
diff --git a/docs/1381260511954054/賞与保険料算出内訳書_令和8年4月分_1_ヘッダー(202605200027318947).csv b/docs/1381260511954054/賞与保険料算出内訳書_令和8年4月分_1_ヘッダー(202605200027318947).csv
new file mode 100644
index 0000000..70d3869
--- /dev/null
+++ b/docs/1381260511954054/賞与保険料算出内訳書_令和8年4月分_1_ヘッダー(202605200027318947).csv
@@ -0,0 +1,2 @@
+"y[Wԍ","@\̂m点","N1","N2","쐬Nx","쐬Nx","[tړIN","[tړIN","[tړI","Ə","ƏL","Əԍ","l","Əی"
+"1","ܗ^یZo̊Tv〈ɂĂ͓{N@\z[y[WmFB
yTvEz
https://www.nenkin.go.jp/denshibenri/online_jigyousho/denshidata/tsuchisho.html#cms04","","@@@nc@@@@N","ߘa","8","ߘa","8","4","V{eNm\[VY@","41-","23090","2",""
diff --git a/docs/1381260511954054/賞与保険料算出内訳書_令和8年4月分_2_内訳(202605200027318947).csv b/docs/1381260511954054/賞与保険料算出内訳書_令和8年4月分_2_内訳(202605200027318947).csv
new file mode 100644
index 0000000..372e180
--- /dev/null
+++ b/docs/1381260511954054/賞与保険料算出内訳書_令和8年4月分_2_内訳(202605200027318947).csv
@@ -0,0 +1,2 @@
+"[tړIN","N","ԍ","\","یҎ","Niܗ^xNj","Wܗ^zQ","Wܗ^zQN","NیQ{z","NیQOȑOz","NیQ{z","NیQOȑOz"
+"ߘa8N4","R080501","2"," 4","@Ē","R080430","4300","1500","503,100.0","","274,500.00",""
diff --git a/frontend/index.html b/frontend/index.html
index 11d7ef6..2eac87d 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -119,6 +119,16 @@
給与システムを開く
+
+ 📋 社会保険電子文書
+
+ 日本年金機構からダウンロードした ZIP ファイルを読み込み、社会保険料額・納入告知・賞与算出内訳などを確認できます。
+
+
+ 文書ポータルを開く
+
+
+
🏛 e-Gov 電子送達
diff --git a/frontend/nenkin-portal.html b/frontend/nenkin-portal.html
new file mode 100644
index 0000000..7ae7af4
--- /dev/null
+++ b/frontend/nenkin-portal.html
@@ -0,0 +1,735 @@
+
+
+
+
+
+ 社会保険電子文書ポータル
+
+
+
+
+
+
+
+
+
+
+
📥 社会保険 ZIP ファイルをここにドロップ
+
またはボタンから選択してください(複数ファイル対応)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | 種別 |
+ 対象年月 |
+ 事業所名 |
+ 整理記号・番号 |
+ 年金事務所 |
+ 発行日 |
+ 登録日時 |
+
+
+
+ | データを読み込んでいます... |
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/payroll.html b/frontend/payroll.html
index 20094b9..658804e 100644
--- a/frontend/payroll.html
+++ b/frontend/payroll.html
@@ -119,6 +119,15 @@
源泉徴収票
給与所得の源泉徴収票を発行します(本人用・区役所用・税務署用 各1部)
+
+