diff --git a/stock_request/models/stock_move.py b/stock_request/models/stock_move.py
index 077452f46..9c8e2a1d8 100644
--- a/stock_request/models/stock_move.py
+++ b/stock_request/models/stock_move.py
@@ -48,3 +48,31 @@ class StockMove(models.Model):
"that of the location."
)
)
+
+ def copy_data(self, default=None):
+ if not default:
+ default = {}
+ if "allocation_ids" not in default:
+ default["allocation_ids"] = []
+ for alloc in self.allocation_ids:
+ default["allocation_ids"].append(
+ (
+ 0,
+ 0,
+ {
+ "stock_request_id": alloc.stock_request_id.id,
+ "requested_product_uom_qty": alloc.requested_product_uom_qty,
+ },
+ )
+ )
+ return super(StockMove, self).copy_data(default)
+
+ def _action_cancel(self):
+ res = super()._action_cancel()
+ self.mapped("allocation_ids.stock_request_id").check_done()
+ return res
+
+ def _action_done(self, cancel_backorder=False):
+ res = super()._action_done(cancel_backorder=cancel_backorder)
+ self.mapped("allocation_ids.stock_request_id").check_done()
+ return res
diff --git a/stock_request/models/stock_move_line.py b/stock_request/models/stock_move_line.py
index 74a463e4e..98a34d1a8 100644
--- a/stock_request/models/stock_move_line.py
+++ b/stock_request/models/stock_move_line.py
@@ -50,16 +50,21 @@ class StockMoveLine(models.Model):
# We do sudo because potentially the user that completes the move
# may not have permissions for stock.request.
- to_allocate_qty = ml.qty_done
- for allocation in ml.move_id.allocation_ids:
+ to_allocate_qty = qty_done
+ for allocation in ml.move_id.allocation_ids.sudo():
allocated_qty = 0.0
if allocation.open_product_qty:
- allocated_qty = min(allocation.open_product_qty, qty_done)
+ allocated_qty = min(allocation.open_product_qty, to_allocate_qty)
allocation.allocated_product_qty += allocated_qty
to_allocate_qty -= allocated_qty
- request = allocation.stock_request_id
- message_data = self._prepare_message_data(ml, request, allocated_qty)
- message = self._stock_request_confirm_done_message_content(message_data)
- request.message_post(body=message, subtype="mail.mt_comment")
- request.check_done()
+ if allocated_qty:
+ request = allocation.stock_request_id
+ message_data = self._prepare_message_data(
+ ml, request, allocated_qty
+ )
+ message = self._stock_request_confirm_done_message_content(
+ message_data
+ )
+ request.message_post(body=message, subtype="mail.mt_comment")
+ request.check_done()
return res
diff --git a/stock_request/models/stock_request.py b/stock_request/models/stock_request.py
index f18cd3887..099a0b3c6 100644
--- a/stock_request/models/stock_request.py
+++ b/stock_request/models/stock_request.py
@@ -104,8 +104,16 @@ class StockRequest(models.Model):
store=True,
help="Quantity completed",
)
+ qty_cancelled = fields.Float(
+ "Qty Cancelled",
+ digits="Product Unit of Measure",
+ readonly=True,
+ compute="_compute_qty",
+ store=True,
+ help="Quantity cancelled",
+ )
picking_count = fields.Integer(
- string="Delivery Orders", compute="_compute_picking_ids", readonly=True
+ string="Delivery Orders", compute="_compute_picking_ids", readonly=True,
)
allocation_ids = fields.One2many(
comodel_name="stock.request.allocation",
@@ -136,12 +144,16 @@ class StockRequest(models.Model):
("name_uniq", "unique(name, company_id)", "Stock Request name must be unique")
]
- @api.depends("allocation_ids")
+ @api.depends("allocation_ids", "allocation_ids.stock_move_id")
def _compute_move_ids(self):
for request in self:
request.move_ids = request.allocation_ids.mapped("stock_move_id")
- @api.depends("allocation_ids")
+ @api.depends(
+ "allocation_ids",
+ "allocation_ids.stock_move_id",
+ "allocation_ids.stock_move_id.picking_id",
+ )
def _compute_picking_ids(self):
for request in self:
request.picking_count = 0
@@ -159,14 +171,31 @@ class StockRequest(models.Model):
)
def _compute_qty(self):
for request in self:
- done_qty = sum(request.allocation_ids.mapped("allocated_product_qty"))
+ incoming_qty = 0.0
+ other_qty = 0.0
+ for allocation in request.allocation_ids:
+ if allocation.stock_move_id.picking_code == "incoming":
+ incoming_qty += allocation.allocated_product_qty
+ else:
+ other_qty += allocation.allocated_product_qty
+ done_qty = abs(other_qty - incoming_qty)
open_qty = sum(request.allocation_ids.mapped("open_product_qty"))
- request.qty_done = request.product_id.uom_id._compute_quantity(
- done_qty, request.product_uom_id
- )
- request.qty_in_progress = request.product_id.uom_id._compute_quantity(
+ uom = request.product_id.uom_id
+ request.qty_done = uom._compute_quantity(done_qty, request.product_uom_id)
+ request.qty_in_progress = uom._compute_quantity(
open_qty, request.product_uom_id
)
+ request.qty_cancelled = (
+ max(
+ 0,
+ uom._compute_quantity(
+ request.product_qty - done_qty - open_qty,
+ request.product_uom_id,
+ ),
+ )
+ if request.allocation_ids
+ else 0
+ )
@api.constrains("order_id", "requested_by")
def check_order_requested_by(self):
@@ -208,7 +237,7 @@ class StockRequest(models.Model):
def _action_confirm(self):
self._action_launch_procurement_rule()
- self.state = "open"
+ self.write({"state": "open"})
def action_confirm(self):
self._action_confirm()
@@ -219,14 +248,13 @@ class StockRequest(models.Model):
return True
def action_cancel(self):
- self.mapped("move_ids")._action_cancel()
- self.state = "cancel"
+ self.sudo().mapped("move_ids")._action_cancel()
+ self.write({"state": "cancel"})
return True
def action_done(self):
- self.state = "done"
- if self.order_id:
- self.order_id.check_done()
+ self.write({"state": "done"})
+ self.mapped("order_id").check_done()
return True
def check_done(self):
@@ -245,8 +273,20 @@ class StockRequest(models.Model):
>= 0
):
request.action_done()
+ elif request._check_done_allocation():
+ request.action_done()
return True
+ def _check_done_allocation(self):
+ precision = self.env["decimal.precision"].precision_get(
+ "Product Unit of Measure"
+ )
+ self.ensure_one()
+ return (
+ self.allocation_ids
+ and float_compare(self.qty_cancelled, 0, precision_digits=precision) > 0
+ )
+
def _prepare_procurement_values(self, group_id=False):
""" Prepare specific key for moves or other components that
diff --git a/stock_request/models/stock_request_allocation.py b/stock_request/models/stock_request_allocation.py
index 4f2f962a9..8d12f75ea 100644
--- a/stock_request/models/stock_request_allocation.py
+++ b/stock_request/models/stock_request_allocation.py
@@ -52,11 +52,12 @@ class StockRequestAllocation(models.Model):
)
allocated_product_qty = fields.Float(
"Allocated Quantity",
+ copy=False,
help="Quantity of the stock request allocated to the stock move, "
"in the default UoM of the product",
)
open_product_qty = fields.Float(
- "Open Quantity", compute="_compute_open_product_qty"
+ "Open Quantity", compute="_compute_open_product_qty",
)
@api.depends(
@@ -78,7 +79,7 @@ class StockRequestAllocation(models.Model):
)
def _compute_open_product_qty(self):
for rec in self:
- if rec.stock_move_id.state == "cancel":
+ if rec.stock_move_id.state in ["cancel", "done"]:
rec.open_product_qty = 0.0
else:
rec.open_product_qty = (
diff --git a/stock_request/models/stock_request_order.py b/stock_request/models/stock_request_order.py
index dc5baf9b8..442e6e8a7 100644
--- a/stock_request/models/stock_request_order.py
+++ b/stock_request/models/stock_request_order.py
@@ -223,30 +223,28 @@ class StockRequestOrder(models.Model):
line.procurement_group_id = self.procurement_group_id
def action_confirm(self):
- for line in self.stock_request_ids:
- line.action_confirm()
- self.state = "open"
+ self.mapped("stock_request_ids").action_confirm()
+ self.write({"state": "open"})
return True
def action_draft(self):
- for line in self.stock_request_ids:
- line.action_draft()
- self.state = "draft"
+ self.mapped("stock_request_ids").action_draft()
+ self.write({"state": "draft"})
return True
def action_cancel(self):
- for line in self.stock_request_ids:
- line.action_cancel()
- self.state = "cancel"
+ self.mapped("stock_request_ids").action_cancel()
+ self.write({"state": "cancel"})
return True
def action_done(self):
- self.state = "done"
+ self.write({"state": "done"})
return True
def check_done(self):
- if not self.stock_request_ids.filtered(lambda r: r.state != "done"):
- self.action_done()
+ for rec in self:
+ if not rec.stock_request_ids.filtered(lambda r: r.state != "done"):
+ rec.action_done()
return
def action_view_transfer(self):
diff --git a/stock_request/tests/test_stock_request.py b/stock_request/tests/test_stock_request.py
index cbfb46477..dabb20bb3 100644
--- a/stock_request/tests/test_stock_request.py
+++ b/stock_request/tests/test_stock_request.py
@@ -688,11 +688,15 @@ class TestStockRequestBase(TestStockRequest):
)
stock_request_2.product_uom_qty = 6.0
self.product.route_ids = [(6, 0, self.route.ids)]
- stock_request_1.with_user(self.stock_request_manager).action_confirm()
- stock_request_2.with_user(self.stock_request_manager).action_confirm()
- self.assertEqual(len(stock_request_1.picking_ids), 1)
- self.assertEqual(stock_request_1.picking_ids, stock_request_2.picking_ids)
- self.assertEqual(stock_request_1.move_ids, stock_request_2.move_ids)
+ stock_request_1.sudo().action_confirm()
+ stock_request_2.sudo().action_confirm()
+ self.assertEqual(len(stock_request_1.sudo().picking_ids), 1)
+ self.assertEqual(
+ stock_request_1.sudo().picking_ids, stock_request_2.sudo().picking_ids
+ )
+ self.assertEqual(
+ stock_request_1.sudo().move_ids, stock_request_2.sudo().move_ids
+ )
self.env["stock.quant"].create(
{
"product_id": self.product.id,
@@ -700,12 +704,28 @@ class TestStockRequestBase(TestStockRequest):
"quantity": 10.0,
}
)
- picking = stock_request_1.picking_ids[0]
- picking.with_user(self.stock_request_manager).action_confirm()
- picking.with_user(self.stock_request_manager).action_assign()
+ picking = stock_request_1.sudo().picking_ids[0]
+ picking.action_confirm()
+ picking.action_assign()
+ self.assertEqual(stock_request_1.qty_in_progress, 4)
+ self.assertEqual(stock_request_1.qty_done, 0)
+ self.assertEqual(stock_request_1.qty_cancelled, 0)
+ self.assertEqual(stock_request_2.qty_in_progress, 6)
+ self.assertEqual(stock_request_2.qty_done, 0)
+ self.assertEqual(stock_request_2.qty_cancelled, 0)
packout1 = picking.move_line_ids[0]
- packout1.qty_done = 10
- picking.with_user(self.stock_request_manager).action_done()
+ packout1.qty_done = 4
+ self.env["stock.backorder.confirmation"].create(
+ {"pick_ids": [(4, picking.id)]}
+ ).process_cancel_backorder()
+ self.assertEqual(stock_request_1.qty_in_progress, 0)
+ self.assertEqual(stock_request_1.qty_done, 4)
+ self.assertEqual(stock_request_1.qty_cancelled, 0)
+ self.assertEqual(stock_request_1.state, "done")
+ self.assertEqual(stock_request_2.qty_in_progress, 0)
+ self.assertEqual(stock_request_2.qty_done, 0)
+ self.assertEqual(stock_request_2.qty_cancelled, 6)
+ self.assertEqual(stock_request_2.state, "done")
def test_cancel_request(self):
expected_date = fields.Datetime.now()
@@ -993,3 +1013,107 @@ class TestStockRequestBase(TestStockRequest):
order = self.request_order.with_user(self.stock_request_user).create(vals)
order.stock_request_ids.onchange_warehouse_id()
self.assertEqual(order.stock_request_ids[0].location_id, self.virtual_loc)
+
+ def test_cancellation(self):
+ group = self.env["procurement.group"].create({"name": "Procurement group"})
+ product2 = self._create_product("SH2", "Shoes2", False)
+ product3 = self._create_product("SH3", "Shoes3", False)
+ self.product.type = "consu"
+ product2.type = "consu"
+ product3.type = "consu"
+ vals = {
+ "company_id": self.main_company.id,
+ "warehouse_id": self.warehouse.id,
+ "location_id": self.virtual_loc.id,
+ "procurement_group_id": group.id,
+ "stock_request_ids": [
+ (
+ 0,
+ 0,
+ {
+ "product_id": self.product.id,
+ "product_uom_id": self.product.uom_id.id,
+ "procurement_group_id": group.id,
+ "product_uom_qty": 5.0,
+ "company_id": self.main_company.id,
+ "warehouse_id": self.warehouse.id,
+ "location_id": self.virtual_loc.id,
+ },
+ ),
+ (
+ 0,
+ 0,
+ {
+ "product_id": product2.id,
+ "product_uom_id": self.product.uom_id.id,
+ "procurement_group_id": group.id,
+ "product_uom_qty": 5.0,
+ "company_id": self.main_company.id,
+ "warehouse_id": self.warehouse.id,
+ "location_id": self.virtual_loc.id,
+ },
+ ),
+ (
+ 0,
+ 0,
+ {
+ "product_id": product3.id,
+ "product_uom_id": self.product.uom_id.id,
+ "procurement_group_id": group.id,
+ "product_uom_qty": 5.0,
+ "company_id": self.main_company.id,
+ "warehouse_id": self.warehouse.id,
+ "location_id": self.virtual_loc.id,
+ },
+ ),
+ ],
+ }
+ order = self.request_order.create(vals)
+ self.product.route_ids = [(6, 0, self.route.ids)]
+ product2.route_ids = [(6, 0, self.route.ids)]
+ product3.route_ids = [(6, 0, self.route.ids)]
+ order.action_confirm()
+ picking = order.picking_ids
+ self.assertEqual(1, len(picking))
+ picking.action_assign()
+ self.assertEqual(3, len(picking.move_lines))
+ line = picking.move_lines.filtered(lambda r: r.product_id == self.product)
+ line.quantity_done = 1
+ sr1 = order.stock_request_ids.filtered(lambda r: r.product_id == self.product)
+ sr2 = order.stock_request_ids.filtered(lambda r: r.product_id == product2)
+ sr3 = order.stock_request_ids.filtered(lambda r: r.product_id == product3)
+ self.assertNotEqual(sr1.state, "done")
+ self.assertNotEqual(sr2.state, "done")
+ self.assertNotEqual(sr3.state, "done")
+ self.env["stock.backorder.confirmation"].create(
+ {"pick_ids": [(4, picking.id)]}
+ ).process()
+ sr1.refresh()
+ sr2.refresh()
+ sr3.refresh()
+ self.assertNotEqual(sr1.state, "done")
+ self.assertNotEqual(sr2.state, "done")
+ self.assertNotEqual(sr3.state, "done")
+ picking = order.picking_ids.filtered(
+ lambda r: r.state not in ["done", "cancel"]
+ )
+ self.assertEqual(1, len(picking))
+ picking.action_assign()
+ self.assertEqual(3, len(picking.move_lines))
+ line = picking.move_lines.filtered(lambda r: r.product_id == self.product)
+ line.quantity_done = 4
+ line = picking.move_lines.filtered(lambda r: r.product_id == product2)
+ line.quantity_done = 1
+
+ self.env["stock.backorder.confirmation"].create(
+ {"pick_ids": [(4, picking.id)]}
+ ).process_cancel_backorder()
+ sr1.refresh()
+ sr2.refresh()
+ sr3.refresh()
+ self.assertEqual(sr1.state, "done")
+ self.assertEqual(sr1.qty_cancelled, 0)
+ self.assertEqual(sr2.state, "done")
+ self.assertEqual(sr2.qty_cancelled, 4)
+ self.assertEqual(sr3.state, "done")
+ self.assertEqual(sr3.qty_cancelled, 5)
diff --git a/stock_request/views/stock_request_order_views.xml b/stock_request/views/stock_request_order_views.xml
index b88281161..fd3480065 100644
--- a/stock_request/views/stock_request_order_views.xml
+++ b/stock_request/views/stock_request_order_views.xml
@@ -166,12 +166,6 @@
-
-
+
@@ -45,11 +46,6 @@
-
-
-
+
+
+