[REF] stock,stock_account: archived products and quantities

PURPOSE

Keep consistent behavior of the stat buttons and the different stock
reports when archiving a product

SPECIFICATION

Currently, you can archive a product but the behavior of the different
stock reports, stat buttons isn't coherent afterwards. Here are the
different behaviors that should be improved:
1.Keep quantity on hand and forecasted quantity visible (in the stat
button + when opening it)
2.Automatically (un)archive RR and BOM if the product is (un)archived
3.Valuation report: show the archived products without the "archived"
filter (behaves already this way in the inventory and forecast reports)
4.When a product has been archived and still has quantity in stock,
include it in inventory adjustments for which counted quantity = default
to stock on hand (dont do so for archived exhausted products).

Also, revert #47478 since we don't have active field.

LINKS

Task 2178269
PR #45586
Fixes #41634

Related: odoo/upgrade#811
Related: odoo/enterprise#8513
Signed-off-by: Simon Lejeune (sle) <sle@openerp.com>
This commit is contained in:
yhu-odoo
2020-03-13 08:37:26 +00:00
parent 7718199ae0
commit b08e3d114a
7 changed files with 54 additions and 21 deletions
+14
View File
@@ -30,6 +30,13 @@ class ProductTemplate(models.Model):
template.used_in_bom_count = self.env['mrp.bom'].search_count(
[('bom_line_ids.product_id', 'in', template.product_variant_ids.ids)])
def write(self, values):
if 'active' in values:
self.filtered(lambda p: p.active != values['active']).with_context(active_test=False).bom_ids.write({
'active': values['active']
})
return super().write(values)
def action_used_in_bom(self):
self.ensure_one()
action = self.env.ref('mrp.mrp_bom_form_action').read()[0]
@@ -70,6 +77,13 @@ class ProductProduct(models.Model):
for product in self:
product.used_in_bom_count = self.env['mrp.bom'].search_count([('bom_line_ids.product_id', '=', product.id)])
def write(self, values):
if 'active' in values:
self.filtered(lambda p: p.active != values['active']).with_context(active_test=False).variant_bom_ids.write({
'active': values['active']
})
return super().write(values)
def get_components(self):
""" Return the components list ids in case of kit product.
Return the product itself otherwise"""
+12 -16
View File
@@ -164,8 +164,8 @@ class Product(models.Model):
domain_move_in += date_date_expected_domain_to
domain_move_out += date_date_expected_domain_to
Move = self.env['stock.move']
Quant = self.env['stock.quant']
Move = self.env['stock.move'].with_context(active_test=False)
Quant = self.env['stock.quant'].with_context(active_test=False)
domain_move_in_todo = [('state', 'in', ('waiting', 'confirmed', 'assigned', 'partially_available'))] + domain_move_in
domain_move_out_todo = [('state', 'in', ('waiting', 'confirmed', 'assigned', 'partially_available'))] + domain_move_out
moves_in_res = dict((item['product_id'][0], item['product_qty']) for item in Move.read_group(domain_move_in_todo, ['product_id', 'product_qty'], ['product_id'], orderby='id'))
@@ -512,16 +512,11 @@ class Product(models.Model):
return theoretical_quantity
def write(self, values):
res = super(Product, self).write(values)
if 'active' in values and not values['active']:
products = self.mapped('orderpoint_ids').filtered(lambda r: r.active).mapped('product_id')
if products:
msg = _('You still have some active reordering rules on this product. Please archive or delete them first.')
msg += '\n\n'
for product in products:
msg += '- %s \n' % product.display_name
raise UserError(msg)
return res
if 'active' in values:
self.filtered(lambda p: p.active != values['active']).with_context(active_test=False).orderpoint_ids.write({
'active': values['active']
})
return super().write(values)
def _get_rules_from_location(self, location, seen_rules=False):
if not seen_rules:
@@ -611,14 +606,14 @@ class ProductTemplate(models.Model):
def _compute_quantities_dict(self):
# TDE FIXME: why not using directly the function fields ?
variants_available = self.mapped('product_variant_ids')._product_available()
variants_available = self.with_context(active_test=False).mapped('product_variant_ids')._product_available()
prod_available = {}
for template in self:
qty_available = 0
virtual_available = 0
incoming_qty = 0
outgoing_qty = 0
for p in template.product_variant_ids:
for p in template.with_context(active_test=False).product_variant_ids:
qty_available += variants_available[p.id]["qty_available"]
virtual_available += variants_available[p.id]["virtual_available"]
incoming_qty += variants_available[p.id]["incoming_qty"]
@@ -711,7 +706,7 @@ class ProductTemplate(models.Model):
# Be aware that the exact same function exists in product.product
def action_open_quants(self):
return self.product_variant_ids.action_open_quants()
return self.with_context(active_test=False).product_variant_ids.filtered(lambda p: p.qty_available != 0).action_open_quants()
def action_update_quantity_on_hand(self):
advanced_option_groups = [
@@ -773,8 +768,9 @@ class ProductTemplate(models.Model):
def action_product_tmpl_forecast_report(self):
action = self.env.ref('stock.report_stock_quantity_action_product').read()[0]
product_ids = self.with_context(active_test=False).product_variant_ids.filtered(lambda p: p.virtual_available != 0)
action['domain'] = [
('product_id', 'in', self.product_variant_ids.ids),
('product_id', 'in', product_ids.ids)
]
return action
+2 -1
View File
@@ -224,8 +224,9 @@ class Inventory(models.Model):
domain = [('company_id', '=', self.company_id.id),
('quantity', '!=', '0'),
('product_id.active', '=', True),
('location_id', 'in', locations_ids)]
if self.prefill_counted_quantity == 'zero':
domain.append(('product_id.active', '=', True))
if self.product_ids:
domain = expression.AND([domain, [('product_id', 'in', self.product_ids.ids)]])
+23
View File
@@ -5,6 +5,7 @@
# Copyright 2015 Camptocamp SA
from odoo.addons.stock.tests.common2 import TestStockCommon
from odoo.tests.common import Form
class TestVirtualAvailable(TestStockCommon):
@@ -81,3 +82,25 @@ class TestVirtualAvailable(TestStockCommon):
self.picking_out_2.do_unreserve()
# 8 units are available again
self.assertAlmostEqual(40.0, self.product_3.free_qty)
def test_archive_product_1(self):
"""`qty_available` and `virtual_available` are computed on archived products"""
self.assertTrue(self.product_3.active)
self.assertAlmostEqual(40.0, self.product_3.qty_available)
self.assertAlmostEqual(40.0, self.product_3.virtual_available)
self.product_3.active = False
self.assertAlmostEqual(40.0, self.product_3.qty_available)
self.assertAlmostEqual(40.0, self.product_3.virtual_available)
def test_archive_product_2(self):
"""Archiving a product should archive its reordering rules"""
self.assertTrue(self.product_3.active)
orderpoint_form = Form(self.env['stock.warehouse.orderpoint'])
orderpoint_form.product_id = self.product_3
orderpoint_form.location_id = self.env.ref('stock.stock_location_stock')
orderpoint_form.product_min_qty = 0.0
orderpoint_form.product_max_qty = 5.0
orderpoint = orderpoint_form.save()
self.assertTrue(orderpoint.active)
self.product_3.active = False
self.assertFalse(orderpoint.active)
+3 -2
View File
@@ -260,7 +260,8 @@ class StockMove(models.Model):
todo_valued_moves._sanity_check_for_valuation()
stock_valuation_layers |= getattr(todo_valued_moves, '_create_%s_svl' % valued_type)()
for svl in stock_valuation_layers.with_context(active_test=False):
for svl in stock_valuation_layers:
if not svl.product_id.valuation == 'real_time':
continue
if svl.currency_id.is_zero(svl.value):
@@ -272,7 +273,7 @@ class StockMove(models.Model):
# For every in move, run the vacuum for the linked product.
products_to_vacuum = valued_moves['in'].mapped('product_id')
company = valued_moves['in'].mapped('company_id') and valued_moves['in'].mapped('company_id')[0] or self.env.company
for product_to_vacuum in products_to_vacuum.with_context(active_test=False):
for product_to_vacuum in products_to_vacuum:
product_to_vacuum._run_fifo_vacuum(company)
return res
@@ -13,7 +13,6 @@ class StockValuationLayer(models.Model):
_rec_name = 'product_id'
active = fields.Boolean(related='product_id.active')
company_id = fields.Many2one('res.company', 'Company', readonly=True, required=True)
product_id = fields.Many2one('product.product', 'Product', readonly=True, required=True, check_company=True)
categ_id = fields.Many2one('product.category', related='product_id.categ_id')
@@ -83,7 +83,6 @@
<field name="categ_id" />
<field name="product_tmpl_id" />
<separator/>
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
<group expand='0' string='Group by...'>
<filter string='Product' name="group_by_product_id" context="{'group_by': 'product_id'}"/>
</group>