Files
skill-dev 3db196e49d 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 测试全部通过
2026-09-07 17:02:05 +08:00

210 lines
8.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""CLI:argparse 与分发模板。
只负责参数解析并转交 service;业务内核在 domain / task_service,勿在此按入口复制逻辑。
"""
from __future__ import annotations
import argparse
import sys
from typing import List, Optional
from service.task_service import (
cmd_calculate_full,
cmd_calculate_simple,
cmd_config_path,
cmd_edit_config,
cmd_explain,
cmd_health,
cmd_init_db,
cmd_run,
cmd_version,
cmd_view_config,
_parse_float_or_none,
)
from util.config_bootstrap import bootstrap_skill_config
from util.argparse_zh import ZhArgumentParser
from util.constants import LOG_LOGGER_NAME, SKILL_SLUG
from util.logging_config import get_skill_logger, setup_skill_logging
def _cli_str_or_none(raw: Optional[str]) -> Optional[str]:
if raw is None:
return None
v = str(raw).strip()
return v or None
def _handle_run(args: argparse.Namespace) -> int:
tail = [str(x).strip() for x in (args.run_tail or []) if str(x).strip()]
if len(tail) > 2:
print("参数过多。")
print("用法:python main.py run [target [input_id]] | run [-t target] [-i input_id]")
return 1
t_target: Optional[str] = None
t_input: Optional[str] = None
if len(tail) == 2:
t_target, t_input = tail[0], tail[1]
elif len(tail) == 1:
if tail[0].isdigit():
t_input = tail[0]
else:
t_target = tail[0]
pick_t = _cli_str_or_none(getattr(args, "target", None))
pick_i = _cli_str_or_none(getattr(args, "input_id", None))
target = pick_t or t_target
input_id = pick_i or t_input
return cmd_run(target=target, input_id=input_id)
def _handle_calculate_simple(args: argparse.Namespace) -> int:
base_salary = float(args.base_salary)
period = _cli_str_or_none(args.period) or ""
reported_net = _parse_float_or_none(_cli_str_or_none(args.reported_net))
region = _cli_str_or_none(args.region) or "default"
return cmd_calculate_simple(base_salary=base_salary, period=period, reported_net=reported_net, region=region)
def _handle_calculate_full(args: argparse.Namespace) -> int:
region = _cli_str_or_none(args.region) or "default"
period = _cli_str_or_none(args.period) or ""
raw_payload = _cli_str_or_none(args.raw_payload) or ""
return cmd_calculate_full(
base_salary=float(args.base_salary or 0),
performance=float(args.performance or 0),
full_attendance=float(args.full_attendance or 0),
meal_allowance=float(args.meal_allowance or 0),
transport_allowance=float(args.transport_allowance or 0),
overtime_pay=float(args.overtime_pay or 0),
other_gross=float(args.other_gross or 0),
social_personal=_parse_float_or_none(_cli_str_or_none(args.social_personal)),
housing_fund_personal=_parse_float_or_none(_cli_str_or_none(args.housing_fund_personal)),
tax=_parse_float_or_none(_cli_str_or_none(args.tax)),
leave_deduction=float(args.leave_deduction or 0),
other_deduction=float(args.other_deduction or 0),
period=period,
reported_net=_parse_float_or_none(_cli_str_or_none(args.reported_net)),
raw_payload=raw_payload,
region=region,
)
def _handle_explain(args: argparse.Namespace) -> int:
term = _cli_str_or_none(args.term) or ""
return cmd_explain(term)
def _handle_view_config(args: argparse.Namespace) -> int:
region = _cli_str_or_none(args.region)
return cmd_view_config(region=region)
def _handle_edit_config(args: argparse.Namespace) -> int:
return cmd_edit_config(
config_key=_cli_str_or_none(args.config_key) or "",
region=_cli_str_or_none(args.region) or "default",
config_value=float(args.config_value),
)
def _print_full_usage() -> None:
print("工资查询助手(main.py)可用命令:")
print(" python main.py calculate-simple --base-salary N --period YYYY-MM [--reported-net N] [--region default|beijing|shanghai|guangzhou]")
print(" python main.py calculate-full [全字段参数] --period YYYY-MM [--region ...]")
print(" python main.py explain --term 名词")
print(" python main.py view-config [--region ...]")
print(" python main.py edit-config --config-key KEY --region R --config-value V")
print(" python main.py run [target [input_id]] [-t target] [-i input_id]")
print(" python main.py health")
print(" python main.py config-path")
print(" python main.py version")
print(" python main.py init-db")
def build_parser() -> ZhArgumentParser:
p = ZhArgumentParser(
prog="main.py",
description="工资查询助手:核算应发/扣除/实发,核对差异,解释名词,估算个税。",
formatter_class=argparse.RawDescriptionHelpFormatter,
)
sub = p.add_subparsers(dest="cmd", required=True, parser_class=ZhArgumentParser)
# calculate-simple
sp = sub.add_parser("calculate-simple", help="快速估算(仅基本工资+period)")
sp.add_argument("--base-salary", required=True, type=float, metavar="基本工资")
sp.add_argument("--period", required=True, metavar="YYYY-MM")
sp.add_argument("--reported-net", default=None, metavar="用户自报实发")
sp.add_argument("--region", default=None, metavar="地区")
sp.set_defaults(handler=_handle_calculate_simple)
# calculate-full
sp = sub.add_parser("calculate-full", help="精确核算(全字段)")
sp.add_argument("--base-salary", type=float, default=0, metavar="基本工资")
sp.add_argument("--performance", type=float, default=0, metavar="绩效")
sp.add_argument("--full-attendance", type=float, default=0, metavar="全勤", dest="full_attendance")
sp.add_argument("--meal-allowance", type=float, default=0, metavar="餐补", dest="meal_allowance")
sp.add_argument("--transport-allowance", type=float, default=0, metavar="交通补", dest="transport_allowance")
sp.add_argument("--overtime-pay", type=float, default=0, metavar="加班费", dest="overtime_pay")
sp.add_argument("--other-gross", type=float, default=0, metavar="其他应发", dest="other_gross")
sp.add_argument("--social-personal", default=None, metavar="社保个人", dest="social_personal")
sp.add_argument("--housing-fund-personal", default=None, metavar="公积金个人", dest="housing_fund_personal")
sp.add_argument("--tax", default=None, metavar="个税")
sp.add_argument("--leave-deduction", type=float, default=0, metavar="事假扣款", dest="leave_deduction")
sp.add_argument("--other-deduction", type=float, default=0, metavar="其他扣款", dest="other_deduction")
sp.add_argument("--period", default="", metavar="YYYY-MM")
sp.add_argument("--reported-net", default=None, metavar="用户自报实发", dest="reported_net")
sp.add_argument("--raw-payload", default=None, metavar="原始工资条文本", dest="raw_payload")
sp.add_argument("--region", default=None, metavar="地区")
sp.set_defaults(handler=_handle_calculate_full)
# explain
sp = sub.add_parser("explain", help="名词解释")
sp.add_argument("--term", required=True, metavar="名词")
sp.set_defaults(handler=_handle_explain)
# view-config
sp = sub.add_parser("view-config", help="查看费率配置")
sp.add_argument("--region", default=None, metavar="地区")
sp.set_defaults(handler=_handle_view_config)
# edit-config
sp = sub.add_parser("edit-config", help="编辑费率配置")
sp.add_argument("--config-key", required=True, metavar="配置键", dest="config_key")
sp.add_argument("--region", default="default", metavar="地区")
sp.add_argument("--config-value", required=True, type=float, metavar="新值", dest="config_value")
sp.set_defaults(handler=_handle_edit_config)
sp = sub.add_parser("run", help="任务执行命令骨架")
sp.add_argument("--target", "-t", default=None, metavar="目标")
sp.add_argument("--input-id", "-i", default=None, metavar="输入id", dest="input_id")
sp.add_argument("run_tail", nargs="*", metavar="位置参数")
sp.set_defaults(handler=_handle_run)
sp = sub.add_parser("health", help="健康检查")
sp.set_defaults(handler=lambda _a: cmd_health())
sp = sub.add_parser("config-path", help="输出用户 .env 与模板路径")
sp.set_defaults(handler=lambda _a: cmd_config_path())
sp = sub.add_parser("version", help="版本信息(JSON)")
sp.set_defaults(handler=lambda _a: cmd_version())
sp = sub.add_parser("init-db", help="幂等初始化本地数据库与数据管理元数据")
sp.set_defaults(handler=lambda _a: cmd_init_db())
return p
def main(argv: Optional[List[str]] = None) -> int:
argv = argv if argv is not None else sys.argv[1:]
bootstrap_skill_config()
setup_skill_logging(SKILL_SLUG, LOG_LOGGER_NAME)
get_skill_logger().info("cli_start argv=%s", sys.argv)
if not argv:
_print_full_usage()
return 1
parser = build_parser()
args = parser.parse_args(argv)
return int(args.handler(args))