Skip to content

Commit

Permalink
[FIX] purchase_request: increase coverage and fix bugs
Browse files Browse the repository at this point in the history
* Fix allocation when splitting moves
* Fix incorrect field path in stock.move's _compute_purchase_request_ids
* Clarify "split origin" test
  • Loading branch information
StefanRijnhart committed Nov 30, 2022
1 parent dcf28ce commit 5ab0dfb
Show file tree
Hide file tree
Showing 7 changed files with 261 additions and 112 deletions.
4 changes: 3 additions & 1 deletion purchase_request/models/purchase_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ def unlink(self):
for alloc in (
rec.order_line.mapped("purchase_request_lines")
.mapped("purchase_request_allocation_ids")
.filtered(lambda alloc: alloc.purchase_line_id.order_id.id == rec.id)
.filtered(
lambda alloc, rec=rec: alloc.purchase_line_id.order_id.id == rec.id
)
):
alloc_to_unlink += alloc
res = super().unlink()
Expand Down
2 changes: 1 addition & 1 deletion purchase_request/models/purchase_request_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ def _compute_is_editable(self):
def _compute_supplier_id(self):
for rec in self:
sellers = rec.product_id.seller_ids.filtered(
lambda si: not si.company_id or si.company_id == rec.company_id
lambda si, rec=rec: not si.company_id or si.company_id == rec.company_id
)
rec.supplier_id = sellers[0].partner_id if sellers else False

Expand Down
73 changes: 46 additions & 27 deletions purchase_request/models/stock_move.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
from odoo.tools import float_compare


class StockMove(models.Model):
Expand Down Expand Up @@ -37,6 +38,7 @@ def _prepare_merge_moves_distinct_fields(self):
return distinct_fields

