[IMP] stock/mrp: add a new alert system in the form view

Add two widgets in the `mrp.production` / `stock.picking` form view.
The widget is visible only if one of the stock move linked to the `mrp.production` / `stock.picking`
has the flag `delay_alert_date` set.

Task #1970595
This commit is contained in:
Debauche Stéphane
2019-12-30 15:12:08 +00:00
parent b7f46da90d
commit a73bed91a0
11 changed files with 152 additions and 3 deletions
+29 -1
View File
@@ -1,13 +1,14 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import json
import datetime
from collections import defaultdict
from itertools import groupby
from odoo import api, fields, models, _
from odoo.exceptions import AccessError, UserError
from odoo.tools import date_utils, float_round, float_is_zero
from odoo.tools import date_utils, float_round, float_is_zero, format_datetime
class MrpProduction(models.Model):
@@ -205,6 +206,8 @@ class MrpProduction(models.Model):
'Propagate cancel and split',
help='If checked, when the previous move of the move (which was generated by a next procurement) is cancelled or split, the move generated by this move will too')
delay_alert = fields.Boolean(string='Delay alert')
delay_alert_date = fields.Datetime('Delay Alert Date', compute='_compute_delay_alert_date', search='_search_delay_alert_date')
json_popover = fields.Char('JSON data for the popover widget', compute='_compute_json_popover')
propagate_date = fields.Boolean(string="Propagate Rescheduling",
help='The rescheduling is propagated to the next move.')
propagate_date_minimum_delta = fields.Integer(string='Reschedule if Higher Than',
@@ -232,6 +235,26 @@ class MrpProduction(models.Model):
def _set_date_planned_finished(self):
self.move_finished_ids.write({'date_expected': self.date_planned_finished})
@api.depends('move_raw_ids.delay_alert_date')
def _compute_delay_alert_date(self):
delay_alert_date_data = self.env['stock.move'].read_group([('id', 'in', self.move_raw_ids.ids), ('delay_alert_date', '!=', False)], ['delay_alert_date:max'], 'raw_material_production_id')
delay_alert_date_data = {data['raw_material_production_id'][0]: data['delay_alert_date'] for data in delay_alert_date_data}
for production in self:
production.delay_alert_date = delay_alert_date_data.get(production.id, False)
def _compute_json_popover(self):
for production in self:
production.json_popover = json.dumps({
'popoverTemplate': 'stock.PopoverStockRescheduling',
'delay_alert_date': format_datetime(self.env, production.delay_alert_date, dt_format=False) if production.delay_alert_date else False,
'late_elements': [{
'id': late_document.id,
'name': late_document.display_name,
'model': late_document._name,
} for late_document in production.move_raw_ids.filtered(lambda m: m.delay_alert_date).move_orig_ids._delay_alert_get_documents()
]
})
@api.depends('move_raw_ids.state', 'move_finished_ids.state')
def _compute_confirm_cancel(self):
""" If the manufacturing order contains some done move (via an intermediate
@@ -403,6 +426,11 @@ class MrpProduction(models.Model):
('qty_positive', 'check (product_qty > 0)', 'The quantity to produce must be positive!'),
]
@api.model
def _search_delay_alert_date(self, operator, value):
late_stock_moves = self.env['stock.move'].search([('delay_alert_date', operator, value)])
return ['|', ('move_raw_ids', 'in', late_stock_moves.ids), ('move_finished_ids', 'in', late_stock_moves.ids)]
@api.onchange('company_id')
def onchange_company_id(self):
if self.company_id:
@@ -25,6 +25,8 @@
<field name="state" optional="show"/>
<field name="company_id" readonly="1" groups="base.group_multi_company" optional="show"/>
<field name="activity_exception_decoration" widget="activity_exception"/>
<field name="delay_alert_date" invisible="1"/>
<field string=" " name="json_popover" widget="stock_rescheduling_popover" attrs="{'invisible': [('delay_alert_date', '=', False)]}"/>
</tree>
</field>
</record>
@@ -157,6 +159,8 @@
('routing_id', '!=', False),
('state', 'in', ['draft', 'confirmed'])
]}"/>
<field name="delay_alert_date" invisible="1"/>
<field string=" " name="json_popover" widget="stock_rescheduling_popover" attrs="{'invisible': [('delay_alert_date', '=', False)]}"/>
</div>
<field name="user_id"/>
<field name="origin"/>
@@ -234,6 +238,8 @@
<div class="oe_kanban_bottom_left text-muted">
<span><t t-esc="record.name.value"/> <t t-esc="record.date_deadline.value and record.date_deadline.value.split(' ')[0] or False"/></span>
<field name="activity_ids" widget="kanban_activity"/>
<field name="delay_alert_date" invisible="1"/>
<field string=" " name="json_popover" widget="stock_rescheduling_popover" attrs="{'invisible': [('delay_alert_date', '=', False)]}"/>
</div>
<div class="oe_kanban_bottom_right">
<span t-attf-class="badge #{['draft', 'cancel'].indexOf(record.state.raw_value) > -1 ? 'badge-secondary' : ['none'].indexOf(record.state.raw_value) > -1 ? 'badge-danger' : ['confirmed'].indexOf(record.state.raw_value) > -1 ? 'badge-warning' : ['done'].indexOf(record.state.raw_value) > -1 ? 'badge-success' : 'badge-primary'}"><t t-esc="record.state.value"/></span>
@@ -317,6 +323,7 @@
<separator/>
<filter string="Late" name="late" help="Production started late"
domain="['&amp;', ('date_deadline', '&lt;', current_date), ('state', '=', 'confirmed')]"/>
<filter string="Planning Issues" name="delay_alert_date" domain="[('delay_alert_date', '!=', False)]"/>
<separator/>
<filter invisible="1" string="Late Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
+1 -1
View File
@@ -36,7 +36,7 @@ Preferences
'wizard/sale_order_cancel_views.xml',
],
'demo': ['data/sale_order_demo.xml'],
'qweb': ['static/src/xml/qty_at_date.xml'],
'qweb': ['static/src/xml/sale_stock.xml'],
'installable': True,
'auto_install': True,
}
+17
View File
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import json
from datetime import datetime, timedelta
from collections import defaultdict
@@ -40,6 +41,8 @@ class SaleOrder(models.Model):
"the order lines in case of Service products. In case of shipping, the shipping policy of "
"the order will be taken into account to either use the minimum or maximum lead time of "
"the order lines.")
json_popover = fields.Char('JSON data for the popover widget', compute='_compute_json_popover')
show_json_popover = fields.Boolean('Has late picking', compute='_compute_json_popover')
@api.depends('picking_ids.date_done')
def _compute_effective_date(self):
@@ -88,6 +91,20 @@ class SaleOrder(models.Model):
order._log_decrease_ordered_quantity(documents)
return res
def _compute_json_popover(self):
for order in self:
late_stock_picking = order.picking_ids.filtered(lambda p: p.delay_alert_date)
order.json_popover = json.dumps({
'popoverTemplate': 'sale_stock.DelayAlertWidget',
'late_elements': [{
'id': late_move.id,
'name': late_move.display_name,
'model': 'stock.picking',
} for late_move in late_stock_picking
]
})
order.show_json_popover = bool(late_stock_picking)
def _action_confirm(self):
self.order_line._action_launch_stock_rule()
return super(SaleOrder, self)._action_confirm()
@@ -42,4 +42,12 @@
View Forecast
</button>
</div>
<div t-name="sale_stock.DelayAlertWidget">
<p>The deliveries
<t t-foreach="late_elements" t-as="late_element">
<a t-esc="late_element.name" href="#" t-att-element-id="late_element.id" t-att-element-model="model"/>,
</t> will be late.
</p>
</div>
</templates>
@@ -25,6 +25,10 @@
<field name="incoterm" widget="selection" groups="sale_stock.group_display_incoterm"/>
<field name="picking_policy" required="True"/>
</xpath>
<xpath expr="//page[@name='other_information']//field[@name='expected_date']" position="after">
<field name="show_json_popover" invisible="1"/>
<field string=" " name="json_popover" widget="stock_rescheduling_popover" attrs="{'invisible': [('show_json_popover', '=', False)]}"/>
</xpath>
<xpath expr="//group[@name='sale_shipping']" position="inside">
<field name="effective_date" attrs="{'invisible': [('effective_date', '=', False)]}"/>
</xpath>
+30 -1
View File
@@ -6,10 +6,11 @@ from datetime import date
from itertools import groupby
from operator import itemgetter
import time
import json
from odoo import api, fields, models, SUPERUSER_ID, _
from odoo.osv import expression
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT, format_datetime
from odoo.tools.float_utils import float_compare, float_is_zero, float_round
from odoo.exceptions import UserError
from odoo.addons.stock.models.stock_move import PROCUREMENT_PRIORITIES
@@ -294,6 +295,8 @@ class Picking(models.Model):
states={'done': [('readonly', True)], 'cancel': [('readonly', True)]},
help="Creation Date, usually the time of the order")
date_done = fields.Datetime('Date of Transfer', copy=False, readonly=True, help="Date at which the transfer has been processed or cancelled.")
delay_alert_date = fields.Datetime('Delay Alert Date', compute='_compute_delay_alert_date', search='_search_delay_alert_date')
json_popover = fields.Char('JSON data for the popover widget', compute='_compute_json_popover')
location_id = fields.Many2one(
'stock.location', "Source Location",
default=lambda self: self.env['stock.picking.type'].browse(self._context.get('default_picking_type_id')).default_location_src_id,
@@ -377,6 +380,13 @@ class Picking(models.Model):
for picking in self:
picking.has_tracking = any(m.has_tracking != 'none' for m in picking.move_lines)
@api.depends('move_lines.delay_alert_date')
def _compute_delay_alert_date(self):
delay_alert_date_data = self.env['stock.move'].read_group([('id', 'in', self.move_lines.ids), ('delay_alert_date', '!=', False)], ['delay_alert_date:max'], 'picking_id')
delay_alert_date_data = {data['picking_id'][0]: data['delay_alert_date'] for data in delay_alert_date_data}
for picking in self:
picking.delay_alert_date = delay_alert_date_data.get(picking.id, False)
@api.depends('picking_type_id.show_operations')
def _compute_show_operations(self):
for picking in self:
@@ -403,6 +413,19 @@ class Picking(models.Model):
else:
picking.show_lots_text = False
def _compute_json_popover(self):
for picking in self:
picking.json_popover = json.dumps({
'popoverTemplate': 'stock.PopoverStockRescheduling',
'delay_alert_date': format_datetime(self.env, picking.delay_alert_date, dt_format=False) if picking.delay_alert_date else False,
'late_elements': [{
'id': late_move.id,
'name': late_move.display_name,
'model': late_move._name,
} for late_move in picking.move_lines.filtered(lambda m: m.delay_alert_date).move_orig_ids._delay_alert_get_documents()
]
})
@api.depends('move_type', 'move_lines.state', 'move_lines.picking_id')
def _compute_state(self):
''' State of a picking depends on the state of its related stock.move
@@ -510,6 +533,11 @@ class Picking(models.Model):
else:
picking.show_validate = True
@api.model
def _search_delay_alert_date(self, operator, value):
late_stock_moves = self.env['stock.move'].search([('delay_alert_date', operator, value)])
return [('move_lines', 'in', late_stock_moves.ids)]
@api.onchange('picking_type_id', 'partner_id')
def onchange_picking_type(self):
if self.picking_type_id:
@@ -1265,3 +1293,4 @@ class Picking(models.Model):
body=message,
)
return True
@@ -0,0 +1,38 @@
odoo.define('stock.PopoverStockPicking', function (require) {
"use strict";
var core = require('web.core');
var PopoverWidgetField = require('stock.popover_widget');
var registry = require('web.field_registry');
var _t = core._t;
var PopoverStockPicking = PopoverWidgetField.extend({
title: _t('Planning Issue'),
color: 'text-warning',
trigger: 'focus',
_render: function () {
this._super();
if (this.$popover) {
var self = this;
this.$popover.find('a').on('click', function (ev) {
ev.preventDefault();
ev.stopPropagation();
self.do_action({
type: 'ir.actions.act_window',
res_model: ev.currentTarget.getAttribute('element-model'),
res_id: parseInt(ev.currentTarget.getAttribute('element-id'), 10),
views: [[false, 'form']],
target: 'current'
});
});
}
},
});
registry.add('stock_rescheduling_popover', PopoverStockPicking);
return PopoverStockPicking;
});
@@ -9,4 +9,11 @@
<t t-esc="msg"/>
</div>
<div t-name="stock.PopoverStockRescheduling">
<p>Preceding operations
<t t-foreach="late_elements" t-as="late_element">
<a t-esc="late_element.name" href="#" t-att-element-id="late_element.id" t-att-element-model="late_element.model"/>,
</t>
planned on <t t-esc="delay_alert_date"/>.</p>
</div>
</templates>
@@ -171,6 +171,8 @@
<div class="oe_kanban_bottom_left">
<t t-esc="record.partner_id.value"/>
<field name="activity_ids" widget="kanban_activity"/>
<field name="delay_alert_date" invisible="1"/>
<field name="json_popover" string=" " widget="stock_rescheduling_popover" attrs="{'invisible': [('delay_alert_date', '=', False)]}"/>
</div>
<div class="oe_kanban_bottom_right">
<t t-esc="record.date.value and record.date.value.split(' ')[0] or False"/>
@@ -206,6 +208,8 @@
<field name="picking_type_id" optional="hide"/>
<field name="company_id" groups="base.group_multi_company" optional="show"/>
<field name="activity_exception_decoration" widget="activity_exception"/>
<field name="delay_alert_date" invisible="1"/>
<field name="json_popover" string=" " widget="stock_rescheduling_popover" attrs="{'invisible': [('delay_alert_date', '=', False)]}"/>
</tree>
</field>
</record>
@@ -294,7 +298,12 @@
<field name="backorder_id" attrs="{'invisible': [('backorder_id','=',False)]}"/>
</group>
<group>
<label for="scheduled_date"/>
<div class="o_row">
<field name="scheduled_date" attrs="{'required': [('id', '!=', False)]}"/>
<field name="delay_alert_date" invisible="1"/>
<field name="json_popover" widget="stock_rescheduling_popover" attrs="{'invisible': [('delay_alert_date', '=', False)]}"/>
</div>
<field name="date_done" string="Effective Date" attrs="{'invisible': [('state', '!=', 'done')]}"/>
<field name="origin" placeholder="e.g. PO0032"/>
<field name="owner_id" groups="stock.group_tracking_owner"
@@ -433,6 +442,7 @@
<filter name="cancel" string="Cancelled" domain="[('state', '=', 'cancel')]" help="Cancelled Moves"/>
<separator/>
<filter name="late" string="Late" domain="[('scheduled_date','&lt;', time.strftime('%%Y-%%m-%%d %%H:%%M:%%S')), ('state', 'in', ('assigned', 'waiting', 'confirmed'))]" help="Transfers that are late on scheduled time"/>
<filter string="Planning Issues" name="delay_alert_date" domain="[('delay_alert_date', '!=', False)]"/>
<separator/>
<filter name="backorder" string="Backorders" domain="[('backorder_id', '!=', False), ('state', 'in', ('assigned', 'waiting', 'confirmed'))]" help="Remaining parts of picking partially processed"/>
<separator/>
+1
View File
@@ -13,6 +13,7 @@
<script type="text/javascript" src="/stock/static/src/js/stock_traceability_report_widgets.js"></script>
<script type="text/javascript" src="/stock/static/src/js/stock_location_hierarchy.js"></script>
<script type="text/javascript" src="/stock/static/src/js/popover_widget.js"></script>
<script type="text/javascript" src="/stock/static/src/js/stock_rescheduling_popover.js"></script>
<link rel="stylesheet" type="text/scss" href="/stock/static/src/scss/stock_traceability_report.scss"/>
<link rel="stylesheet" type="text/scss" href="/stock/static/src/scss/stock_location_hierarchy.scss"/>
</xpath>