diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index 1b213b65b69..c0302f04432 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -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: diff --git a/addons/mrp/views/mrp_production_views.xml b/addons/mrp/views/mrp_production_views.xml index 8fe96b9a71a..4a506886504 100644 --- a/addons/mrp/views/mrp_production_views.xml +++ b/addons/mrp/views/mrp_production_views.xml @@ -25,6 +25,8 @@ + + @@ -157,6 +159,8 @@ ('routing_id', '!=', False), ('state', 'in', ['draft', 'confirmed']) ]}"/> + + @@ -234,6 +238,8 @@
+ +
@@ -317,6 +323,7 @@ +
+ +
+

The deliveries + + , + will be late. +

+
diff --git a/addons/sale_stock/views/sale_order_views.xml b/addons/sale_stock/views/sale_order_views.xml index 896797ad8f6..7a50cfa7cf8 100644 --- a/addons/sale_stock/views/sale_order_views.xml +++ b/addons/sale_stock/views/sale_order_views.xml @@ -25,6 +25,10 @@ + + + + diff --git a/addons/stock/models/stock_picking.py b/addons/stock/models/stock_picking.py index 423c505c5eb..10d8b31dd78 100644 --- a/addons/stock/models/stock_picking.py +++ b/addons/stock/models/stock_picking.py @@ -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 + diff --git a/addons/stock/static/src/js/stock_rescheduling_popover.js b/addons/stock/static/src/js/stock_rescheduling_popover.js new file mode 100644 index 00000000000..e30b88bace7 --- /dev/null +++ b/addons/stock/static/src/js/stock_rescheduling_popover.js @@ -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; +}); diff --git a/addons/stock/static/src/xml/popover_widget.xml b/addons/stock/static/src/xml/popover_widget.xml index 0cd4d8c7645..37cffd522df 100644 --- a/addons/stock/static/src/xml/popover_widget.xml +++ b/addons/stock/static/src/xml/popover_widget.xml @@ -9,4 +9,11 @@ + diff --git a/addons/stock/views/stock_picking_views.xml b/addons/stock/views/stock_picking_views.xml index eeb1761f241..cb7338f9b98 100644 --- a/addons/stock/views/stock_picking_views.xml +++ b/addons/stock/views/stock_picking_views.xml @@ -171,6 +171,8 @@
+ +
@@ -206,6 +208,8 @@ + + @@ -294,7 +298,12 @@ +