waybill_api.py 12.4 KB
#!/usr/bin/env python3
"""
运单查询 API 模块
包含运单查询相关的所有函数
"""

import requests
import json
from .api_config import API_CONFIG


def create_waybill_d(consignmentCode: str) -> str:
    """根据运单号创建D类运单
    
    Args:
        consignmentCode: 运单号(必填)
    
    Returns:
        创建结果信息
    """
    try:
        url = f"{API_CONFIG['base_url']}/Exp/bus-customer/consignment/createChmConsignmentD"
        
        # 构建请求数据
        data = {
            "consignmentCode": consignmentCode,
            "consignmentId": "",
            "isAIRecognition": 0
        }
        
        # 发送POST请求
        response = requests.post(
            url,
            headers=API_CONFIG['headers'],
            json=data,
            timeout=30
        )
        
        # 检查响应状态
        if response.status_code != 200:
            return f"❌ 创建失败: HTTP {response.status_code}, 错误信息: {response.text}"

        result = response.json()

        # 添加调试信息
        print(f"创建D类运单API响应: {result}")

        # 业务状态判定(保守容错)
        if result.get('success') is False:
            error_msg = result.get('message', '未知错误')
            return f"❌ 创建失败: {error_msg}"

        # 提取新建的运单ID与编号
        data = result.get('data') or {}
        extra = data.get('extra') or {}
        waybill_id = extra.get('id')
        code = extra.get('consignmentCode') or consignmentCode or ""

        # 初始化返回数据
        result_data = {
            "consignmentCode": code,
            "waybill_id": str(waybill_id) if waybill_id else "",
            "operate_unit_name": "",
            "operate_unit_code": "",
            "detailList": []
        }

        # 若拿不到ID,直接返回空数据
        if not waybill_id:
            return json.dumps(result_data, ensure_ascii=False, indent=2)

        # 请求表头详情获取 operate_unit_name 和 operate_unit_code
        head_url = f"{API_CONFIG['base_url']}/Exp/bus-customer/consignment/headInfo"
        try:
            head_resp = requests.post(
                head_url,
                headers=API_CONFIG["headers"],
                json={"id": waybill_id},
                timeout=30,
            )
            if head_resp.status_code == 200:
                head_json = head_resp.json()
                head_extra = (head_json.get("data") or {}).get("extra") or {}
                result_data["operate_unit_name"] = head_extra.get("operateUnitName") or ""
                result_data["operate_unit_code"] = head_extra.get("operateUnitCode") or ""
        except Exception:
            pass

        # 请求表体明细获取所有商品明细(sku_code 和 sku_name)
        detail_url = f"{API_CONFIG['base_url']}/Exp/bus-customer/consignment/detail/list"
        try:
            detail_payload = {
                "consType": "consType_D",
                "consignmentId": waybill_id,
                "pageIndex": 1,
                "pageSize": 100,  # 增加页面大小以获取更多记录
            }
            detail_resp = requests.post(
                detail_url,
                headers=API_CONFIG["headers"],
                json=detail_payload,
                timeout=30,
            )
            if detail_resp.status_code == 200:
                detail_json = detail_resp.json()
                records = (detail_json.get("data") or {}).get("records") or []
                if isinstance(records, list):
                    # 遍历所有记录,构建 detailList
                    for item in records:
                        if isinstance(item, dict):
                            detail_item = {
                                "sku_code": item.get("skuCode") or "",
                                "sku_name": item.get("skuName") or ""
                            }
                            result_data["detailList"].append(detail_item)
        except Exception:
            pass

        # 返回 JSON 格式数据
        return json.dumps(result_data, ensure_ascii=False, indent=2)
            
    except requests.exceptions.RequestException as e:
        return f"❌ 网络请求失败: {str(e)}"
    except json.JSONDecodeError as e:
        return f"❌ 响应解析失败: {str(e)}"
    except Exception as e:
        return f"❌ 创建D类运单失败: {str(e)}"


def query_waybill_list(initialize: int = 1, consStatusName: str = "等待录入") -> str:
    """查询运单列表信息
    
    Args:
        initialize: 初始化标志,默认为1
        consStatusName: 运单状态名称,默认为"等待录入"
    
    Returns:
        运单查询结果
    """
    try:
        url = f"{API_CONFIG['base_url']}{API_CONFIG['endpoint']}"
        
        # 构建请求数据
        data = {
            "consStatusName": consStatusName,
            "consTypeId": None,
            "consignmentCode": [],
            "endTime": None,
            "sender": None,
            "startTime": None,
            "initialize": initialize,
            "pageIndex": 1,
            "pageSize": 5,
            "total": 0
        }
        
        # 发送POST请求
        response = requests.post(
            url,
            headers=API_CONFIG['headers'],
            json=data,
            timeout=30
        )
        
        # 检查响应状态
        if response.status_code == 200:
            result = response.json()
            
            # 添加调试信息
            print(f"API 响应结构: {type(result)}")
            print(f"响应内容: {result}")
            
            # 正确提取 records 数组
            data = result.get('data', {}).get('records', [])
            
            # 添加数据类型检查
            if not isinstance(data, list):
                return f"查询成功但数据格式错误: records 字段类型为 {type(data)},内容: {data}"
            
            if not data:
                return f"查询成功: 未找到状态为 '{consStatusName}' 的运单记录。"
            
            # 提取所需字段并返回 JSON 格式
            result_list = []
            for item in data:
                if isinstance(item, dict):
                    result_item = {
                        "consignmentCode": item.get('consignmentCode', ''),
                        "consType": item.get('consType', ''),
                        "consStatus": item.get('consStatus', ''),
                        "sender": item.get('sender', ''),
                        "createTime": item.get('createTime', '')
                    }
                    result_list.append(result_item)
            
            # 返回 JSON 格式数据
            return json.dumps(result_list, ensure_ascii=False, indent=2)
        else:
            return f"查询失败: HTTP {response.status_code}, 错误信息: {response.text}"
            
    except requests.exceptions.RequestException as e:
        return f"网络请求失败: {str(e)}"
    except json.JSONDecodeError as e:
        return f"响应解析失败: {str(e)}"
    except Exception as e:
        return f"查询运单失败: {str(e)}"


