From 5c67fbb7c89bfae6c4681411407e2333c1677a1e Mon Sep 17 00:00:00 2001 From: ArnauCForgeFlow Date: Wed, 17 Apr 2024 16:09:25 +0200 Subject: [PATCH 01/18] [15.0][IMP] stock_cycle_count: add auto complete into cron --- stock_cycle_count/models/stock_cycle_count.py | 27 ++++++++++++++++ .../models/stock_cycle_count_rule.py | 2 +- stock_cycle_count/models/stock_inventory.py | 10 ++++++ stock_cycle_count/models/stock_location.py | 2 +- stock_cycle_count/models/stock_warehouse.py | 22 +++++++++++-- .../tests/test_stock_cycle_count.py | 32 +++++++++++++++---- .../views/stock_cycle_count_view.xml | 1 + 7 files changed, 85 insertions(+), 11 deletions(-) diff --git a/stock_cycle_count/models/stock_cycle_count.py b/stock_cycle_count/models/stock_cycle_count.py index 89384fca8..e1b40d30d 100644 --- a/stock_cycle_count/models/stock_cycle_count.py +++ b/stock_cycle_count/models/stock_cycle_count.py @@ -32,6 +32,21 @@ class StockCycleCount(models.Model): readonly=True, states={"draft": [("readonly", False)]}, tracking=True, + compute="_compute_date_deadline", + inverse="_inverse_date_deadline", + store=True, + ) + automatic_deadline_date = fields.Date( + string="Automatic Required Date", + readonly=True, + states={"draft": [("readonly", False)]}, + tracking=True, + ) + manual_deadline_date = fields.Date( + string="Manual Required Date", + readonly=True, + states={"draft": [("readonly", False)]}, + tracking=True, ) cycle_count_rule_id = fields.Many2one( comodel_name="stock.cycle.count.rule", @@ -124,3 +139,15 @@ class StockCycleCount(models.Model): action["views"] = [(res and res.id or False, "form")] action["res_id"] = adjustment_ids and adjustment_ids[0] or False return action + + @api.depends("automatic_deadline_date", "manual_deadline_date") + def _compute_date_deadline(self): + for rec in self: + if rec.manual_deadline_date: + rec.date_deadline = rec.manual_deadline_date + else: + rec.date_deadline = rec.automatic_deadline_date + + def _inverse_date_deadline(self): + for rec in self: + rec.manual_deadline_date = rec.date_deadline diff --git a/stock_cycle_count/models/stock_cycle_count_rule.py b/stock_cycle_count/models/stock_cycle_count_rule.py index 15f7a7545..e585f59a5 100644 --- a/stock_cycle_count/models/stock_cycle_count_rule.py +++ b/stock_cycle_count/models/stock_cycle_count_rule.py @@ -172,7 +172,7 @@ class StockCycleCountRule(models.Model): .search( [ ("location_ids", "in", [loc.id]), - ("state", "in", ["confirm", "done", "draft"]), + ("state", "in", ["in_progress", "done", "draft"]), ], order="date desc", limit=1, diff --git a/stock_cycle_count/models/stock_inventory.py b/stock_cycle_count/models/stock_inventory.py index 9993856bf..dc82bcf0f 100644 --- a/stock_cycle_count/models/stock_inventory.py +++ b/stock_cycle_count/models/stock_inventory.py @@ -144,3 +144,13 @@ class StockInventory(models.Model): message=msg, ) ) + + def action_state_to_in_progress(self): + res = super().action_state_to_in_progress() + self.stock_quant_ids.update( + { + "user_id": self.cycle_count_id.responsible_id, + "inventory_date": self.cycle_count_id.date_deadline, + } + ) + return res diff --git a/stock_cycle_count/models/stock_location.py b/stock_cycle_count/models/stock_location.py index 2a09fc760..a03e30e4a 100644 --- a/stock_cycle_count/models/stock_location.py +++ b/stock_cycle_count/models/stock_location.py @@ -106,7 +106,7 @@ class StockLocation(models.Model): ) self.env["stock.cycle.count"].create( { - "date_deadline": date, + "automatic_deadline_date": date, "location_id": self.id, "cycle_count_rule_id": rule.id, "state": "draft", diff --git a/stock_cycle_count/models/stock_warehouse.py b/stock_cycle_count/models/stock_warehouse.py index 49eaa1028..afe05f636 100644 --- a/stock_cycle_count/models/stock_warehouse.py +++ b/stock_cycle_count/models/stock_warehouse.py @@ -70,7 +70,7 @@ class StockWarehouse(models.Model): @api.model def _prepare_cycle_count(self, cycle_count_proposed): return { - "date_deadline": cycle_count_proposed["date"], + "automatic_deadline_date": cycle_count_proposed["date"], "location_id": cycle_count_proposed["location"].id, "cycle_count_rule_id": cycle_count_proposed["rule_type"].id, "state": "draft", @@ -133,7 +133,7 @@ class StockWarehouse(models.Model): ) cc_to_update.write( { - "date_deadline": cycle_count_proposed_date, + "automatic_deadline_date": cycle_count_proposed_date, "cycle_count_rule_id": cycle_count_proposed["rule_type"].id, } ) @@ -144,6 +144,24 @@ class StockWarehouse(models.Model): try: whs = self.search([]) whs.action_compute_cycle_count_rules() + today = fields.Date.today() + cycle_counts = self.env["stock.cycle.count"].search( + [("date_deadline", "<=", today), ("state", "=", "draft")] + ) + for cycle_count in cycle_counts: + open_cycle_counts = self.env["stock.cycle.count"].search( + [ + ("location_id", "=", cycle_count.location_id.id), + ("state", "=", "open"), + ] + ) + if open_cycle_counts: + continue + cycle_count.action_create_inventory_adjustment() + try: + cycle_count.stock_adjustment_ids.action_state_to_in_progress() + except Exception as e: + _logger.info("Error when beginning an adjustment: %s", str(e)) except Exception as e: _logger.info("Error while running stock_cycle_count cron job: %s", str(e)) raise diff --git a/stock_cycle_count/tests/test_stock_cycle_count.py b/stock_cycle_count/tests/test_stock_cycle_count.py index 449fcd8dd..7266aa13b 100644 --- a/stock_cycle_count/tests/test_stock_cycle_count.py +++ b/stock_cycle_count/tests/test_stock_cycle_count.py @@ -153,7 +153,7 @@ class TestStockCycleCount(common.TransactionCase): "name": "To be cancelled when running cron job.", "cycle_count_rule_id": self.rule_periodic.id, "location_id": loc.id, - "date_deadline": date_pre_existing_cc, + "automatic_deadline_date": date_pre_existing_cc, } ) self.assertEqual( @@ -188,14 +188,32 @@ class TestStockCycleCount(common.TransactionCase): move1._action_assign() move1.move_line_ids[0].qty_done = 1.0 move1._action_done() + # Remove the pre_existing_count + self.inventory_model.search( + [("cycle_count_id", "=", pre_existing_count.id)], limit=1 + ).unlink() + pre_existing_count.unlink() + # Execute cron for first time wh.cron_cycle_count() - self.assertNotEqual( - pre_existing_count.date_deadline, - date_pre_existing_cc, - "Date of pre-existing cycle counts has not been " "updated.", + # There are counts in state open(execution) and not in state draft + open_counts = self.cycle_count_model.search( + [("location_id", "in", locs.ids), ("state", "=", "open")] ) - counts = self.cycle_count_model.search([("location_id", "in", locs.ids)]) - self.assertTrue(counts, "Cycle counts not planned") + self.assertTrue(open_counts, "Cycle counts in execution state") + draft_counts = self.cycle_count_model.search( + [("location_id", "in", locs.ids), ("state", "=", "draft")] + ) + self.assertFalse(draft_counts, "No Cycle counts in draft state") + # Execute the cron for second time + wh.cron_cycle_count() + # New cycle counts for same location created in draft state + draft_counts = self.cycle_count_model.search( + [("location_id", "in", locs.ids), ("state", "=", "draft")] + ) + self.assertTrue(draft_counts, "No Cycle counts in draft state") + # Inventory adjustment only started for cycle counts in open state + self.assertTrue(open_counts.stock_adjustment_ids) + self.assertFalse(draft_counts.stock_adjustment_ids) # Zero-confirmations: count = self.cycle_count_model.search( [ diff --git a/stock_cycle_count/views/stock_cycle_count_view.xml b/stock_cycle_count/views/stock_cycle_count_view.xml index e4495efeb..c607433bd 100644 --- a/stock_cycle_count/views/stock_cycle_count_view.xml +++ b/stock_cycle_count/views/stock_cycle_count_view.xml @@ -10,6 +10,7 @@ From 7a06f33f0b36086ea05b54d45ad855a3cc2bafd4 Mon Sep 17 00:00:00 2001 From: Joan Sisquella Date: Mon, 29 Apr 2024 12:03:09 +0200 Subject: [PATCH 02/18] [ADD] stock_cycle_count: line_accuracy in stock move lines --- stock_cycle_count/__manifest__.py | 1 + stock_cycle_count/models/__init__.py | 2 + stock_cycle_count/models/stock_move_line.py | 15 +++++ stock_cycle_count/models/stock_quant.py | 47 ++++++++++++++++ .../views/stock_move_line_view.xml | 55 +++++++++++++++++++ 5 files changed, 120 insertions(+) create mode 100644 stock_cycle_count/models/stock_move_line.py create mode 100644 stock_cycle_count/models/stock_quant.py create mode 100644 stock_cycle_count/views/stock_move_line_view.xml diff --git a/stock_cycle_count/__manifest__.py b/stock_cycle_count/__manifest__.py index 9d0e6aab7..2fb52fae1 100644 --- a/stock_cycle_count/__manifest__.py +++ b/stock_cycle_count/__manifest__.py @@ -17,6 +17,7 @@ "views/stock_warehouse_view.xml", "views/stock_inventory_view.xml", "views/stock_location_view.xml", + "views/stock_move_line_view.xml", "views/res_config_settings_view.xml", "data/cycle_count_sequence.xml", "data/cycle_count_ir_cron.xml", diff --git a/stock_cycle_count/models/__init__.py b/stock_cycle_count/models/__init__.py index e1320941d..9a1035894 100644 --- a/stock_cycle_count/models/__init__.py +++ b/stock_cycle_count/models/__init__.py @@ -6,3 +6,5 @@ from . import stock_location from . import stock_inventory from . import stock_warehouse from . import stock_move +from . import stock_move_line +from . import stock_quant diff --git a/stock_cycle_count/models/stock_move_line.py b/stock_cycle_count/models/stock_move_line.py new file mode 100644 index 000000000..23b4184bb --- /dev/null +++ b/stock_cycle_count/models/stock_move_line.py @@ -0,0 +1,15 @@ +# Copyright 2024 ForgeFlow S.L. +# (http://www.forgeflow.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo import fields, models + + +class StockMoveLine(models.Model): + _inherit = "stock.move.line" + + line_accuracy = fields.Float( + string="Accuracy", + store=True, + ) + theoretical_qty = fields.Float(string="Theoretical Quantity", store=True) + counted_qty = fields.Float(string="Counted Quantity", store=True) diff --git a/stock_cycle_count/models/stock_quant.py b/stock_cycle_count/models/stock_quant.py new file mode 100644 index 000000000..bd5f367d0 --- /dev/null +++ b/stock_cycle_count/models/stock_quant.py @@ -0,0 +1,47 @@ +# Copyright 2024 ForgeFlow S.L. +# (http://www.forgeflow.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html) +from odoo import models + + +class StockQuant(models.Model): + _inherit = "stock.quant" + + def _apply_inventory(self): + accuracy_dict = {} + theoretical_dict = {} + counted_dict = {} + for rec in self: + if rec.discrepancy_percent > 100: + line_accuracy = 0 + else: + line_accuracy = 1 - (rec.discrepancy_percent / 100) + accuracy_dict[rec.id] = line_accuracy + theoretical_dict[rec.id] = rec.quantity + counted_dict[rec.id] = rec.inventory_quantity + res = super()._apply_inventory() + for rec in self: + record_moves = self.env["stock.move.line"] + moves = record_moves.search( + [ + ("product_id", "=", rec.product_id.id), + ("lot_id", "=", rec.lot_id.id), + "|", + ("location_id", "=", rec.location_id.id), + ("location_dest_id", "=", rec.location_id.id), + ], + order="create_date asc", + ).filtered( + lambda x: not x.company_id.id + or not rec.company_id.id + or rec.company_id.id == x.company_id.id + ) + move = moves[len(moves) - 1] + move.write( + { + "line_accuracy": accuracy_dict[rec.id], + "theoretical_qty": theoretical_dict[rec.id], + "counted_qty": counted_dict[rec.id], + } + ) + return res diff --git a/stock_cycle_count/views/stock_move_line_view.xml b/stock_cycle_count/views/stock_move_line_view.xml new file mode 100644 index 000000000..d05ec3711 --- /dev/null +++ b/stock_cycle_count/views/stock_move_line_view.xml @@ -0,0 +1,55 @@ + + + + + Stock Move Line Tree - cycle count extension + stock.move.line + + + + + + + + + + + + Stock Move Line Form - cycle count extension + stock.move.line + + + + + + + + + + + From b864b957c0ef749b4c732f8573219ac3640d5c2c Mon Sep 17 00:00:00 2001 From: ArnauCForgeFlow Date: Fri, 3 May 2024 15:57:19 +0200 Subject: [PATCH 03/18] [IMP] stock_cycle_count: improved inventory_accuracy calculation --- stock_cycle_count/models/stock_inventory.py | 37 ++++-- .../tests/test_stock_cycle_count.py | 122 ++++++++++++++++++ 2 files changed, 145 insertions(+), 14 deletions(-) diff --git a/stock_cycle_count/models/stock_inventory.py b/stock_cycle_count/models/stock_inventory.py index dc82bcf0f..e9472932a 100644 --- a/stock_cycle_count/models/stock_inventory.py +++ b/stock_cycle_count/models/stock_inventory.py @@ -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 diff --git a/stock_cycle_count/tests/test_stock_cycle_count.py b/stock_cycle_count/tests/test_stock_cycle_count.py index 7266aa13b..6299f2a25 100644 --- a/stock_cycle_count/tests/test_stock_cycle_count.py +++ b/stock_cycle_count/tests/test_stock_cycle_count.py @@ -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) From 5e8f02a79cf2ca6110d78a7e10ff719af2a3dad0 Mon Sep 17 00:00:00 2001 From: Joan Sisquella Date: Fri, 10 May 2024 12:17:07 +0200 Subject: [PATCH 04/18] [FIX] stock_cyle_count: remove duplicated field location_ids in tree view --- stock_cycle_count/views/stock_inventory_view.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/stock_cycle_count/views/stock_inventory_view.xml b/stock_cycle_count/views/stock_inventory_view.xml index 1fe85b044..aaf709c65 100644 --- a/stock_cycle_count/views/stock_inventory_view.xml +++ b/stock_cycle_count/views/stock_inventory_view.xml @@ -8,7 +8,6 @@ - From ef33ed1b2f84536f00ac188f90106fbaba9e7f97 Mon Sep 17 00:00:00 2001 From: ArnauCForgeFlow Date: Tue, 28 May 2024 18:36:25 +0200 Subject: [PATCH 05/18] [IMP] stock_cycle_count: start inv adjustments depending on setting --- stock_cycle_count/models/stock_cycle_count.py | 9 ++++++++- stock_cycle_count/models/stock_inventory.py | 1 - stock_cycle_count/models/stock_warehouse.py | 4 ---- stock_cycle_count/tests/test_stock_cycle_count.py | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/stock_cycle_count/models/stock_cycle_count.py b/stock_cycle_count/models/stock_cycle_count.py index e1b40d30d..8f9a633fc 100644 --- a/stock_cycle_count/models/stock_cycle_count.py +++ b/stock_cycle_count/models/stock_cycle_count.py @@ -1,10 +1,13 @@ # Copyright 2017-18 ForgeFlow S.L. # (http://www.forgeflow.com) # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +import logging from odoo import _, api, fields, models from odoo.exceptions import UserError +_logger = logging.getLogger(__name__) + class StockCycleCount(models.Model): _name = "stock.cycle.count" @@ -117,7 +120,11 @@ class StockCycleCount(models.Model): inv.prefill_counted_quantity = ( rec.company_id.inventory_adjustment_counted_quantities ) - inv.action_state_to_in_progress() + try: + inv.action_state_to_in_progress() + except Exception as e: + _logger.info("Error when beginning an adjustment: %s", str(e)) + if inv.prefill_counted_quantity == "zero": inv.stock_quant_ids.write({"inventory_quantity": 0}) else: diff --git a/stock_cycle_count/models/stock_inventory.py b/stock_cycle_count/models/stock_inventory.py index e9472932a..fb6838e73 100644 --- a/stock_cycle_count/models/stock_inventory.py +++ b/stock_cycle_count/models/stock_inventory.py @@ -35,7 +35,6 @@ class StockInventory(models.Model): default=False, ) - def _update_cycle_state(self): for inv in self: if inv.cycle_count_id and inv.state == "done": diff --git a/stock_cycle_count/models/stock_warehouse.py b/stock_cycle_count/models/stock_warehouse.py index afe05f636..db84413d1 100644 --- a/stock_cycle_count/models/stock_warehouse.py +++ b/stock_cycle_count/models/stock_warehouse.py @@ -158,10 +158,6 @@ class StockWarehouse(models.Model): if open_cycle_counts: continue cycle_count.action_create_inventory_adjustment() - try: - cycle_count.stock_adjustment_ids.action_state_to_in_progress() - except Exception as e: - _logger.info("Error when beginning an adjustment: %s", str(e)) except Exception as e: _logger.info("Error while running stock_cycle_count cron job: %s", str(e)) raise diff --git a/stock_cycle_count/tests/test_stock_cycle_count.py b/stock_cycle_count/tests/test_stock_cycle_count.py index 6299f2a25..74926a33e 100644 --- a/stock_cycle_count/tests/test_stock_cycle_count.py +++ b/stock_cycle_count/tests/test_stock_cycle_count.py @@ -77,7 +77,7 @@ 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( + cls.product2 = cls.product_model.create( {"name": "Test Product 2", "type": "product", "default_code": "PROD2"} ) From 996bb8ccc9fc935475e8f7e6c5d042283d38203c Mon Sep 17 00:00:00 2001 From: ArnauCForgeFlow Date: Tue, 25 Jun 2024 15:40:25 +0200 Subject: [PATCH 06/18] [FIX] stock_cycle_count: prevent creating infinte cycle counts --- stock_cycle_count/models/stock_warehouse.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/stock_cycle_count/models/stock_warehouse.py b/stock_cycle_count/models/stock_warehouse.py index db84413d1..8585dfc1c 100644 --- a/stock_cycle_count/models/stock_warehouse.py +++ b/stock_cycle_count/models/stock_warehouse.py @@ -106,12 +106,17 @@ class StockWarehouse(models.Model): cycle_count_proposed = next( filter(lambda x: x["date"] == earliest_date, proposed_for_loc) ) - self._handle_existing_cycle_counts(loc, cycle_count_proposed) + existing_cycle_counts = self._handle_existing_cycle_counts( + loc, cycle_count_proposed + ) delta = ( fields.Datetime.from_string(cycle_count_proposed["date"]) - datetime.today() ) - if delta.days < self.cycle_count_planning_horizon: + if ( + not existing_cycle_counts + and delta.days < self.cycle_count_planning_horizon + ): cc_vals = self._prepare_cycle_count(cycle_count_proposed) cc_vals_list.append(cc_vals) return cc_vals_list @@ -137,6 +142,7 @@ class StockWarehouse(models.Model): "cycle_count_rule_id": cycle_count_proposed["rule_type"].id, } ) + return existing_cycle_counts @api.model def cron_cycle_count(self): From f06842fcb0c691142454178ddf351817a1b98db1 Mon Sep 17 00:00:00 2001 From: ArnauCForgeFlow Date: Mon, 22 Jul 2024 13:49:01 +0200 Subject: [PATCH 07/18] [FIX] stock_cycle_count: prefill_counted_quantity zero, counted, false --- stock_cycle_count/models/stock_cycle_count.py | 9 --------- stock_cycle_count/models/stock_inventory.py | 8 ++++++++ stock_cycle_count/static/description/index.html | 11 +++++++---- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/stock_cycle_count/models/stock_cycle_count.py b/stock_cycle_count/models/stock_cycle_count.py index 8f9a633fc..843a41b97 100644 --- a/stock_cycle_count/models/stock_cycle_count.py +++ b/stock_cycle_count/models/stock_cycle_count.py @@ -117,19 +117,10 @@ class StockCycleCount(models.Model): data = rec._prepare_inventory_adjustment() inv = self.env["stock.inventory"].create(data) if rec.company_id.auto_start_inventory_from_cycle_count: - inv.prefill_counted_quantity = ( - rec.company_id.inventory_adjustment_counted_quantities - ) try: inv.action_state_to_in_progress() except Exception as e: _logger.info("Error when beginning an adjustment: %s", str(e)) - - if inv.prefill_counted_quantity == "zero": - inv.stock_quant_ids.write({"inventory_quantity": 0}) - else: - for quant in inv.stock_quant_ids: - quant.write({"inventory_quantity": quant.quantity}) self.write({"state": "open"}) return True diff --git a/stock_cycle_count/models/stock_inventory.py b/stock_cycle_count/models/stock_inventory.py index fb6838e73..b82133af4 100644 --- a/stock_cycle_count/models/stock_inventory.py +++ b/stock_cycle_count/models/stock_inventory.py @@ -155,10 +155,18 @@ class StockInventory(models.Model): def action_state_to_in_progress(self): res = super().action_state_to_in_progress() + self.prefill_counted_quantity = ( + self.company_id.inventory_adjustment_counted_quantities + ) self.stock_quant_ids.update( { "user_id": self.cycle_count_id.responsible_id, "inventory_date": self.cycle_count_id.date_deadline, } ) + if self.prefill_counted_quantity == "zero": + self.stock_quant_ids.write({"inventory_quantity": 0}) + elif self.prefill_counted_quantity == "counted": + for quant in self.stock_quant_ids: + quant.write({"inventory_quantity": quant.quantity}) return res diff --git a/stock_cycle_count/static/description/index.html b/stock_cycle_count/static/description/index.html index 62c20ad9b..3d59554a2 100644 --- a/stock_cycle_count/static/description/index.html +++ b/stock_cycle_count/static/description/index.html @@ -8,10 +8,11 @@ /* :Author: David Goodger (goodger@python.org) -:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $ +:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. +Despite the name, some widely supported CSS2 features are used. See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to customize this style sheet. @@ -274,7 +275,7 @@ pre.literal-block, pre.doctest-block, pre.math, pre.code { margin-left: 2em ; margin-right: 2em } -pre.code .ln { color: grey; } /* line numbers */ +pre.code .ln { color: gray; } /* line numbers */ pre.code, code { background-color: #eeeeee } pre.code .comment, code .comment { color: #5C6576 } pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } @@ -300,7 +301,7 @@ span.option { span.pre { white-space: pre } -span.problematic { +span.problematic, pre.problematic { color: red } span.section-subtitle { @@ -513,7 +514,9 @@ If you spotted it first, help us to smash it by providing a detailed and welcome

Maintainers

This module is maintained by the OCA.

-Odoo Community Association + +Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

From a958cab5c9e5b4e82f805c1aa66a5ad5ecedb1d8 Mon Sep 17 00:00:00 2001 From: ArnauCForgeFlow Date: Wed, 24 Jul 2024 11:02:53 +0200 Subject: [PATCH 08/18] [FIX] stock_cycle_count: inventory adjustment already adds these fields --- stock_cycle_count/models/stock_inventory.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/stock_cycle_count/models/stock_inventory.py b/stock_cycle_count/models/stock_inventory.py index b82133af4..7a6026776 100644 --- a/stock_cycle_count/models/stock_inventory.py +++ b/stock_cycle_count/models/stock_inventory.py @@ -158,12 +158,6 @@ class StockInventory(models.Model): self.prefill_counted_quantity = ( self.company_id.inventory_adjustment_counted_quantities ) - self.stock_quant_ids.update( - { - "user_id": self.cycle_count_id.responsible_id, - "inventory_date": self.cycle_count_id.date_deadline, - } - ) if self.prefill_counted_quantity == "zero": self.stock_quant_ids.write({"inventory_quantity": 0}) elif self.prefill_counted_quantity == "counted": From c5fd916a6974191e3b5698063bc41259eb9caa76 Mon Sep 17 00:00:00 2001 From: ArnauCForgeFlow Date: Wed, 24 Jul 2024 15:02:09 +0200 Subject: [PATCH 09/18] [IMP] stock_cycle_count: added more tests --- .../tests/test_stock_cycle_count.py | 100 +++++++++++++++++- 1 file changed, 97 insertions(+), 3 deletions(-) diff --git a/stock_cycle_count/tests/test_stock_cycle_count.py b/stock_cycle_count/tests/test_stock_cycle_count.py index 74926a33e..575465842 100644 --- a/stock_cycle_count/tests/test_stock_cycle_count.py +++ b/stock_cycle_count/tests/test_stock_cycle_count.py @@ -58,10 +58,13 @@ class TestStockCycleCount(common.TransactionCase): ] cls.big_wh.write({"cycle_count_rule_ids": [(6, 0, cls.rule_ids)]}) - # Create a location: + # Create locations: cls.count_loc = cls.stock_location_model.create( {"name": "Place", "usage": "production"} ) + cls.count_loc_2 = cls.stock_location_model.create( + {"name": "Place 2", "usage": "production"} + ) cls.stock_location_model._parent_store_compute() # Create a cycle count: @@ -438,7 +441,7 @@ class TestStockCycleCount(common.TransactionCase): { "product_id": self.product2.id, "location_id": loc.id, - "quantity": 300.0, + "quantity": 0.0, } ) # Create adjustment for specific location @@ -461,7 +464,7 @@ class TestStockCycleCount(common.TransactionCase): ) quant2.update( { - "inventory_quantity": 0, + "inventory_quantity": 10, } ) # Apply the changes @@ -476,3 +479,94 @@ class TestStockCycleCount(common.TransactionCase): adjustment.action_state_to_done() # Check that accuracy is correctly calculated self.assertEqual(adjustment.inventory_accuracy, 0) + # Check discrepancy over 100% + adjustment_2 = self.inventory_model.create( + { + "name": "Adjustment 2", + "location_ids": [(4, loc.id)], + "date": date, + } + ) + adjustment_2.action_state_to_in_progress() + quant1.update( + { + "inventory_quantity": 1500, + } + ) + quant1._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)] + ) + # Check that line_accuracy is still 0 + self.assertEqual(sml.line_accuracy, 0) + + def test_auto_start_inventory_from_cycle_count(self): + # Set the auto_start_inventory_from_cycle_count rule to True + self.company.auto_start_inventory_from_cycle_count = True + # Create Cycle Count 1 cont_loc_2 + cycle_count_1 = self.cycle_count_model.create( + { + "name": "Cycle Count 1", + "cycle_count_rule_id": self.rule_periodic.id, + "location_id": self.count_loc_2.id, + "date_deadline": "2026-11-30", + "manual_deadline_date": "2026-11-30", + } + ) + cycle_count_1.flush() + # Confirm the Cycle Count + cycle_count_1.action_create_inventory_adjustment() + # Inventory adjustments change their state to in_progress + self.assertEqual(cycle_count_1.stock_adjustment_ids.state, "in_progress") + + def test_prefill_counted_quantity(self): + self.company.inventory_adjustment_counted_quantities = "counted" + date = datetime.today() - timedelta(days=1) + # Create locations + loc_1 = self.stock_location_model.create( + {"name": "Test Location 1", "usage": "internal"} + ) + loc_2 = self.stock_location_model.create( + {"name": "Test Location 2", "usage": "internal"} + ) + # Create stock quants for different locations + quant_1 = self.quant_model.create( + { + "product_id": self.product1.id, + "location_id": loc_1.id, + "quantity": 25, + } + ) + quant_2 = self.quant_model.create( + { + "product_id": self.product1.id, + "location_id": loc_2.id, + "quantity": 50, + } + ) + # Create adjustments for different locations + adjustment_1 = self.inventory_model.create( + { + "name": "Adjustment Location 1", + "location_ids": [(4, loc_1.id)], + "date": date, + } + ) + adjustment_2 = self.inventory_model.create( + { + "name": "Adjustment Location 2", + "location_ids": [(4, loc_2.id)], + "date": date, + } + ) + # Start the adjustment 1 with prefill quantity as counted + adjustment_1.action_state_to_in_progress() + # Check that the inventory_quantity is 25 + self.assertEqual(quant_1.inventory_quantity, 25) + # Change company prefill option to zero + self.company.inventory_adjustment_counted_quantities = "zero" + # Start the adjustment 2 with prefill quantity as zero + adjustment_2.action_state_to_in_progress() + # Check that the inventory_quantity is 0 + self.assertEqual(quant_2.inventory_quantity, 0.0) From 2e7a818aa3f920913e25eb09f879e1d18ba50b46 Mon Sep 17 00:00:00 2001 From: Joan Sisquella Date: Wed, 31 Jul 2024 11:32:47 +0200 Subject: [PATCH 10/18] [IMP] stock_cycle_count: Enable edit and cascade updates for responsible_id --- stock_cycle_count/models/stock_cycle_count.py | 13 +++- stock_cycle_count/models/stock_inventory.py | 20 +++++++ .../static/description/index.html | 11 ++-- .../tests/test_stock_cycle_count.py | 60 +++++++++++++++++++ 4 files changed, 96 insertions(+), 8 deletions(-) diff --git a/stock_cycle_count/models/stock_cycle_count.py b/stock_cycle_count/models/stock_cycle_count.py index 843a41b97..d1b4b243e 100644 --- a/stock_cycle_count/models/stock_cycle_count.py +++ b/stock_cycle_count/models/stock_cycle_count.py @@ -27,7 +27,7 @@ class StockCycleCount(models.Model): comodel_name="res.users", string="Assigned to", readonly=True, - states={"draft": [("readonly", False)]}, + states={"draft": [("readonly", False)], "open": [("readonly", False)]}, tracking=True, ) date_deadline = fields.Date( @@ -84,6 +84,17 @@ class StockCycleCount(models.Model): readonly=True, ) + def write(self, vals): + result = super().write(vals) + if "responsible_id" in vals and not self.env.context.get("no_propagate"): + stock_inventory_records = self.mapped("stock_adjustment_ids") + for record in stock_inventory_records: + if record.responsible_id.id != vals["responsible_id"]: + record.with_context(no_propagate=True).write( + {"responsible_id": vals["responsible_id"]} + ) + return result + @api.depends("stock_adjustment_ids") def _compute_inventory_adj_count(self): for rec in self: diff --git a/stock_cycle_count/models/stock_inventory.py b/stock_cycle_count/models/stock_inventory.py index 7a6026776..74fabd2d0 100644 --- a/stock_cycle_count/models/stock_inventory.py +++ b/stock_cycle_count/models/stock_inventory.py @@ -34,6 +34,26 @@ class StockInventory(models.Model): group_operator="avg", default=False, ) + responsible_id = fields.Many2one( + states={"draft": [("readonly", False)], "in_progress": [("readonly", False)]}, + tracking=True, + ) + + def write(self, vals): + result = super().write(vals) + if "responsible_id" in vals: + if not self.env.context.get("no_propagate"): + if ( + self.cycle_count_id + and self.cycle_count_id.responsible_id.id != vals["responsible_id"] + ): + self.cycle_count_id.with_context(no_propagate=True).write( + {"responsible_id": vals["responsible_id"]} + ) + for quant in self.mapped("stock_quant_ids"): + if quant.user_id.id != vals["responsible_id"]: + quant.write({"user_id": vals["responsible_id"]}) + return result def _update_cycle_state(self): for inv in self: diff --git a/stock_cycle_count/static/description/index.html b/stock_cycle_count/static/description/index.html index 3d59554a2..62c20ad9b 100644 --- a/stock_cycle_count/static/description/index.html +++ b/stock_cycle_count/static/description/index.html @@ -8,11 +8,10 @@ /* :Author: David Goodger (goodger@python.org) -:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $ +:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. -Despite the name, some widely supported CSS2 features are used. See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to customize this style sheet. @@ -275,7 +274,7 @@ pre.literal-block, pre.doctest-block, pre.math, pre.code { margin-left: 2em ; margin-right: 2em } -pre.code .ln { color: gray; } /* line numbers */ +pre.code .ln { color: grey; } /* line numbers */ pre.code, code { background-color: #eeeeee } pre.code .comment, code .comment { color: #5C6576 } pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } @@ -301,7 +300,7 @@ span.option { span.pre { white-space: pre } -span.problematic, pre.problematic { +span.problematic { color: red } span.section-subtitle { @@ -514,9 +513,7 @@ If you spotted it first, help us to smash it by providing a detailed and welcome