def _action_cancel(self):
"""Create an activity on the request for the cancelled procurement move"""
for move in self:
if move.created_purchase_request_line_id:
try:
Expand All @@ -52,7 +54,9 @@ def _action_cancel(self):
"purchase request has been cancelled/deleted. "
"Check if an action is needed."
),
"user_id": pr_line.product_id.responsible_id.id,
"user_id": (
pr_line.product_id.responsible_id.id or self.env.user.id
),
"res_id": pr_line.request_id.id,
"res_model_id": self.env.ref(
"purchase_request.model_purchase_request"
Expand All @@ -64,8 +68,8 @@ def _action_cancel(self):
@api.depends("purchase_request_allocation_ids")
def _compute_purchase_request_ids(self):
for rec in self:
rec.purchase_request_ids = rec.purchase_request_allocation_ids.mapped(
"purchase_request_id"
rec.purchase_request_ids = (
rec.purchase_request_allocation_ids.purchase_request_line_id.request_id
)

def _merge_moves_fields(self):
Expand Down Expand Up @@ -100,31 +104,46 @@ def _check_company_purchase_request(self):
)

def copy_data(self, default=None):
if not default:
"""Propagate request allocation on copy.
If this move is being split, or if this move is processed and there is
a remaining allocation, move the appropriate quantity over to the new move.
"""
if default is None:
default = {}
if "allocation_ids" not in default:
default["purchase_request_allocation_ids"] = []
first_it = True
for alloc in self.purchase_request_allocation_ids.filtered(
lambda al: al.requested_product_uom_qty > al.allocated_product_qty
if not default.get("purchase_request_allocation_ids") and (
default.get("product_uom_qty") or self.state in ("done", "cancel")
):
qty_done = sum(alloc.stock_move_id.mapped("move_line_ids.qty_done"))
if first_it:
qty_left = qty_done
first_it = False
if qty_left >= alloc.open_product_qty:
qty_left = qty_done - alloc.open_product_qty
continue
else:
open_qty = alloc.open_product_qty - qty_left
default["purchase_request_allocation_ids"].append(
(
0,
0,
{
"purchase_request_line_id": alloc.purchase_request_line_id.id,
"requested_product_uom_qty": open_qty,
},
default["purchase_request_allocation_ids"] = []
new_move_qty = default.get("product_uom_qty") or self.product_uom_qty
rounding = self.product_id.uom_id.rounding
for alloc in self.purchase_request_allocation_ids.filtered(
"open_product_qty"
):
if (
float_compare(
new_move_qty,
0,
precision_rounding=self.product_id.uom_id.rounding,
)
<= 0
or float_compare(
alloc.open_product_qty, 0, precision_rounding=rounding
)
<= 0
):
break
open_qty = min(new_move_qty, alloc.open_product_qty)
new_move_qty -= open_qty
default["purchase_request_allocation_ids"].append(
(
0,
0,
{
"purchase_request_line_id": alloc.purchase_request_line_id.id,
"requested_product_uom_qty": open_qty,
},
)
)
)
alloc.requested_product_uom_qty -= open_qty
return super(StockMove, self).copy_data(default)
8 changes: 5 additions & 3 deletions purchase_request/models/stock_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,14 @@ def create_purchase_request(self, procurement_group):
)
pr = purchase_request_model.create(request_data)
cache[domain] = pr
elif not pr.origin or procurement.origin not in pr.origin.split(", "):
elif (
not pr.origin
or procurement.origin not in pr.origin.split(", ")
and procurement.origin != "/"
):
if pr.origin:
if procurement.origin:
pr.write({"origin": pr.origin + ", " + procurement.origin})
else:
pr.write({"origin": pr.origin})
else:
pr.write({"origin": procurement.origin})
# Create Line
Expand Down
59 changes: 52 additions & 7 deletions purchase_request/tests/test_purchase_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ def setUp(self):
self.purchase_request_line_obj = self.env["purchase.request.line"]
self.purchase_order = self.env["purchase.order"]
self.wiz = self.env["purchase.request.line.make.purchase.order"]
self.picking_type_id = self.env.ref("stock.picking_type_in")
vals = {
"picking_type_id": self.env.ref("stock.picking_type_in").id,
"group_id": self.env["procurement.group"].create({}).id,
"picking_type_id": self.picking_type_id.id,
"requested_by": SUPERUSER_ID,
}
self.purchase_request = self.purchase_request_obj.create(vals)
Expand All @@ -26,6 +28,10 @@ def setUp(self):
}
self.purchase_request_line_obj.create(vals)

def test_purchase_request_line_action(self):
action = self.purchase_request.line_ids.action_show_details()
self.assertEqual(action["res_id"], self.purchase_request.line_ids.id)

def test_purchase_request_status(self):
"""Tests Purchase Request status workflow."""
purchase_request = self.purchase_request
Expand Down Expand Up @@ -62,6 +68,35 @@ def test_purchase_request_status(self):
purchase_request_line = self.purchase_request_line_obj.create(vals)
purchase_request.button_approved()
vals = {"supplier_id": self.env.ref("base.res_partner_1").id}

# It is required to have a picking type
purchase_request.picking_type_id = False
with self.assertRaisesRegex(UserError, "a Picking Type"):
self.wiz.with_context(
active_model="purchase.request",
active_ids=[purchase_request.id],
).create(vals)
purchase_request.picking_type_id = self.picking_type_id

# Picking type across all lines have to be the same
purchase_request2 = purchase_request.copy(
{"picking_type_id": self.picking_type_id.copy().id}
)
purchase_request2.button_approved()
with self.assertRaisesRegex(UserError, "same Picking Type"):
self.wiz.with_context(
active_model="purchase.request.line",
active_ids=(purchase_request_line + purchase_request2.line_ids).ids,
).create(vals)

purchase_request2.picking_type_id = purchase_request.picking_type_id
purchase_request2.group_id = self.env["procurement.group"].create({})
with self.assertRaisesRegex(UserError, "different procurement group"):
self.wiz.with_context(
active_model="purchase.request.line",
active_ids=(purchase_request_line + purchase_request2.line_ids).ids,
).create(vals)

wiz_id = self.wiz.with_context(
active_model="purchase.request.line", active_ids=[purchase_request_line.id]
).create(vals)
Expand All @@ -72,17 +107,27 @@ def test_purchase_request_status(self):
purchase = purchase_request_line.purchase_lines.order_id
purchase.button_done()
self.assertEqual(purchase.state, "done")

with self.assertRaisesRegex(
UserError, "The purchase has already been completed"
):
self.wiz.with_context(
active_model="purchase.request.line",
active_ids=[purchase_request_line.id],
).create(vals)

purchase_request_line._compute_purchase_state()
# Error case purchase_order in state done
with self.assertRaises(UserError):
with self.assertRaisesRegex(UserError, "has already been completed"):
purchase.button_confirm()
purchase.button_cancel()
self.assertEqual(purchase.state, "cancel")
purchase_request_line._compute_purchase_state()
with self.assertRaises(exceptions.UserError) as e:
with self.assertRaisesRegex(
exceptions.UserError,
"You cannot delete a purchase request which is not draft",
):
purchase_request.unlink()
msg = "You cannot delete a purchase request which is not draft."
self.assertIn(msg, e.exception.args[0])
purchase_request.button_draft()
purchase_request.unlink()

Expand Down Expand Up @@ -201,7 +246,7 @@ def test_raise_error(self):
# create purchase order from done state
self.assertEqual(purchase_request.state, "done")
purchase_request_line._compute_is_editable()
with self.assertRaises(UserError):
with self.assertRaisesRegex(UserError, "already been completed"):
self.wiz.with_context(
active_model="purchase.request.line",
active_ids=[purchase_request_line.id],
Expand All @@ -214,7 +259,7 @@ def test_raise_error(self):
wiz_id = self.wiz.with_context(
active_model="purchase.request.line", active_ids=[purchase_request_line.id]
).create(vals)
with self.assertRaises(UserError):
with self.assertRaisesRegex(UserError, "Enter a positive quantity"):
wiz_id.make_purchase_order()

def test_purchase_request_unlink(self):
Expand Down
44 changes: 35 additions & 9 deletions purchase_request/tests/test_purchase_request_allocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,44 @@ def test_purchase_request_allocation(self):
self.assertEqual(purchase_request_line1.qty_in_progress, 2.0)
self.assertEqual(purchase_request_line2.qty_in_progress, 2.0)
picking = purchase.picking_ids[0]

# Check the move
move = picking.move_ids
self.assertEqual(move.purchase_request_ids, purchase_request1)
# Do a move split/merge roundtrip and check that the allocatable
# quantity remains the same.
self.assertEqual(
sum(move.purchase_request_allocation_ids.mapped("open_product_qty")), 4
)
split_move = self.env["stock.move"].create(move._split(1))
split_move._action_confirm(merge=False)
self.assertEqual(split_move.purchase_request_ids, purchase_request1)
# The quantity of 4 is now split between the two moves
self.assertEqual(
sum(move.purchase_request_allocation_ids.mapped("open_product_qty")), 3
)
self.assertEqual(
sum(split_move.purchase_request_allocation_ids.mapped("open_product_qty")),
1,
)
split_move._merge_moves(merge_into=move)
self.assertFalse(split_move.exists())
self.assertEqual(
sum(move.purchase_request_allocation_ids.mapped("open_product_qty")), 4
)
# Reset reserved quantities messed up by the roundtrip
move._do_unreserve()
move._action_assign()

picking.move_line_ids[0].write({"qty_done": 2.0})
backorder_wiz_id = picking.button_validate()
common.Form(
self.env[backorder_wiz_id["res_model"]].with_context(
**backorder_wiz_id["context"]
)
).save().process()
self.assertEqual(purchase_request_line1.qty_done, 2.0)
self.assertEqual(purchase_request_line2.qty_done, 0.0)
request_lines = purchase_request_line1 + purchase_request_line2
self.assertEqual(sum(request_lines.mapped("qty_done")), 2.0)

backorder_picking = purchase.picking_ids.filtered(lambda p: p.id != picking.id)
backorder_picking.move_line_ids[0].write({"qty_done": 1.0})
Expand All @@ -100,15 +129,12 @@ def test_purchase_request_allocation(self):
)
).save().process()

