Skip to content

Commit

Permalink
Merge pull request #105 from qrtl/4207-imp-stock_owner_restrcition
Browse files Browse the repository at this point in the history
[4207][IMP] stock_owner_restriction, mrp_stock_owner_restriction
  • Loading branch information
yostashiro authored Apr 4, 2024
2 parents 230cfd8 + a66ff59 commit 2a1e674
Show file tree
Hide file tree
Showing 10 changed files with 130 additions and 46 deletions.
8 changes: 8 additions & 0 deletions mrp_stock_owner_restriction/models/mrp_production.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,11 @@ class MrpProduction(models.Model):
help="Produced products will be assigned to this owner.",
)
owner_restriction = fields.Selection(related="picking_type_id.owner_restriction")

def write(self, vals):
if "owner_id" in vals:
for production in self:
owner_restriction = self.picking_type_id.owner_restriction
if owner_restriction in ("unassigned_owner", "picking_partner"):
production.move_line_raw_ids.unlink()
return super().write(vals)
2 changes: 1 addition & 1 deletion mrp_stock_owner_restriction/models/mrp_unbuild.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ class MrpUnbuild(models.Model):
def action_validate(self):
owner_restriction = self.mo_id.picking_type_id.owner_restriction
owner = self.mo_id.owner_id
if owner and owner_restriction != "standard_behavior":
if owner and owner_restriction in ("unassigned_owner", "picking_partner"):
self = self.with_context(force_restricted_owner_id=owner)
return super().action_validate()
7 changes: 6 additions & 1 deletion mrp_stock_owner_restriction/models/stock_move.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
# Copyright 2023 Quartile Limited
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import models
from odoo import api, models


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

# Just to add a trigger
@api.depends("production_id.owner_id")
def _compute_restrict_partner_id(self):
return super()._compute_restrict_partner_id()

def _get_mo_to_unbuild(self):
self.ensure_one()
return self.consume_unbuild_id.mo_id or self.unbuild_id.mo_id
Expand Down
2 changes: 1 addition & 1 deletion mrp_stock_owner_restriction/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
This module adopts the functionality of stock_owner_restriction in the manufacturing
This module adapts the functionality of stock_owner_restriction in the manufacturing
order.
Original file line number Diff line number Diff line change
Expand Up @@ -10,39 +10,37 @@ def setUp(self):
self.company = self.env.ref("base.main_company")
self.manufacture_route = self.env.ref("mrp.route_warehouse0_manufacture")

