diff --git a/stock_barcodes_serial_source_relocation/README.rst b/stock_barcodes_serial_source_relocation/README.rst new file mode 100644 index 000000000..d75de6ac1 --- /dev/null +++ b/stock_barcodes_serial_source_relocation/README.rst @@ -0,0 +1,65 @@ +======================================= +Stock Barcodes Serial Source Relocation +======================================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:2e5788fe8cb1bd9f1a4f43a27cbc9fa24c9d1b1546cb88e7082e499b42aab687 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-NuoBiT%2Fodoo--addons-lightgray.png?logo=github + :target: https://github.com/NuoBiT/odoo-addons/tree/18.0/stock_barcodes_serial_source_relocation + :alt: NuoBiT/odoo-addons + +|badge1| |badge2| |badge3| + +Allow to always have quantities available when entering a serial product +from the barcodes module. + +**Table of contents** + +.. contents:: + :local: + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* NuoBiT Solutions SL + +Contributors +------------ + +- `NuoBiT `__: + + - Frank Cespedes fcespedes@nuobit.com + - Eric Antones eantones@nuobit.com + - Deniz Gallo dgallo@nuobit.com + +Maintainers +----------- + +This module is part of the `NuoBiT/odoo-addons `_ project on GitHub. + +You are welcome to contribute. diff --git a/stock_barcodes_serial_source_relocation/__init__.py b/stock_barcodes_serial_source_relocation/__init__.py new file mode 100644 index 000000000..a7faefa57 --- /dev/null +++ b/stock_barcodes_serial_source_relocation/__init__.py @@ -0,0 +1,4 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from . import models +from . import wizard diff --git a/stock_barcodes_serial_source_relocation/__manifest__.py b/stock_barcodes_serial_source_relocation/__manifest__.py new file mode 100644 index 000000000..dbc95dbc3 --- /dev/null +++ b/stock_barcodes_serial_source_relocation/__manifest__.py @@ -0,0 +1,17 @@ +# Copyright NuoBiT Solutions - Eric Antones +# Copyright NuoBiT Solutions - Frank Cespedes +# Copyright 2026 NuoBiT Solutions - Deniz Gallo +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +{ + "name": "Stock Barcodes Serial Source Relocation", + "summary": "Allow to always have quantities available when entering a serial " + "product from the barcodes module.", + "version": "18.0.1.0.0", + "author": "NuoBiT Solutions SL", + "website": "https://github.com/NuoBiT/odoo-addons", + "category": "Extra Tools", + "depends": ["stock_barcodes"], + "data": ["views/stock_picking_type_views.xml"], + "license": "AGPL-3", +} diff --git a/stock_barcodes_serial_source_relocation/models/__init__.py b/stock_barcodes_serial_source_relocation/models/__init__.py new file mode 100644 index 000000000..1e4f45c34 --- /dev/null +++ b/stock_barcodes_serial_source_relocation/models/__init__.py @@ -0,0 +1,5 @@ +from . import stock_picking_type +from . import stock_picking +from . import stock_move_line +from . import stock_location +from . import stock_quant diff --git a/stock_barcodes_serial_source_relocation/models/stock_location.py b/stock_barcodes_serial_source_relocation/models/stock_location.py new file mode 100644 index 000000000..d52faa6cd --- /dev/null +++ b/stock_barcodes_serial_source_relocation/models/stock_location.py @@ -0,0 +1,13 @@ +# Copyright NuoBiT Solutions - Frank Cespedes +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import models + + +class Location(models.Model): + _inherit = "stock.location" + + def should_bypass_reservation(self): + if self.env.context.get("relocation"): + return False + return super().should_bypass_reservation() diff --git a/stock_barcodes_serial_source_relocation/models/stock_move_line.py b/stock_barcodes_serial_source_relocation/models/stock_move_line.py new file mode 100644 index 000000000..ff178e434 --- /dev/null +++ b/stock_barcodes_serial_source_relocation/models/stock_move_line.py @@ -0,0 +1,11 @@ +# Copyright NuoBiT Solutions - Eric Antones +# Copyright NuoBiT Solutions - Frank Cespedes +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import fields, models + + +class StockMoveLine(models.Model): + _inherit = "stock.move.line" + + barcode_relocation_scanned = fields.Boolean(default=False, copy=False) diff --git a/stock_barcodes_serial_source_relocation/models/stock_picking.py b/stock_barcodes_serial_source_relocation/models/stock_picking.py new file mode 100644 index 000000000..11af366c1 --- /dev/null +++ b/stock_barcodes_serial_source_relocation/models/stock_picking.py @@ -0,0 +1,165 @@ +# Copyright NuoBiT Solutions - Eric Antones +# Copyright NuoBiT Solutions - Frank Cespedes +# Copyright 2026 NuoBiT Solutions - Deniz Gallo +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import _, models +from odoo.exceptions import ValidationError +from odoo.tools import float_compare + + +class Picking(models.Model): + _inherit = "stock.picking" + + def _prepare_relocation_move_line_values(self, move_line, new_picking, quant): + return { + "picking_id": new_picking.id, + "product_id": move_line.product_id.id, + "origin": self.name, + "product_uom_id": move_line.product_id.uom_id.id, + "quantity": quant.quantity, + "location_id": quant.location_id.id, + "location_dest_id": move_line.location_id.id, + "lot_id": move_line.lot_id.id, + } + + def _prepare_relocation_move_values(self, move_line, quant): + return { + "name": self.name, + "origin": self.name, + "company_id": self.company_id.id, + "product_id": move_line.product_id.id, + "product_uom": move_line.product_id.uom_id.id, + "product_uom_qty": quant.quantity, + "quantity": quant.quantity, + "location_id": quant.location_id.id, + "location_dest_id": move_line.location_id.id, + } + + def _prepare_relocation_picking_values(self, move_line, new_picking_type, quant): + return { + "picking_type_id": new_picking_type.id, + "location_id": quant.location_id.id, + "location_dest_id": move_line.location_id.id, + "origin": move_line.picking_id.name, + "company_id": move_line.company_id.id, + "move_ids": [ + ( + 0, + 0, + self._prepare_relocation_move_values(move_line, quant), + ) + ], + } + + def _create_inventory_adjustment(self, move_line): + quant = self.env["stock.quant"].search( + [ + ("location_id", "=", move_line.location_id.id), + ("product_id", "=", move_line.product_id.id), + ("lot_id", "=", move_line.lot_id.id), + ("company_id", "=", self.company_id.id), + ], + limit=1, + ) + + if not quant: + quant = self.env["stock.quant"].create( + { + "location_id": move_line.location_id.id, + "product_id": move_line.product_id.id, + "lot_id": move_line.lot_id.id, + "company_id": self.company_id.id, + "quantity": 0, + } + ) + + quant.inventory_quantity_set = True + quant.inventory_quantity = 1 + quant.with_context( + inventory_mode=True, relocation_origin=self.name + ).action_apply_inventory() + + def button_validate(self): + res = super().button_validate() + if ( + self.picking_type_code != "incoming" + and not self.picking_type_id.barcode_option_group_id.allow_negative_quant + ): + for move_line in self.move_line_ids.filtered( + lambda x: x.product_id.tracking == "serial" + and x.barcode_relocation_scanned + and x.lot_id + ): + quants = move_line.lot_id.quant_ids.filtered( + lambda q, ml=move_line: float_compare( + q.quantity, + 0, + precision_rounding=ml.product_id.uom_id.rounding, + ) + > 0 + ) + if len(quants) > 1: + raise ValidationError( + _( + "S/N %(name)s is found in more than one location.", + name=move_line.lot_id.name, + ) + ) + if quants: + qty_available = quants.filtered( + lambda x, ml=move_line: x.location_id == ml.location_id + ).quantity + if ( + float_compare( + move_line.quantity, + qty_available, + precision_rounding=move_line.product_id.uom_id.rounding, + ) + > 0 + ): + warehouse = move_line.location_id.warehouse_id + picking_type = ( + self.env["stock.picking.type"] + .search( + [ + ("warehouse_id", "in", (warehouse.id, False)), + ("code", "=", "internal"), + ("is_regularization", "=", True), + ], + ) + .sorted(lambda x: x.warehouse_id, reverse=True) + ) + warehouse_ids = [pt.warehouse_id.id for pt in picking_type] + if len(warehouse_ids) != len(set(warehouse_ids)): + raise ValidationError( + _( + "More than one regularization picking " + "type for the same warehouse %(warehouse)s", + warehouse=move_line.location_id.name, + ) + ) + if not picking_type: + raise ValidationError( + _( + "No regularization picking " + "type for location %(location)s", + location=move_line.location_id.name, + ) + ) + new_picking = self.env["stock.picking"].create( + self._prepare_relocation_picking_values( + move_line, picking_type[0], quants + ) + ) + new_picking.with_context(relocation=self.name).action_confirm() + for move in new_picking.move_ids: + move.move_line_ids.write( + self._prepare_relocation_move_line_values( + move_line, new_picking, quants + ) + ) + new_picking.button_validate() + else: + self._create_inventory_adjustment(move_line) + return res diff --git a/stock_barcodes_serial_source_relocation/models/stock_picking_type.py b/stock_barcodes_serial_source_relocation/models/stock_picking_type.py new file mode 100644 index 000000000..d8979c3b4 --- /dev/null +++ b/stock_barcodes_serial_source_relocation/models/stock_picking_type.py @@ -0,0 +1,38 @@ +# Copyright NuoBiT Solutions - Frank Cespedes +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class PickingType(models.Model): + _inherit = "stock.picking.type" + + is_regularization = fields.Boolean(copy=False) + + @api.constrains("is_regularization", "code", "warehouse_id", "company_id") + def _check_is_regularization(self): + for rec in self: + if rec.is_regularization: + if rec.code != "internal": + raise ValidationError( + _("Only internal picking types can be regularization."), + ) + if ( + rec.env["stock.picking.type"].search_count( + [ + ("is_regularization", "=", True), + ("warehouse_id", "=", rec.warehouse_id.id), + ("code", "=", rec.code), + ("company_id", "=", rec.company_id.id), + ] + ) + > 1 + ): + raise ValidationError( + _( + "Only one picking type can be regularization " + "in a warehouse %(warehouse)s.", + warehouse=rec.warehouse_id.name, + ) + ) diff --git a/stock_barcodes_serial_source_relocation/models/stock_quant.py b/stock_barcodes_serial_source_relocation/models/stock_quant.py new file mode 100644 index 000000000..e822858e6 --- /dev/null +++ b/stock_barcodes_serial_source_relocation/models/stock_quant.py @@ -0,0 +1,25 @@ +# Copyright 2026 NuoBiT Solutions - Deniz Gallo +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import models + + +class StockQuant(models.Model): + _inherit = "stock.quant" + + def _get_inventory_move_values( + self, + qty, + location_id, + location_dest_id, + package_id=False, + package_dest_id=False, + ): + values = super()._get_inventory_move_values( + qty, location_id, location_dest_id, package_id, package_dest_id + ) + origin = self.env.context.get("relocation_origin", False) + if origin: + values["origin"] = origin + values["move_line_ids"][0][2]["origin"] = origin + return values diff --git a/stock_barcodes_serial_source_relocation/pyproject.toml b/stock_barcodes_serial_source_relocation/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/stock_barcodes_serial_source_relocation/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/stock_barcodes_serial_source_relocation/readme/CONTRIBUTORS.md b/stock_barcodes_serial_source_relocation/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..0ff45d152 --- /dev/null +++ b/stock_barcodes_serial_source_relocation/readme/CONTRIBUTORS.md @@ -0,0 +1,4 @@ +- [NuoBiT](https://www.nuobit.com): + - Frank Cespedes + - Eric Antones + - Deniz Gallo diff --git a/stock_barcodes_serial_source_relocation/readme/DESCRIPTION.md b/stock_barcodes_serial_source_relocation/readme/DESCRIPTION.md new file mode 100644 index 000000000..5116c0302 --- /dev/null +++ b/stock_barcodes_serial_source_relocation/readme/DESCRIPTION.md @@ -0,0 +1,2 @@ +Allow to always have quantities available when entering a serial product +from the barcodes module. diff --git a/stock_barcodes_serial_source_relocation/static/description/icon.png b/stock_barcodes_serial_source_relocation/static/description/icon.png new file mode 100644 index 000000000..1cd641e79 Binary files /dev/null and b/stock_barcodes_serial_source_relocation/static/description/icon.png differ diff --git a/stock_barcodes_serial_source_relocation/static/description/index.html b/stock_barcodes_serial_source_relocation/static/description/index.html new file mode 100644 index 000000000..132897b30 --- /dev/null +++ b/stock_barcodes_serial_source_relocation/static/description/index.html @@ -0,0 +1,422 @@ + + + + + +Stock Barcodes Serial Source Relocation + + + +
+