self.assertEqual(purchase_request_line1.qty_done, 2.0)
self.assertEqual(purchase_request_line2.qty_done, 1.0)
self.assertEqual(sum(request_lines.mapped("qty_done")), 3.0)
for pick in purchase.picking_ids:
if pick.state == "assigned":
pick.action_cancel()
self.assertEqual(purchase_request_line1.qty_cancelled, 0.0)
self.assertEqual(purchase_request_line2.qty_cancelled, 1.0)
self.assertEqual(purchase_request_line1.pending_qty_to_receive, 0.0)
self.assertEqual(purchase_request_line2.pending_qty_to_receive, 1.0)
self.assertEqual(sum(request_lines.mapped("qty_cancelled")), 1.0)
self.assertEqual(sum(request_lines.mapped("pending_qty_to_receive")), 1.0)

def test_purchase_request_allocation_services(self):
vals = {
Expand Down Expand Up @@ -165,7 +191,7 @@ def test_purchase_request_allocation_services(self):
active_model="purchase.request.line", active_ids=[purchase_request_line2.id]
).create(vals)
wiz_id.make_purchase_order()
purchase_request2.action_view_purchase_order()
(purchase_request1 + purchase_request2).action_view_purchase_order()
po_line = purchase_request_line2.purchase_lines[0]
purchase2 = po_line.order_id
purchase2.button_confirm()
Expand Down

0 comments on commit 5ab0dfb

Please sign in to comment.