def get_waybill_head_info(waybill_id: int) -> str:
    """根据运单ID查询表头详情并返回指定字段
    
    Args:
        waybill_id: 运单ID(必填)
    
    Returns:
        格式化字符串:
        您好!运单号:XXXX\n
    \t公司信息\n
    \t发件人公司:XXXXX\n
    \t海关编码:XXXX
    """
    try:
        url = f"{API_CONFIG['base_url']}/Exp/bus-customer/consignment/headInfo"

        payload = {"id": waybill_id}

        response = requests.post(
            url,
            headers=API_CONFIG["headers"],
            json=payload,
            timeout=30,
        )

        if response.status_code != 200:
            return f"查询失败: HTTP {response.status_code}, 错误信息: {response.text}"

        result = response.json()

        # 容错解析:期望字段位于 data.extra
        data = result.get("data") or {}
        extra = data.get("extra") or {}

        consignment_code = extra.get("consignmentCode") or ""
        operate_unit_name = extra.get("operateUnitName") or ""
        operate_unit_code = extra.get("operateUnitCode") or ""

        return (
            f"您好!运单号:{consignment_code}\n"
            f"\t公司信息\n"
            f"\t发件人公司:{operate_unit_name}\n"
            f"\t海关编码:{operate_unit_code}"
        )

    except requests.exceptions.RequestException as e:
        return f"网络请求失败: {str(e)}"
    except json.JSONDecodeError as e:
        return f"响应解析失败: {str(e)}"
    except Exception as e:
        return f"查询表头详情失败: {str(e)}"


def get_waybill_detail_list(
    consignment_id: int,
    cons_type: str = "consType_D",
    page_index: int = 1,
    page_size: int = 5,
) -> str:
    """根据运单ID查询表体明细列表,仅返回 skuCode 与 skuName。

    Args:
        consignment_id: 运单ID(必填)。
        cons_type: 运单类型,默认 "consType_D"。
        page_index: 页码,默认 1。
        page_size: 每页数量,默认 5。

    Returns:
        多行文本:
        \t商品明细信息\n
        \t出口商品编码:XXXX\n
        \t中文品名:XXXX
        若数据为空,则两个字段均为空字符。
    """
    try:
        url = f"{API_CONFIG['base_url']}/Exp/bus-customer/consignment/detail/list"

        payload = {
            "consType": cons_type,
            "consignmentId": consignment_id,
            "pageIndex": page_index,
            "pageSize": page_size,
        }

        response = requests.post(
            url,
            headers=API_CONFIG["headers"],
            json=payload,
            timeout=30,
        )

        if response.status_code != 200:
            return f"查询失败: HTTP {response.status_code}, 错误信息: {response.text}"

        result = response.json()

        data = result.get("data") or {}
        records = data.get("records") or []

        # 若无记录,按需求返回空字段
        if not isinstance(records, list) or len(records) == 0:
            return (
                "\t商品明细信息\n"
                "\t出口商品编码:\n"
                "\t中文品名:"
            )

        # 拼接所有记录(每条记录两行)。如需仅返回第一条,可切片 records[:1]
        lines = ["\t商品明细信息"]
        for item in records:
            sku_code = item.get("skuCode") if isinstance(item, dict) else ""
            sku_name = item.get("skuName") if isinstance(item, dict) else ""
            lines.append(f"\t出口商品编码:{sku_code or ''}")
            lines.append(f"\t中文品名:{sku_name or ''}")

        return "\n".join(lines)

    except requests.exceptions.RequestException as e:
        return f"网络请求失败: {str(e)}"
    except json.JSONDecodeError as e:
        return f"响应解析失败: {str(e)}"
    except Exception as e:
        return f"查询表体明细失败: {str(e)}"


def push_waybill_for_ocr(waybill_id: int) -> str:
    """根据运单ID推送OCR进行识别
    
    Args:
        waybill_id: 运单ID(必填)
    
    Returns:
        推送结果信息
    """
    try:
        url = f"{API_CONFIG['base_url']}/Exp/bus-customer/dataworksNew/vue/push/file"
        
        # 构建请求数据
        data = {
            "id": waybill_id
        }
        
        # 发送POST请求
        response = requests.post(
            url,
            headers=API_CONFIG['headers'],
            json=data,
            timeout=30
        )
        
        # 检查响应状态
        if response.status_code != 200:
            return f"❌ 推送失败: HTTP {response.status_code}, 错误信息: {response.text}"
        
        result = response.json()
        
        # 添加调试信息
        print(f"推送OCR识别API响应: {result}")
        
        # 业务状态判定
        if result.get('success') is False:
            error_msg = result.get('message', '未知错误')
            return f"❌ 推送失败: {error_msg}"
        
        # 成功情况
        return f"✅ 运单ID {waybill_id} 已成功推送到OCR识别系统"
            
    except requests.exceptions.RequestException as e:
        return f"❌ 网络请求失败: {str(e)}"
    except json.JSONDecodeError as e:
        return f"❌ 响应解析失败: {str(e)}"
    except Exception as e:
        return f"❌ 推送OCR识别失败: {str(e)}"