diff --git a/addons/stock/__manifest__.py b/addons/stock/__manifest__.py index ddff4d0100a..20f0d9c076d 100644 --- a/addons/stock/__manifest__.py +++ b/addons/stock/__manifest__.py @@ -135,6 +135,7 @@ ], 'web.assets_qweb': [ 'stock/static/src/xml/inventory_report.xml', + 'stock/static/src/xml/inventory_adjustments.xml', 'stock/static/src/xml/inventory_lines.xml', 'stock/static/src/xml/popover_widget.xml', 'stock/static/src/xml/forecast_widget.xml', diff --git a/addons/stock/models/stock_quant.py b/addons/stock/models/stock_quant.py index 486c3b62c92..82e022d6055 100644 --- a/addons/stock/models/stock_quant.py +++ b/addons/stock/models/stock_quant.py @@ -61,15 +61,19 @@ class StockQuant(models.Model): product_uom_id = fields.Many2one( 'uom.uom', 'Unit of Measure', readonly=True, related='product_id.uom_id') + priority = fields.Selection(related='product_tmpl_id.priority') company_id = fields.Many2one(related='location_id.company_id', string='Company', store=True, readonly=True) location_id = fields.Many2one( 'stock.location', 'Location', domain=lambda self: self._domain_location_id(), auto_join=True, ondelete='restrict', required=True, index=True, check_company=True) + storage_category_id = fields.Many2one(related='location_id.storage_category_id', store=True) + cyclic_inventory_frequency = fields.Integer(related='location_id.cyclic_inventory_frequency') lot_id = fields.Many2one( 'stock.lot', 'Lot/Serial Number', index=True, ondelete='restrict', check_company=True, domain=lambda self: self._domain_lot_id()) + sn_duplicated = fields.Boolean(string="Duplicated Serial Number", compute='_compute_sn_duplicated', help="If the same SN is in another Quant") package_id = fields.Many2one( 'stock.quant.package', 'Package', domain="[('location_id', '=', location_id)]", @@ -110,6 +114,7 @@ class StockQuant(models.Model): inventory_date = fields.Date( 'Scheduled Date', compute='_compute_inventory_date', store=True, readonly=False, help="Next date the On Hand Quantity should be counted.") + last_count_date = fields.Date(compute='_compute_last_count_date', help='Last time the Quantity was Updated') inventory_quantity_set = fields.Boolean(store=True, compute='_compute_inventory_quantity_set', readonly=False) is_outdated = fields.Boolean('Quantity has been moved since last count', compute='_compute_is_outdated') user_id = fields.Many2one( @@ -127,6 +132,56 @@ class StockQuant(models.Model): for quant in quants: quant.inventory_date = date_by_location[quant.location_id] + def _compute_last_count_date(self): + """ We look at the stock move lines associated with every quant to get the last count date. + """ + self.last_count_date = False + groups = self.env['stock.move.line'].read_group( + [ + ('state', '=', 'done'), + ('is_inventory', '=', True), + ('product_id', 'in', self.product_id.ids), + '|', + ('lot_id', 'in', self.lot_id.ids), + ('lot_id', '=', False), + '|', + ('owner_id', 'in', self.owner_id.ids), + ('owner_id', '=', False), + '|', + ('location_id', 'in', self.location_id.ids), + ('location_dest_id', 'in', self.location_id.ids), + '|', + ('package_id', '=', False), + '|', + ('package_id', 'in', self.package_id.ids), + ('result_package_id', 'in', self.package_id.ids), + ], + ['date:max', 'product_id', 'lot_id', 'package_id', 'owner_id', 'result_package_id', 'location_id', 'location_dest_id'], + ['product_id', 'lot_id', 'package_id', 'owner_id', 'result_package_id', 'location_id', 'location_dest_id'], + lazy=False) + + def _update_dict(date_by_quant, key, value): + current_date = date_by_quant.get(key) + if not current_date or value > current_date: + date_by_quant[key] = value + + date_by_quant = {} + for group in groups: + move_line_date = group['date'] + location_id = group['location_id'][0] + location_dest_id = group['location_dest_id'][0] + package_id = group['package_id'] and group['package_id'][0] + result_package_id = group['result_package_id'] and group['result_package_id'][0] + lot_id = group['lot_id'] and group['lot_id'][0] + owner_id = group['owner_id'] and group['owner_id'][0] + product_id = group['product_id'][0] + _update_dict(date_by_quant, (location_id, package_id, product_id, lot_id, owner_id), move_line_date) + _update_dict(date_by_quant, (location_dest_id, package_id, product_id, lot_id, owner_id), move_line_date) + _update_dict(date_by_quant, (location_id, result_package_id, product_id, lot_id, owner_id), move_line_date) + _update_dict(date_by_quant, (location_dest_id, result_package_id, product_id, lot_id, owner_id), move_line_date) + for quant in self: + quant.last_count_date = date_by_quant.get((quant.location_id.id, quant.package_id.id, quant.product_id.id, quant.lot_id.id, quant.owner_id.id)) + @api.depends('inventory_quantity') def _compute_inventory_diff_quantity(self): for quant in self: @@ -148,6 +203,15 @@ class StockQuant(models.Model): for quant in self: quant.inventory_quantity_auto_apply = quant.quantity + @api.depends('lot_id') + def _compute_sn_duplicated(self): + self.sn_duplicated = False + domain = [('tracking', '=', 'serial'), ('lot_id', 'in', self.lot_id.ids), ('location_id.usage', 'in', ['internal', 'transit'])] + results = self.read_group(domain, ['lot_id'], ['lot_id']) + duplicated_sn_ids = [x['lot_id'][0] for x in results if x['lot_id_count'] > 1] + quants_with_duplicated_sn = self.env['stock.quant'].search([('lot_id', 'in', duplicated_sn_ids)]) + quants_with_duplicated_sn.sn_duplicated = True + def _set_inventory_quantity(self): """ Inverse method to create stock move when `inventory_quantity` is set (`inventory_quantity` is only accessible in inventory mode). @@ -309,7 +373,7 @@ class StockQuant(models.Model): and fields.Float.is_zero(quant.quantity, precision_rounding=rounding): continue if quant.product_id.tracking in ['lot', 'serial'] and\ - not quant.lot_id and quant.inventory_quantity != quant.quantity: + not quant.lot_id and quant.inventory_quantity != quant.quantity and not quant.quantity: products_tracked_without_lot.append(quant.product_id.id) # for some reason if multi-record, env.context doesn't pass to wizards... ctx = dict(self.env.context or {}) @@ -408,6 +472,15 @@ class StockQuant(models.Model): self.inventory_diff_quantity = 0 self.inventory_quantity_set = False + def action_warning_duplicated_sn(self): + return { + 'name': _('Warning Duplicated SN'), + 'type': 'ir.actions.act_window', + 'res_model': 'stock.quant', + 'views': [(self.env.ref('stock.duplicated_sn_warning').id, 'form')], + 'target': 'new', + } + @api.constrains('product_id') def check_product_id(self): if any(elem.product_id.type != 'product' for elem in self): @@ -782,7 +855,7 @@ class StockQuant(models.Model): """ Returns a list of fields user can edit when he want to edit a quant in `inventory_mode`. """ fields = ['inventory_quantity', 'inventory_quantity_auto_apply', 'inventory_diff_quantity', - 'inventory_date', 'user_id', 'inventory_quantity_set', 'is_outdated'] + 'inventory_date', 'user_id', 'inventory_quantity_set', 'is_outdated', 'lot_id'] return fields def _get_inventory_move_values(self, qty, location_id, location_dest_id, out=False): diff --git a/addons/stock/static/src/js/inventory_singleton_list_controller.js b/addons/stock/static/src/js/inventory_singleton_list_controller.js index 22dc2ea4b28..f2d0d848f44 100644 --- a/addons/stock/static/src/js/inventory_singleton_list_controller.js +++ b/addons/stock/static/src/js/inventory_singleton_list_controller.js @@ -22,6 +22,30 @@ var _t = core._t; var SingletonListController = InventoryReportListController.extend({ + + init: function (parent, model, renderer, params) { + this.context = renderer.state.getContext(); + return this._super(...arguments); + }, + + willStart() { + const sup = this._super(...arguments); + const acl = this.getSession().user_has_group('stock.group_stock_manager').then(hasGroup => { + if (hasGroup) { + this.buttons_template = 'StockInventoryAdjustments.Buttons'; + } + }); + return Promise.all([sup, acl]); + }, + + /** + * @override + */ + renderButtons: function ($node) { + this._super(...arguments); + this.$buttons.on('click', '.o_button_apply_all', this._onApplyAll.bind(this)); + }, + // ------------------------------------------------------------------------- // Private // ------------------------------------------------------------------------- @@ -95,6 +119,16 @@ var SingletonListController = InventoryReportListController.extend({ self._enableButtons(); }).guardedCatch(this._enableButtons.bind(this)); }, + + _onApplyAll: function () { + this.do_action('stock.action_stock_inventory_adjustement_name', { + additional_context: { + 'active_model': 'ir.ui.view', + 'active_ids': this.renderer.state.data.map(el => el['res_id']), + }, + on_close: this.reload, + }); + }, }); return SingletonListController; diff --git a/addons/stock/static/src/xml/inventory_adjustments.xml b/addons/stock/static/src/xml/inventory_adjustments.xml new file mode 100644 index 00000000000..b423d7e70cf --- /dev/null +++ b/addons/stock/static/src/xml/inventory_adjustments.xml @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/addons/stock/views/stock_location_views.xml b/addons/stock/views/stock_location_views.xml index 9aca420fe38..cf389aa76c8 100644 --- a/addons/stock/views/stock_location_views.xml +++ b/addons/stock/views/stock_location_views.xml @@ -40,7 +40,7 @@ - + diff --git a/addons/stock/views/stock_quant_views.xml b/addons/stock/views/stock_quant_views.xml index bacaa9218c6..bf15329e093 100644 --- a/addons/stock/views/stock_quant_views.xml +++ b/addons/stock/views/stock_quant_views.xml @@ -9,6 +9,7 @@ + @@ -22,6 +23,7 @@ + @@ -33,6 +35,7 @@ + @@ -298,6 +301,20 @@ + + stock.quant.duplicated.sn.warning + 1000 + stock.quant + +
+ This SN is already in another location. +
+
+
+
+
+ stock.quant.inventory.tree.editable @@ -312,19 +329,27 @@ + + + + +