diff --git a/stock_cycle_count/__manifest__.py b/stock_cycle_count/__manifest__.py index fe7ae7cbf036..03c29d565d12 100644 --- a/stock_cycle_count/__manifest__.py +++ b/stock_cycle_count/__manifest__.py @@ -17,12 +17,14 @@ "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", "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/models/__init__.py b/stock_cycle_count/models/__init__.py index e1320941dc81..9a1035894393 100644 --- a/stock_cycle_count/models/__init__.py +++ b/stock_cycle_count/models/__init__.py @@ -6,3 +6,5 @@ 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_cycle_count.py b/stock_cycle_count/models/stock_cycle_count.py index 89384fca8f51..d39eb72e74d6 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" @@ -24,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( @@ -32,6 +35,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", @@ -99,15 +117,10 @@ def action_create_inventory_adjustment(self): 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 - ) - inv.action_state_to_in_progress() - 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}) + try: + inv.action_state_to_in_progress() + except Exception as e: + _logger.info("Error when beginning an adjustment: %s", str(e)) self.write({"state": "open"}) return True @@ -124,3 +137,15 @@ def action_view_inventory(self): 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 15f7a75459fb..06e2129d06da 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 @@ def _propose_cycle_count(self, date, location): "date": fields.Datetime.from_string(date), "location": location, "rule_type": self, + "company_id": location.company_id, } return cycle_count @@ -172,7 +173,7 @@ def _compute_rule_periodic(self, locs): .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 9993856bf055..c7304ed56f10 100644 --- a/stock_cycle_count/models/stock_inventory.py +++ b/stock_cycle_count/models/stock_inventory.py @@ -29,25 +29,48 @@ class StockInventory(models.Model): ) inventory_accuracy = fields.Float( string="Accuracy", - compute="_compute_inventory_accuracy", digits=(3, 2), store=True, group_operator="avg", + default=False, + ) + responsible_id = fields.Many2one( + tracking=True, + compute="_compute_responsible_id", + inverse="_inverse_responsible_id", + store=True, + readonly=False, ) - @api.depends("state", "stock_quant_ids") - def _compute_inventory_accuracy(self): + @api.depends("cycle_count_id.responsible_id") + def _compute_responsible_id(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 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} ) - if not inv.stock_quant_ids and inv.state == "done": - inv.inventory_accuracy = PERCENT + + 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: + 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: @@ -61,6 +84,26 @@ def _domain_cycle_count_candidate(self): ("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 +128,13 @@ def _link_to_planned_cycle_count(self): 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 @@ -144,3 +189,15 @@ def _check_cycle_count_consistency(self): message=msg, ) ) + + 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 + ) + 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/models/stock_location.py b/stock_cycle_count/models/stock_location.py index 2a09fc760315..a03e30e4abef 100644 --- a/stock_cycle_count/models/stock_location.py +++ b/stock_cycle_count/models/stock_location.py @@ -106,7 +106,7 @@ def create_zero_confirmation_cycle_count(self): ) 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_move_line.py b/stock_cycle_count/models/stock_move_line.py new file mode 100644 index 000000000000..23b4184bbffa --- /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 000000000000..1a4d6608c121 --- /dev/null +++ b/stock_cycle_count/models/stock_quant.py @@ -0,0 +1,44 @@ +# 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), + ] + + ([("company_id", "=", rec.company_id.id)] if rec.company_id else []), + order="create_date asc", + ) + 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/models/stock_warehouse.py b/stock_cycle_count/models/stock_warehouse.py index 49eaa10288cd..4078af0005b2 100644 --- a/stock_cycle_count/models/stock_warehouse.py +++ b/stock_cycle_count/models/stock_warehouse.py @@ -70,10 +70,11 @@ def _cycle_count_rules_to_compute(self): @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", + "company_id": cycle_count_proposed["company_id"].id, } def action_compute_cycle_count_rules(self): @@ -106,12 +107,17 @@ def _process_cycle_counts(self, proposed_cycle_counts): 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 @@ -133,10 +139,11 @@ def _handle_existing_cycle_counts(self, location, cycle_count_proposed): ) 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, } ) + return existing_cycle_counts @api.model def cron_cycle_count(self): diff --git a/stock_cycle_count/security/security.xml b/stock_cycle_count/security/security.xml new file mode 100644 index 000000000000..b369f0667bd5 --- /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/static/description/index.html b/stock_cycle_count/static/description/index.html index 7cf0ee64c511..17edd7396e84 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 @@ 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.pre { white-space: pre } -span.problematic { +span.problematic, pre.problematic { color: red } span.section-subtitle { @@ -513,7 +514,9 @@

Contributors

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 449fcd8dd36a..d96992984203 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 @@ def setUpClass(cls): ] 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: @@ -77,6 +80,9 @@ def setUpClass(cls): cls.product1 = cls.product_model.create( {"name": "Test Product 1", "type": "product", "default_code": "PROD1"} ) + cls.product2 = cls.product_model.create( + {"name": "Test Product 2", "type": "product", "default_code": "PROD2"} + ) @classmethod def _create_user(cls, login, groups, company): @@ -153,7 +159,7 @@ def test_cycle_count_planner(self): "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 +194,13 @@ def test_cycle_count_planner(self): 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.", - ) - counts = self.cycle_count_model.search([("location_id", "in", locs.ids)]) - self.assertTrue(counts, "Cycle counts not planned") # Zero-confirmations: count = self.cycle_count_model.search( [ @@ -334,5 +339,279 @@ def test_cycle_count_contrains(self): 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": 15.0, + } + ) + quant2 = self.quant_model.create( + { + "product_id": self.product2.id, + "location_id": loc.id, + "quantity": 10.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": 0, + } + ) + quant2.update( + { + "inventory_quantity": 0, + } + ) + # Apply the changes + quant1._apply_inventory() + quant2._apply_inventory() + # Check that line_accuracy is calculated properly + move_1 = adjustment.stock_move_ids.filtered( + lambda c: c.product_id == self.product1 + ) + 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 + 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) + + 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.", + ) diff --git a/stock_cycle_count/views/stock_cycle_count_view.xml b/stock_cycle_count/views/stock_cycle_count_view.xml index e4495efeb26e..09b7ff1175a5 100644 --- a/stock_cycle_count/views/stock_cycle_count_view.xml +++ b/stock_cycle_count/views/stock_cycle_count_view.xml @@ -10,6 +10,8 @@ @@ -17,6 +19,7 @@ + @@ -124,6 +127,7 @@ domain="[('state','=', 'cancelled')]" help="Cycle Counts Cancelled" /> + + diff --git a/stock_cycle_count/views/stock_inventory_view.xml b/stock_cycle_count/views/stock_inventory_view.xml index 1fe85b04415d..aaf709c65eed 100644 --- a/stock_cycle_count/views/stock_inventory_view.xml +++ b/stock_cycle_count/views/stock_inventory_view.xml @@ -8,7 +8,6 @@ - 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 000000000000..d05ec3711022 --- /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 + + + + + + + + + + +