[IMP] mrp: improve UX

Several UX improvements:

1 - Add "Unbuild" filter to MOs (1 or more 'Done' unbuilds) + added
  Unbuilds smart button to MO form view
2 - When copying an operation across BoMs, it is annoying that it adds
  "Copy" to the end of the name, so we remove that.
3 - Display a warning when using an expired Lot/SN in detailed operations
  view (burger window + tab).
4 - Allow adding/removing WO Real Duration + add/remove/edit specific time
  time tracking (time_ids) for Done, Unlocked MOs.
5 - Allow search WOs by product to manufacture
7 - Fix validation error message typo

Parts 1-5 and 7 of Task: 2667151

Part-of: odoo/odoo#80319
This commit is contained in:
Tiffany Chang (tic)
2022-01-21 07:57:49 +00:00
parent 583b48cedd
commit b935db1e70
7 changed files with 45 additions and 3 deletions
+18
View File
@@ -6,6 +6,7 @@ import datetime
import math
import re
from ast import literal_eval
from collections import defaultdict
from dateutil.relativedelta import relativedelta
@@ -235,6 +236,8 @@ class MrpProduction(models.Model):
json_popover = fields.Char('JSON data for the popover widget', compute='_compute_json_popover')
scrap_ids = fields.One2many('stock.scrap', 'production_id', 'Scraps')
scrap_count = fields.Integer(compute='_compute_scrap_move_count', string='Scrap Move')
unbuild_ids = fields.One2many('mrp.unbuild', 'mo_id', 'Unbuilds')
unbuild_count = fields.Integer(compute='_compute_unbuild_count', string='Number of Unbuilds')
is_locked = fields.Boolean('Is Locked', default=_get_default_is_locked, copy=False)
is_planned = fields.Boolean('Its Operations are Planned', compute="_compute_is_planned", store=True)
@@ -513,6 +516,11 @@ class MrpProduction(models.Model):
for production in self:
production.scrap_count = count_data.get(production.id, 0)
@api.depends('unbuild_ids')
def _compute_unbuild_count(self):
for production in self:
production.unbuild_count = len(production.unbuild_ids)
@api.depends('move_finished_ids')
def _compute_move_byproduct_ids(self):
for order in self:
@@ -1818,6 +1826,16 @@ class MrpProduction(models.Model):
def action_view_reception_report(self):
return self.env["ir.actions.actions"]._for_xml_id("mrp.mrp_reception_action")
def action_view_mrp_production_unbuilds(self):
self.ensure_one()
action = self.env["ir.actions.actions"]._for_xml_id("mrp.mrp_unbuild")
action['domain'] = [('mo_id', '=', self.id)]
context = literal_eval(action['context'])
context.update(self.env.context)
context['default_mo_id'] = self.id
action['context'] = context
return action
@api.model
def get_empty_list_help(self, help):
self = self.with_context(
+1 -1
View File
@@ -93,7 +93,7 @@ class MrpRoutingWorkcenter(models.Model):
if 'bom_id' in self.env.context:
bom_id = self.env.context.get('bom_id')
for operation in self:
operation.copy({'name': _("%s (copy)", operation.name), 'bom_id': bom_id})
operation.copy({'bom_id': bom_id})
return {
'view_mode': 'form',
'res_model': 'mrp.bom',
+8 -1
View File
@@ -150,6 +150,12 @@
<span class="o_stat_text">Backorders</span>
</div>
</button>
<button class="oe_stat_button" name="action_view_mrp_production_unbuilds" type="object" icon="fa-undo" attrs="{'invisible': [('unbuild_count', '=', 0)]}">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value"><field name="unbuild_count"/></span>
<span class="o_stat_text">Unbuilds</span>
</div>
</button>
<button class="oe_stat_button" name="action_see_move_scrap" type="object" icon="fa-arrows-v" attrs="{'invisible': [('scrap_count', '=', 0)]}">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value"><field name="scrap_count"/></span>
@@ -335,7 +341,7 @@
</field>
</page>
<page string="Work Orders" name="operations" groups="mrp.group_mrp_routings">
<field name="workorder_ids" attrs="{'readonly': [('state', 'in', ['cancel', 'done'])]}" context="{'tree_view_ref': 'mrp.mrp_production_workorder_tree_editable_view', 'default_product_uom_id': product_uom_id}"/>
<field name="workorder_ids" attrs="{'readonly': ['|', ('state', '=', 'cancel'), '&amp;', ('state', '=', 'done'), ('is_locked', '=', True)]}" context="{'tree_view_ref': 'mrp.mrp_production_workorder_tree_editable_view', 'default_product_uom_id': product_uom_id}"/>
</page>
<page string="By-Products" name="finished_products" groups="mrp.group_mrp_byproducts">
<field name="move_byproduct_ids" context="{'default_date': date_planned_finished, 'default_date_deadline': date_deadline, 'default_location_id': production_location_id, 'default_location_dest_id': location_src_id, 'default_state': 'draft', 'default_production_id': id, 'default_picking_type_id': picking_type_id, 'default_company_id': company_id}" attrs="{'readonly': ['|', ('state', '=', 'cancel'), '&amp;', ('state', '=', 'done'), ('is_locked', '=', True)]}" options="{'delete': [('state', '=', 'draft')]}">
@@ -518,6 +524,7 @@
<filter string="To Do" name="todo" domain="[('state', 'in', ('draft', 'confirmed', 'progress', 'to_close'))]"
help="Manufacturing Orders which are in confirmed state."/>
<filter string="Starred" name="starred" domain="[('priority', '=', '1')]"/>
<filter string="Unbuilt" name="filter_unbuilt" domain="[('unbuild_ids.state', '=', 'done')]"/>
<separator/>
<filter string="Draft" name="filter_draft" domain="[('state', '=', 'draft')]"/>
<filter string="Confirmed" name="filter_confirmed" domain="[('state', '=', 'confirmed')]"/>
+2
View File
@@ -7,6 +7,7 @@
<search>
<field name="production_id"/>
<field name="workcenter_id"/>
<field name="product_id"/>
<filter string="Ready" name="ready" domain="[('state','=','ready')]"/>
<filter string="Waiting" name="waiting" domain="[('state','=','waiting')]"/>
<filter string="Pending" name="pending" domain="[('state','=','pending')]"/>
@@ -236,6 +237,7 @@
<field name="name" string="Work Order"/>
<field name="workcenter_id"/>
<field name="production_id"/>
<field name="product_id"/>
<filter string="In Progress" name="progress" domain="[('state', '=', 'progress')]"/>
<filter string="Ready" name="ready" domain="[('state', '=', 'ready')]"/>
<filter string="Waiting" name="waiting" domain="[('state', '=', 'waiting')]"/>
@@ -13,6 +13,7 @@ class StockMoveLine(models.Model):
string='Expiration Date', compute='_compute_expiration_date', store=True,
help='This is the date on which the goods with this Serial Number may'
' become dangerous and must not be consumed.')
is_expired = fields.Boolean(related='lot_id.product_expiry_alert')
@api.depends('product_id', 'picking_type_use_create_lots')
def _compute_expiration_date(self):
@@ -17,6 +17,13 @@
<field name="model">stock.move.line</field>
<field name="inherit_id" ref="stock.view_stock_move_line_operation_tree"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='lot_id']" position="after">
<field name="is_expired" invisible="1"/>
<button class="fa fa-exclamation-triangle text-danger"
title="This lot is expired." string="This lot is expired"
disabled="1"
attrs="{'invisible': [('is_expired', '=', False)]}"/>
</xpath>
<xpath expr="//field[@name='lot_name']" position="after" >
<field name="picking_type_use_existing_lots" invisible="1"/>
<field name="expiration_date" force_save="1" attrs="{
@@ -39,6 +46,13 @@
'readonly': [('picking_type_use_existing_lots', '=', True)],
}"/>
</xpath>
<xpath expr="//field[@name='lot_id']" position="after">
<field name="is_expired" invisible="1"/>
<button class="fa fa-exclamation-triangle text-danger"
title="This lot is expired." string="This lot is expired"
disabled="1"
attrs="{'invisible': [('is_expired', '=', False)]}"/>
</xpath>
</field>
</record>
</odoo>
@@ -26,7 +26,7 @@ class StockAssignSerialNumbers(models.TransientModel):
def _check_next_serial_count(self):
for wizard in self:
if wizard.next_serial_count < 1:
raise ValidationError(_("The number of Serial Numbers to generate must greater than zero."))
raise ValidationError(_("The number of Serial Numbers to generate must be greater than zero."))
def generate_serial_numbers(self):
self.ensure_one()