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/mrp_production.py b/sf_manufacturing/models/mrp_production.py
index e7d7648d..8bec3e65 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)])
@@ -1705,7 +1727,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/models/sale_order.py b/sf_manufacturing/models/sale_order.py
index c22a26f5..9f8fbd19 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 ceaa09ab..bfaca560 100644
--- a/sf_manufacturing/views/sale_order_views.xml
+++ b/sf_manufacturing/views/sale_order_views.xml
@@ -22,15 +22,25 @@
+
+
+
- {'invisible': [('state', '!=', 'draft')]}
-
-
- {'invisible': [('state', '!=', 'draft')]}
+ {'invisible': [('state', 'not in', ['draft', 'supply method'])]}
+ 警告:取消操作将不可逆,是否确定要取消该单据?
{'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..f50ff306
--- /dev/null
+++ b/sf_manufacturing/wizard/sale_order_cancel.py
@@ -0,0 +1,328 @@
+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):
+ 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
+
+ @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()
+
+ # 取消销售订单关联的采购单
+ 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)
+ ])
+ 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'
+ _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 = []
+ map_dict = {
+ 'waiting': '等待其他作业',
+ 'to approve': '待批准',
+ 'technology_to_confirmed': '待工艺确认',
+ 'confirmed': '已确认',
+ 'pending': '等待其他工单',
+ 'none': '待处理',
+ 'draft': '报价',
+ 'cancel': '已取消'
+ }
+
+ # 检查销售订单
+ 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,
+ 'quantity': sum(picking.move_ids.mapped('product_uom_qty') or [0]),
+ '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))
+ 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': map_dict.get(po.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': '制造订单',
+ 'doc_number': mo.name,
+ 'operation_type': '制造',
+ 'product_name': mo.product_id.name,
+ 'quantity': mo.product_qty,
+ '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
+
+ # 检查制造订单关联的采购单
+ 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': map_dict.get(po.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': sum(picking.move_ids.mapped('product_uom_qty') or [0]),
+ '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))
+ 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': 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_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': 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))
+ 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': '质检单',
+ 'operation_type': '质检',
+ 'doc_number': check.name,
+ '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 ''
+ }
+ 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_manufacturing/wizard/sale_order_cancel_views.xml b/sf_manufacturing/wizard/sale_order_cancel_views.xml
new file mode 100644
index 00000000..9758a8a9
--- /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
+
+
+
+
+
\ No newline at end of file
diff --git a/sf_quality/models/quality.py b/sf_quality/models/quality.py
index 80fe2f46..6bfdf4f1 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')
@@ -122,4 +123,3 @@ class QualityCheck(models.Model):
return "零件特采发送成功"
else:
raise ValidationError("零件特采发送失败")
-