[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:
@@ -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(
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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'), '&', ('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'), '&', ('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')]"/>
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user