From 6b9428c8bf13e28d4dc94c46cde109c7174ae496 Mon Sep 17 00:00:00 2001 From: mgw <1392924357@qq.com> Date: Mon, 10 Feb 2025 16:38:34 +0800 Subject: [PATCH 01/27] =?UTF-8?q?=E5=A4=9A=E7=A7=8D=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E4=B8=8B=E6=98=BE=E7=A4=BA=E6=8B=92=E7=BB=9D=E6=8E=A5=E5=8D=95?= =?UTF-8?q?/=E5=8F=96=E6=B6=88=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_manufacturing/views/sale_order_views.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sf_manufacturing/views/sale_order_views.xml b/sf_manufacturing/views/sale_order_views.xml index 8e1d1061..aa5bfaa0 100644 --- a/sf_manufacturing/views/sale_order_views.xml +++ b/sf_manufacturing/views/sale_order_views.xml @@ -22,11 +22,11 @@ + + + - {'invisible': [('state', '!=', 'draft')]} - - - {'invisible': [('state', '!=', 'draft')]} + {'invisible': [('state', 'not in', ['draft', 'supply method', 'sale', 'processing'])]} {'invisible': ['|','&',('check_status', '!=', 'approved'),('state', 'in', ['draft','cancel','supply method']),'&',('check_status', '=', 'approved'),('state', 'in', ['sale','cancel','supply method'])]} From a05885936db495a7e03a3b9da71f761e3bc6e377 Mon Sep 17 00:00:00 2001 From: yuxianghui <3437689193@qq.com> Date: Thu, 13 Feb 2025 14:26:01 +0800 Subject: [PATCH 02/27] =?UTF-8?q?=E5=88=80=E5=85=B7=E6=A0=87=E5=87=86?= =?UTF-8?q?=E5=BA=93=E5=8F=8A=E5=85=B3=E8=81=94=E6=A8=A1=E5=9E=8B=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E6=8E=A5=E5=8F=A3=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_mrs_connect/models/sync_common.py | 55 ++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/sf_mrs_connect/models/sync_common.py b/sf_mrs_connect/models/sync_common.py index 835183b3..9115bc65 100644 --- a/sf_mrs_connect/models/sync_common.py +++ b/sf_mrs_connect/models/sync_common.py @@ -1988,6 +1988,9 @@ class CuttingSpeed(models.Model): }) else: cutting_speed.write({ + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'execution_standard_id': self.env['sf.international.standards'].search( [('code', '=', item['execution_standard_code'])]).id, 'material_name_id': self.env['sf.materials.model'].search( @@ -2040,6 +2043,9 @@ class CuttingSpeed(models.Model): }) else: cutting_speed.write({ + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'execution_standard_id': self.env['sf.international.standards'].search( [('code', '=', item['execution_standard_code'])]).id, 'material_name_id': self.env['sf.materials.model'].search( @@ -2130,6 +2136,9 @@ class CuttingSpeed(models.Model): }) else: feed_per_tooth.write({ + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'materials_type_id': self.env['sf.materials.model'].search( [('materials_no', '=', item['materials_type_code'])]).id, 'cutting_width_depth_id': self.env['sf.cutting.width.depth'].search( @@ -2168,6 +2177,9 @@ class CuttingSpeed(models.Model): }) else: feed_per_tooth.write({ + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'materials_type_id': self.env['sf.materials.model'].search( [('materials_no', '=', item['materials_type_code'])]).id, 'cutting_width_depth_id': self.env['sf.cutting.width.depth'].search( @@ -2454,6 +2466,11 @@ class CuttingToolBasicParameters(models.Model): else: self.search([('code', '=', integral_tool_item['code'])]).write({ 'name': integral_tool_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [( + 'code', '=', + integral_tool_item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'total_length': integral_tool_item['total_length'], 'blade_diameter': integral_tool_item['blade_diameter'], 'blade_length': integral_tool_item['blade_length'], @@ -2516,6 +2533,9 @@ class CuttingToolBasicParameters(models.Model): else: self.search([('code', '=', blade_item['code'])]).write({ 'name': blade_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', blade_item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'length': blade_item['length'], 'thickness': blade_item['thickness'], 'cutting_blade_length': blade_item['cutting_blade_length'], @@ -2573,6 +2593,9 @@ class CuttingToolBasicParameters(models.Model): else: self.search([('code', '=', chuck_item['code'])]).write({ 'name': chuck_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', chuck_item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'er_size_model': chuck_item['size_model'], 'min_clamping_diameter': chuck_item['clamping_diameter_min'], 'max_clamping_diameter': chuck_item['clamping_diameter_max'], @@ -2632,6 +2655,9 @@ class CuttingToolBasicParameters(models.Model): else: self.search([('code', '=', cutter_arbor_item['code'])]).write({ 'name': cutter_arbor_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', cutter_arbor_item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'height': cutter_arbor_item['height'], 'width': cutter_arbor_item['width'], 'total_length': cutter_arbor_item['total_length'], @@ -2697,6 +2723,9 @@ class CuttingToolBasicParameters(models.Model): else: self.search([('code', '=', cutter_head_item['code'])]).write({ 'name': cutter_head_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', cutter_head_item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'install_blade_tip_num': cutter_head_item['number_blade_installed'], 'blade_diameter': cutter_head_item['blade_diameter'], 'cutter_head_diameter': cutter_head_item['cutter_diameter'], @@ -2727,6 +2756,9 @@ class CuttingToolBasicParameters(models.Model): [('code', '=', knife_handle_item['code']), ('active', 'in', [True, False])]) val = { 'name': knife_handle_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', knife_handle_item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'taper_shank_model': knife_handle_item['taper_shank_model'], 'total_length': knife_handle_item['total_length'], 'flange_shank_length': knife_handle_item['flange_length'], @@ -2751,9 +2783,6 @@ class CuttingToolBasicParameters(models.Model): if not knife_handle: val['code'] = knife_handle_item['code'] val['cutting_tool_type'] = '刀柄' - val['standard_library_id'] = self.env['sf.cutting_tool.standard.library'].search( - [('code', '=', knife_handle_item['standard_library_code'].replace("JKM", result[ - 'factory_short_name']))]).id self.create(val) else: self.search([('code', '=', knife_handle_item['code'])]).write(val) @@ -2809,6 +2838,11 @@ class CuttingToolBasicParameters(models.Model): else: integral_tool.write({ 'name': integral_tool_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [( + 'code', '=', + integral_tool_item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'total_length': integral_tool_item['total_length'], 'blade_diameter': integral_tool_item['blade_diameter'], 'blade_length': integral_tool_item['blade_length'], @@ -2871,6 +2905,9 @@ class CuttingToolBasicParameters(models.Model): else: blade.write({ 'name': blade_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', blade_item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'length': blade_item['length'], 'thickness': blade_item['thickness'], 'cutting_blade_length': blade_item['cutting_blade_length'], @@ -2928,6 +2965,9 @@ class CuttingToolBasicParameters(models.Model): else: chuck.write({ 'name': chuck_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', chuck_item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'er_size_model': chuck_item['size_model'], 'min_clamping_diameter': chuck_item['clamping_diameter_min'], 'max_clamping_diameter': chuck_item['clamping_diameter_max'], @@ -2987,6 +3027,9 @@ class CuttingToolBasicParameters(models.Model): else: cutter_arbor.write({ 'name': cutter_arbor_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', cutter_arbor_item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'height': cutter_arbor_item['height'], 'width': cutter_arbor_item['width'], 'total_length': cutter_arbor_item['total_length'], @@ -3053,6 +3096,9 @@ class CuttingToolBasicParameters(models.Model): else: cutter_head.write({ 'name': cutter_head_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', cutter_head_item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'install_blade_tip_num': cutter_head_item['number_blade_installed'], 'blade_diameter': cutter_head_item['blade_diameter'], 'cutter_head_diameter': cutter_head_item['cutter_diameter'], @@ -3114,6 +3160,9 @@ class CuttingToolBasicParameters(models.Model): else: knife_handle.write({ 'name': knife_handle_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', knife_handle_item['standard_library_code'].replace("JKM", result[ + 'factory_short_name']))]).id, 'total_length': knife_handle_item['total_length'], 'taper_shank_model': knife_handle_item['taper_shank_model'], 'flange_shank_length': knife_handle_item['flange_length'], From 3ac096e9a797ba5920a45e1a626021d01b894ffb Mon Sep 17 00:00:00 2001 From: mgw <1392924357@qq.com> Date: Thu, 13 Feb 2025 17:41:18 +0800 Subject: [PATCH 03/27] =?UTF-8?q?=E5=8F=96=E6=B6=88=E6=8E=A5=E5=8D=95?= =?UTF-8?q?=E9=9C=80=E6=B1=82=E8=BF=9B=E8=A1=8C=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_manufacturing/__manifest__.py | 1 + sf_manufacturing/models/sale_order.py | 17 +++ sf_manufacturing/security/ir.model.access.csv | 2 + sf_manufacturing/views/sale_order_views.xml | 10 ++ sf_manufacturing/wizard/__init__.py | 1 + sf_manufacturing/wizard/sale_order_cancel.py | 103 ++++++++++++++++++ .../wizard/sale_order_cancel_views.xml | 42 +++++++ 7 files changed, 176 insertions(+) create mode 100644 sf_manufacturing/wizard/sale_order_cancel.py create mode 100644 sf_manufacturing/wizard/sale_order_cancel_views.xml diff --git a/sf_manufacturing/__manifest__.py b/sf_manufacturing/__manifest__.py index e2ef6dbe..ba0c6751 100644 --- a/sf_manufacturing/__manifest__.py +++ b/sf_manufacturing/__manifest__.py @@ -27,6 +27,7 @@ 'wizard/production_technology_re_adjust_wizard_views.xml', 'wizard/mrp_workorder_batch_replan_wizard_views.xml', 'wizard/sf_programming_reason_views.xml', + 'wizard/sale_order_cancel_views.xml', 'views/mrp_views_menus.xml', 'views/agv_scheduling_views.xml', 'views/stock_lot_views.xml', diff --git a/sf_manufacturing/models/sale_order.py b/sf_manufacturing/models/sale_order.py index e31faf1c..1b94f07f 100644 --- a/sf_manufacturing/models/sale_order.py +++ b/sf_manufacturing/models/sale_order.py @@ -149,6 +149,23 @@ class SaleOrder(models.Model): product_bom_purchase.with_user(self.env.ref("base.user_admin")).bom_create_line_has( purchase_embryo) return super(SaleOrder, self).action_confirm() + + def action_show_cancel_wizard(self): + wizard = self.env['sf.sale.order.cancel.wizard'].create({ + 'order_id': self.id, + }) + + # 创建关联单据行 + self.env['sf.sale.order.cancel.line'].create_from_order(wizard.id, self) + + return { + 'name': '取消销售订单', + 'type': 'ir.actions.act_window', + 'res_model': 'sf.sale.order.cancel.wizard', + 'view_mode': 'form', + 'target': 'new', + 'res_id': wizard.id, + } class SaleOrderLine(models.Model): _inherit = 'sale.order.line' diff --git a/sf_manufacturing/security/ir.model.access.csv b/sf_manufacturing/security/ir.model.access.csv index e8c1882f..246cce63 100644 --- a/sf_manufacturing/security/ir.model.access.csv +++ b/sf_manufacturing/security/ir.model.access.csv @@ -192,3 +192,5 @@ access_sf_programming_reason,sf_programming_reason,model_sf_programming_reason,b access_sf_programming_record,sf_programming_record,model_sf_programming_record,base.group_user,1,1,1,0 access_sf_work_individuation_page,sf_work_individuation_page,model_sf_work_individuation_page,sf_base.group_sf_mrp_user,1,1,1,0 access_sf_work_individuation_page_group_plan_dispatch,sf_work_individuation_page_group_plan_dispatch,model_sf_work_individuation_page,sf_base.group_plan_dispatch,1,1,0,0 +access_sf_sale_order_cancel_wizard,sf_sale_order_cancel_wizard,model_sf_sale_order_cancel_wizard,sf_base.group_sf_order_user,1,1,1,0 +access_sf_sale_order_cancel_line,sf_sale_order_cancel_line,model_sf_sale_order_cancel_line,sf_base.group_sf_order_user,1,1,1,0 \ No newline at end of file diff --git a/sf_manufacturing/views/sale_order_views.xml b/sf_manufacturing/views/sale_order_views.xml index aa5bfaa0..e17c1546 100644 --- a/sf_manufacturing/views/sale_order_views.xml +++ b/sf_manufacturing/views/sale_order_views.xml @@ -27,10 +27,20 @@ {'invisible': [('state', 'not in', ['draft', 'supply method', 'sale', 'processing'])]} + 警告:取消操作将不可逆,是否确定要取消该单据? {'invisible': ['|','&',('check_status', '!=', 'approved'),('state', 'in', ['draft','cancel','supply method']),'&',('check_status', '=', 'approved'),('state', 'in', ['sale','cancel','supply method'])]} + + + + diff --git a/sf_manufacturing/wizard/__init__.py b/sf_manufacturing/wizard/__init__.py index 6a0af9e5..7a2541c3 100644 --- a/sf_manufacturing/wizard/__init__.py +++ b/sf_manufacturing/wizard/__init__.py @@ -5,3 +5,4 @@ from . import production_technology_wizard from . import production_technology_re_adjust_wizard from . import mrp_workorder_batch_replan_wizard from . import sf_programming_reason +from . import sale_order_cancel diff --git a/sf_manufacturing/wizard/sale_order_cancel.py b/sf_manufacturing/wizard/sale_order_cancel.py new file mode 100644 index 00000000..223c75d8 --- /dev/null +++ b/sf_manufacturing/wizard/sale_order_cancel.py @@ -0,0 +1,103 @@ +from odoo import models, fields, api + +class SFSaleOrderCancelWizard(models.TransientModel): + _name = 'sf.sale.order.cancel.wizard' + _description = '销售订单取消向导' + + order_id = fields.Many2one('sale.order', string='销售订单') + related_docs = fields.One2many('sf.sale.order.cancel.line', 'wizard_id', string='相关单据') + + @api.model + def default_get(self, fields_list): + defaults = super().default_get(fields_list) + if self._context.get('active_id'): + order = self.env['sale.order'].browse(self._context.get('active_id')) + defaults['order_id'] = order.id + # 创建向导时自动创建关联单据行 + wizard = self.create(defaults) + self.env['sf.sale.order.cancel.line'].create_from_order(wizard.id, order) + defaults['related_docs'] = wizard.related_docs.ids + return defaults + + def action_confirm_cancel(self): + self.ensure_one() + return self.order_id.action_cancel() + +class SFSaleOrderCancelLine(models.TransientModel): + _name = 'sf.sale.order.cancel.line' + _description = '销售订单取消行' + + wizard_id = fields.Many2one('sf.sale.order.cancel.wizard') + sequence = fields.Integer('序号') + category = fields.Char('大类') + doc_name = fields.Char('单据名称') + operation_type = fields.Char('作业类型') + doc_number = fields.Char('单据编号') + line_number = fields.Char('行号') + product_name = fields.Char('产品名称') + quantity = fields.Float('数量') + doc_state = fields.Char('单据状态') + cancel_reason = fields.Char('禁止取消原因') + + @api.model + def create_from_order(self, wizard_id, order): + sequence = 1 + lines = [] + + # 检查销售订单 + if order.invoice_ids: + for invoice in order.invoice_ids: + vals = { + 'wizard_id': wizard_id, + 'sequence': sequence, + 'category': '销售', + 'doc_name': '销售订单', + 'operation_type': '销售', + 'doc_number': invoice.name, + 'product_name': invoice.product_id.name, + 'quantity': invoice.quantity, + 'doc_state': invoice.state, + 'cancel_reason': '已有异动' if invoice.state != 'draft' else '' + } + lines.append(self.create(vals)) + sequence += 1 + + # 检查交货单 + if order.picking_ids: + for picking in order.picking_ids: + vals = { + 'wizard_id': wizard_id, + 'sequence': sequence, + 'category': '库存', + 'doc_name': '交货单', + 'operation_type': '调拨', + 'doc_number': picking.name, + 'product_name': picking.product_id.name if picking.product_id else '', + 'quantity': picking.product_qty if hasattr(picking, 'product_qty') else 0, + 'doc_state': picking.state, + 'cancel_reason': '已有异动' if picking.state not in ['draft', 'cancel', 'waiting'] else '' + } + lines.append(self.create(vals)) + sequence += 1 + + # 检查制造订单 + manufacturing_orders = self.env['mrp.production'].search([ + ('origin', '=', order.name) + ]) + for mo in manufacturing_orders: + vals = { + 'wizard_id': wizard_id, + 'sequence': sequence, + 'category': '制造', + 'doc_name': '制造订单', + 'operation_type': '制造', + 'doc_number': mo.name, + 'product_name': mo.product_id.name, + 'quantity': mo.product_qty, + 'doc_state': mo.state, + 'cancel_reason': '已有异动' if mo.state not in ['draft', 'cancel'] else '' + } + lines.append(self.create(vals)) + sequence += 1 + + return lines \ No newline at end of file diff --git a/sf_manufacturing/wizard/sale_order_cancel_views.xml b/sf_manufacturing/wizard/sale_order_cancel_views.xml new file mode 100644 index 00000000..0adf0081 --- /dev/null +++ b/sf_manufacturing/wizard/sale_order_cancel_views.xml @@ -0,0 +1,42 @@ + + + + sf.sale.order.cancel.wizard.form + sf.sale.order.cancel.wizard + + + + + + + 弹窗描述: + 1) 若无异动,描述为: 确认所有下游单据全部取消? + 2) 若有异动,描述为: 部分或全部下游单据存在异动,无法取消,详情如下: + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From e98a9a3788528e6e3cb942df2bc567b560b7e106 Mon Sep 17 00:00:00 2001 From: mgw <1392924357@qq.com> Date: Fri, 14 Feb 2025 17:29:33 +0800 Subject: [PATCH 04/27] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=8F=96=E6=B6=88?= =?UTF-8?q?=E8=AE=A2=E5=8D=95=E5=85=B6=E4=BB=96=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_manufacturing/models/mrp_production.py | 18 +-- sf_manufacturing/wizard/sale_order_cancel.py | 145 ++++++++++++++++++- 2 files changed, 152 insertions(+), 11 deletions(-) diff --git a/sf_manufacturing/models/mrp_production.py b/sf_manufacturing/models/mrp_production.py index 8cf3b7df..d49fe4f2 100644 --- a/sf_manufacturing/models/mrp_production.py +++ b/sf_manufacturing/models/mrp_production.py @@ -701,14 +701,14 @@ class MrpProduction(models.Model): config_url = configsettings['sf_url'] + url res['token'] = configsettings['token'] # res_str = json.dumps(res) - ret = requests.post(config_url, json={}, data=res, headers=config_header) - ret = ret.json() - logging.info('fetchCNC-ret:%s' % ret) - if ret['status'] == 1: - self.write( - {'programming_no': ret['programming_no'], 'programming_state': '编程中', 'work_state': '编程中'}) - else: - raise UserError(ret['message']) + # ret = requests.post(config_url, json={}, data=res, headers=config_header) + # ret = ret.json() + # logging.info('fetchCNC-ret:%s' % ret) + # if ret['status'] == 1: + # self.write( + # {'programming_no': ret['programming_no'], 'programming_state': '编程中', 'work_state': '编程中'}) + # else: + # raise UserError(ret['message']) except Exception as e: logging.info('fetchCNC error:%s' % e) raise UserError("cnc程序获取编程单失败,请联系管理员") @@ -1704,7 +1704,7 @@ class sf_programming_record(models.Model): programming_method = fields.Selection([ ('auto', '自动'), ('manual operation', '人工')], string="编程方式") - current_programming_count = fields.Integer('当前编程次数') + current_programming_count = fields.Integer('重新编程次数') target_production_id = fields.Char('目标制造单号') apply_time = fields.Datetime('申请时间') send_time = fields.Datetime('下发时间') diff --git a/sf_manufacturing/wizard/sale_order_cancel.py b/sf_manufacturing/wizard/sale_order_cancel.py index 223c75d8..bd49ea9d 100644 --- a/sf_manufacturing/wizard/sale_order_cancel.py +++ b/sf_manufacturing/wizard/sale_order_cancel.py @@ -79,25 +79,166 @@ class SFSaleOrderCancelLine(models.TransientModel): } lines.append(self.create(vals)) sequence += 1 + + # 检查销售订单直接关联的采购单 + purchase_orders = self.env['purchase.order'].search([ + ('origin', '=', order.name) + ]) + if purchase_orders: + for po in purchase_orders: + vals = { + 'wizard_id': wizard_id, + 'sequence': sequence, + 'category': '采购', + 'doc_name': '采购单', + 'operation_type': '销售采购', + 'doc_number': po.name, + 'product_name': po.order_line[0].product_id.name if po.order_line else '', + 'quantity': po.order_line[0].product_qty if po.order_line else 0, + 'doc_state': po.state, + 'cancel_reason': '已有异动' if po.state not in ['draft', 'cancel'] else '' + } + lines.append(self.create(vals)) + sequence += 1 # 检查制造订单 manufacturing_orders = self.env['mrp.production'].search([ ('origin', '=', order.name) ]) for mo in manufacturing_orders: + # 添加制造订单本身 vals = { 'wizard_id': wizard_id, 'sequence': sequence, 'category': '制造', 'doc_name': '制造订单', - 'operation_type': '制造', 'doc_number': mo.name, 'product_name': mo.product_id.name, 'quantity': mo.product_qty, 'doc_state': mo.state, - 'cancel_reason': '已有异动' if mo.state not in ['draft', 'cancel'] else '' + 'cancel_reason': '已有异动' if mo.state not in ['technology_to_confirmed'] else '' } lines.append(self.create(vals)) sequence += 1 + + # 检查制造订单关联的采购单 + purchase_orders = self.env['purchase.order'].search([ + ('origin', '=', mo.name) + ]) + if purchase_orders: + for po in purchase_orders: + vals = { + 'wizard_id': wizard_id, + 'sequence': sequence, + 'category': '制造', + 'doc_name': '采购单', + 'doc_number': po.name, + 'operation_type': '制造采购', + 'product_name': po.order_line[0].product_id.name if po.order_line else '', + 'quantity': po.order_line[0].product_qty if po.order_line else 0, + 'doc_state': po.state, + 'cancel_reason': '已有异动' if po.state not in ['draft', 'cancel'] else '' + } + lines.append(self.create(vals)) + sequence += 1 + # 检查制造订单的领料单 + if mo.picking_ids: + for picking in mo.picking_ids: + vals = { + 'wizard_id': wizard_id, + 'sequence': sequence, + 'category': '制造', + 'doc_name': '领料单', + 'doc_number': picking.name, + 'operation_type': picking.picking_type_id.name, + 'product_name': picking.product_id.name if picking.product_id else '', + 'quantity': picking.product_qty if hasattr(picking, 'product_qty') else 0, + 'doc_state': picking.state, + 'cancel_reason': '已有异动' if picking.state not in ['draft', 'cancel', 'waiting'] else '' + } + lines.append(self.create(vals)) + sequence += 1 + + # 检查制造订单的工单 + if mo.workorder_ids: + for workorder in mo.workorder_ids: + vals = { + 'wizard_id': wizard_id, + 'sequence': sequence, + 'category': '制造', + 'doc_name': '工单', + 'doc_number': workorder.name, + 'operation_type': workorder.workcenter_id.name, + 'product_name': mo.product_id.name, + 'quantity': workorder.qty_production, + 'doc_state': workorder.state, + 'cancel_reason': '已有异动' if workorder.state not in ['draft', 'cancel', 'pending'] else '' + } + lines.append(self.create(vals)) + sequence += 1 + + # 检查制造订单组件的采购单和制造单 + for move in mo.move_raw_ids: + # 检查组件的采购单 + component_pos = self.env['purchase.order'].search([ + ('origin', '=', mo.name), + ('order_line.product_id', '=', move.product_id.id) + ]) + for po in component_pos: + vals = { + 'wizard_id': wizard_id, + 'sequence': sequence, + 'category': '制造', + 'doc_name': '组件采购单', + 'operation_type': '组件采购', + 'doc_number': po.name, + 'product_name': move.product_id.name, + 'quantity': po.order_line[0].product_qty if po.order_line else 0, + 'doc_state': po.state, + 'cancel_reason': '已有异动' if po.state not in ['draft', 'cancel'] else '' + } + lines.append(self.create(vals)) + sequence += 1 + + # 检查组件的制造单 + component_mos = self.env['mrp.production'].search([ + ('origin', '=', mo.name), + ('product_id', '=', move.product_id.id) + ]) + for comp_mo in component_mos: + vals = { + 'wizard_id': wizard_id, + 'sequence': sequence, + 'category': '制造', + 'doc_name': '组件制造单', + 'operation_type': '组件制造', + 'doc_number': comp_mo.name, + 'product_name': move.product_id.name, + 'quantity': comp_mo.product_qty, + 'doc_state': comp_mo.state, + 'cancel_reason': '已有异动' if comp_mo.state not in ['technology_to_confirmed'] else '' + } + lines.append(self.create(vals)) + sequence += 1 + + # 检查制造订单的质检单 + quality_checks = self.env['quality.check'].search([ + ('production_id', '=', mo.id) + ]) + if quality_checks: + for check in quality_checks: + vals = { + 'wizard_id': wizard_id, + 'sequence': sequence, + 'category': '制造', + 'doc_name': '质检单', + 'doc_number': check.name, + 'product_name': check.product_id.name, + 'doc_state': check.state, + 'cancel_reason': '已有异动' if check.state not in ['draft', 'cancel'] else '' + } + lines.append(self.create(vals)) + sequence += 1 + return lines \ No newline at end of file From b117fde8c3dd0de78ced16f744bc00dc0aa8db89 Mon Sep 17 00:00:00 2001 From: hyyy <123@qq.com> Date: Fri, 14 Feb 2025 17:31:19 +0800 Subject: [PATCH 05/27] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=EF=BC=8C=E7=A7=BB=E5=88=B0efms=E9=87=8C=E9=9D=A2=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jikimo_frontend/__manifest__.py | 4 +- .../many2one_radio_field.css | 3 - .../many2one_radio_field.js | 53 ---------- .../many2one_radio_field.xml | 35 ------ .../custom_many2many_checkboxes.css | 100 ------------------ .../custom_many2many_checkboxes.js | 60 ----------- .../custom_many2many_checkboxes.xml | 23 ---- sf_base/static/js/updateTable.js | 80 +++----------- sf_base/views/tool_views.xml | 4 +- sf_manufacturing/views/sale_order_views.xml | 2 +- sf_sale/static/src/css/purchase_list.css | 6 ++ sf_sale/views/sale_order_view.xml | 3 +- .../static/src/js/3d_viewer.css | 3 + .../static/src/js/3d_viewer.js | 5 + .../static/src/js/3d_viewer.xml | 1 + 15 files changed, 36 insertions(+), 346 deletions(-) delete mode 100644 jikimo_frontend/static/src/fields/Many2OneRadioField/many2one_radio_field.css delete mode 100644 jikimo_frontend/static/src/fields/Many2OneRadioField/many2one_radio_field.js delete mode 100644 jikimo_frontend/static/src/fields/Many2OneRadioField/many2one_radio_field.xml delete mode 100644 jikimo_frontend/static/src/fields/custom_many2many_checkboxes/custom_many2many_checkboxes.css delete mode 100644 jikimo_frontend/static/src/fields/custom_many2many_checkboxes/custom_many2many_checkboxes.js delete mode 100644 jikimo_frontend/static/src/fields/custom_many2many_checkboxes/custom_many2many_checkboxes.xml create mode 100644 web_widget_model_viewer/static/src/js/3d_viewer.css diff --git a/jikimo_frontend/__manifest__.py b/jikimo_frontend/__manifest__.py index c3598323..b8b77eb1 100644 --- a/jikimo_frontend/__manifest__.py +++ b/jikimo_frontend/__manifest__.py @@ -21,8 +21,8 @@ 'web.assets_qweb': [ ], 'web.assets_backend': [ - 'jikimo_frontend/static/src/fields/custom_many2many_checkboxes/*', - 'jikimo_frontend/static/src/fields/Many2OneRadioField/*', + # 'jikimo_frontend/static/src/fields/custom_many2many_checkboxes/*', + # 'jikimo_frontend/static/src/fields/Many2OneRadioField/*', # 移除odoo相关标识 'jikimo_frontend/static/src/bye_odoo/*', 'jikimo_frontend/static/src/scss/custom_style.scss', diff --git a/jikimo_frontend/static/src/fields/Many2OneRadioField/many2one_radio_field.css b/jikimo_frontend/static/src/fields/Many2OneRadioField/many2one_radio_field.css deleted file mode 100644 index 72d877a0..00000000 --- a/jikimo_frontend/static/src/fields/Many2OneRadioField/many2one_radio_field.css +++ /dev/null @@ -1,3 +0,0 @@ -.many2one_radio_field { - display: inline-block; -} \ No newline at end of file diff --git a/jikimo_frontend/static/src/fields/Many2OneRadioField/many2one_radio_field.js b/jikimo_frontend/static/src/fields/Many2OneRadioField/many2one_radio_field.js deleted file mode 100644 index 8c2be97f..00000000 --- a/jikimo_frontend/static/src/fields/Many2OneRadioField/many2one_radio_field.js +++ /dev/null @@ -1,53 +0,0 @@ -/** @odoo-module **/ - -import { RadioField } from "@web/views/fields/radio/radio_field"; // 导入单选按钮组件 -import { registry } from "@web/core/registry"; - -export class Many2OneRadioField extends RadioField { - // 你可以重写或者添加一些方法和属性 - // 例如,你可以重写setup方法来添加一些事件监听器或者初始化一些变量 - setup() { - super.setup(); // 调用父类的setup方法 - // 你自己的代码 - } - - onImageClick(event) { - // 放大图片逻辑 - // 获取图片元素 - const img = event.target; - const close = img.nextSibling; - // 实现放大图片逻辑 - // 比如使用 CSS 放大 - img.parentElement.classList.add('zoomed'); - close.classList.add('img_close'); - } - - onCloseClick(event) { - const close = event.target; - const img = close.previousSibling; - img.parentElement.classList.remove('zoomed'); - close.classList.remove('img_close'); - } - - get items() { - return Many2OneRadioField.getItems(this.props.name, this.props.record); - } - - static getItems(fieldName, record) { - switch (record.fields[fieldName].type) { - case "selection": - return record.fields[fieldName].selection; - case "many2one": { - const value = record.preloadedData[fieldName] || []; - return value.map((item) => [item.id, item.display_name, item.image]); - } - default: - return []; - } - } -} - -Many2OneRadioField.template = "jikimo_frontend.Many2OneRadioField"; -// MyCustomWidget.supportedTypes = ['many2many']; - -registry.category("fields").add("many2one_radio", Many2OneRadioField); diff --git a/jikimo_frontend/static/src/fields/Many2OneRadioField/many2one_radio_field.xml b/jikimo_frontend/static/src/fields/Many2OneRadioField/many2one_radio_field.xml deleted file mode 100644 index 3d797eb0..00000000 --- a/jikimo_frontend/static/src/fields/Many2OneRadioField/many2one_radio_field.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - × - - - - - - - - diff --git a/jikimo_frontend/static/src/fields/custom_many2many_checkboxes/custom_many2many_checkboxes.css b/jikimo_frontend/static/src/fields/custom_many2many_checkboxes/custom_many2many_checkboxes.css deleted file mode 100644 index 20cb4c7e..00000000 --- a/jikimo_frontend/static/src/fields/custom_many2many_checkboxes/custom_many2many_checkboxes.css +++ /dev/null @@ -1,100 +0,0 @@ - -.processing-capabilities-grid { - display: grid; - grid-template-columns: repeat(6, 1fr); - gap: 10px; - width: 100%; -} - -.grid-item { - display: flex; - align-items: center; -} - -.item-content { - display: flex; - flex-direction: column; - align-items: center; - text-align: center; -} -/*控制图片大小*/ -.item-icon { - width: 50px; - height: 50px; - margin-bottom: 5px; - margin-top: 15px; -} - -.item-label { - font-size: 12px; - word-break: break-word; -} - -@media (max-width: 1200px) { - .processing-capabilities-grid { - grid-template-columns: repeat(4, 1fr); - } -} - -@media (max-width: 768px) { - .processing-capabilities-grid { - grid-template-columns: repeat(3, 1fr); - } -} - -@media (max-width: 480px) { - .processing-capabilities-grid { - grid-template-columns: repeat(2, 1fr); - } -} -.image-preview-container { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.9); - display: flex; - justify-content: center; - align-items: center; - z-index: 1000; - opacity: 0; - transition: opacity 0.3s ease; -} - -.image-preview-container.show { - opacity: 1; -} - -.image-preview { - max-width: 90%; - max-height: 90%; - object-fit: contain; - box-shadow: 0 0 20px rgba(255, 255, 255, 0.2); - border-radius: 5px; - transform: scale(0.9); - transition: transform 0.3s ease; -} - -.image-preview-container.show .image-preview { - transform: scale(1); -} - -.image-preview-close { - position: absolute; - top: 20px; - right: 30px; - color: #fff; - font-size: 40px; - font-weight: bold; - transition: 0.3s; - cursor: pointer; - opacity: 0.7; -} - -.image-preview-close:hover, -.image-preview-close:focus { - opacity: 1; - text-decoration: none; - cursor: pointer; -} \ No newline at end of file diff --git a/jikimo_frontend/static/src/fields/custom_many2many_checkboxes/custom_many2many_checkboxes.js b/jikimo_frontend/static/src/fields/custom_many2many_checkboxes/custom_many2many_checkboxes.js deleted file mode 100644 index dee78c5f..00000000 --- a/jikimo_frontend/static/src/fields/custom_many2many_checkboxes/custom_many2many_checkboxes.js +++ /dev/null @@ -1,60 +0,0 @@ -/** @odoo-module **/ - -import {Many2ManyCheckboxesField} from "@web/views/fields/many2many_checkboxes/many2many_checkboxes_field"; -import {registry} from "@web/core/registry"; - -export class MyCustomWidget extends Many2ManyCheckboxesField { - setup() { - super.setup(); - } - - onImageClick(event, src) { - event.preventDefault(); - event.stopPropagation(); - - // 创建预览框 - const previewContainer = document.createElement('div'); - previewContainer.className = 'image-preview-container'; - - const previewImg = document.createElement('img'); - previewImg.src = src; - previewImg.className = 'image-preview'; - // 设置放大的预览图片大小 - previewImg.style.width = '600px'; - previewImg.style.height = 'auto'; // 保持宽高比 - - const closeButton = document.createElement('span'); - closeButton.innerHTML = '×'; - closeButton.className = 'image-preview-close'; - - previewContainer.appendChild(previewImg); - previewContainer.appendChild(closeButton); - document.body.appendChild(previewContainer); - - // 添加关闭预览的事件监听器 - const closePreview = () => { - previewContainer.classList.remove('show'); - setTimeout(() => { - document.body.removeChild(previewContainer); - }, 300); - }; - - closeButton.addEventListener('click', closePreview); - - // 点击预览框外部也可以关闭 - previewContainer.addEventListener('click', (e) => { - if (e.target === previewContainer) { - closePreview(); - } - }); - - // 使用 setTimeout 来触发过渡效果 - setTimeout(() => { - previewContainer.classList.add('show'); - }, 10); - } -} - -MyCustomWidget.template = "jikimo_frontend.MyCustomWidget"; - -registry.category("fields").add("custom_many2many_checkboxes", MyCustomWidget); \ No newline at end of file diff --git a/jikimo_frontend/static/src/fields/custom_many2many_checkboxes/custom_many2many_checkboxes.xml b/jikimo_frontend/static/src/fields/custom_many2many_checkboxes/custom_many2many_checkboxes.xml deleted file mode 100644 index 9bb8797d..00000000 --- a/jikimo_frontend/static/src/fields/custom_many2many_checkboxes/custom_many2many_checkboxes.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/sf_base/static/js/updateTable.js b/sf_base/static/js/updateTable.js index beec2c94..f76ff006 100644 --- a/sf_base/static/js/updateTable.js +++ b/sf_base/static/js/updateTable.js @@ -9,6 +9,7 @@ function getDomData() { table.hide() const thead = customTable.children('thead') const tbody = customTable.children('tbody') + const tfooter = customTable.children('tfoot') const tableData = [] const tbody_child = tbody.children() @@ -16,30 +17,29 @@ function getDomData() { for (let v = 0; v < tbody_child_len; v++) { // 将数据取出来到tableData里面 const data = tbody_child[v].innerText.split('\t') - // console.log('dom data',data) const [index, deep, name, Φ, value] = data - tableData.push({index, deep, name, Φ, value}) + tableData.push({ index, deep, name, Φ, value }) } - const ΦList = [...new Set(tableData.map(_ => _.name))] // ΦList去重 + const ΦList = [...new Set(tableData.map(_ => _.Φ))] // ΦList去重 const newTableData = {} tableData.forEach(_ => { - const key = _.deep + '|' + _.Φ - !newTableData[key] ? newTableData[key] = {i: _.index} : ''; + const key = _.deep + '|' + _.name + !newTableData[key] ? newTableData[key] = { i: _.index } : ''; if (_.Φ) { // 去除没有Φ的脏数据 newTableData[key]['Φ' + _.Φ] = _.value newTableData[key]['Φ' + _.Φ + 'i'] = _.index } }) - // console.log('qwdh',tableData, ΦList, newTableData); + // console.log(tableData, ΦList, newTableData); if (ΦList.filter(_ => _).length == 0) return; - handleThead(thead, ΦList) + handleThead(thead, ΦList, tfooter) - handleTbody(tbody, newTableData, ΦList, table) + handleTbody(tbody, newTableData, ΦList, table ) } // 重新设置表头、 -function handleThead(thead, ΦList) { +function handleThead(thead, ΦList, tfooter) { const dom = thead.children().eq(0).children() const len = dom.length dom.eq(0).attr('rowspan', 2) @@ -47,7 +47,11 @@ function handleThead(thead, ΦList) { len == 5 ? dom.eq(2).attr('rowspan', 2) : '' dom.eq(-2).attr('colspan', ΦList.length) dom.eq(-1).remove() - + if(tfooter && tfooter.length) { + tfooter.children().each(function () { + $(this).children().eq(-1).remove() + }) + } const tr = document.createElement('tr') for (let v = 0; v < ΦList.length; v++) { const th = document.createElement('th') @@ -68,7 +72,6 @@ function handleTbody(tbody, newTableData, ΦList, table) { // b = b.split('=')[1].split('%')[0] // return a - b // }) - // console.log('wqoqw ',ΦList) data.forEach(_ => { i++ const tr = $('') @@ -98,61 +101,6 @@ function handleTbody(tbody, newTableData, ΦList, table) { // // } tbody.append(tr) }) - // $(document).click(function (e) { - // if ($(e.target).attr('coustomTd')) { - // const orginV = $('[coustomInput=1]').children('input').val() - // $('[coustomInput=1]').parent().html(orginV) - // const v = $(e.target).attr('val') - // console.log($(e.target)); - // $(e.target).html('') - // const input = $('') - // input.children('input').val(v) - // $(e.target).append(input) - // input.children('input').focus() - // input.children('input').select() - // } else if ($(e.target).attr('coustomInput')) { - // - // } else { - // const orginV = $('[coustomInput=1]').children('input').val() - // $('[coustomInput=1]').parent().html(orginV) - // const v = $(e.target).attr('val') - // } - // }) - // $(document).off('change') // 防止重复绑定 -// $(document).on('change', '[coustomInput] input', function () { -// $(this).parents('td').attr('val', $(this).val()); -// var eve1 = new Event('change'); -// var eve2 = new Event('input'); -// var eve3 = new Event('click'); -// const i = $(this).parents('td').attr('col'); -// let patchDom = table.find('tbody').children('tr').eq(i - 1); -// -// if (patchDom.length === 0) { -// console.error('No such row found'); -// return; -// } -// -// patchDom = patchDom.children().eq(-1); -// -// setTimeout(() => { -// if (patchDom.length === 0) { -// console.error('No such cell found'); -// return; -// } -// patchDom[0].dispatchEvent(eve3); // Simulate click event -// -// setTimeout(() => { -// patchDom = patchDom.find('input'); -// if (patchDom.length === 0) { -// console.error('No input found in the target cell'); -// return; -// } -// patchDom.val($(this).val()); -// patchDom[0].dispatchEvent(eve2); -// patchDom[0].dispatchEvent(eve1); -// }, 200); -// }, 500); -// }); } diff --git a/sf_base/views/tool_views.xml b/sf_base/views/tool_views.xml index b2d53392..e0679e55 100644 --- a/sf_base/views/tool_views.xml +++ b/sf_base/views/tool_views.xml @@ -177,12 +177,12 @@ - + + widget="custom_many2many_checkboxes" attrs="{'showExpand': True}"/> diff --git a/sf_manufacturing/views/sale_order_views.xml b/sf_manufacturing/views/sale_order_views.xml index 8e1d1061..ceaa09ab 100644 --- a/sf_manufacturing/views/sale_order_views.xml +++ b/sf_manufacturing/views/sale_order_views.xml @@ -19,7 +19,7 @@ - + diff --git a/sf_sale/static/src/css/purchase_list.css b/sf_sale/static/src/css/purchase_list.css index 2deacb9c..5e2aa86e 100644 --- a/sf_sale/static/src/css/purchase_list.css +++ b/sf_sale/static/src/css/purchase_list.css @@ -1,3 +1,9 @@ .purchase_order_list_name { min-width: 62px !important; +} + +.section_and_note_text span{ + white-space: wrap!important; + overflow: auto!important; + text-overflow: unset!important; } \ No newline at end of file diff --git a/sf_sale/views/sale_order_view.xml b/sf_sale/views/sale_order_view.xml index 24e8f3ef..d408e807 100644 --- a/sf_sale/views/sale_order_view.xml +++ b/sf_sale/views/sale_order_view.xml @@ -102,7 +102,7 @@ + string="模型文件" attrs="{'readonly': [('state', 'in', ['draft'])], 'isInList': True}"/> @@ -112,6 +112,7 @@ {'no_create': True} {'is_sale_order_line': True } + section_and_note_text {'readonly': [('state', 'in', ['cancel','sale'])]} diff --git a/web_widget_model_viewer/static/src/js/3d_viewer.css b/web_widget_model_viewer/static/src/js/3d_viewer.css new file mode 100644 index 00000000..238f793d --- /dev/null +++ b/web_widget_model_viewer/static/src/js/3d_viewer.css @@ -0,0 +1,3 @@ +.model-viewer-in-list { + width: 150px; +} \ No newline at end of file diff --git a/web_widget_model_viewer/static/src/js/3d_viewer.js b/web_widget_model_viewer/static/src/js/3d_viewer.js index 4ed9dcc1..6e4b969c 100644 --- a/web_widget_model_viewer/static/src/js/3d_viewer.js +++ b/web_widget_model_viewer/static/src/js/3d_viewer.js @@ -63,11 +63,16 @@ StepViewer.supportedTypes = ["binary"]; StepViewer.props = { ...standardFieldProps, url: {type: String, optional: true}, + isInList: {type: Boolean, optional: true}, }; StepViewer.extractProps = ({attrs}) => { + const modifiedAttrs = JSON.parse(attrs.modifiers || '{}'); + + return { url: attrs.options.url, + isInList: modifiedAttrs.isInList, }; }; diff --git a/web_widget_model_viewer/static/src/js/3d_viewer.xml b/web_widget_model_viewer/static/src/js/3d_viewer.xml index 73f142c9..9053d184 100644 --- a/web_widget_model_viewer/static/src/js/3d_viewer.xml +++ b/web_widget_model_viewer/static/src/js/3d_viewer.xml @@ -5,6 +5,7 @@ Date: Mon, 17 Feb 2025 10:00:40 +0800 Subject: [PATCH 06/27] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=8F=96=E6=B6=88?= =?UTF-8?q?=E6=8E=A5=E5=8D=95=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_manufacturing/wizard/sale_order_cancel.py | 50 ++++++++++++++----- .../wizard/sale_order_cancel_views.xml | 8 +-- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/sf_manufacturing/wizard/sale_order_cancel.py b/sf_manufacturing/wizard/sale_order_cancel.py index bd49ea9d..6e1d35ef 100644 --- a/sf_manufacturing/wizard/sale_order_cancel.py +++ b/sf_manufacturing/wizard/sale_order_cancel.py @@ -1,11 +1,14 @@ from odoo import models, fields, api + class SFSaleOrderCancelWizard(models.TransientModel): _name = 'sf.sale.order.cancel.wizard' _description = '销售订单取消向导' order_id = fields.Many2one('sale.order', string='销售订单') related_docs = fields.One2many('sf.sale.order.cancel.line', 'wizard_id', string='相关单据') + has_movement = fields.Boolean(compute='_compute_has_movement', string='是否有异动') + display_message = fields.Char(compute='_compute_display_message', string='显示消息') @api.model def default_get(self, fields_list): @@ -19,14 +22,33 @@ class SFSaleOrderCancelWizard(models.TransientModel): defaults['related_docs'] = wizard.related_docs.ids return defaults + @api.depends('related_docs.cancel_reason') + def _compute_has_movement(self): + for wizard in self: + wizard.has_movement = any(doc.cancel_reason for doc in wizard.related_docs) + + @api.depends('has_movement') + def _compute_display_message(self): + for wizard in self: + wizard.display_message = '部分或全部下游单据存在异动,无法取消,详情如下:' if wizard.has_movement else '确认所有下游单据全部取消?' + def action_confirm_cancel(self): self.ensure_one() - return self.order_id.action_cancel() + # 取消销售订单 + result = self.order_id.action_cancel() + # 取消关联的制造订单 + manufacturing_orders = self.env['mrp.production'].search([ + ('origin', '=', self.order_id.name) + ]) + if manufacturing_orders: + manufacturing_orders.action_cancel() + return result + class SFSaleOrderCancelLine(models.TransientModel): _name = 'sf.sale.order.cancel.line' _description = '销售订单取消行' - + wizard_id = fields.Many2one('sf.sale.order.cancel.wizard') sequence = fields.Integer('序号') category = fields.Char('大类') @@ -43,7 +65,7 @@ class SFSaleOrderCancelLine(models.TransientModel): def create_from_order(self, wizard_id, order): sequence = 1 lines = [] - + # 检查销售订单 if order.invoice_ids: for invoice in order.invoice_ids: @@ -61,7 +83,7 @@ class SFSaleOrderCancelLine(models.TransientModel): } lines.append(self.create(vals)) sequence += 1 - + # 检查交货单 if order.picking_ids: for picking in order.picking_ids: @@ -73,7 +95,8 @@ class SFSaleOrderCancelLine(models.TransientModel): 'operation_type': '调拨', 'doc_number': picking.name, 'product_name': picking.product_id.name if picking.product_id else '', - 'quantity': picking.product_qty if hasattr(picking, 'product_qty') else 0, + # 'quantity': picking.product_qty if hasattr(picking, 'product_qty') else 0, + 'quantity': sum(picking.move_ids.mapped('product_uom_qty') or [0]), 'doc_state': picking.state, 'cancel_reason': '已有异动' if picking.state not in ['draft', 'cancel', 'waiting'] else '' } @@ -100,7 +123,7 @@ class SFSaleOrderCancelLine(models.TransientModel): } lines.append(self.create(vals)) sequence += 1 - + # 检查制造订单 manufacturing_orders = self.env['mrp.production'].search([ ('origin', '=', order.name) @@ -113,6 +136,7 @@ class SFSaleOrderCancelLine(models.TransientModel): 'category': '制造', 'doc_name': '制造订单', 'doc_number': mo.name, + 'operation_type': '制造', 'product_name': mo.product_id.name, 'quantity': mo.product_qty, 'doc_state': mo.state, @@ -141,7 +165,7 @@ class SFSaleOrderCancelLine(models.TransientModel): } lines.append(self.create(vals)) sequence += 1 - + # 检查制造订单的领料单 if mo.picking_ids: for picking in mo.picking_ids: @@ -153,13 +177,13 @@ class SFSaleOrderCancelLine(models.TransientModel): 'doc_number': picking.name, 'operation_type': picking.picking_type_id.name, 'product_name': picking.product_id.name if picking.product_id else '', - 'quantity': picking.product_qty if hasattr(picking, 'product_qty') else 0, + 'quantity': sum(picking.move_ids.mapped('product_uom_qty') or [0]), 'doc_state': picking.state, 'cancel_reason': '已有异动' if picking.state not in ['draft', 'cancel', 'waiting'] else '' } lines.append(self.create(vals)) sequence += 1 - + # 检查制造订单的工单 if mo.workorder_ids: for workorder in mo.workorder_ids: @@ -200,7 +224,7 @@ class SFSaleOrderCancelLine(models.TransientModel): } lines.append(self.create(vals)) sequence += 1 - + # 检查组件的制造单 component_mos = self.env['mrp.production'].search([ ('origin', '=', mo.name), @@ -221,7 +245,7 @@ class SFSaleOrderCancelLine(models.TransientModel): } lines.append(self.create(vals)) sequence += 1 - + # 检查制造订单的质检单 quality_checks = self.env['quality.check'].search([ ('production_id', '=', mo.id) @@ -240,5 +264,5 @@ class SFSaleOrderCancelLine(models.TransientModel): } lines.append(self.create(vals)) sequence += 1 - - return lines \ No newline at end of file + + return lines diff --git a/sf_manufacturing/wizard/sale_order_cancel_views.xml b/sf_manufacturing/wizard/sale_order_cancel_views.xml index 0adf0081..9758a8a9 100644 --- a/sf_manufacturing/wizard/sale_order_cancel_views.xml +++ b/sf_manufacturing/wizard/sale_order_cancel_views.xml @@ -7,11 +7,10 @@ + - 弹窗描述: - 1) 若无异动,描述为: 确认所有下游单据全部取消? - 2) 若有异动,描述为: 部分或全部下游单据存在异动,无法取消,详情如下: + @@ -31,7 +30,8 @@ + class="btn-primary" + attrs="{'invisible': [('has_movement', '=', True)]}"/> From fc4eae04888c2ffe4542f75373db1342e4bbc56c Mon Sep 17 00:00:00 2001 From: yuxianghui <3437689193@qq.com> Date: Mon, 17 Feb 2025 12:42:07 +0800 Subject: [PATCH 07/27] =?UTF-8?q?=E5=AE=8C=E6=88=90=20=20=E5=85=A5?= =?UTF-8?q?=E5=BA=93=E4=BC=98=E5=8C=96=EF=BC=9A=E8=87=AA=E5=8A=A8=E7=A1=AE?= =?UTF-8?q?=E8=AE=A4=E5=BA=8F=E5=88=97=E5=8F=B7=20=20=E9=9C=80=E6=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_manufacturing/models/stock.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/sf_manufacturing/models/stock.py b/sf_manufacturing/models/stock.py index 1e0b0849..901b521b 100644 --- a/sf_manufacturing/models/stock.py +++ b/sf_manufacturing/models/stock.py @@ -611,6 +611,18 @@ class StockPicking(models.Model): return sequence_id def button_validate(self): + # 校验“收料入库单、客供料入库单”是否已经分配序列号,如果没有分配则自动分配 + if self.picking_type_id.use_existing_lots is False and self.picking_type_id.use_create_lots is True: + for move in self.move_ids: + if not move.move_line_nosuggest_ids: + move.action_show_details() + else: + # 对已经生成的序列号做唯一性校验,如果重复则重新生成新的序列号 + line_lot_name = [line_id.lot_name for line_id in move.move_line_nosuggest_ids] + lot_ids = self.env['stock.lot'].sudo().search([('name', 'in', line_lot_name)]) + if lot_ids: + move.action_clear_lines_show_details() + move.action_show_details() res = super().button_validate() picking_type_in = self.env.ref('sf_manufacturing.outcontract_picking_in').id if res is True and self.picking_type_id.id == picking_type_in: @@ -844,7 +856,8 @@ class ReStockMove(models.Model): self.next_serial = self._get_tool_next_serial(self.company_id, self.product_id, self.origin) else: self.next_serial = self.env['stock.lot']._get_next_serial(self.company_id, self.product_id) - if self.picking_type_id.sequence_code == 'DL' and not self.move_line_nosuggest_ids: + if (self.picking_type_id.use_existing_lots is False + and self.picking_type_id.use_create_lots is True and not self.move_line_nosuggest_ids): self.action_assign_serial_show_details() elif self.product_id.tracking == "lot": self._put_tool_lot(self.company_id, self.product_id, self.origin) From 3d2e3833309b5c2584b4c5f375fed14b57940edf Mon Sep 17 00:00:00 2001 From: yuxianghui <3437689193@qq.com> Date: Mon, 17 Feb 2025 15:17:54 +0800 Subject: [PATCH 08/27] =?UTF-8?q?1=E3=80=81=E5=A4=84=E7=90=86=20sf-?= =?UTF-8?q?=E5=88=B6=E9=80=A0=E8=AE=A2=E5=8D=95-=E9=80=9A=E8=BF=87?= =?UTF-8?q?=E8=B4=A8=E9=87=8F=E6=A3=80=E6=9F=A5=E5=AF=B9=E8=A3=85=E5=A4=B9?= =?UTF-8?q?=E9=A2=84=E8=B0=83=E8=BF=9B=E8=A1=8C=E8=BF=94=E5=B7=A5-?= =?UTF-8?q?=E5=88=B6=E9=80=A0=E8=AE=A2=E5=8D=95=E6=9C=AA=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E6=A3=80=E6=B5=8B=E7=BB=93=E6=9E=9C=20=E9=97=AE=E9=A2=98?= =?UTF-8?q?=EF=BC=9B2=E3=80=81=E5=A4=84=E7=90=86=20=E6=89=8B=E5=8A=A8?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=E8=B0=83=E6=8B=A8=E5=8D=95=E5=8F=B3=E4=B8=8A?= =?UTF-8?q?=E8=A7=92=E4=BC=9A=E5=85=B3=E8=81=94=E5=9D=AF=E6=96=99=E9=87=87?= =?UTF-8?q?=E8=B4=AD=E5=8D=95=E5=92=8C=E5=9D=AF=E6=96=99=E5=A7=94=E5=A4=96?= =?UTF-8?q?=E5=8D=95=20=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_quality/models/quality.py | 12 ++++++++++++ sf_stock/models/stock_picking.py | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/sf_quality/models/quality.py b/sf_quality/models/quality.py index 201103f1..80fe2f46 100644 --- a/sf_quality/models/quality.py +++ b/sf_quality/models/quality.py @@ -84,6 +84,18 @@ class QualityCheck(models.Model): raise ValidationError('请填写【判定结果】里的信息') if self.test_results == '合格': raise ValidationError('请重新选择【判定结果】-【检测结果】') + if self.workorder_id.routing_type != 'CNC加工' and self.workorder_id.individuation_page_PTD is False: + self.workorder_id.production_id.write({'detection_result_ids': [(0, 0, { + 'rework_reason': self.workorder_id.reason, + 'detailed_reason': self.workorder_id.detailed_reason, + 'processing_panel': self.workorder_id.processing_panel, + 'routing_type': self.workorder_id.routing_type, + 'handle_result': '待处理' if (self.workorder_id.test_results in ['返工', '报废'] + or self.workorder_id.is_rework is True) else '', + 'test_results': self.workorder_id.test_results, + 'test_report': self.workorder_id.detection_report})], + 'is_scrap': True if self.workorder_id.test_results == '报废' else False + }) if self.workorder_id.state not in ['done']: self.workorder_id.write( {'test_results': self.test_results, 'reason': self.reason, 'detailed_reason': self.detailed_reason}) diff --git a/sf_stock/models/stock_picking.py b/sf_stock/models/stock_picking.py index f8105097..7b73485b 100644 --- a/sf_stock/models/stock_picking.py +++ b/sf_stock/models/stock_picking.py @@ -18,7 +18,7 @@ class StockPicking(models.Model): @api.depends('name') def _compute_pro_purchase_count(self): for sp in self: - if sp: + if sp.name and sp.name != '/': po_ids = self.env['purchase.order'].sudo().search([ ('origin', 'like', sp.name), ('purchase_type', '=', 'standard')]) if po_ids: @@ -52,7 +52,7 @@ class StockPicking(models.Model): @api.depends('name') def _compute_pro_out_purchase_count(self): for sp in self: - if sp: + if sp.name and sp.name != '/': po_ids = self.env['purchase.order'].sudo().search([ ('origin', 'like', sp.name), ('purchase_type', '=', 'outsourcing')]) if po_ids: From 567ea84b00ee27ce860faaa54005a5309f70d12d Mon Sep 17 00:00:00 2001 From: mgw <1392924357@qq.com> Date: Mon, 17 Feb 2025 15:37:30 +0800 Subject: [PATCH 09/27] =?UTF-8?q?=E5=AE=8C=E5=96=84=E4=BA=BA=E5=B7=A5?= =?UTF-8?q?=E7=BC=96=E7=A8=8B=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_manufacturing/models/mrp_production.py | 40 ++++-- sf_manufacturing/views/sale_order_views.xml | 6 +- sf_manufacturing/wizard/sale_order_cancel.py | 130 ++++++++++++++----- sf_quality/models/quality.py | 4 +- 4 files changed, 131 insertions(+), 49 deletions(-) diff --git a/sf_manufacturing/models/mrp_production.py b/sf_manufacturing/models/mrp_production.py index d49fe4f2..1af65426 100644 --- a/sf_manufacturing/models/mrp_production.py +++ b/sf_manufacturing/models/mrp_production.py @@ -235,7 +235,7 @@ class MrpProduction(models.Model): programming_no = fields.Char('编程单号') work_state = fields.Char('业务状态') programming_state = fields.Selection( - [('编程中', '编程中'), ('已编程', '已编程'), ('已编程未下发', '已编程未下发'), ('已下发', '已下发')], + [('编程中', '编程中'), ('已编程', '已编程'), ('已编程未下发', '已编程未下发'), ('已下发', '已下发'), ('已取消', '已取消')], string='编程状态', tracking=True) glb_file = fields.Binary("glb模型文件") @@ -646,6 +646,28 @@ class MrpProduction(models.Model): logging.info('update_programming_state error:%s' % e) raise UserError("更新编程单状态失败,请联系管理员") + # 修改编程单状态 + def _change_programming_state(self): + try: + res = {"programming_no": self.programming_no, "state": "已取消"} + logging.info('res=%s:' % res) + configsettings = self.env['res.config.settings'].get_values() + config_header = Common.get_headers(self, configsettings['token'], configsettings['sf_secret_key']) + url = '/api/intelligent_programming/set_state' + config_url = configsettings['sf_url'] + url + ret = requests.post(config_url, json=res, data=None, headers=config_header) + ret = ret.json() + result = json.loads(ret['result']) + logging.info('change_programming_state-ret:%s' % result) + if result['status'] == 1: + self.write({'programming_state': '已取消'}) + else: + raise UserError(ret['message']) + except Exception as e: + logging.info('change_programming_state error:%s' % e) + raise UserError("修改编程单状态失败,请联系管理员") + + # cnc程序获取 def fetchCNC(self, production_names): cnc = self.env['mrp.production'].search([('id', '=', self.id)]) @@ -701,14 +723,14 @@ class MrpProduction(models.Model): config_url = configsettings['sf_url'] + url res['token'] = configsettings['token'] # res_str = json.dumps(res) - # ret = requests.post(config_url, json={}, data=res, headers=config_header) - # ret = ret.json() - # logging.info('fetchCNC-ret:%s' % ret) - # if ret['status'] == 1: - # self.write( - # {'programming_no': ret['programming_no'], 'programming_state': '编程中', 'work_state': '编程中'}) - # else: - # raise UserError(ret['message']) + ret = requests.post(config_url, json={}, data=res, headers=config_header) + ret = ret.json() + logging.info('fetchCNC-ret:%s' % ret) + if ret['status'] == 1: + self.write( + {'programming_no': ret['programming_no'], 'programming_state': '编程中', 'work_state': '编程中'}) + else: + raise UserError(ret['message']) except Exception as e: logging.info('fetchCNC error:%s' % e) raise UserError("cnc程序获取编程单失败,请联系管理员") diff --git a/sf_manufacturing/views/sale_order_views.xml b/sf_manufacturing/views/sale_order_views.xml index e17c1546..353739c1 100644 --- a/sf_manufacturing/views/sale_order_views.xml +++ b/sf_manufacturing/views/sale_order_views.xml @@ -26,7 +26,7 @@ - {'invisible': [('state', 'not in', ['draft', 'supply method', 'sale', 'processing'])]} + {'invisible': [('state', 'not in', ['draft', 'supply method'])]} 警告:取消操作将不可逆,是否确定要取消该单据? @@ -36,9 +36,9 @@ diff --git a/sf_manufacturing/wizard/sale_order_cancel.py b/sf_manufacturing/wizard/sale_order_cancel.py index 6e1d35ef..f50ff306 100644 --- a/sf_manufacturing/wizard/sale_order_cancel.py +++ b/sf_manufacturing/wizard/sale_order_cancel.py @@ -34,16 +34,46 @@ class SFSaleOrderCancelWizard(models.TransientModel): def action_confirm_cancel(self): self.ensure_one() + + # 取消销售订单关联的采购单 + purchase_orders = self.env['purchase.order'].search([ + ('origin', '=', self.order_id.name) + ]) + if purchase_orders: + purchase_orders.write({'state': 'cancel'}) + # 取消销售订单 result = self.order_id.action_cancel() - # 取消关联的制造订单 + + # 取消关联的制造订单及其采购单 manufacturing_orders = self.env['mrp.production'].search([ ('origin', '=', self.order_id.name) ]) - if manufacturing_orders: - manufacturing_orders.action_cancel() - return result + for mo in manufacturing_orders: + # 取消制造订单关联的采购单,但保持关联关系 + mo_purchase_orders = self.env['purchase.order'].search([ + ('origin', '=', mo.name) + ]) + if mo_purchase_orders: + mo_purchase_orders.write({'state': 'cancel'}) + + # 取消制造订单 + mo.action_cancel() + # 取消制造订单关联的编程单 + mo._change_programming_state() + + # 取消组件的制造单关联的采购单 + for comp_mo in self.env['mrp.production'].search([ + ('origin', '=', mo.name) + ]): + comp_purchase_orders = self.env['purchase.order'].search([ + ('origin', '=', comp_mo.name) + ]) + if comp_purchase_orders: + comp_purchase_orders.button_cancel() + + return result class SFSaleOrderCancelLine(models.TransientModel): _name = 'sf.sale.order.cancel.line' @@ -65,6 +95,16 @@ class SFSaleOrderCancelLine(models.TransientModel): def create_from_order(self, wizard_id, order): sequence = 1 lines = [] + map_dict = { + 'waiting': '等待其他作业', + 'to approve': '待批准', + 'technology_to_confirmed': '待工艺确认', + 'confirmed': '已确认', + 'pending': '等待其他工单', + 'none': '待处理', + 'draft': '报价', + 'cancel': '已取消' + } # 检查销售订单 if order.invoice_ids: @@ -97,7 +137,7 @@ class SFSaleOrderCancelLine(models.TransientModel): 'product_name': picking.product_id.name if picking.product_id else '', # 'quantity': picking.product_qty if hasattr(picking, 'product_qty') else 0, 'quantity': sum(picking.move_ids.mapped('product_uom_qty') or [0]), - 'doc_state': picking.state, + 'doc_state': map_dict.get(picking.state, picking.state), 'cancel_reason': '已有异动' if picking.state not in ['draft', 'cancel', 'waiting'] else '' } lines.append(self.create(vals)) @@ -118,7 +158,7 @@ class SFSaleOrderCancelLine(models.TransientModel): 'doc_number': po.name, 'product_name': po.order_line[0].product_id.name if po.order_line else '', 'quantity': po.order_line[0].product_qty if po.order_line else 0, - 'doc_state': po.state, + 'doc_state': map_dict.get(po.state, po.state), 'cancel_reason': '已有异动' if po.state not in ['draft', 'cancel'] else '' } lines.append(self.create(vals)) @@ -139,8 +179,8 @@ class SFSaleOrderCancelLine(models.TransientModel): 'operation_type': '制造', 'product_name': mo.product_id.name, 'quantity': mo.product_qty, - 'doc_state': mo.state, - 'cancel_reason': '已有异动' if mo.state not in ['technology_to_confirmed'] else '' + 'doc_state': map_dict.get(mo.state, mo.state), + 'cancel_reason': '已有异动' if mo.state not in ['technology_to_confirmed', 'cancel'] else '' } lines.append(self.create(vals)) sequence += 1 @@ -160,7 +200,7 @@ class SFSaleOrderCancelLine(models.TransientModel): 'operation_type': '制造采购', 'product_name': po.order_line[0].product_id.name if po.order_line else '', 'quantity': po.order_line[0].product_qty if po.order_line else 0, - 'doc_state': po.state, + 'doc_state': map_dict.get(po.state, po.state), 'cancel_reason': '已有异动' if po.state not in ['draft', 'cancel'] else '' } lines.append(self.create(vals)) @@ -178,7 +218,7 @@ class SFSaleOrderCancelLine(models.TransientModel): 'operation_type': picking.picking_type_id.name, 'product_name': picking.product_id.name if picking.product_id else '', 'quantity': sum(picking.move_ids.mapped('product_uom_qty') or [0]), - 'doc_state': picking.state, + 'doc_state': map_dict.get(picking.state, picking.state), 'cancel_reason': '已有异动' if picking.state not in ['draft', 'cancel', 'waiting'] else '' } lines.append(self.create(vals)) @@ -196,34 +236,34 @@ class SFSaleOrderCancelLine(models.TransientModel): 'operation_type': workorder.workcenter_id.name, 'product_name': mo.product_id.name, 'quantity': workorder.qty_production, - 'doc_state': workorder.state, - 'cancel_reason': '已有异动' if workorder.state not in ['draft', 'cancel', 'pending'] else '' + 'doc_state': map_dict.get(workorder.state, workorder.state), + 'cancel_reason': '已有异动' if workorder.state not in ['draft', 'cancel', 'pending', 'waiting'] else '' } lines.append(self.create(vals)) sequence += 1 # 检查制造订单组件的采购单和制造单 for move in mo.move_raw_ids: - # 检查组件的采购单 - component_pos = self.env['purchase.order'].search([ - ('origin', '=', mo.name), - ('order_line.product_id', '=', move.product_id.id) - ]) - for po in component_pos: - vals = { - 'wizard_id': wizard_id, - 'sequence': sequence, - 'category': '制造', - 'doc_name': '组件采购单', - 'operation_type': '组件采购', - 'doc_number': po.name, - 'product_name': move.product_id.name, - 'quantity': po.order_line[0].product_qty if po.order_line else 0, - 'doc_state': po.state, - 'cancel_reason': '已有异动' if po.state not in ['draft', 'cancel'] else '' - } - lines.append(self.create(vals)) - sequence += 1 + # # 检查组件的采购单 + # component_pos = self.env['purchase.order'].search([ + # ('origin', '=', mo.name), + # ('order_line.product_id', '=', move.product_id.id) + # ]) + # for po in component_pos: + # vals = { + # 'wizard_id': wizard_id, + # 'sequence': sequence, + # 'category': '制造', + # 'doc_name': '组件采购单', + # 'operation_type': '组件采购', + # 'doc_number': po.name, + # 'product_name': move.product_id.name, + # 'quantity': po.order_line[0].product_qty if po.order_line else 0, + # 'doc_state': po.state, + # 'cancel_reason': '已有异动' if po.state not in ['draft', 'cancel'] else '' + # } + # lines.append(self.create(vals)) + # sequence += 1 # 检查组件的制造单 component_mos = self.env['mrp.production'].search([ @@ -240,7 +280,7 @@ class SFSaleOrderCancelLine(models.TransientModel): 'doc_number': comp_mo.name, 'product_name': move.product_id.name, 'quantity': comp_mo.product_qty, - 'doc_state': comp_mo.state, + 'doc_state': map_dict.get(comp_mo.state, comp_mo.state), 'cancel_reason': '已有异动' if comp_mo.state not in ['technology_to_confirmed'] else '' } lines.append(self.create(vals)) @@ -257,12 +297,32 @@ class SFSaleOrderCancelLine(models.TransientModel): 'sequence': sequence, 'category': '制造', 'doc_name': '质检单', + 'operation_type': '质检', 'doc_number': check.name, 'product_name': check.product_id.name, - 'doc_state': check.state, - 'cancel_reason': '已有异动' if check.state not in ['draft', 'cancel'] else '' + 'quantity': 1, + 'doc_state': map_dict.get(check.quality_state, check.quality_state), + 'cancel_reason': '已有异动' if check.quality_state not in ['none'] else '' } lines.append(self.create(vals)) sequence += 1 + # 检查制造订单的编程单 + cloud_programming = mo._cron_get_programming_state() + if cloud_programming: + vals = { + 'wizard_id': wizard_id, + 'sequence': sequence, + 'category': '编程', + 'doc_name': '编程单', + 'operation_type': '编程', + 'doc_number': cloud_programming['programming_no'], + 'product_name': cloud_programming['production_order_no'], + 'quantity': 1, + 'doc_state': cloud_programming['programming_state'], + 'cancel_reason': '' + } + lines.append(self.create(vals)) + sequence += 1 + return lines diff --git a/sf_quality/models/quality.py b/sf_quality/models/quality.py index 201103f1..4b6c1774 100644 --- a/sf_quality/models/quality.py +++ b/sf_quality/models/quality.py @@ -14,7 +14,8 @@ class QualityCheck(models.Model): ('waiting', '等待'), ('none', '待处理'), ('pass', '通过的'), - ('fail', '失败的')], string='状态', tracking=True, store=True, + ('fail', '失败的'), + ('cancel', '已取消'), ], string='状态', tracking=True, store=True, default='none', copy=False, compute='_compute_quality_state') individuation_page_PTD = fields.Boolean('个性化记录(是否显示后置三元检测[PTD]页签)', related='workorder_id.individuation_page_PTD') @@ -110,4 +111,3 @@ class QualityCheck(models.Model): return "零件特采发送成功" else: raise ValidationError("零件特采发送失败") - From 69c5996db72d07f0f16917c14a5ca988a47eb2f6 Mon Sep 17 00:00:00 2001 From: hyyy <123@qq.com> Date: Mon, 17 Feb 2025 16:03:14 +0800 Subject: [PATCH 10/27] =?UTF-8?q?=E5=90=88=E5=B9=B6=E9=94=80=E5=94=AE?= =?UTF-8?q?=E8=AE=A2=E5=8D=95=E5=88=97=EF=BC=8C=E4=BF=AE=E6=94=B9=E5=BF=85?= =?UTF-8?q?=E5=A1=AB*=E5=B1=95=E7=A4=BA=E6=96=B9=E6=B3=95=EF=BC=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../static/src/css/list_border_styles.css | 5 +++ .../src/js/custom_form_status_indicator.js | 43 ++++++++++++------- sf_sale/__manifest__.py | 1 + sf_sale/static/lib/merge_field.js | 18 ++++++++ sf_sale/static/lib/merge_field.xml | 8 ++++ sf_sale/views/sale_order_view.xml | 7 +++ 6 files changed, 66 insertions(+), 16 deletions(-) create mode 100644 sf_sale/static/lib/merge_field.js create mode 100644 sf_sale/static/lib/merge_field.xml diff --git a/jikimo_frontend/static/src/css/list_border_styles.css b/jikimo_frontend/static/src/css/list_border_styles.css index bca5499e..35fc8099 100644 --- a/jikimo_frontend/static/src/css/list_border_styles.css +++ b/jikimo_frontend/static/src/css/list_border_styles.css @@ -1,3 +1,8 @@ .o_list_renderer .o_list_table tbody > tr > td:not(.o_list_record_selector):not(.o_handle_cell):not(.o_list_button):not(.o_list_record_remove){ border:1px solid #dee2e6 !important; +} + +.custom_required_add::before{ + content: '*'; + color: red; } \ No newline at end of file diff --git a/jikimo_frontend/static/src/js/custom_form_status_indicator.js b/jikimo_frontend/static/src/js/custom_form_status_indicator.js index 0b6a6b50..038c2dc0 100644 --- a/jikimo_frontend/static/src/js/custom_form_status_indicator.js +++ b/jikimo_frontend/static/src/js/custom_form_status_indicator.js @@ -6,6 +6,8 @@ import {_t} from "@web/core/l10n/translation"; import {FormStatusIndicator} from "@web/views/form/form_status_indicator/form_status_indicator"; import {ListRenderer} from "@web/views/list/list_renderer"; // import {StatusBarField} from "@web/views/fields/statusbar/statusbar_field"; +import {FormLabel} from "@web/views/form/form_label"; +import { fieldVisualFeedback } from "@web/views/fields/field"; import {Field} from "@web/views/fields/field"; @@ -115,9 +117,6 @@ patch(Field.prototype, 'jikimo_frontend.Field', { setRequired() { const id = this.props.id const isRequired = filedRequiredList[id] - if(id == 'number_of_axles') { - console.log(isRequired) - } if(isRequired) { let dom; dom = $(`label[for=${id}]`) @@ -191,7 +190,31 @@ patch(ListRenderer.prototype, 'jikimo_frontend.ListRenderer', { } } }) - +patch(FormLabel.prototype, 'jikimo_frontend.FormLabel', { + get className() { + + const { invalid, empty, readonly } = fieldVisualFeedback( + this.props.fieldInfo.FieldComponent, + this.props.record, + this.props.fieldName, + this.props.fieldInfo + ); + const classes = this.props.className ? [this.props.className] : []; + if(this.props.fieldInfo?.rawAttrs?.class?.indexOf('custom_required') >= 0) { + classes.push('custom_required_add') + } + if (invalid) { + classes.push("o_field_invalid"); + } + if (empty) { + classes.push("o_form_label_empty"); + } + if (readonly) { + classes.push("o_form_label_readonly"); + } + return classes.join(" "); + } +}) // 根据进度条设置水印 // const statusbar_params = { @@ -231,7 +254,6 @@ $(function () { clearInterval(timer) timer = setInterval(() => { timer_count++ - const dom = $('.custom_required') let tableDom = $('.table_custom_required') if (tableDom.length) { tableDom = tableDom.eq(0).parents('tr').children('.table_custom_required') @@ -243,17 +265,6 @@ $(function () { }) clearInterval(timer) } - if (dom.length) { - dom.each(function () { - const requiredDom = $(this).parent().prev().find('label') - let t = requiredDom.html() - if (t && t.indexOf('c*') < 0) { - t = '*' + t - } - requiredDom.html(t) - }) - clearInterval(timer) - } if (timer_count == 20) { clearInterval(timer) } diff --git a/sf_sale/__manifest__.py b/sf_sale/__manifest__.py index e824240e..c12f94fd 100644 --- a/sf_sale/__manifest__.py +++ b/sf_sale/__manifest__.py @@ -28,6 +28,7 @@ 'web.assets_backend': [ 'sf_sale/static/js/setTableWidth.js', 'sf_sale/static/src/css/purchase_list.css', + 'sf_sale/static/lib/*', ] }, 'demo': [ diff --git a/sf_sale/static/lib/merge_field.js b/sf_sale/static/lib/merge_field.js new file mode 100644 index 00000000..6278c36e --- /dev/null +++ b/sf_sale/static/lib/merge_field.js @@ -0,0 +1,18 @@ +/** @odoo-module */ + +import { Component } from "@odoo/owl"; +import { registry } from "@web/core/registry"; + + +export class MergeField extends Component { + get mergeValue() { + const data = this.props.record.data; + + const v = data?.product_uom_qty + const unit = data?.product_uom[1] + return `${v} ${unit}` + } +} +MergeField.template = "jikimo_sf.MergeField"; + +registry.category("fields").add("merge_field", MergeField); diff --git a/sf_sale/static/lib/merge_field.xml b/sf_sale/static/lib/merge_field.xml new file mode 100644 index 00000000..59103fa9 --- /dev/null +++ b/sf_sale/static/lib/merge_field.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/sf_sale/views/sale_order_view.xml b/sf_sale/views/sale_order_view.xml index d408e807..185050c0 100644 --- a/sf_sale/views/sale_order_view.xml +++ b/sf_sale/views/sale_order_view.xml @@ -123,6 +123,13 @@ + + + + + + hide + {'readonly': [('state', 'in', ['cancel','sale'])]} From 8d9aee497a48dbb761246da0ffbaf41454d2cc36 Mon Sep 17 00:00:00 2001 From: mgw <1392924357@qq.com> Date: Mon, 17 Feb 2025 16:11:48 +0800 Subject: [PATCH 11/27] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=8F=96=E6=B6=88?= =?UTF-8?q?=E8=AE=A2=E5=8D=95=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_manufacturing/wizard/sale_order_cancel.py | 27 ++++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/sf_manufacturing/wizard/sale_order_cancel.py b/sf_manufacturing/wizard/sale_order_cancel.py index f50ff306..98caf001 100644 --- a/sf_manufacturing/wizard/sale_order_cancel.py +++ b/sf_manufacturing/wizard/sale_order_cancel.py @@ -34,17 +34,17 @@ class SFSaleOrderCancelWizard(models.TransientModel): def action_confirm_cancel(self): self.ensure_one() - + # 取消销售订单关联的采购单 purchase_orders = self.env['purchase.order'].search([ ('origin', '=', self.order_id.name) ]) if purchase_orders: purchase_orders.write({'state': 'cancel'}) - + # 取消销售订单 result = self.order_id.action_cancel() - + # 取消关联的制造订单及其采购单 manufacturing_orders = self.env['mrp.production'].search([ ('origin', '=', self.order_id.name) @@ -56,13 +56,20 @@ class SFSaleOrderCancelWizard(models.TransientModel): ]) if mo_purchase_orders: mo_purchase_orders.write({'state': 'cancel'}) - + + # 取消制造订单的质检单 + mo_quality_checks = self.env['quality.check'].search([ + ('production_id', '=', mo.id) + ]) + if mo_quality_checks: + mo_quality_checks.write({'state': 'cancel'}) + # 取消制造订单 mo.action_cancel() # 取消制造订单关联的编程单 mo._change_programming_state() - + # 取消组件的制造单关联的采购单 for comp_mo in self.env['mrp.production'].search([ ('origin', '=', mo.name) @@ -72,9 +79,10 @@ class SFSaleOrderCancelWizard(models.TransientModel): ]) if comp_purchase_orders: comp_purchase_orders.button_cancel() - + return result + class SFSaleOrderCancelLine(models.TransientModel): _name = 'sf.sale.order.cancel.line' _description = '销售订单取消行' @@ -103,7 +111,9 @@ class SFSaleOrderCancelLine(models.TransientModel): 'pending': '等待其他工单', 'none': '待处理', 'draft': '报价', - 'cancel': '已取消' + 'cancel': '已取消', + 'pass': '通过的', + 'fail': '失败的' } # 检查销售订单 @@ -237,7 +247,8 @@ class SFSaleOrderCancelLine(models.TransientModel): 'product_name': mo.product_id.name, 'quantity': workorder.qty_production, 'doc_state': map_dict.get(workorder.state, workorder.state), - 'cancel_reason': '已有异动' if workorder.state not in ['draft', 'cancel', 'pending', 'waiting'] else '' + 'cancel_reason': '已有异动' if workorder.state not in ['draft', 'cancel', 'pending', + 'waiting'] else '' } lines.append(self.create(vals)) sequence += 1 From f24c5ff75a3880fdd8328120c53b077a5e4a0667 Mon Sep 17 00:00:00 2001 From: mgw <1392924357@qq.com> Date: Mon, 17 Feb 2025 16:21:01 +0800 Subject: [PATCH 12/27] =?UTF-8?q?=E8=B0=83=E6=95=B4=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_manufacturing/wizard/sale_order_cancel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sf_manufacturing/wizard/sale_order_cancel.py b/sf_manufacturing/wizard/sale_order_cancel.py index 98caf001..e00b14fb 100644 --- a/sf_manufacturing/wizard/sale_order_cancel.py +++ b/sf_manufacturing/wizard/sale_order_cancel.py @@ -62,7 +62,7 @@ class SFSaleOrderCancelWizard(models.TransientModel): ('production_id', '=', mo.id) ]) if mo_quality_checks: - mo_quality_checks.write({'state': 'cancel'}) + mo_quality_checks.write({'quality_state': 'cancel'}) # 取消制造订单 mo.action_cancel() From 71cbd7ac36f953cbfe141051439f5cd698786692 Mon Sep 17 00:00:00 2001 From: yuxianghui <3437689193@qq.com> Date: Mon, 17 Feb 2025 17:15:30 +0800 Subject: [PATCH 13/27] =?UTF-8?q?1=E3=80=81=E4=BC=98=E5=8C=96=E7=94=B1?= =?UTF-8?q?=E8=B4=A8=E6=A3=80=E8=BF=94=E5=B7=A5=E7=94=9F=E6=88=90=E7=9A=84?= =?UTF-8?q?=E5=88=B6=E9=80=A0=E8=AE=A2=E5=8D=95=E6=A3=80=E6=B5=8B=E7=BB=93?= =?UTF-8?q?=E6=9E=9C=E6=95=B0=E6=8D=AE=E4=B8=8D=E5=85=A8=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98=EF=BC=9B2=E3=80=81=20=E5=8F=91=E7=A5=A8=E8=B4=A6?= =?UTF-8?q?=E5=8D=95=EF=BC=9A=E5=A2=9E=E5=8A=A0=E5=AD=97=E6=AE=B5=E5=8F=8A?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jikimo_account_process/__manifest__.py | 2 +- jikimo_account_process/models/account_move.py | 10 +++++++++- sf_quality/models/quality.py | 11 +++++------ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/jikimo_account_process/__manifest__.py b/jikimo_account_process/__manifest__.py index 0c1b521c..637e74b0 100644 --- a/jikimo_account_process/__manifest__.py +++ b/jikimo_account_process/__manifest__.py @@ -20,7 +20,7 @@ 'version': '0.1', # any module necessary for this one to work correctly - 'depends': ['base', 'account'], + 'depends': ['base', 'account', 'l10n_cn'], # always loaded 'data': [ diff --git a/jikimo_account_process/models/account_move.py b/jikimo_account_process/models/account_move.py index e85d4e16..94242570 100644 --- a/jikimo_account_process/models/account_move.py +++ b/jikimo_account_process/models/account_move.py @@ -1,4 +1,4 @@ -from odoo import models, fields, api +from odoo import models, fields, api, _ from odoo.exceptions import ValidationError @@ -7,6 +7,14 @@ class CustomAccountMoveLine(models.Model): _inherit = 'account.move' _description = "account move line" + fapiao = fields.Char(string='发票号', size=20, copy=False, tracking=True, required=True) + + @api.constrains('fapiao') + def _check_fapiao(self): + for record in self: + if record.fapiao and (len(record.fapiao) != 20 or not record.fapiao.isdecimal()): + raise ValidationError(_("Fapiao number is an 20-digit number. Please enter a correct one.")) + @api.model_create_multi def create(self, vals): for val in vals: diff --git a/sf_quality/models/quality.py b/sf_quality/models/quality.py index 80fe2f46..272a3e8a 100644 --- a/sf_quality/models/quality.py +++ b/sf_quality/models/quality.py @@ -86,15 +86,14 @@ class QualityCheck(models.Model): raise ValidationError('请重新选择【判定结果】-【检测结果】') if self.workorder_id.routing_type != 'CNC加工' and self.workorder_id.individuation_page_PTD is False: self.workorder_id.production_id.write({'detection_result_ids': [(0, 0, { - 'rework_reason': self.workorder_id.reason, - 'detailed_reason': self.workorder_id.detailed_reason, + 'rework_reason': self.reason, + 'detailed_reason': self.detailed_reason, 'processing_panel': self.workorder_id.processing_panel, 'routing_type': self.workorder_id.routing_type, - 'handle_result': '待处理' if (self.workorder_id.test_results in ['返工', '报废'] - or self.workorder_id.is_rework is True) else '', - 'test_results': self.workorder_id.test_results, + 'handle_result': '待处理', + 'test_results': self.test_results, 'test_report': self.workorder_id.detection_report})], - 'is_scrap': True if self.workorder_id.test_results == '报废' else False + 'is_scrap': True if self.test_results == '报废' else False }) if self.workorder_id.state not in ['done']: self.workorder_id.write( From 09f8f5c0fd78f32b0bf3709bf4140540217d1aae Mon Sep 17 00:00:00 2001 From: mgw <1392924357@qq.com> Date: Mon, 17 Feb 2025 17:30:02 +0800 Subject: [PATCH 14/27] =?UTF-8?q?=E8=B0=83=E6=95=B4=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_manufacturing/wizard/sale_order_cancel.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sf_manufacturing/wizard/sale_order_cancel.py b/sf_manufacturing/wizard/sale_order_cancel.py index e00b14fb..c78fbe39 100644 --- a/sf_manufacturing/wizard/sale_order_cancel.py +++ b/sf_manufacturing/wizard/sale_order_cancel.py @@ -113,7 +113,11 @@ class SFSaleOrderCancelLine(models.TransientModel): 'draft': '报价', 'cancel': '已取消', 'pass': '通过的', - 'fail': '失败的' + 'fail': '失败的', + 'done': '已完成', + 'rework': '返工', + 'purchase': '采购订单', + 'ready': '就绪' } # 检查销售订单 @@ -313,7 +317,7 @@ class SFSaleOrderCancelLine(models.TransientModel): 'product_name': check.product_id.name, 'quantity': 1, 'doc_state': map_dict.get(check.quality_state, check.quality_state), - 'cancel_reason': '已有异动' if check.quality_state not in ['none'] else '' + 'cancel_reason': '已有异动' if check.quality_state not in ['none', 'cancel'] else '' } lines.append(self.create(vals)) sequence += 1 From 9fce93e1eff2c6c436a3562db4c63b2d12be73a3 Mon Sep 17 00:00:00 2001 From: mgw <1392924357@qq.com> Date: Mon, 17 Feb 2025 17:34:54 +0800 Subject: [PATCH 15/27] =?UTF-8?q?=E8=B0=83=E6=95=B4=E5=B7=A5=E5=8D=95?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E6=94=B9=E5=8F=98=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_manufacturing/models/mrp_workorder.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sf_manufacturing/models/mrp_workorder.py b/sf_manufacturing/models/mrp_workorder.py index 3717cebe..fb458f28 100644 --- a/sf_manufacturing/models/mrp_workorder.py +++ b/sf_manufacturing/models/mrp_workorder.py @@ -1087,7 +1087,8 @@ class ResMrpWorkOrder(models.Model): if (workorder.production_id.production_type == '人工线下加工' and workorder.production_id.schedule_state == '已排' and len(workorder.production_id.picking_ids.filtered( - lambda w: w.state not in ['done', 'cancel'])) == 0): + lambda w: w.state not in ['done', 'cancel'])) == 0 + and workorder.production_id.programming_state == '已编程'): if workorder.is_subcontract is True: purchase_orders_id = self._get_surface_technics_purchase_ids() if purchase_orders_id.state == 'purchase': From dc29671f1ada88848d7890ea6248554e84d2b07b Mon Sep 17 00:00:00 2001 From: mgw <1392924357@qq.com> Date: Mon, 17 Feb 2025 17:59:38 +0800 Subject: [PATCH 16/27] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E6=98=A0=E5=B0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_manufacturing/wizard/sale_order_cancel.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sf_manufacturing/wizard/sale_order_cancel.py b/sf_manufacturing/wizard/sale_order_cancel.py index c78fbe39..a5d28b7f 100644 --- a/sf_manufacturing/wizard/sale_order_cancel.py +++ b/sf_manufacturing/wizard/sale_order_cancel.py @@ -117,7 +117,10 @@ class SFSaleOrderCancelLine(models.TransientModel): 'done': '已完成', 'rework': '返工', 'purchase': '采购订单', - 'ready': '就绪' + 'ready': '就绪', + 'approved': '已批准', + 'pending_cam': '待加工', + 'progress': '加工中' } # 检查销售订单 From 6f4f0ab0fb2b2784a4ddc4a3889b6b67bfa2147f Mon Sep 17 00:00:00 2001 From: hyyy <123@qq.com> Date: Tue, 18 Feb 2025 15:44:40 +0800 Subject: [PATCH 17/27] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=88=80=E5=85=B7?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E7=9A=84=E5=B1=95=E5=BC=80=E5=88=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_base/views/tool_views.xml | 10 +++++----- sf_sale/static/src/css/purchase_list.css | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sf_base/views/tool_views.xml b/sf_base/views/tool_views.xml index e0679e55..e9d672e5 100644 --- a/sf_base/views/tool_views.xml +++ b/sf_base/views/tool_views.xml @@ -187,23 +187,23 @@ + widget="many2one_radio" attrs="{'showExpand': True}"/> - + - + - + - + diff --git a/sf_sale/static/src/css/purchase_list.css b/sf_sale/static/src/css/purchase_list.css index 5e2aa86e..ec3d1e76 100644 --- a/sf_sale/static/src/css/purchase_list.css +++ b/sf_sale/static/src/css/purchase_list.css @@ -2,7 +2,7 @@ min-width: 62px !important; } -.section_and_note_text span{ +.section_and_note_text, .section_and_note_text span{ white-space: wrap!important; overflow: auto!important; text-overflow: unset!important; From 6984f3154713d16009af410019a44d3494b64ac9 Mon Sep 17 00:00:00 2001 From: hyyy <123@qq.com> Date: Tue, 18 Feb 2025 16:23:25 +0800 Subject: [PATCH 18/27] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=A4=9A=E4=BD=99?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/js/custom_form_status_indicator.js | 31 +++---------------- 1 file changed, 4 insertions(+), 27 deletions(-) diff --git a/jikimo_frontend/static/src/js/custom_form_status_indicator.js b/jikimo_frontend/static/src/js/custom_form_status_indicator.js index 038c2dc0..5d8b2fbf 100644 --- a/jikimo_frontend/static/src/js/custom_form_status_indicator.js +++ b/jikimo_frontend/static/src/js/custom_form_status_indicator.js @@ -9,7 +9,6 @@ import {ListRenderer} from "@web/views/list/list_renderer"; import {FormLabel} from "@web/views/form/form_label"; import { fieldVisualFeedback } from "@web/views/fields/field"; -import {Field} from "@web/views/fields/field"; var Dialog = require('web.Dialog'); // var {patch} = require("web.utils") 这句话也行 @@ -53,7 +52,6 @@ const tableRequiredList = [ 'product_template_id', 'product_uom_qty', 'price_unit','product_id','product_qty', 'name', 'fault_type', 'maintenance_standards', 'Period' ] - patch(FormStatusIndicator.prototype, 'jikimo_frontend.FormStatusIndicator', { setup() { owl.onMounted(() => { @@ -109,30 +107,7 @@ patch(FormStatusIndicator.prototype, 'jikimo_frontend.FormStatusIndicator', { } ); -patch(Field.prototype, 'jikimo_frontend.Field', { - setup() { - owl.onMounted(this.setRequired); - return this._super(...arguments); - }, - setRequired() { - const id = this.props.id - const isRequired = filedRequiredList[id] - if(isRequired) { - let dom; - dom = $(`label[for=${id}]`) - if(isRequired.multiple && dom.length > 1) { - dom = dom.eq(-1) - dom = dom.parent().parent().next().find('label') - } - if(isRequired.noLabel) { - dom = dom.parent().parent() - } - let t = dom.html() - t = '*' + t - dom.html(t) - } - } -}) + patch(ListRenderer.prototype, 'jikimo_frontend.ListRenderer', { setup(){ owl.onMounted(() => { @@ -200,7 +175,9 @@ patch(FormLabel.prototype, 'jikimo_frontend.FormLabel', { this.props.fieldInfo ); const classes = this.props.className ? [this.props.className] : []; - if(this.props.fieldInfo?.rawAttrs?.class?.indexOf('custom_required') >= 0) { + const otherRequired = filedRequiredList[this.props.fieldName] + + if(this.props.fieldInfo?.rawAttrs?.class?.indexOf('custom_required') >= 0 || otherRequired) { classes.push('custom_required_add') } if (invalid) { From 9e6fc3bacb8ee469c0a83817afdbea2d80ff4220 Mon Sep 17 00:00:00 2001 From: hyyy <123@qq.com> Date: Wed, 19 Feb 2025 10:29:25 +0800 Subject: [PATCH 19/27] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=A0=B7=E5=BC=8F?= =?UTF-8?q?=E6=9D=83=E9=87=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_sale/static/src/css/purchase_list.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sf_sale/static/src/css/purchase_list.css b/sf_sale/static/src/css/purchase_list.css index ec3d1e76..17df6065 100644 --- a/sf_sale/static/src/css/purchase_list.css +++ b/sf_sale/static/src/css/purchase_list.css @@ -2,8 +2,10 @@ min-width: 62px !important; } -.section_and_note_text, .section_and_note_text span{ +.o_list_renderer .o_list_table .o_data_row td.o_data_cell.o_field_cell.o_list_char.section_and_note_text, .section_and_note_text span{ white-space: wrap!important; overflow: auto!important; text-overflow: unset!important; + word-wrap: break-word; + word-break: break-all; } \ No newline at end of file From 4dc19cab8163ec6eb4f8df0f889cd59114631089 Mon Sep 17 00:00:00 2001 From: guanhuan Date: Wed, 19 Feb 2025 10:42:48 +0800 Subject: [PATCH 20/27] =?UTF-8?q?=E9=87=87=E8=B4=AD=E5=8D=8F=E8=AE=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jikimo_purchase_tier_validation/__init__.py | 1 - .../__manifest__.py | 3 - .../controllers/__init__.py | 3 - .../controllers/controllers.py | 21 -- .../data/documents_data.xml | 11 - jikimo_purchase_tier_validation/demo/demo.xml | 30 --- .../models/models.py | 41 ---- .../security/ir.model.access.csv | 2 - .../views/templates.xml | 24 -- .../views/views.xml | 66 ------ .../wizards/__init__.py | 3 +- .../wizards/upload_file_wizard.py | 114 ---------- sf_mrs_connect/__manifest__.py | 2 +- sf_sale/__manifest__.py | 7 +- sf_sale/data/documents_data.xml | 23 ++ sf_sale/i18n/zh_CN.po | 10 + sf_sale/models/__init__.py | 2 +- sf_sale/models/purchase_requisition.py | 210 ++++++++++++++++++ sf_sale/models/sale_order.py | 149 ++++++++++++- sf_sale/security/ir.model.access.csv | 2 +- sf_sale/views/purchase_order_view.xml | 21 +- sf_sale/views/purchase_requisition_view.xml | 75 +++++++ sf_sale/wizard/__init__.py | 2 + sf_sale/wizard/purchase_requisition_wizard.py | 20 ++ .../purchase_requisition_wizard_views.xml | 17 ++ sf_sale/wizard/upload_file_upload.py | 60 +++++ .../wizard/upload_file_upload_view.xml | 6 +- 27 files changed, 597 insertions(+), 328 deletions(-) delete mode 100644 jikimo_purchase_tier_validation/controllers/__init__.py delete mode 100644 jikimo_purchase_tier_validation/controllers/controllers.py delete mode 100644 jikimo_purchase_tier_validation/data/documents_data.xml delete mode 100644 jikimo_purchase_tier_validation/demo/demo.xml delete mode 100644 jikimo_purchase_tier_validation/security/ir.model.access.csv delete mode 100644 jikimo_purchase_tier_validation/views/templates.xml delete mode 100644 jikimo_purchase_tier_validation/wizards/upload_file_wizard.py create mode 100644 sf_sale/data/documents_data.xml create mode 100644 sf_sale/i18n/zh_CN.po create mode 100644 sf_sale/models/purchase_requisition.py create mode 100644 sf_sale/views/purchase_requisition_view.xml create mode 100644 sf_sale/wizard/purchase_requisition_wizard.py create mode 100644 sf_sale/wizard/purchase_requisition_wizard_views.xml create mode 100644 sf_sale/wizard/upload_file_upload.py rename jikimo_purchase_tier_validation/wizards/upload_file_wizard_view.xml => sf_sale/wizard/upload_file_upload_view.xml (81%) diff --git a/jikimo_purchase_tier_validation/__init__.py b/jikimo_purchase_tier_validation/__init__.py index 40c32597..f553d8ff 100644 --- a/jikimo_purchase_tier_validation/__init__.py +++ b/jikimo_purchase_tier_validation/__init__.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from . import controllers from . import models from . import wizards diff --git a/jikimo_purchase_tier_validation/__manifest__.py b/jikimo_purchase_tier_validation/__manifest__.py index a7bdbddf..4d676d8f 100644 --- a/jikimo_purchase_tier_validation/__manifest__.py +++ b/jikimo_purchase_tier_validation/__manifest__.py @@ -24,9 +24,6 @@ # always loaded 'data': [ - 'security/ir.model.access.csv', - 'data/documents_data.xml', - 'wizards/upload_file_wizard_view.xml', 'views/views.xml', ], # only loaded in demonstration mode diff --git a/jikimo_purchase_tier_validation/controllers/__init__.py b/jikimo_purchase_tier_validation/controllers/__init__.py deleted file mode 100644 index 457bae27..00000000 --- a/jikimo_purchase_tier_validation/controllers/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- - -from . import controllers \ No newline at end of file diff --git a/jikimo_purchase_tier_validation/controllers/controllers.py b/jikimo_purchase_tier_validation/controllers/controllers.py deleted file mode 100644 index 6bbe5e40..00000000 --- a/jikimo_purchase_tier_validation/controllers/controllers.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -# from odoo import http - - -# class JikimoPurchaseTierValidation(http.Controller): -# @http.route('/jikimo_purchase_tier_validation/jikimo_purchase_tier_validation', auth='public') -# def index(self, **kw): -# return "Hello, world" - -# @http.route('/jikimo_purchase_tier_validation/jikimo_purchase_tier_validation/objects', auth='public') -# def list(self, **kw): -# return http.request.render('jikimo_purchase_tier_validation.listing', { -# 'root': '/jikimo_purchase_tier_validation/jikimo_purchase_tier_validation', -# 'objects': http.request.env['jikimo_purchase_tier_validation.jikimo_purchase_tier_validation'].search([]), -# }) - -# @http.route('/jikimo_purchase_tier_validation/jikimo_purchase_tier_validation/objects/', auth='public') -# def object(self, obj, **kw): -# return http.request.render('jikimo_purchase_tier_validation.object', { -# 'object': obj -# }) diff --git a/jikimo_purchase_tier_validation/data/documents_data.xml b/jikimo_purchase_tier_validation/data/documents_data.xml deleted file mode 100644 index 990a6418..00000000 --- a/jikimo_purchase_tier_validation/data/documents_data.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - 采购合同 - 存放采购合同相关文件 - 10 - - - \ No newline at end of file diff --git a/jikimo_purchase_tier_validation/demo/demo.xml b/jikimo_purchase_tier_validation/demo/demo.xml deleted file mode 100644 index cbba42ce..00000000 --- a/jikimo_purchase_tier_validation/demo/demo.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - \ No newline at end of file diff --git a/jikimo_purchase_tier_validation/models/models.py b/jikimo_purchase_tier_validation/models/models.py index 34d33d32..018766eb 100644 --- a/jikimo_purchase_tier_validation/models/models.py +++ b/jikimo_purchase_tier_validation/models/models.py @@ -21,12 +21,8 @@ class jikimo_purchase_tier_validation(models.Model): def button_confirm(self): for record in self: - # if record.need_validation and record.validation_status != 'validated': - # raise ValidationError(_('此操作需要至少对一条记录进行审批。\n请发起审批申请。')) if record.state in ['to approve']: raise ValidationError(_('请先完成审批。')) - # if record.state == 'approved': - # record.state = 'purchase' res = super(jikimo_purchase_tier_validation, self).button_confirm() for record in self: if record.state == 'approved': @@ -39,45 +35,8 @@ class jikimo_purchase_tier_validation(models.Model): record.message_subscribe([record.partner_id.id]) return res - # def button_confirm(self): - # self = self.with_context(skip_validation=True) - # return super().button_confirm() - # - # def _check_state_conditions(self, vals): - # self.ensure_one() - # if self._context.get('skip_validation'): - # return False - # return ( - # self._check_state_from_condition() - # and vals.get(self._state_field) in self._state_to - # ) - def request_validation(self): for record in self: - error_messages = [] - - # 检查必填字段 - required_fields = { - 'partner_ref': '合同名称', - 'contract_number': '合同编号' - } - - missing_fields = [ - name for field, name in required_fields.items() - if not record[field] - ] - - if missing_fields: - error_messages.append('* 如下字段要求必须填写:%s' % '、'.join(missing_fields)) - - # 检查合同文件 - if not record.contract_document_id: - error_messages.append('* 必须点击上传合同文件') - - # 如果有任何错误,一次性显示所有错误信息 - if error_messages: - raise ValidationError('\n'.join(error_messages)) - # 添加通知消息 if hasattr(record, 'message_post'): current_user = self.env.user.name diff --git a/jikimo_purchase_tier_validation/security/ir.model.access.csv b/jikimo_purchase_tier_validation/security/ir.model.access.csv deleted file mode 100644 index 09ba96bc..00000000 --- a/jikimo_purchase_tier_validation/security/ir.model.access.csv +++ /dev/null @@ -1,2 +0,0 @@ -id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_ir_attachment_wizard,ir.attachment.wizard,model_ir_attachment_wizard,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/jikimo_purchase_tier_validation/views/templates.xml b/jikimo_purchase_tier_validation/views/templates.xml deleted file mode 100644 index cea6b39a..00000000 --- a/jikimo_purchase_tier_validation/views/templates.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - \ No newline at end of file diff --git a/jikimo_purchase_tier_validation/views/views.xml b/jikimo_purchase_tier_validation/views/views.xml index b651e914..fdfd52c1 100644 --- a/jikimo_purchase_tier_validation/views/views.xml +++ b/jikimo_purchase_tier_validation/views/views.xml @@ -23,76 +23,10 @@ - - - - - - - ]}"/> - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/jikimo_purchase_tier_validation/wizards/__init__.py b/jikimo_purchase_tier_validation/wizards/__init__.py index 43edb50d..2484d9e0 100644 --- a/jikimo_purchase_tier_validation/wizards/__init__.py +++ b/jikimo_purchase_tier_validation/wizards/__init__.py @@ -1,2 +1 @@ -from . import upload_file_wizard -from . import comment_wizard \ No newline at end of file +from . import comment_wizard diff --git a/jikimo_purchase_tier_validation/wizards/upload_file_wizard.py b/jikimo_purchase_tier_validation/wizards/upload_file_wizard.py deleted file mode 100644 index f7ead9e1..00000000 --- a/jikimo_purchase_tier_validation/wizards/upload_file_wizard.py +++ /dev/null @@ -1,114 +0,0 @@ -from odoo import models, fields, api, _ - - -class IrAttachmentWizard(models.TransientModel): - _name = 'ir.attachment.wizard' - _description = '文件上传向导' - - attachment = fields.Binary(string='选择文件', required=True) - filename = fields.Char(string='文件名') - res_model = fields.Char() - res_id = fields.Integer() - - # def action_upload_file(self): - # self.ensure_one() - # # 首先创建 ir.attachment - # attachment = self.env['ir.attachment'].create({ - # 'name': self.filename, - # 'type': 'binary', - # 'datas': self.attachment, - # 'res_model': self.res_model, - # 'res_id': self.res_id, - # }) - # - # # 获取默认的文档文件夹 - # workspace = self.env['documents.folder'].search([('name', '=', '采购合同')], limit=1) - # - # # 创建 documents.document 记录 - # document = self.env['documents.document'].create({ - # 'name': self.filename, - # 'attachment_id': attachment.id, - # 'folder_id': workspace.id, - # 'res_model': self.res_model, - # 'res_id': self.res_id, - # }) - # - # return { - # 'type': 'ir.actions.client', - # 'tag': 'display_notification', - # 'params': { - # 'title': _('成功'), - # 'message': _('文件上传成功'), - # 'type': 'success', - # } - # } - - def action_upload_file(self): - self.ensure_one() - # 获取当前用户的 partner_id - current_partner = self.env.user.partner_id - # 首先创建 ir.attachment - attachment = self.env['ir.attachment'].create({ - 'name': self.filename, - 'type': 'binary', - 'datas': self.attachment, - 'res_model': self.res_model, - 'res_id': self.res_id, - }) - - # 获取默认的文档文件夹 - workspace = self.env['documents.folder'].search([('name', '=', '采购合同')], limit=1) - - # 创建 documents.document 记录 - document = self.env['documents.document'].create({ - 'name': self.filename, - 'attachment_id': attachment.id, - 'folder_id': workspace.id, - 'res_model': self.res_model, - 'res_id': self.res_id, - 'partner_id': current_partner.id, - }) - - # 更新采购订单的合同文档字段 - purchase_order = self.env['purchase.order'].browse(self.res_id) - purchase_order.write({ - 'contract_document_id': document.id, - 'is_upload_contract_file': True - }) - - # 显示成功消息并关闭向导 - message = { - 'type': 'ir.actions.client', - 'tag': 'display_notification', - 'params': { - 'title': _('成功'), - 'message': _('文件上传成功'), - 'type': 'success', - 'sticky': False, # 自动消失 - 'next': { - 'type': 'ir.actions.act_window_close' - } - } - } - - return message - - - # def action_upload_file(self): - # self.ensure_one() - # attachment = self.env['ir.attachment'].create({ - # 'name': self.filename, - # 'type': 'binary', - # 'datas': self.attachment, - # 'res_model': self.res_model, - # 'res_id': self.res_id, - # }) - # return { - # 'type': 'ir.actions.client', - # 'tag': 'display_notification', - # 'params': { - # 'title': _('成功'), - # 'message': _('文件上传成功'), - # 'type': 'success', - # } - # } diff --git a/sf_mrs_connect/__manifest__.py b/sf_mrs_connect/__manifest__.py index 1ea4f693..ea1fb001 100644 --- a/sf_mrs_connect/__manifest__.py +++ b/sf_mrs_connect/__manifest__.py @@ -10,7 +10,7 @@ """, 'category': 'sf', 'website': 'https://www.sf.cs.jikimo.com', - 'depends': ['sf_base', 'base_setup','sf_bf_connect'], + 'depends': ['sf_base', 'base_setup','sf_bf_connect','sf_sale'], 'data': [ 'data/ir_cron_data.xml', 'security/ir.model.access.csv', diff --git a/sf_sale/__manifest__.py b/sf_sale/__manifest__.py index e824240e..d33bb02e 100644 --- a/sf_sale/__manifest__.py +++ b/sf_sale/__manifest__.py @@ -10,18 +10,23 @@ """, 'category': 'sf', 'website': 'https://www.sf.jikimo.com', - 'depends': ['sale', 'sale_management', 'web_widget_model_viewer', 'sf_base', 'account', 'purchase', 'delivery'], + 'depends': ['sale', 'sale_management', 'web_widget_model_viewer', 'sf_base', 'account', 'purchase', 'delivery', + 'purchase_requisition', 'purchase_requisition_stock', 'documents'], 'data': [ + 'data/documents_data.xml', 'security/group_security.xml', 'security/ir.model.access.csv', 'wizard/sale_order_wizard_views.xml', 'wizard/purchase_order_wizard_views.xml', + 'wizard/upload_file_upload_view.xml', + 'wizard/purchase_requisition_wizard_views.xml', 'data/cron_data.xml', 'views/sale_team.xml', 'views/sale_order_view.xml', 'views/res_partner_view.xml', 'views/purchase_order_view.xml', 'views/quick_easy_order_view.xml', + 'views/purchase_requisition_view.xml', 'views/purchase_menu.xml' ], 'assets': { diff --git a/sf_sale/data/documents_data.xml b/sf_sale/data/documents_data.xml new file mode 100644 index 00000000..f5f46023 --- /dev/null +++ b/sf_sale/data/documents_data.xml @@ -0,0 +1,23 @@ + + + + + + 采购合同 + 存放采购合同相关文件 + 10 + + + 废弃 + + + + 通过 + + + + 待审 + + + + \ No newline at end of file diff --git a/sf_sale/i18n/zh_CN.po b/sf_sale/i18n/zh_CN.po new file mode 100644 index 00000000..2cb98706 --- /dev/null +++ b/sf_sale/i18n/zh_CN.po @@ -0,0 +1,10 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * purchase_requisition +# + +#. module: purchase_requisition +#: model:ir.actions.act_window,name:purchase_requisition.action_purchase_requisition +#: model:ir.ui.menu,name:purchase_requisition.menu_purchase_requisition_pro_mgt +msgid "Blanket Orders" +msgstr "采购协议" diff --git a/sf_sale/models/__init__.py b/sf_sale/models/__init__.py index 97400237..c8e3a43f 100644 --- a/sf_sale/models/__init__.py +++ b/sf_sale/models/__init__.py @@ -4,4 +4,4 @@ from . import quick_easy_order_old from . import auto_quatotion_common from . import parser_and_calculate_work_time from . import preload_datas_functions - +from . import purchase_requisition diff --git a/sf_sale/models/purchase_requisition.py b/sf_sale/models/purchase_requisition.py new file mode 100644 index 00000000..263f65ab --- /dev/null +++ b/sf_sale/models/purchase_requisition.py @@ -0,0 +1,210 @@ +from odoo import models, fields, api, _ +from odoo.exceptions import UserError, ValidationError +import logging + +_logger = logging.getLogger(__name__) + + +class PurchaseRequisition(models.Model): + _inherit = 'purchase.requisition' + + partner_ref = fields.Char(string='合同名称', required=True) + contract_number = fields.Char(string='合同编码', size=20, required=True) + payment_term_id = fields.Many2one('account.payment.term', '付款条件', + domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", + required=True) + contract_summary = fields.Text(string='合同概况', required=True) + + contract_document_id = fields.Many2one('documents.document', string='合同文件') + contract_file = fields.Binary(related='contract_document_id.datas', string='合同文件内容') + contract_file_name = fields.Char(related='contract_document_id.attachment_id.name', string='文件名') + + # 是否已上传合同文件 + is_upload_contract_file = fields.Boolean(string='是否已上传合同文件', default=False) + + def upload_contract_file(self): + self.ensure_one() + action = { + 'type': 'ir.actions.act_window', + 'name': _('上传合同文件'), + 'res_model': 'ir.attachment.upload', # 我们需要创建一个新的向导模型 + 'view_mode': 'form', + 'target': 'new', + 'context': { + 'default_res_model': self._name, + 'default_res_id': self.id, + } + } + return action + + # 删除合同文件 + def delete_contract_file(self): + self.ensure_one() + if self.contract_document_id: + try: + document = self.contract_document_id + + # 清空关联 + self.write({ + 'contract_document_id': False, + 'contract_file': False, + 'contract_file_name': False + }) + + # 删除文档 + if document: + document.with_context(no_attachment=True).sudo().unlink() + + self.is_upload_contract_file = False + + # 返回视图动作来刷新当前表单 + return { + 'type': 'ir.actions.act_window', + 'res_model': 'purchase.requisition', + 'res_id': self.id, + 'view_mode': 'form', + 'view_type': 'form', + 'target': 'current', + 'flags': {'mode': 'readonly'}, + } + + except Exception as e: + _logger.error('删除合同文件时出错: %s', str(e)) + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': _('错误'), + 'message': _('删除文件时出现错误'), + 'type': 'danger', + 'sticky': True, + } + } + + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': _('提示'), + 'message': _('没有需要删除的合同文件'), + 'type': 'warning', + 'sticky': False, + } + } + + def action_in_progress(self): + self.ensure_one() + # 检查合同文件 + if not self.contract_document_id: + raise ValidationError('* 必须点击上传合同文件') + if self.origin: + purchase_order = self.env['purchase.order'].search([('name', '=', self.origin)], limit=1) + if purchase_order: + if not purchase_order.requisition_id: + # 绑定采购协议 + self.purchase_ids += purchase_order + else: + raise ValidationError('源单据已绑定采购协议,请手动清除源单据解绑。') + res = super(PurchaseRequisition, self).action_in_progress() + # 将合同文档移动到废弃 + for requisition in self: + if requisition.contract_document_id: + workspace = self.update_documents_folder('通过') + requisition.contract_document_id.write({ + 'folder_id': workspace.id, + }) + return res + + def action_cancel(self): + for requisition in self: + if requisition.purchase_ids: + raise ValidationError(_('已关联采购订单,不能取消该采购协议')) + res = super(PurchaseRequisition, self).action_cancel() + # 将合同文档移动到废弃 + for requisition in self: + if requisition.contract_document_id: + workspace = self.update_documents_folder('废弃') + requisition.contract_document_id.write({ + 'folder_id': workspace.id, + }) + return res + + def action_draft(self): + res = super(PurchaseRequisition, self).action_draft() + for requisition in self: + if requisition.contract_document_id: + workspace = self.update_documents_folder('待审') + requisition.contract_document_id.write({ + 'folder_id': workspace.id, + }) + return res + + def update_documents_folder(self, documents_folder_name): + workspace_parent = self.env['documents.folder'].search([('name', '=', '采购合同')], limit=1) + workspace = self.env['documents.folder'].search( + [('name', '=', documents_folder_name), ('parent_folder_id', '=', workspace_parent.id)], limit=1) + return workspace + + @api.constrains('line_ids') + def check_line_ids(self): + if self.origin: + purchase_order = self.env['purchase.order'].search([('name', '=', self.origin)], limit=1) + if purchase_order: + error_products = [] + # 获取采购订单中的所有产品 + purchase_order_products = purchase_order.order_line.mapped('product_id') + if self.line_ids: + for line in self.line_ids: + # 检查当前行的产品在采购订单中的数量 + order_line = purchase_order.order_line.filtered(lambda ol: ol.product_id == line.product_id) + if order_line and line.product_qty < order_line.product_qty: + error_products.append(line.product_id.name) # 收集不符合条件的产品名称 + else: + raise ValidationError( + _('采购协议的产品和数量应达超源单据的产品和数量,请修改产品数量或修改源单据。')) + + # 检查采购订单中的产品是否都在采购协议中 + for product in purchase_order_products: + if product not in self.line_ids.mapped('product_id'): + raise ValidationError( + _('采购协议的产品和数量应达超源单据的产品和数量,请修改产品数量或修改源单据。')) + if error_products: + raise ValidationError( + _('产品 %s 的数量不能小于源单据数量,当前无法保存。') % (', '.join(error_products))) + + +class PurchaseRequisitionLine(models.Model): + _inherit = 'purchase.requisition.line' + + materials_id = fields.Many2one('sf.production.materials', string='材料', compute='_compute_product_info') + materials_type_id = fields.Many2one('sf.materials.model', string='规格型号', compute='_compute_product_info') + part_number = fields.Char(string='零件图号', compute='_compute_product_info') + delivery_date = fields.Date(string='交货时间') + + @api.depends('product_id') + def _compute_product_info(self): + for line in self: + if line.product_id: + line.materials_id = line.product_id.materials_id + line.materials_type_id = line.product_id.materials_type_id + line.part_number = line.product_id.part_number + else: + line.materials_id = False + line.materials_type_id = False + line.part_number = False + + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + if 'product_qty' in vals and vals.get('product_qty') <= 0: + raise UserError('请对【产品】中的【数量】进行输入') + if 'price_unit' in vals and vals.get('price_unit') <= 0: + raise UserError('请对【产品】中的【单价】进行输入') + return super(PurchaseRequisitionLine, self).create(vals_list) + + def write(self, vals): + if 'product_qty' in vals and vals.get('product_qty') <= 0: + raise UserError('请对【产品】中的【数量】进行输入') + if 'price_unit' in vals and vals.get('price_unit') <= 0: + raise UserError('请对【产品】中的【单价】进行输入') + return super(PurchaseRequisitionLine, self).write(vals) diff --git a/sf_sale/models/sale_order.py b/sf_sale/models/sale_order.py index 4d0106bc..3d5d2b21 100644 --- a/sf_sale/models/sale_order.py +++ b/sf_sale/models/sale_order.py @@ -132,7 +132,7 @@ class ReSaleOrder(models.Model): 'name': '%s/%s/%s/%s/%s/%s' % ( self.format_float(product.model_long), self.format_float(product.model_width), - self.format_float(product.model_height), + self.format_float(product.model_height), self.format_float(product.model_volume), machining_accuracy_name, product.materials_id.name), @@ -303,13 +303,39 @@ class RePurchaseOrder(models.Model): string='采购类型', default='standard', store=True, compute='_compute_purchase_type') # 合同编号 - contract_number = fields.Char(string='合同编号', size=20) + contract_number = fields.Char(related='requisition_id.contract_number', string='合同编号', size=20) # 合同概况 contract_summary = fields.Text(string='合同概况') # 选择是否为紧急采购 urgent_purchase = fields.Selection([('no', '否'), ('yes', '是')], string='紧急采购', default='no') + purchase_requisition_count = fields.Integer('采购协议数量', compute='_compute_purchase_requisition_count') + + partner_ref = fields.Char(related='requisition_id.partner_ref') + payment_term_id = fields.Many2one(related='requisition_id.payment_term_id') + + requisition_id = fields.Many2one('purchase.requisition', string='采购协议', copy=False, readonly=True) + show_create_requisition_button = fields.Boolean(string='显示创建采购协议按钮', + compute='_compute_show_create_requisition_button' + ) + show_requisition_fields = fields.Boolean(string='显示协议按钮', compute='_compute_show_requisition_fields') + + @api.depends('requisition_id') + def _compute_show_requisition_fields(self): + for order in self: + order.show_requisition_fields = bool(order.requisition_id and order.requisition_id.state == 'ongoing') + + @api.depends('requisition_id') + def _compute_show_create_requisition_button(self): + for order in self: + purchase_requisition = self.env['purchase.requisition'].search_count([('origin', '=', order.name)]) + order.show_create_requisition_button = purchase_requisition > 0 + + def _compute_purchase_requisition_count(self): + for record in self: + record.purchase_requisition_count = len(record.requisition_id) + @api.depends('origin') def _compute_purchase_type(self): for purchase in self: @@ -344,6 +370,26 @@ class RePurchaseOrder(models.Model): for item in self: if not item.order_line: raise UserError('该询价单未添加【产品】,请进行添加') + missing_products = [] + over_ordered_products = [] + if item.requisition_id: + requisition_product_ids = item.requisition_id.line_ids.mapped('product_id.id') + for line in item.order_line: + if line.product_id.id not in requisition_product_ids: + missing_products.append(line.product_id.name) + else: + requisition_line = item.requisition_id.line_ids.filtered( + lambda r: r.product_id.id == line.product_id.id) + remaining_qty = requisition_line.product_qty - requisition_line.qty_ordered + if requisition_line and line.product_qty > remaining_qty: + over_ordered_products.append( + f'{line.product_id.name}:{remaining_qty}{requisition_line.product_uom_id.name}') + + if missing_products: + raise UserError('关联协议不存在产品 %s ,无法保存。' % '、'.join(missing_products)) + if over_ordered_products: + raise UserError('当前订购数量超过了关联协议的剩余数量,无法保存。\n涉及产品及剩余数量有:\n%s' % '\n'.join( + over_ordered_products)) for line in item.order_line: if not line.product_id: raise UserError('【产品】未添加,请进行添加') @@ -399,6 +445,21 @@ class RePurchaseOrder(models.Model): # raise ValidationError('【%s】已存在,请勿重复添加' % product[-1].name) def button_confirm(self): + over_ordered_products = [] + for item in self: + if item.requisition_id: + requisition_product_ids = item.requisition_id.line_ids.mapped('product_id.id') + for line in item.order_line: + if line.product_id.id in requisition_product_ids: + requisition_line = item.requisition_id.line_ids.filtered( + lambda r: r.product_id.id == line.product_id.id) + remaining_qty = requisition_line.product_qty - requisition_line.qty_ordered + if requisition_line and line.product_qty > remaining_qty: + over_ordered_products.append( + f'{line.product_id.name}:{remaining_qty} {requisition_line.product_uom_id.name}') + if over_ordered_products: + raise UserError('当前订购数量超过了关联协议的剩余数量,无法保存。\n涉及产品及剩余数量有:\n%s' % '\n'.join( + over_ordered_products)) result = super(RePurchaseOrder, self).button_confirm() for item in self: # 确认订单时,自动分配序列号 @@ -439,6 +500,90 @@ class RePurchaseOrder(models.Model): purchase_order_overdue.write({'delivery_warning': 'normal'}) return last_overdue_order, last_warning_order + def button_create_requisition(self): + self.ensure_one() + line_ids = [] + for order_line in self.order_line: + line_values = { + 'product_id': order_line.product_id.id, + 'product_uom_id': order_line.product_uom.id, + 'product_qty': order_line.product_qty, + 'price_unit': order_line.price_unit, + } + line_ids.append((0, 0, line_values)) + initial_data = { + 'vendor_id': self.partner_id.id, + 'origin': self.name, + 'line_ids': line_ids, + } + return { + 'type': 'ir.actions.act_window', + 'res_model': 'purchase.requisition', + 'view_mode': 'form', + 'target': 'current', + 'context': { + 'default_origin': initial_data['origin'], + 'default_line_ids': initial_data['line_ids'], + 'default_vendor_id': initial_data['vendor_id'], + } + } + + def button_link_requisition(self): + requisition_ids = self.env['purchase.requisition'].sudo().search( + [('state', '=', 'ongoing'), ('vendor_id', '=', self.partner_id.id), + ('line_ids.product_id', 'in', self.order_line.mapped('product_id.id'))]) + + # 新增的逻辑:过滤出符合条件的requisition_ids + valid_requisition_ids = [] + for requisition in requisition_ids: + for line in requisition.line_ids: + if line.product_id in self.order_line.mapped( + 'product_id') and line.product_qty - line.qty_ordered >= self.order_line.filtered( + lambda ol: ol.product_id == line.product_id).product_qty: + valid_requisition_ids.append(requisition.id) + break + + action = { + 'res_model': 'purchase.requisition', + 'type': 'ir.actions.act_window', + 'name': _("选择采购申请"), + 'domain': [('id', 'in', valid_requisition_ids)], + 'views': [[self.env.ref('sf_sale.purchase_requisition_wizard_tree_view').id, 'list']], + 'target': 'new', + 'context': {'purchase_order_id': self.id}, + } + return action + + def button_unlink_requisition(self): + self.ensure_one() + for line in self.order_line: + if self.requisition_id: + requisition_line = self.requisition_id.line_ids.filtered(lambda r: r.product_id.id == line.product_id.id) + if requisition_line: + self.requisition_id.origin = False + self.requisition_id.purchase_ids = [(3, self.id)] + + + def action_view_requisition(self): + self.ensure_one() + requisition_ids = self.requisition_id.ids + action = { + 'res_model': 'purchase.requisition', + 'type': 'ir.actions.act_window', + } + if len(requisition_ids) == 1: + action.update({ + 'view_mode': 'form', + 'res_id': requisition_ids[0], + }) + else: + action.update({ + 'name': _("采购协议来源%s", self.name), + 'domain': [('id', 'in', requisition_ids)], + 'view_mode': 'tree,form', + }) + return action + class PurchaseOrderLine(models.Model): _inherit = 'purchase.order.line' diff --git a/sf_sale/security/ir.model.access.csv b/sf_sale/security/ir.model.access.csv index 64194f9a..03f636e6 100644 --- a/sf_sale/security/ir.model.access.csv +++ b/sf_sale/security/ir.model.access.csv @@ -119,5 +119,5 @@ access_stock_scrap_group_sale_director,stock_scrap_group_sale_director,stock.mod access_account_move_group_plan_dispatch,account_move_group_plan_dispatch,account.model_account_move,sf_base.group_plan_dispatch,1,1,1,0 - +access_ir_attachment_upload,ir.attachment.upload,model_ir_attachment_upload,base.group_user,1,1,1,1 diff --git a/sf_sale/views/purchase_order_view.xml b/sf_sale/views/purchase_order_view.xml index ecc061fa..9a271cb9 100644 --- a/sf_sale/views/purchase_order_view.xml +++ b/sf_sale/views/purchase_order_view.xml @@ -25,6 +25,14 @@ data-hotkey="y" groups="stock.group_stock_user"/> + + + + + 1 @@ -139,7 +147,7 @@ - + {'readonly': [('state', 'in', ['purchase'])]} @@ -210,6 +218,17 @@ 1 + + + + + + + 采购协议 + + + diff --git a/sf_sale/views/purchase_requisition_view.xml b/sf_sale/views/purchase_requisition_view.xml new file mode 100644 index 00000000..e96e5c31 --- /dev/null +++ b/sf_sale/views/purchase_requisition_view.xml @@ -0,0 +1,75 @@ + + + + + purchase.requisition.form.inherit.sf + purchase.requisition + + + + + + ]}"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sf_sale/wizard/__init__.py b/sf_sale/wizard/__init__.py index 7f1bd0c7..d74cb77b 100644 --- a/sf_sale/wizard/__init__.py +++ b/sf_sale/wizard/__init__.py @@ -1,2 +1,4 @@ from . import sale_order_wizard from . import purchase_order_wizard +from . import upload_file_upload +from . import purchase_requisition_wizard diff --git a/sf_sale/wizard/purchase_requisition_wizard.py b/sf_sale/wizard/purchase_requisition_wizard.py new file mode 100644 index 00000000..7b335b57 --- /dev/null +++ b/sf_sale/wizard/purchase_requisition_wizard.py @@ -0,0 +1,20 @@ +from odoo import models, fields, api, _ +import logging +from odoo.exceptions import UserError + +_logger = logging.getLogger(__name__) + + +class PurchaseRequisition(models.Model): + _inherit = 'purchase.requisition' + + def action_confirm(self): + purchase_order_id = self.env.context.get('purchase_order_id') + purchase_order = self.env['purchase.order'].sudo().search([('id', '=', purchase_order_id)]) + if purchase_order.requisition_id.id == self.id: + raise UserError(_('该采购订单已绑定%s协议') % purchase_order.name) + if purchase_order.requisition_id: + # 先解绑 + purchase_order.button_unlink_requisition() + purchase_order.write({'requisition_id': self.id}) + self.purchase_ids += purchase_order diff --git a/sf_sale/wizard/purchase_requisition_wizard_views.xml b/sf_sale/wizard/purchase_requisition_wizard_views.xml new file mode 100644 index 00000000..a18321f3 --- /dev/null +++ b/sf_sale/wizard/purchase_requisition_wizard_views.xml @@ -0,0 +1,17 @@ + + + + purchase.requisition.wizard.tree.view + purchase.requisition + + + + + + + + + + + + \ No newline at end of file diff --git a/sf_sale/wizard/upload_file_upload.py b/sf_sale/wizard/upload_file_upload.py new file mode 100644 index 00000000..4d84caa6 --- /dev/null +++ b/sf_sale/wizard/upload_file_upload.py @@ -0,0 +1,60 @@ +from odoo import models, fields, api, _ + + +class IrAttachmentUpload(models.TransientModel): + _name = 'ir.attachment.upload' + _description = '文件上传向导' + + attachment = fields.Binary(string='选择文件', required=True) + filename = fields.Char(string='文件名') + res_model = fields.Char() + res_id = fields.Integer() + + def action_upload_file(self): + self.ensure_one() + # 获取当前用户的 partner_id + current_partner = self.env.user.partner_id + # 首先创建 ir.attachment + attachment = self.env['ir.attachment'].create({ + 'name': self.filename, + 'type': 'binary', + 'datas': self.attachment, + 'res_model': self.res_model, + 'res_id': self.res_id, + }) + + # 获取默认的文档文件夹 + workspace = self.env['purchase.requisition'].update_documents_folder('待审') + + # 创建 documents.document 记录 + document = self.env['documents.document'].create({ + 'name': self.filename, + 'attachment_id': attachment.id, + 'folder_id': workspace.id, + 'res_model': self.res_model, + 'res_id': self.res_id, + 'partner_id': current_partner.id, + }) + + # 更新采购订单的合同文档字段 + purchase_requisition = self.env['purchase.requisition'].browse(self.res_id) + purchase_requisition.write({ + 'contract_document_id': document.id, + 'is_upload_contract_file': True + }) + + # 显示成功消息并关闭向导 + message = { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': _('成功'), + 'message': _('文件上传成功'), + 'type': 'success', + 'sticky': False, # 自动消失 + 'next': { + 'type': 'ir.actions.act_window_close' + } + } + } + return message diff --git a/jikimo_purchase_tier_validation/wizards/upload_file_wizard_view.xml b/sf_sale/wizard/upload_file_upload_view.xml similarity index 81% rename from jikimo_purchase_tier_validation/wizards/upload_file_wizard_view.xml rename to sf_sale/wizard/upload_file_upload_view.xml index d75e75cb..54412063 100644 --- a/jikimo_purchase_tier_validation/wizards/upload_file_wizard_view.xml +++ b/sf_sale/wizard/upload_file_upload_view.xml @@ -1,8 +1,8 @@ - - ir.attachment.wizard.form - ir.attachment.wizard + + ir.attachment.upload.form + ir.attachment.upload From bdccce07d4366e11ccc8814cd94b5483b7eb7d7f Mon Sep 17 00:00:00 2001 From: guanhuan Date: Wed, 19 Feb 2025 11:31:46 +0800 Subject: [PATCH 21/27] =?UTF-8?q?=E9=87=87=E8=B4=AD=E5=8D=8F=E8=AE=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_mrs_connect/__manifest__.py | 2 +- sf_sale/__manifest__.py | 7 +- sf_sale/data/documents_data.xml | 23 -- sf_sale/i18n/zh_CN.po | 10 - sf_sale/models/__init__.py | 1 - sf_sale/models/purchase_requisition.py | 210 ------------------ sf_sale/models/sale_order.py | 147 +----------- sf_sale/views/purchase_order_view.xml | 19 -- sf_sale/views/purchase_requisition_view.xml | 75 ------- sf_sale/wizard/__init__.py | 2 - sf_sale/wizard/purchase_requisition_wizard.py | 20 -- .../purchase_requisition_wizard_views.xml | 17 -- sf_sale/wizard/upload_file_upload.py | 60 ----- sf_sale/wizard/upload_file_upload_view.xml | 21 -- 14 files changed, 3 insertions(+), 611 deletions(-) delete mode 100644 sf_sale/data/documents_data.xml delete mode 100644 sf_sale/i18n/zh_CN.po delete mode 100644 sf_sale/models/purchase_requisition.py delete mode 100644 sf_sale/views/purchase_requisition_view.xml delete mode 100644 sf_sale/wizard/purchase_requisition_wizard.py delete mode 100644 sf_sale/wizard/purchase_requisition_wizard_views.xml delete mode 100644 sf_sale/wizard/upload_file_upload.py delete mode 100644 sf_sale/wizard/upload_file_upload_view.xml diff --git a/sf_mrs_connect/__manifest__.py b/sf_mrs_connect/__manifest__.py index ea1fb001..1ea4f693 100644 --- a/sf_mrs_connect/__manifest__.py +++ b/sf_mrs_connect/__manifest__.py @@ -10,7 +10,7 @@ """, 'category': 'sf', 'website': 'https://www.sf.cs.jikimo.com', - 'depends': ['sf_base', 'base_setup','sf_bf_connect','sf_sale'], + 'depends': ['sf_base', 'base_setup','sf_bf_connect'], 'data': [ 'data/ir_cron_data.xml', 'security/ir.model.access.csv', diff --git a/sf_sale/__manifest__.py b/sf_sale/__manifest__.py index d33bb02e..e824240e 100644 --- a/sf_sale/__manifest__.py +++ b/sf_sale/__manifest__.py @@ -10,23 +10,18 @@ """, 'category': 'sf', 'website': 'https://www.sf.jikimo.com', - 'depends': ['sale', 'sale_management', 'web_widget_model_viewer', 'sf_base', 'account', 'purchase', 'delivery', - 'purchase_requisition', 'purchase_requisition_stock', 'documents'], + 'depends': ['sale', 'sale_management', 'web_widget_model_viewer', 'sf_base', 'account', 'purchase', 'delivery'], 'data': [ - 'data/documents_data.xml', 'security/group_security.xml', 'security/ir.model.access.csv', 'wizard/sale_order_wizard_views.xml', 'wizard/purchase_order_wizard_views.xml', - 'wizard/upload_file_upload_view.xml', - 'wizard/purchase_requisition_wizard_views.xml', 'data/cron_data.xml', 'views/sale_team.xml', 'views/sale_order_view.xml', 'views/res_partner_view.xml', 'views/purchase_order_view.xml', 'views/quick_easy_order_view.xml', - 'views/purchase_requisition_view.xml', 'views/purchase_menu.xml' ], 'assets': { diff --git a/sf_sale/data/documents_data.xml b/sf_sale/data/documents_data.xml deleted file mode 100644 index f5f46023..00000000 --- a/sf_sale/data/documents_data.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - 采购合同 - 存放采购合同相关文件 - 10 - - - 废弃 - - - - 通过 - - - - 待审 - - - - \ No newline at end of file diff --git a/sf_sale/i18n/zh_CN.po b/sf_sale/i18n/zh_CN.po deleted file mode 100644 index 2cb98706..00000000 --- a/sf_sale/i18n/zh_CN.po +++ /dev/null @@ -1,10 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * purchase_requisition -# - -#. module: purchase_requisition -#: model:ir.actions.act_window,name:purchase_requisition.action_purchase_requisition -#: model:ir.ui.menu,name:purchase_requisition.menu_purchase_requisition_pro_mgt -msgid "Blanket Orders" -msgstr "采购协议" diff --git a/sf_sale/models/__init__.py b/sf_sale/models/__init__.py index c8e3a43f..9f2eb4dc 100644 --- a/sf_sale/models/__init__.py +++ b/sf_sale/models/__init__.py @@ -4,4 +4,3 @@ from . import quick_easy_order_old from . import auto_quatotion_common from . import parser_and_calculate_work_time from . import preload_datas_functions -from . import purchase_requisition diff --git a/sf_sale/models/purchase_requisition.py b/sf_sale/models/purchase_requisition.py deleted file mode 100644 index 263f65ab..00000000 --- a/sf_sale/models/purchase_requisition.py +++ /dev/null @@ -1,210 +0,0 @@ -from odoo import models, fields, api, _ -from odoo.exceptions import UserError, ValidationError -import logging - -_logger = logging.getLogger(__name__) - - -class PurchaseRequisition(models.Model): - _inherit = 'purchase.requisition' - - partner_ref = fields.Char(string='合同名称', required=True) - contract_number = fields.Char(string='合同编码', size=20, required=True) - payment_term_id = fields.Many2one('account.payment.term', '付款条件', - domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", - required=True) - contract_summary = fields.Text(string='合同概况', required=True) - - contract_document_id = fields.Many2one('documents.document', string='合同文件') - contract_file = fields.Binary(related='contract_document_id.datas', string='合同文件内容') - contract_file_name = fields.Char(related='contract_document_id.attachment_id.name', string='文件名') - - # 是否已上传合同文件 - is_upload_contract_file = fields.Boolean(string='是否已上传合同文件', default=False) - - def upload_contract_file(self): - self.ensure_one() - action = { - 'type': 'ir.actions.act_window', - 'name': _('上传合同文件'), - 'res_model': 'ir.attachment.upload', # 我们需要创建一个新的向导模型 - 'view_mode': 'form', - 'target': 'new', - 'context': { - 'default_res_model': self._name, - 'default_res_id': self.id, - } - } - return action - - # 删除合同文件 - def delete_contract_file(self): - self.ensure_one() - if self.contract_document_id: - try: - document = self.contract_document_id - - # 清空关联 - self.write({ - 'contract_document_id': False, - 'contract_file': False, - 'contract_file_name': False - }) - - # 删除文档 - if document: - document.with_context(no_attachment=True).sudo().unlink() - - self.is_upload_contract_file = False - - # 返回视图动作来刷新当前表单 - return { - 'type': 'ir.actions.act_window', - 'res_model': 'purchase.requisition', - 'res_id': self.id, - 'view_mode': 'form', - 'view_type': 'form', - 'target': 'current', - 'flags': {'mode': 'readonly'}, - } - - except Exception as e: - _logger.error('删除合同文件时出错: %s', str(e)) - return { - 'type': 'ir.actions.client', - 'tag': 'display_notification', - 'params': { - 'title': _('错误'), - 'message': _('删除文件时出现错误'), - 'type': 'danger', - 'sticky': True, - } - } - - return { - 'type': 'ir.actions.client', - 'tag': 'display_notification', - 'params': { - 'title': _('提示'), - 'message': _('没有需要删除的合同文件'), - 'type': 'warning', - 'sticky': False, - } - } - - def action_in_progress(self): - self.ensure_one() - # 检查合同文件 - if not self.contract_document_id: - raise ValidationError('* 必须点击上传合同文件') - if self.origin: - purchase_order = self.env['purchase.order'].search([('name', '=', self.origin)], limit=1) - if purchase_order: - if not purchase_order.requisition_id: - # 绑定采购协议 - self.purchase_ids += purchase_order - else: - raise ValidationError('源单据已绑定采购协议,请手动清除源单据解绑。') - res = super(PurchaseRequisition, self).action_in_progress() - # 将合同文档移动到废弃 - for requisition in self: - if requisition.contract_document_id: - workspace = self.update_documents_folder('通过') - requisition.contract_document_id.write({ - 'folder_id': workspace.id, - }) - return res - - def action_cancel(self): - for requisition in self: - if requisition.purchase_ids: - raise ValidationError(_('已关联采购订单,不能取消该采购协议')) - res = super(PurchaseRequisition, self).action_cancel() - # 将合同文档移动到废弃 - for requisition in self: - if requisition.contract_document_id: - workspace = self.update_documents_folder('废弃') - requisition.contract_document_id.write({ - 'folder_id': workspace.id, - }) - return res - - def action_draft(self): - res = super(PurchaseRequisition, self).action_draft() - for requisition in self: - if requisition.contract_document_id: - workspace = self.update_documents_folder('待审') - requisition.contract_document_id.write({ - 'folder_id': workspace.id, - }) - return res - - def update_documents_folder(self, documents_folder_name): - workspace_parent = self.env['documents.folder'].search([('name', '=', '采购合同')], limit=1) - workspace = self.env['documents.folder'].search( - [('name', '=', documents_folder_name), ('parent_folder_id', '=', workspace_parent.id)], limit=1) - return workspace - - @api.constrains('line_ids') - def check_line_ids(self): - if self.origin: - purchase_order = self.env['purchase.order'].search([('name', '=', self.origin)], limit=1) - if purchase_order: - error_products = [] - # 获取采购订单中的所有产品 - purchase_order_products = purchase_order.order_line.mapped('product_id') - if self.line_ids: - for line in self.line_ids: - # 检查当前行的产品在采购订单中的数量 - order_line = purchase_order.order_line.filtered(lambda ol: ol.product_id == line.product_id) - if order_line and line.product_qty < order_line.product_qty: - error_products.append(line.product_id.name) # 收集不符合条件的产品名称 - else: - raise ValidationError( - _('采购协议的产品和数量应达超源单据的产品和数量,请修改产品数量或修改源单据。')) - - # 检查采购订单中的产品是否都在采购协议中 - for product in purchase_order_products: - if product not in self.line_ids.mapped('product_id'): - raise ValidationError( - _('采购协议的产品和数量应达超源单据的产品和数量,请修改产品数量或修改源单据。')) - if error_products: - raise ValidationError( - _('产品 %s 的数量不能小于源单据数量,当前无法保存。') % (', '.join(error_products))) - - -class PurchaseRequisitionLine(models.Model): - _inherit = 'purchase.requisition.line' - - materials_id = fields.Many2one('sf.production.materials', string='材料', compute='_compute_product_info') - materials_type_id = fields.Many2one('sf.materials.model', string='规格型号', compute='_compute_product_info') - part_number = fields.Char(string='零件图号', compute='_compute_product_info') - delivery_date = fields.Date(string='交货时间') - - @api.depends('product_id') - def _compute_product_info(self): - for line in self: - if line.product_id: - line.materials_id = line.product_id.materials_id - line.materials_type_id = line.product_id.materials_type_id - line.part_number = line.product_id.part_number - else: - line.materials_id = False - line.materials_type_id = False - line.part_number = False - - @api.model_create_multi - def create(self, vals_list): - for vals in vals_list: - if 'product_qty' in vals and vals.get('product_qty') <= 0: - raise UserError('请对【产品】中的【数量】进行输入') - if 'price_unit' in vals and vals.get('price_unit') <= 0: - raise UserError('请对【产品】中的【单价】进行输入') - return super(PurchaseRequisitionLine, self).create(vals_list) - - def write(self, vals): - if 'product_qty' in vals and vals.get('product_qty') <= 0: - raise UserError('请对【产品】中的【数量】进行输入') - if 'price_unit' in vals and vals.get('price_unit') <= 0: - raise UserError('请对【产品】中的【单价】进行输入') - return super(PurchaseRequisitionLine, self).write(vals) diff --git a/sf_sale/models/sale_order.py b/sf_sale/models/sale_order.py index 3d5d2b21..66a32f20 100644 --- a/sf_sale/models/sale_order.py +++ b/sf_sale/models/sale_order.py @@ -303,39 +303,13 @@ class RePurchaseOrder(models.Model): string='采购类型', default='standard', store=True, compute='_compute_purchase_type') # 合同编号 - contract_number = fields.Char(related='requisition_id.contract_number', string='合同编号', size=20) + contract_number = fields.Char(string='合同编号', size=20) # 合同概况 contract_summary = fields.Text(string='合同概况') # 选择是否为紧急采购 urgent_purchase = fields.Selection([('no', '否'), ('yes', '是')], string='紧急采购', default='no') - purchase_requisition_count = fields.Integer('采购协议数量', compute='_compute_purchase_requisition_count') - - partner_ref = fields.Char(related='requisition_id.partner_ref') - payment_term_id = fields.Many2one(related='requisition_id.payment_term_id') - - requisition_id = fields.Many2one('purchase.requisition', string='采购协议', copy=False, readonly=True) - show_create_requisition_button = fields.Boolean(string='显示创建采购协议按钮', - compute='_compute_show_create_requisition_button' - ) - show_requisition_fields = fields.Boolean(string='显示协议按钮', compute='_compute_show_requisition_fields') - - @api.depends('requisition_id') - def _compute_show_requisition_fields(self): - for order in self: - order.show_requisition_fields = bool(order.requisition_id and order.requisition_id.state == 'ongoing') - - @api.depends('requisition_id') - def _compute_show_create_requisition_button(self): - for order in self: - purchase_requisition = self.env['purchase.requisition'].search_count([('origin', '=', order.name)]) - order.show_create_requisition_button = purchase_requisition > 0 - - def _compute_purchase_requisition_count(self): - for record in self: - record.purchase_requisition_count = len(record.requisition_id) - @api.depends('origin') def _compute_purchase_type(self): for purchase in self: @@ -370,26 +344,6 @@ class RePurchaseOrder(models.Model): for item in self: if not item.order_line: raise UserError('该询价单未添加【产品】,请进行添加') - missing_products = [] - over_ordered_products = [] - if item.requisition_id: - requisition_product_ids = item.requisition_id.line_ids.mapped('product_id.id') - for line in item.order_line: - if line.product_id.id not in requisition_product_ids: - missing_products.append(line.product_id.name) - else: - requisition_line = item.requisition_id.line_ids.filtered( - lambda r: r.product_id.id == line.product_id.id) - remaining_qty = requisition_line.product_qty - requisition_line.qty_ordered - if requisition_line and line.product_qty > remaining_qty: - over_ordered_products.append( - f'{line.product_id.name}:{remaining_qty}{requisition_line.product_uom_id.name}') - - if missing_products: - raise UserError('关联协议不存在产品 %s ,无法保存。' % '、'.join(missing_products)) - if over_ordered_products: - raise UserError('当前订购数量超过了关联协议的剩余数量,无法保存。\n涉及产品及剩余数量有:\n%s' % '\n'.join( - over_ordered_products)) for line in item.order_line: if not line.product_id: raise UserError('【产品】未添加,请进行添加') @@ -445,21 +399,6 @@ class RePurchaseOrder(models.Model): # raise ValidationError('【%s】已存在,请勿重复添加' % product[-1].name) def button_confirm(self): - over_ordered_products = [] - for item in self: - if item.requisition_id: - requisition_product_ids = item.requisition_id.line_ids.mapped('product_id.id') - for line in item.order_line: - if line.product_id.id in requisition_product_ids: - requisition_line = item.requisition_id.line_ids.filtered( - lambda r: r.product_id.id == line.product_id.id) - remaining_qty = requisition_line.product_qty - requisition_line.qty_ordered - if requisition_line and line.product_qty > remaining_qty: - over_ordered_products.append( - f'{line.product_id.name}:{remaining_qty} {requisition_line.product_uom_id.name}') - if over_ordered_products: - raise UserError('当前订购数量超过了关联协议的剩余数量,无法保存。\n涉及产品及剩余数量有:\n%s' % '\n'.join( - over_ordered_products)) result = super(RePurchaseOrder, self).button_confirm() for item in self: # 确认订单时,自动分配序列号 @@ -500,90 +439,6 @@ class RePurchaseOrder(models.Model): purchase_order_overdue.write({'delivery_warning': 'normal'}) return last_overdue_order, last_warning_order - def button_create_requisition(self): - self.ensure_one() - line_ids = [] - for order_line in self.order_line: - line_values = { - 'product_id': order_line.product_id.id, - 'product_uom_id': order_line.product_uom.id, - 'product_qty': order_line.product_qty, - 'price_unit': order_line.price_unit, - } - line_ids.append((0, 0, line_values)) - initial_data = { - 'vendor_id': self.partner_id.id, - 'origin': self.name, - 'line_ids': line_ids, - } - return { - 'type': 'ir.actions.act_window', - 'res_model': 'purchase.requisition', - 'view_mode': 'form', - 'target': 'current', - 'context': { - 'default_origin': initial_data['origin'], - 'default_line_ids': initial_data['line_ids'], - 'default_vendor_id': initial_data['vendor_id'], - } - } - - def button_link_requisition(self): - requisition_ids = self.env['purchase.requisition'].sudo().search( - [('state', '=', 'ongoing'), ('vendor_id', '=', self.partner_id.id), - ('line_ids.product_id', 'in', self.order_line.mapped('product_id.id'))]) - - # 新增的逻辑:过滤出符合条件的requisition_ids - valid_requisition_ids = [] - for requisition in requisition_ids: - for line in requisition.line_ids: - if line.product_id in self.order_line.mapped( - 'product_id') and line.product_qty - line.qty_ordered >= self.order_line.filtered( - lambda ol: ol.product_id == line.product_id).product_qty: - valid_requisition_ids.append(requisition.id) - break - - action = { - 'res_model': 'purchase.requisition', - 'type': 'ir.actions.act_window', - 'name': _("选择采购申请"), - 'domain': [('id', 'in', valid_requisition_ids)], - 'views': [[self.env.ref('sf_sale.purchase_requisition_wizard_tree_view').id, 'list']], - 'target': 'new', - 'context': {'purchase_order_id': self.id}, - } - return action - - def button_unlink_requisition(self): - self.ensure_one() - for line in self.order_line: - if self.requisition_id: - requisition_line = self.requisition_id.line_ids.filtered(lambda r: r.product_id.id == line.product_id.id) - if requisition_line: - self.requisition_id.origin = False - self.requisition_id.purchase_ids = [(3, self.id)] - - - def action_view_requisition(self): - self.ensure_one() - requisition_ids = self.requisition_id.ids - action = { - 'res_model': 'purchase.requisition', - 'type': 'ir.actions.act_window', - } - if len(requisition_ids) == 1: - action.update({ - 'view_mode': 'form', - 'res_id': requisition_ids[0], - }) - else: - action.update({ - 'name': _("采购协议来源%s", self.name), - 'domain': [('id', 'in', requisition_ids)], - 'view_mode': 'tree,form', - }) - return action - class PurchaseOrderLine(models.Model): _inherit = 'purchase.order.line' diff --git a/sf_sale/views/purchase_order_view.xml b/sf_sale/views/purchase_order_view.xml index 9a271cb9..209fd4fa 100644 --- a/sf_sale/views/purchase_order_view.xml +++ b/sf_sale/views/purchase_order_view.xml @@ -25,14 +25,6 @@ data-hotkey="y" groups="stock.group_stock_user"/> - - - - - 1 @@ -218,17 +210,6 @@ 1 - - - - - - - 采购协议 - - - diff --git a/sf_sale/views/purchase_requisition_view.xml b/sf_sale/views/purchase_requisition_view.xml deleted file mode 100644 index e96e5c31..00000000 --- a/sf_sale/views/purchase_requisition_view.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - - purchase.requisition.form.inherit.sf - purchase.requisition - - - - - - ]}"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/sf_sale/wizard/__init__.py b/sf_sale/wizard/__init__.py index d74cb77b..7f1bd0c7 100644 --- a/sf_sale/wizard/__init__.py +++ b/sf_sale/wizard/__init__.py @@ -1,4 +1,2 @@ from . import sale_order_wizard from . import purchase_order_wizard -from . import upload_file_upload -from . import purchase_requisition_wizard diff --git a/sf_sale/wizard/purchase_requisition_wizard.py b/sf_sale/wizard/purchase_requisition_wizard.py deleted file mode 100644 index 7b335b57..00000000 --- a/sf_sale/wizard/purchase_requisition_wizard.py +++ /dev/null @@ -1,20 +0,0 @@ -from odoo import models, fields, api, _ -import logging -from odoo.exceptions import UserError - -_logger = logging.getLogger(__name__) - - -class PurchaseRequisition(models.Model): - _inherit = 'purchase.requisition' - - def action_confirm(self): - purchase_order_id = self.env.context.get('purchase_order_id') - purchase_order = self.env['purchase.order'].sudo().search([('id', '=', purchase_order_id)]) - if purchase_order.requisition_id.id == self.id: - raise UserError(_('该采购订单已绑定%s协议') % purchase_order.name) - if purchase_order.requisition_id: - # 先解绑 - purchase_order.button_unlink_requisition() - purchase_order.write({'requisition_id': self.id}) - self.purchase_ids += purchase_order diff --git a/sf_sale/wizard/purchase_requisition_wizard_views.xml b/sf_sale/wizard/purchase_requisition_wizard_views.xml deleted file mode 100644 index a18321f3..00000000 --- a/sf_sale/wizard/purchase_requisition_wizard_views.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - purchase.requisition.wizard.tree.view - purchase.requisition - - - - - - - - - - - - \ No newline at end of file diff --git a/sf_sale/wizard/upload_file_upload.py b/sf_sale/wizard/upload_file_upload.py deleted file mode 100644 index 4d84caa6..00000000 --- a/sf_sale/wizard/upload_file_upload.py +++ /dev/null @@ -1,60 +0,0 @@ -from odoo import models, fields, api, _ - - -class IrAttachmentUpload(models.TransientModel): - _name = 'ir.attachment.upload' - _description = '文件上传向导' - - attachment = fields.Binary(string='选择文件', required=True) - filename = fields.Char(string='文件名') - res_model = fields.Char() - res_id = fields.Integer() - - def action_upload_file(self): - self.ensure_one() - # 获取当前用户的 partner_id - current_partner = self.env.user.partner_id - # 首先创建 ir.attachment - attachment = self.env['ir.attachment'].create({ - 'name': self.filename, - 'type': 'binary', - 'datas': self.attachment, - 'res_model': self.res_model, - 'res_id': self.res_id, - }) - - # 获取默认的文档文件夹 - workspace = self.env['purchase.requisition'].update_documents_folder('待审') - - # 创建 documents.document 记录 - document = self.env['documents.document'].create({ - 'name': self.filename, - 'attachment_id': attachment.id, - 'folder_id': workspace.id, - 'res_model': self.res_model, - 'res_id': self.res_id, - 'partner_id': current_partner.id, - }) - - # 更新采购订单的合同文档字段 - purchase_requisition = self.env['purchase.requisition'].browse(self.res_id) - purchase_requisition.write({ - 'contract_document_id': document.id, - 'is_upload_contract_file': True - }) - - # 显示成功消息并关闭向导 - message = { - 'type': 'ir.actions.client', - 'tag': 'display_notification', - 'params': { - 'title': _('成功'), - 'message': _('文件上传成功'), - 'type': 'success', - 'sticky': False, # 自动消失 - 'next': { - 'type': 'ir.actions.act_window_close' - } - } - } - return message diff --git a/sf_sale/wizard/upload_file_upload_view.xml b/sf_sale/wizard/upload_file_upload_view.xml deleted file mode 100644 index 54412063..00000000 --- a/sf_sale/wizard/upload_file_upload_view.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - ir.attachment.upload.form - ir.attachment.upload - - - - - - - - - - - - - \ No newline at end of file From a628826b52cc6e3210170414c79b75594de774ad Mon Sep 17 00:00:00 2001 From: guanhuan Date: Wed, 19 Feb 2025 11:34:58 +0800 Subject: [PATCH 22/27] =?UTF-8?q?=E9=87=87=E8=B4=AD=E5=8D=8F=E8=AE=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_sale/models/__init__.py | 1 + sf_sale/security/ir.model.access.csv | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/sf_sale/models/__init__.py b/sf_sale/models/__init__.py index 9f2eb4dc..97400237 100644 --- a/sf_sale/models/__init__.py +++ b/sf_sale/models/__init__.py @@ -4,3 +4,4 @@ from . import quick_easy_order_old from . import auto_quatotion_common from . import parser_and_calculate_work_time from . import preload_datas_functions + diff --git a/sf_sale/security/ir.model.access.csv b/sf_sale/security/ir.model.access.csv index 03f636e6..64194f9a 100644 --- a/sf_sale/security/ir.model.access.csv +++ b/sf_sale/security/ir.model.access.csv @@ -119,5 +119,5 @@ access_stock_scrap_group_sale_director,stock_scrap_group_sale_director,stock.mod access_account_move_group_plan_dispatch,account_move_group_plan_dispatch,account.model_account_move,sf_base.group_plan_dispatch,1,1,1,0 -access_ir_attachment_upload,ir.attachment.upload,model_ir_attachment_upload,base.group_user,1,1,1,1 + From 0969967570ed5afd73bf91943209e648f2514044 Mon Sep 17 00:00:00 2001 From: mgw <1392924357@qq.com> Date: Wed, 19 Feb 2025 15:14:05 +0800 Subject: [PATCH 23/27] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E5=8F=96=E6=B6=88=E8=AE=A2=E5=8D=95=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_bf_connect/models/process_status.py | 2 +- sf_manufacturing/views/sale_order_views.xml | 6 ++ sf_manufacturing/wizard/sale_order_cancel.py | 77 +++++++++++++++---- .../wizard/sale_order_cancel_views.xml | 2 +- 4 files changed, 70 insertions(+), 17 deletions(-) diff --git a/sf_bf_connect/models/process_status.py b/sf_bf_connect/models/process_status.py index 69eb84f1..497544dd 100644 --- a/sf_bf_connect/models/process_status.py +++ b/sf_bf_connect/models/process_status.py @@ -72,7 +72,7 @@ class StatusChange(models.Model): logging.info('函数已经执行=============') # 使用super()来调用原始方法(在本例中为'sale.order'模型的'action_cancel'方法) - res = super(StatusChange, self).action_cancel() + res = super(StatusChange, self.with_context(disable_cancel_warning=True)).action_cancel() # 原有方法执行后,进行额外的操作(如调用外部API) logging.info('函数已经执行=============2') diff --git a/sf_manufacturing/views/sale_order_views.xml b/sf_manufacturing/views/sale_order_views.xml index bfaca560..dbf7093f 100644 --- a/sf_manufacturing/views/sale_order_views.xml +++ b/sf_manufacturing/views/sale_order_views.xml @@ -40,6 +40,12 @@ type="object" attrs="{'invisible': [('state', 'not in', ['sale', 'processing'])]}" /> + diff --git a/sf_manufacturing/wizard/sale_order_cancel.py b/sf_manufacturing/wizard/sale_order_cancel.py index a5d28b7f..208e6ba3 100644 --- a/sf_manufacturing/wizard/sale_order_cancel.py +++ b/sf_manufacturing/wizard/sale_order_cancel.py @@ -25,12 +25,24 @@ class SFSaleOrderCancelWizard(models.TransientModel): @api.depends('related_docs.cancel_reason') def _compute_has_movement(self): for wizard in self: - wizard.has_movement = any(doc.cancel_reason for doc in wizard.related_docs) + docs_has_movement = any(doc.cancel_reason for doc in wizard.related_docs) + order_canceled = wizard.order_id.state == 'cancel' + wizard.has_movement = docs_has_movement or order_canceled - @api.depends('has_movement') + @api.depends('has_movement', 'related_docs', 'related_docs.doc_state') def _compute_display_message(self): for wizard in self: - wizard.display_message = '部分或全部下游单据存在异动,无法取消,详情如下:' if wizard.has_movement else '确认所有下游单据全部取消?' + # 如果没有相关记录,显示为空 + if not wizard.related_docs: + wizard.display_message = '无下游单据' + continue + + # 检查是否所有记录都是已取消状态 + all_canceled = all(doc.doc_state == '已取消' for doc in wizard.related_docs) + if all_canceled: + wizard.display_message = '取消的下游单据如下:' + else: + wizard.display_message = '部分或全部下游单据存在异动,无法取消,详情如下:' if wizard.has_movement else '确认所有下游单据全部取消?' def action_confirm_cancel(self): self.ensure_one() @@ -110,7 +122,7 @@ class SFSaleOrderCancelLine(models.TransientModel): 'confirmed': '已确认', 'pending': '等待其他工单', 'none': '待处理', - 'draft': '报价', + 'draft': '询价', 'cancel': '已取消', 'pass': '通过的', 'fail': '失败的', @@ -125,14 +137,17 @@ class SFSaleOrderCancelLine(models.TransientModel): # 检查销售订单 if order.invoice_ids: + a = 0 for invoice in order.invoice_ids: + a += 1 vals = { 'wizard_id': wizard_id, 'sequence': sequence, 'category': '销售', 'doc_name': '销售订单', - 'operation_type': '销售', + 'operation_type': '', 'doc_number': invoice.name, + 'line_number': a, 'product_name': invoice.product_id.name, 'quantity': invoice.quantity, 'doc_state': invoice.state, @@ -143,7 +158,9 @@ class SFSaleOrderCancelLine(models.TransientModel): # 检查交货单 if order.picking_ids: + b = 0 for picking in order.picking_ids: + b += 1 vals = { 'wizard_id': wizard_id, 'sequence': sequence, @@ -151,6 +168,7 @@ class SFSaleOrderCancelLine(models.TransientModel): 'doc_name': '交货单', 'operation_type': '调拨', 'doc_number': picking.name, + 'line_number': b, 'product_name': picking.product_id.name if picking.product_id else '', # 'quantity': picking.product_qty if hasattr(picking, 'product_qty') else 0, 'quantity': sum(picking.move_ids.mapped('product_uom_qty') or [0]), @@ -165,14 +183,17 @@ class SFSaleOrderCancelLine(models.TransientModel): ('origin', '=', order.name) ]) if purchase_orders: + c = 0 for po in purchase_orders: + c += 1 vals = { 'wizard_id': wizard_id, 'sequence': sequence, 'category': '采购', - 'doc_name': '采购单', - 'operation_type': '销售采购', + 'doc_name': '询价单', + 'operation_type': po.picking_type_id.name, 'doc_number': po.name, + 'line_number': c, 'product_name': po.order_line[0].product_id.name if po.order_line else '', 'quantity': po.order_line[0].product_qty if po.order_line else 0, 'doc_state': map_dict.get(po.state, po.state), @@ -185,15 +206,18 @@ class SFSaleOrderCancelLine(models.TransientModel): manufacturing_orders = self.env['mrp.production'].search([ ('origin', '=', order.name) ]) + d = 0 for mo in manufacturing_orders: # 添加制造订单本身 + d += 1 vals = { 'wizard_id': wizard_id, 'sequence': sequence, 'category': '制造', 'doc_name': '制造订单', 'doc_number': mo.name, - 'operation_type': '制造', + 'operation_type': '', + 'line_number': d, 'product_name': mo.product_id.name, 'quantity': mo.product_qty, 'doc_state': map_dict.get(mo.state, mo.state), @@ -207,14 +231,17 @@ class SFSaleOrderCancelLine(models.TransientModel): ('origin', '=', mo.name) ]) if purchase_orders: + e = 0 for po in purchase_orders: + e += 1 vals = { 'wizard_id': wizard_id, 'sequence': sequence, 'category': '制造', - 'doc_name': '采购单', + 'doc_name': '询价单', 'doc_number': po.name, - 'operation_type': '制造采购', + 'line_number': e, + 'operation_type': po.picking_type_id.name, 'product_name': po.order_line[0].product_id.name if po.order_line else '', 'quantity': po.order_line[0].product_qty if po.order_line else 0, 'doc_state': map_dict.get(po.state, po.state), @@ -225,13 +252,16 @@ class SFSaleOrderCancelLine(models.TransientModel): # 检查制造订单的领料单 if mo.picking_ids: + f = 0 for picking in mo.picking_ids: + f += 1 vals = { 'wizard_id': wizard_id, 'sequence': sequence, 'category': '制造', - 'doc_name': '领料单', + 'doc_name': '库存移动', 'doc_number': picking.name, + 'line_number': f, 'operation_type': picking.picking_type_id.name, 'product_name': picking.product_id.name if picking.product_id else '', 'quantity': sum(picking.move_ids.mapped('product_uom_qty') or [0]), @@ -243,13 +273,16 @@ class SFSaleOrderCancelLine(models.TransientModel): # 检查制造订单的工单 if mo.workorder_ids: + g = 0 for workorder in mo.workorder_ids: + g += 1 vals = { 'wizard_id': wizard_id, 'sequence': sequence, 'category': '制造', 'doc_name': '工单', 'doc_number': workorder.name, + 'line_number': g, 'operation_type': workorder.workcenter_id.name, 'product_name': mo.product_id.name, 'quantity': workorder.qty_production, @@ -288,14 +321,17 @@ class SFSaleOrderCancelLine(models.TransientModel): ('origin', '=', mo.name), ('product_id', '=', move.product_id.id) ]) + h = 0 for comp_mo in component_mos: + h += 1 vals = { 'wizard_id': wizard_id, 'sequence': sequence, 'category': '制造', 'doc_name': '组件制造单', - 'operation_type': '组件制造', + 'operation_type': '', 'doc_number': comp_mo.name, + 'line_number': h, 'product_name': move.product_id.name, 'quantity': comp_mo.product_qty, 'doc_state': map_dict.get(comp_mo.state, comp_mo.state), @@ -309,14 +345,17 @@ class SFSaleOrderCancelLine(models.TransientModel): ('production_id', '=', mo.id) ]) if quality_checks: + i = 0 for check in quality_checks: + i += 1 vals = { 'wizard_id': wizard_id, 'sequence': sequence, 'category': '制造', 'doc_name': '质检单', - 'operation_type': '质检', + 'operation_type': '', 'doc_number': check.name, + 'line_number': i, 'product_name': check.product_id.name, 'quantity': 1, 'doc_state': map_dict.get(check.quality_state, check.quality_state), @@ -333,8 +372,9 @@ class SFSaleOrderCancelLine(models.TransientModel): 'sequence': sequence, 'category': '编程', 'doc_name': '编程单', - 'operation_type': '编程', + 'operation_type': '', 'doc_number': cloud_programming['programming_no'], + 'line_number': 1, 'product_name': cloud_programming['production_order_no'], 'quantity': 1, 'doc_state': cloud_programming['programming_state'], @@ -343,4 +383,11 @@ class SFSaleOrderCancelLine(models.TransientModel): lines.append(self.create(vals)) sequence += 1 - return lines + unique_lines = {} + for line in lines: + doc_number = line.doc_number + if doc_number not in unique_lines: + unique_lines[doc_number] = line + + # 返回去重后的记录列表 + return list(unique_lines.values()) diff --git a/sf_manufacturing/wizard/sale_order_cancel_views.xml b/sf_manufacturing/wizard/sale_order_cancel_views.xml index 9758a8a9..6c07e31a 100644 --- a/sf_manufacturing/wizard/sale_order_cancel_views.xml +++ b/sf_manufacturing/wizard/sale_order_cancel_views.xml @@ -14,7 +14,7 @@ - + From 064af3b884345bc04557372722a6e1ffec9fbd2e Mon Sep 17 00:00:00 2001 From: mgw <1392924357@qq.com> Date: Wed, 19 Feb 2025 15:34:42 +0800 Subject: [PATCH 24/27] =?UTF-8?q?=E8=B0=83=E6=95=B4=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_manufacturing/wizard/sale_order_cancel.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sf_manufacturing/wizard/sale_order_cancel.py b/sf_manufacturing/wizard/sale_order_cancel.py index 208e6ba3..984c811b 100644 --- a/sf_manufacturing/wizard/sale_order_cancel.py +++ b/sf_manufacturing/wizard/sale_order_cancel.py @@ -132,7 +132,8 @@ class SFSaleOrderCancelLine(models.TransientModel): 'ready': '就绪', 'approved': '已批准', 'pending_cam': '待加工', - 'progress': '加工中' + 'progress': '加工中', + 'assigned': '就绪' } # 检查销售订单 From cddcec4225f0ed5a0e10b21d64c1277419a3b7b9 Mon Sep 17 00:00:00 2001 From: yuxianghui <3437689193@qq.com> Date: Wed, 19 Feb 2025 16:22:00 +0800 Subject: [PATCH 25/27] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=B0=83=E6=8B=A8?= =?UTF-8?q?=E5=8D=95=E7=9A=84=E8=B4=A8=E6=A3=80=E5=AE=8C=E6=88=90=E7=9A=84?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E9=80=9A=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_message/data/bussiness_node.xml | 5 +++++ sf_message/data/template_data.xml | 12 ++++++++++++ sf_message/models/sf_message_stock_picking.py | 12 +++++++++++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/sf_message/data/bussiness_node.xml b/sf_message/data/bussiness_node.xml index 4003df45..14115e27 100644 --- a/sf_message/data/bussiness_node.xml +++ b/sf_message/data/bussiness_node.xml @@ -68,6 +68,11 @@ stock.picking + + 调拨单质检完成提醒 + stock.picking + + 装夹预调工单逾期预警 mrp.workorder diff --git a/sf_message/data/template_data.xml b/sf_message/data/template_data.xml index c0e96d99..4d580127 100644 --- a/sf_message/data/template_data.xml +++ b/sf_message/data/template_data.xml @@ -252,6 +252,18 @@ ### 订单发货提醒: 单号:发料出库单[{{name}}]({{request_url}}) 事项:销售订单{{sale_order_name}}已全部产出并入库,请及时发货 + + + + 调拨单质检完成提醒 + + stock.picking + + markdown + normal + ### {{picking_type_name}}待处理提醒: +单号:{{name}} +事项:质量检查已完成 diff --git a/sf_message/models/sf_message_stock_picking.py b/sf_message/models/sf_message_stock_picking.py index a2c17407..10304cf3 100644 --- a/sf_message/models/sf_message_stock_picking.py +++ b/sf_message/models/sf_message_stock_picking.py @@ -9,6 +9,8 @@ class SFMessageStockPicking(models.Model): _description = "库存调拨" _inherit = ['stock.picking', 'jikimo.message.dispatch'] + quality_check_ids = fields.One2many('quality.check', 'picking_id', '质量检测单') + @api.model_create_multi def create(self, vals): result = super(SFMessageStockPicking, self).create(vals) @@ -20,7 +22,8 @@ class SFMessageStockPicking(models.Model): logging.info('add_queue调拨入库 error:%s' % e) return result - @api.depends('move_type', 'immediate_transfer', 'move_ids.state', 'move_ids.picking_id') + @api.depends('move_type', 'immediate_transfer', 'move_ids.state', 'move_ids.picking_id', + 'quality_check_ids.quality_state') def _compute_state(self): super(SFMessageStockPicking, self)._compute_state() try: @@ -48,6 +51,8 @@ class SFMessageStockPicking(models.Model): all_ready_or_done = all(picking.state in ['assigned', 'done'] for picking in stock_picking_list) if all_ready_or_done: mrp_production.add_queue('工序外协发料通知') + if all(qc.quality_state in ['pass', 'fail'] for qc in record.quality_check_ids): + record.add_queue('调拨单质检完成提醒') except Exception as e: logging.info('add_queue_compute_state error:%s' % e) @@ -83,6 +88,11 @@ class SFMessageStockPicking(models.Model): content = self.deal_stock_picking_sfp(message_queue_id) if content: contents.append(content) + elif message_queue_id.message_template_id.name == '调拨单质检完成提醒': + content = message_queue_id.message_template_id.content + content = content.replace('{{picking_type_name}}', self.picking_type_id.name).replace( + '{{name}}', self.name) + contents.append(content) return contents, message_queue_ids def get_special_url(self, id, tmplate_name, special_name, model_id): From 7ff1fed4b6b4a252727180a3cefdfaab5b45055c Mon Sep 17 00:00:00 2001 From: yuxianghui <3437689193@qq.com> Date: Thu, 20 Feb 2025 10:04:22 +0800 Subject: [PATCH 26/27] =?UTF-8?q?=E8=B0=83=E6=8B=A8=E5=8D=95=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E8=B0=83=E6=8B=A8=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E8=B0=83=E6=8B=A8=E5=8D=95=E7=9A=84=E8=B4=A8=E6=A3=80?= =?UTF-8?q?=E5=8D=95=E6=98=AF=E5=90=A6=E5=AE=8C=E6=88=90=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_warehouse/models/model.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sf_warehouse/models/model.py b/sf_warehouse/models/model.py index 582d7a59..581ef114 100644 --- a/sf_warehouse/models/model.py +++ b/sf_warehouse/models/model.py @@ -951,6 +951,12 @@ class SfStockPicking(models.Model): """ 批量调拨,非就绪状态的会被忽略,完成后有通知提示 """ + # 对所以调拨单的质检单进行是否完成校验 + sp_ids = [sp.id for sp in self] + qc_ids = self.env['quality.check'].sudo().search( + [('picking_id', 'in', sp_ids), ('quality_state', 'in', ['waiting', 'none'])]) + if qc_ids: + raise ValidationError(f'单据{[qc.picking_id.name for qc in qc_ids]}未完成质量检查,完成后再试。') for record in self: if record.state != 'assigned': continue From e6ab17ca0df86c2ca488767606fedc08d83cb9fa Mon Sep 17 00:00:00 2001 From: hyyy <123@qq.com> Date: Thu, 20 Feb 2025 11:34:19 +0800 Subject: [PATCH 27/27] =?UTF-8?q?sf-=E5=88=B6=E9=80=A0-=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=88=80=E5=85=B7=E6=A0=87=E5=87=86=E5=BA=93=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sf_base/static/src/scss/format_img.scss | 14 ++++++++++++++ sf_base/views/tool_views.xml | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/sf_base/static/src/scss/format_img.scss b/sf_base/static/src/scss/format_img.scss index fc6f02dc..940e3a68 100644 --- a/sf_base/static/src/scss/format_img.scss +++ b/sf_base/static/src/scss/format_img.scss @@ -24,4 +24,18 @@ .o_search_panel.account_root { flex: unset !important; +} + +.multi-line { + display: flex; + flex-wrap: nowrap; + > label.o_form_label { + width: 52px; + } + > span { + flex: 1; + } + > div { + flex: 2 + } } \ No newline at end of file diff --git a/sf_base/views/tool_views.xml b/sf_base/views/tool_views.xml index e9d672e5..6ce80937 100644 --- a/sf_base/views/tool_views.xml +++ b/sf_base/views/tool_views.xml @@ -138,7 +138,7 @@ -