基于 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 测试全部通过
210 lines
8.9 KiB
Python
210 lines
8.9 KiB
Python
"""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))
|