Maintainers

This module is maintained by the OCA.

- -Odoo Community Association - +Odoo Community Association

OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

diff --git a/stock_cycle_count/tests/test_stock_cycle_count.py b/stock_cycle_count/tests/test_stock_cycle_count.py index 575465842..b671ae31a 100644 --- a/stock_cycle_count/tests/test_stock_cycle_count.py +++ b/stock_cycle_count/tests/test_stock_cycle_count.py @@ -570,3 +570,63 @@ class TestStockCycleCount(common.TransactionCase): adjustment_2.action_state_to_in_progress() # Check that the inventory_quantity is 0 self.assertEqual(quant_2.inventory_quantity, 0.0) + + def test_responsible_id_propagation_with_inventory_adjustment(self): + additional_user = self._create_user( + "user_3", [self.g_stock_manager], self.company + ) + additional_user_2 = self._create_user( + "user_4", [self.g_stock_manager], self.company + ) + self.cycle_count_1.responsible_id = self.manager + self.assertEqual( + self.cycle_count_1.responsible_id.id, + self.manager, + "Initial responsible not correctly assigned.", + ) + self.quant_model.create( + { + "product_id": self.product1.id, + "location_id": self.count_loc.id, + "quantity": 100, + } + ) + self.cycle_count_1.action_create_inventory_adjustment() + inventory = self.cycle_count_1.stock_adjustment_ids[0] + self.assertEqual( + inventory.responsible_id.id, + self.cycle_count_1.responsible_id.id, + "Inventory responsible does not match cycle count responsible.", + ) + for quant in inventory.stock_quant_ids: + self.assertEqual( + quant.user_id.id, + inventory.responsible_id.id, + "Quant user does not match inventory responsible.", + ) + self.cycle_count_1.responsible_id = additional_user.id + inventory.invalidate_cache() + self.cycle_count_1.stock_adjustment_ids[0].stock_quant_ids.invalidate_cache() + self.assertEqual( + inventory.responsible_id.id, + additional_user.id, + "Inventory responsible not updated after cycle count responsible change.", + ) + for quant in inventory.stock_quant_ids: + self.assertEqual( + quant.user_id.id, + additional_user.id, + "Quant user not updated after inventory responsible change.", + ) + inventory.responsible_id = additional_user_2 + self.assertEqual( + self.cycle_count_1.responsible_id.id, + additional_user_2.id, + "Cycle Count not updated after inventory responsible change.", + ) + for quant in inventory.stock_quant_ids: + self.assertEqual( + quant.user_id.id, + additional_user_2.id, + "Quant user not updated after inventory responsible change.", + ) From 5c1442e0e336cea3e00c044d3b455373df200471 Mon Sep 17 00:00:00 2001 From: ArnauCForgeFlow Date: Fri, 6 Sep 2024 09:18:23 +0200 Subject: [PATCH 11/18] [IMP] stock_cycle_count: added new group by required date --- stock_cycle_count/static/description/index.html | 11 +++++++---- stock_cycle_count/views/stock_cycle_count_view.xml | 8 +++++++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/stock_cycle_count/static/description/index.html b/stock_cycle_count/static/description/index.html index 62c20ad9b..3d59554a2 100644 --- a/stock_cycle_count/static/description/index.html +++ b/stock_cycle_count/static/description/index.html @@ -8,10 +8,11 @@ /* :Author: David Goodger (goodger@python.org) -:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $ +:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. +Despite the name, some widely supported CSS2 features are used. See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to customize this style sheet. @@ -274,7 +275,7 @@ pre.literal-block, pre.doctest-block, pre.math, pre.code { margin-left: 2em ; margin-right: 2em } -pre.code .ln { color: grey; } /* line numbers */ +pre.code .ln { color: gray; } /* line numbers */ pre.code, code { background-color: #eeeeee } pre.code .comment, code .comment { color: #5C6576 } pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } @@ -300,7 +301,7 @@ span.option { span.pre { white-space: pre } -span.problematic { +span.problematic, pre.problematic { color: red } span.section-subtitle { @@ -513,7 +514,9 @@ If you spotted it first, help us to smash it by providing a detailed and welcome

