提交 5cc688bb authored 作者: 伍姿英's avatar 伍姿英

Merge branch 'release/3.13.0'

......@@ -3,3 +3,4 @@ from . import tt_controllers
from . import order_controller
from . import binary
from . import main
import json
import logging
from odoo import http, fields
from odoo.http import request
_logger = logging.getLogger(__name__)
class CmcIntegrationController(http.Controller):
# 1. 核心修复:将 type='json' 改为 type='http'
@http.route('/api/cmc/push', type='http', auth='public', methods=['POST'], csrf=False, cors='*')
def receive_tk_push(self, **post):
"""
接收创美程推送的接口
"""
# 2. 核心修复:从 httprequest.data 读取原始字节,并用 json.loads 解析
try:
raw_data = request.httprequest.data
if not raw_data:
return self._json_response({'status': 'error', 'message': 'Empty body'})
data = json.loads(raw_data)
_logger.info("收到创美程推送数据: %s", data)
except json.JSONDecodeError:
_logger.error("收到的数据不是合法的 JSON 格式")
return self._json_response({'status': 'error', 'message': 'Invalid JSON format'})
# 3. 基础数据校验
tracking_number = data.get('tracking_no')
event_type = data.get('event')
event_time_str = data.get('event_time')
event_time_local = data.get('event_time_local')
if not tracking_number or not event_type:
return self._json_response({'status': 'error', 'message': 'Missing parameters'})
public_user = request.env.ref('base.public_user')
bill = request.env['cc.bl'].sudo().search([('bl_no', '=', tracking_number)], limit=1)
if not bill:
return self._json_response({'status': 'error', 'message': 'Bill not found'})
# 4. 执行业务逻辑更新
try:
event_time = fields.Datetime.to_datetime(event_time_str)
dynamic_type = 'departure' if event_type == 'DEPARTURE' else 'arrival'
bill.with_user(public_user).sudo().write({
'flight_dynamic_ids': [(0, 0, {
'type': dynamic_type,
'event_time_utc': event_time,
'event_time_local': event_time_local,
# 'timezone': 'UTC',
'status': 'valid',
})]
})
if dynamic_type == 'departure':
bill.with_user(public_user).sudo().actual_departure_time = event_time
else:
bill.with_user(public_user).sudo().actual_arrival_time = event_time
# 5. 核心修复:使用自建的方法返回标准的 JSON 响应
return self._json_response({'status': 'success', 'message': 'Record updated'})
except Exception as e:
_logger.error("处理推送数据失败: %s", str(e))
return self._json_response({'status': 'error', 'message': str(e)})
def _json_response(self, result_dict):
"""
辅助方法:将字典转换为 type='http' 接口所需的 HTTP Response 对象
"""
return request.make_response(
json.dumps(result_dict),
headers=[('Content-Type', 'application/json')]
)
\ No newline at end of file
......@@ -10,7 +10,7 @@ import aiohttp
import certifi
import pytz
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
from odoo.exceptions import ValidationError, UserError
def get_rfc339_time(utc_time=None):
......@@ -1493,6 +1493,109 @@ class CcBl(models.Model):
except Exception as err:
logging.error('fetch_mail_dlv--error:%s' % str(err))
# 新增字段:实际起飞/落地时间。tracking=True 用于在 Chatter 中记录由原值改为现值的日志
actual_departure_time = fields.Datetime(string="实际起飞时间", tracking=True)
actual_arrival_time = fields.Datetime(string="实际落地时间", tracking=True)
# 关联起飞落地动态的 One2many 字段
flight_dynamic_ids = fields.One2many(
'freight.flight.dynamic', 'bill_id', string="起飞落地动态"
)
class FreightFlightDynamic(models.Model):
_name = 'freight.flight.dynamic'
_description = '起飞落地动态'
# 按创建时间降序排列
_order = 'create_date desc'
bill_id = fields.Many2one('cc.bl', string="关联提单", ondelete='cascade')
type = fields.Selection([
('departure', '起飞'),
('arrival', '落地')
], string="类型", required=True)
# Odoo 的 Datetime 字段在数据库层面默认就是以 0时区 (UTC) 存储的
event_time_utc = fields.Datetime(string="发生时间", default=fields.Datetime.now, required=True)
# 时区选择和计算本地时间
@api.model
def _tz_get(self):
return [(x, x) for x in pytz.all_timezones]
# timezone = fields.Selection(_tz_get, string='发生地时区', default='UTC', required=True)
event_time_local = fields.Char(string="发生地时间")
# 记录人,默认当前用户
user_id = fields.Many2one('res.users', string="记录人", default=lambda self: self.env.user, readonly=True)
# 状态
status = fields.Selection([
('valid', '有效'),
('invalid', '无效')
], string="状态", default='valid', readonly=True)
# @api.depends('event_time_utc', 'timezone')
# def _compute_local_time(self):
# """根据 UTC 时间和选择的时区,计算并拼接出当地时间字符串"""
# for record in self:
# if record.event_time_utc and record.timezone:
# tz = pytz.timezone(record.timezone)
# utc_dt = pytz.utc.localize(record.event_time_utc)
# local_dt = utc_dt.astimezone(tz)
# # 格式化为:2023-10-25 14:30:00 (Asia/Shanghai)
# record.event_time_local = f"{local_dt.strftime('%Y-%m-%d %H:%M:%S')} ({record.timezone})"
# else:
# record.event_time_local = False
# @api.model
# def create(self, vals):
# """
# 拦截创建方法:
# 新增时,查找该提单下同类型的【有效】记录,将其置为【无效】。
# 确保只有最新的一条是【有效】的。
# """
# bill_id = vals.get('bill_id')
# dyn_type = vals.get('type')
#
# if bill_id and dyn_type:
# # 寻找同类型且状态为有效的老记录
# existing_valid_records = self.search([
# ('bill_id', '=', bill_id),
# ('type', '=', dyn_type),
# ('status', '=', 'valid')
# ])
# if existing_valid_records:
# existing_valid_records.write({'status': 'invalid'})
#
# # 强制将新创建的记录设为有效
# vals['status'] = 'valid'
# return super(FreightFlightDynamic, self).create(vals)
@api.model
def create(self, vals):
vals['status'] = 'valid'
record = super(FreightFlightDynamic, self).create(vals)
if record.bill_id and record.type:
existing_valid_records = self.search([
('bill_id', '=', record.bill_id.id),
('type', '=', record.type),
('status', '=', 'valid'),
('id', '!=', record.id) # 核心注意点:必须排除刚刚创建的这条新记录自己
])
if existing_valid_records:
existing_valid_records.write({'status': 'invalid'})
if record.event_time_utc:
if record.type == 'departure':
record.bill_id.write({'actual_departure_time': record.event_time_utc})
elif record.type == 'arrival':
record.bill_id.write({'actual_arrival_time': record.event_time_utc})
return record
def unlink(self):
"""拦截删除方法:只能新增,不能删除"""
raise UserError("动态记录禁止删除!如需修正,请直接新增一条记录覆盖原状态。")
# 提单节点同步日志
class CcBlSyncLog(models.Model):
......
......@@ -27,7 +27,7 @@ class WarningConfig(models.Model):
# 当类型为"航班落地"时使用的占位展示字段
flight_landing_time = fields.Char(
string='预警时间点',
default='预计到达时间',
default='实际落地时间',
readonly=True
)
......@@ -539,7 +539,9 @@ class WarningConfig(models.Model):
for pkg in all_packages:
packages_by_bl[pkg.bl_id.id].append(pkg)
bl_eta_utc_dict = {bl.id: self.convert_to_utc(bl.eta) if bl.eta else None for bl in bl_objs}
# bl_eta_utc_dict = {bl.id: self.convert_to_utc(bl.eta) if bl.eta else None for bl in bl_objs}
bl_eta_utc_dict = {bl.id: bl.actual_arrival_time if bl.actual_arrival_time else None for bl in bl_objs}
# 3. 核心优化:彻底解耦时间节点和未同步节点的类型,分别收集 process_code!
bl_codes_needed = set()
......
......@@ -26,3 +26,9 @@ access_package_data_wizard_base.group_user,package_data_wizard base.group_user,c
access_warning_config_user,warning.config.user,model_warning_config,base.group_user,1,1,1,1
access_freight_flight_dynamic_user,freight_flight_dynamic,model_freight_flight_dynamic,base.group_user,1,1,1,1
......@@ -65,6 +65,28 @@
</group>
</page>
</notebook>
<xpath expr="//field[@name='cc_deadline']" position="after">
<field name="actual_departure_time"/>
<field name="actual_arrival_time"/>
</xpath>
<xpath expr="//notebook" position="inside">
<page string="起飞落地动态">
<field name="flight_dynamic_ids" context="{'default_bill_id': active_id}">
<tree string="动态记录" editable="top" delete="0" default_order="create_date desc">
<field name="type"/>
<field name="event_time_utc"/>
<!-- <field name="timezone"/>-->
<field name="event_time_local"/>
<field name="user_id"/>
<field name="create_date" string="创建时间" readonly="1"/>
<field name="status" widget="badge"
decoration-success="status == 'valid'"
decoration-muted="status == 'invalid'"/>
</tree>
</field>
</page>
</xpath>
</field>
</record>
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论