refactor(api): yun8_sync 并入 tests 分层框架(接口层 apis/yun8 + 用例层 api + Allure)

- tests/core: 新增 BaseHttpClient 统一 HTTP 封装(直连绕行/超时/日志/自定义编码);
  db_helper 追加 Db 长连接类(事务/mogrify/自动重连),既有函数不动
- tests/apis/yun8: 21 个 step 方法按域拆分为 5 个 Mixin(driver/accounting/
  audit_payment/receipt/third_party),Yun8Pipeline 24 步编排原样迁移,
  SQL 全部参数化并集中至 sql_templates
- tests/api: 5 阶段有序 P0 E2E 用例(业务失败由 print 改为真实报错)+
  README 迁移(删除明文密码段,修正步骤编号漂移)
- yun8_sync 目录退役:配置并入 test_config.yaml 的 yun8_api 段;
  造数 CLI 保留(python -m tests.apis.yun8.cli,兼容旧 config.yaml 结构)
- pytest.ini 注册 api/yun8_sync marker;requirements.txt 补 requests>=2.28
- 静态验证: compileall / 全仓 collect 24 条 / marker 过滤 / CLI --help /
  客户端与 pipeline 离线实例化 / SQL 占位符核对 / cross_end 兼容
This commit is contained in:
xst
2026-09-08 11:38:17 +08:00
parent e62e3224bf
commit 52b73f456c
26 changed files with 2147 additions and 1786 deletions
+337
View File
@@ -0,0 +1,337 @@
"""运费核算接口 Mixin — step06-09(管理端,含 ~190 字段核算提交报文)。
所有方法签名/返回值/默认值与旧 yun8_sync/client.py 逐字一致,
仅将原始 requests.Session 调用替换为 BaseHttpClient(self.admin_http)。
Usage:
from tests.apis.yun8.accounting_api import AccountingApi
class Yun8ApiClient(AccountingApi, ...): ...
"""
class AccountingApi:
"""运费核算接口(挂载于 Yun8ApiClient,依赖 admin_http / ac / dp 属性)。"""
# ==================================================================
# Step 06: 运费核算查询运单
# ==================================================================
def step06_query_for_accounting(self, order_code: str,
page_size: int = 20, page_num: int = 1) -> dict:
"""
Postman Test Script: 设置 30+ 个 collection variables
从 data.records[0] 中提取所有字段
Returns: dict with all fields from records[0]
"""
params = {
"status": "3",
"lastLoadingTimeRange": "",
"fillTimeRange": "",
"lastUnloadingTimeRange": "",
"signTimeRange": "",
"gmtModifiedRange": "",
"rangeValue_start_1": "",
"rangeValue_end_1": "",
"rangeValue_start_2": "",
"rangeValue_end_2": "",
"rangeValue_start_3": "",
"rangeValue_end_3": "",
"pageNum": page_num,
"pageSize": page_size,
"gmtCreatedStart": "",
"gmtCreatedEnd": "",
"lastUnloadingTimeStart": "",
"lastUnloadingTimeEnd": "",
"lastLoadingTimeStart": "",
"lastLoadingTimeEnd": "",
"gmtModifiedStart": "",
"gmtModifiedEnd": "",
"fillTimeStart": "",
"fillTimeEnd": "",
"signTimeStart": "",
"signTimeEnd": "",
"ordCreatedByName": "",
"isUnloadingConfirm": "1",
"isCannotAccounting": "0",
"riskControlStatus": "",
"riskControlReason": "",
"sortField": "",
"sortType": "",
"orderCode": order_code,
}
data = self.admin_http.get("/ntocc-tms-api/orderDetail/orderDetailHandlePageList", params)
records = data.get("data", {}).get("records", [])
if not records:
raise RuntimeError("运费核算查询运单: 无记录返回")
return records[0]
# ==================================================================
# Step 07: 查询运单详情
# ==================================================================
def step07_query_order_expenses(self, code: str) -> dict:
"""
Postman Test Script:
pm.collectionVariables.set("canWastage", ...);
pm.collectionVariables.set("deliveryCashFee", ...);
pm.collectionVariables.set("deliveryFeeDeserved", ...);
pm.collectionVariables.set("deliveryFeePractical", ...);
pm.collectionVariables.set("driverActualCash", ...);
pm.collectionVariables.set("driverActualFreight", ...);
Returns: {canWastage, deliveryCashFee, deliveryFeeDeserved,
deliveryFeePractical, driverActualCash, driverActualFreight}
"""
params = {"codes": code}
data = self.admin_http.post(
"/ntocc-tms-api/orderDetail/getOrderDetailExpensesParamList",
params=params)
items = data.get("data", [])
if not items:
raise RuntimeError("查询运单详情: 无数据返回")
r = items[0]
return {
"canWastage": str(r.get("canWastage", "")),
"deliveryCashFee": str(r.get("deliveryCashFee", "")),
"deliveryFeeDeserved": str(r.get("deliveryFeeDeserved", "")),
"deliveryFeePractical": str(r.get("deliveryFeePractical", "")),
"driverActualCash": str(r.get("driverActualCash", "")),
"driverActualFreight": str(r.get("driverActualFreight", "")),
}
# ==================================================================
# Step 08: 核算试算 (calculateOrderDetail)
# ==================================================================
def step08_calculate_order_detail(self, code: str) -> dict:
"""
Postman Test Script:
pm.collectionVariables.set("ticketImageUrls[]", jsonData.data.ticketImageUrls);
Returns: dict with ticketImageUrls + 其他 step08 返回的核算数据 (taxPrice 等)
"""
params = {"code": code, "isFirst": "1"}
data = self.admin_http.put("/ntocc-tms-api/orderDetail/calculateOrderDetail", params=params)
inner = data.get("data", {}) or {}
ticket_urls = inner.get("ticketImageUrls", "")
if isinstance(ticket_urls, list):
ticket_urls = ticket_urls[0] if ticket_urls else ""
# 返回所有有用字段,供 pipeline 存入 vars
result = {"ticketImageUrls": str(ticket_urls)}
for key in ("taxPrice", "taxFee", "taxPayFee", "taxPoint",
"price", "realPrice", "shipmentActualCash",
"shipmentActualFreight"):
if key in inner:
result[key] = str(inner[key])
return result
# ==================================================================
# Step 09: 核算提交
# ==================================================================
def step09_submit_accounting(self, accounting_vars: dict) -> dict:
"""
提交核算,参数来自前面步骤累积的所有变量。
accounting_vars 应包含 steps 03/06/07/08 的全部字段。
优先从 step06 返回值取值,取不到再用 config.yaml 默认值。
"""
av = accounting_vars
# 便捷取值:vars 优先 → config 兜底
def _v(key, cfg_key, default=""):
return av.get(key) or self.ac.get(cfg_key, default)
# Build the massive query string
params = {
"abnormalFee": "0",
"accountCheckAuthStatus": "0",
"accountCheckInfoAuthStatus": "0",
"accountCheckUpAuthStatus": "0",
"accountStatus": "0",
"allTrans": "0",
"allotAmount": "0",
"allotOil": "0",
"allotTaxAmount": "0",
"appSendLoadLocation": "0",
"appSendUnloadLocation": "0",
"applyStatus": "0",
"branchCode": _v("branchCode", "branch_code"),
"businessTypeCode": _v("businessTypeCode", "business_type_code"),
"canWastage": av.get("canWastage", ""),
"cancelTrans": "0",
"cancelType": "0",
"captainSplitCurrentVersion": "0",
"captainSplitMode": self.ac.get("captain_split_mode") or av.get("captainSplitMode", ""),
"cashDeposit": "0",
"coalName": "",
"coalPrice": av.get("coalPrice", ""),
"coalType": av.get("coalType", ""),
"coalTypeName": av.get("coalTypeName", ""),
"coalUnit": "0",
"coalUnitName": av.get("coalUnitName", ""),
"code": av.get("code", ""),
"contractStatus": "1",
"createdBy": av.get("createdBy", ""),
"createdByName": av.get("createdByName", ""),
"deleted": "0",
"deliveryCashFee": av.get("deliveryCashFee", ""),
"deliveryFeeDeserved": av.get("deliveryFeeDeserved", ""),
"deliveryFeePractical": av.get("deliveryFeePractical", ""),
"deliveryOilFee": "0",
"distance": av.get("distance", ""),
"driverActualCash": av.get("driverActualCash", ""),
"driverActualFreight": av.get("driverActualFreight", ""),
"driverCode": av.get("driverCode", ""),
"driverName": av.get("driverName", ""),
"driverPhone": av.get("driverPhone", ""),
"energyCardType": av.get("energyCardType", ""),
"etcType": "0",
"extraFee": "0",
"fillTime": av.get("fillTime", ""),
"finishedTrans": "0",
"gmtCreated": av.get("gmtCreated", ""),
"gmtModified": av.get("gmtModified", ""),
"id": av.get("id", ""),
"inFee": "0",
"infoFee": "0",
"insuranceAmount": "0",
"insuranceFee": "0",
"invoiceIsAll": "0",
"invoiceStatus": "0",
"invoiceStatusCaptain": "0",
"invoiceStatusDriver": "0",
"isAbnormal": "0",
"isCancel": "0",
"isCaptainOrder": "0",
"isCash": "0",
"isCuikuan": "0",
"isDriverComment": "0",
"isEqualDriver": "1",
"isEqualTeam": "1",
"isEqualVehicle": "1",
"isEtcHistory": "0",
"isFill": "1",
"isImport": "0",
"isInsure": "0",
"isLoadingConfirm": "1",
"isLoadingConfirmDate": av.get("isLoadingConfirmDate", ""),
"isOriginalOrder": "1",
"isPaperReturns": "0",
"isReceiptReturn": "0",
"isReportOrder": "0",
"isSend": "0",
"isSendDishonestyVehicle": "0",
"isSendFund": "0",
"isSendFundZhongLian": "0",
"isSendLoadLocation": "0",
"isSendTax": "0",
"isSendUnloadLocation": "0",
"isSendZhongLian": "0",
"isSettle": "0",
"isShipmentComment": "0",
"isSign": "1",
"isSplitOrder": "0",
"isSupplementary": "0",
"isTransfer": "0",
"isTrunkName": av.get("isTrunkName", ""),
"isUnloadingComfirmDate": av.get("isUnloadingComfirmDate", ""),
"isUnloadingConfirm": "1",
"isWsShare": "0",
"loadImgUrls[]": av.get("ticketImageUrls", ""),
"loadLatitude": av.get("loadLatitude", ""),
"loadLongitude": av.get("loadLongitude", ""),
"loadPlace": av.get("loadPlace", ""),
"loadQuantity": av.get("loadQuantity", ""),
"loadWeight": av.get("loadWeight", "1"),
"mainOrderCode": av.get("mainOrderCode", ""),
"modifiedBy": av.get("modifiedBy", ""),
"modifiedByName": av.get("modifiedByName", ""),
"monthlySettlementStatus": "0",
"oilFee": av.get("oilFee", ""),
"onTrans": "0",
"orderCode": av.get("orderCode", ""),
"payAmount": "0",
"payOil": "0",
"payStatus": "0",
"payStep": "10",
"payTaxAmount": "0",
"payTotalAmount": av.get("payTotalAmount", ""),
"payType": "0",
"payeeCode": _v("createdBy", "payee_code"), # step06 createdBy = payeeCode
"payeeName": av.get("payeeName", ""),
"payerReceiver": self.ac.get("payer_receiver", ""),
"payerReceiverLinkMan": self.ac.get("payer_receiver_link_man", ""),
"platformPrepayment": "0",
"prepaidStatus": "0",
"price": av.get("price", ""),
"proofAmount": "0",
"proofStatus": "0",
"realPrice": av.get("realPrice", ""),
"receiptNum": "",
"receiptPayAmount": "0",
"refuelProportion": "0",
"reportMark": "0",
"reportStage": "0",
"settlementCash": "0",
"settlementOil": "0",
"settlementType": self.ac.get("settlement_type", "2"),
"settlementWeight": av.get("settlementWeight", "1"),
"shipmentActualCash": av.get("shipmentActualCash", ""),
"shipmentActualFreight": av.get("shipmentActualFreight", ""),
"shipmentCardScale": "10",
"shipmentDeliveryFeePractical": av.get("shipmentDeliveryFeePractical", ""),
"shipmentName": av.get("shipmentName", ""),
"shipmentPayStatus": "0",
"shipmentPhone": _v("shipmentPhone", "shipment_phone"),
"shipperDeliveryFee": av.get("shipperDeliveryFee", ""),
"shippingNoteNumber": av.get("shippingNoteNumber", ""),
"signTime": av.get("signTime", ""),
"status": "3",
"takeLatitude": av.get("takeLatitude", ""),
"takeLongitude": av.get("takeLongitude", ""),
"takeOrderType": "2",
"takePlace": av.get("takePlace", ""),
"taxFee": av.get("taxFee", ""),
"taxPayFee": av.get("taxPayFee", ""),
"taxPrice": av.get("taxPrice", ""),
"ticketImageUrls[]": av.get("ticketImageUrls", ""),
"txStatus": "0",
"unloadLatitude": self.dp.get("latitude", ""),
"unloadLongitude": self.dp.get("longitude", ""),
"unloadPlace": av.get("unloadPlace", ""),
"unloadQuantity": av.get("unloadQuantity", ""),
"unloadWeight": av.get("unloadWeight", "1"),
"vehicleCode": av.get("vehicleCode", ""),
"vehicleLicense": av.get("vehicleLicense", ""),
"waitCar": "0",
"waitCheckTrans": "0",
"waitDriverCheckTrans": "0",
"waitPay": "0",
"waitSettle": "0",
"waitTrans": "0",
"wastage": "0",
"weight": av.get("weight", "1"),
"whetherOverload": "0",
"wsShareRatio": "0",
"deductionFee": "0",
"driverErase": "0",
"driverFixedPay": "0",
"driverSettlementWeight": av.get("driverSettlementWeight", "1"),
"electricityCardScale": "10",
"gasCardScale": "10",
"isTrunk": "0",
"limitWastageAmount": "500",
"limitWastageType": "1",
"shipmentErase": "0",
"shipmentPartyCode": _v("shipmentPartyCode", "shipment_party_code"),
"shipmentSettlementWeight": av.get("shipmentSettlementWeight", "1"),
"shipperFixedPay": "0",
"taxPoint": av.get("taxPoint", ""),
"taxPriceFixedPay": "0",
"orderExtraFeeListStr": "[]",
"BranchCode": _v("branchCode", "branch_code"),
"unloadImgaes": av.get("ticketImageUrls", ""),
"truckLoadingImgaes": av.get("ticketImageUrls", ""),
"IsTaxPoint": "0",
"RealPrice": av.get("realPrice", ""),
"gmtCreated2": av.get("gmtCreated", ""),
"gmtModified2": av.get("gmtModified", ""),
}
return self.admin_http.request_with_encoded_query(
"PUT", "/ntocc-tms-api/orderDetail/orderHandleEditSubmitHandle", params=params)