Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 32 additions & 15 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand All @@ -63,4 +80,4 @@ workflows:
- tests
filters:
branches:
only: /^1\d\.0/
only: /^1\d\.0/
11 changes: 11 additions & 0 deletions .codacy.yml
Original file line number Diff line number Diff line change
@@ -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'
12 changes: 12 additions & 0 deletions gitoo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down Expand Up @@ -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"
Expand Down
10 changes: 10 additions & 0 deletions stock_serial_single_quant/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
------------

Expand Down
2 changes: 2 additions & 0 deletions stock_serial_single_quant/__init__.py
Original file line number Diff line number Diff line change
@@ -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
10 changes: 8 additions & 2 deletions stock_serial_single_quant/__manifest__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
# © 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",
"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,
}
5 changes: 4 additions & 1 deletion stock_serial_single_quant/models/__init__.py
Original file line number Diff line number Diff line change
@@ -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 # noqa: F401
from . import stock_move_line # noqa: F401
from . import stock_production_lot # noqa: F401
from . import stock_warehouse # noqa: F401
9 changes: 5 additions & 4 deletions stock_serial_single_quant/models/stock_move.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())
28 changes: 19 additions & 9 deletions stock_serial_single_quant/models/stock_move_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
13 changes: 13 additions & 0 deletions stock_serial_single_quant/models/stock_warehouse.py
Original file line number Diff line number Diff line change
@@ -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,
)
34 changes: 34 additions & 0 deletions stock_serial_single_quant/tests/test_stock_move.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
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)
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)
13 changes: 13 additions & 0 deletions stock_serial_single_quant/views/stock_warehouse.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_warehouse_inherit_stock_serial_single_quant" model="ir.ui.view">
<field name="name">stock.warehouse.form.inherit.serial.single.quant</field>
<field name="model">stock.warehouse</field>
<field name="inherit_id" ref="stock.view_warehouse"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='resupply_wh_ids']" position="after">
<field name="bypass_serial_single_quant"/>
</xpath>
</field>
</record>
</odoo>
Loading