""" 流程编排器 ========== 将 Postman Collection 中 16 步运输流程 + SQL 写入串联为完整流水线。 变量传递完全模拟 Postman 的 Collection Variables 机制: 步骤 N 的 output 传递给后续步骤作为 input。 用法: from pipeline import Pipeline pipe = Pipeline(config) # 仅跑司机端 (步骤 1-5) result = pipe.run_driver_flow() # 完整流程 (步骤 1-16 + SQL) result = pipe.run_full_flow(dry_run=False) """ import json import time import uuid from typing import Optional from client import Yun8Client from db import Database def _gen_code() -> str: """按现有数据规则生成 code: 32 位小写十六进制 (UUID4 hex 去横线)""" return uuid.uuid4().hex # ============================================================ # Pipeline — 变量上下文 # ============================================================ class Pipeline: """ 模拟 Postman Collection Variables 的变量上下文。 每个步骤的输出写入 self.vars,后续步骤从 self.vars 读取。 """ def __init__(self, config: dict): self.config = config self.client = Yun8Client(config) self.db = Database(config) # 所有步骤间共享的变量池(对应 Postman collectionVariables) self.vars: dict = {} # 动态 weight 模式:每次循环随机生成 1~10 之间的小数,per_weight 和 weight 保持一致 dp = config.get("driver_params", {}) if dp.get("weight_mode", "fixed") == "random": import random as _r val = str(round(_r.uniform(1.0, 10.0), 1)) self.client.dp["per_weight"] = val self.client.dp["weight"] = val print(f" [random] weight = {val}") # ------------------------------------------------------------------ # 辅助方法 # ------------------------------------------------------------------ def _set_vars(self, **kwargs): """批量写入变量""" self.vars.update(kwargs) def _get(self, key: str, default: str = "") -> str: """读取变量""" return self.vars.get(key, default) def _log_step(self, num: int, name: str): print(f"\n{'─' * 50}") print(f"[Step {num:02d}] {name}") print(f"{'─' * 50}") def _check_response(self, data: dict, step_name: str): """检查 API 返回 code 是否为 0""" code = data.get("code") msg = data.get("msg", "") if code is not None and code != 0: print(f" [!] {step_name} 返回 code={code}, msg={msg}") def _load_lat(self) -> str: """装货纬度: config.load_latitude → 货源 shipperLatitude → config.latitude""" dp = self.config.get("driver_params", {}) return dp.get("load_latitude") or self._get("shipperLatitude") or dp.get("latitude", "") def _load_lng(self) -> str: """装货经度: config.load_longitude → 货源 shipperLongitude → config.longitude""" dp = self.config.get("driver_params", {}) return dp.get("load_longitude") or self._get("shipperLongitude") or dp.get("longitude", "") def _unload_lat(self) -> str: """卸货纬度: config.unload_latitude → 货源 consigneeLatitude → config.latitude""" dp = self.config.get("driver_params", {}) return dp.get("unload_latitude") or self._get("consigneeLatitude") or dp.get("latitude", "") def _unload_lng(self) -> str: """卸货经度: config.unload_longitude → 货源 consigneeLongitude → config.longitude""" dp = self.config.get("driver_params", {}) return dp.get("unload_longitude") or self._get("consigneeLongitude") or dp.get("longitude", "") # ================================================================== # 步骤 1-5 : 司机端流程 # ================================================================== def step01_query_goods(self) -> dict: self._log_step(1, "查询货源") result = self.client.step01_query_goods() self._set_vars(mainOrderCode=result["mainOrderCode"], residueNumber=result["residueNumber"], shipperLatitude=result["shipperLatitude"], shipperLongitude=result["shipperLongitude"], consigneeLatitude=result["consigneeLatitude"], consigneeLongitude=result["consigneeLongitude"]) print(f" mainOrderCode = {result['mainOrderCode']}") print(f" residueNumber = {result['residueNumber']}") return result def step02_accept_order(self) -> dict: self._log_step(2, "接单") data = self.client.step02_accept_order( main_order_code=self._get("mainOrderCode"), residue_number=self._get("residueNumber"), latitude=self._load_lat(), longitude=self._load_lng()) self._check_response(data, "接单") print(f" 响应: code={data.get('code')}, msg={data.get('msg')}") return data def step03_query_pending_transport(self) -> dict: self._log_step(3, "查询待运输单") result = self.client.step03_query_pending_transport() self._set_vars(code=result["code"], vehicleCode=result["vehicleCode"], orderCode=result["orderCode"]) print(f" code = {result['code']}") print(f" vehicleCode = {result['vehicleCode']}") print(f" orderCode = {result['orderCode']}") return result def step04_load_goods(self) -> dict: self._log_step(4, "装货") data = self.client.step04_load_goods( code=self._get("code"), vehicle_code=self._get("vehicleCode"), latitude=self._load_lat(), longitude=self._load_lng()) self._check_response(data, "装货") print(f" 响应: code={data.get('code')}, msg={data.get('msg')}") return data def step05_unload_goods(self) -> dict: self._log_step(5, "卸货") data = self.client.step05_unload_goods( code=self._get("code"), latitude=self._unload_lat(), longitude=self._unload_lng()) self._check_response(data, "卸货") print(f" 响应: code={data.get('code')}, msg={data.get('msg')}") return data # ================================================================== # 步骤 6-9 : 运费核算流程 # ================================================================== def step06_query_for_accounting(self) -> dict: self._log_step(6, "运费核算查询运单") record = self.client.step06_query_for_accounting( order_code=self._get("orderCode")) for k, v in record.items(): self.vars[k] = str(v) if v is not None else "" print(f" 提取字段 ({len(record)}): {', '.join(list(record.keys())[:10])}...") return record def step07_query_order_expenses(self) -> dict: self._log_step(7, "查询运单详情") result = self.client.step07_query_order_expenses(code=self._get("code")) self._set_vars(**result) for k, v in result.items(): print(f" {k} = {v}") return result def step08_calculate_order_detail(self) -> dict: self._log_step(8, "核算试算 (calculateOrderDetail)") result = self.client.step08_calculate_order_detail(code=self._get("code")) self._set_vars(**result) for k, v in result.items(): print(f" {k} = {v[:80] if len(str(v)) > 80 else v}") return result def step09_submit_accounting(self) -> dict: self._log_step(9, "核算提交 (orderHandleEditSubmitHandle)") data = self.client.step09_submit_accounting(self.vars) self._check_response(data, "核算提交") print(f" 响应: code={data.get('code')}, msg={data.get('msg')}") return data # ================================================================== # 步骤 10-16 : 审核 & 付款流程 # ================================================================== def step10_query_audit(self) -> dict: self._log_step(10, "查询运单审核") data = self.client.step10_query_audit(order_code=self._get("orderCode")) self._check_response(data, "查询运单审核") records = data.get("data", {}).get("records", []) print(f" 记录数: {len(records)}") return data def step11_approve_audit(self) -> dict: self._log_step(11, "核算审核通过") data = self.client.step11_approve_audit(code=self._get("code")) self._check_response(data, "核算审核通过") print(f" 响应: code={data.get('code')}, msg={data.get('msg')}") return data # Step 12-14 wrappers 不单独定义(已内联在 run_audit_payment_flow 中) def step15_pay_to_driver(self) -> dict: self._log_step(15, "垫资-财务打款") data = self.client.step15_pay_to_driver( code=self._get("code"), delivery_cash_fee=self._get("deliveryCashFee"), driver_actual_freight=self._get("driverActualFreight"), plan_driver_payout=self._get("planDriverPayout", "0"), plan_captain_payout=self._get("planCaptainPayout", "0")) self._check_response(data, "垫资-财务打款") print(f" 响应: code={data.get('code')}, msg={data.get('msg')}") return data def step16_apply_for_payment(self) -> dict: self._log_step(16, "垫资-货主打款 (申请付款)") data = self.client.step16_apply_for_payment( code=self._get("code"), total_amount=self._get("shipmentActualCash")) self._check_response(data, "垫资-货主打款") print(f" 响应: code={data.get('code')}, msg={data.get('msg')}") return data # ================================================================== # 步骤 17-21 : 回单/文件名前缀 # ================================================================== def step17_query_upload_for_payno(self) -> dict: """ 14. 查询运单上传 → payNo ───────────────── GET /ntocc-basic-api/dytBusiness/orderDetail (status=5) Test: records[0].sendMsg → regex【(payNo)】 Input: orderCode """ self._log_step(17, "查询运单上传 (提取 payNo)") result = self.client.step17_query_upload_for_payno( order_code=self._get("orderCode"), ) pay_no = result["payNo"] self._set_vars(payNo=pay_no) print(f" payNo = {pay_no}") if not pay_no: print(" [!] 未提取到 payNo,后续 SQL 中 front_seq_no 将为空") return result def step18_query_remit_records(self) -> list[dict]: """ 15. 查询打款记录 ───────────────── GET /ntocc-tms-api/remitDefinite (分页) Test: records[0].code → remitCode Input: orderCode """ self._log_step(18, "查询打款记录") records = self.client.step18_query_remit_records( order_code=self._get("orderCode"), ) print(f" 共 {len(records)} 条打款记录") if records: print(f" 首条字段: {list(records[0].keys())}") return records def step19_query_file_prefix(self, remit_code: str) -> str: """ 16. 查询文件名前缀 ───────────────── GET /ntocc-tms-api/centralizedPayment/downLoadElectronicReceipt?code={remitCode} Test: msg → '文件名前缀:{filePrefix}' """ file_prefix = self.client.step19_query_file_prefix(remit_code) return file_prefix # ================================================================== # 组装流程 # ================================================================== def run_driver_flow(self) -> dict: """仅跑司机端(步骤 1-5)""" print("\n" + "=" * 60) print(" 司机端流程 (Step 1-5)") print("=" * 60) self.step01_query_goods() time.sleep(0.3) self.step02_accept_order() time.sleep(0.3) self.step03_query_pending_transport() # ---- 重置运单 (条件触发) ---- dp = self.config.get("driver_params", {}) if dp.get("reset_order_detail", False): code = self._get("code") order_code = self._get("orderCode") print(f"\n [reset] 重置运单 orderCode={order_code}") reset_code = dp.get("reset_order_detail_code", "") for sql in [ f"DELETE FROM tms_order_detail WHERE code = '{reset_code}'", f"DELETE FROM tms_order_detail_ext WHERE order_detail_code = '{reset_code}'", f"DELETE FROM tms_order_detail_abnormal_status WHERE order_detail_code = '{reset_code}'", f"UPDATE tms_order_detail SET code = '{reset_code}' WHERE order_code = '{order_code}'", ]: rows = self.db.execute(sql) print(f" SQL: {sql[:80]}... → {rows} rows") time.sleep(0.3) # 重新查询待运输单,刷新变量 self.step03_query_pending_transport() time.sleep(0.3) self.step04_load_goods() time.sleep(0.3) self.step05_unload_goods() # 卸货后 SQL if dp.get("reset_order_detail", False): reset_code = dp.get("reset_order_detail_code", "") sql = (f"UPDATE tms_order_detail SET code = '{reset_code}', " f"fill_time = '2026-07-05 19:30:56', " f"sign_time = '2026-07-05 19:52:30' " f"WHERE order_code = '{self._get('orderCode')}'") rows = self.db.execute(sql) print(f" [reset] 卸货后 UPDATE: {rows} rows") else: print(" [...] 等待 10s ...") time.sleep(10) sql = (f"UPDATE tms_order_detail_ext SET risk_control_status = 1 " f"WHERE order_detail_code = '{self._get('code')}'") rows = self.db.execute(sql) print(f" 卸货后 risk_control_status UPDATE: {rows} rows") return self.vars def run_accounting_flow(self) -> dict: """运费核算流程(步骤 6-9)""" print("\n" + "=" * 60) print(" 运费核算流程 (Step 6-9)") print("=" * 60) self.step06_query_for_accounting() time.sleep(0.5) self.step07_query_order_expenses() time.sleep(0.3) self.step08_calculate_order_detail() time.sleep(0.3) self.step09_submit_accounting() return self.vars def _retry_on_processing(self, step_fn, step_name: str, max_retries: int = 5): """ 遇到 "处理中" 错误时自动重试,每次间隔递增。 服务端审核通过后需要异步处理,可能持续数秒到十几秒。 """ for attempt in range(1, max_retries + 1): data = step_fn() msg = str(data.get("msg", "")) code = data.get("code") if code == 0 or (msg and ("成功" in msg or "success" in msg.lower())): return data if "处理中" in msg or "稍后" in msg: wait = 3 + attempt * 2 # 5, 7, 9, 11, 13 秒 print(f" [...] {step_name} 服务端正处理中,第 {attempt}/{max_retries} 次重试(等待 {wait}s)...") time.sleep(wait) else: # 非 "处理中" 错误,直接返回 return data return data def run_audit_payment_flow(self) -> dict: """审核 & 付款流程(步骤 10-16)""" print("\n" + "=" * 60) print(" 审核 & 付款流程 (Step 10-16)") print("=" * 60) # 确保前置依赖已获取(独立运行 --stage payment 时 vars 可能为空) self._ensure_code_and_order_code() self.step10_query_audit() time.sleep(0.5) self.step11_approve_audit() print(" [...] 等待服务端处理审核结果...") ac = self.config.get("accounting", {}) if not ac.get("execute_after_audit", True): print(f"\n [stop] execute_after_audit=false,跳过步骤 12-21") return self.vars # ---- 队长分账 (条件触发:payerReceiverPair 非空 + captainSplitMode=2) ---- payer_receiver = ac.get("payer_receiver", "") payer_link_man = ac.get("payer_receiver_link_man", "") captain_split_mode = ac.get("captain_split_mode") or self._get("captainSplitMode") if (payer_receiver or payer_link_man) and captain_split_mode == "2": self._log_step(12, "队长分账预览 (captainSplitAdjustPreview)") preview = self.client.step12_captain_split_preview( order_detail_code=self._get("code"), ) self._check_response(preview, "队长分账预览") latest_driver = float(preview.get("data", {}).get("latestDriverAmount", 0)) oil_fee = float(preview.get("data", {}).get("deliveryOilFee", 0)) print(f" latestDriverAmount = {latest_driver}") print(f" deliveryOilFee = {oil_fee}") time.sleep(0.5) self._log_step(13, "队长分账调整 (adjustCaptainSplit)") # 计算 driverAmount/captainAmount (与 client 中计算逻辑一致) import math as _m _driver_amt = _m.ceil(latest_driver * 0.8 * 100) / 100.0 _captain_amt = round(latest_driver - _driver_amt, 2) adjust = self.client.step13_adjust_captain_split( order_detail_code=self._get("code"), latest_driver_amount=latest_driver, delivery_oil_fee=oil_fee, ) self._check_response(adjust, "队长分账调整") print(f" 响应: code={adjust.get('code')}, msg={adjust.get('msg')}") # 存入 vars,供 step13 财务打款使用 self._set_vars(planDriverPayout=str(_driver_amt), planCaptainPayout=str(_captain_amt)) print(f" planDriverPayout = {_driver_amt}") print(f" planCaptainPayout = {_captain_amt}") else: print(f" [skip] 队长分账 (payerReceiver='{payer_receiver}', captainSplitMode={captain_split_mode})") time.sleep(0.5) # ---- Step 14: singleOil 带重试 ---- self._log_step(14, "单油卡充值 (singleOil)") oil_amount = self._get("oilFee") data_12 = self._retry_on_processing( lambda: self.client.step14_single_oil( order_detail_code=self._get("code"), amount=oil_amount if oil_amount else "0.03", ), "单油卡充值", ) self._check_response(data_12, "singleOil") print(f" 响应: code={data_12.get('code')}, msg={data_12.get('msg')}") # Step 13 带重试 self._log_step(15, "垫资-财务打款") data_12 = self._retry_on_processing( lambda: self.client.step15_pay_to_driver( code=self._get("code"), delivery_cash_fee=self._get("deliveryCashFee"), driver_actual_freight=self._get("driverActualFreight"), plan_driver_payout=self._get("planDriverPayout", "0"), plan_captain_payout=self._get("planCaptainPayout", "0"), ), "垫资-财务打款", ) self._check_response(data_12, "垫资-财务打款") print(f" 响应: code={data_12.get('code')}, msg={data_12.get('msg')}") # Step 14 带重试 (totalAmount 取 step06 的 shipmentActualCash) self._log_step(16, "垫资-货主打款 (申请付款)") data_13 = self._retry_on_processing( lambda: self.client.step16_apply_for_payment( code=self._get("code"), total_amount=self._get("shipmentActualCash"), ), "垫资-货主打款", ) self._check_response(data_13, "垫资-货主打款") print(f" 响应: code={data_13.get('code')}, msg={data_13.get('msg')}") return self.vars def run_receipt_file_flow(self, dry_run: bool = False) -> list[dict]: """回单文件流程(步骤 15-18)+ SQL INSERT""" print("\n" + "=" * 60) print(" 回单文件流程 (Step 14-16 + SQL)") print("=" * 60) # 确保 orderCode 已就绪(独立运行 --stage receipt 时需要) self._ensure_code_and_order_code() # 14. 获取 payNo self.step17_query_upload_for_payno() time.sleep(0.3) # 15. 获取所有打款记录 remit_records = self.step18_query_remit_records() if not remit_records: print(" [EXIT] 无打款记录") return [] # 16 + SQL: 遍历每条打款记录 pay_no = self._get("payNo") results = [] ok = 0 ng = 0 for i, rec in enumerate(remit_records): remit_code = rec.get("code", "") if not remit_code: print(f"\n [{i+1}/{len(remit_records)}] 跳过:缺 code 字段") ng += 1 results.append({"remit_code": remit_code, "status": "skip_no_code"}) continue print(f"\n [{i+1}/{len(remit_records)}] remitCode = {remit_code}") # 16. 查询文件名前缀 file_prefix = self.step19_query_file_prefix(remit_code) print(f" filePrefix = {file_prefix}") if not file_prefix: print(f" [!] filePrefix 为空,跳过 SQL INSERT") ng += 1 results.append({"remit_code": remit_code, "status": "skip_no_prefix"}) continue file_name = self.db.file_name_template.format(file_prefix=file_prefix) print(f" file_name = {file_name}") print(f" payNo = {pay_no}") # SQL INSERT sql = self.db.build_sql(file_prefix, pay_no) if dry_run: print(f" [DRY RUN] SQL:\n{sql}") ok += 1 results.append({"remit_code": remit_code, "file_prefix": file_prefix, "status": "dry_run"}) else: try: rowcount = self.db.insert(file_prefix, pay_no) print(f" [OK] INSERT 成功 (rows: {rowcount})") ok += 1 results.append({"remit_code": remit_code, "file_prefix": file_prefix, "status": "inserted", "rows": rowcount}) except Exception as exc: print(f" [FAIL] INSERT 失败: {exc}") ng += 1 results.append({"remit_code": remit_code, "file_prefix": file_prefix, "status": "error", "error": str(exc)}) # 汇总 print(f"\n ───────────────────────") print(f" 回单文件流程完成: 成功 {ok}, 失败 {ng}, 总计 {len(remit_records)}") # Step 18: 上报运单 self._log_step(20, "上报运单 (reportOrderDetail)") try: data_17 = self.client.step20_report_order_detail( codes=self._get("code"), ) self._check_response(data_17, "上报运单") print(f" 响应: code={data_17.get('code')}, msg={data_17.get('msg')}") except Exception as exc: print(f" [!] 上报运单失败: {exc}") # Step 19: 滇运通审核 (独立 token/host,config 控制开关) if self.client.tp_enabled: self._log_step(21, "滇运通审核 (updateOrderDetailExt)") try: data_19 = self.client.step21_owner_review( order_detail_code=self._get("code"), ) self._check_response(data_19, "滇运通审核") print(f" 响应: code={data_19.get('code')}, msg={data_19.get('msg')}") except Exception as exc: print(f" [!] 滇运通审核失败: {exc}") else: print(f"\n [skip] Step 19 滇运通审核 (dianyuntong.enabled=false)") return results # ================================================================== # 步骤 22-23 : ETC 开票 SQL 写入 # ================================================================== def step22_insert_etc_invoice(self, dry_run: bool = False) -> dict: """ 22. 写入 ETC 开票信息 (base_etc_invoice) ───────────────── INSERT INTO ntocc.base_etc_invoice order_code ← 步骤03的 orderCode push_order_no ← orderCode + "-1" order_detail_code ← 步骤03的 code code 按现有数据规则自动生成 (32 位十六进制) """ self._log_step(22, "ETC 开票信息写入 (base_etc_invoice)") order_code = self._get("orderCode") detail_code = self._get("code") if not order_code or not detail_code: print(" [!] orderCode/code 为空,无法写入 base_etc_invoice") return {"table": "base_etc_invoice", "status": "skip_no_vars"} etc_code = _gen_code() push_order_no = f"{order_code}-1" sql = ( "INSERT INTO `ntocc`.`base_etc_invoice` (" "`id`, `code`, `order_code`, `push_order_no`, `order_detail_code`, " "`invoice_time`, `sale_tax_code`, `sale_name`, `import_station`, `export_station`, " "`transaction_time`, `total_price`, `amount`, `tax_rate`, `tax_amount`, " "`invoice_type`, `invoice_code`, `invoice_number`, `waybill_start_time`, `waybill_end_time`, " "`invoice_url`, `invoice_html_url`, `en_date`, `ex_date`, `ex_time`, " "`is_valid`, `task_name`, `plate_num`, `etc_invoice_status`, `tax_deduction_status`, " "`sign`, `is_deleted`, `created_by`, `modified_by`, `gmt_created`, `gmt_modified`) " "VALUES (" f"NULL, '{etc_code}', '{order_code}', '{push_order_no}', '{detail_code}', " "'2026-06-04 23:23:51', '9141010055833160XE', '河南省视博电子股份有限公司', '广东古镇东站', '河北成安西站', " "'2026-05-14 19:03:42', 0.02, 0.02, 0.03, 0.03, " "'征税发票', '*', '26417910230600041165', '2026-05-13 15:35:38', '2026-05-15 13:35:43', " "'https://hdfs.txffp.com/e/1102e1en66nPPP1ChAeC161e1C6Pei16.zip?Expires=1812122633&OSSAccessKeyId=LTAIYAJbICRhyCD8&Signature=TLjlHgKtCxwWpPliPRQ0xQ6HdKY%3D', " "'https://gateway.txffp.com/alle/common/htmlurl/getinvoice/41012526221804003093/1102e1en66nPPP1ChAeC161e1C6Pei16', " "'2026-05-14', '2026-05-14', '2026-05-14 19:03:42', " "0, 'etc开票申请2026070604', '冀D8E016', 3, 0, " "NULL, 0, '', '04b7e539c77d4bdbb4fe4e87eaaf41e4', '2026-07-06 09:55:16', '2026-07-06 10:03:26')" ) print(f" order_code = {order_code}") print(f" push_order_no = {push_order_no}") print(f" order_detail_code = {detail_code}") print(f" code = {etc_code}") if dry_run: print(f" [DRY RUN] SQL:\n{sql}") return {"table": "base_etc_invoice", "status": "dry_run"} try: rows = self.db.execute(sql) print(f" [OK] INSERT 成功 (rows: {rows})") return {"table": "base_etc_invoice", "status": "inserted", "rows": rows} except Exception as exc: print(f" [FAIL] INSERT 失败: {exc}") return {"table": "base_etc_invoice", "status": "error", "error": str(exc)} def step23_insert_etc_push(self, dry_run: bool = False) -> dict: """ 23. 写入 ETC 推送记录 (tms_order_etc_push) ───────────────── INSERT INTO ntocc.tms_order_etc_push order_no ← 步骤03的 orderCode push_order_no ← orderCode + "-1" order_detail_code ← 步骤03的 code code 按现有数据规则自动生成 (32 位十六进制) """ self._log_step(23, "ETC 推送记录写入 (tms_order_etc_push)") order_code = self._get("orderCode") detail_code = self._get("code") if not order_code or not detail_code: print(" [!] orderCode/code 为空,无法写入 tms_order_etc_push") return {"table": "tms_order_etc_push", "status": "skip_no_vars"} etc_code = _gen_code() push_order_no = f"{order_code}-1" sql = ( "INSERT INTO `ntocc`.`tms_order_etc_push` (" "`id`, `code`, `push_order_no`, `order_no`, `order_detail_code`, " "`start_time`, `end_time`, `year`, `month`, `vendor`, " "`request`, `start_request`, `end_request`, `invoice_pull_status`, `invoice_pull_time`, " "`start_status`, `start_push_time`, `start_result`, `end_status`, `end_push_time`, `end_result`, " "`is_deleted`, `created_by`, `modified_by`, `gmt_created`, `gmt_modified`) " "VALUES (" f"NULL, '{etc_code}', '{push_order_no}', '{order_code}', '{detail_code}', " "'2026-07-07 15:08:25', '2026-07-07 15:08:28', 2026, 6, 1, " "NULL, '11111', '11111', 0, '2026-07-07 11:40:21', " "2, '2026-07-07 15:17:02', '2', 2, '2026-07-07 15:11:04', 'dsdsad', " "0, NULL, NULL, '2026-07-07 11:09:17', '2026-07-07 15:15:18')" ) print(f" order_no = {order_code}") print(f" push_order_no = {push_order_no}") print(f" order_detail_code = {detail_code}") print(f" code = {etc_code}") if dry_run: print(f" [DRY RUN] SQL:\n{sql}") return {"table": "tms_order_etc_push", "status": "dry_run"} try: rows = self.db.execute(sql) print(f" [OK] INSERT 成功 (rows: {rows})") return {"table": "tms_order_etc_push", "status": "inserted", "rows": rows} except Exception as exc: print(f" [FAIL] INSERT 失败: {exc}") return {"table": "tms_order_etc_push", "status": "error", "error": str(exc)} def run_etc_invoice_flow(self, dry_run: bool = False) -> list[dict]: """ETC 开票 SQL 写入(步骤 22-23)""" print("\n" + "=" * 60) print(" ETC 开票 SQL 写入 (Step 22-23)") print("=" * 60) # 确保 orderCode / code 已就绪(独立运行 --stage receipt 时需要) self._ensure_code_and_order_code() results = [ self.step22_insert_etc_invoice(dry_run=dry_run), self.step23_insert_etc_push(dry_run=dry_run), ] return results def _ensure_code_and_order_code(self): """ 确保 code / orderCode 等关键变量已就绪。 独立运行 --stage payment/receipt 时 vars 为空, 需自动补充。优先尝试 driver 端 step03(waitTrans 状态), 若订单已过 driver 阶段则降级为 admin 端查询(step10 接口)。 """ if self._get("code") and self._get("orderCode"): print(f" (复用已有 code={self._get('code')}, orderCode={self._get('orderCode')})") return print(" [*] 自动补充前置数据...") # 方式 A: 通过 driver 端 step03(status=waitTrans) driver_auth = self.client.driver.headers.get("authorization", "") if driver_auth: try: result = self.client.step03_query_pending_transport() self._set_vars( code=result["code"], vehicleCode=result["vehicleCode"], orderCode=result["orderCode"], ) print(f" [driver] code={self._get('code')}, orderCode={self._get('orderCode')}") except Exception: print(" driver 端无待运输单,切换 admin 端查询...") driver_auth = "" # 标记失败,走降级 # 方式 B: 通过 admin 端(status=6 已结算 / status=5 已打款) if not self._get("code") or not self._get("orderCode"): for status_val in ["6", "5"]: try: # status=6 用 step10(/ntocc-tms-api/orderDetail?status=6&isSettle=1) if status_val == "6": data = self.client.step10_query_audit( order_code="", page_size=5, ) records = data.get("data", {}).get("records", []) else: # status=5 用 step17(/ntocc-basic-api/dytBusiness/orderDetail?status=5) data = self.client.step17_query_upload_for_payno( order_code="", page_size=5, ) records = data.get("raw_records", []) if records: r = records[0] self._set_vars( code=r.get("code", ""), orderCode=r.get("orderCode", ""), ) if self._get("code"): print(f" [admin status={status_val}] code={self._get('code')}, orderCode={self._get('orderCode')}") break except Exception as e: continue if not self._get("code"): print(" [!] 无法自动获取 code,请使用 --stage all 完整运行或手动指定") return # 补充核算数据(payment 阶段需要 deliveryCashFee/driverActualFreight) if not self._get("deliveryCashFee"): try: record = self.client.step06_query_for_accounting( order_code=self._get("orderCode"), ) for k, v in record.items(): self.vars[k] = str(v) if v is not None else "" expenses = self.client.step07_query_order_expenses( code=self._get("code"), ) self._set_vars(**expenses) print(f" deliveryCashFee={self._get('deliveryCashFee')}, driverActualFreight={self._get('driverActualFreight')}") except Exception as e: print(f" [!] 补充核算字段失败: {e}") def set_auth(self, driver_token: str, admin_token: str): """设置授权 Token""" self.client.set_driver_auth(driver_token) self.client.set_admin_auth(admin_token) print(f" Driver auth: {driver_token[:30]}...") print(f" Admin auth: {admin_token[:30]}...")