feat(business): add Kingdee sales order API sync
Implement private Kingdee config generation, token login/refresh caching, paginated sales order fetching, official response-field normalization, and CLI filters for deterministic business fact sync. Update docs to keep credentials in .runtime and cover dry-run auth. Validation: python3 -m unittest discover -v; python3 -m compileall obsidian_wiki tests; git diff --check.
This commit is contained in:
+80
-3
@@ -34,6 +34,13 @@ from obsidian_wiki.file_assets import (
|
||||
search_assets,
|
||||
stage_files,
|
||||
)
|
||||
from obsidian_wiki.kingdee_api import (
|
||||
build_sales_order_filters,
|
||||
ensure_kingdee_config,
|
||||
get_kingdee_access_token,
|
||||
load_kingdee_config,
|
||||
token_cache_path_for_config,
|
||||
)
|
||||
|
||||
HOME = Path.home()
|
||||
GLOBAL_CONFIG_DIR = HOME / ".obsidian-wiki"
|
||||
@@ -506,7 +513,27 @@ def cmd_business_sync_kingdee(args: argparse.Namespace) -> int:
|
||||
if args.input:
|
||||
records, payload = load_kingdee_payload_from_json(args.input)
|
||||
else:
|
||||
records, payload = fetch_kingdee_payload(api_url=args.api_url, token=args.token, since=args.since)
|
||||
config_path = getattr(args, "config", None)
|
||||
created = ensure_kingdee_config(config_path)
|
||||
overrides = _kingdee_cli_overrides(args)
|
||||
config = load_kingdee_config(config_path)
|
||||
config.update(overrides)
|
||||
if created and not (config.get("KINGDEE_ACCESS_TOKEN") or config.get("KINGDEE_TOKEN_URL")):
|
||||
raise RuntimeError(f"Kingdee config template created at {Path(config_path or '.runtime/kingdee.env').expanduser()}; fill it and rerun")
|
||||
if args.dry_run_auth:
|
||||
token = get_kingdee_access_token(config, cache_path=token_cache_path_for_config(config_path))
|
||||
print(f"✅ Kingdee auth OK: token length {len(token)}")
|
||||
return 0
|
||||
records, payload = fetch_kingdee_payload(
|
||||
api_url=args.api_url,
|
||||
token=args.token or args.access_token,
|
||||
since=args.since,
|
||||
config_path=config_path,
|
||||
config_overrides=overrides,
|
||||
filters=build_sales_order_filters(args),
|
||||
page_size=args.page_size,
|
||||
max_pages=args.max_pages,
|
||||
)
|
||||
result = sync_kingdee_orders(
|
||||
vault_path,
|
||||
records,
|
||||
@@ -523,6 +550,29 @@ def cmd_business_sync_kingdee(args: argparse.Namespace) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_business_config_kingdee(args: argparse.Namespace) -> int:
|
||||
config_path = Path(args.config or ".runtime/kingdee.env").expanduser()
|
||||
created = ensure_kingdee_config(config_path)
|
||||
state = "created" if created else "exists"
|
||||
print(f"✅ Kingdee config {state}: {config_path}")
|
||||
return 0
|
||||
|
||||
|
||||
def _kingdee_cli_overrides(args: argparse.Namespace) -> dict[str, str]:
|
||||
overrides: dict[str, str] = {}
|
||||
if getattr(args, "api_url", None):
|
||||
overrides["KINGDEE_SALORDER_API_URL"] = args.api_url
|
||||
if getattr(args, "token", None):
|
||||
overrides["KINGDEE_ACCESS_TOKEN"] = args.token
|
||||
if getattr(args, "access_token", None):
|
||||
overrides["KINGDEE_ACCESS_TOKEN"] = args.access_token
|
||||
if getattr(args, "identity", None):
|
||||
overrides["KINGDEE_ACGW_IDENTITY"] = args.identity
|
||||
if getattr(args, "page_size", None):
|
||||
overrides["KINGDEE_PAGE_SIZE"] = str(args.page_size)
|
||||
return overrides
|
||||
|
||||
|
||||
def cmd_business_query(args: argparse.Namespace) -> int:
|
||||
vault_path = _require_vault(args)
|
||||
question = " ".join(args.question).strip()
|
||||
@@ -663,6 +713,13 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
bp = sub.add_parser("business", help="sync and query structured business facts")
|
||||
business_sub = bp.add_subparsers(dest="business_command")
|
||||
|
||||
bcp = business_sub.add_parser("config", help="create local business integration config files")
|
||||
business_config_sub = bcp.add_subparsers(dest="business_config_target")
|
||||
|
||||
bckp = business_config_sub.add_parser("kingdee", help="create a local Kingdee API config template")
|
||||
bckp.add_argument("--config", metavar="PATH", help="Kingdee config path (defaults to .runtime/kingdee.env)")
|
||||
bckp.set_defaults(func=cmd_business_config_kingdee)
|
||||
|
||||
bsp = business_sub.add_parser("sync", help="sync business facts from an external system")
|
||||
business_sync_sub = bsp.add_subparsers(dest="business_sync_source")
|
||||
|
||||
@@ -670,9 +727,29 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
_add_vault_arg(bkp)
|
||||
bkp.add_argument("--db", metavar="PATH", help="SQLite DB path (defaults to <vault>/_meta/business-facts.sqlite)")
|
||||
bkp.add_argument("--input", metavar="PATH", help="JSON payload exported from or fetched from Kingdee")
|
||||
bkp.add_argument("--api-url", help="Kingdee-compatible JSON API URL; defaults to KINGDEE_API_URL")
|
||||
bkp.add_argument("--token", help="API bearer token; defaults to KINGDEE_API_TOKEN")
|
||||
bkp.add_argument("--config", metavar="PATH", help="Kingdee config path (defaults to .runtime/kingdee.env)")
|
||||
bkp.add_argument("--api-url", help="Kingdee sales order API URL; defaults to KINGDEE_SALORDER_API_URL")
|
||||
bkp.add_argument("--token", help="direct access token override; compatibility alias for --access-token")
|
||||
bkp.add_argument("--access-token", help="direct access token override")
|
||||
bkp.add_argument("--identity", help="x-acgw-identity override")
|
||||
bkp.add_argument("--since", help="optional updated_since cursor passed to the API and sync log")
|
||||
bkp.add_argument("--start-bizdate")
|
||||
bkp.add_argument("--end-bizdate")
|
||||
bkp.add_argument("--start-createtime")
|
||||
bkp.add_argument("--end-createtime")
|
||||
bkp.add_argument("--start-modifytime")
|
||||
bkp.add_argument("--end-modifytime")
|
||||
bkp.add_argument("--start-auditdate")
|
||||
bkp.add_argument("--end-auditdate")
|
||||
bkp.add_argument("--id", action="append", help="sales order ID; repeat or comma-separate")
|
||||
bkp.add_argument("--billno", action="append", help="sales order bill number; repeat or comma-separate")
|
||||
bkp.add_argument("--billstatus", action="append", help="A/B/C status; repeat or comma-separate")
|
||||
bkp.add_argument("--org-number", action="append", help="sales org code; repeat or comma-separate")
|
||||
bkp.add_argument("--customer-number", action="append", help="customer code; repeat or comma-separate")
|
||||
bkp.add_argument("--customer-createorg-number", action="append", help="customer create org code; repeat or comma-separate")
|
||||
bkp.add_argument("--page-size", type=int, help="Kingdee pageSize")
|
||||
bkp.add_argument("--max-pages", type=int, default=100, help="maximum pages to fetch")
|
||||
bkp.add_argument("--dry-run-auth", action="store_true", help="fetch/refresh access token and exit without syncing orders")
|
||||
bkp.add_argument("--json", action="store_true", help="print machine-readable sync result")
|
||||
bkp.set_defaults(func=cmd_business_sync_kingdee)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user