mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
[IMP] stock_cycle_count: improved inventory_accuracy calculation
This commit is contained in:
@@ -29,25 +29,12 @@ class StockInventory(models.Model):
|
||||
)
|
||||
inventory_accuracy = fields.Float(
|
||||
string="Accuracy",
|
||||
compute="_compute_inventory_accuracy",
|
||||
digits=(3, 2),
|
||||
store=True,
|
||||
group_operator="avg",
|
||||
default=False,
|
||||
)
|
||||
|
||||
@api.depends("state", "stock_quant_ids")
|
||||
def _compute_inventory_accuracy(self):
|
||||
for inv in self:
|
||||
theoretical = sum(inv.stock_quant_ids.mapped(lambda x: abs(x.quantity)))
|
||||
abs_discrepancy = sum(
|
||||
inv.stock_quant_ids.mapped(lambda x: abs(x.inventory_diff_quantity))
|
||||
)
|
||||
if theoretical:
|
||||
inv.inventory_accuracy = max(
|
||||
PERCENT * (theoretical - abs_discrepancy) / theoretical, 0.0
|
||||
)
|
||||
if not inv.stock_quant_ids and inv.state == "done":
|
||||
inv.inventory_accuracy = PERCENT
|
||||
|
||||
def _update_cycle_state(self):
|
||||
for inv in self:
|
||||
@@ -61,6 +48,26 @@ class StockInventory(models.Model):
|
||||
("location_id", "in", self.location_ids.ids),
|
||||
]
|
||||
|
||||
def _calculate_inventory_accuracy(self):
|
||||
for inv in self:
|
||||
accuracy = 100
|
||||
sum_line_accuracy = 0
|
||||
sum_theoretical_qty = 0
|
||||
if inv.stock_move_ids:
|
||||
for line in inv.stock_move_ids:
|
||||
sum_line_accuracy += line.theoretical_qty * line.line_accuracy
|
||||
sum_theoretical_qty += line.theoretical_qty
|
||||
if sum_theoretical_qty != 0:
|
||||
accuracy = (sum_line_accuracy / sum_theoretical_qty) * 100
|
||||
else:
|
||||
accuracy = 0
|
||||
inv.update(
|
||||
{
|
||||
"inventory_accuracy": accuracy,
|
||||
}
|
||||
)
|
||||
return False
|
||||
|
||||
def _link_to_planned_cycle_count(self):
|
||||
self.ensure_one()
|
||||
domain = self._domain_cycle_count_candidate()
|
||||
@@ -85,11 +92,13 @@ class StockInventory(models.Model):
|
||||
|
||||
def action_state_to_done(self):
|
||||
res = super().action_state_to_done()
|
||||
self._calculate_inventory_accuracy()
|
||||
self._update_cycle_state()
|
||||
return res
|
||||
|
||||
def action_force_done(self):
|
||||
res = super().action_force_done()
|
||||
self._calculate_inventory_accuracy()
|
||||
self._update_cycle_state()
|
||||
return res
|
||||
|
||||
|
||||
@@ -77,6 +77,9 @@ class TestStockCycleCount(common.TransactionCase):
|
||||
cls.product1 = cls.product_model.create(
|
||||
{"name": "Test Product 1", "type": "product", "default_code": "PROD1"}
|
||||
)
|
||||
self.product2 = self.product_model.create(
|
||||
{"name": "Test Product 2", "type": "product", "default_code": "PROD2"}
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _create_user(cls, login, groups, company):
|
||||
@@ -352,5 +355,124 @@ class TestStockCycleCount(common.TransactionCase):
|
||||
with self.assertRaises(ValidationError):
|
||||
inventory.exclude_sublocation = False
|
||||
company = self.env["res.company"].create({"name": "Test"})
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
inventory.company_id = company
|
||||
|
||||
def test_inventory_adjustment_accuracy(self):
|
||||
date = datetime.today() - timedelta(days=1)
|
||||
# Create location
|
||||
loc = self.stock_location_model.create(
|
||||
{"name": "Test Location", "usage": "internal"}
|
||||
)
|
||||
# Create stock quants for specific location
|
||||
quant1 = self.quant_model.create(
|
||||
{
|
||||
"product_id": self.product1.id,
|
||||
"location_id": loc.id,
|
||||
"quantity": 10.0,
|
||||
}
|
||||
)
|
||||
quant2 = self.quant_model.create(
|
||||
{
|
||||
"product_id": self.product2.id,
|
||||
"location_id": loc.id,
|
||||
"quantity": 15.0,
|
||||
}
|
||||
)
|
||||
# Create adjustments for specific location
|
||||
adjustment = self.inventory_model.create(
|
||||
{
|
||||
"name": "Pre-existing inventory",
|
||||
"location_ids": [(4, loc.id)],
|
||||
"date": date,
|
||||
}
|
||||
)
|
||||
# Start the adjustment
|
||||
adjustment.action_state_to_in_progress()
|
||||
# Check that there are stock quants for the specific location
|
||||
self.assertTrue(self.env["stock.quant"].search([("location_id", "=", loc.id)]))
|
||||
# Make the count of the stock
|
||||
quant1.update(
|
||||
{
|
||||
"inventory_quantity": 5,
|
||||
}
|
||||
)
|
||||
quant2.update(
|
||||
{
|
||||
"inventory_quantity": 10,
|
||||
}
|
||||
)
|
||||
# Apply the changes
|
||||
quant1._apply_inventory()
|
||||
quant2._apply_inventory()
|
||||
# Check that line_accuracy is calculated properly
|
||||
sml = self.env["stock.move.line"].search(
|
||||
[("location_id", "=", loc.id), ("product_id", "=", self.product1.id)]
|
||||
)
|
||||
self.assertEqual(sml.line_accuracy, 0.5)
|
||||
sml = self.env["stock.move.line"].search(
|
||||
[("location_id", "=", loc.id), ("product_id", "=", self.product2.id)]
|
||||
)
|
||||
self.assertEqual(sml.line_accuracy, 0.6667000000000001)
|
||||
# Set Inventory Adjustment to Done
|
||||
adjustment.action_state_to_done()
|
||||
# Check that accuracy is correctly calculated
|
||||
self.assertEqual(adjustment.inventory_accuracy, 60)
|
||||
|
||||
def test_zero_inventory_adjustment_accuracy(self):
|
||||
date = datetime.today() - timedelta(days=1)
|
||||
# Create location
|
||||
loc = self.stock_location_model.create(
|
||||
{"name": "Test Location", "usage": "internal"}
|
||||
)
|
||||
# Create stock quants for specific location
|
||||
quant1 = self.quant_model.create(
|
||||
{
|
||||
"product_id": self.product1.id,
|
||||
"location_id": loc.id,
|
||||
"quantity": 0.0,
|
||||
}
|
||||
)
|
||||
quant2 = self.quant_model.create(
|
||||
{
|
||||
"product_id": self.product2.id,
|
||||
"location_id": loc.id,
|
||||
"quantity": 300.0,
|
||||
}
|
||||
)
|
||||
# Create adjustment for specific location
|
||||
adjustment = self.inventory_model.create(
|
||||
{
|
||||
"name": "Pre-existing inventory qty zero",
|
||||
"location_ids": [(4, loc.id)],
|
||||
"date": date,
|
||||
}
|
||||
)
|
||||
# Start the adjustment
|
||||
adjustment.action_state_to_in_progress()
|
||||
# Check that there are stock quants for the specific location
|
||||
self.assertTrue(self.env["stock.quant"].search([("location_id", "=", loc.id)]))
|
||||
# Make the count of the stock
|
||||
quant1.update(
|
||||
{
|
||||
"inventory_quantity": 5,
|
||||
}
|
||||
)
|
||||
quant2.update(
|
||||
{
|
||||
"inventory_quantity": 0,
|
||||
}
|
||||
)
|
||||
# Apply the changes
|
||||
quant1._apply_inventory()
|
||||
quant2._apply_inventory()
|
||||
# Check that line_accuracy is calculated properly
|
||||
sml = self.env["stock.move.line"].search(
|
||||
[("location_id", "=", loc.id), ("product_id", "=", self.product1.id)]
|
||||
)
|
||||
self.assertEqual(sml.line_accuracy, 0)
|
||||
# Set Inventory Adjustment to Done
|
||||
adjustment.action_state_to_done()
|
||||
# Check that accuracy is correctly calculated
|
||||
self.assertEqual(adjustment.inventory_accuracy, 0)
|
||||
|
||||
Reference in New Issue
Block a user