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