From 32b6e820a306a73ab22e5549f203123f67dbc131 Mon Sep 17 00:00:00 2001 From: Abdellatif Benzbiria Date: Tue, 9 Jun 2026 12:10:46 +0000 Subject: [PATCH 1/6] [14.0][INP] stock_serial_single_quant : bypass constraint by warehouse and fix singleton error --- .circleci/config.yml | 47 +++++++++++++------ .codacy.yml | 11 +++++ stock_serial_single_quant/__manifest__.py | 7 ++- stock_serial_single_quant/models/__init__.py | 5 +- .../models/stock_move.py | 9 ++-- .../models/stock_move_line.py | 28 +++++++---- .../models/stock_warehouse.py | 13 +++++ .../tests/test_stock_move.py | 34 ++++++++++++++ .../views/stock_warehouse_views.xml | 13 +++++ 9 files changed, 136 insertions(+), 31 deletions(-) create mode 100644 .codacy.yml create mode 100644 stock_serial_single_quant/models/stock_warehouse.py create mode 100644 stock_serial_single_quant/views/stock_warehouse_views.xml diff --git a/.circleci/config.yml b/.circleci/config.yml index e59d8566..79ee144f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,43 +8,60 @@ jobs: tests: machine: true steps: - - checkout + - checkout - - run: - <<: *quay_io_login + - run: + <<: *quay_io_login - - run: + - run: name: Build -- Init Database command: docker-compose run --rm odoo odoo --stop-after-init -i main - - run: + - run: name: Setup Log Folder For Reports command: sudo mkdir -p .log && sudo chmod 777 .log - - run: - name: Run Test - command: docker-compose run --rm odoo run_pytest.sh + - run: + name: Run Test and Generate Coverage + # Add the --junitxml parameter to generate test results in a specific subfolder. + # This prevents CircleCI from confusing coverage.xml with JUnit test results. + command: | + docker-compose run --rm odoo bash -c "mkdir -p /var/log/odoo/test-results && cd /var/log/odoo && pytest /mnt/extra-addons -v --disable-warnings --cov=/mnt/extra-addons --cov-branch --cov-report=xml --junitxml=/var/log/odoo/test-results/junit.xml" - - run: + - run: name: Codacy Coverage - command: bash <(curl -Ls https://coverage.codacy.com/get.sh) report -l python -r .log/coverage.xml + # Always run this step to upload coverage even if tests fail + when: always + command: | + # Verify the file is successfully passed to the host machine + if [ -f ".log/coverage.xml" ]; then + bash <(curl -Ls https://coverage.codacy.com/get.sh) report -l python -r .log/coverage.xml + else + echo "Error: coverage.xml not found in .log directory." + exit 1 + fi - - store_test_results: - path: .log + - store_test_results: + # Point strictly to the subfolder containing the JUnit XML + # so CircleCI ignores the coverage.xml file. + path: .log/test-results - # job that find the next tag for the current branch/repo and push the tag to github. - # it will trigger the publish of a new docker image. + # Job that finds the next tag for the current branch/repo and pushes the tag to github. + # It will trigger the publish of a new docker image. auto-tag: machine: true steps: - checkout + - run: <<: *quay_io_login + - run: name: Get nws command: | curl -L $NWS_BIN_LOCATION > ./nws chmod +x ./nws + - run: name: Set tag command: | @@ -63,4 +80,4 @@ workflows: - tests filters: branches: - only: /^1\d\.0/ + only: /^1\d\.0/ \ No newline at end of file diff --git a/.codacy.yml b/.codacy.yml new file mode 100644 index 00000000..d1e1e566 --- /dev/null +++ b/.codacy.yml @@ -0,0 +1,11 @@ +# .codacy.yml +# Exclude folders and heavy files from Codacy static analysis to prevent Diff/Deltas timeout +exclude_paths: + - '**/data/**' + - '**/static/lib/**' + - '**/tests/**' + - '**/doc/**' + - '**/i18n/**' + - '**/*.csv' + - '**/*.po' + - '**/*.pot' \ No newline at end of file diff --git a/stock_serial_single_quant/__manifest__.py b/stock_serial_single_quant/__manifest__.py index 17a6d519..7246224c 100644 --- a/stock_serial_single_quant/__manifest__.py +++ b/stock_serial_single_quant/__manifest__.py @@ -4,11 +4,14 @@ { "name": "Stock Serial Single Quant", "summary": "Add constraints on serial numbers", - "version": "1.0.0", + "version": "14.0.1.1.0", "category": "Inventory", "author": "Numigi", "license": "LGPL-3", "depends": ["stock"], - "data": ["views/stock_production_lot.xml"], + "data": [ + "views/stock_production_lot.xml", + "views/stock_warehouse.xml", + ], "installable": True, } diff --git a/stock_serial_single_quant/models/__init__.py b/stock_serial_single_quant/models/__init__.py index dbee5082..b9518dae 100644 --- a/stock_serial_single_quant/models/__init__.py +++ b/stock_serial_single_quant/models/__init__.py @@ -1,4 +1,7 @@ # © Numigi (tm) and all its contributors (https://numigi.com/r/home) # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -from . import stock_move, stock_move_line, stock_production_lot +from . import stock_move +from . import stock_move_line +from . import stock_production_lot +from . import stock_warehouse diff --git a/stock_serial_single_quant/models/stock_move.py b/stock_serial_single_quant/models/stock_move.py index 5e384b51..68c42764 100644 --- a/stock_serial_single_quant/models/stock_move.py +++ b/stock_serial_single_quant/models/stock_move.py @@ -17,8 +17,9 @@ def _has_serialized_product(self): return self.product_id.tracking == "serial" def _check_serial_number_constraints(self): - lines_with_existing_quants = self.move_line_ids.filtered( - lambda l: bool(l.lot_id.sudo().get_positive_quants()) - ) - for line in lines_with_existing_quants: + lines_to_check = self._get_lines_to_check() + for line in lines_to_check: line.check_serial_number_constraints() + + def _get_lines_to_check(self): + return self.move_line_ids.filtered(lambda line: line._requires_serial_check()) diff --git a/stock_serial_single_quant/models/stock_move_line.py b/stock_serial_single_quant/models/stock_move_line.py index c11c5d36..a94e825c 100644 --- a/stock_serial_single_quant/models/stock_move_line.py +++ b/stock_serial_single_quant/models/stock_move_line.py @@ -58,17 +58,27 @@ def check_serial_number_constraints(self): self._check_serial_number_source_package() self._check_serial_number_source_owner() + def _requires_serial_check(self): + dest_warehouse = self.location_dest_id.get_warehouse() + is_bypassed = dest_warehouse.bypass_serial_single_quant + has_quants = bool(self.lot_id.sudo().get_positive_quants()) + return not is_bypassed and has_quants + def _check_serial_number_source_location(self): - serial_location = self.lot_id.get_current_location() - if serial_location != self.location_id: - raise ValidationError( - _(WRONG_LOCATION_MESSAGE).format( - serial=self.lot_id.name, - product=self.product_id.display_name, - location=self.location_id.display_name, - serial_location=serial_location.display_name, - ) + serial_locations = self.lot_id.get_current_location() + if self.location_id not in serial_locations: + self._raise_wrong_location_error(serial_locations) + + def _raise_wrong_location_error(self, serial_locations): + location_names = ", ".join(serial_locations.mapped("display_name")) + raise ValidationError( + _(WRONG_LOCATION_MESSAGE).format( + serial=self.lot_id.name, + product=self.product_id.display_name, + location=self.location_id.display_name, + serial_location=location_names, ) + ) def _check_serial_number_source_package(self): serial_package = self.lot_id.get_current_package() diff --git a/stock_serial_single_quant/models/stock_warehouse.py b/stock_serial_single_quant/models/stock_warehouse.py new file mode 100644 index 00000000..09d50de1 --- /dev/null +++ b/stock_serial_single_quant/models/stock_warehouse.py @@ -0,0 +1,13 @@ +# © Numigi (tm) and all its contributors (https://numigi.com/r/home) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo import fields, models + + +class StockWarehouse(models.Model): + _inherit = "stock.warehouse" + + bypass_serial_single_quant = fields.Boolean( + string="Bypass Serial Constraints", + default=False, + ) diff --git a/stock_serial_single_quant/tests/test_stock_move.py b/stock_serial_single_quant/tests/test_stock_move.py index 02d3423e..e612fe2c 100644 --- a/stock_serial_single_quant/tests/test_stock_move.py +++ b/stock_serial_single_quant/tests/test_stock_move.py @@ -109,3 +109,37 @@ def test_expected_source_owner(self): self.quant_1.owner_id = self.owner_1 with pytest.raises(ValidationError): self.move_serial_number(self.serial_1, self.location_1, self.location_2) + + def test_bypass_serial_single_quant_enabled(self): + # Enable the bypass on the destination warehouse + self.warehouse_3.bypass_serial_single_quant = True + + # Move the serial number from an invalid location (location_2) + # to the bypassed warehouse (location_3) + # It should NOT raise a ValidationError because the check is bypassed + move = self.move_serial_number(self.serial_1, self.location_2, self.location_3) + + assert move.state == "done" + assert self.serial_1.get_current_location() == self.location_3 + + def test_multi_quant_raises_validation_error_not_singleton(self): + # Create a second quant for the same serial in a different location (incoherence) + self.make_quant(self.location_2, self.serial_1) + + # Now serial_1 is physically in both location_1 and location_2. + # Moving it from a completely different location (location_1a) should raise a + # ValidationError and NOT a ValueError (Expected Singleton) + with pytest.raises(ValidationError) as excinfo: + self.move_serial_number(self.serial_1, self.location_1a, self.location_3) + + # Verify that both locations are correctly joined and displayed in the error message + error_message = str(excinfo.value) + assert self.location_1.display_name in error_message + assert self.location_2.display_name in error_message + + def test_bypass_disabled_still_raises_error(self): + # Ensure that by default (bypass = False), the error is still raised + self.warehouse_3.bypass_serial_single_quant = False + + with pytest.raises(ValidationError): + self.move_serial_number(self.serial_1, self.location_2, self.location_3) diff --git a/stock_serial_single_quant/views/stock_warehouse_views.xml b/stock_serial_single_quant/views/stock_warehouse_views.xml new file mode 100644 index 00000000..3ada93ea --- /dev/null +++ b/stock_serial_single_quant/views/stock_warehouse_views.xml @@ -0,0 +1,13 @@ + + + + stock.warehouse.form.inherit.serial.single.quant + stock.warehouse + + + + + + + + \ No newline at end of file From eb3719d1885cadf93096ff0e06f11414cc7034c0 Mon Sep 17 00:00:00 2001 From: Abdellatif Benzbiria Date: Tue, 9 Jun 2026 12:15:03 +0000 Subject: [PATCH 2/6] [14.0][INP] stock_serial_single_quant : update Readme --- stock_serial_single_quant/README.rst | 10 ++++++++++ .../{stock_warehouse_views.xml => stock_warehouse.xml} | 0 2 files changed, 10 insertions(+) rename stock_serial_single_quant/views/{stock_warehouse_views.xml => stock_warehouse.xml} (100%) diff --git a/stock_serial_single_quant/README.rst b/stock_serial_single_quant/README.rst index 65214dae..01b6f12a 100644 --- a/stock_serial_single_quant/README.rst +++ b/stock_serial_single_quant/README.rst @@ -53,6 +53,16 @@ If the serial number has a quant, the owner of the stock move line must match th .. image:: static/description/wrong_owner_message.png +Configuration +------------- +To bypass the constraints for a specific warehouse: + +1. Go to **Inventory > Configuration > Warehouses**. +2. Select the warehouse you want to configure. +3. Check the box **Bypass Serial Constraints**. + +When this option is enabled, stock moves destined to this warehouse will not be subject to the single quant constraints. This is particularly useful for specific logistical flows (like quarantine or transit zones). + Contributors ------------ diff --git a/stock_serial_single_quant/views/stock_warehouse_views.xml b/stock_serial_single_quant/views/stock_warehouse.xml similarity index 100% rename from stock_serial_single_quant/views/stock_warehouse_views.xml rename to stock_serial_single_quant/views/stock_warehouse.xml From b416761269bf1e13205dacac6884a6461c774e00 Mon Sep 17 00:00:00 2001 From: Abdellatif Benzbiria Date: Tue, 9 Jun 2026 12:28:44 +0000 Subject: [PATCH 3/6] [14.0][INP] stock_serial_single_quant : TU --- stock_serial_single_quant/tests/test_stock_move.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stock_serial_single_quant/tests/test_stock_move.py b/stock_serial_single_quant/tests/test_stock_move.py index e612fe2c..e77bac7f 100644 --- a/stock_serial_single_quant/tests/test_stock_move.py +++ b/stock_serial_single_quant/tests/test_stock_move.py @@ -113,14 +113,14 @@ def test_expected_source_owner(self): def test_bypass_serial_single_quant_enabled(self): # Enable the bypass on the destination warehouse self.warehouse_3.bypass_serial_single_quant = True - # Move the serial number from an invalid location (location_2) # to the bypassed warehouse (location_3) # It should NOT raise a ValidationError because the check is bypassed move = self.move_serial_number(self.serial_1, self.location_2, self.location_3) assert move.state == "done" - assert self.serial_1.get_current_location() == self.location_3 + expected_locations = self.location_1 | self.location_3 + assert self.serial_1.get_current_location() == expected_locations def test_multi_quant_raises_validation_error_not_singleton(self): # Create a second quant for the same serial in a different location (incoherence) From 1bc8782d4f3586cab323e92e0d09e9d5ac6d26c7 Mon Sep 17 00:00:00 2001 From: Abdellatif Benzbiria Date: Tue, 9 Jun 2026 12:33:15 +0000 Subject: [PATCH 4/6] [14.0][INP] stock_serial_single_quant : codacy --- stock_serial_single_quant/__init__.py | 2 ++ stock_serial_single_quant/__manifest__.py | 3 +++ stock_serial_single_quant/models/__init__.py | 2 ++ 3 files changed, 7 insertions(+) diff --git a/stock_serial_single_quant/__init__.py b/stock_serial_single_quant/__init__.py index 36ffad66..bd6efb6a 100644 --- a/stock_serial_single_quant/__init__.py +++ b/stock_serial_single_quant/__init__.py @@ -1,4 +1,6 @@ # © Numigi (tm) and all its contributors (https://numigi.com/r/home) # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +"""Init module.""" + from . import models diff --git a/stock_serial_single_quant/__manifest__.py b/stock_serial_single_quant/__manifest__.py index 7246224c..9c0f40b6 100644 --- a/stock_serial_single_quant/__manifest__.py +++ b/stock_serial_single_quant/__manifest__.py @@ -1,6 +1,9 @@ # © Numigi (tm) and all its contributors (https://numigi.com/r/home) # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +# pylint: disable=pointless-statement +# noqa: B018 + { "name": "Stock Serial Single Quant", "summary": "Add constraints on serial numbers", diff --git a/stock_serial_single_quant/models/__init__.py b/stock_serial_single_quant/models/__init__.py index b9518dae..ccb7efba 100644 --- a/stock_serial_single_quant/models/__init__.py +++ b/stock_serial_single_quant/models/__init__.py @@ -1,6 +1,8 @@ # © Numigi (tm) and all its contributors (https://numigi.com/r/home) # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +"""Init module.""" + from . import stock_move from . import stock_move_line from . import stock_production_lot From 4faf5251f7e89f4a8cb2038ff6eb4cce4628d4b5 Mon Sep 17 00:00:00 2001 From: Abdellatif Benzbiria Date: Tue, 9 Jun 2026 12:40:04 +0000 Subject: [PATCH 5/6] [14.0][INP] stock_serial_single_quant : codacy --- stock_serial_single_quant/models/__init__.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/stock_serial_single_quant/models/__init__.py b/stock_serial_single_quant/models/__init__.py index ccb7efba..f97802de 100644 --- a/stock_serial_single_quant/models/__init__.py +++ b/stock_serial_single_quant/models/__init__.py @@ -1,9 +1,7 @@ # © Numigi (tm) and all its contributors (https://numigi.com/r/home) # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -"""Init module.""" - -from . import stock_move -from . import stock_move_line -from . import stock_production_lot -from . import stock_warehouse +from . import stock_move # noqa: F401 +from . import stock_move_line # noqa: F401 +from . import stock_production_lot # noqa: F401 +from . import stock_warehouse # noqa: F401 From 2924bf251751570533470f0c6f3c12b7d2ca75e4 Mon Sep 17 00:00:00 2001 From: Abdellatif Benzbiria Date: Tue, 9 Jun 2026 12:49:11 +0000 Subject: [PATCH 6/6] [14.0][INP] fix Runboat --- gitoo.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/gitoo.yml b/gitoo.yml index 3e9fdeda..c100e442 100644 --- a/gitoo.yml +++ b/gitoo.yml @@ -11,6 +11,12 @@ includes: - account_asset_management +- url: https://github.com/OCA/dms + branch: "14.0" + includes: + - dms + + - url: https://github.com/oca/queue branch: "14.0" includes: ["queue_job"] @@ -40,6 +46,12 @@ branch: "14.0" includes: - onchange_helper + - auditlog + +- url: https://github.com/OCA/server-ux + branch: "14.0" + includes: + - mass_editing - url: https://github.com/OCA/stock-logistics-warehouse branch: "14.0"