Skip to content
Open
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
65 changes: 65 additions & 0 deletions stock_barcodes_serial_source_relocation/README.rst
Original file line number Diff line number Diff line change
@@ -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 <https://github.com/NuoBiT/odoo-addons/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 <https://github.com/NuoBiT/odoo-addons/issues/new?body=module:%20stock_barcodes_serial_source_relocation%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

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

Credits
=======

Authors
-------

* NuoBiT Solutions SL

Contributors
------------

- `NuoBiT <https://www.nuobit.com>`__:

- 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 <https://github.com/NuoBiT/odoo-addons/tree/18.0/stock_barcodes_serial_source_relocation>`_ project on GitHub.

You are welcome to contribute.
4 changes: 4 additions & 0 deletions stock_barcodes_serial_source_relocation/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)

from . import models
from . import wizard
17 changes: 17 additions & 0 deletions stock_barcodes_serial_source_relocation/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copyright NuoBiT Solutions - Eric Antones <eantones@nuobit.com>
# Copyright NuoBiT Solutions - Frank Cespedes <fcespedes@nuobit.com>
# Copyright 2026 NuoBiT Solutions - Deniz Gallo <dgallo@nuobit.com>
# 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",
}
5 changes: 5 additions & 0 deletions stock_barcodes_serial_source_relocation/models/__init__.py
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions stock_barcodes_serial_source_relocation/models/stock_location.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright NuoBiT Solutions - Frank Cespedes <fcespedes@nuobit.com>
# 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()
11 changes: 11 additions & 0 deletions stock_barcodes_serial_source_relocation/models/stock_move_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Copyright NuoBiT Solutions - Eric Antones <eantones@nuobit.com>
# Copyright NuoBiT Solutions - Frank Cespedes <fcespedes@nuobit.com>
# 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)
165 changes: 165 additions & 0 deletions stock_barcodes_serial_source_relocation/models/stock_picking.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# Copyright NuoBiT Solutions - Eric Antones <eantones@nuobit.com>
# Copyright NuoBiT Solutions - Frank Cespedes <fcespedes@nuobit.com>
# Copyright 2026 NuoBiT Solutions - Deniz Gallo <dgallo@nuobit.com>
# 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Copyright NuoBiT Solutions - Frank Cespedes <fcespedes@nuobit.com>
# 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,
)
)
25 changes: 25 additions & 0 deletions stock_barcodes_serial_source_relocation/models/stock_quant.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright 2026 NuoBiT Solutions - Deniz Gallo <dgallo@nuobit.com>
# 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
3 changes: 3 additions & 0 deletions stock_barcodes_serial_source_relocation/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- [NuoBiT](https://www.nuobit.com):
- Frank Cespedes <fcespedes@nuobit.com>
- Eric Antones <eantones@nuobit.com>
- Deniz Gallo <dgallo@nuobit.com>
2 changes: 2 additions & 0 deletions stock_barcodes_serial_source_relocation/readme/DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Allow to always have quantities available when entering a serial product
from the barcodes module.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading