|
@@ -36,6 +36,8 @@ class OutputPresenter:
|
|
|
DESTINATION_TRAILER_TOOLS = frozenset(('query_destination_trailer_list',))
|
|
DESTINATION_TRAILER_TOOLS = frozenset(('query_destination_trailer_list',))
|
|
|
ORDER_ABNORMAL_TOOLS = frozenset(('query_order_abnormal_list',))
|
|
ORDER_ABNORMAL_TOOLS = frozenset(('query_order_abnormal_list',))
|
|
|
RECEIVE_VOLUME_TOOLS = frozenset(('query_receive_volume_list',))
|
|
RECEIVE_VOLUME_TOOLS = frozenset(('query_receive_volume_list',))
|
|
|
|
|
+ GOODS_INFO_TOOLS = frozenset(('query_goods_info',))
|
|
|
|
|
+ GOODS_TAX_TOOLS = frozenset(('trial_goods_tax',))
|
|
|
CONTAINER_TIMELINESS_TOOLS = frozenset(('query_container_timeliness_list',))
|
|
CONTAINER_TIMELINESS_TOOLS = frozenset(('query_container_timeliness_list',))
|
|
|
FREIGHTOWER_TRACK_TOOLS = frozenset(('query_freightower_track',))
|
|
FREIGHTOWER_TRACK_TOOLS = frozenset(('query_freightower_track',))
|
|
|
DETAIL_TOOLS = frozenset(('query_outbound_detail',))
|
|
DETAIL_TOOLS = frozenset(('query_outbound_detail',))
|
|
@@ -65,6 +67,8 @@ class OutputPresenter:
|
|
|
| DESTINATION_TRAILER_TOOLS
|
|
| DESTINATION_TRAILER_TOOLS
|
|
|
| ORDER_ABNORMAL_TOOLS
|
|
| ORDER_ABNORMAL_TOOLS
|
|
|
| RECEIVE_VOLUME_TOOLS
|
|
| RECEIVE_VOLUME_TOOLS
|
|
|
|
|
+ | GOODS_INFO_TOOLS
|
|
|
|
|
+ | GOODS_TAX_TOOLS
|
|
|
| CONTAINER_TIMELINESS_TOOLS
|
|
| CONTAINER_TIMELINESS_TOOLS
|
|
|
| FREIGHTOWER_TRACK_TOOLS
|
|
| FREIGHTOWER_TRACK_TOOLS
|
|
|
| OPTION_TOOLS | EXPORT_TOOLS | TASK_TOOLS
|
|
| OPTION_TOOLS | EXPORT_TOOLS | TASK_TOOLS
|
|
@@ -568,6 +572,20 @@ class OutputPresenter:
|
|
|
ORDER_ABNORMAL_STATUSES = frozenset((
|
|
ORDER_ABNORMAL_STATUSES = frozenset((
|
|
|
'待处理', '放行待审核', '包裹放行', '退件待审核', '退件驳回', '已退件',
|
|
'待处理', '放行待审核', '包裹放行', '退件待审核', '退件驳回', '已退件',
|
|
|
))
|
|
))
|
|
|
|
|
+ GOODS_INFO_COLUMNS = (
|
|
|
|
|
+ ('sku', 'SKU'), ('departure_country', '起运国'),
|
|
|
|
|
+ ('destination_country', '目的国'), ('goods_attr', '商品属性'),
|
|
|
|
|
+ ('cn_name', '中文名称'), ('en_name', '英文名称'),
|
|
|
|
|
+ ('declaration_hscode', '报关编码'), ('clearance_hscode', '清关编码'),
|
|
|
|
|
+ ('tax_rate', '税率'), ('is_inspection_fee', '木制/竹制品商检费'),
|
|
|
|
|
+ ('is_fda_declaration', 'FDA申报'), ('product_surcharge', '产品附加费'),
|
|
|
|
|
+ ('tax_surcharge', '关税附加费'), ('is_epa_declaration', 'EPA申报'),
|
|
|
|
|
+ ('is_act_declaration', 'LACY ACT申报'), ('is_dot_declaration', 'DOT申报'),
|
|
|
|
|
+ ('is_cpsc_declaration', 'CPSC申报'), ('brand_type', '品牌类型'),
|
|
|
|
|
+ ('brand', '品牌'), ('goods_type', '型号'),
|
|
|
|
|
+ ('material_cn', '材质(CN)'), ('material_en', '材质(EN)'),
|
|
|
|
|
+ ('purpose_cn', '用途(CN)'), ('purpose_en', '用途(EN)'),
|
|
|
|
|
+ )
|
|
|
RECEIVE_VOLUME_COLUMNS = (
|
|
RECEIVE_VOLUME_COLUMNS = (
|
|
|
('order_number', '订单号'), ('customer_name', '客户名称'),
|
|
('order_number', '订单号'), ('customer_name', '客户名称'),
|
|
|
('packing_type', '货物类型'), ('inbound_volume', '入库体积'),
|
|
('packing_type', '货物类型'), ('inbound_volume', '入库体积'),
|
|
@@ -820,6 +838,12 @@ class OutputPresenter:
|
|
|
'MCP_1601': '没有可导出的数据',
|
|
'MCP_1601': '没有可导出的数据',
|
|
|
'MCP_9001': '工具调用失败,请稍后重试',
|
|
'MCP_9001': '工具调用失败,请稍后重试',
|
|
|
}
|
|
}
|
|
|
|
|
+ TAX_TRIAL_MESSAGES = frozenset((
|
|
|
|
|
+ '未找到已审核商品',
|
|
|
|
|
+ '匹配到多条商品,请核对起运国和目的国',
|
|
|
|
|
+ '该商品没有应收清关编码,无法试算',
|
|
|
|
|
+ '关税试算失败,请稍后重试',
|
|
|
|
|
+ ))
|
|
|
|
|
|
|
|
NON_RETRYABLE_CODES = frozenset((
|
|
NON_RETRYABLE_CODES = frozenset((
|
|
|
'MCP_1101',
|
|
'MCP_1101',
|
|
@@ -910,6 +934,12 @@ class OutputPresenter:
|
|
|
return self._present_receive_volume_list(
|
|
return self._present_receive_volume_list(
|
|
|
data, tool_result.get('meta'), meta
|
|
data, tool_result.get('meta'), meta
|
|
|
)
|
|
)
|
|
|
|
|
+ if tool_name in self.GOODS_INFO_TOOLS:
|
|
|
|
|
+ return self._present_goods_info(
|
|
|
|
|
+ data, tool_result.get('meta'), meta
|
|
|
|
|
+ )
|
|
|
|
|
+ if tool_name in self.GOODS_TAX_TOOLS:
|
|
|
|
|
+ return self._present_goods_tax(data, meta)
|
|
|
if tool_name in self.CONTAINER_TIMELINESS_TOOLS:
|
|
if tool_name in self.CONTAINER_TIMELINESS_TOOLS:
|
|
|
return self._present_container_timeliness_list(
|
|
return self._present_container_timeliness_list(
|
|
|
data, tool_result.get('meta'), meta
|
|
data, tool_result.get('meta'), meta
|
|
@@ -1779,6 +1809,79 @@ class OutputPresenter:
|
|
|
}
|
|
}
|
|
|
return self._success_result(content, self._render_table(content), meta)
|
|
return self._success_result(content, self._render_table(content), meta)
|
|
|
|
|
|
|
|
|
|
+ def _present_goods_tax(self, data, meta):
|
|
|
|
|
+ expected = {
|
|
|
|
|
+ 'estimated_tax', 'sku', 'departure_country', 'destination_country',
|
|
|
|
|
+ }
|
|
|
|
|
+ if set(data) != expected:
|
|
|
|
|
+ return self._format_error(meta)
|
|
|
|
|
+ amount = data.get('estimated_tax')
|
|
|
|
|
+ sku = data.get('sku')
|
|
|
|
|
+ departure = data.get('departure_country')
|
|
|
|
|
+ destination = data.get('destination_country')
|
|
|
|
|
+ if (
|
|
|
|
|
+ isinstance(amount, bool) or not isinstance(amount, (int, float))
|
|
|
|
|
+ or not math.isfinite(float(amount))
|
|
|
|
|
+ or not isinstance(sku, str) or not sku.strip()
|
|
|
|
|
+ or not self._country_code(departure)
|
|
|
|
|
+ or not self._country_code(destination)
|
|
|
|
|
+ ):
|
|
|
|
|
+ return self._format_error(meta)
|
|
|
|
|
+ content = {
|
|
|
|
|
+ 'estimated_tax': amount,
|
|
|
|
|
+ 'sku': sku,
|
|
|
|
|
+ 'departure_country': departure,
|
|
|
|
|
+ 'destination_country': destination,
|
|
|
|
|
+ }
|
|
|
|
|
+ text = '预计关税金额: {0}'.format(amount)
|
|
|
|
|
+ return self._success_result(content, text, meta)
|
|
|
|
|
+
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def _country_code(value):
|
|
|
|
|
+ return (
|
|
|
|
|
+ isinstance(value, str)
|
|
|
|
|
+ and value.isalpha()
|
|
|
|
|
+ and value.isupper()
|
|
|
|
|
+ and 2 <= len(value) <= 3
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ def _present_goods_info(self, data, raw_meta, meta):
|
|
|
|
|
+ if set(data) != {'columns', 'records'}:
|
|
|
|
|
+ return self._format_error(meta)
|
|
|
|
|
+ expected = list(self.GOODS_INFO_COLUMNS)
|
|
|
|
|
+ pagination = self._customer_pagination(raw_meta)
|
|
|
|
|
+ columns = data.get('columns')
|
|
|
|
|
+ records = data.get('records')
|
|
|
|
|
+ if (
|
|
|
|
|
+ not isinstance(columns, list)
|
|
|
|
|
+ or not isinstance(records, list)
|
|
|
|
|
+ or len(columns) != len(expected)
|
|
|
|
|
+ or pagination is None
|
|
|
|
|
+ ):
|
|
|
|
|
+ return self._format_error(meta)
|
|
|
|
|
+ for column, (key, name) in zip(columns, expected):
|
|
|
|
|
+ if column != {'key': key, 'name': name}:
|
|
|
|
|
+ return self._format_error(meta)
|
|
|
|
|
+ rows = []
|
|
|
|
|
+ expected_keys = [key for key, _ in expected]
|
|
|
|
|
+ for record in records:
|
|
|
|
|
+ if not isinstance(record, dict) or set(record) != set(expected_keys):
|
|
|
|
|
+ return self._format_error(meta)
|
|
|
|
|
+ row = []
|
|
|
|
|
+ for key in expected_keys:
|
|
|
|
|
+ value = record[key]
|
|
|
|
|
+ if not isinstance(value, str):
|
|
|
|
|
+ return self._format_error(meta)
|
|
|
|
|
+ row.append(value)
|
|
|
|
|
+ rows.append(row)
|
|
|
|
|
+ headers = [{'label': name} for _, name in expected]
|
|
|
|
|
+ content = {
|
|
|
|
|
+ 'headers': headers,
|
|
|
|
|
+ 'rows': rows,
|
|
|
|
|
+ 'pagination': pagination,
|
|
|
|
|
+ }
|
|
|
|
|
+ return self._success_result(content, self._render_table(content), meta)
|
|
|
|
|
+
|
|
|
def _present_receive_volume_list(self, data, raw_meta, meta):
|
|
def _present_receive_volume_list(self, data, raw_meta, meta):
|
|
|
if set(data) != {'columns', 'records'}:
|
|
if set(data) != {'columns', 'records'}:
|
|
|
return self._format_error(meta)
|
|
return self._format_error(meta)
|
|
@@ -2227,6 +2330,11 @@ class OutputPresenter:
|
|
|
return False
|
|
return False
|
|
|
|
|
|
|
|
def _business_error(self, tool_name, code, raw_message, meta):
|
|
def _business_error(self, tool_name, code, raw_message, meta):
|
|
|
|
|
+ message = str(raw_message or '').strip()
|
|
|
|
|
+ if tool_name == 'trial_goods_tax' and message in self.TAX_TRIAL_MESSAGES:
|
|
|
|
|
+ safe_code = code if code in self.ERROR_MESSAGES else 'MCP_9001'
|
|
|
|
|
+ retryable = safe_code not in self.NON_RETRYABLE_CODES
|
|
|
|
|
+ return self._error_result(safe_code, message, retryable, meta)
|
|
|
if code not in self.ERROR_MESSAGES and code != 'MCP_1401':
|
|
if code not in self.ERROR_MESSAGES and code != 'MCP_1401':
|
|
|
logger.warning(
|
|
logger.warning(
|
|
|
'Unknown MCP business error code',
|
|
'Unknown MCP business error code',
|