feat: 工资查询助手首版 v1.0.105

基于 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 测试全部通过
This commit is contained in:
skill-dev
2026-09-07 17:02:05 +08:00
commit 3db196e49d
205 changed files with 29927 additions and 0 deletions

View File

@@ -0,0 +1,147 @@
"""费率配置读取服务。
从 payroll_config 表读取指定地区的费率;地区未单独插入时回退到全国默认。
"""
from __future__ import annotations
import json
from typing import Any
from db.connection import get_conn
from util.logging_config import get_skill_logger
log = get_skill_logger()
def get_config_value(config_key: str, region: str = "default") -> float | str | None:
"""读取单个配置项的值。
优先取 (config_key, region);若该地区无记录,回退到 (config_key, 'default')。
"""
conn = get_conn()
try:
cur = conn.cursor()
cur.execute(
"SELECT config_value FROM payroll_config WHERE config_key=? AND region=?",
(config_key, region),
)
row = cur.fetchone()
if row is None and region != "default":
cur.execute(
"SELECT config_value FROM payroll_config WHERE config_key=? AND region='default'",
(config_key,),
)
row = cur.fetchone()
if row is None:
return None
return row[0]
finally:
conn.close()
def get_tax_brackets(region: str = "default") -> list[dict[str, Any]]:
"""读取 7 级累计预扣税率表。"""
raw = get_config_value("tax_brackets_json", region)
if raw is None or not isinstance(raw, str):
return []
try:
brackets = json.loads(raw)
return brackets if isinstance(brackets, list) else []
except (json.JSONDecodeError, TypeError):
return []
def get_region_config(region: str = "default") -> dict[str, Any]:
"""读取一个地区的全部费率配置,返回 dict。"""
keys = [
"social_pension_rate",
"social_medical_rate",
"social_unemployment_rate",
"social_base_min",
"social_base_max",
"housing_fund_rate",
"housing_fund_base_min",
"housing_fund_base_max",
"tax_threshold",
]
result: dict[str, Any] = {"region": region}
for key in keys:
result[key] = get_config_value(key, region)
result["tax_brackets"] = get_tax_brackets(region)
return result
def list_all_config(region: str | None = None) -> list[dict[str, Any]]:
"""列出费率配置(供 view-config Action)。
region 为 None 时列出全部地区;否则只列该地区(含回退默认值)。
"""
conn = get_conn()
try:
cur = conn.cursor()
if region is None:
cur.execute(
"SELECT config_key, region, config_value, display_name, description FROM payroll_config ORDER BY region, config_key"
)
else:
cur.execute(
"SELECT config_key, region, config_value, display_name, description FROM payroll_config WHERE region=? ORDER BY config_key",
(region,),
)
rows = cur.fetchall()
return [
{
"config_key": r[0],
"region": r[1],
"config_value": r[2],
"display_name": r[3],
"description": r[4],
}
for r in rows
]
finally:
conn.close()
def update_config_value(config_key: str, region: str, config_value: float) -> dict[str, Any]:
"""更新单个费率配置(供 edit-config Action)。
返回 {ok, skill, config_key, region, old_value, new_value}。
"""
log.info("config_update key=%s region=%s value=%s", config_key, region, config_value)
conn = get_conn()
try:
cur = conn.cursor()
cur.execute(
"SELECT config_value FROM payroll_config WHERE config_key=? AND region=?",
(config_key, region),
)
row = cur.fetchone()
if row is None:
return {
"ok": False,
"skill": "query-payslip-default",
"error_code": "CONFIG_NOT_FOUND",
"error": f"配置项 {config_key}(地区 {region})不存在",
}
old_value = row[0]
cur.execute(
"""
UPDATE payroll_config
SET config_value=?, updated_at=strftime('%s','now')
WHERE config_key=? AND region=?
""",
(config_value, config_key, region),
)
conn.commit()
return {
"ok": True,
"skill": "query-payslip-default",
"config_key": config_key,
"region": region,
"old_value": old_value,
"new_value": config_value,
}
finally:
conn.close()