Skip to content

Commit

Permalink
[IMP] repair_picking_after_done: Allow backorders for stock moves lin…
Browse files Browse the repository at this point in the history
…ked to completed repair orders
  • Loading branch information
ppyczko committed Nov 18, 2024
1 parent f9905ee commit d2023e3
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 0 deletions.
1 change: 1 addition & 0 deletions repair_picking_after_done/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from . import repair
from . import stock_move
96 changes: 96 additions & 0 deletions repair_picking_after_done/models/stock_move.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
from odoo import _, models
from odoo.exceptions import UserError
from odoo.tools import float_is_zero, float_round
from odoo.tools.float_utils import float_compare


class StockMove(models.Model):
_inherit = "stock.move"

def _split(self, qty, restrict_partner_id=False):
"""Splits `self` quantity and return values
for a new moves to be created afterwards
This method is based on the core Odoo `stock.move`
`_split` method with the following customization:
- Allows splitting stock moves related to repair
orders that are marked as "done", which is prevented
by the core Odoo logic.
- This change enables the creation of backorders for
these split stock moves when the associated repair is completed.
Note: This customization overrides logic in both the `stock`
and `repair` modules to ensure that backorders can be created
for completed repairs, while adhering to the business rules of both modules.
:param qty: float. quantity to split (given in product UoM)
:param restrict_partner_id: optional partner that can be given in order
to force the new move to restrict its choice of quants to the ones
belonging to this partner.
:returns: list of dict. stock move values"""
self.ensure_one()

# Custom logic: Prevent splitting stock moves tied to incomplete repairs
if self.repair_id and self.repair_id.state != "done":
return []

# --- Core logic begins here ---

if self.state in ("done", "cancel"):
raise UserError(

Check warning on line 40 in repair_picking_after_done/models/stock_move.py

View check run for this annotation

Codecov / codecov/patch

repair_picking_after_done/models/stock_move.py#L40

Added line #L40 was not covered by tests
_(
"You cannot split a stock move that has been "
"set to 'Done' or 'Cancel'."
)
)
elif self.state == "draft":
raise UserError(

Check warning on line 47 in repair_picking_after_done/models/stock_move.py

View check run for this annotation

Codecov / codecov/patch

repair_picking_after_done/models/stock_move.py#L47

Added line #L47 was not covered by tests
_("You cannot split a draft move. It needs to be confirmed first.")
)

if float_is_zero(qty, precision_rounding=self.product_id.uom_id.rounding):
return []

Check warning on line 52 in repair_picking_after_done/models/stock_move.py

View check run for this annotation

Codecov / codecov/patch

repair_picking_after_done/models/stock_move.py#L52

Added line #L52 was not covered by tests

decimal_precision = self.env["decimal.precision"].precision_get(
"Product Unit of Measure"
)

uom_qty = self.product_id.uom_id._compute_quantity(
qty, self.product_uom, rounding_method="HALF-UP"
)
if (
float_compare(
qty,
self.product_uom._compute_quantity(
uom_qty, self.product_id.uom_id, rounding_method="HALF-UP"
),
precision_digits=decimal_precision,
)
== 0
):
defaults = self._prepare_move_split_vals(uom_qty)
else:
defaults = self.with_context(

Check warning on line 73 in repair_picking_after_done/models/stock_move.py

View check run for this annotation

Codecov / codecov/patch

repair_picking_after_done/models/stock_move.py#L73

Added line #L73 was not covered by tests
force_split_uom_id=self.product_id.uom_id.id
)._prepare_move_split_vals(qty)

if restrict_partner_id:
defaults["restrict_partner_id"] = restrict_partner_id

Check warning on line 78 in repair_picking_after_done/models/stock_move.py

View check run for this annotation

Codecov / codecov/patch

repair_picking_after_done/models/stock_move.py#L78

Added line #L78 was not covered by tests

if self.env.context.get("source_location_id"):
defaults["location_id"] = self.env.context["source_location_id"]

Check warning on line 81 in repair_picking_after_done/models/stock_move.py

View check run for this annotation

Codecov / codecov/patch

repair_picking_after_done/models/stock_move.py#L81

Added line #L81 was not covered by tests
new_move_vals = self.copy_data(defaults)

new_product_qty = self.product_id.uom_id._compute_quantity(
max(0, self.product_qty - qty), self.product_uom, round=False
)
new_product_qty = float_round(
new_product_qty,
precision_digits=self.env["decimal.precision"].precision_get(
"Product Unit of Measure"
),
)
self.with_context(do_not_unreserve=True).write(
{"product_uom_qty": new_product_qty}
)
return new_move_vals
1 change: 1 addition & 0 deletions repair_picking_after_done/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)

from . import test_repair_transfers
from . import test_stock_move_split
58 changes: 58 additions & 0 deletions repair_picking_after_done/tests/test_stock_move_split.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from odoo.tests.common import TransactionCase


class TestStockMoveSplit(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
# Create a product
cls.product = cls.env["product.product"].create(
{
"name": "Test Product",
"type": "product",
}
)

# Create a stock location
cls.location = cls.env["stock.location"].create(
{
"name": "Test Location",
"usage": "internal",
}
)

# Create a repair order in 'draft' state
cls.repair = cls.env["repair.order"].create(
{
"name": "Repair Order 1",
"product_id": cls.product.id,
"state": "draft",
}
)

# Create a stock move linked to the repair order
cls.stock_move = cls.env["stock.move"].create(
{
"name": "Stock Move for Repair Order 1",
"product_id": cls.product.id,
"product_uom_qty": 10.0,
"product_uom": cls.product.uom_id.id,
"repair_id": cls.repair.id,
"state": "confirmed",
"location_id": cls.location.id,
"location_dest_id": cls.location.id,
}
)

def test_split_move_with_incomplete_repair(self):
"""Ensure a stock move linked to an incomplete repair cannot be split"""
result = self.stock_move._split(5.0)
self.assertEqual(
result, [], "Move should not split as the repair is not 'done'."
)

def test_split_move_with_completed_repair(self):
"""Ensure a stock move linked to a completed repair can be split"""
self.repair.write({"state": "done"})
result = self.stock_move._split(5.0)
self.assertTrue(result, "Move should split as the repair is 'done'.")

0 comments on commit d2023e3

Please sign in to comment.