self.product = self.env["product.product"].create(
{
"name": "test product",
"type": "product",
}
self.finished_product = self.env["product.product"].create(
{"name": "test product", "type": "product"}
)
self.product_2 = self.env["product.product"].create(
{"name": "test component", "default_code": "PRD 2", "type": "product"}
self.component = self.env["product.product"].create(
{"name": "test component", "type": "product"}
)
self.bom = self.env["mrp.bom"].create(
{
"product_id": self.product.id,
"product_tmpl_id": self.product.product_tmpl_id.id,
"product_uom_id": self.product.uom_id.id,
"product_id": self.finished_product.id,
"product_tmpl_id": self.finished_product.product_tmpl_id.id,
"product_uom_id": self.finished_product.uom_id.id,
"product_qty": 1.0,
"type": "normal",
}
)
self.env["mrp.bom.line"].create(
{"bom_id": self.bom.id, "product_id": self.product_2.id, "product_qty": 2}
{"bom_id": self.bom.id, "product_id": self.component.id, "product_qty": 1}
)
self.owner = self.env["res.partner"].create({"name": "Owner test"})
self.product.route_ids = [(6, 0, self.manufacture_route.ids)]
self.picking_type_id = self.env["stock.picking.type"].search(
self.finished_product.route_ids = [(6, 0, self.manufacture_route.ids)]
self.picking_type = self.env["stock.picking.type"].search(
[
("code", "=", "mrp_operation"),
("warehouse_id.company_id", "=", self.company.id),
],
limit=1,
)
self.picking_type.write({"owner_restriction": "picking_partner"})
quant_vals = {
"product_id": self.product_2.id,
"location_id": self.picking_type_id.default_location_src_id.id,
"product_id": self.component.id,
"location_id": self.picking_type.default_location_src_id.id,
"quantity": 250.00,
}
# Create quants without owner
Expand All @@ -51,12 +49,17 @@ def setUp(self):
self.env["stock.quant"].create(dict(quant_vals, owner_id=self.owner.id))

def test_mrp_quant_assign_owner(self):
self.assertEqual(self.component.qty_available, 250)
self.component.invalidate_model(["qty_available"])
self.assertEqual(
self.component.with_context(skip_restricted_owner=True).qty_available, 500
)
mo = self.env["mrp.production"].create(
{
"product_id": self.product.id,
"product_id": self.finished_product.id,
"bom_id": self.bom.id,
"product_qty": 250,
"picking_type_id": self.picking_type_id.id,
"picking_type_id": self.picking_type.id,
"owner_id": self.owner.id,
}
)
Expand All @@ -69,17 +72,22 @@ def test_mrp_quant_assign_owner(self):
action = wizard.process()

# Check produced product owner and qty_available
self.assertEqual(self.product.qty_available, 0.00)
self.product.invalidate_model()
self.assertEqual(self.finished_product.qty_available, 0.00)
self.finished_product.invalidate_model(["qty_available"])
self.assertEqual(
self.product.with_context(skip_restricted_owner=True).qty_available, 250.00
self.finished_product.with_context(
skip_restricted_owner=True
).qty_available,
250.00,
)
quant = self.env["stock.quant"].search(
[("product_id", "=", self.finished_product.id)]
)
quant = self.env["stock.quant"].search([("product_id", "=", self.product.id)])
self.assertEqual(quant.owner_id, self.owner)

# Check component product qty_available
self.assertEqual(self.product_2.qty_available, 0.00)
self.product.invalidate_model()
# Confirm that component inventory with owner has been consumed
self.assertEqual(self.component.qty_available, 250)
self.component.invalidate_model(["qty_available"])
self.assertEqual(
self.product_2.with_context(skip_restricted_owner=True).qty_available, 0.00
self.component.with_context(skip_restricted_owner=True).qty_available, 250
)
12 changes: 12 additions & 0 deletions purchase_order_owner/models/purchase_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@ class PurchaseOrder(models.Model):
"incoming stock picking.",
)

def button_confirm(self):
"""For subcontracting orders
Without this, the downstream component demands may misjudge the available
quantities with owner consideration.
"""
# TODO: Double-check if the logic is optimal
for order in self:
order = order.with_context(owner_id=order.owner_id.id)
super(PurchaseOrder, order).button_confirm()
return True

def _prepare_picking(self):
res = super()._prepare_picking()
res["owner_id"] = self.owner_id.id
Expand Down
35 changes: 29 additions & 6 deletions stock_owner_restriction/models/stock_move.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,37 @@
# Copyright 2020 Carlos Dauden - Tecnativa
# Copyright 2020 Sergio Teruel - Tecnativa
# Copyright 2023 Quartile Limited
# Copyright 2023-2024 Quartile Limited
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from collections import defaultdict

from odoo import models
from odoo import api, fields, models


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

restrict_partner_id = fields.Many2one(
compute="_compute_restrict_partner_id",
store=True,
readonly=False,
)

@api.depends("picking_type_id", "picking_id.owner_id", "move_dest_ids")
def _compute_restrict_partner_id(self):
for move in self:
if move.picking_type_id.owner_restriction == "picking_partner":
move.restrict_partner_id = move._get_owner_for_assign()
else:
move.restrict_partner_id = False

# pylint: disable=W8110
@api.depends("restrict_partner_id")
def _compute_forecast_information(self):
for move in self:
if move.picking_type_id.owner_restriction == "picking_partner":
move = move.with_context(owner_id=move.restrict_partner_id.id)
super(StockMove, move)._compute_forecast_information()

def _get_moves_to_assign_with_standard_behavior(self):
"""This method is expected to be extended as necessary. e.g. you may not want to
handle subcontracting receipts (whose picking type is normal incoming receipt
Expand All @@ -32,10 +54,11 @@ def _get_owner_for_assign(self):
needs to be applied to moves in manufacturing orders.
"""
self.ensure_one()
partner = self.move_dest_ids.picking_id.owner_id
if not partner:
partner = self.picking_id.owner_id or self.picking_id.partner_id
return partner
return (
self.move_dest_ids.restrict_partner_id
or self.picking_id.owner_id
or self.picking_id.partner_id
)

def _action_assign(self, force_qty=False):
# Split moves by picking type owner behavior restriction to process
Expand Down
8 changes: 8 additions & 0 deletions stock_owner_restriction/models/stock_picking.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,11 @@ class StockPicking(models.Model):
_inherit = "stock.picking"

owner_restriction = fields.Selection(related="picking_type_id.owner_restriction")

def write(self, vals):
if "owner_id" in vals:
for pick in self:
owner_restriction = pick.picking_type_id.owner_restriction
if owner_restriction in ("unassigned_owner", "picking_partner"):
pick.move_line_ids.unlink()
return super().write(vals)
20 changes: 8 additions & 12 deletions stock_owner_restriction/models/stock_quant.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,14 @@ def read_group(
if not owner_in_domain:
restricted_owner = self.env.context.get("force_restricted_owner_id", None)
if restricted_owner is not None:
domain = expression.AND(
[
domain,
[
(
"owner_id",
"=",
restricted_owner and restricted_owner.id or False,
)
],
]
)
owner_domain = [
(
"owner_id",
"=",
restricted_owner and restricted_owner.id or False,
),
]
domain = expression.AND([domain, owner_domain])
return super(StockQuant, self).read_group(
domain,
fields,
Expand Down
24 changes: 24 additions & 0 deletions stock_owner_restriction/tests/test_stock_owner_restriction.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,27 @@ def test_search_qty(self):
[("id", "=", self.product.id), ("qty_available", ">", 499.00)]
)
self.assertTrue(products)

def test_update_available_quantity(self):
self.env["stock.quant"].with_context(
force_restricted_owner_id=self.owner
)._update_available_quantity(
self.product, self.picking_type_out.default_location_src_id, 100
)
self.assertEqual(self.product.qty_available, 500.00)
self.product.invalidate_model()
self.assertEqual(
self.product.with_context(skip_restricted_owner=True).qty_available, 1100.00
)

def test_update_reserved_quantity(self):
self.env["stock.quant"].with_context(
force_restricted_owner_id=self.owner
)._update_reserved_quantity(
self.product, self.picking_type_out.default_location_src_id, 100
)
self.assertEqual(self.product.qty_available, 500.00)
self.product.invalidate_model()
self.assertEqual(
self.product.with_context(skip_restricted_owner=True).qty_available, 1000.00
)

0 comments on commit 2a1e674

Please sign in to comment.