[IMP] stock,mrp,purchase: refactor priority

- MO/picking/PO/stock.move have now the "same" `priority` fields
(Boolean logic: Normal or Urgent).
These models are now sort by priority first. Note that the priority field of
stock move is a technical one.
- For MO and picking, the set of `priority` field change priority of
the related stock move. The prioriry field on stock move is
used by the scheduler for reservation ('Urgent' before 'Normal')
- The Stock Replenishment Report also take in account the priority
to be consistent with the reservation.

task-2246665
This commit is contained in:
Rémy Voet (ryv)
2020-08-18 07:48:27 +00:00
parent bb9ef00452
commit e40553723b
11 changed files with 63 additions and 44 deletions
+7 -4
View File
@@ -14,6 +14,8 @@ from odoo import api, fields, models, _
from odoo.exceptions import AccessError, UserError
from odoo.tools import float_compare, float_round, float_is_zero, format_datetime
from odoo.addons.stock.models.stock_move import PROCUREMENT_PRIORITIES
SIZE_BACK_ORDER_NUMERING = 3
@@ -23,7 +25,7 @@ class MrpProduction(models.Model):
_description = 'Production Order'
_date_name = 'date_planned_start'
_inherit = ['mail.thread', 'mail.activity.mixin']
_order = 'date_planned_start asc,id'
_order = 'priority desc, date_planned_start asc,id'
@api.model
def _get_default_picking_type(self):
@@ -71,6 +73,9 @@ class MrpProduction(models.Model):
name = fields.Char(
'Reference', copy=False, readonly=True, default=lambda x: _('New'))
priority = fields.Selection(
PROCUREMENT_PRIORITIES, string='Priority', default='0', index=True,
help="Components will be reserved first for the MO with the highest priorities.")
backorder_sequence = fields.Integer("Backorder Sequence", default=0, copy=False, help="Backorder sequence, if equals to 0 means there is not related backorder")
origin = fields.Char(
'Source', copy=False,
@@ -229,8 +234,6 @@ class MrpProduction(models.Model):
help='The change must be higher than this value to be propagated')
scrap_ids = fields.One2many('stock.scrap', 'production_id', 'Scraps')
scrap_count = fields.Integer(compute='_compute_scrap_move_count', string='Scrap Move')
priority = fields.Selection([('0', 'Not urgent'), ('1', 'Normal'), ('2', 'Urgent'), ('3', 'Very Urgent')], 'Priority',
readonly=True, states={'draft': [('readonly', False)]}, default='1')
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")
is_partially_planned = fields.Boolean('One operation is Planned', compute="_compute_is_planned")
@@ -1471,7 +1474,7 @@ class MrpProduction(models.Model):
})
for production in self:
production.write({'date_finished': fields.Datetime.now(), 'product_qty': production.qty_produced})
production.write({'date_finished': fields.Datetime.now(), 'product_qty': production.qty_produced, 'priority': '0'})
for workorder in self.workorder_ids.filtered(lambda w: w.state not in ('done', 'cancel')):
workorder.duration_expected = workorder._get_duration_expected()
+6
View File
@@ -119,6 +119,12 @@ class StockMove(models.Model):
.filtered(lambda ml: ml.qty_done == 0.0)\
.write({'move_id': new_move, 'product_uom_qty': 0})
@api.depends('raw_material_production_id.priority')
def _compute_priority(self):
super()._compute_priority()
for move in self:
move.priority = move.raw_material_production_id.priority or move.priority or '0'
@api.depends('raw_material_production_id.move_finished_ids.move_line_ids.lot_id')
def _compute_order_finished_lot_ids(self):
for move in self:
+9 -3
View File
@@ -6,11 +6,12 @@
<field name="name">mrp.production.tree</field>
<field name="model">mrp.production</field>
<field name="arch" type="xml">
<tree string="Manufacturing Orders" default_order="date_planned_start desc" multi_edit="1" sample="1">
<tree string="Manufacturing Orders" default_order="priority desc, date_planned_start desc" multi_edit="1" sample="1">
<header>
<button name="button_plan" type="object" string="Plan"/>
<button name="do_unreserve" type="object" string="Unreserve"/>
</header>
<field name="priority" optional="show" widget="priority" nolabel="1"/>
<field name="message_needaction" invisible="1"/>
<field name="name" decoration-bf="1"/>
<field name="date_planned_start" readonly="1" optional="show"
@@ -135,7 +136,10 @@
<button name="%(action_mrp_production_moves)d" type="action" string="Product Moves" class="oe_stat_button" icon="fa-exchange" attrs="{'invisible': [('state', 'not in', ('progress', 'done'))]}"/>
</div>
<div class="oe_title">
<h1><field name="name" placeholder="Manufacturing Reference" nolabel="1"/></h1>
<h1>
<field name="priority" widget="priority" class="mr-3"/>
<field name="name" placeholder="Manufacturing Reference" nolabel="1"/>
</h1>
</div>
<group>
<group>
@@ -366,7 +370,8 @@
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_card oe_kanban_global_click">
<div class="o_kanban_record_top">
<div class="o_kanban_record_headings mt4">
<field name="priority" widget="priority"/>
<div class="o_kanban_record_headings mt4 ml-1">
<strong class="o_kanban_record_title"><span><t t-esc="record.product_id.value"/></span></strong>
</div>
<span class="float-right text-right"><t t-esc="record.product_qty.value"/> <small><t t-esc="record.product_uom_id.value"/></small></span>
@@ -444,6 +449,7 @@
<field name="origin"/>
<filter string="To Do" name="todo" domain="[('state', 'in', ('draft', 'confirmed', 'planned','progress', 'to_close'))]"
help="Manufacturing Orders which are in confirmed state."/>
<filter string="Starred" name="starred" domain="[('priority', '=', '1')]"/>
<separator/>
<filter string="Draft" name="filter_draft" domain="[('state', '=', 'draft')]"/>
<filter string="Confirmed" name="filter_confirmed" domain="[('state', '=', 'confirmed')]"/>
+4 -2
View File
@@ -18,7 +18,7 @@ class PurchaseOrder(models.Model):
_name = "purchase.order"
_inherit = ['mail.thread', 'mail.activity.mixin', 'portal.mixin']
_description = "Purchase Order"
_order = 'date_order desc, id desc'
_order = 'priority desc, date_order desc, id desc'
@api.depends('order_line.price_total')
def _amount_all(self):
@@ -71,6 +71,8 @@ class PurchaseOrder(models.Model):
}
name = fields.Char('Order Reference', required=True, index=True, copy=False, default='New')
priority = fields.Selection(
[('0', 'Normal'), ('1', 'Urgent')], 'Priority', default='0', index=True)
origin = fields.Char('Source Document', copy=False,
help="Reference of the document that generated this purchase order "
"request (e.g. a sales order)")
@@ -406,7 +408,7 @@ class PurchaseOrder(models.Model):
self.write({'state': 'purchase'})
def button_done(self):
self.write({'state': 'done'})
self.write({'state': 'done', 'priority': '0'})
def _add_supplier_to_product(self):
# Add the partner in the supplier list of the product if the supplier is not registered for
+8 -1
View File
@@ -164,6 +164,7 @@
<span class="o_form_label" attrs="{'invisible': [('state','not in',('draft','sent'))]}">Request for Quotation </span>
<span class="o_form_label" attrs="{'invisible': [('state','in',('draft','sent'))]}">Purchase Order </span>
<h1>
<field name="priority" widget="priority" class="mr-3"/>
<field name="name" readonly="1"/>
</h1>
</div>
@@ -393,6 +394,7 @@
<field name="user_id"/>
<field name="product_id"/>
<filter name="my_purchases" string="My Purchases" domain="[('user_id', '=', uid)]"/>
<filter string="Starred" name="starred" domain="[('priority', '=', '1')]"/>
<separator/>
<filter name="draft" string="RFQs" domain="[('state', 'in', ('draft', 'sent', 'to approve'))]"/>
<separator/>
@@ -434,6 +436,7 @@
<field name="user_id"/>
<field name="product_id"/>
<filter name="my_Orders" string="My Orders" domain="[('user_id', '=', uid)]"/>
<filter string="Starred" name="starred" domain="[('priority', '=', '1')]"/>
<separator/>
<filter name="unconfirmed" string="Not Acknowledged" domain="[('mail_reception_confirmed', '=', False), ('state', '=', 'purchase')]"/>
<filter name="not_invoiced" string="Waiting Bills" domain="[('invoice_status', '=', 'to invoice')]" help="Purchase orders that include lines not invoiced."/>
@@ -479,7 +482,8 @@
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_card oe_kanban_global_click">
<div class="o_kanban_record_top mb16">
<div class="o_kanban_record_headings mt4">
<field name="priority" widget="priority"/>
<div class="o_kanban_record_headings ml-1">
<strong class="o_kanban_record_title"><span><t t-esc="record.partner_id.value"/></span></strong>
</div>
<strong><field name="amount_total" widget="monetary"/></strong>
@@ -507,6 +511,7 @@
<field name="arch" type="xml">
<tree string="Purchase Order" multi_edit="1" decoration-bf="message_unread==True"
decoration-muted="state=='cancel'" decoration-info="state in ('wait','confirmed')" sample="1">
<field name="priority" optional="show" widget="priority" nolabel="1"/>
<field name="message_unread" invisible="1"/>
<field name="partner_ref" optional="hide"/>
<field name="name" string="Reference" readonly="1"/>
@@ -540,6 +545,7 @@
<header>
<button name="action_create_invoice" type="object" string="Create Bills"/>
</header>
<field name="priority" optional="show" widget="priority" nolabel="1"/>
<field name="message_unread" invisible="1"/>
<field name="partner_ref" optional="hide"/>
<field name="name" string="Reference" readonly="1" decoration-bf="1"/>
@@ -576,6 +582,7 @@
<header>
<button name="action_create_invoice" type="object" string="Create Bills"/>
</header>
<field name="priority" optional="show" widget="priority" nolabel="1"/>
<field name="message_unread" invisible="1"/>
<field name="partner_ref" optional="hide"/>
<field name="name" string="Reference" readonly="1" decoration-bf="1"/>
+10 -3
View File
@@ -16,7 +16,7 @@ from odoo.exceptions import UserError
from odoo.tools.float_utils import float_compare, float_is_zero, float_repr, float_round
from odoo.tools.misc import format_date
PROCUREMENT_PRIORITIES = [('0', 'Not urgent'), ('1', 'Normal'), ('2', 'Urgent'), ('3', 'Very Urgent')]
PROCUREMENT_PRIORITIES = [('0', 'Normal'), ('1', 'Urgent')]
class StockMove(models.Model):
@@ -31,7 +31,9 @@ class StockMove(models.Model):
name = fields.Char('Description', index=True, required=True)
sequence = fields.Integer('Sequence', default=10)
priority = fields.Selection(PROCUREMENT_PRIORITIES, 'Priority', default='1')
priority = fields.Selection(
PROCUREMENT_PRIORITIES, 'Priority', default='0',
compute="_compute_priority", store=True, index=True)
create_date = fields.Datetime('Creation Date', index=True, readonly=True)
date = fields.Datetime(
'Date', default=fields.Datetime.now, index=True, required=True,
@@ -198,6 +200,11 @@ class StockMove(models.Model):
not move.picking_type_id.use_existing_lots
)
@api.depends('picking_id.priority')
def _compute_priority(self):
for move in self:
move.priority = move.picking_id.priority or '0'
@api.depends('picking_id.is_locked')
def _compute_is_locked(self):
for move in self:
@@ -390,7 +397,7 @@ class StockMove(models.Model):
total_availability = self.env['stock.quant']._get_available_quantity(move.product_id, move.location_id) if move.product_id else 0.0
move.availability = min(move.product_qty, total_availability)
@api.depends('product_id', 'picking_type_id', 'picking_id', 'reserved_availability')
@api.depends('product_id', 'picking_type_id', 'picking_id', 'reserved_availability', 'priority')
def _compute_json_forecast(self):
self.json_forecast = False
if not any(self._ids):
+2 -18
View File
@@ -280,10 +280,7 @@ class Picking(models.Model):
'procurement.group', 'Procurement Group',
readonly=True, related='move_lines.group_id', store=True)
priority = fields.Selection(
PROCUREMENT_PRIORITIES, string='Priority',
compute='_compute_priority', inverse='_set_priority', store=True,
index=True, tracking=True,
states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
PROCUREMENT_PRIORITIES, string='Priority', default='0', index=True,
help="Products will be reserved first for the transfers with the highest priorities.")
scheduled_date = fields.Datetime(
'Scheduled Date', compute='_compute_scheduled_date', inverse='_set_scheduled_date', store=True,
@@ -483,19 +480,6 @@ class Picking(models.Model):
else:
picking.state = relevant_move_state
@api.depends('move_lines.priority')
def _compute_priority(self):
for picking in self:
if picking.mapped('move_lines'):
priorities = [priority for priority in picking.mapped('move_lines.priority') if priority] or ['1']
picking.priority = max(priorities)
else:
picking.priority = '1'
def _set_priority(self):
for picking in self:
picking.move_lines.write({'priority': picking.priority})
@api.depends('move_lines.date_expected')
def _compute_scheduled_date(self):
for picking in self:
@@ -767,7 +751,7 @@ class Picking(models.Model):
new_move._action_confirm()
todo_moves |= new_move
todo_moves._action_done(cancel_backorder=self.env.context.get('cancel_backorder'))
self.write({'date_done': fields.Datetime.now()})
self.write({'date_done': fields.Datetime.now(), 'priority': '0'})
# if incoming moves make other confirmed/partially_available moves available, assign them
done_incoming_moves = self.filtered(lambda p: p.picking_type_id.code == 'incoming').move_lines.filtered(lambda m: m.state == 'done')
+2 -2
View File
@@ -320,7 +320,7 @@ class StockRule(models.Model):
'propagate_date': self.propagate_date,
'propagate_date_minimum_delta': self.propagate_date_minimum_delta,
'description_picking': picking_description,
'priority': values.get('priority', "1"),
'priority': values.get('priority', "0"),
'delay_alert': self.delay_alert,
'orderpoint_id': values.get('orderpoint_id') and values['orderpoint_id'].id,
}
@@ -411,7 +411,7 @@ class ProcurementGroup(models.Model):
procurement_errors = []
for procurement in procurements:
procurement.values.setdefault('company_id', self.env.company)
procurement.values.setdefault('priority', '1')
procurement.values.setdefault('priority', '0')
procurement.values.setdefault('date_planned', fields.Datetime.now())
if (
procurement.product_id.type not in ('consu', 'product') or
@@ -136,11 +136,11 @@ class ReplenishmentReport(models.AbstractModel):
in_domain, out_domain = self._move_confirmed_domain(
product_template_ids, product_variant_ids, wh_location_ids
)
outs = self.env['stock.move'].search(out_domain, order='date_expected, id')
outs = self.env['stock.move'].search(out_domain, order='priority desc, date_expected, id')
outs_per_product = defaultdict(lambda: [])
for out in outs:
outs_per_product[out.product_id.id].append(out)
ins = self.env['stock.move'].search(in_domain, order='date_expected, id')
ins = self.env['stock.move'].search(in_domain, order='priority desc, date_expected, id')
ins_per_product = defaultdict(lambda: [])
for in_ in ins:
ins_per_product[in_.product_id.id].append([in_.product_qty, in_])
+10 -6
View File
@@ -160,7 +160,8 @@
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_card oe_kanban_global_click">
<div class="o_kanban_record_top mb8">
<div class="o_kanban_record_headings">
<field name="priority" widget="priority"/>
<div class="o_kanban_record_headings ml-1">
<strong class="o_kanban_record_title"><span><t t-esc="record.name.value"/></span></strong>
</div>
<strong>
@@ -193,6 +194,7 @@
<header>
<button name="do_unreserve" type="object" string="Unreserve"/>
</header>
<field name="priority" optional="show" widget="priority" nolabel="1"/>
<field name="name" decoration-bf="1"/>
<field name="location_id" options="{'no_create': True}" string="From" groups="stock.group_stock_multi_locations" optional="show"/>
<field name="location_dest_id" options="{'no_create': True}" string="To" groups="stock.group_stock_multi_locations" optional="show"/>
@@ -208,7 +210,6 @@
<field name="origin" optional="show"/>
<field name="group_id" invisible="1"/>
<field name="backorder_id" optional="hide"/>
<field name="priority" optional="hide"/>
<field name="picking_type_id" optional="hide"/>
<field name="company_id" groups="base.group_multi_company" optional="show"/>
<field name="state" optional="show" widget="badge" decoration-success="state == 'done'" decoration-info="state not in ('done', 'cancel')"/>
@@ -285,9 +286,12 @@
</div>
</button>
</div>
<h1>
<field name="name" attrs="{'invisible': [('name','=','/')]}"/>
</h1>
<div class="oe_title">
<h1>
<field name="priority" widget="priority" class="mr-3" attrs="{'invisible': [('name','=','/')]}"/>
<field name="name" attrs="{'invisible': [('name','=','/')]}"/>
</h1>
</div>
<group>
<group>
<div class="o_td_label">
@@ -413,7 +417,6 @@
<group string="Other Information" name="other_infos">
<field name="picking_type_code" invisible="1"/>
<field name="move_type" attrs="{'invisible': [('picking_type_code', '=', 'incoming')]}"/>
<field name="priority" widget="priority"/>
<field name="user_id" domain="[('share', '=', False)]"/>
<field name="group_id" groups="base.group_no_one"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}" force_save="1"/>
@@ -445,6 +448,7 @@
<field name="product_id"/>
<field name="picking_type_id"/>
<filter name="my_transfers" string="My Transfers" domain="[('user_id', '=', uid)]"/>
<filter string="Starred" name="starred" domain="[('priority', '=', '1')]"/>
<separator/>
<filter name="draft" string="Draft" domain="[('state', '=', 'draft')]" help="Draft Moves"/>
<filter name="waiting" string="Waiting" domain="[('state', 'in', ('confirmed', 'waiting'))]" help="Waiting Moves"/>
@@ -53,7 +53,7 @@
<!-- Add picking -->
<record id="Picking_A" model="stock.picking">
<field name="move_type">one</field>
<field name="priority">2</field>
<field name="priority">1</field>
<field name="user_id" eval="False"/>
<field name="picking_type_id" ref="stock.picking_type_internal"/>
<field name="batch_id" ref="stock_picking_batch_freeze_1"/>
@@ -63,7 +63,7 @@
</record>
<record id="Picking_B" model="stock.picking">
<field name="move_type">one</field>
<field name="priority">1</field>
<field name="priority">0</field>
<field name="user_id" eval="False"/>
<field name="picking_type_id" ref="stock.picking_type_internal"/>
<field name="batch_id" ref="stock_picking_batch_freeze_1"/>
@@ -73,7 +73,7 @@
</record>
<record id="Picking_C" model="stock.picking">
<field name="move_type">one</field>
<field name="priority">1</field>
<field name="priority">0</field>
<field name="user_id" eval="False"/>
<field name="picking_type_id" ref="stock.picking_type_internal"/>
<field name="batch_id" ref="stock_picking_batch_dry_1"/>