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

Merge branch 'release/3.3.0'

...@@ -6,11 +6,11 @@ msgid "" ...@@ -6,11 +6,11 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Odoo Server 16.0\n" "Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 01:56+0000\n" "POT-Creation-Date: 2025-09-23 09:28+0000\n"
"PO-Revision-Date: 2025-09-22 09:57+0800\n" "PO-Revision-Date: 2025-09-23 17:30+0800\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: \n" "Language-Team: \n"
"Language: zh_CN\n" "Language: zh\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
...@@ -173,6 +173,11 @@ msgstr "<span class=\"o_stat_text\">已交货大包</span>" ...@@ -173,6 +173,11 @@ msgstr "<span class=\"o_stat_text\">已交货大包</span>"
msgid "<span class=\"o_stat_text\">Goods</span>" msgid "<span class=\"o_stat_text\">Goods</span>"
msgstr "<span class=\"o_stat_text\">货物</span>" msgstr "<span class=\"o_stat_text\">货物</span>"
#. module: ccs_base
#: model_terms:ir.ui.view,arch_db:ccs_base.form_cc_bl_view
msgid "<span class=\"o_stat_text\">Picked Up Big Packages</span>"
msgstr "<span class=\"o_stat_text\">已提货大包</span>"
#. module: ccs_base #. module: ccs_base
#: model_terms:ir.ui.view,arch_db:ccs_base.form_cc_big_package_view #: model_terms:ir.ui.view,arch_db:ccs_base.form_cc_big_package_view
#: model_terms:ir.ui.view,arch_db:ccs_base.form_cc_bl_view #: model_terms:ir.ui.view,arch_db:ccs_base.form_cc_bl_view
...@@ -2545,6 +2550,39 @@ msgstr "托盘号" ...@@ -2545,6 +2550,39 @@ msgstr "托盘号"
msgid "Pallet Usage Date" msgid "Pallet Usage Date"
msgstr "托盘使用日期" msgstr "托盘使用日期"
#. module: ccs_base
#: model:ir.model.fields.selection,name:ccs_base.selection__cc_big_package__tally_state__picked_up
#: model_terms:ir.ui.view,arch_db:ccs_base.search_cc_big_package_view
msgid "Picked Up"
msgstr "已提货"
#. module: ccs_base
#. odoo-python
#: code:addons/ccs_base/models/cc_bill_loading.py:0
#, python-format
msgid "Picked Up Big Package"
msgstr "已提货大包"
#. module: ccs_base
#: model:ir.model.fields,field_description:ccs_base.field_cc_bl__picked_up_big_package_qty
msgid "Picked Up Big Package Qty"
msgstr "已提货大包数量"
#. module: ccs_base
#: model:ir.model.fields.selection,name:ccs_base.selection__cc_node__tally_state__picked_up
msgid "Picked up"
msgstr "已提货"
#. module: ccs_base
#: model:ir.model.fields,field_description:ccs_base.field_cc_big_package__pickup_time
msgid "Pickup Time"
msgstr "提货时间"
#. module: ccs_base
#: model:ir.model.fields,field_description:ccs_base.field_cc_big_package__pickup_user_id
msgid "Pickup User"
msgstr "提货人"
#. module: ccs_base #. module: ccs_base
#: model:ir.model.fields,field_description:ccs_base.field_cc_last_mile_provider__placement_area #: model:ir.model.fields,field_description:ccs_base.field_cc_last_mile_provider__placement_area
msgid "Placement Area" msgid "Placement Area"
......
...@@ -55,11 +55,16 @@ class CcBigPackage(models.Model): ...@@ -55,11 +55,16 @@ class CcBigPackage(models.Model):
# 增加is_cancel字段, 用于标识大包是否取消, 来自提单的is_cancel字段 # 增加is_cancel字段, 用于标识大包是否取消, 来自提单的is_cancel字段
is_cancel = fields.Boolean(string='Is Cancel', related='bl_id.is_cancel', store=True) is_cancel = fields.Boolean(string='Is Cancel', related='bl_id.is_cancel', store=True)
# 提货相关字段
pickup_user_id = fields.Many2one('res.users', 'Pickup User', index=True) # 提货人
pickup_time = fields.Datetime('Pickup Time') # 提货时间
tally_state = fields.Selection([ tally_state = fields.Selection([
('unprocessed_goods', 'Unprocessed goods'), ('unprocessed_goods', 'Unprocessed goods'),
('picked_up', 'Picked Up'),
('checked_goods', 'Checked goods'), ('checked_goods', 'Checked goods'),
('handover_completed', 'Handover Completed') ('handover_completed', 'Handover Completed')
], default='unprocessed_goods', string='Tally Status', index=True) # 理货状态 未理货/已理货/尾程交接 ], default='unprocessed_goods', string='Tally Status', index=True) # 理货状态 未理货/已提货/已理货/尾程交接
tally_user_id = fields.Many2one('res.users', 'Tally User', index=True) # 理货人 tally_user_id = fields.Many2one('res.users', 'Tally User', index=True) # 理货人
tally_time = fields.Datetime('Tally Time') # 理货时间 tally_time = fields.Datetime('Tally Time') # 理货时间
delivery_user_id = fields.Many2one('res.users', string='Delivery User', index=True) # 尾程交货人 delivery_user_id = fields.Many2one('res.users', string='Delivery User', index=True) # 尾程交货人
...@@ -583,6 +588,16 @@ class CcBL(models.Model): ...@@ -583,6 +588,16 @@ class CcBL(models.Model):
raise ValidationError( raise ValidationError(
_('Transfer B/L No. cannot be the same as B/L No. or Transfer B/L No.')) # 转单号不能与提单号或转单号重复 _('Transfer B/L No. cannot be the same as B/L No. or Transfer B/L No.')) # 转单号不能与提单号或转单号重复
@api.depends('big_package_ids', 'big_package_ids.tally_state', 'big_package_ids.is_cancel')
def cal_picked_up_big_package_qty(self):
"""
已提货的大包数量
"""
for item in self:
item.picked_up_big_package_qty = len(
item.big_package_ids.filtered(
lambda package: package.tally_state == 'picked_up' and not package.is_cancel))
@api.depends('big_package_ids', 'big_package_ids.tally_state', 'big_package_ids.is_cancel') @api.depends('big_package_ids', 'big_package_ids.tally_state', 'big_package_ids.is_cancel')
def cal_tally_big_package_qty(self): def cal_tally_big_package_qty(self):
""" """
...@@ -631,6 +646,9 @@ class CcBL(models.Model): ...@@ -631,6 +646,9 @@ class CcBL(models.Model):
# 大包数量 # 大包数量
big_package_qty = fields.Integer(string='Big Package Qty') big_package_qty = fields.Integer(string='Big Package Qty')
# 已提货大包数量
picked_up_big_package_qty = fields.Integer(string='Picked Up Big Package Qty', compute='cal_picked_up_big_package_qty',
store=True)
# 理货大包数量 # 理货大包数量
tally_big_package_qty = fields.Integer(string='Tally Big Package Qty', compute='cal_tally_big_package_qty', tally_big_package_qty = fields.Integer(string='Tally Big Package Qty', compute='cal_tally_big_package_qty',
store=True) store=True)
...@@ -855,6 +873,16 @@ class CcBL(models.Model): ...@@ -855,6 +873,16 @@ class CcBL(models.Model):
'domain': [('bl_id', '=', self.id), ('is_cancel', '=', False)], 'domain': [('bl_id', '=', self.id), ('is_cancel', '=', False)],
} }
def action_show_big_package_picked_up(self):
# 返回一个action,显示已提货的大包
return {
'name': _('Picked Up Big Package'),
'type': 'ir.actions.act_window',
'res_model': 'cc.big.package',
'view_mode': 'tree,form',
'domain': [('bl_id', '=', self.id), ('is_cancel', '=', False), ('tally_state', '=', 'picked_up')],
}
def action_show_big_package_tally(self): def action_show_big_package_tally(self):
# 返回一个action,显示已理货的大包 # 返回一个action,显示已理货的大包
return { return {
......
...@@ -43,6 +43,7 @@ class CcNode(models.Model): ...@@ -43,6 +43,7 @@ class CcNode(models.Model):
tally_state = fields.Selection([ tally_state = fields.Selection([
('unprocessed_goods', 'Unprocessed goods'), ('unprocessed_goods', 'Unprocessed goods'),
('picked_up', 'Picked up'),
('checked_goods', 'Checked goods'), ('checked_goods', 'Checked goods'),
('handover_completed', 'Handover Completed') ('handover_completed', 'Handover Completed')
], default='', string='Corresponding to the status of the big package', index=True) # 对应大包状态 未理货/已理货/尾程交接 ], default='', string='Corresponding to the status of the big package', index=True) # 对应大包状态 未理货/已理货/尾程交接
......
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
<field name="next_provider_name"/> <field name="next_provider_name"/>
<field name="pallet_number"/> <field name="pallet_number"/>
<field name="pallet_usage_date"/> <field name="pallet_usage_date"/>
<field name="pickup_user_id" optional="show"/>
<field name="pickup_time" optional="show"/>
<field name="tally_state" optional="show"/> <field name="tally_state" optional="show"/>
<field name="tally_user_id" optional="show"/> <field name="tally_user_id" optional="show"/>
<field name="tally_time" optional="show"/> <field name="tally_time" optional="show"/>
...@@ -70,6 +72,8 @@ ...@@ -70,6 +72,8 @@
<field name="pallet_usage_date" readonly="1"/> <field name="pallet_usage_date" readonly="1"/>
<field name="exception_info_ids" readonly="1" widget="many2many_tags"/> <field name="exception_info_ids" readonly="1" widget="many2many_tags"/>
<field name="is_cancel" string="Cancelled"/> <field name="is_cancel" string="Cancelled"/>
<field name="pickup_user_id" readonly="1" options="{'no_create':True}"/>
<field name="pickup_time" readonly="1"/>
<field name="tally_state" readonly="1"/> <field name="tally_state" readonly="1"/>
<field name="tally_user_id" readonly="1" options="{'no_create':True}"/> <field name="tally_user_id" readonly="1" options="{'no_create':True}"/>
<field name="tally_time" readonly="1"/> <field name="tally_time" readonly="1"/>
...@@ -136,6 +140,8 @@ ...@@ -136,6 +140,8 @@
<separator/> <separator/>
<filter string="Unprocessed goods" name="filter_unprocessed_goods" <filter string="Unprocessed goods" name="filter_unprocessed_goods"
domain="[('tally_state', '=', 'unprocessed_goods')]"/> domain="[('tally_state', '=', 'unprocessed_goods')]"/>
<filter string="Picked Up" name="filter_picked_up"
domain="[('tally_state', '=', 'picked_up')]"/>
<filter string="Checked goods" name="filter_checked_goods" <filter string="Checked goods" name="filter_checked_goods"
domain="[('tally_state', '=', 'checked_goods')]"/> domain="[('tally_state', '=', 'checked_goods')]"/>
<filter string="Handover Completed" name="filter_handover_completed" <filter string="Handover Completed" name="filter_handover_completed"
......
...@@ -93,6 +93,13 @@ ...@@ -93,6 +93,13 @@
<span class="o_stat_text">Tally Big Packages</span> <span class="o_stat_text">Tally Big Packages</span>
</div> </div>
</button> </button>
<button name="action_show_big_package_picked_up" type="object"
class="oe_stat_button" icon="fa-cube">
<div class="o_stat_info">
<field name="picked_up_big_package_qty" class="o_stat_value"/>
<span class="o_stat_text">Picked Up Big Packages</span>
</div>
</button>
<button name="action_show_big_package_delivered" type="object" <button name="action_show_big_package_delivered" type="object"
class="oe_stat_button" icon="fa-cube"> class="oe_stat_button" icon="fa-cube">
<div class="o_stat_info"> <div class="o_stat_info">
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
<data> <data>
<!-- 隐藏讨论菜单--> <!-- 隐藏讨论菜单-->
<menuitem id="mail.menu_root_discuss" active="False"/> <menuitem id="mail.menu_root_discuss" active="False"/>
<!-- 应用的权限给系统管理员-->
<menuitem id="base.menu_management" groups="base.group_system"/>
<!-- # 增加名称为主数据的,排序99--> <!-- # 增加名称为主数据的,排序99-->
<record id="menu_ccs_base_main" model="ir.ui.menu"> <record id="menu_ccs_base_main" model="ir.ui.menu">
<field name="name">Master Data</field> <field name="name">Master Data</field>
......
...@@ -95,19 +95,28 @@ class OrderController(http.Controller): ...@@ -95,19 +95,28 @@ class OrderController(http.Controller):
""" """
kwargs = json.loads(request.httprequest.data) kwargs = json.loads(request.httprequest.data)
pda_lang = kwargs.get('pda_lang') or 'zh' pda_lang = kwargs.get('pda_lang') or 'zh'
action_type = kwargs.get('action_type') or 'tally' # tally / handover action_type = kwargs.get('action_type') or 'tally' # tally / handover/pickup
res = {'state': 201, 'message': ''} res = {'state': 201, 'message': ''}
try: try:
logging.info('bl_info kwargs:%s' % kwargs) logging.info('bl_info kwargs:%s' % kwargs)
if kwargs.get('bl_no'): if kwargs.get('bl_no'):
bl_no = kwargs['bl_no'] bl_no = kwargs['bl_no']
# bl_obj = request.env['cc.bl'].sudo().search([('bl_no', '=', bl_no)]) # 提单
state_arr = ['draft', 'ccing'] state_arr = ['draft', 'ccing']
bl_obj = request.env['cc.bl'].sudo().deal_bl_no_and_transfer_bl_no( bl_obj = request.env['cc.bl'].sudo().deal_bl_no_and_transfer_bl_no(
bl_no) # 提单号去掉杠和空格,并转换为小写,优先匹配提单号,匹配不到则匹配转单号 bl_no) # 提单号去掉杠和空格,并转换为小写,优先匹配提单号,匹配不到则匹配转单号
if bl_obj: if bl_obj:
# 1.按提单交货时,创建批次,填写的提单号传给后台查找对应的提单号,在匹配查找前,先查找是否pda扫码记录,若有类型该提单,类型为理货的,且操作时间-当前时间小于等于配置的参数(交货操作晚于提货操作X分钟),则提示:该提单未到交货时间,有风险产生倒叙,请间隔规定时间后再扫码; # 1.按提单理货时,检查是否存在时间倒序风险
if action_type == 'handover': if action_type == 'tally':
# 检查是否存在时间倒序风险
time_check_result = self._check_tally_time_risk(bl_obj, pda_lang)
if time_check_result['has_risk']:
res['state'] = 400
res['message'] = time_check_result['message']
return res
# 2.按提单交货时,创建批次,填写的提单号传给后台查找对应的提单号,在匹配查找前,先查找是否pda扫码记录,
# 若有类型该提单,类型为理货的,且操作时间-当前时间小于等于配置的参数(交货操作晚于提货操作X分钟),则提示:该提单未到交货时间,有风险产生倒叙,请间隔规定时间后再扫码;
# 2025-09-23 新增PDA交货时,提示:该提单未到交货时间,有风险产生倒叙,请间隔规定时间后再扫码;此处判断需加上查找提货记录;
elif action_type == 'handover':
# 检查是否存在时间倒序风险 # 检查是否存在时间倒序风险
time_check_result = self._check_delivery_time_risk(bl_obj, pda_lang) time_check_result = self._check_delivery_time_risk(bl_obj, pda_lang)
if time_check_result['has_risk']: if time_check_result['has_risk']:
...@@ -151,8 +160,9 @@ class OrderController(http.Controller): ...@@ -151,8 +160,9 @@ class OrderController(http.Controller):
kwargs = json.loads(request.httprequest.data) kwargs = json.loads(request.httprequest.data)
pda_lang = kwargs.get('pda_lang') or 'zh' pda_lang = kwargs.get('pda_lang') or 'zh'
res = {'state': 201, 'message': ''} res = {'state': 201, 'message': ''}
action_type = kwargs.get('action_type') or 'tally' # tally / handover action_type = kwargs.get('action_type') or 'tally' # tally / handover/pickup
operation = 'bill_tally' if action_type == 'tally' else 'bill_handover' operation = 'bill_tally' if action_type == 'tally' else (
'bill_handover' if action_type == 'handover' else 'bill_pickup')
bl_obj = False bl_obj = False
tally_user_id = False # 理货人ID tally_user_id = False # 理货人ID
try: try:
...@@ -203,7 +213,9 @@ class OrderController(http.Controller): ...@@ -203,7 +213,9 @@ class OrderController(http.Controller):
package_obj.update_exception_info( package_obj.update_exception_info(
exception_cause_ids) # 修改异常信息 exception_cause_ids) # 修改异常信息
tally_time = package_item.get('tally_time') tally_time = package_item.get('tally_time')
if (action_type == 'tally' and package_item.get('tally_state') == 'checked_goods') or ( if (action_type == 'pickup' and package_item.get('tally_state') == 'picked_up') or (
action_type == 'tally' and package_item.get(
'tally_state') == 'checked_goods') or (
action_type == 'handover' and package_item.get( action_type == 'handover' and package_item.get(
'tally_state') == 'handover_completed'): 'tally_state') == 'handover_completed'):
if package_type == 'ship': if package_type == 'ship':
...@@ -214,9 +226,11 @@ class OrderController(http.Controller): ...@@ -214,9 +226,11 @@ class OrderController(http.Controller):
else: else:
for package in package_obj: for package in package_obj:
if ( if (
action_type == 'tally' and package.tally_state == 'unprocessed_goods') or ( action_type == 'pickup' and package.tally_state == 'unprocessed_goods') or (
action_type == 'tally' and package.tally_state in (
'unprocessed_goods', 'picked_up')) or (
action_type == 'handover' and package.tally_state in ( action_type == 'handover' and package.tally_state in (
'unprocessed_goods', 'checked_goods')): 'unprocessed_goods', 'checked_goods', 'picked_up')):
ship_packages.append({ ship_packages.append({
'id': package.ship_package_ids.ids, 'id': package.ship_package_ids.ids,
'bl_id': package.bl_id.id, 'bl_id': package.bl_id.id,
...@@ -359,8 +373,9 @@ class OrderController(http.Controller): ...@@ -359,8 +373,9 @@ class OrderController(http.Controller):
kwargs = json.loads(request.httprequest.data) kwargs = json.loads(request.httprequest.data)
pda_lang = kwargs.get('pda_lang') or 'zh' pda_lang = kwargs.get('pda_lang') or 'zh'
res = {'state': 201, 'message': ''} res = {'state': 201, 'message': ''}
action_type = kwargs.get('action_type') or 'tally' # tally / handover action_type = kwargs.get('action_type') or 'tally' # tally / handover/pickup
operation = 'tail_tally' if action_type == 'tally' else 'tail_handover' operation = 'tail_tally' if action_type == 'tally' else (
'tail_handover' if action_type == 'handover' else 'tail_pickup')
tally_user_id = False # 理货人ID tally_user_id = False # 理货人ID
ship_packages = [] ship_packages = []
try: try:
...@@ -399,7 +414,8 @@ class OrderController(http.Controller): ...@@ -399,7 +414,8 @@ class OrderController(http.Controller):
package_obj.update_exception_info( package_obj.update_exception_info(
exception_cause_ids) # 修改异常信息 exception_cause_ids) # 修改异常信息
tally_time = package_item.get('tally_time') tally_time = package_item.get('tally_time')
if (action_type == 'tally' and package_item.get('tally_state') == 'checked_goods') or ( if (action_type == 'pickup' and package_item.get('tally_state') == 'picked_up') or (
action_type == 'tally' and package_item.get('tally_state') == 'checked_goods') or (
action_type == 'handover' and package_item.get( action_type == 'handover' and package_item.get(
'tally_state') == 'handover_completed'): 'tally_state') == 'handover_completed'):
if package_type == 'ship': if package_type == 'ship':
...@@ -410,9 +426,11 @@ class OrderController(http.Controller): ...@@ -410,9 +426,11 @@ class OrderController(http.Controller):
else: else:
for package in package_obj: for package in package_obj:
if ( if (
action_type == 'tally' and package.tally_state == 'unprocessed_goods') or ( action_type == 'pickup' and package.tally_state == 'unprocessed_goods') or (
action_type == 'tally' and package.tally_state in (
'unprocessed_goods', 'picked_up')) or (
action_type == 'handover' and package.tally_state in ( action_type == 'handover' and package.tally_state in (
'unprocessed_goods', 'checked_goods')): 'unprocessed_goods', 'checked_goods', 'picked_up')):
ship_packages.append({ ship_packages.append({
'id': package.ship_package_ids.ids, 'id': package.ship_package_ids.ids,
'bl_id': package.bl_id.id, 'bl_id': package.bl_id.id,
...@@ -577,7 +595,7 @@ class OrderController(http.Controller): ...@@ -577,7 +595,7 @@ class OrderController(http.Controller):
res = {'state': 201, 'message': ''} res = {'state': 201, 'message': ''}
try: try:
logging.info('last_mile_tally kwargs:%s' % kwargs) logging.info('last_mile_tally kwargs:%s' % kwargs)
return self._get_last_mile_grouped('unprocessed_goods', pda_lang) return self._get_last_mile_grouped(['unprocessed_goods', 'picked_up'], pda_lang)
except Exception as e: except Exception as e:
exceptions_msg_dic = { exceptions_msg_dic = {
'en': 'System parsing error, the reason for the error is %s' % e, 'en': 'System parsing error, the reason for the error is %s' % e,
...@@ -600,7 +618,7 @@ class OrderController(http.Controller): ...@@ -600,7 +618,7 @@ class OrderController(http.Controller):
try: try:
logging.info('last_mile_delivery kwargs:%s' % kwargs) logging.info('last_mile_delivery kwargs:%s' % kwargs)
# 按尾程交货时,检查时间风险 大包或小包对应的提单是否已存在成功扫码记录 # 按尾程交货时,检查时间风险 大包或小包对应的提单是否已存在成功扫码记录
res = self._get_last_mile_grouped('checked_goods', pda_lang) res = self._get_last_mile_grouped(['checked_goods', 'picked_up'], pda_lang)
return res return res
except Exception as e: except Exception as e:
exceptions_msg_dic = { exceptions_msg_dic = {
...@@ -623,7 +641,7 @@ class OrderController(http.Controller): ...@@ -623,7 +641,7 @@ class OrderController(http.Controller):
res = {'state': 201, 'message': ''} res = {'state': 201, 'message': ''}
try: try:
logging.info('last_mile_delivery_time_check kwargs:%s' % kwargs) logging.info('last_mile_delivery_time_check kwargs:%s' % kwargs)
all_bl_ids_in_request = kwargs['bl_ids'] all_bl_ids_in_request = kwargs.get('bl_ids', [])
logging.info(f"all_bl_ids_in_request: {all_bl_ids_in_request}") logging.info(f"all_bl_ids_in_request: {all_bl_ids_in_request}")
if all_bl_ids_in_request: if all_bl_ids_in_request:
bl_objs = request.env['cc.bl'].sudo().search([('id', 'in', all_bl_ids_in_request)]) bl_objs = request.env['cc.bl'].sudo().search([('id', 'in', all_bl_ids_in_request)])
...@@ -644,24 +662,104 @@ class OrderController(http.Controller): ...@@ -644,24 +662,104 @@ class OrderController(http.Controller):
logging.info('last_mile_delivery_time_check res:%s' % res) logging.info('last_mile_delivery_time_check res:%s' % res)
return res return res
def _get_last_mile_grouped(self, tally_state, pda_lang, is_pallet=False): @http.route('/api/last_mile/tally/time_check', type='json', auth='public', csrf=False)
def last_mile_tally_time_check(self):
"""
尾程快递理货检查时间是否正常
"""
kwargs = json.loads(request.httprequest.data)
pda_lang = kwargs.get('pda_lang') or 'zh'
res = {'state': 201, 'message': ''}
try:
logging.info('last_mile_tally_time_check kwargs:%s' % kwargs)
all_bl_ids_in_request = kwargs.get('bl_ids', [])
logging.info(f"all_bl_ids_in_request: {all_bl_ids_in_request}")
if all_bl_ids_in_request:
bl_objs = request.env['cc.bl'].sudo().search([('id', 'in', all_bl_ids_in_request)])
for bl_obj in bl_objs:
time_check_result = self._check_tally_time_risk(bl_obj, pda_lang)
if time_check_result['has_risk']:
res['state'] = 400
res['message'] = time_check_result['message']
return res
res['state'] = 200
except Exception as e:
exceptions_msg_dic = {
'en': 'System parsing error, the reason for the error is %s' % e,
'zh': '系统解析错误,错误原因是%s' % e
}
logging.info('last_mile_tally_time_check error:%s' % e)
res['message'] = exceptions_msg_dic[pda_lang]
logging.info('last_mile_tally_time_check res:%s' % res)
return res
def _get_last_mile_grouped(self, tally_state_arr, pda_lang, is_pallet=False):
lang = 'zh_CN' if pda_lang == 'zh' else 'en_US' # 语言 lang = 'zh_CN' if pda_lang == 'zh' else 'en_US' # 语言
# if is_pallet:
# domain=[('state', '!=', 'done')]# 1. 按托盘理货时,查非已完成状态的提单 # 1. 查询大包数据,使用更高效的查询
# else: domain = [('bl_id.state', '=', 'ccing')] # 查所有清关中提单
# 先都查清关中的,如果以后要查非已完成状态的提单,再修改domain。而且查尾程快递和对应的大包或托盘信息得分两个接口 if not is_pallet or (is_pallet and 'unprocessed_goods' in tally_state_arr):
domain = [('bl_id.state', '=', 'ccing')] # 1. 按尾程理货时,查所有清关中提单 domain += [('tally_state', 'in', tally_state_arr)]
# 2. 查所有大包 如果是托盘的交货,不需要过滤理货状态
if not is_pallet or (is_pallet and tally_state == 'unprocessed_goods'): # 预加载关联数据,避免N+1查询
domain += [('tally_state', '=', tally_state)]
big_packages = request.env['cc.big.package'].sudo().search(domain) big_packages = request.env['cc.big.package'].sudo().search(domain)
# 3. 按"下一阶段服务商名称"分组 if not big_packages:
return {'provider_info_arr': [], 'state': 200, 'bl_ids': []}
# 2. 预加载所有服务商数据,避免重复查询
all_providers = request.env['cc.last.mile.provider'].sudo().with_context({'lang': lang}).search([])
provider_map = {} # 缓存服务商匹配结果
# 创建服务商匹配索引,提高匹配效率
provider_index = {}
for provider in all_providers:
if provider.matching_value:
matching_values = [value.lower().strip() for value in provider.matching_value.split('\n') if value.strip()]
for value in matching_values:
provider_index[value] = provider
# 3. 预加载托盘数据(如果需要)
pallet_data = {}
if is_pallet:
# 先匹配所有服务商,获取ID列表
provider_ids = set()
for pkg in big_packages:
if not pkg.next_provider_name:
continue
for provider in all_providers:
if provider.match_provider(pkg.next_provider_name, all_providers):
provider_ids.add(provider.id)
break
# 批量查询托盘数据
if provider_ids:
pallet_domain = [('express_company_id', 'in', list(provider_ids))]
if 'unprocessed_goods' in tally_state_arr:
pallet_domain.append(('usage_state', '=', 'unused'))
else:
pallet_domain.append(('usage_state', '=', 'used'))
pallets = request.env['cc.pallet'].sudo().search(pallet_domain)
for pallet in pallets:
if pallet.express_company_id.id not in pallet_data:
pallet_data[pallet.express_company_id.id] = []
pallet_data[pallet.express_company_id.id].append(pallet.search_pallet_info())
# 4. 按"下一阶段服务商名称"分组
group_dict = {} group_dict = {}
for pkg in big_packages: for pkg in big_packages:
provider = request.env['cc.last.mile.provider'].sudo().with_context({'lang': lang}).match_provider( if not pkg.next_provider_name:
pkg.next_provider_name) continue
# 使用缓存的匹配结果
if pkg.next_provider_name not in provider_map:
provider = provider_index.get(pkg.next_provider_name.lower().strip())
provider_map[pkg.next_provider_name] = provider
provider = provider_map[pkg.next_provider_name]
if not provider: if not provider:
continue continue
key = provider.id key = provider.id
if key not in group_dict: if key not in group_dict:
group_dict[key] = provider.search_pro_info() # 查询快递信息 group_dict[key] = provider.search_pro_info() # 查询快递信息
...@@ -669,31 +767,89 @@ class OrderController(http.Controller): ...@@ -669,31 +767,89 @@ class OrderController(http.Controller):
group_dict[key]['big_package_arr'] = [] # 大包信息 group_dict[key]['big_package_arr'] = [] # 大包信息
group_dict[key]['ship_package_arr'] = [] # 小包信息 group_dict[key]['ship_package_arr'] = [] # 小包信息
group_dict[key]['pallet_info_arr'] = [] # 托盘信息 group_dict[key]['pallet_info_arr'] = [] # 托盘信息
# 添加预加载的托盘数据
if is_pallet and key in pallet_data:
group_dict[key]['pallet_info_arr'] = sorted(pallet_data[key], key=lambda x: x.get('name', ''))
group_dict[key]['count'] += 1 group_dict[key]['count'] += 1
if tally_state == 'unprocessed_goods' or (tally_state == 'checked_goods' and not is_pallet):
group_dict[key]['big_package_arr'].append( # 添加大包信息
pkg.search_big_package_info(pda_lang=pda_lang, type=tally_state)) if 'unprocessed_goods' in tally_state_arr or ('checked_goods' in tally_state_arr and not is_pallet):
# 按托盘理货时返回未使用的托盘信息,按尾程时返回小包信息 for tally_state in tally_state_arr:
if is_pallet: group_dict[key]['big_package_arr'].append(
# 如果是未理货,查未使用托盘;如果是已理货,查已使用托盘 pkg.search_big_package_info(pda_lang=pda_lang, type=tally_state))
if tally_state == 'unprocessed_goods':
domain = [('usage_state', '=', 'unused')] # 添加小包信息(非托盘模式)
else: if not is_pallet:
domain = [('usage_state', '=', 'used')]
unused_pallets = request.env['cc.pallet'].sudo().search([('express_company_id', '=', key)] + domain)
pallet_info_arr = [pallet.search_pallet_info() for pallet in unused_pallets]
group_dict[key]['pallet_info_arr'] = pallet_info_arr
group_dict[key]['pallet_info_arr'].sort(key=lambda x: x.get('name', '')) # 根据托盘号升序排序
else:
group_dict[key]['ship_package_arr'].extend( group_dict[key]['ship_package_arr'].extend(
[ship_package_item.search_ship_package_info(pda_lang=pda_lang) for ship_package_item in [ship_package_item.search_ship_package_info(pda_lang=pda_lang) for ship_package_item in
pkg.ship_package_ids]) pkg.ship_package_ids])
# 4. 返回
# 5. 返回结果
provider_info_arr = list(group_dict.values()) provider_info_arr = list(group_dict.values())
# 按服务商名称升序排序 provider_info_arr.sort(key=lambda x: x.get('name', '')) # 按服务商名称升序排序
provider_info_arr.sort(key=lambda x: x.get('name', ''))
return {'provider_info_arr': provider_info_arr, 'state': 200, return {
'bl_ids': list(set(list(map(lambda x: x.bl_id.id, big_packages))))} 'provider_info_arr': provider_info_arr,
'state': 200,
'bl_ids': list(set(pkg.bl_id.id for pkg in big_packages))
}
def _check_tally_time_risk(self, bl_obj, pda_lang='zh', pallet_name=None):
"""
检查提单理货时间倒序风险(需要先有提货记录)
:param bl_obj: 提单对象
:param pda_lang: 语言设置
:param pallet_name: 托盘号
:return: 检查结果字典
"""
# 获取配置的理货操作晚于提货操作的时间参数(分钟)
tally_interval_time = request.env['ir.config_parameter'].sudo().get_param('tally_interval_time', '80')
allowed_minutes = int(tally_interval_time)
# 首先检查是否有提货记录
pickup_records = request.env['pda.scan.record'].sudo().search([
('bl_id', '=', bl_obj.id),
('record_type', '=', 'pickup'), # 提货类型
('state', '=', 'success') # 成功状态
], order='operation_time desc', limit=1)
if pickup_records:
# 有提货记录,检查时间间隔
latest_pickup_record = pickup_records[0]
current_time = request.env['common.common'].sudo().get_utc_time(datetime.now())
operation_time = latest_pickup_record.operation_time
time_diff = (datetime.strptime(current_time, '%Y-%m-%d %H:%M:%S') - operation_time).total_seconds() / 60
logging.info(
f"tally_time_check current_time: {current_time}, pickup_time: {operation_time}, time_diff: {time_diff}")
# 如果时间差小于等于配置的参数,则存在风险
if time_diff <= allowed_minutes:
if pallet_name:
if pda_lang == 'en':
message = f"The pallet {pallet_name} has not reached the tally time, and there is a risk of flashback. Please scan the code after {allowed_minutes} minutes."
else:
message = f"该托盘号{pallet_name}装载的提单未到理货上传时间,有风险产生倒叙,请间隔{allowed_minutes}分钟后再扫码;"
else:
if pda_lang == 'en':
message = f"The bill of lading has not reached the tally time, and there is a risk of flashback. Please scan the code after {allowed_minutes} minutes."
else:
message = f"该提单%s未到理货上传时间,有风险产生倒叙,请间隔{allowed_minutes}分钟后再扫码;" % bl_obj.bl_no
return {
'has_risk': True,
'message': message,
'time_diff': time_diff,
'allowed_minutes': allowed_minutes
}
return {
'has_risk': False,
'message': '',
'time_diff': 0,
'allowed_minutes': allowed_minutes
}
def _check_delivery_time_risk(self, bl_obj, pda_lang='zh', pallet_name=None): def _check_delivery_time_risk(self, bl_obj, pda_lang='zh', pallet_name=None):
""" """
...@@ -703,23 +859,42 @@ class OrderController(http.Controller): ...@@ -703,23 +859,42 @@ class OrderController(http.Controller):
:param pallet_name: 托盘号 :param pallet_name: 托盘号
:return: 检查结果字典 :return: 检查结果字典
""" """
# 获取配置的交货操作晚于提货操作的时间参数(分钟) # 获取配置的时间参数(分钟)
config_param = request.env['ir.config_parameter'].sudo().get_param('delivery_time', '80') tally_interval_time = int(
allowed_minutes = int(config_param) request.env['ir.config_parameter'].sudo().get_param('tally_interval_time', '80')) # 理货间隔时间
delivery_time = int(request.env['ir.config_parameter'].sudo().get_param('delivery_time', '80')) # 交货间隔时间
# 查找该提单的PDA扫码记录,类型为理货的 # 查找该提单的PDA扫码记录,类型为理货的
pda_records = request.env['pda.scan.record'].sudo().search([ tally_records = request.env['pda.scan.record'].sudo().search([
('bl_id', '=', bl_obj.id), ('bl_id', '=', bl_obj.id),
('record_type', '=', 'tally'), # 理货类型 ('record_type', '=', 'tally'), # 只查找理货类型
('state', '=', 'success') # 成功状态 ('state', '=', 'success') # 成功状态
], order='operation_time desc', limit=1) ], order='operation_time desc', limit=1)
if pda_records: # 查找该提单的PDA扫码记录,类型为提货的
latest_tally_record = pda_records[0] pickup_records = request.env['pda.scan.record'].sudo().search([
('bl_id', '=', bl_obj.id),
('record_type', '=', 'pickup'), # 只查找提货类型
('state', '=', 'success') # 成功状态
], order='operation_time desc', limit=1)
# 确定使用哪个记录和间隔时间
allowed_minutes = 0
latest_record = False
if pickup_records:
# 有提货记录,先提货再理货,使用提货记录,间隔时间 = 交货间隔时间 + 理货间隔时间
latest_record = pickup_records[0]
allowed_minutes = tally_interval_time + delivery_time
if tally_records:
# 有理货记录,按理货记录,使用理货记录,间隔时间 = 交货间隔时间
latest_record = tally_records[0]
allowed_minutes = delivery_time
if latest_record:
current_time = request.env['common.common'].sudo().get_utc_time(datetime.now()) current_time = request.env['common.common'].sudo().get_utc_time(datetime.now())
# Ensure operation_time is also timezone-aware (UTC) operation_time = latest_record.operation_time
operation_time = latest_tally_record.operation_time
time_diff = (datetime.strptime(current_time, '%Y-%m-%d %H:%M:%S') - operation_time).total_seconds() / 60 time_diff = (datetime.strptime(current_time, '%Y-%m-%d %H:%M:%S') - operation_time).total_seconds() / 60
logging.info(f"current_time: {current_time}, operation_time: {operation_time},time_diff: {time_diff}") logging.info(
f"delivery_time_check current_time: {current_time}, operation_time: {operation_time}, time_diff: {time_diff}")
# 如果时间差小于等于配置的参数,则存在风险 # 如果时间差小于等于配置的参数,则存在风险
if time_diff <= allowed_minutes: if time_diff <= allowed_minutes:
if pallet_name: if pallet_name:
...@@ -763,7 +938,6 @@ class OrderController(http.Controller): ...@@ -763,7 +938,6 @@ class OrderController(http.Controller):
if isinstance(tally_time, str): if isinstance(tally_time, str):
try: try:
# 尝试解析时间字符串 # 尝试解析时间字符串
from datetime import datetime
parsed_time = datetime.strptime(tally_time, '%Y-%m-%d %H:%M:%S') parsed_time = datetime.strptime(tally_time, '%Y-%m-%d %H:%M:%S')
if latest_time is None or parsed_time > latest_time: if latest_time is None or parsed_time > latest_time:
latest_time = parsed_time latest_time = parsed_time
...@@ -793,7 +967,7 @@ class OrderController(http.Controller): ...@@ -793,7 +967,7 @@ class OrderController(http.Controller):
res = {'state': 201, 'message': ''} res = {'state': 201, 'message': ''}
try: try:
logging.info('pallet_tally kwargs:%s' % kwargs) logging.info('pallet_tally kwargs:%s' % kwargs)
return self._get_last_mile_grouped('unprocessed_goods', pda_lang, is_pallet=True) return self._get_last_mile_grouped(['unprocessed_goods', 'picked_up'], pda_lang, is_pallet=True)
except Exception as e: except Exception as e:
exceptions_msg_dic = { exceptions_msg_dic = {
'en': 'System parsing error, the reason for the error is %s' % e, 'en': 'System parsing error, the reason for the error is %s' % e,
...@@ -816,7 +990,7 @@ class OrderController(http.Controller): ...@@ -816,7 +990,7 @@ class OrderController(http.Controller):
res = {'state': 201, 'message': ''} res = {'state': 201, 'message': ''}
try: try:
logging.info('pallet_handover kwargs:%s' % kwargs) logging.info('pallet_handover kwargs:%s' % kwargs)
res = self._get_last_mile_grouped('checked_goods', pda_lang, is_pallet=True) res = self._get_last_mile_grouped(['checked_goods', 'picked_up'], pda_lang, is_pallet=True)
except Exception as e: except Exception as e:
exceptions_msg_dic = { exceptions_msg_dic = {
'en': 'System parsing error, the reason for the error is %s' % e, 'en': 'System parsing error, the reason for the error is %s' % e,
...@@ -854,7 +1028,6 @@ class OrderController(http.Controller): ...@@ -854,7 +1028,6 @@ class OrderController(http.Controller):
'zh': '托盘号[%s]不存在' % ','.join(pallet_nos) 'zh': '托盘号[%s]不存在' % ','.join(pallet_nos)
}[pda_lang] }[pda_lang]
return res return res
# 将托盘状态更新为已使用 # 将托盘状态更新为已使用
all_big_package_arr = [] all_big_package_arr = []
for pallet_item in kwargs.get('pallet_arr'): for pallet_item in kwargs.get('pallet_arr'):
...@@ -870,6 +1043,16 @@ class OrderController(http.Controller): ...@@ -870,6 +1043,16 @@ class OrderController(http.Controller):
('big_package_no', 'in', [pkg.get('big_package_no') for pkg in big_package_arr]) ('big_package_no', 'in', [pkg.get('big_package_no') for pkg in big_package_arr])
]) ])
if big_package_objs: if big_package_objs:
#检查托盘关联的大包的提单时间是否倒序
# 获取所有大包对应的提单(去重)
bl_objs = big_package_objs.mapped('bl_id')
# 检查每个提单的时间风险
for bl_obj in bl_objs:
time_check_result = self._check_tally_time_risk(bl_obj, pda_lang, pallet.name)
if time_check_result['has_risk']:
res['state'] = 400
res['message'] = time_check_result['message']
return res
# 调用托盘的update_usage_state方法 # 调用托盘的update_usage_state方法
pallet.update_usage_state(big_package_objs, pallet_use_date) pallet.update_usage_state(big_package_objs, pallet_use_date)
tally_user_id = kwargs['pallet_arr'][0]['big_package_arr'][0].get('tally_user_id') if kwargs.get( tally_user_id = kwargs['pallet_arr'][0]['big_package_arr'][0].get('tally_user_id') if kwargs.get(
...@@ -894,14 +1077,9 @@ class OrderController(http.Controller): ...@@ -894,14 +1077,9 @@ class OrderController(http.Controller):
package_obj.update_exception_info( package_obj.update_exception_info(
exception_cause_ids) # 修改异常信息 exception_cause_ids) # 修改异常信息
tally_time = package_item.get('tally_time') tally_time = package_item.get('tally_time')
if (action_type == 'tally' and package_item.get('tally_state') == 'checked_goods') or ( if action_type == 'tally' and package_item.get('tally_state') == 'checked_goods':
action_type == 'handover' and package_item.get(
'tally_state') == 'handover_completed'):
for package in package_obj: for package in package_obj:
if ( if action_type == 'tally' and package.tally_state in ('picked_up','unprocessed_goods') :
action_type == 'tally' and package.tally_state == 'unprocessed_goods') or (
action_type == 'handover' and package.tally_state in (
'unprocessed_goods', 'checked_goods')):
ship_packages.append({ ship_packages.append({
'id': package.ship_package_ids.ids, 'id': package.ship_package_ids.ids,
'bl_id': package.bl_id.id, 'bl_id': package.bl_id.id,
...@@ -1148,3 +1326,192 @@ class OrderController(http.Controller): ...@@ -1148,3 +1326,192 @@ class OrderController(http.Controller):
res['message'] = exceptions_msg_dic[pda_lang] res['message'] = exceptions_msg_dic[pda_lang]
logging.info('pallet_delivery_time_check res:%s' % res) logging.info('pallet_delivery_time_check res:%s' % res)
return res return res
@http.route('/api/pallet/tally/time_check', type='json', auth='public', csrf=False)
def pallet_tally_time_check(self):
"""
托盘理货检查时间是否正常
"""
kwargs = json.loads(request.httprequest.data)
pda_lang = kwargs.get('pda_lang') or 'zh'
res = {'state': 201, 'message': ''}
try:
logging.info('pallet_tally_time_check kwargs:%s' % kwargs)
all_pallet_ids_in_request = kwargs['pallet_ids']
if all_pallet_ids_in_request:
pallet_objs = request.env['cc.pallet'].sudo().search([('id', 'in', all_pallet_ids_in_request)])
for pallet_obj in pallet_objs:
bl_objs = pallet_obj.package_ids.mapped('bl_id')
for bl_obj in bl_objs:
time_check_result = self._check_tally_time_risk(bl_obj, pda_lang, pallet_obj.name)
if time_check_result['has_risk']:
res['state'] = 400
res['message'] = time_check_result['message']
return res
res['state'] = 200
except Exception as e:
exceptions_msg_dic = {
'en': 'System parsing error, the reason for the error is %s' % e,
'zh': '系统解析错误,错误原因是%s' % e
}
logging.info('pallet_tally_time_check error:%s' % e)
res['message'] = exceptions_msg_dic[pda_lang]
logging.info('pallet_tally_time_check res:%s' % res)
return res
@http.route('/api/bl/pickup/info', type='json', auth='public', methods=['GET', 'POST'], csrf=False)
def bl_pickup_info(self):
"""
按提单提货查询接口
返回所有状态为未开始和清关中,且提单的关务节点状态不是已完成节点,
关务节点关联的小包状态,小包状态关联的大包状态不是已提货/已理货/尾程交接的提单信息
"""
kwargs = json.loads(request.httprequest.data)
pda_lang = kwargs.get('pda_lang') or 'zh'
res = {'state': 201, 'message': ''}
try:
logging.info('bl_pickup_info kwargs:%s' % kwargs)
# 查询符合条件的提单
# 1. 状态为未开始和清关中
# 2. 提单的关务节点状态不是已完成节点
# 3. 关务节点的package_state为空 或者 package_state的tally_state不在已提货/已理货/尾程交接状态
bl_domain = [
('state', 'in', ['draft', 'ccing']),
('customs_clearance_status.is_done', '=', False),
'|', ('customs_clearance_status.package_state', '=', False), # package_state为空
('customs_clearance_status.package_state.tally_state', 'not in',
['picked_up', 'checked_goods', 'handover_completed']) # 或者tally_state不在指定状态
]
bl_objs = request.env['cc.bl'].sudo().search(bl_domain)
if bl_objs:
res['bl_arr'] = [bl_obj.search_bl_info(pda_lang=pda_lang, type='pickup', is_all=False) for bl_obj in
bl_objs]
res['state'] = 200
else:
res['message'] = {
'en': 'No bills of lading found that meet the pickup criteria',
'zh': '没有找到符合提货条件的提单'
}[pda_lang]
except Exception as e:
exceptions_msg_dic = {
'en': 'System parsing error, the reason for the error is %s' % e,
'zh': '系统解析错误,错误原因是%s' % e
}
logging.info('bl_pickup_info error:%s' % e)
res['message'] = exceptions_msg_dic[pda_lang]
logging.info('bl_pickup_info res:%s' % res)
return res
@http.route('/api/bl/pickup/update', type='json', auth='public', methods=['GET', 'POST'], csrf=False)
def bl_pickup_update(self):
"""
按提单提货修改提货信息接口
请求参数是字典数组,包含了提单号和提货时间和提货人
回写提单下所有未理货的大包的理货状态和提货人,提货时间
且根据大包的状态回写对应小包的状态
"""
kwargs = json.loads(request.httprequest.data)
pda_lang = kwargs.get('pda_lang') or 'zh'
res = {'state': 201, 'message': ''}
state = 'picked_up' # 提货状态
operation = 'bill_pickup' # 操作
action_type = 'pickup' # 记录类型
tally_user_id = False # 提货人
try:
logging.info('bl_pickup_update kwargs:%s' % kwargs)
bl_arr = kwargs.get('bl_arr', [])
if not bl_arr:
res['message'] = {
'en': 'bl_arr is required',
'zh': '提货数据bl_arr必须提供'
}[pda_lang]
return res
error_messages = []
ship_packages = [] # 需要更新的小包
# 取第一个提货人的id
tally_user_id = bl_arr[0].get('tally_user_id')
for pickup_item in bl_arr:
bl_no = pickup_item.get('bl_no')
pickup_time = pickup_item.get('tally_time') # 提货时间
pickup_user_id = pickup_item.get('tally_user_id') # 提货人
if not all([bl_no, pickup_time, pickup_user_id]):
error_messages.append(f"提单号{bl_no}缺少必要参数")
continue
# 查找提单
bl_obj = request.env['cc.bl'].sudo().deal_bl_no_and_transfer_bl_no(bl_no)
if not bl_obj:
error_messages.append(f"提单号{bl_no}不存在")
continue
# 查找提单下所有未理货的大包
big_packages = bl_obj.big_package_ids.filtered(lambda x: x.tally_state == 'unprocessed_goods')
logging.info(f"big_packages: {big_packages}")
if big_packages:
# 更新大包提货信息
big_packages.update_big_package_info(action_type='pickup', tally_state=state,
tally_user_id=pickup_user_id, tally_time=pickup_time)
ship_packages.append({
'id': big_packages.ship_package_ids.ids,
'bl_id': bl_obj.id,
'tally_time': pickup_time
})
res['state'] = 200
# 创建成功的pda扫码记录
request.env['pda.scan.record'].sudo().create_scan_record(
operation=operation,
record_type=action_type,
bill_number=bl_obj.bl_no,
transfer_number=bl_obj.transfer_bl_no,
state='success',
bl_id=bl_obj.id,
operation_time=pickup_time,
operator_id=tally_user_id)
logging.info(
'update_big_package_tally_detail ship_packages:%s' % len(ship_packages))
# 有小包 就更新小包状态和同步
if ship_packages:
redis_conn = request.env['common.common'].sudo(
).get_redis()
if redis_conn and redis_conn != 'no':
# redis_conn.lpush('push_ship_package_state', json.dumps(
# {'bl_id': bl_obj.id, 'ship_package_ids': ship_package_ids}))
redis_conn.lpush('mail_push_package_list', json.dumps(
{'id': bl_obj.id, 'ship_packages': str(ship_packages), 'action_type': action_type}))
if error_messages:
res['message'] = ';'.join(error_messages)
except Exception as e:
exceptions_msg_dic = {
'en': 'System parsing error, the reason for the error is %s' % e,
'zh': '系统解析错误,错误原因是%s' % e
}
logging.info('bl_pickup_update error:%s' % e)
res['message'] = exceptions_msg_dic[pda_lang]
# 创建失败的pda扫码记录
if ship_packages:
bl_obj = request.env['cc.bl'].sudo().search(
[('id', 'in', [ship_package.get('bl_id') for ship_package in ship_packages])], limit=1)
if bl_obj:
for bl in bl_obj:
request.env['pda.scan.record'].sudo().create_scan_record(
operation=operation,
record_type=action_type,
bill_number=bl.bl_no,
transfer_number=bl.transfer_bl_no,
state='failed',
bl_id=bl.id,
failure_reason=e,
operator_id=tally_user_id)
else:
request.env['pda.scan.record'].sudo().create_scan_record(
operation=operation,
record_type=action_type,
bill_number='',
transfer_number='',
state='failed',
failure_reason=e,
operator_id=tally_user_id
)
logging.info('bl_pickup_update res:%s' % res)
return res
...@@ -6,8 +6,8 @@ msgid "" ...@@ -6,8 +6,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Odoo Server 16.0\n" "Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-22 01:59+0000\n" "POT-Creation-Date: 2025-09-23 09:31+0000\n"
"PO-Revision-Date: 2025-09-22 10:00+0800\n" "PO-Revision-Date: 2025-09-23 17:35+0800\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: \n" "Language-Team: \n"
"Language: zh_CN\n" "Language: zh_CN\n"
...@@ -95,6 +95,14 @@ msgstr "提单号" ...@@ -95,6 +95,14 @@ msgstr "提单号"
msgid "Bill Of Loading Sync Logs" msgid "Bill Of Loading Sync Logs"
msgstr "关务提单状态同步日志" msgstr "关务提单状态同步日志"
#. module: ccs_connect_tiktok
#. odoo-python
#: code:addons/ccs_connect_tiktok/models/pda_scan_record.py:0
#: model:ir.model.fields.selection,name:ccs_connect_tiktok.selection__pda_scan_record__operation__bill_pickup
#, python-format
msgid "Bill Pickup"
msgstr "按提单提货"
#. module: ccs_connect_tiktok #. module: ccs_connect_tiktok
#. odoo-python #. odoo-python
#: code:addons/ccs_connect_tiktok/models/pda_scan_record.py:0 #: code:addons/ccs_connect_tiktok/models/pda_scan_record.py:0
...@@ -388,6 +396,14 @@ msgstr "托盘" ...@@ -388,6 +396,14 @@ msgstr "托盘"
msgid "Pallet Handover" msgid "Pallet Handover"
msgstr "托盘交货" msgstr "托盘交货"
#. module: ccs_connect_tiktok
#. odoo-python
#: code:addons/ccs_connect_tiktok/models/pda_scan_record.py:0
#: model:ir.model.fields.selection,name:ccs_connect_tiktok.selection__pda_scan_record__operation__pallet_pickup
#, python-format
msgid "Pallet Pickup"
msgstr "托盘提货"
#. module: ccs_connect_tiktok #. module: ccs_connect_tiktok
#. odoo-python #. odoo-python
#: code:addons/ccs_connect_tiktok/models/pda_scan_record.py:0 #: code:addons/ccs_connect_tiktok/models/pda_scan_record.py:0
...@@ -396,6 +412,14 @@ msgstr "托盘交货" ...@@ -396,6 +412,14 @@ msgstr "托盘交货"
msgid "Pallet Tally" msgid "Pallet Tally"
msgstr "托盘理货" msgstr "托盘理货"
#. module: ccs_connect_tiktok
#. odoo-python
#: code:addons/ccs_connect_tiktok/models/pda_scan_record.py:0
#: model:ir.model.fields.selection,name:ccs_connect_tiktok.selection__pda_scan_record__record_type__pickup
#, python-format
msgid "Pickup"
msgstr "提货"
#. module: ccs_connect_tiktok #. module: ccs_connect_tiktok
#: model:ir.model.fields,field_description:ccs_connect_tiktok.field_cc_node__interval_minutes #: model:ir.model.fields,field_description:ccs_connect_tiktok.field_cc_node__interval_minutes
msgid "Predecessor Node Interval (Minutes)" msgid "Predecessor Node Interval (Minutes)"
...@@ -541,6 +565,14 @@ msgstr "进度编码" ...@@ -541,6 +565,14 @@ msgstr "进度编码"
msgid "Tail Handover" msgid "Tail Handover"
msgstr "按尾程交货" msgstr "按尾程交货"
#. module: ccs_connect_tiktok
#. odoo-python
#: code:addons/ccs_connect_tiktok/models/pda_scan_record.py:0
#: model:ir.model.fields.selection,name:ccs_connect_tiktok.selection__pda_scan_record__operation__tail_pickup
#, python-format
msgid "Tail Pickup"
msgstr ""
#. module: ccs_connect_tiktok #. module: ccs_connect_tiktok
#. odoo-python #. odoo-python
#: code:addons/ccs_connect_tiktok/models/pda_scan_record.py:0 #: code:addons/ccs_connect_tiktok/models/pda_scan_record.py:0
...@@ -685,7 +717,7 @@ msgstr "" ...@@ -685,7 +717,7 @@ msgstr ""
#. module: ccs_connect_tiktok #. module: ccs_connect_tiktok
#: model:ir.model.fields,field_description:ccs_connect_tiktok.field_res_config_settings__delivery_time #: model:ir.model.fields,field_description:ccs_connect_tiktok.field_res_config_settings__delivery_time
msgid "交货操作晚于提货操作X分钟" msgid "交货间隔时间"
msgstr "" msgstr ""
#. module: ccs_connect_tiktok #. module: ccs_connect_tiktok
...@@ -901,6 +933,11 @@ msgstr "" ...@@ -901,6 +933,11 @@ msgstr ""
msgid "状态" msgid "状态"
msgstr "" msgstr ""
#. module: ccs_connect_tiktok
#: model:ir.model.fields,field_description:ccs_connect_tiktok.field_res_config_settings__tally_interval_time
msgid "理货间隔时间"
msgstr ""
#. module: ccs_connect_tiktok #. module: ccs_connect_tiktok
#: model:ir.model.fields,field_description:ccs_connect_tiktok.field_ao_tt_api_log__source #: model:ir.model.fields,field_description:ccs_connect_tiktok.field_ao_tt_api_log__source
#: model_terms:ir.ui.view,arch_db:ccs_connect_tiktok.search_ao_tt_api_log_view #: model_terms:ir.ui.view,arch_db:ccs_connect_tiktok.search_ao_tt_api_log_view
......
...@@ -1169,22 +1169,28 @@ class CcBl(models.Model): ...@@ -1169,22 +1169,28 @@ class CcBl(models.Model):
'domain': [('bl_id', '=', self.id), ('is_sync', '=', False)], 'domain': [('bl_id', '=', self.id), ('is_sync', '=', False)],
} }
def search_bl_info(self, pda_lang=False, type='tally'): def search_bl_info(self, pda_lang=False, type='tally', is_all=True):
""" """
查询提单信息 查询提单信息
""" """
vals = { vals = {
'bl_id': self.id, # 提单id
'bl_no': self.bl_no or '', # 提单号 'bl_no': self.bl_no or '', # 提单号
'scan_big_package_qty': self.tally_big_package_qty + self.delivered_big_package_qty if type == 'tally' else self.delivered_big_package_qty,
# 已扫大包数量
'big_package_arr': [big_package_item.search_big_package_info(pda_lang=pda_lang, type=type) for
big_package_item in
self.big_package_ids],
# 大包信息
'ship_package_arr': [ship_package_item.search_ship_package_info(pda_lang=pda_lang) for ship_package_item in
self.ship_package_ids], # 小包信息
'pallet_arr': self.get_unique_pallet_info(), # 托盘信息
} }
if is_all:
vals.update({
'scan_big_package_qty': self.tally_big_package_qty + self.delivered_big_package_qty if type == 'tally' else self.delivered_big_package_qty,
# 已扫大包数量
'big_package_arr': [big_package_item.search_big_package_info(pda_lang=pda_lang, type=type) for
big_package_item in
self.big_package_ids],
# 大包信息
'ship_package_arr': [ship_package_item.search_ship_package_info(pda_lang=pda_lang) for ship_package_item
in
self.ship_package_ids], # 小包信息
'pallet_arr': self.get_unique_pallet_info(), # 托盘信息
})
return vals return vals
def get_unique_pallet_info(self): def get_unique_pallet_info(self):
...@@ -1243,7 +1249,8 @@ class CcBl(models.Model): ...@@ -1243,7 +1249,8 @@ class CcBl(models.Model):
user_obj = self.env['res.users'].search([('login', '=', pda_db_user)], limit=1) user_obj = self.env['res.users'].search([('login', '=', pda_db_user)], limit=1)
ship_package_ids = [ship_package_dict for sublist in [d['id'] for d in ship_packages] for ship_package_ids = [ship_package_dict for sublist in [d['id'] for d in ship_packages] for
ship_package_dict in sublist] ship_package_dict in sublist]
tally_state = 'checked_goods' if action_type == 'tally' else 'handover_completed' tally_state = 'checked_goods' if action_type == 'tally' else (
'picked_up' if action_type == 'pickup' else 'handover_completed')
# 后续节点 # 后续节点
node_obj = self.env['cc.node'].sudo().search([ node_obj = self.env['cc.node'].sudo().search([
('node_type', '=', 'package'), ('node_type', '=', 'package'),
...@@ -1417,11 +1424,16 @@ class CcBigPackage(models.Model): ...@@ -1417,11 +1424,16 @@ class CcBigPackage(models.Model):
def search_big_package_info(self, pda_lang=False, type='tally'): def search_big_package_info(self, pda_lang=False, type='tally'):
""" """
查询大包信息 查询大包信息
type 为 pickup 时,返回提货信息
""" """
unprocessed_goods_msg_dic = { unprocessed_goods_msg_dic = {
'en': 'Unprocessed goods', 'en': 'Unprocessed goods',
'zh': '未理货' 'zh': '未理货'
} }
picked_up_msg_dic = {
'en': 'Picked Up',
'zh': '已提货'
}
checked_goods_msg_dic = { checked_goods_msg_dic = {
'en': 'Checked goods', 'en': 'Checked goods',
'zh': '已理货' 'zh': '已理货'
...@@ -1431,21 +1443,24 @@ class CcBigPackage(models.Model): ...@@ -1431,21 +1443,24 @@ class CcBigPackage(models.Model):
'zh': '尾程交接' 'zh': '尾程交接'
} }
state_arr = {'unprocessed_goods': unprocessed_goods_msg_dic[pda_lang], state_arr = {'unprocessed_goods': unprocessed_goods_msg_dic[pda_lang],
'picked_up': picked_up_msg_dic[pda_lang],
'checked_goods': checked_goods_msg_dic[pda_lang], 'checked_goods': checked_goods_msg_dic[pda_lang],
'handover_completed': handover_completed_msg_dic[pda_lang]} # 未理货/已理货/尾程交接 'handover_completed': handover_completed_msg_dic[pda_lang]} # 未理货/已提货/已理货/尾程交接
# 根据下一阶段服务商名称获取尾程服务商的记录 # 根据下一阶段服务商名称获取尾程服务商的记录
provider_obj = self.env['cc.last.mile.provider'].match_provider(self.next_provider_name) provider_obj = self.env['cc.last.mile.provider'].match_provider(self.next_provider_name)
vals = { vals = {
'tally_state_label': state_arr[self.tally_state] or '', # 理货状态显示名称 'tally_state_label': state_arr[self.tally_state] or '', # 理货状态显示名称
'tally_state': self.tally_state or '', # 理货状态系统KEY 'tally_state': self.tally_state or '', # 理货状态系统KEY
'tally_user_id': (self.tally_user_id.id or 0) if type == 'tally' else (self.delivery_user_id.id or 0), 'tally_user_id': (self.tally_user_id.id or 0) if type == 'tally' else (
# 理货人id/交货人id (self.delivery_user_id.id or 0) if type == 'handover' else (self.pickup_user_id.id or 0)),
'tally_user_name': (self.tally_user_id.name or '') if type == 'tally' else ( # 理货人id/交货人id/提货人id
self.delivery_user_id.name or ''), 'tally_user_name': (self.tally_user_id.name or '') if type == 'tally' else ((
# 理货人名称/交货人名称 self.delivery_user_id.name or '') if type == 'handover' else (
'tally_time': (self.tally_time or '') if type == 'tally' else (self.delivery_time or ''), self.pickup_user_id.name or '')),
# self.env['common.common'].sudo().get_format_time(str(self.tally_time)) if self.tally_time else '', # 理货人名称/交货人名称/提货人名称
# 理货时间/交货时间 'tally_time': (self.tally_time or '') if type == 'tally' else (
(self.delivery_time or '') if type == 'handover' else (self.pickup_time or '')),
# 理货时间/交货时间/提货时间
'big_package_no': self.big_package_no or '', # 大包号 'big_package_no': self.big_package_no or '', # 大包号
'next_service_provider_name': self.next_provider_name or '', # 下一个服务商名称 'next_service_provider_name': self.next_provider_name or '', # 下一个服务商名称
'next_service_provider_tape_color': (provider_obj.tape_color_value or '') if provider_obj else '', 'next_service_provider_tape_color': (provider_obj.tape_color_value or '') if provider_obj else '',
...@@ -1457,17 +1472,20 @@ class CcBigPackage(models.Model): ...@@ -1457,17 +1472,20 @@ class CcBigPackage(models.Model):
def update_big_package_info(self, **kwargs): def update_big_package_info(self, **kwargs):
""" """
理货 tally/尾程交接 handover 理货 tally/尾程交接 handover/提货 pickup
""" """
action_type = kwargs.get('action_type') action_type = kwargs.get('action_type')
for item in self: for item in self:
if action_type == 'tally' and item.tally_state == 'unprocessed_goods': if action_type == 'tally' and item.tally_state in ('unprocessed_goods', 'picked_up'):
# 更新理货信息 # 更新理货信息
self._update_info(item, kwargs, 'tally') self._update_info(item, kwargs, 'tally')
elif action_type == 'handover' and item.tally_state != 'handover_completed': elif action_type == 'handover' and item.tally_state != 'handover_completed':
# 更新交接信息 # 更新交接信息
self._update_info(item, kwargs, 'handover') self._update_info(item, kwargs, 'handover')
elif action_type == 'pickup' and item.tally_state == 'unprocessed_goods':
# 更新提货信息
self._update_info(item, kwargs, 'pickup')
def _update_info(self, item, kwargs, action_type): def _update_info(self, item, kwargs, action_type):
""" """
...@@ -1480,6 +1498,13 @@ class CcBigPackage(models.Model): ...@@ -1480,6 +1498,13 @@ class CcBigPackage(models.Model):
item.tally_user_id = kwargs['tally_user_id'] item.tally_user_id = kwargs['tally_user_id']
if kwargs.get('tally_time'): if kwargs.get('tally_time'):
item.tally_time = datetime.strptime(kwargs['tally_time'], '%Y-%m-%d %H:%M:%S') item.tally_time = datetime.strptime(kwargs['tally_time'], '%Y-%m-%d %H:%M:%S')
elif action_type == 'pickup':
if kwargs.get('tally_state'):
item.tally_state = kwargs['tally_state']
if kwargs.get('tally_user_id'):
item.pickup_user_id = kwargs['tally_user_id']
if kwargs.get('tally_time'):
item.pickup_time = datetime.strptime(kwargs['tally_time'], '%Y-%m-%d %H:%M:%S')
elif action_type == 'handover': elif action_type == 'handover':
if kwargs.get('tally_state'): if kwargs.get('tally_state'):
item.tally_state = kwargs['tally_state'] item.tally_state = kwargs['tally_state']
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from odoo import models, fields, api, _ from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
class PDAScanRecord(models.Model): class PDAScanRecord(models.Model):
...@@ -16,28 +15,32 @@ class PDAScanRecord(models.Model): ...@@ -16,28 +15,32 @@ class PDAScanRecord(models.Model):
if self.operation: if self.operation:
if 'tally' in self.operation: if 'tally' in self.operation:
self.record_type = 'tally' self.record_type = 'tally'
elif 'pickup' in self.operation:
self.record_type = 'pickup'
elif 'handover' in self.operation: elif 'handover' in self.operation:
self.record_type = 'handover' self.record_type = 'handover'
operator_id = fields.Many2one('res.users', string='操作人', required=True) operator_id = fields.Many2one('res.users', string='操作人', required=True)
operation_time = fields.Datetime(string='操作时间', required=True, default=fields.Datetime.now) operation_time = fields.Datetime(string='操作时间', required=True, default=fields.Datetime.now)
operation = fields.Selection([ operation = fields.Selection([
('bill_tally', _('Bill Tally')), # 按提单理货 ('bill_tally', _('Bill Tally')), # 按提单理货
('tail_tally', _('Tail Tally')), # 按尾程理货 ('tail_tally', _('Tail Tally')), # 按尾程理货
('pallet_tally', _('Pallet Tally')), # 按托盘理货 ('pallet_tally', _('Pallet Tally')), # 按托盘理货
('bill_handover', _('Bill Handover')), # 按提单交货 ('bill_pickup', _('Bill Pickup')), # 按提单提货
('tail_handover', _('Tail Handover')), # 按尾程交货 ('bill_handover', _('Bill Handover')), # 按提单交货
('tail_handover', _('Tail Handover')), # 按尾程交货
('pallet_handover', _('Pallet Handover')) # 按托盘交货 ('pallet_handover', _('Pallet Handover')) # 按托盘交货
], string=_('Operation'), required=True) # 操作 ], string=_('Operation'), required=True) # 操作
record_type = fields.Selection([ record_type = fields.Selection([
('tally', _('Tally')), # 理货 ('tally', _('Tally')), # 理货
('handover', _('Handover')) # 交货 ('pickup', _('Pickup')), # 提货
('handover', _('Handover')) # 交货
], string=_('Type'), required=True) # 类型 ], string=_('Type'), required=True) # 类型
bill_number = fields.Char(string=_('Bill Number')) # 提单号 bill_number = fields.Char(string=_('Bill Number')) # 提单号
transfer_number = fields.Char(string=_('Transfer Number')) # 转运单号 transfer_number = fields.Char(string=_('Transfer Number')) # 转运单号
# 增加提单关联字段 # 增加提单关联字段
bl_id = fields.Many2one('cc.bl', string=_('Bill of Lading'), ondelete='cascade') # 提单对象 bl_id = fields.Many2one('cc.bl', string=_('Bill of Lading'), ondelete='cascade') # 提单对象
#增加状态 成功 失败 # 增加状态 成功 失败
state = fields.Selection([ state = fields.Selection([
('success', _('Success')), ('success', _('Success')),
('failed', _('Failed')) ('failed', _('Failed'))
...@@ -45,7 +48,8 @@ class PDAScanRecord(models.Model): ...@@ -45,7 +48,8 @@ class PDAScanRecord(models.Model):
failure_reason = fields.Char(string=_('Failure Reason')) # 失败原因 failure_reason = fields.Char(string=_('Failure Reason')) # 失败原因
@api.model @api.model
def create_scan_record(self, operation, record_type, bill_number, transfer_number, state, operator_id=False, bl_id=False, failure_reason=False, operation_time=False): def create_scan_record(self, operation, record_type, bill_number, transfer_number, state, operator_id=False,
bl_id=False, failure_reason=False, operation_time=False):
""" """
创建扫码记录的方法,供接口调用 创建扫码记录的方法,供接口调用
Create scan record method for API calls Create scan record method for API calls
...@@ -53,13 +57,13 @@ class PDAScanRecord(models.Model): ...@@ -53,13 +57,13 @@ class PDAScanRecord(models.Model):
try: try:
if not operator_id: if not operator_id:
operator_id = self.env['res.users'].search([('login', '=', 'pda')], limit=1).id operator_id = self.env['res.users'].search([('login', '=', 'pda')], limit=1).id
# 如果没有传入bl_id,根据bill_number查找提单对象 # 如果没有传入bl_id,根据bill_number查找提单对象
if not bl_id and bill_number: if not bl_id and bill_number:
bl_obj = self.env['cc.bl'].sudo().deal_bl_no_and_transfer_bl_no(bill_number) bl_obj = self.env['cc.bl'].sudo().deal_bl_no_and_transfer_bl_no(bill_number)
if bl_obj: if bl_obj:
bl_id = bl_obj.id bl_id = bl_obj.id
# 准备创建记录的数据 # 准备创建记录的数据
record_data = { record_data = {
'operator_id': operator_id, 'operator_id': operator_id,
...@@ -71,11 +75,11 @@ class PDAScanRecord(models.Model): ...@@ -71,11 +75,11 @@ class PDAScanRecord(models.Model):
'state': state, 'state': state,
'failure_reason': failure_reason 'failure_reason': failure_reason
} }
# 如果传入了自定义操作时间,使用它 # 如果传入了自定义操作时间,使用它
if operation_time: if operation_time:
record_data['operation_time'] = operation_time record_data['operation_time'] = operation_time
record = self.create(record_data) record = self.create(record_data)
return { return {
'success': True, 'success': True,
...@@ -86,4 +90,4 @@ class PDAScanRecord(models.Model): ...@@ -86,4 +90,4 @@ class PDAScanRecord(models.Model):
return { return {
'success': False, 'success': False,
'message': _('Creation failed: %s') % str(e) # 创建失败 'message': _('Creation failed: %s') % str(e) # 创建失败
} }
\ No newline at end of file
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
# Part of SmartGo. See LICENSE file for full copyright and licensing details. # Part of SmartGo. See LICENSE file for full copyright and licensing details.
import logging import logging
from odoo import api, fields, models, _ from odoo import api, fields, models
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
...@@ -15,14 +15,19 @@ class ResConfigSettings(models.TransientModel): ...@@ -15,14 +15,19 @@ class ResConfigSettings(models.TransientModel):
tt_app_secret = fields.Char('AppSecret', default='') tt_app_secret = fields.Char('AppSecret', default='')
tt_version = fields.Char('接口版本', default='3.0') tt_version = fields.Char('接口版本', default='3.0')
tt_customer_id = fields.Many2one('res.partner', string='客户') tt_customer_id = fields.Many2one('res.partner', string='客户')
#交货操作晚于提货操作X分钟【默认80分钟】 # 交货操作晚于提货操作X分钟【默认80分钟】
delivery_time = fields.Integer('交货操作晚于提货操作X分钟', default=80,config_parameter='delivery_time') delivery_time = fields.Integer('交货间隔时间', default=80, config_parameter='delivery_time')
# 提货操作晚于理货操作X分钟【默认80分钟】
tally_interval_time = fields.Integer('理货间隔时间', default=80, config_parameter='tally_interval_time')
# 巡查配置 # 巡查配置
patrol_receiver_emails = fields.Char('接收邮件地址', help='多个邮箱地址,用逗号分隔', config_parameter='patrol_receiver_emails') patrol_receiver_emails = fields.Char('接收邮件地址', help='多个邮箱地址,用逗号分隔',
config_parameter='patrol_receiver_emails')
patrol_sender_email = fields.Char('发送邮箱地址', config_parameter='patrol_sender_email') patrol_sender_email = fields.Char('发送邮箱地址', config_parameter='patrol_sender_email')
patrol_check_days = fields.Integer('巡查天数', default=5, help='检查近几天的提单', config_parameter='patrol_check_days') patrol_check_days = fields.Integer('巡查天数', default=5, help='检查近几天的提单',
patrol_start_hour = fields.Integer('巡查开始时间(小时)', default=8, help='北京时间,24小时制', config_parameter='patrol_start_hour') config_parameter='patrol_check_days')
patrol_start_hour = fields.Integer('巡查开始时间(小时)', default=8, help='北京时间,24小时制',
config_parameter='patrol_start_hour')
@api.model @api.model
def get_values(self): def get_values(self):
...@@ -38,6 +43,7 @@ class ResConfigSettings(models.TransientModel): ...@@ -38,6 +43,7 @@ class ResConfigSettings(models.TransientModel):
tt_version = config.get_param('tt_version', default='') tt_version = config.get_param('tt_version', default='')
tt_customer_id = config.get_param('tt_customer_id', default=False) tt_customer_id = config.get_param('tt_customer_id', default=False)
delivery_time = config.get_param('delivery_time', default=80) delivery_time = config.get_param('delivery_time', default=80)
tally_interval_time = config.get_param('tally_interval_time', default=80)
patrol_receiver_emails = config.get_param('patrol_receiver_emails', default='') patrol_receiver_emails = config.get_param('patrol_receiver_emails', default='')
patrol_sender_email = config.get_param('patrol_sender_email', default='') patrol_sender_email = config.get_param('patrol_sender_email', default='')
patrol_check_days = config.get_param('patrol_check_days', default=5) patrol_check_days = config.get_param('patrol_check_days', default=5)
...@@ -50,6 +56,7 @@ class ResConfigSettings(models.TransientModel): ...@@ -50,6 +56,7 @@ class ResConfigSettings(models.TransientModel):
tt_version=tt_version, tt_version=tt_version,
tt_customer_id=customer, tt_customer_id=customer,
delivery_time=delivery_time, delivery_time=delivery_time,
tally_interval_time=tally_interval_time,
patrol_receiver_emails=patrol_receiver_emails, patrol_receiver_emails=patrol_receiver_emails,
patrol_sender_email=patrol_sender_email, patrol_sender_email=patrol_sender_email,
patrol_check_days=patrol_check_days, patrol_check_days=patrol_check_days,
...@@ -66,7 +73,8 @@ class ResConfigSettings(models.TransientModel): ...@@ -66,7 +73,8 @@ class ResConfigSettings(models.TransientModel):
ir_config.set_param("tt_version", self.tt_version or "") ir_config.set_param("tt_version", self.tt_version or "")
ir_config.set_param("tt_customer_id", self.tt_customer_id.id or False) ir_config.set_param("tt_customer_id", self.tt_customer_id.id or False)
ir_config.set_param("delivery_time", self.delivery_time or 80) ir_config.set_param("delivery_time", self.delivery_time or 80)
ir_config.set_param("tally_interval_time", self.tally_interval_time or 80)
ir_config.set_param("patrol_receiver_emails", self.patrol_receiver_emails or "") ir_config.set_param("patrol_receiver_emails", self.patrol_receiver_emails or "")
ir_config.set_param("patrol_sender_email", self.patrol_sender_email or "") ir_config.set_param("patrol_sender_email", self.patrol_sender_email or "")
ir_config.set_param("patrol_check_days", self.patrol_check_days or 5) ir_config.set_param("patrol_check_days", self.patrol_check_days or 5)
ir_config.set_param("patrol_start_hour", self.patrol_start_hour or 8) ir_config.set_param("patrol_start_hour", self.patrol_start_hour or 8)
\ No newline at end of file
...@@ -25,6 +25,10 @@ ...@@ -25,6 +25,10 @@
<label for="tt_customer_id"/> <label for="tt_customer_id"/>
<field name="tt_customer_id"/> <field name="tt_customer_id"/>
</div> </div>
<div class="text-muted">
<label for="tally_interval_time"/>
<field name="tally_interval_time"/>
</div>
<div class="text-muted"> <div class="text-muted">
<label for="delivery_time"/> <label for="delivery_time"/>
<field name="delivery_time"/> <field name="delivery_time"/>
......
List View Sticky Header V16
============================
This module will helps to stick the list view header.
Credits
=======
Cybrosys Techno Solutions
Author
------
* Cybrosys Techno Solutions <odoo@cybrosys.com>
Muhammad Shahil @ Cybro
V15: Midilaj V K @ Cybro
V16: Viswanth @ Cybro
# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2022-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
{
'name': 'Sticky Header in List and Form view',
'version': '16.0.1.0.1',
'summary': 'Helps to Stick The Header of List View',
'description': 'Helps to Stick The Header of List View',
'category': 'Tools',
'author': 'Cybrosys Techno Solutions',
'company': 'Cybrosys Techno Solutions',
'maintainer': 'Cybrosys Techno Solutions',
'website': "https://www.cybrosys.com",
'license': 'LGPL-3',
'images': ['static/description/banner.png'],
'depends': ['base'],
'assets': {
'web.assets_backend': [
"list_view_sticky_header/static/src/scss/sticky_header.scss"
],
},
'installable': True,
'auto_install': False,
'application': False,
}
## Module <list_view_sticky_header>
#### 14.09.2022
#### Version 16.0.1.0.0
#### ADD Initial Commit for list_view_sticky_header
<div style="background-color: #714B67; min-height: 600px; width: 100%; padding: 15px; position: relative;">
<!-- TITLE BAR -->
<div
style="border-bottom: 1px solid #875A7B; padding: 15px; display: flex; justify-content: space-between; align-items: center;">
<img src="assets/misc/cybrosys-logo.png" width="42" height="42" style="width: 42px; height: 42px;" />
<div>
<div style="color: #7C7BAD; font-size: 14px; font-family: 'Montserrat', sans-serif; font-weight: bold; background-color: white; display: inline-block; padding: 3px 10px; border-radius: 50px;"
class="mr-2">
<i class="fa fa-check mr-1"></i>Community
</div>
<div style="color: #875A7B; font-size: 14px; font-family: 'Montserrat', sans-serif; font-weight: bold; background-color: white; display: inline-block; padding: 3px 10px; border-radius: 50px;"
class="mr-2">
<i class="fa fa-check mr-1"></i>Enterprise
</div>
<div style="color: #017E84; font-size: 14px; font-family: 'Montserrat', sans-serif; font-weight: bold; background-color: white; display: inline-block; padding: 3px 10px; border-radius: 50px;"
class="mr-2">
<i class="fa fa-check mr-1"></i>Odoo.sh
</div>
</div>
</div>
<!-- END OF TITLE BAR -->
<!-- APP HERO -->
<h1 style="color: #FFFFFF; font-weight: bolder; font-size: 50px; text-align: center; margin-top: 50px;">Sticky Header in List and Form view</h1>
<p style="color:#FFFFFF; padding: 8px 15px; text-align: center; font-size: 24px;">Helps to Stick The Header of List View</p>
<!-- END OF APP HERO -->
<img src="assets/screenshots/hero.png"
style="width: 75%; height: auto; position: absolute; margin-left: auto; margin-right: auto; top: 45%; left: 12%; right: auto;" />
</div>
<!-- NAVIGATION SECTION -->
<div class="d-flex align-items-center" style="border-bottom: 2px solid #714B67; padding: 15px 0px; margin-top: 300px;">
<div class="d-flex justify-content-center align-items-center mr-2"
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
<img src="assets/misc/compass.png" />
</div>
<h2 class="mt-2" style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">Explore This
Module</h2>
</div>
<div class="row my-4" style="font-family: 'Montserrat', sans-serif;">
<div class="col-sm-12 col-md-6 my-3">
<a href="#overview">
<div class="d-flex justify-content-between align-items-center"
style="background-color: #f5f5f5; padding: 30px; width: 100%;">
<div>
<span style="color: #714B67; font-size: 24px; font-weight: 500; display: block;">Overview</span>
<span
style="color: #714B67; font-size: 16px; font-weight: 400; color:#282F33; display: block;">Learn
more about this
module</span>
</div>
<img src="assets/misc/right-arrow.png" width="36" height="36" />
</div>
</a>
</div>
<div class="col-sm-12 col-md-6 my-3">
<a href="#features">
<div class="d-flex justify-content-between align-items-center"
style="background-color: #f5f5f5; padding: 30px; width: 100%;">
<div>
<span style="color: #714B67; font-size: 24px; font-weight: 500; display: block;">Features</span>
<span
style="color: #714B67; font-size: 16px; font-weight: 400; color:#282F33; display: block;">View
features of this
module</span>
</div>
<img src="assets/misc/right-arrow.png" width="36" height="36" />
</div>
</a>
</div>
<div class="col-sm-12 col-md-6 my-3">
<a href="#screenshots">
<div class="d-flex justify-content-between align-items-center"
style="background-color: #f5f5f5; padding: 30px; width: 100%;">
<div>
<span style="color: #714B67; font-size: 24px; font-weight: 500; display: block;">Screenshots</span>
<span
style="color: #714B67; font-size: 16px; font-weight: 400; color:#282F33; display: block;">View
screenshots of this
module</span>
</div>
<img src="assets/misc/right-arrow.png" width="36" height="36" />
</div>
</a>
</div>
</div>
<!-- END OF NAVIGATION SECTION -->
<!-- OVERVIEW SECTION -->
<div class="d-flex align-items-center" style="border-bottom: 2px solid #714B67; padding: 15px 0px;" id="overview">
<div class="d-flex justify-content-center align-items-center mr-2"
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
<img src="assets/misc/pie-chart.png" />
</div>
<h2 class="mt-2" style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">Overview
</h2>
</div>
<div class="row" style="font-family: 'Montserrat', sans-serif; font-weight: 400; font-size: 14px; line-height: 200%;">
<div class="col-sm-12 py-4">
This module will ensure sticky headers for list and form view,it enhance the user experience so that the user will never lose the headers while scrolling the records.
</div>
</div>
<!-- END OF OVERVIEW SECTION -->
<!-- FEATURES SECTION -->
<div class="d-flex align-items-center" style="border-bottom: 2px solid #714B67; padding: 15px 0px;" id="features">
<div class="d-flex justify-content-center align-items-center mr-2"
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
<img src="assets/misc/features.png" />
</div>
<h2 class="mt-2" style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">Features
</h2>
</div>
<div class="row" style="font-family: 'Montserrat', sans-serif; font-weight: 400; font-size: 14px; line-height: 200%;">
<div class="col-sm-12 col-md-6">
<div class="d-flex align-items-center" style="margin-top: 40px; margin-bottom: 40px">
<img src="assets/misc/check-box.png" class="mr-2" />
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Community & Enterprise Support</span>
</div>
<div class="d-flex align-items-center" style="margin-top: 30px; margin-bottom: 30px">
<img src="assets/misc/check-box.png" class="mr-2" />
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Sticky Header in List and Form views</span>
</div>
<div class="d-flex align-items-center" style="margin-top: 30px; margin-bottom: 30px">
<img src="assets/misc/check-box.png" class="mr-2" />
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Available in Odoo 16.0 Community and Enterprise</span>
</div>
</div>
</div>
<!-- END OF FEATURES SECTION -->
<!-- SCREENSHOTS SECTION -->
<div class="d-flex align-items-center" style="border-bottom: 2px solid #714B67; padding: 15px 0px;" id="screenshots">
<div class="d-flex justify-content-center align-items-center mr-2"
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
<img src="assets/misc/pictures.png" />
</div>
<h2 class="mt-2" style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">Screenshots
</h2>
</div>
<div class="row">
<div class="col-sm-12">
<div style="display: block; margin: 30px auto;">
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Sticky Header in List Views</h3>
<img src="assets/screenshots/1.png" class="img-thumbnail">
</div>
</div>
<div class="col-sm-12">
<div style="display: block; margin: 30px auto;">
<h3 style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Sticky Header in Form Views</h3>
<img src="assets/screenshots/2.png" class="img-thumbnail">
<img src="assets/screenshots/3.png" class="img-thumbnail">
</div>
</div>
</div>
<!-- END OF SCREENSHOTS SECTION -->
<!-- RELATED PRODUCTS -->
<div class="d-flex align-items-center" style="border-bottom: 2px solid #714B67; padding: 15px 0px;">
<div class="d-flex justify-content-center align-items-center mr-2"
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
<img src="assets/misc/categories.png" />
</div>
<h2 class="mt-2" style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">Related
Products
</h2>
</div>
<div class="row">
<div class="col-sm-12">
<div id="demo1" class="row carousel slide" data-ride="carousel">
<!-- The slideshow -->
<div class="carousel-inner" style="padding: 30px;">
<div class="carousel-item" style="min-height: 198.656px;">
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left">
<a href="https://apps.odoo.com/apps/modules/15.0/dynamic_accounts_report/" target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block" style="border-radius: 0px;"
src="assets/modules/1.png">
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left">
<a href="https://apps.odoo.com/apps/modules/15.0/custom_gantt_view/" target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block" style="border-radius: 0px;"
src="assets/modules/2.png">
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left">
<a href="https://apps.odoo.com/apps/modules/15.0/project_custom_gantt/" target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block" style="border-radius: 0px;"
src="assets/modules/3.png">
</div>
</a>
</div>
</div>
<div class="carousel-item active" style="min-height: 198.656px;">
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left">
<a href="https://apps.odoo.com/apps/modules/15.0/account_reports_xlsx/" target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block" style="border-radius: 0px;"
src="assets/modules/4.png">
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left">
<a href="https://apps.odoo.com/apps/modules/15.0/base_accounting_kit/" target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block" style="border-radius: 0px;"
src="assets/modules/5.png">
</div>
</a>
</div>
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float:left">
<a href="https://apps.odoo.com/apps/modules/15.0/hr_payroll_community/" target="_blank">
<div style="border-radius:10px">
<img class="img img-responsive center-block" style="border-radius: 0px;"
src="assets/modules/6.png">
</div>
</a>
</div>
</div>
</div>
<!-- Left and right controls -->
<a class="carousel-control-prev" href="#demo1" data-slide="prev" style="width:35px; color:#000"> <span
class="carousel-control-prev-icon"><i class="fa fa-chevron-left" style="font-size:24px"></i></span>
</a> <a class="carousel-control-next" href="#demo1" data-slide="next" style="width:35px; color:#000">
<span class="carousel-control-next-icon"><i class="fa fa-chevron-right"
style="font-size:24px"></i></span>
</a>
</div>
</div>
</div>
<!-- END OF RELATED PRODUCTS -->
<!-- OUR SERVICES -->
<div class="d-flex align-items-center" style="border-bottom: 2px solid #714B67; padding: 15px 0px;">
<div class="d-flex justify-content-center align-items-center mr-2"
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
<img src="assets/misc/star.png" />
</div>
<h2 class="mt-2" style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">Our Services
</h2>
</div>
<div class="container my-5">
<div class="row">
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #1dd1a1 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/cogs.png" class="img-responsive" height="48px" width="48px">
</div>
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Customization</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #ff6b6b !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/wrench.png" class="img-responsive" height="48px" width="48px">
</div>
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Implementation</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #6462CD !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/lifebuoy.png" class="img-responsive" height="48px" width="48px">
</div>
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Support</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #ffa801 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/user.png" class="img-responsive" height="48px" width="48px">
</div>
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Hire
Odoo
Developer</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #54a0ff !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/puzzle.png" class="img-responsive" height="48px" width="48px">
</div>
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Integration</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #6d7680 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/update.png" class="img-responsive" height="48px" width="48px">
</div>
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Migration</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #786fa6 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/consultation.png" class="img-responsive" height="48px" width="48px">
</div>
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Consultancy</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #f8a5c2 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/training.png" class="img-responsive" height="48px" width="48px">
</div>
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Implementation</h6>
</div>
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4">
<div class="d-flex justify-content-center align-items-center mx-3 my-3"
style="background-color: #e6be26 !important; border-radius: 15px !important; height: 80px; width: 80px;">
<img src="assets/icons/license.png" class="img-responsive" height="48px" width="48px">
</div>
<h6 class="text-center" style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;">
Odoo
Licensing Consultancy</h6>
</div>
</div>
</div>
<!-- END OF END OF OUR SERVICES -->
<!-- OUR INDUSTRIES -->
<div class="d-flex align-items-center" style="border-bottom: 2px solid #714B67; padding: 15px 0px;">
<div class="d-flex justify-content-center align-items-center mr-2"
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
<img src="assets/misc/corporate.png" />
</div>
<h2 class="mt-2" style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">Our
Industries
</h2>
</div>
<div class="container my-5">
<div class="row">
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
<img src="assets/icons/trading-black.png" class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Trading
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
Easily procure
and
sell your products</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
<img src="assets/icons/pos-black.png" class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
POS
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
Easy
configuration
and convivial experience</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
<img src="assets/icons/education-black.png" class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Education
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
A platform for
educational management</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
<img src="assets/icons/manufacturing-black.png" class="img-responsive mb-3" height="48px"
width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Manufacturing
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
Plan, track and
schedule your operations</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
<img src="assets/icons/ecom-black.png" class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
E-commerce &amp; Website
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
Mobile
friendly,
awe-inspiring product pages</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
<img src="assets/icons/service-black.png" class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Service Management
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
Keep track of
services and invoice</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
<img src="assets/icons/restaurant-black.png" class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Restaurant
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
Run your bar or
restaurant methodically</p>
</div>
</div>
<div class="col-lg-3">
<div class="my-4 d-flex flex-column justify-content-center"
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;">
<img src="assets/icons/hotel-black.png" class="img-responsive mb-3" height="48px" width="48px">
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;">
Hotel Management
</h5>
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;">
An
all-inclusive
hotel management application</p>
</div>
</div>
</div>
</div>
<!-- END OF END OF OUR INDUSTRIES -->
<!-- SUPPORT -->
<div class="d-flex align-items-center" style="border-bottom: 2px solid #714B67; padding: 15px 0px;">
<div class="d-flex justify-content-center align-items-center mr-2"
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;">
<img src="assets/misc/customer-support.png" />
</div>
<h2 class="mt-2" style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;">Support
</h2>
</div>
<div class="container mt-5">
<div class="row">
<div class="col-sm-12 col-md-6">
<div style="background-color: #F6F8F9; padding: 30px; display: flex; align-items: center;">
<div class="mr-4"
style="background-color: #714B67; display: inline-block; height: 70px; width: 70px; display: flex; align-items: center; justify-content: center;">
<img src="assets/misc/support.png" height="48" width="48" style="width: 42px; height: 42px;" />
</div>
<div>
<h4>Need Help?</h4>
<p style="line-height: 100%;">Got questions or need help? Get in touch.</p>
<a href="mailto:odoo@cybrosys.com">
<p style="font-weight: 400; font-size: 28px; line-height: 80%; color: #714B67;">
odoo@cybrosys.com</p>
</a>
</div>
</div>
</div>
<div class="col-sm-12 col-md-6">
<div style="background-color: #F6F8F9; padding: 30px; display: flex; align-items: center;">
<div class="mr-4"
style="background-color: #2AC44D; display: inline-block; height: 70px; width: 70px; display: flex; align-items: center; justify-content: center;">
<img src="assets/misc/whatsapp.png" height="52" width="52" style="width: 52px; height: 52px;" />
</div>
<div>
<h4>WhatsApp</h4>
<p style="line-height: 100%;">Say hi to us on WhatsApp!</p>
<a href="https://api.whatsapp.com/send?phone=918606827707">
<p style="font-weight: 400; font-size: 28px; line-height: 80%; color: #714B67;">+91 86068
27707</p>
</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12 my-5 d-flex justify-content-center align-items-center">
<img src="assets/misc/logo.png" width="144" height="31"
style="width:144px; height: 31px; margin-top: 40px;" />
</div>
</div>
</div>
<!-- END OF SUPPORT -->
\ No newline at end of file
.o_list_view .o_list_table thead {
position: sticky;
top: 0;
z-index:999;
}
.o_account_reports_page .table-responsive{
overflow-x: auto;
overflow-y: visible;
}
.table-responsive{
overflow: visible;
}
.o_list_view .table-responsive .table thead{
z-index: 1;
}
element.style {
}
.o_list_renderer .o_list_table thead {
position: sticky;
top: 0;
z-index:999;
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论