"""CLI:argparse 与分发(票据合规预审助手)。 只负责参数解析并转交 service;业务内核在 prereview_service / standards_service, 勿在此按入口复制逻辑。 """ from __future__ import annotations import argparse import sys from typing import List, Optional from service.task_service import ( cmd_add_whitelist, cmd_config_path, cmd_health, cmd_init_db, cmd_list_holidays, cmd_list_reports, cmd_list_whitelist, cmd_mark_verified, cmd_prereview, cmd_prereview_batch_input, cmd_remove_whitelist, cmd_render_html, cmd_run, cmd_set_standard, cmd_show_report, cmd_stats, cmd_version, cmd_view_standards, ) 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_prereview(args: argparse.Namespace) -> int: return cmd_prereview( invoice_json=str(args.invoice_json or "").strip(), purpose=_cli_str_or_none(args.purpose) or "", project_no=_cli_str_or_none(args.project_no) or "", trip_start=_cli_str_or_none(args.trip_start) or "", trip_end=_cli_str_or_none(args.trip_end) or "", trip_city=_cli_str_or_none(args.trip_city) or "", staff_title=_cli_str_or_none(args.staff_title) or "", ) def _handle_render_html(args: argparse.Namespace) -> int: report_id = getattr(args, "report_id", None) return cmd_render_html(report_id=int(report_id) if report_id else None) def _handle_list_reports(args: argparse.Namespace) -> int: limit = int(args.limit or 20) return cmd_list_reports(limit=limit, verdict=_cli_str_or_none(args.verdict)) def _handle_show_report(args: argparse.Namespace) -> int: return cmd_show_report(report_id=int(args.report_id)) def _handle_mark_verified(args: argparse.Namespace) -> int: return cmd_mark_verified( report_id=int(args.report_id), result=str(args.result or "").strip(), note=_cli_str_or_none(args.note) or "", ) def _handle_view_standards(args: argparse.Namespace) -> int: return cmd_view_standards(category=_cli_str_or_none(args.category)) def _handle_set_standard(args: argparse.Namespace) -> int: return cmd_set_standard( category=str(args.category or "").strip(), city_tier=str(args.city_tier or "").strip(), staff_title=str(args.staff_title or "").strip(), max_amount=float(args.max_amount), unit=_cli_str_or_none(args.unit) or "", basis_doc=_cli_str_or_none(args.basis_doc) or "", clause=_cli_str_or_none(args.clause) or "", city_example=_cli_str_or_none(args.city_example) or "", ) def _handle_add_whitelist(args: argparse.Namespace) -> int: return cmd_add_whitelist( tax_no=str(args.tax_no or "").strip(), org_name=str(args.org_name or "").strip(), note=_cli_str_or_none(args.note) or "", ) def _handle_remove_whitelist(args: argparse.Namespace) -> int: return cmd_remove_whitelist(tax_no=str(args.tax_no or "").strip()) def _handle_list_holidays(args: argparse.Namespace) -> int: return cmd_list_holidays(year=_cli_str_or_none(args.year)) def _print_full_usage() -> None: print("票据合规预审助手(main.py)可用命令:") print(" python main.py prereview --invoice-json [--purpose 文本] [--project-no 编号]") print(" [--trip-start YYYY-MM-DD] [--trip-end YYYY-MM-DD]") print(" [--trip-city 城市] [--staff-title 职级]") print(" python main.py prereview-batch-input # 批量预审 input/ 目录 JSON") print(" python main.py render-html [--report-id N] # 重建初审意见单(缺省最近 10 份)") print(" python main.py list-reports [--limit N] [--verdict 通过|待确认|驳回]") print(" python main.py show-report --id N") print(" python main.py mark-verified --report-id N --result pass|fail [--note 备注]") print(" python main.py view-standards [--category 住宿费|交通费]") print(" python main.py set-standard --category C --city-tier T --staff-title J --max-amount N") print(" python main.py list-whitelist") print(" python main.py add-whitelist --tax-no X --name Y [--note 备注]") print(" python main.py remove-whitelist --tax-no X") print(" python main.py list-holidays [--year YYYY]") print(" python main.py stats") 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) # prereview sp = sub.add_parser("prereview", help="票据合规预审(主命令)") sp.add_argument("--invoice-json", required=True, metavar="发票JSON(文件|内联|-)", dest="invoice_json") sp.add_argument("--purpose", default="", metavar="报销事由") sp.add_argument("--project-no", default="", metavar="预算项目编号", dest="project_no") sp.add_argument("--trip-start", default="", metavar="出差开始 YYYY-MM-DD", dest="trip_start") sp.add_argument("--trip-end", default="", metavar="出差结束 YYYY-MM-DD", dest="trip_end") sp.add_argument("--trip-city", default="", metavar="目的地城市", dest="trip_city") sp.add_argument("--staff-title", default="", metavar="职级(副高及以上/中级及以下)", dest="staff_title") sp.set_defaults(handler=_handle_prereview) # prereview-batch-input sp = sub.add_parser("prereview-batch-input", help="批量预审 input/ 目录下的发票 JSON 文件") sp.set_defaults(handler=lambda _a: cmd_prereview_batch_input()) # render-html sp = sub.add_parser("render-html", help="重建《初审意见单》HTML(缺省最近 10 份)") sp.add_argument("--report-id", default=None, metavar="报告编号", dest="report_id") sp.set_defaults(handler=_handle_render_html) # list-reports sp = sub.add_parser("list-reports", help="查询初审历史") sp.add_argument("--limit", default=20, metavar="条数") sp.add_argument("--verdict", default=None, metavar="结论筛选") sp.set_defaults(handler=_handle_list_reports) # show-report sp = sub.add_parser("show-report", help="查看一份初审报告详情") sp.add_argument("--report-id", required=True, metavar="报告编号", dest="report_id") sp.set_defaults(handler=_handle_show_report) # mark-verified sp = sub.add_parser("mark-verified", help="回填人工查验结果(pass/fail)") sp.add_argument("--report-id", required=True, metavar="报告编号", dest="report_id") sp.add_argument("--result", required=True, metavar="pass|fail") sp.add_argument("--note", default=None, metavar="查验备注") sp.set_defaults(handler=_handle_mark_verified) # view-standards sp = sub.add_parser("view-standards", help="查看经费标准库") sp.add_argument("--category", default=None, metavar="费用类别") sp.set_defaults(handler=_handle_view_standards) # set-standard sp = sub.add_parser("set-standard", help="新增/更新经费标准") sp.add_argument("--category", required=True, metavar="费用类别") sp.add_argument("--city-tier", required=True, metavar="城市等级", dest="city_tier") sp.add_argument("--staff-title", required=True, metavar="职级", dest="staff_title") sp.add_argument("--max-amount", required=True, type=float, metavar="标准值", dest="max_amount") sp.add_argument("--unit", default=None, metavar="单位") sp.add_argument("--basis-doc", default=None, metavar="依据文件", dest="basis_doc") sp.add_argument("--clause", default=None, metavar="条款") sp.add_argument("--city-example", default=None, metavar="城市示例(逗号分隔)", dest="city_example") sp.set_defaults(handler=_handle_set_standard) # whitelist sp = sub.add_parser("list-whitelist", help="查看税号白名单") sp.set_defaults(handler=lambda _a: cmd_list_whitelist()) sp = sub.add_parser("add-whitelist", help="新增税号白名单") sp.add_argument("--tax-no", required=True, metavar="税号", dest="tax_no") sp.add_argument("--name", required=True, metavar="单位名称", dest="org_name") sp.add_argument("--note", default=None, metavar="备注") sp.set_defaults(handler=_handle_add_whitelist) sp = sub.add_parser("remove-whitelist", help="删除税号白名单") sp.add_argument("--tax-no", required=True, metavar="税号", dest="tax_no") sp.set_defaults(handler=_handle_remove_whitelist) # holidays sp = sub.add_parser("list-holidays", help="查看节假日表") sp.add_argument("--year", default=None, metavar="年份") sp.set_defaults(handler=_handle_list_holidays) # stats sp = sub.add_parser("stats", help="预审统计") sp.set_defaults(handler=lambda _a: cmd_stats()) 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))