Stock Barcodes Serial Source Relocation

+ + +

Beta License: AGPL-3 NuoBiT/odoo-addons

+

Allow to always have quantities available when entering a serial product +from the barcodes module.

+

Table of contents

+ +
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • NuoBiT Solutions SL
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is part of the NuoBiT/odoo-addons project on GitHub.

+

You are welcome to contribute.

+
+
+
+ + diff --git a/stock_barcodes_serial_source_relocation/views/stock_picking_type_views.xml b/stock_barcodes_serial_source_relocation/views/stock_picking_type_views.xml new file mode 100644 index 000000000..917d3b019 --- /dev/null +++ b/stock_barcodes_serial_source_relocation/views/stock_picking_type_views.xml @@ -0,0 +1,15 @@ + + + + + stock.picking.type + + + + + + + + diff --git a/stock_barcodes_serial_source_relocation/wizard/__init__.py b/stock_barcodes_serial_source_relocation/wizard/__init__.py new file mode 100644 index 000000000..63d37ec49 --- /dev/null +++ b/stock_barcodes_serial_source_relocation/wizard/__init__.py @@ -0,0 +1 @@ +from . import stock_barcodes_read_picking diff --git a/stock_barcodes_serial_source_relocation/wizard/stock_barcodes_read_picking.py b/stock_barcodes_serial_source_relocation/wizard/stock_barcodes_read_picking.py new file mode 100644 index 000000000..7a1bfa02a --- /dev/null +++ b/stock_barcodes_serial_source_relocation/wizard/stock_barcodes_read_picking.py @@ -0,0 +1,20 @@ +# Copyright NuoBiT Solutions - Eric Antones +# Copyright NuoBiT Solutions - Frank Cespedes +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import models + + +class WizStockBarcodesReadPicking(models.TransientModel): + _inherit = "wiz.stock.barcodes.read.picking" + + def check_done_conditions(self): + if self.product_id.tracking == "serial": + self = self.with_context(force_create_move=True) + return super().check_done_conditions() + + def _prepare_move_line_values(self, candidate_move, available_qty): + vals = super()._prepare_move_line_values(candidate_move, available_qty) + if self.product_id.tracking == "serial": + vals["barcode_relocation_scanned"] = True + return vals diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 000000000..1ca304aa6 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1 @@ +odoo-addon-stock_barcodes@git+https://github.com/OCA/stock-logistics-barcode.git@refs/pull/725/head#subdirectory=stock_barcodes