Maintainers

This module is maintained by the OCA.

-Odoo Community Association + +Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

diff --git a/stock_cycle_count/views/stock_cycle_count_view.xml b/stock_cycle_count/views/stock_cycle_count_view.xml index c607433bd..84f85148f 100644 --- a/stock_cycle_count/views/stock_cycle_count_view.xml +++ b/stock_cycle_count/views/stock_cycle_count_view.xml @@ -151,6 +151,12 @@ domain="[]" context="{'group_by':'responsible_id'}" /> + @@ -162,7 +168,7 @@ tree,form {'search_default_planned':1,'search_default_execution':1} + >{'search_default_planned':1,'search_default_execution':1,'search_default_date_deadline':1} Date: Tue, 10 Sep 2024 15:08:56 +0200 Subject: [PATCH 12/18] [IMP] stock_cycle_count: add multicompany rule --- stock_cycle_count/__manifest__.py | 1 + stock_cycle_count/security/security.xml | 11 +++++++++++ stock_cycle_count/views/stock_cycle_count_view.xml | 1 + 3 files changed, 13 insertions(+) create mode 100644 stock_cycle_count/security/security.xml diff --git a/stock_cycle_count/__manifest__.py b/stock_cycle_count/__manifest__.py index 2fb52fae1..98a3aa23f 100644 --- a/stock_cycle_count/__manifest__.py +++ b/stock_cycle_count/__manifest__.py @@ -24,6 +24,7 @@ "reports/stock_location_accuracy_report.xml", "reports/stock_cycle_count_report.xml", "security/ir.model.access.csv", + "security/security.xml", ], "license": "AGPL-3", "installable": True, diff --git a/stock_cycle_count/security/security.xml b/stock_cycle_count/security/security.xml new file mode 100644 index 000000000..b369f0667 --- /dev/null +++ b/stock_cycle_count/security/security.xml @@ -0,0 +1,11 @@ + + + + Stock Cycle Count multi-company + + + ['|',('company_id','=',False),('company_id', 'in', company_ids)] + + diff --git a/stock_cycle_count/views/stock_cycle_count_view.xml b/stock_cycle_count/views/stock_cycle_count_view.xml index 84f85148f..5eacdd102 100644 --- a/stock_cycle_count/views/stock_cycle_count_view.xml +++ b/stock_cycle_count/views/stock_cycle_count_view.xml @@ -18,6 +18,7 @@ + From c61ba7a0d1eec00c3171fc97be2ab8c0b74c42b9 Mon Sep 17 00:00:00 2001 From: Joan Sisquella Date: Tue, 17 Sep 2024 13:02:00 +0200 Subject: [PATCH 13/18] [IMP] stock_cycle_count: remove auto confirmation logic from the cron --- stock_cycle_count/models/stock_warehouse.py | 14 -------------- .../static/description/index.html | 11 ++++------- .../tests/test_stock_cycle_count.py | 19 ------------------- .../views/stock_cycle_count_view.xml | 4 +++- 4 files changed, 7 insertions(+), 41 deletions(-) diff --git a/stock_cycle_count/models/stock_warehouse.py b/stock_cycle_count/models/stock_warehouse.py index 8585dfc1c..469ed0ec7 100644 --- a/stock_cycle_count/models/stock_warehouse.py +++ b/stock_cycle_count/models/stock_warehouse.py @@ -150,20 +150,6 @@ class StockWarehouse(models.Model): try: whs = self.search([]) whs.action_compute_cycle_count_rules() - today = fields.Date.today() - cycle_counts = self.env["stock.cycle.count"].search( - [("date_deadline", "<=", today), ("state", "=", "draft")] - ) - for cycle_count in cycle_counts: - open_cycle_counts = self.env["stock.cycle.count"].search( - [ - ("location_id", "=", cycle_count.location_id.id), - ("state", "=", "open"), - ] - ) - if open_cycle_counts: - continue - cycle_count.action_create_inventory_adjustment() except Exception as e: _logger.info("Error while running stock_cycle_count cron job: %s", str(e)) raise diff --git a/stock_cycle_count/static/description/index.html b/stock_cycle_count/static/description/index.html index 3d59554a2..62c20ad9b 100644 --- a/stock_cycle_count/static/description/index.html +++ b/stock_cycle_count/static/description/index.html @@ -8,11 +8,10 @@ /* :Author: David Goodger (goodger@python.org) -:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $ +:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. -Despite the name, some widely supported CSS2 features are used. See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to customize this style sheet. @@ -275,7 +274,7 @@ pre.literal-block, pre.doctest-block, pre.math, pre.code { margin-left: 2em ; margin-right: 2em } -pre.code .ln { color: gray; } /* line numbers */ +pre.code .ln { color: grey; } /* line numbers */ pre.code, code { background-color: #eeeeee } pre.code .comment, code .comment { color: #5C6576 } pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } @@ -301,7 +300,7 @@ span.option { span.pre { white-space: pre } -span.problematic, pre.problematic { +span.problematic { color: red } span.section-subtitle { @@ -514,9 +513,7 @@ If you spotted it first, help us to smash it by providing a detailed and welcome

Maintainers

This module is maintained by the OCA.

- -Odoo Community Association - +Odoo Community Association

OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

diff --git a/stock_cycle_count/tests/test_stock_cycle_count.py b/stock_cycle_count/tests/test_stock_cycle_count.py index b671ae31a..5b5eb9e8d 100644 --- a/stock_cycle_count/tests/test_stock_cycle_count.py +++ b/stock_cycle_count/tests/test_stock_cycle_count.py @@ -201,25 +201,6 @@ class TestStockCycleCount(common.TransactionCase): pre_existing_count.unlink() # Execute cron for first time wh.cron_cycle_count() - # There are counts in state open(execution) and not in state draft - open_counts = self.cycle_count_model.search( - [("location_id", "in", locs.ids), ("state", "=", "open")] - ) - self.assertTrue(open_counts, "Cycle counts in execution state") - draft_counts = self.cycle_count_model.search( - [("location_id", "in", locs.ids), ("state", "=", "draft")] - ) - self.assertFalse(draft_counts, "No Cycle counts in draft state") - # Execute the cron for second time - wh.cron_cycle_count() - # New cycle counts for same location created in draft state - draft_counts = self.cycle_count_model.search( - [("location_id", "in", locs.ids), ("state", "=", "draft")] - ) - self.assertTrue(draft_counts, "No Cycle counts in draft state") - # Inventory adjustment only started for cycle counts in open state - self.assertTrue(open_counts.stock_adjustment_ids) - self.assertFalse(draft_counts.stock_adjustment_ids) # Zero-confirmations: count = self.cycle_count_model.search( [ diff --git a/stock_cycle_count/views/stock_cycle_count_view.xml b/stock_cycle_count/views/stock_cycle_count_view.xml index 5eacdd102..09b7ff117 100644 --- a/stock_cycle_count/views/stock_cycle_count_view.xml +++ b/stock_cycle_count/views/stock_cycle_count_view.xml @@ -11,6 +11,7 @@ decoration-muted="state == 'cancelled'" decoration-info="state == 'draft'" multi_edit="1" + default_order="date_deadline asc" > @@ -126,6 +127,7 @@ domain="[('state','=', 'cancelled')]" help="Cycle Counts Cancelled" /> + tree,form {'search_default_planned':1,'search_default_execution':1,'search_default_date_deadline':1} + >{'search_default_planned':1,'search_default_execution':1} Date: Wed, 25 Sep 2024 10:41:02 +0200 Subject: [PATCH 14/18] [FIX] stock_cycle_count: fixed tests since having initial quantity 0 isnt possible --- .../static/description/index.html | 11 +++++++---- .../tests/test_stock_cycle_count.py | 18 +++++++++++------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/stock_cycle_count/static/description/index.html b/stock_cycle_count/static/description/index.html index 62c20ad9b..3d59554a2 100644 --- a/stock_cycle_count/static/description/index.html +++ b/stock_cycle_count/static/description/index.html @@ -8,10 +8,11 @@ /* :Author: David Goodger (goodger@python.org) -:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $ +:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. +Despite the name, some widely supported CSS2 features are used. See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to customize this style sheet. @@ -274,7 +275,7 @@ pre.literal-block, pre.doctest-block, pre.math, pre.code { margin-left: 2em ; margin-right: 2em } -pre.code .ln { color: grey; } /* line numbers */ +pre.code .ln { color: gray; } /* line numbers */ pre.code, code { background-color: #eeeeee } pre.code .comment, code .comment { color: #5C6576 } pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } @@ -300,7 +301,7 @@ span.option { span.pre { white-space: pre } -span.problematic { +span.problematic, pre.problematic { color: red } span.section-subtitle { @@ -513,7 +514,9 @@ If you spotted it first, help us to smash it by providing a detailed and welcome

Maintainers

This module is maintained by the OCA.

-Odoo Community Association + +Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

diff --git a/stock_cycle_count/tests/test_stock_cycle_count.py b/stock_cycle_count/tests/test_stock_cycle_count.py index 5b5eb9e8d..d96992984 100644 --- a/stock_cycle_count/tests/test_stock_cycle_count.py +++ b/stock_cycle_count/tests/test_stock_cycle_count.py @@ -415,14 +415,14 @@ class TestStockCycleCount(common.TransactionCase): { "product_id": self.product1.id, "location_id": loc.id, - "quantity": 0.0, + "quantity": 15.0, } ) quant2 = self.quant_model.create( { "product_id": self.product2.id, "location_id": loc.id, - "quantity": 0.0, + "quantity": 10.0, } ) # Create adjustment for specific location @@ -440,22 +440,26 @@ class TestStockCycleCount(common.TransactionCase): # Make the count of the stock quant1.update( { - "inventory_quantity": 5, + "inventory_quantity": 0, } ) quant2.update( { - "inventory_quantity": 10, + "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)] + move_1 = adjustment.stock_move_ids.filtered( + lambda c: c.product_id == self.product1 ) - self.assertEqual(sml.line_accuracy, 0) + move_2 = adjustment.stock_move_ids.filtered( + lambda c: c.product_id == self.product1 + ) + self.assertEqual(move_1.line_accuracy, 0) + self.assertEqual(move_2.line_accuracy, 0) # Set Inventory Adjustment to Done adjustment.action_state_to_done() # Check that accuracy is correctly calculated From f17e438dea2168a6904c210ddf47cc54d5cd9e5a Mon Sep 17 00:00:00 2001 From: Joan Sisquella Date: Tue, 22 Oct 2024 16:07:58 +0200 Subject: [PATCH 15/18] [IMP] stock_cycle_count: Refactor responsible_id sync using compute/inverse fields --- stock_cycle_count/models/stock_cycle_count.py | 11 ----------- stock_cycle_count/models/stock_inventory.py | 19 ++++++++++++++++++- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/stock_cycle_count/models/stock_cycle_count.py b/stock_cycle_count/models/stock_cycle_count.py index d1b4b243e..d39eb72e7 100644 --- a/stock_cycle_count/models/stock_cycle_count.py +++ b/stock_cycle_count/models/stock_cycle_count.py @@ -84,17 +84,6 @@ class StockCycleCount(models.Model): readonly=True, ) - def write(self, vals): - result = super().write(vals) - if "responsible_id" in vals and not self.env.context.get("no_propagate"): - stock_inventory_records = self.mapped("stock_adjustment_ids") - for record in stock_inventory_records: - if record.responsible_id.id != vals["responsible_id"]: - record.with_context(no_propagate=True).write( - {"responsible_id": vals["responsible_id"]} - ) - return result - @api.depends("stock_adjustment_ids") def _compute_inventory_adj_count(self): for rec in self: diff --git a/stock_cycle_count/models/stock_inventory.py b/stock_cycle_count/models/stock_inventory.py index 74fabd2d0..c7304ed56 100644 --- a/stock_cycle_count/models/stock_inventory.py +++ b/stock_cycle_count/models/stock_inventory.py @@ -35,10 +35,27 @@ class StockInventory(models.Model): default=False, ) responsible_id = fields.Many2one( - states={"draft": [("readonly", False)], "in_progress": [("readonly", False)]}, tracking=True, + compute="_compute_responsible_id", + inverse="_inverse_responsible_id", + store=True, + readonly=False, ) + @api.depends("cycle_count_id.responsible_id") + def _compute_responsible_id(self): + for inv in self: + if inv.cycle_count_id: + inv.responsible_id = inv.cycle_count_id.responsible_id + inv.stock_quant_ids.write( + {"user_id": inv.cycle_count_id.responsible_id} + ) + + def _inverse_responsible_id(self): + for inv in self: + if inv.cycle_count_id and inv.responsible_id: + inv.cycle_count_id.responsible_id = inv.responsible_id + def write(self, vals): result = super().write(vals) if "responsible_id" in vals: From ecdeba21c7cd9477e6e17221722442e5a2006369 Mon Sep 17 00:00:00 2001 From: ArnauCForgeFlow Date: Fri, 15 Nov 2024 12:58:08 +0100 Subject: [PATCH 16/18] [IMP] stock_cycle_count: Remove useless filtered --- stock_cycle_count/models/stock_quant.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/stock_cycle_count/models/stock_quant.py b/stock_cycle_count/models/stock_quant.py index bd5f367d0..39e6183b5 100644 --- a/stock_cycle_count/models/stock_quant.py +++ b/stock_cycle_count/models/stock_quant.py @@ -31,10 +31,6 @@ class StockQuant(models.Model): ("location_dest_id", "=", rec.location_id.id), ], order="create_date asc", - ).filtered( - lambda x: not x.company_id.id - or not rec.company_id.id - or rec.company_id.id == x.company_id.id ) move = moves[len(moves) - 1] move.write( From 490c19abe72298462ed1c8a00510bd777a761b97 Mon Sep 17 00:00:00 2001 From: Joan Sisquella Date: Thu, 21 Nov 2024 12:50:27 +0100 Subject: [PATCH 17/18] [FIX] stock_cycle_count: use company of the location when creating cycle_count --- stock_cycle_count/models/stock_cycle_count_rule.py | 1 + stock_cycle_count/models/stock_warehouse.py | 1 + 2 files changed, 2 insertions(+) diff --git a/stock_cycle_count/models/stock_cycle_count_rule.py b/stock_cycle_count/models/stock_cycle_count_rule.py index e585f59a5..06e2129d0 100644 --- a/stock_cycle_count/models/stock_cycle_count_rule.py +++ b/stock_cycle_count/models/stock_cycle_count_rule.py @@ -160,6 +160,7 @@ class StockCycleCountRule(models.Model): "date": fields.Datetime.from_string(date), "location": location, "rule_type": self, + "company_id": location.company_id, } return cycle_count diff --git a/stock_cycle_count/models/stock_warehouse.py b/stock_cycle_count/models/stock_warehouse.py index 469ed0ec7..4078af000 100644 --- a/stock_cycle_count/models/stock_warehouse.py +++ b/stock_cycle_count/models/stock_warehouse.py @@ -74,6 +74,7 @@ class StockWarehouse(models.Model): "location_id": cycle_count_proposed["location"].id, "cycle_count_rule_id": cycle_count_proposed["rule_type"].id, "state": "draft", + "company_id": cycle_count_proposed["company_id"].id, } def action_compute_cycle_count_rules(self): From 4e1b9c02962a4bcab2fbaea08c75cc90028f2f60 Mon Sep 17 00:00:00 2001 From: ArnauCForgeFlow Date: Thu, 28 Nov 2024 10:08:26 +0100 Subject: [PATCH 18/18] [IMP] stock_cycle_count: add rec company_id to domain --- stock_cycle_count/models/stock_quant.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stock_cycle_count/models/stock_quant.py b/stock_cycle_count/models/stock_quant.py index 39e6183b5..1a4d6608c 100644 --- a/stock_cycle_count/models/stock_quant.py +++ b/stock_cycle_count/models/stock_quant.py @@ -29,7 +29,8 @@ class StockQuant(models.Model): "|", ("location_id", "=", rec.location_id.id), ("location_dest_id", "=", rec.location_id.id), - ], + ] + + ([("company_id", "=", rec.company_id.id)] if rec.company_id else []), order="create_date asc", ) move = moves[len(moves) - 1]