基于 skill-template 脚手架开发,包含: - payroll_config(费率配置,可编辑)+ payslip_snapshots(解析历史,只读)2 张业务表 - 全国默认 + 北上广 4 套费率(社保/公积金/个税起征点/7 级税率表) - 5 个业务 Action:calculate-payslip-simple / calculate-payslip-full / explain-term / view-config / edit-config - 个税累计预扣法计算,支持多月历史累加 - 20 个薪资名词库(社保/公积金/个税/全勤/加班费等) - 完整市场文档(README/TUTORIAL/DEMO/CHANGELOG) - 296 测试全部通过
281 lines
9.5 KiB
Python
281 lines
9.5 KiB
Python
"""SQLite 连接与幂等初始化。
|
||
|
||
模板默认只建数据管理元数据表。业务表由复制后的技能自行 CREATE,并同步展示元数据。
|
||
本技能建两张业务表:payroll_config(费率配置,可编辑)与 payslip_snapshots(解析历史,只读)。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import sqlite3
|
||
|
||
from db.display_metadata import (
|
||
init_display_metadata,
|
||
seed_column_metadata_batch,
|
||
upsert_table_metadata,
|
||
)
|
||
from db.seed_config import seed_default_config
|
||
from util.runtime_paths import get_db_path
|
||
|
||
|
||
def get_conn() -> sqlite3.Connection:
|
||
return sqlite3.connect(get_db_path())
|
||
|
||
|
||
_PAYROLL_CONFIG_DDL = """
|
||
CREATE TABLE IF NOT EXISTS payroll_config (
|
||
config_key TEXT NOT NULL,
|
||
region TEXT NOT NULL DEFAULT 'default',
|
||
config_value REAL NOT NULL,
|
||
display_name TEXT NOT NULL,
|
||
description TEXT,
|
||
effective_date INTEGER,
|
||
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
||
updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
||
PRIMARY KEY (config_key, region)
|
||
)
|
||
"""
|
||
|
||
_PAYROLL_CONFIG_UPDATED_AT_TRIGGER = """
|
||
CREATE TRIGGER IF NOT EXISTS payroll_config_set_updated_at
|
||
AFTER UPDATE ON payroll_config
|
||
FOR EACH ROW
|
||
BEGIN
|
||
UPDATE payroll_config SET updated_at = unixepoch() WHERE config_key = NEW.config_key AND region = NEW.region;
|
||
END
|
||
"""
|
||
|
||
_PAYSLIP_SNAPSHOTS_DDL = """
|
||
CREATE TABLE IF NOT EXISTS payslip_snapshots (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
snapshot_date INTEGER NOT NULL,
|
||
period TEXT,
|
||
base_salary REAL,
|
||
gross_total REAL NOT NULL,
|
||
deduction_total REAL NOT NULL,
|
||
net_salary REAL NOT NULL,
|
||
reported_net REAL,
|
||
check_match INTEGER,
|
||
check_diff REAL,
|
||
detail_json TEXT,
|
||
raw_payload TEXT,
|
||
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
||
)
|
||
"""
|
||
|
||
|
||
_PAYROLL_CONFIG_COLUMNS: tuple[dict[str, object], ...] = (
|
||
{
|
||
"column_name": "config_key",
|
||
"display_name": "配置项",
|
||
"description": "费率配置键,如 social_pension_rate / tax_threshold。",
|
||
"visible": 1,
|
||
"searchable": 1,
|
||
"editable": 0,
|
||
},
|
||
{
|
||
"column_name": "region",
|
||
"display_name": "地区",
|
||
"description": "费率适用地区:default(全国默认)/ beijing / shanghai / guangzhou。",
|
||
"visible": 1,
|
||
"searchable": 1,
|
||
"editable": 0,
|
||
"options_json": '[{"value":"default","label":"全国默认"},{"value":"beijing","label":"北京"},{"value":"shanghai","label":"上海"},{"value":"guangzhou","label":"广州"}]',
|
||
},
|
||
{
|
||
"column_name": "config_value",
|
||
"display_name": "数值",
|
||
"description": "费率数值(比例为小数,如 0.08 表示 8%)。",
|
||
"visible": 1,
|
||
"searchable": 1,
|
||
"editable": 1,
|
||
"display_type": "number",
|
||
},
|
||
{
|
||
"column_name": "display_name",
|
||
"display_name": "中文名",
|
||
"description": "配置项的中文展示名。",
|
||
"visible": 1,
|
||
"searchable": 1,
|
||
"editable": 1,
|
||
},
|
||
{
|
||
"column_name": "description",
|
||
"display_name": "说明",
|
||
"description": "配置项的详细说明。",
|
||
"visible": 1,
|
||
"searchable": 0,
|
||
"editable": 1,
|
||
"display_type": "textarea",
|
||
},
|
||
{
|
||
"column_name": "effective_date",
|
||
"display_name": "生效日期",
|
||
"description": "该费率的生效日期(Unix 秒级时间戳)。",
|
||
"visible": 1,
|
||
"searchable": 1,
|
||
"editable": 1,
|
||
"display_type": "datetime_unix_seconds",
|
||
},
|
||
{
|
||
"column_name": "created_at",
|
||
"display_name": "创建时间",
|
||
"description": "记录创建时间(Unix 秒级时间戳,自动生成)。",
|
||
"visible": 1,
|
||
"searchable": 0,
|
||
"editable": 0,
|
||
"display_type": "datetime_unix_seconds",
|
||
},
|
||
{
|
||
"column_name": "updated_at",
|
||
"display_name": "更新时间",
|
||
"description": "记录最后更新时间(Unix 秒级时间戳,自动维护)。",
|
||
"visible": 1,
|
||
"searchable": 0,
|
||
"editable": 0,
|
||
"display_type": "datetime_unix_seconds",
|
||
},
|
||
)
|
||
|
||
|
||
_PAYSLIP_SNAPSHOTS_COLUMNS: tuple[dict[str, object], ...] = (
|
||
{"column_name": "id", "display_name": "编号", "description": "主键", "visible": 1, "searchable": 1, "editable": 0},
|
||
{
|
||
"column_name": "snapshot_date",
|
||
"display_name": "解析时间",
|
||
"description": "本次解析执行的时间(Unix 秒级时间戳)。",
|
||
"visible": 1, "searchable": 1, "editable": 0,
|
||
"display_type": "datetime_unix_seconds",
|
||
},
|
||
{
|
||
"column_name": "period",
|
||
"display_name": "工资期间",
|
||
"description": "工资所属期间,如 2026-08。",
|
||
"visible": 1, "searchable": 1, "editable": 0,
|
||
},
|
||
{
|
||
"column_name": "base_salary",
|
||
"display_name": "基本工资",
|
||
"description": "本次解析的基本工资。",
|
||
"visible": 1, "searchable": 1, "editable": 0,
|
||
"display_type": "number",
|
||
},
|
||
{
|
||
"column_name": "gross_total",
|
||
"display_name": "应发合计",
|
||
"description": "本次核算的应发合计。",
|
||
"visible": 1, "searchable": 1, "editable": 0,
|
||
"display_type": "number",
|
||
},
|
||
{
|
||
"column_name": "deduction_total",
|
||
"display_name": "扣除合计",
|
||
"description": "本次核算的扣除合计。",
|
||
"visible": 1, "searchable": 1, "editable": 0,
|
||
"display_type": "number",
|
||
},
|
||
{
|
||
"column_name": "net_salary",
|
||
"display_name": "实发工资",
|
||
"description": "本次核算的实发工资(应发-扣除)。",
|
||
"visible": 1, "searchable": 1, "editable": 0,
|
||
"display_type": "number",
|
||
},
|
||
{
|
||
"column_name": "reported_net",
|
||
"display_name": "用户自报实发",
|
||
"description": "用户给出的实发金额,用于核对。",
|
||
"visible": 1, "searchable": 1, "editable": 0,
|
||
"display_type": "number",
|
||
},
|
||
{
|
||
"column_name": "check_match",
|
||
"display_name": "核对一致",
|
||
"description": "1=用户自报与计算一致,0=不一致,NULL=未提供自报。",
|
||
"visible": 1, "searchable": 1, "editable": 0,
|
||
},
|
||
{
|
||
"column_name": "check_diff",
|
||
"display_name": "差异金额",
|
||
"description": "用户自报 - 计算实发。正数表示用户多报,负数表示少报。",
|
||
"visible": 1, "searchable": 1, "editable": 0,
|
||
"display_type": "number",
|
||
},
|
||
{
|
||
"column_name": "detail_json",
|
||
"display_name": "完整明细",
|
||
"description": "本次核算的完整明细(JSON 字符串),含各项应发/扣除/估算。",
|
||
"visible": 1, "searchable": 0, "editable": 0,
|
||
"display_type": "textarea",
|
||
},
|
||
{
|
||
"column_name": "raw_payload",
|
||
"display_name": "原始文本",
|
||
"description": "用户粘贴的原始工资条文本(隐私保护,默认隐藏)。",
|
||
"visible": 0, "searchable": 0, "editable": 0,
|
||
"display_type": "textarea",
|
||
},
|
||
{
|
||
"column_name": "created_at",
|
||
"display_name": "创建时间",
|
||
"description": "记录入库时间(Unix 秒级时间戳,自动生成)。",
|
||
"visible": 1, "searchable": 0, "editable": 0,
|
||
"display_type": "datetime_unix_seconds",
|
||
},
|
||
)
|
||
|
||
|
||
def _init_payroll_config_metadata(cursor) -> None:
|
||
"""建 payroll_config 表 + 展示元数据。"""
|
||
cursor.execute(_PAYROLL_CONFIG_DDL)
|
||
cursor.execute(_PAYROLL_CONFIG_UPDATED_AT_TRIGGER)
|
||
upsert_table_metadata(
|
||
cursor,
|
||
table_name="payroll_config",
|
||
display_name="费率配置",
|
||
description="社保/公积金/个税等费率配置,按地区区分。管理员可编辑数值,影响估算结果。",
|
||
grid_help="按地区查看费率;双击数值可编辑(仅管理员)。地区 default 为全国默认。",
|
||
visible=1,
|
||
readonly=0,
|
||
)
|
||
seed_column_metadata_batch(
|
||
cursor,
|
||
table_name="payroll_config",
|
||
columns=_PAYROLL_CONFIG_COLUMNS,
|
||
valid_column_names=[str(c["column_name"]) for c in _PAYROLL_CONFIG_COLUMNS],
|
||
)
|
||
|
||
|
||
def _init_payslip_snapshots_metadata(cursor) -> None:
|
||
"""建 payslip_snapshots 表 + 展示元数据(只读)。"""
|
||
cursor.execute(_PAYSLIP_SNAPSHOTS_DDL)
|
||
upsert_table_metadata(
|
||
cursor,
|
||
table_name="payslip_snapshots",
|
||
display_name="解析历史",
|
||
description="本人工资条解析历史记录。每次解析自动写入,可回顾/对比多月。",
|
||
grid_help="按工资期间或时间排序查看历史;raw_payload 默认隐藏。",
|
||
visible=1,
|
||
readonly=1,
|
||
default_sort_json='[{"column":"snapshot_date","direction":"desc"},{"column":"id","direction":"desc"}]',
|
||
default_visible_columns_json='["snapshot_date","period","base_salary","gross_total","deduction_total","net_salary","check_match"]',
|
||
)
|
||
seed_column_metadata_batch(
|
||
cursor,
|
||
table_name="payslip_snapshots",
|
||
columns=_PAYSLIP_SNAPSHOTS_COLUMNS,
|
||
valid_column_names=[str(c["column_name"]) for c in _PAYSLIP_SNAPSHOTS_COLUMNS],
|
||
)
|
||
|
||
|
||
def init_db() -> None:
|
||
conn = get_conn()
|
||
try:
|
||
cur = conn.cursor()
|
||
init_display_metadata(cur)
|
||
_init_payroll_config_metadata(cur)
|
||
_init_payslip_snapshots_metadata(cur)
|
||
seed_default_config(cur)
|
||
conn.commit()
|
||
finally:
|
||
conn.close()
|