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 1/5] =?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 3ac096e9a797ba5920a45e1a626021d01b894ffb Mon Sep 17 00:00:00 2001 From: mgw <1392924357@qq.com> Date: Thu, 13 Feb 2025 17:41:18 +0800 Subject: [PATCH 2/5] =?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 3/5] =?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 3e9b6f808daa9f2075f9db4b92d48fe4490712c5 Mon Sep 17 00:00:00 2001 From: mgw <1392924357@qq.com> Date: Mon, 17 Feb 2025 10:00:40 +0800 Subject: [PATCH 4/5] =?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 567ea84b00ee27ce860faaa54005a5309f70d12d Mon Sep 17 00:00:00 2001 From: mgw <1392924357@qq.com> Date: Mon, 17 Feb 2025 15:37:30 +0800 Subject: [PATCH 5/5] =?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("零件特采发送失败") -