summaryrefslogtreecommitdiff
path: root/addons/mrp/wizard
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/mrp/wizard
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/mrp/wizard')
-rw-r--r--addons/mrp/wizard/__init__.py8
-rw-r--r--addons/mrp/wizard/change_production_qty.py103
-rw-r--r--addons/mrp/wizard/change_production_qty_views.xml33
-rw-r--r--addons/mrp/wizard/mrp_consumption_warning.py60
-rw-r--r--addons/mrp/wizard/mrp_consumption_warning_views.xml57
-rw-r--r--addons/mrp/wizard/mrp_immediate_production.py82
-rw-r--r--addons/mrp/wizard/mrp_immediate_production_views.xml27
-rw-r--r--addons/mrp/wizard/mrp_production_backorder.py38
-rw-r--r--addons/mrp/wizard/mrp_production_backorder.xml44
-rw-r--r--addons/mrp/wizard/mrp_workcenter_block_view.xml41
-rw-r--r--addons/mrp/wizard/stock_warn_insufficient_qty.py19
-rw-r--r--addons/mrp/wizard/stock_warn_insufficient_qty_views.xml13
12 files changed, 525 insertions, 0 deletions
diff --git a/addons/mrp/wizard/__init__.py b/addons/mrp/wizard/__init__.py
new file mode 100644
index 00000000..d90ee8f5
--- /dev/null
+++ b/addons/mrp/wizard/__init__.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import change_production_qty
+from . import stock_warn_insufficient_qty
+from . import mrp_production_backorder
+from . import mrp_consumption_warning
+from . import mrp_immediate_production
diff --git a/addons/mrp/wizard/change_production_qty.py b/addons/mrp/wizard/change_production_qty.py
new file mode 100644
index 00000000..4a58bc7b
--- /dev/null
+++ b/addons/mrp/wizard/change_production_qty.py
@@ -0,0 +1,103 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models, _
+from odoo.exceptions import UserError
+from odoo.tools import float_is_zero, float_round
+
+
+class ChangeProductionQty(models.TransientModel):
+ _name = 'change.production.qty'
+ _description = 'Change Production Qty'
+
+ mo_id = fields.Many2one('mrp.production', 'Manufacturing Order',
+ required=True, ondelete='cascade')
+ product_qty = fields.Float(
+ 'Quantity To Produce',
+ digits='Product Unit of Measure', required=True)
+
+ @api.model
+ def default_get(self, fields):
+ res = super(ChangeProductionQty, self).default_get(fields)
+ if 'mo_id' in fields and not res.get('mo_id') and self._context.get('active_model') == 'mrp.production' and self._context.get('active_id'):
+ res['mo_id'] = self._context['active_id']
+ if 'product_qty' in fields and not res.get('product_qty') and res.get('mo_id'):
+ res['product_qty'] = self.env['mrp.production'].browse(res['mo_id']).product_qty
+ return res
+
+ @api.model
+ def _update_finished_moves(self, production, new_qty, old_qty):
+ """ Update finished product and its byproducts. This method only update
+ the finished moves not done or cancel and just increase or decrease
+ their quantity according the unit_ratio. It does not use the BoM, BoM
+ modification during production would not be taken into consideration.
+ """
+ modification = {}
+ for move in production.move_finished_ids:
+ if move.state in ('done', 'cancel'):
+ continue
+ qty = (new_qty - old_qty) * move.unit_factor
+ modification[move] = (move.product_uom_qty + qty, move.product_uom_qty)
+ move.write({'product_uom_qty': move.product_uom_qty + qty})
+ return modification
+
+ def change_prod_qty(self):
+ precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
+ for wizard in self:
+ production = wizard.mo_id
+ produced = sum(production.move_finished_ids.filtered(lambda m: m.product_id == production.product_id).mapped('quantity_done'))
+ if wizard.product_qty < produced:
+ format_qty = '%.{precision}f'.format(precision=precision)
+ raise UserError(_(
+ "You have already processed %(quantity)s. Please input a quantity higher than %(minimum)s ",
+ quantity=format_qty % produced,
+ minimum=format_qty % produced
+ ))
+ old_production_qty = production.product_qty
+ new_production_qty = wizard.product_qty
+ done_moves = production.move_finished_ids.filtered(lambda x: x.state == 'done' and x.product_id == production.product_id)
+ qty_produced = production.product_id.uom_id._compute_quantity(sum(done_moves.mapped('product_qty')), production.product_uom_id)
+
+ factor = (new_production_qty - qty_produced) / (old_production_qty - qty_produced)
+ update_info = production._update_raw_moves(factor)
+ documents = {}
+ for move, old_qty, new_qty in update_info:
+ iterate_key = production._get_document_iterate_key(move)
+ if iterate_key:
+ document = self.env['stock.picking']._log_activity_get_documents({move: (new_qty, old_qty)}, iterate_key, 'UP')
+ for key, value in document.items():
+ if documents.get(key):
+ documents[key] += [value]
+ else:
+ documents[key] = [value]
+ production._log_manufacture_exception(documents)
+ finished_moves_modification = self._update_finished_moves(production, new_production_qty - qty_produced, old_production_qty - qty_produced)
+ if finished_moves_modification:
+ production._log_downside_manufactured_quantity(finished_moves_modification)
+ production.write({'product_qty': new_production_qty})
+
+ for wo in production.workorder_ids:
+ operation = wo.operation_id
+ wo.duration_expected = wo._get_duration_expected(ratio=new_production_qty / old_production_qty)
+ quantity = wo.qty_production - wo.qty_produced
+ if production.product_id.tracking == 'serial':
+ quantity = 1.0 if not float_is_zero(quantity, precision_digits=precision) else 0.0
+ else:
+ quantity = quantity if (quantity > 0 and not float_is_zero(quantity, precision_digits=precision)) else 0
+ wo._update_qty_producing(quantity)
+ if wo.qty_produced < wo.qty_production and wo.state == 'done':
+ wo.state = 'progress'
+ if wo.qty_produced == wo.qty_production and wo.state == 'progress':
+ wo.state = 'done'
+ if wo.next_work_order_id.state == 'pending':
+ wo.next_work_order_id.state = 'ready'
+ # assign moves; last operation receive all unassigned moves
+ # TODO: following could be put in a function as it is similar as code in _workorders_create
+ # TODO: only needed when creating new moves
+ moves_raw = production.move_raw_ids.filtered(lambda move: move.operation_id == operation and move.state not in ('done', 'cancel'))
+ if wo == production.workorder_ids[-1]:
+ moves_raw |= production.move_raw_ids.filtered(lambda move: not move.operation_id)
+ moves_finished = production.move_finished_ids.filtered(lambda move: move.operation_id == operation) #TODO: code does nothing, unless maybe by_products?
+ moves_raw.mapped('move_line_ids').write({'workorder_id': wo.id})
+ (moves_finished + moves_raw).write({'workorder_id': wo.id})
+ return {}
diff --git a/addons/mrp/wizard/change_production_qty_views.xml b/addons/mrp/wizard/change_production_qty_views.xml
new file mode 100644
index 00000000..8d5004d7
--- /dev/null
+++ b/addons/mrp/wizard/change_production_qty_views.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <data>
+
+ <!-- Change Product Quantity -->
+ <record id="view_change_production_qty_wizard" model="ir.ui.view">
+ <field name="name">Change Quantity To Produce</field>
+ <field name="model">change.production.qty</field>
+ <field name="arch" type="xml">
+ <form string="Change Product Qty">
+ <group>
+ <field name="product_qty"/>
+ <field name="mo_id" invisible="1"/>
+ </group>
+ <footer>
+ <button name="change_prod_qty" string="Approve"
+ colspan="1" type="object" class="btn-primary"/>
+ <button string="Cancel" class="btn-secondary" special="cancel" />
+ </footer>
+ </form>
+ </field>
+ </record>
+
+ <record id="action_change_production_qty" model="ir.actions.act_window">
+ <field name="name">Change Quantity To Produce</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">change.production.qty</field>
+ <field name="view_mode">form</field>
+ <field name="target">new</field>
+ </record>
+
+ </data>
+</odoo>
diff --git a/addons/mrp/wizard/mrp_consumption_warning.py b/addons/mrp/wizard/mrp_consumption_warning.py
new file mode 100644
index 00000000..bae87759
--- /dev/null
+++ b/addons/mrp/wizard/mrp_consumption_warning.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import fields, models, api
+
+
+class MrpConsumptionWarning(models.TransientModel):
+ _name = 'mrp.consumption.warning'
+ _description = "Wizard in case of consumption in warning/strict and more component has been used for a MO (related to the bom)"
+
+ mrp_production_ids = fields.Many2many('mrp.production')
+ mrp_production_count = fields.Integer(compute="_compute_mrp_production_count")
+
+ consumption = fields.Selection([
+ ('flexible', 'Allowed'),
+ ('warning', 'Allowed with warning'),
+ ('strict', 'Blocked')], compute="_compute_consumption")
+ mrp_consumption_warning_line_ids = fields.One2many('mrp.consumption.warning.line', 'mrp_consumption_warning_id')
+
+ @api.depends("mrp_production_ids")
+ def _compute_mrp_production_count(self):
+ for wizard in self:
+ wizard.mrp_production_count = len(wizard.mrp_production_ids)
+
+ @api.depends("mrp_consumption_warning_line_ids.consumption")
+ def _compute_consumption(self):
+ for wizard in self:
+ consumption_map = set(wizard.mrp_consumption_warning_line_ids.mapped("consumption"))
+ wizard.consumption = "strict" in consumption_map and "strict" or "warning" in consumption_map and "warning" or "flexible"
+
+ def action_confirm(self):
+ action_from_do_finish = False
+ if self.env.context.get('from_workorder'):
+ if self.env.context.get('active_model') == 'mrp.workorder':
+ action_from_do_finish = self.env['mrp.workorder'].browse(self.env.context.get('active_id')).do_finish()
+ action_from_mark_done = self.mrp_production_ids.with_context(skip_consumption=True).button_mark_done()
+ return action_from_do_finish or action_from_mark_done
+
+ def action_cancel(self):
+ if self.env.context.get('from_workorder') and len(self.mrp_production_ids) == 1:
+ return {
+ 'type': 'ir.actions.act_window',
+ 'res_model': 'mrp.production',
+ 'views': [[self.env.ref('mrp.mrp_production_form_view').id, 'form']],
+ 'res_id': self.mrp_production_ids.id,
+ 'target': 'main',
+ }
+
+class MrpConsumptionWarningLine(models.TransientModel):
+ _name = 'mrp.consumption.warning.line'
+ _description = "Line of issue consumption"
+
+ mrp_consumption_warning_id = fields.Many2one('mrp.consumption.warning', "Parent Wizard", readonly=True, required=True, ondelete="cascade")
+ mrp_production_id = fields.Many2one('mrp.production', "Manufacturing Order", readonly=True, required=True, ondelete="cascade")
+ consumption = fields.Selection(related="mrp_production_id.consumption")
+
+ product_id = fields.Many2one('product.product', "Product", readonly=True, required=True)
+ product_uom_id = fields.Many2one('uom.uom', "Unit of Measure", related="product_id.uom_id", readonly=True)
+ product_consumed_qty_uom = fields.Float("Consumed", readonly=True)
+ product_expected_qty_uom = fields.Float("To Consume", readonly=True)
diff --git a/addons/mrp/wizard/mrp_consumption_warning_views.xml b/addons/mrp/wizard/mrp_consumption_warning_views.xml
new file mode 100644
index 00000000..a09d66db
--- /dev/null
+++ b/addons/mrp/wizard/mrp_consumption_warning_views.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <data>
+
+ <!-- MO Consumption Warning -->
+ <record id="view_mrp_consumption_warning_form" model="ir.ui.view">
+ <field name="name">Consumption Warning</field>
+ <field name="model">mrp.consumption.warning</field>
+ <field name="arch" type="xml">
+ <form string="Consumption Warning">
+ <field name="mrp_production_ids" invisible="1"/>
+ <field name="consumption" invisible="1"/>
+ <field name="mrp_production_count" invisible="1"/>
+ <div class="m-2">
+ You consumed a different quantity than expected for the following products.
+ <b attrs="{'invisible': [('consumption', '=', 'strict')]}">
+ Please confirm it has been done on purpose.
+ </b>
+ <b attrs="{'invisible': [('consumption', '!=', 'strict')]}">
+ Please review your component consumption or ask a manager to validate
+ <span attrs="{'invisible':[('mrp_production_count', '!=', 1)]}">this manufacturing order</span>
+ <span attrs="{'invisible':[('mrp_production_count', '=', 1)]}">these manufacturing orders</span>.
+ </b>
+ </div>
+ <field name="mrp_consumption_warning_line_ids" nolabel="1">
+ <tree create="0" delete="0" editable="top">
+ <field name="mrp_production_id" attrs="{'column_invisible':[('parent.mrp_production_count', '=', 1)]}" force_save="1"/>
+ <field name="consumption" invisible="1" force_save="1"/>
+ <field name="product_id" force_save="1"/>
+ <field name="product_uom_id" groups="uom.group_uom" force_save="1"/>
+ <field name="product_expected_qty_uom" force_save="1"/>
+ <field name="product_consumed_qty_uom" force_save="1"/>
+ </tree>
+ </field>
+ <footer>
+ <button name="action_confirm" string="Force"
+ groups="mrp.group_mrp_manager" attrs="{'invisible': [('consumption', '!=', 'strict')]}"
+ colspan="1" type="object" class="btn-primary"/>
+ <button name="action_confirm" string="Confirm" attrs="{'invisible': [('consumption', '=', 'strict')]}"
+ colspan="1" type="object" class="btn-primary"/>
+ <button name="action_cancel" string="Review Consumption"
+ colspan="1" type="object" class="btn-primary"/>
+ </footer>
+ </form>
+ </field>
+ </record>
+
+ <record id="action_mrp_consumption_warning" model="ir.actions.act_window">
+ <field name="name">Consumption Warning</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">mrp.consumption.warning</field>
+ <field name="view_mode">form</field>
+ <field name="target">new</field>
+ </record>
+
+ </data>
+</odoo>
diff --git a/addons/mrp/wizard/mrp_immediate_production.py b/addons/mrp/wizard/mrp_immediate_production.py
new file mode 100644
index 00000000..d1e1b27d
--- /dev/null
+++ b/addons/mrp/wizard/mrp_immediate_production.py
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import _, api, fields, models
+from odoo.exceptions import UserError
+from odoo.tools import float_compare
+
+
+class MrpImmediateProductionLine(models.TransientModel):
+ _name = 'mrp.immediate.production.line'
+ _description = 'Immediate Production Line'
+
+ immediate_production_id = fields.Many2one('mrp.immediate.production', 'Immediate Production', required=True)
+ production_id = fields.Many2one('mrp.production', 'Production', required=True)
+ to_immediate = fields.Boolean('To Process')
+
+
+class MrpImmediateProduction(models.TransientModel):
+ _name = 'mrp.immediate.production'
+ _description = 'Immediate Production'
+
+ @api.model
+ def default_get(self, fields):
+ res = super().default_get(fields)
+ if 'immediate_production_line_ids' in fields:
+ if self.env.context.get('default_mo_ids'):
+ res['mo_ids'] = self.env.context['default_mo_ids']
+ res['immediate_production_line_ids'] = [(0, 0, {'to_immediate': True, 'production_id': mo_id[1]}) for mo_id in res['mo_ids']]
+ return res
+
+ mo_ids = fields.Many2many('mrp.production', 'mrp_production_production_rel')
+ show_productions = fields.Boolean(compute='_compute_show_production')
+ immediate_production_line_ids = fields.One2many(
+ 'mrp.immediate.production.line',
+ 'immediate_production_id',
+ string="Immediate Production Lines")
+
+ @api.depends('immediate_production_line_ids')
+ def _compute_show_production(self):
+ for wizard in self:
+ wizard.show_productions = len(wizard.immediate_production_line_ids.production_id) > 1
+
+ def process(self):
+ productions_to_do = self.env['mrp.production']
+ productions_not_to_do = self.env['mrp.production']
+ for line in self.immediate_production_line_ids:
+ if line.to_immediate is True:
+ productions_to_do |= line.production_id
+ else:
+ productions_not_to_do |= line.production_id
+
+ for production in productions_to_do:
+ error_msg = ""
+ if production.product_tracking in ('lot', 'serial') and not production.lot_producing_id:
+ production.action_generate_serial()
+ if production.product_tracking == 'serial' and float_compare(production.qty_producing, 1, precision_rounding=production.product_uom_id.rounding) == 1:
+ production.qty_producing = 1
+ else:
+ production.qty_producing = production.product_qty - production.qty_produced
+ production._set_qty_producing()
+ for move in production.move_raw_ids.filtered(lambda m: m.state not in ['done', 'cancel']):
+ rounding = move.product_uom.rounding
+ for move_line in move.move_line_ids:
+ if move_line.product_uom_qty:
+ move_line.qty_done = min(move_line.product_uom_qty, move_line.move_id.should_consume_qty)
+ if float_compare(move.quantity_done, move.should_consume_qty, precision_rounding=rounding) >= 0:
+ break
+ if float_compare(move.product_uom_qty, move.quantity_done, precision_rounding=move.product_uom.rounding) == 1:
+ if move.has_tracking in ('serial', 'lot'):
+ error_msg += "\n - %s" % move.product_id.display_name
+
+ if error_msg:
+ error_msg = _('You need to supply Lot/Serial Number for products:') + error_msg
+ raise UserError(error_msg)
+
+ productions_to_validate = self.env.context.get('button_mark_done_production_ids')
+ if productions_to_validate:
+ productions_to_validate = self.env['mrp.production'].browse(productions_to_validate)
+ productions_to_validate = productions_to_validate - productions_not_to_do
+ return productions_to_validate.with_context(skip_immediate=True).button_mark_done()
+ return True
+
diff --git a/addons/mrp/wizard/mrp_immediate_production_views.xml b/addons/mrp/wizard/mrp_immediate_production_views.xml
new file mode 100644
index 00000000..9c98f67c
--- /dev/null
+++ b/addons/mrp/wizard/mrp_immediate_production_views.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<odoo>
+ <record id="view_immediate_production" model="ir.ui.view">
+ <field name="name">mrp.immediate.production.view.form</field>
+ <field name="model">mrp.immediate.production</field>
+ <field name="arch" type="xml">
+ <form string="Immediate production?">
+ <group>
+ <p>You have not recorded <i>produced</i> quantities yet, by clicking on <i>apply</i> Odoo will produce all the finished products and consume all components.</p>
+ </group>
+
+ <field name="show_productions" invisible="1"/>
+ <field name="immediate_production_line_ids" nolabel="1" attrs="{'invisible': [('show_productions', '=', False)]}">>
+ <tree create="0" delete="0" editable="top">
+ <field name="production_id"/>
+ <field name="to_immediate" widget="boolean_toggle"/>
+ </tree>
+ </field>
+
+ <footer>
+ <button name="process" string="Apply" type="object" class="btn-primary"/>
+ <button string="Cancel" class="btn-secondary" special="cancel" />
+ </footer>
+ </form>
+ </field>
+ </record>
+</odoo>
diff --git a/addons/mrp/wizard/mrp_production_backorder.py b/addons/mrp/wizard/mrp_production_backorder.py
new file mode 100644
index 00000000..681b3fff
--- /dev/null
+++ b/addons/mrp/wizard/mrp_production_backorder.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models
+
+
+class MrpProductionBackorderLine(models.TransientModel):
+ _name = 'mrp.production.backorder.line'
+ _description = "Backorder Confirmation Line"
+
+ mrp_production_backorder_id = fields.Many2one('mrp.production.backorder', 'MO Backorder', required=True, ondelete="cascade")
+ mrp_production_id = fields.Many2one('mrp.production', 'Manufacturing Order', required=True, ondelete="cascade", readonly=True)
+ to_backorder = fields.Boolean('To Backorder')
+
+
+class MrpProductionBackorder(models.TransientModel):
+ _name = 'mrp.production.backorder'
+ _description = "Wizard to mark as done or create back order"
+
+ mrp_production_ids = fields.Many2many('mrp.production')
+
+ mrp_production_backorder_line_ids = fields.One2many(
+ 'mrp.production.backorder.line',
+ 'mrp_production_backorder_id',
+ string="Backorder Confirmation Lines")
+ show_backorder_lines = fields.Boolean("Show backorder lines", compute="_compute_show_backorder_lines")
+
+ @api.depends('mrp_production_backorder_line_ids')
+ def _compute_show_backorder_lines(self):
+ for wizard in self:
+ wizard.show_backorder_lines = len(wizard.mrp_production_backorder_line_ids) > 1
+
+ def action_close_mo(self):
+ return self.mrp_production_ids.with_context(skip_backorder=True).button_mark_done()
+
+ def action_backorder(self):
+ mo_ids_to_backorder = self.mrp_production_backorder_line_ids.filtered(lambda l: l.to_backorder).mrp_production_id.ids
+ return self.mrp_production_ids.with_context(skip_backorder=True, mo_ids_to_backorder=mo_ids_to_backorder).button_mark_done()
diff --git a/addons/mrp/wizard/mrp_production_backorder.xml b/addons/mrp/wizard/mrp_production_backorder.xml
new file mode 100644
index 00000000..7b116eb3
--- /dev/null
+++ b/addons/mrp/wizard/mrp_production_backorder.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <data>
+
+ <!-- MO Backorder -->
+ <record id="view_mrp_production_backorder_form" model="ir.ui.view">
+ <field name="name">Create Backorder</field>
+ <field name="model">mrp.production.backorder</field>
+ <field name="arch" type="xml">
+ <form string="Create a Backorder">
+ <group>
+ <p>
+ Create a backorder if you expect to process the remaining products later. Do not create a backorder if you will not process the remaining products.
+ </p>
+ </group>
+ <field name="show_backorder_lines" invisible="1"/>
+ <field name="mrp_production_backorder_line_ids" nolabel="1" attrs="{'invisible': [('show_backorder_lines', '=', False)]}">
+ <tree create="0" delete="0" editable="top">
+ <field name="mrp_production_id" force_save="1"/>
+ <field name="to_backorder" widget="boolean_toggle"/>
+ </tree>
+ </field>
+ <footer>
+ <button name="action_backorder" string="Create backorder"
+ colspan="1" type="object" class="btn-primary" attrs="{'invisible': [('show_backorder_lines', '!=', False)]}"/>
+ <button name="action_backorder" string="Validate"
+ colspan="1" type="object" class="btn-primary" attrs="{'invisible': [('show_backorder_lines', '=', False)]}"/>
+ <button name="action_close_mo" type="object" string="No Backorder" attrs="{'invisible': [('show_backorder_lines', '!=', False)]}"/>
+ <button string="Discard" class="btn-secondary" special="cancel" />
+ </footer>
+ </form>
+ </field>
+ </record>
+
+ <record id="action_mrp_production_backorder" model="ir.actions.act_window">
+ <field name="name">You produced less than initial demand</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">mrp.production.backorder</field>
+ <field name="view_mode">form</field>
+ <field name="target">new</field>
+ </record>
+
+ </data>
+</odoo>
diff --git a/addons/mrp/wizard/mrp_workcenter_block_view.xml b/addons/mrp/wizard/mrp_workcenter_block_view.xml
new file mode 100644
index 00000000..8104b8ea
--- /dev/null
+++ b/addons/mrp/wizard/mrp_workcenter_block_view.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <!-- Workcenter Block Dialog -->
+ <record id="mrp_workcenter_block_wizard_form" model="ir.ui.view">
+ <field name="name">mrp.workcenter.productivity.form</field>
+ <field name="model">mrp.workcenter.productivity</field>
+ <field name="arch" type="xml">
+ <form string="Block Workcenter">
+ <group>
+ <field name="loss_id" class="oe_inline" domain="[('manual','=',True)]"/>
+ <field name="description" placeholder="Add a description..."/>
+ <field name="workcenter_id" invisible="1"/>
+ <field name="company_id" invisible="1"/>
+ </group>
+ <footer>
+ <button name="button_block" string="Block" type="object" class="btn-danger text-uppercase"/>
+ <button string="Cancel" class="btn-secondary" special="cancel" />
+ </footer>
+ </form>
+ </field>
+ </record>
+
+ <record id="act_mrp_block_workcenter" model="ir.actions.act_window">
+ <field name="name">Block Workcenter</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">mrp.workcenter.productivity</field>
+ <field name="view_mode">form</field>
+ <field name="context">{'default_workcenter_id': active_id}</field>
+ <field name="view_id" eval="mrp_workcenter_block_wizard_form"/>
+ <field name="target">new</field>
+ </record>
+
+ <record id="act_mrp_block_workcenter_wo" model="ir.actions.act_window">
+ <field name="name">Block Workcenter</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">mrp.workcenter.productivity</field>
+ <field name="view_mode">form</field>
+ <field name="view_id" eval="mrp_workcenter_block_wizard_form"/>
+ <field name="target">new</field>
+ </record>
+</odoo>
diff --git a/addons/mrp/wizard/stock_warn_insufficient_qty.py b/addons/mrp/wizard/stock_warn_insufficient_qty.py
new file mode 100644
index 00000000..335c5424
--- /dev/null
+++ b/addons/mrp/wizard/stock_warn_insufficient_qty.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models
+
+
+class StockWarnInsufficientQtyUnbuild(models.TransientModel):
+ _name = 'stock.warn.insufficient.qty.unbuild'
+ _inherit = 'stock.warn.insufficient.qty'
+ _description = 'Warn Insufficient Unbuild Quantity'
+
+ unbuild_id = fields.Many2one('mrp.unbuild', 'Unbuild')
+
+ def _get_reference_document_company_id(self):
+ return self.unbuild_id.company_id
+
+ def action_done(self):
+ self.ensure_one()
+ return self.unbuild_id.action_unbuild()
diff --git a/addons/mrp/wizard/stock_warn_insufficient_qty_views.xml b/addons/mrp/wizard/stock_warn_insufficient_qty_views.xml
new file mode 100644
index 00000000..63df4691
--- /dev/null
+++ b/addons/mrp/wizard/stock_warn_insufficient_qty_views.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <record id="stock_warn_insufficient_qty_unbuild_form_view" model="ir.ui.view">
+ <field name="name">stock.warn.insufficient.qty.unbuild</field>
+ <field name="model">stock.warn.insufficient.qty.unbuild</field>
+ <field name="inherit_id" ref="stock.stock_warn_insufficient_qty_form_view"/>
+ <field name="arch" type="xml">
+ <xpath expr="//div[@name='description']" position="inside">
+ Do you confirm you want to unbuild <strong><field name="quantity" readonly="True"/></strong><field name="product_uom_name" readonly="True" class="mx-1"/>from location <strong><field name="location_id" readonly="True"/></strong>? This may lead to inconsistencies in your inventory.
+ </xpath>
+ </field>
+ </record>
+</odoo>