|
@@ -34,6 +34,7 @@ class OutputPresenter:
|
|
|
PAYABLE_FILTER_TOOLS = frozenset(('list_payable_cost_filter_options',))
|
|
PAYABLE_FILTER_TOOLS = frozenset(('list_payable_cost_filter_options',))
|
|
|
PAYABLE_EXPORT_TOOLS = frozenset(('export_payable_cost_list',))
|
|
PAYABLE_EXPORT_TOOLS = frozenset(('export_payable_cost_list',))
|
|
|
DESTINATION_TRAILER_TOOLS = frozenset(('query_destination_trailer_list',))
|
|
DESTINATION_TRAILER_TOOLS = frozenset(('query_destination_trailer_list',))
|
|
|
|
|
+ ARRANGE_TRAILER_TOOLS = frozenset(('arrange_destination_trailer',))
|
|
|
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_INFO_TOOLS = frozenset(('query_goods_info',))
|
|
@@ -43,6 +44,7 @@ class OutputPresenter:
|
|
|
HEADHAUL_PREPARE_TOOLS = frozenset(('prepare_headhaul_document_upload',))
|
|
HEADHAUL_PREPARE_TOOLS = frozenset(('prepare_headhaul_document_upload',))
|
|
|
HEADHAUL_SAVE_TOOLS = frozenset(('save_headhaul_document',))
|
|
HEADHAUL_SAVE_TOOLS = frozenset(('save_headhaul_document',))
|
|
|
HEADHAUL_DELETE_TOOLS = frozenset(('delete_headhaul_document',))
|
|
HEADHAUL_DELETE_TOOLS = frozenset(('delete_headhaul_document',))
|
|
|
|
|
+ INBOUND_RECEIPT_TOOLS = frozenset(('list_inbound_receipt_templates',))
|
|
|
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',))
|
|
|
ORDER_DETAIL_TOOLS = frozenset(('query_order_detail',))
|
|
ORDER_DETAIL_TOOLS = frozenset(('query_order_detail',))
|
|
@@ -62,6 +64,7 @@ class OutputPresenter:
|
|
|
'export_receivable_cost_list',
|
|
'export_receivable_cost_list',
|
|
|
'export_pallet_data',
|
|
'export_pallet_data',
|
|
|
'export_container_timeliness_report',
|
|
'export_container_timeliness_report',
|
|
|
|
|
+ 'export_inbound_receipt',
|
|
|
))
|
|
))
|
|
|
TASK_TOOLS = frozenset(('query_export_task',))
|
|
TASK_TOOLS = frozenset(('query_export_task',))
|
|
|
SAFE_TOOLS = (
|
|
SAFE_TOOLS = (
|
|
@@ -70,6 +73,7 @@ class OutputPresenter:
|
|
|
| RECEIVABLE_LIST_TOOLS | RECEIVABLE_FILTER_TOOLS
|
|
| RECEIVABLE_LIST_TOOLS | RECEIVABLE_FILTER_TOOLS
|
|
|
| PAYABLE_COST_TOOLS | PAYABLE_FILTER_TOOLS | PAYABLE_EXPORT_TOOLS
|
|
| PAYABLE_COST_TOOLS | PAYABLE_FILTER_TOOLS | PAYABLE_EXPORT_TOOLS
|
|
|
| DESTINATION_TRAILER_TOOLS
|
|
| DESTINATION_TRAILER_TOOLS
|
|
|
|
|
+ | ARRANGE_TRAILER_TOOLS
|
|
|
| ORDER_ABNORMAL_TOOLS
|
|
| ORDER_ABNORMAL_TOOLS
|
|
|
| RECEIVE_VOLUME_TOOLS
|
|
| RECEIVE_VOLUME_TOOLS
|
|
|
| GOODS_INFO_TOOLS
|
|
| GOODS_INFO_TOOLS
|
|
@@ -79,6 +83,7 @@ class OutputPresenter:
|
|
|
| HEADHAUL_PREPARE_TOOLS
|
|
| HEADHAUL_PREPARE_TOOLS
|
|
|
| HEADHAUL_SAVE_TOOLS
|
|
| HEADHAUL_SAVE_TOOLS
|
|
|
| HEADHAUL_DELETE_TOOLS
|
|
| HEADHAUL_DELETE_TOOLS
|
|
|
|
|
+ | INBOUND_RECEIPT_TOOLS
|
|
|
| FREIGHTOWER_TRACK_TOOLS
|
|
| FREIGHTOWER_TRACK_TOOLS
|
|
|
| OPTION_TOOLS | EXPORT_TOOLS | TASK_TOOLS
|
|
| OPTION_TOOLS | EXPORT_TOOLS | TASK_TOOLS
|
|
|
)
|
|
)
|
|
@@ -557,6 +562,15 @@ class OutputPresenter:
|
|
|
('wharf_wait_time', '码头等待时间'),
|
|
('wharf_wait_time', '码头等待时间'),
|
|
|
('amazon_wait_time', '亚马逊等待时间'),
|
|
('amazon_wait_time', '亚马逊等待时间'),
|
|
|
('bill_status', '应付费用状态'),
|
|
('bill_status', '应付费用状态'),
|
|
|
|
|
+ ('issue_way', '出单方式'),
|
|
|
|
|
+ ('release_status', '放货状态'),
|
|
|
|
|
+ ('freight_collect', '海运费到付'),
|
|
|
|
|
+ ('is_dg', 'DG'),
|
|
|
|
|
+ ('is_soc', 'SOC'),
|
|
|
|
|
+ ('is_oneway', 'ONEWAY'),
|
|
|
|
|
+ ('appointment_method', '预约方式'),
|
|
|
|
|
+ ('need_appointment_code', '是否需要预约码'),
|
|
|
|
|
+ ('warehouse_special_remark', '仓库特殊备注'),
|
|
|
),
|
|
),
|
|
|
'do': (
|
|
'do': (
|
|
|
('trailer_number', '拖车单号'), ('outbound_number', '出库单号'),
|
|
('trailer_number', '拖车单号'), ('outbound_number', '出库单号'),
|
|
@@ -979,6 +993,8 @@ class OutputPresenter:
|
|
|
return self._present_destination_trailer_list(
|
|
return self._present_destination_trailer_list(
|
|
|
data, tool_result.get('meta'), meta
|
|
data, tool_result.get('meta'), meta
|
|
|
)
|
|
)
|
|
|
|
|
+ if tool_name in self.ARRANGE_TRAILER_TOOLS:
|
|
|
|
|
+ return self._present_arrange_destination_trailer(data, meta)
|
|
|
if tool_name in self.ORDER_ABNORMAL_TOOLS:
|
|
if tool_name in self.ORDER_ABNORMAL_TOOLS:
|
|
|
return self._present_order_abnormal_list(
|
|
return self._present_order_abnormal_list(
|
|
|
data, tool_result.get('meta'), meta
|
|
data, tool_result.get('meta'), meta
|
|
@@ -1007,6 +1023,8 @@ class OutputPresenter:
|
|
|
return self._present_headhaul_save(data, meta)
|
|
return self._present_headhaul_save(data, meta)
|
|
|
if tool_name in self.HEADHAUL_DELETE_TOOLS:
|
|
if tool_name in self.HEADHAUL_DELETE_TOOLS:
|
|
|
return self._present_headhaul_delete(data, meta)
|
|
return self._present_headhaul_delete(data, meta)
|
|
|
|
|
+ if tool_name in self.INBOUND_RECEIPT_TOOLS:
|
|
|
|
|
+ return self._present_inbound_receipt_templates(data, meta)
|
|
|
if tool_name in self.FREIGHTOWER_TRACK_TOOLS:
|
|
if tool_name in self.FREIGHTOWER_TRACK_TOOLS:
|
|
|
return self._present_freightower_track(data, meta)
|
|
return self._present_freightower_track(data, meta)
|
|
|
if tool_name in self.TABLE_TOOLS:
|
|
if tool_name in self.TABLE_TOOLS:
|
|
@@ -1784,6 +1802,23 @@ class OutputPresenter:
|
|
|
}
|
|
}
|
|
|
return self._success_result(content, self._render_table(content), meta)
|
|
return self._success_result(content, self._render_table(content), meta)
|
|
|
|
|
|
|
|
|
|
+ def _present_arrange_destination_trailer(self, data, meta):
|
|
|
|
|
+ expected = ('trailer_number', 'bl_number', 'container_code')
|
|
|
|
|
+ if set(data) != set(expected):
|
|
|
|
|
+ return self._format_error(meta)
|
|
|
|
|
+ for key in expected:
|
|
|
|
|
+ if not isinstance(data.get(key), str):
|
|
|
|
|
+ return self._format_error(meta)
|
|
|
|
|
+ content = {
|
|
|
|
|
+ '拖车单号': data['trailer_number'],
|
|
|
|
|
+ '提单号': data['bl_number'],
|
|
|
|
|
+ '柜号': data['container_code'],
|
|
|
|
|
+ }
|
|
|
|
|
+ text = '拖车单号:{0};提单号:{1};柜号:{2}'.format(
|
|
|
|
|
+ data['trailer_number'], data['bl_number'], data['container_code']
|
|
|
|
|
+ )
|
|
|
|
|
+ return self._success_result(content, text, meta)
|
|
|
|
|
+
|
|
|
def _present_destination_trailer_list(self, data, raw_meta, meta):
|
|
def _present_destination_trailer_list(self, data, raw_meta, meta):
|
|
|
if set(data) != {'trailer_stage', 'columns', 'records'}:
|
|
if set(data) != {'trailer_stage', 'columns', 'records'}:
|
|
|
return self._format_error(meta)
|
|
return self._format_error(meta)
|
|
@@ -2385,6 +2420,38 @@ class OutputPresenter:
|
|
|
content['pagination'] = pagination
|
|
content['pagination'] = pagination
|
|
|
return self._success_result(content, self._render_table(content), meta)
|
|
return self._success_result(content, self._render_table(content), meta)
|
|
|
|
|
|
|
|
|
|
+ def _present_inbound_receipt_templates(self, data, meta):
|
|
|
|
|
+ if set(data) != {'receipt_kind', 'templates'}:
|
|
|
|
|
+ return self._format_error(meta)
|
|
|
|
|
+ kind = data.get('receipt_kind')
|
|
|
|
|
+ templates = data.get('templates')
|
|
|
|
|
+ if kind not in ('直送入仓单', '非直送入仓单') or not isinstance(templates, list):
|
|
|
|
|
+ return self._format_error(meta)
|
|
|
|
|
+ if not templates:
|
|
|
|
|
+ return self._format_error(meta)
|
|
|
|
|
+ rows = []
|
|
|
|
|
+ for item in templates:
|
|
|
|
|
+ if not isinstance(item, dict) or set(item) != {'name', 'template_code'}:
|
|
|
|
|
+ return self._format_error(meta)
|
|
|
|
|
+ name = item.get('name')
|
|
|
|
|
+ code = item.get('template_code')
|
|
|
|
|
+ if (
|
|
|
|
|
+ not isinstance(name, str) or not name.strip()
|
|
|
|
|
+ or not isinstance(code, str) or not code.strip()
|
|
|
|
|
+ ):
|
|
|
|
|
+ return self._format_error(meta)
|
|
|
|
|
+ rows.append([name.strip(), code.strip()])
|
|
|
|
|
+ content = {
|
|
|
|
|
+ 'receipt_kind': kind,
|
|
|
|
|
+ 'summary': kind,
|
|
|
|
|
+ 'headers': [
|
|
|
|
|
+ {'label': '模板名称'},
|
|
|
|
|
+ {'label': '模板编码'},
|
|
|
|
|
+ ],
|
|
|
|
|
+ 'rows': rows,
|
|
|
|
|
+ }
|
|
|
|
|
+ return self._success_result(content, self._render_table(content), meta)
|
|
|
|
|
+
|
|
|
def _present_export_submission(self, data, meta):
|
|
def _present_export_submission(self, data, meta):
|
|
|
expected = {'task_ref', 'status', 'retry_after_seconds'}
|
|
expected = {'task_ref', 'status', 'retry_after_seconds'}
|
|
|
if set(data) != expected:
|
|
if set(data) != expected:
|
|
@@ -2517,9 +2584,23 @@ class OutputPresenter:
|
|
|
},
|
|
},
|
|
|
)
|
|
)
|
|
|
code = 'MCP_9001'
|
|
code = 'MCP_9001'
|
|
|
- if code == 'MCP_1401':
|
|
|
|
|
|
|
+ if code == 'MCP_1401' and tool_name in self.ARRANGE_TRAILER_TOOLS:
|
|
|
|
|
+ if not self._arrange_trailer_message(message):
|
|
|
|
|
+ message = self._parameter_message('')
|
|
|
|
|
+ return self._error_result(code, message, False, meta)
|
|
|
|
|
+ elif code == 'MCP_1401':
|
|
|
label = self._find_field_label(tool_name, raw_message)
|
|
label = self._find_field_label(tool_name, raw_message)
|
|
|
message = self._parameter_message(label)
|
|
message = self._parameter_message(label)
|
|
|
|
|
+ elif tool_name in self.INBOUND_RECEIPT_TOOLS | self.EXPORT_TOOLS and (
|
|
|
|
|
+ tool_name in (
|
|
|
|
|
+ 'list_inbound_receipt_templates',
|
|
|
|
|
+ 'export_inbound_receipt',
|
|
|
|
|
+ )
|
|
|
|
|
+ ):
|
|
|
|
|
+ allowed = self._inbound_receipt_message(message)
|
|
|
|
|
+ message = allowed or self.ERROR_MESSAGES.get(
|
|
|
|
|
+ code, self.ERROR_MESSAGES['MCP_9001']
|
|
|
|
|
+ )
|
|
|
elif tool_name in self.FREIGHTOWER_TRACK_TOOLS:
|
|
elif tool_name in self.FREIGHTOWER_TRACK_TOOLS:
|
|
|
allowed = str(raw_message or '').strip()
|
|
allowed = str(raw_message or '').strip()
|
|
|
if allowed not in (
|
|
if allowed not in (
|
|
@@ -2538,6 +2619,49 @@ class OutputPresenter:
|
|
|
retryable = code not in self.NON_RETRYABLE_CODES
|
|
retryable = code not in self.NON_RETRYABLE_CODES
|
|
|
return self._error_result(code, message, retryable, meta)
|
|
return self._error_result(code, message, retryable, meta)
|
|
|
|
|
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def _arrange_trailer_message(message):
|
|
|
|
|
+ if message in (
|
|
|
|
|
+ '请提供提单号或柜号',
|
|
|
|
|
+ '存在不能填写的内容',
|
|
|
|
|
+ '号码不正确',
|
|
|
|
|
+ '请填写拖车行',
|
|
|
|
|
+ '找不到可安排的柜子',
|
|
|
|
|
+ '匹配到不止一柜,请改说另一个号码',
|
|
|
|
|
+ '当前柜子不能安排拖车行',
|
|
|
|
|
+ '当前不需要填写海外仓',
|
|
|
|
|
+ '当前不需要填写直送改拆',
|
|
|
|
|
+ '当前不需要填写改拆原因',
|
|
|
|
|
+ '直送柜只能选择是或否',
|
|
|
|
|
+ '拖车行请说完整的名称-代码',
|
|
|
|
|
+ '拖车行不能填',
|
|
|
|
|
+ '港后跟进人不能填',
|
|
|
|
|
+ '预计提柜时间不正确',
|
|
|
|
|
+ '请选择直送改拆',
|
|
|
|
|
+ '直送改拆只能选择是或否',
|
|
|
|
|
+ '改拆原因不正确',
|
|
|
|
|
+ '请填写海外仓',
|
|
|
|
|
+ '海外仓不能填',
|
|
|
|
|
+ '请填写仓库联系人',
|
|
|
|
|
+ '请填写仓库联系电话',
|
|
|
|
|
+ '当前不需要填写重量体积',
|
|
|
|
|
+ '该柜子需要在页面上传BOL后再安排',
|
|
|
|
|
+ '重量体积不正确',
|
|
|
|
|
+ '订单不在这一柜',
|
|
|
|
|
+ '请填写重量体积',
|
|
|
|
|
+ '干线未出发不可安排拖车',
|
|
|
|
|
+ '当前柜子已安排拖车行',
|
|
|
|
|
+ '仓库不存在',
|
|
|
|
|
+ '仓库联系人不能为空',
|
|
|
|
|
+ '仓库联系电话不能为空',
|
|
|
|
|
+ '拖车行不存在',
|
|
|
|
|
+ '拖车行类型不是海外拖车',
|
|
|
|
|
+ '排舱单不存在',
|
|
|
|
|
+ '安排拖车行失败',
|
|
|
|
|
+ ):
|
|
|
|
|
+ return True
|
|
|
|
|
+ return message.startswith('请填写订单') and message.endswith('的重量和体积')
|
|
|
|
|
+
|
|
|
def _find_field_label(self, tool_name, raw_message):
|
|
def _find_field_label(self, tool_name, raw_message):
|
|
|
message = str(raw_message or '')
|
|
message = str(raw_message or '')
|
|
|
fields = self.FIELD_LABELS.get(tool_name, {})
|
|
fields = self.FIELD_LABELS.get(tool_name, {})
|
|
@@ -2550,6 +2674,45 @@ class OutputPresenter:
|
|
|
def _parameter_message(label):
|
|
def _parameter_message(label):
|
|
|
return '{0}参数不正确'.format(label) if label else '工具参数不正确,请检查后重试'
|
|
return '{0}参数不正确'.format(label) if label else '工具参数不正确,请检查后重试'
|
|
|
|
|
|
|
|
|
|
+ @classmethod
|
|
|
|
|
+ def _inbound_receipt_message(cls, message):
|
|
|
|
|
+ text = str(message or '').strip()
|
|
|
|
|
+ if text in (
|
|
|
|
|
+ '当前没有可用的入仓单模板',
|
|
|
|
|
+ '所选模板不属于这次柜号对应的入仓单',
|
|
|
|
|
+ ):
|
|
|
|
|
+ return text
|
|
|
|
|
+ missing_prefix = '这些柜号没有对应的排舱单:'
|
|
|
|
|
+ if text.startswith(missing_prefix) and cls._container_code_list(
|
|
|
|
|
+ text[len(missing_prefix):]
|
|
|
|
|
+ ):
|
|
|
|
|
+ return text
|
|
|
|
|
+ direct_prefix = '这些柜号是直送柜:'
|
|
|
|
|
+ indirect_mark = '。这些柜号是非直送柜:'
|
|
|
|
|
+ if text.startswith(direct_prefix) and text.endswith('。') and indirect_mark in text:
|
|
|
|
|
+ body = text[len(direct_prefix):-1]
|
|
|
|
|
+ direct, indirect = body.split(indirect_mark, 1)
|
|
|
|
|
+ if cls._container_code_list(direct) and cls._container_code_list(indirect):
|
|
|
|
|
+ return text
|
|
|
|
|
+ return ''
|
|
|
|
|
+
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def _container_code_list(text):
|
|
|
|
|
+ if not text or len(text) > 200 * 101:
|
|
|
|
|
+ return False
|
|
|
|
|
+ parts = text.split('、')
|
|
|
|
|
+ if not parts:
|
|
|
|
|
+ return False
|
|
|
|
|
+ for part in parts:
|
|
|
|
|
+ if (
|
|
|
|
|
+ not part
|
|
|
|
|
+ or part != part.strip()
|
|
|
|
|
+ or len(part) > 100
|
|
|
|
|
+ or any(ord(char) < 32 for char in part)
|
|
|
|
|
+ ):
|
|
|
|
|
+ return False
|
|
|
|
|
+ return True
|
|
|
|
|
+
|
|
|
@staticmethod
|
|
@staticmethod
|
|
|
def _build_meta(raw_meta):
|
|
def _build_meta(raw_meta):
|
|
|
if not isinstance(raw_meta, dict):
|
|
if not isinstance(raw_meta, dict):
|
|
@@ -2616,98 +2779,132 @@ class OutputPresenter:
|
|
|
)
|
|
)
|
|
|
return '\n'.join(lines)
|
|
return '\n'.join(lines)
|
|
|
|
|
|
|
|
- return self._success_result(content, '\n'.join(lines), meta)
|
|
|
|
|
-
|
|
|
|
|
def _present_freightower_track(self, data, meta):
|
|
def _present_freightower_track(self, data, meta):
|
|
|
- if set(data) != {'schedule', 'ports', 'tracks'}:
|
|
|
|
|
|
|
+ expected = {'schedule', 'ports', 'containers', 'tracks', 'terminal_plan'}
|
|
|
|
|
+ if set(data) != expected:
|
|
|
return self._format_error(meta)
|
|
return self._format_error(meta)
|
|
|
- schedule = data.get('schedule')
|
|
|
|
|
- ports = data.get('ports')
|
|
|
|
|
- tracks = data.get('tracks')
|
|
|
|
|
- schedule_keys = (
|
|
|
|
|
- 'bl_number', 'container_code', 'vessel', 'voyage', 'etd', 'eta',
|
|
|
|
|
- )
|
|
|
|
|
- if (
|
|
|
|
|
- not isinstance(schedule, dict)
|
|
|
|
|
- or set(schedule) != set(schedule_keys)
|
|
|
|
|
- or not isinstance(ports, list)
|
|
|
|
|
- or not isinstance(tracks, list)
|
|
|
|
|
- ):
|
|
|
|
|
|
|
+ schedule = self._freightower_object(data.get('schedule'), (
|
|
|
|
|
+ ('bl_number', '提单号'),
|
|
|
|
|
+ ('container_code', '柜号'),
|
|
|
|
|
+ ('carrier_name', '船公司'),
|
|
|
|
|
+ ('vessel', '船名'),
|
|
|
|
|
+ ('voyage', '航次'),
|
|
|
|
|
+ ('route_code', '航线'),
|
|
|
|
|
+ ('current_status', '当前状态'),
|
|
|
|
|
+ ('current_status_time', '当前状态时间'),
|
|
|
|
|
+ ('current_status_place', '当前状态地点'),
|
|
|
|
|
+ ('update_time', '数据更新时间'),
|
|
|
|
|
+ ('etd', '预计离港'),
|
|
|
|
|
+ ('atd', '实际离港'),
|
|
|
|
|
+ ('eta', '预计到达'),
|
|
|
|
|
+ ))
|
|
|
|
|
+ if schedule is None:
|
|
|
|
|
+ return self._format_error(meta)
|
|
|
|
|
+ ports = self._freightower_rows(data.get('ports'), (
|
|
|
|
|
+ ('place_type', '地点类型'),
|
|
|
|
|
+ ('port_name', '港口'),
|
|
|
|
|
+ ('terminal_name', '码头'),
|
|
|
|
|
+ ('etd', '预计离港'),
|
|
|
|
|
+ ('atd', '实际离港'),
|
|
|
|
|
+ ('eta', '预计到达'),
|
|
|
|
|
+ ('ata', '实际到达'),
|
|
|
|
|
+ ), {'place_type': ('起运港', '中转港', '目的港', '交货地')})
|
|
|
|
|
+ containers = self._freightower_rows(data.get('containers'), (
|
|
|
|
|
+ ('container_no', '箱号'),
|
|
|
|
|
+ ('container_type', '箱型'),
|
|
|
|
|
+ ('seal_no', '铅封号'),
|
|
|
|
|
+ ('current_status', '当前状态'),
|
|
|
|
|
+ ('off_load', '是否甩柜'),
|
|
|
|
|
+ ), {'off_load': ('', '是', '否')})
|
|
|
|
|
+ tracks = self._freightower_rows(data.get('tracks'), (
|
|
|
|
|
+ ('time', '时间'),
|
|
|
|
|
+ ('location', '地点'),
|
|
|
|
|
+ ('content', '内容'),
|
|
|
|
|
+ ('event_code', '状态代码'),
|
|
|
|
|
+ ('estimate_flag', '实际/预计'),
|
|
|
|
|
+ ('vessel_voyage', '船名航次'),
|
|
|
|
|
+ ('terminal_name', '码头'),
|
|
|
|
|
+ ), {'estimate_flag': ('实际', '预计')})
|
|
|
|
|
+ terminal_plan = self._freightower_object(data.get('terminal_plan'), (
|
|
|
|
|
+ ('port_name', '港口'),
|
|
|
|
|
+ ('terminal_name', '码头'),
|
|
|
|
|
+ ('open_time', '开港'),
|
|
|
|
|
+ ('close_time', '截港'),
|
|
|
|
|
+ ('etd', '预计离港'),
|
|
|
|
|
+ ('atd', '实际离港'),
|
|
|
|
|
+ ('shipping_agent', '船代'),
|
|
|
|
|
+ ))
|
|
|
|
|
+ if None in (ports, containers, tracks, terminal_plan):
|
|
|
return self._format_error(meta)
|
|
return self._format_error(meta)
|
|
|
- labels = {
|
|
|
|
|
- 'bl_number': '提单号',
|
|
|
|
|
- 'container_code': '柜号',
|
|
|
|
|
- 'vessel': '船名',
|
|
|
|
|
- 'voyage': '航次',
|
|
|
|
|
- 'etd': '预计离港',
|
|
|
|
|
- 'eta': '预计到达',
|
|
|
|
|
- }
|
|
|
|
|
- safe_schedule = {}
|
|
|
|
|
- for key in schedule_keys:
|
|
|
|
|
- value = schedule.get(key)
|
|
|
|
|
- if not isinstance(value, str):
|
|
|
|
|
- return self._format_error(meta)
|
|
|
|
|
- safe_schedule[labels[key]] = value
|
|
|
|
|
- safe_ports = []
|
|
|
|
|
- for port in ports:
|
|
|
|
|
- if not isinstance(port, dict) or set(port) != {
|
|
|
|
|
- 'place_type', 'port_name', 'eta', 'ata',
|
|
|
|
|
- }:
|
|
|
|
|
- return self._format_error(meta)
|
|
|
|
|
- if port['place_type'] not in ('中转港', '目的港', '交货地'):
|
|
|
|
|
- return self._format_error(meta)
|
|
|
|
|
- for key in ('port_name', 'eta', 'ata'):
|
|
|
|
|
- if not isinstance(port[key], str):
|
|
|
|
|
- return self._format_error(meta)
|
|
|
|
|
- safe_ports.append({
|
|
|
|
|
- '地点类型': port['place_type'],
|
|
|
|
|
- '港口': port['port_name'],
|
|
|
|
|
- '预计到达': port['eta'],
|
|
|
|
|
- '实际到达': port['ata'],
|
|
|
|
|
- })
|
|
|
|
|
- safe_tracks = []
|
|
|
|
|
- for track in tracks:
|
|
|
|
|
- if not isinstance(track, dict) or set(track) != {
|
|
|
|
|
- 'time', 'location', 'content', 'event_code',
|
|
|
|
|
- }:
|
|
|
|
|
- return self._format_error(meta)
|
|
|
|
|
- for key in ('time', 'location', 'content', 'event_code'):
|
|
|
|
|
- if not isinstance(track[key], str):
|
|
|
|
|
- return self._format_error(meta)
|
|
|
|
|
- safe_tracks.append({
|
|
|
|
|
- '时间': track['time'],
|
|
|
|
|
- '地点': track['location'],
|
|
|
|
|
- '内容': track['content'],
|
|
|
|
|
- '状态代码': track['event_code'],
|
|
|
|
|
- })
|
|
|
|
|
content = {
|
|
content = {
|
|
|
- '船期': safe_schedule,
|
|
|
|
|
- '港口': safe_ports,
|
|
|
|
|
- '轨迹': safe_tracks,
|
|
|
|
|
|
|
+ '船期': schedule,
|
|
|
|
|
+ '港口': ports,
|
|
|
|
|
+ '箱子': containers,
|
|
|
|
|
+ '轨迹': tracks,
|
|
|
|
|
+ '港区计划': terminal_plan,
|
|
|
}
|
|
}
|
|
|
lines = ['飞驼船期']
|
|
lines = ['飞驼船期']
|
|
|
- for label, value in safe_schedule.items():
|
|
|
|
|
|
|
+ for label, value in schedule.items():
|
|
|
lines.append('- {0}: {1}'.format(label, value))
|
|
lines.append('- {0}: {1}'.format(label, value))
|
|
|
- lines.append('港口')
|
|
|
|
|
- if not safe_ports:
|
|
|
|
|
- lines.append('- 无')
|
|
|
|
|
- for port in safe_ports:
|
|
|
|
|
- lines.append(
|
|
|
|
|
- '- {0} {1} 预计到达 {2} 实际到达 {3}'.format(
|
|
|
|
|
- port['地点类型'], port['港口'], port['预计到达'], port['实际到达'],
|
|
|
|
|
- )
|
|
|
|
|
- )
|
|
|
|
|
- lines.append('轨迹')
|
|
|
|
|
- if not safe_tracks:
|
|
|
|
|
|
|
+ lines.extend(self._freightower_section('港口', ports, (
|
|
|
|
|
+ '地点类型', '港口', '码头', '预计离港', '实际离港', '预计到达', '实际到达',
|
|
|
|
|
+ )))
|
|
|
|
|
+ lines.extend(self._freightower_section('箱子', containers, (
|
|
|
|
|
+ '箱号', '箱型', '铅封号', '当前状态', '是否甩柜',
|
|
|
|
|
+ )))
|
|
|
|
|
+ lines.extend(self._freightower_section('轨迹', tracks, (
|
|
|
|
|
+ '时间', '地点', '内容', '状态代码', '实际/预计', '船名航次', '码头',
|
|
|
|
|
+ )))
|
|
|
|
|
+ if any(terminal_plan.values()):
|
|
|
|
|
+ lines.append('港区计划')
|
|
|
|
|
+ for label, value in terminal_plan.items():
|
|
|
|
|
+ lines.append('- {0}: {1}'.format(label, value))
|
|
|
|
|
+ else:
|
|
|
|
|
+ lines.append('港区计划')
|
|
|
lines.append('- 无')
|
|
lines.append('- 无')
|
|
|
- for track in safe_tracks:
|
|
|
|
|
- lines.append(
|
|
|
|
|
- '- {0} {1} {2} {3}'.format(
|
|
|
|
|
- track['时间'], track['地点'], track['内容'], track['状态代码'],
|
|
|
|
|
- )
|
|
|
|
|
- )
|
|
|
|
|
return self._success_result(content, '\n'.join(lines), meta)
|
|
return self._success_result(content, '\n'.join(lines), meta)
|
|
|
|
|
|
|
|
|
|
+ def _freightower_object(self, value, fields):
|
|
|
|
|
+ if not isinstance(value, dict) or set(value) != {key for key, _label in fields}:
|
|
|
|
|
+ return None
|
|
|
|
|
+ result = {}
|
|
|
|
|
+ for key, label in fields:
|
|
|
|
|
+ item = value.get(key)
|
|
|
|
|
+ if not isinstance(item, str):
|
|
|
|
|
+ return None
|
|
|
|
|
+ result[label] = item
|
|
|
|
|
+ return result
|
|
|
|
|
+
|
|
|
|
|
+ def _freightower_rows(self, rows, fields, choices):
|
|
|
|
|
+ if not isinstance(rows, list):
|
|
|
|
|
+ return None
|
|
|
|
|
+ keys = {key for key, _label in fields}
|
|
|
|
|
+ result = []
|
|
|
|
|
+ for row in rows:
|
|
|
|
|
+ if not isinstance(row, dict) or set(row) != keys:
|
|
|
|
|
+ return None
|
|
|
|
|
+ item = {}
|
|
|
|
|
+ for key, label in fields:
|
|
|
|
|
+ cell = row.get(key)
|
|
|
|
|
+ if not isinstance(cell, str):
|
|
|
|
|
+ return None
|
|
|
|
|
+ allowed = choices.get(key)
|
|
|
|
|
+ if allowed is not None and cell not in allowed:
|
|
|
|
|
+ return None
|
|
|
|
|
+ item[label] = cell
|
|
|
|
|
+ result.append(item)
|
|
|
|
|
+ return result
|
|
|
|
|
+
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def _freightower_section(title, rows, labels):
|
|
|
|
|
+ lines = [title]
|
|
|
|
|
+ if not rows:
|
|
|
|
|
+ lines.append('- 无')
|
|
|
|
|
+ return lines
|
|
|
|
|
+ for row in rows:
|
|
|
|
|
+ lines.append('- ' + ' '.join(row[label] for label in labels))
|
|
|
|
|
+ return lines
|
|
|
|
|
+
|
|
|
@staticmethod
|
|
@staticmethod
|
|
|
def _success_result(content, text, meta):
|
|
def _success_result(content, text, meta):
|
|
|
return {
|
|
return {
|