diff --git a/rma_repair/models/__init__.py b/rma_repair/models/__init__.py
index dc5f3eb0..e98920d9 100644
--- a/rma_repair/models/__init__.py
+++ b/rma_repair/models/__init__.py
@@ -4,3 +4,5 @@ from . import repair
from . import rma_order_line
from . import rma_order
from . import rma_operation
+from . import stock_move
+from . import stock_rule
diff --git a/rma_repair/models/rma_operation.py b/rma_repair/models/rma_operation.py
index c24d5345..6026e7b9 100644
--- a/rma_repair/models/rma_operation.py
+++ b/rma_repair/models/rma_operation.py
@@ -35,3 +35,8 @@ class RmaOperation(models.Model):
"respectively. 'No invoice' means you don't want to generate "
"invoice for this repair order.",
)
+ repair_route_id = fields.Many2one(
+ comodel_name="stock.route",
+ string="Repair Route",
+ domain=[("rma_selectable", "=", True)],
+ )
diff --git a/rma_repair/models/rma_order.py b/rma_repair/models/rma_order.py
index b3091f0c..a3fa6837 100644
--- a/rma_repair/models/rma_order.py
+++ b/rma_repair/models/rma_order.py
@@ -12,13 +12,37 @@ class RmaOrder(models.Model):
repairs = rma.mapped("rma_line_ids.repair_ids")
rma.repair_count = len(repairs)
+ def _compute_repair_transfer_count(self):
+ for order in self:
+ pickings = (
+ order.mapped("rma_line_ids.move_ids")
+ .filtered(lambda m: m.is_rma_put_away)
+ .mapped("picking_id")
+ )
+ order.put_away_count = len(pickings)
+
repair_count = fields.Integer(
compute="_compute_repair_count", string="# of Repairs"
)
+ repair_transfer_count = fields.Integer(
+ compute="_compute_repair_transfer_count", string="# Repair Transfers"
+ )
+
def action_view_repair_order(self):
action = self.env.ref("repair.action_repair_order_tree")
result = action.sudo().read()[0]
repair_ids = self.mapped("rma_line_ids.repair_ids").ids
result["domain"] = [("id", "in", repair_ids)]
return result
+
+ def action_view_repair_transfers(self):
+ self.ensure_one()
+ action = self.env.ref("stock.action_picking_tree_all")
+ result = action.sudo().read()[0]
+ pickings = self.env["stock.picking"]
+ for line in self.rma_line_ids:
+ pickings |= line.move_ids.filtered(
+ lambda m: m.is_rma_repair_transfer
+ ).mapped("picking_id")
+ return self._view_shipments(result, pickings)
diff --git a/rma_repair/models/rma_order_line.py b/rma_repair/models/rma_order_line.py
index eddc0c3a..d9a313d9 100644
--- a/rma_repair/models/rma_order_line.py
+++ b/rma_repair/models/rma_order_line.py
@@ -36,6 +36,13 @@ class RmaOrderLine(models.Model):
for line in self:
line.repair_count = len(line.repair_ids)
+ def _compute_repair_transfer_count(self):
+ for line in self:
+ pickings = line.move_ids.filtered(
+ lambda m: m.is_rma_repair_transfer
+ ).mapped("picking_id")
+ line.repair_transfer_count = len(pickings)
+
repair_ids = fields.One2many(
comodel_name="repair.order",
inverse_name="rma_line_id",
@@ -80,6 +87,10 @@ class RmaOrderLine(models.Model):
compute="_compute_repair_count", string="# of Repairs"
)
+ repair_transfer_count = fields.Integer(
+ compute="_compute_repair_transfer_count", string="# of Repair Transfers"
+ )
+
delivery_policy = fields.Selection(
selection_add=[("repair", "Based on Repair Quantities")],
ondelete={"repair": lambda recs: recs.write({"delivery_policy": "no"})},
@@ -182,3 +193,20 @@ class RmaOrderLine(models.Model):
for rec in self.filtered(lambda l: l.delivery_policy == "repair"):
rec.qty_to_deliver = rec.qty_repaired - rec.qty_delivered
return res
+
+ def action_view_repair_transfers(self):
+ action = self.env.ref("stock.action_picking_tree_all")
+ result = action.sudo().read()[0]
+ pickings = self.env["stock.picking"]
+ for line in self:
+ pickings |= line.move_ids.filtered(
+ lambda m: m.is_rma_repair_transfer
+ ).mapped("picking_id")
+ # choose the view_mode accordingly
+ if len(pickings) != 1:
+ result["domain"] = "[('id', 'in', " + str(pickings.ids) + ")]"
+ elif len(pickings) == 1:
+ res = self.env.ref("stock.view_picking_form", False)
+ result["views"] = [(res and res.id or False, "form")]
+ result["res_id"] = pickings.ids[0]
+ return result
diff --git a/rma_repair/models/stock_move.py b/rma_repair/models/stock_move.py
new file mode 100644
index 00000000..e1c65cc3
--- /dev/null
+++ b/rma_repair/models/stock_move.py
@@ -0,0 +1,19 @@
+# Copyright 2022 ForgeFlow S.L.
+# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
+
+from odoo import fields, models
+
+
+class StockMove(models.Model):
+ _inherit = "stock.move"
+
+ is_rma_repair_transfer = fields.Boolean(
+ string="Is RMA Repair",
+ help="This Stock Move has been created from a Repair operation in " "the RMA.",
+ )
+
+ def _is_in_out_rma_move(self, op, states, location_type):
+ res = super(StockMove, self)._is_in_out_rma_move(op, states, location_type)
+ if self.is_rma_repair_transfer:
+ return False
+ return res
diff --git a/rma_repair/models/stock_rule.py b/rma_repair/models/stock_rule.py
new file mode 100644
index 00000000..f80df6ab
--- /dev/null
+++ b/rma_repair/models/stock_rule.py
@@ -0,0 +1,33 @@
+# Copyright 2022 ForgeFlow S.L.
+# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
+
+from odoo import models
+
+
+class StockRule(models.Model):
+ _inherit = "stock.rule"
+
+ def _get_stock_move_values(
+ self,
+ product_id,
+ product_qty,
+ product_uom,
+ location_id,
+ name,
+ origin,
+ company_id,
+ values,
+ ):
+ res = super()._get_stock_move_values(
+ product_id,
+ product_qty,
+ product_uom,
+ location_id,
+ name,
+ origin,
+ company_id,
+ values,
+ )
+ if "is_rma_repair_transfer" in values:
+ res["is_rma_repair_transfer"] = values.get("is_rma_repair_transfer")
+ return res
diff --git a/rma_repair/tests/test_rma_repair.py b/rma_repair/tests/test_rma_repair.py
index 4df8201e..32a9dca0 100644
--- a/rma_repair/tests/test_rma_repair.py
+++ b/rma_repair/tests/test_rma_repair.py
@@ -28,6 +28,36 @@ class TestRmaRepair(common.SingleTransactionCase):
# Create partners
cls.customer1 = cls.partner_obj.create({"name": "Customer 1"})
+ # Create routes
+ cls.wh = cls.env.ref("stock.warehouse0")
+ cls.stock_rma_location = cls.wh.lot_rma_id
+ cls.repair_loc = cls.env["stock.location"].create(
+ {
+ "name": "WH Repair Location",
+ "location_id": cls.wh.view_location_id.id,
+ }
+ )
+ cls.repair_route = cls.env["stock.route"].create(
+ {
+ "name": "Transfer RMA to Repair",
+ "rma_selectable": True,
+ "sequence": 10,
+ }
+ )
+
+ cls.env["stock.rule"].create(
+ {
+ "name": "Transfer",
+ "route_id": cls.repair_route.id,
+ "location_src_id": cls.stock_rma_location.id,
+ "location_dest_id": cls.repair_loc.id,
+ "action": "pull",
+ "picking_type_id": cls.wh.int_type_id.id,
+ "procure_method": "make_to_stock",
+ "warehouse_id": cls.wh.id,
+ }
+ )
+
# Create RMA group and operation:
cls.rma_group_customer = cls.rma_obj.create(
{"partner_id": cls.customer1.id, "type": "customer"}
@@ -41,6 +71,8 @@ class TestRmaRepair(common.SingleTransactionCase):
"repair_type": "received",
"in_route_id": cls.rma_route_cust.id,
"out_route_id": cls.rma_route_cust.id,
+ "repair_location_id": cls.repair_loc.id,
+ "repair_route_id": cls.repair_route.id,
}
)
cls.operation_2 = cls.rma_op.create(
@@ -52,6 +84,8 @@ class TestRmaRepair(common.SingleTransactionCase):
"repair_type": "ordered",
"in_route_id": cls.rma_route_cust.id,
"out_route_id": cls.rma_route_cust.id,
+ "repair_location_id": cls.repair_loc.id,
+ "repair_route_id": cls.repair_route.id,
}
)
cls.operation_3 = cls.rma_op.create(
@@ -64,6 +98,8 @@ class TestRmaRepair(common.SingleTransactionCase):
"delivery_policy": "repair",
"in_route_id": cls.rma_route_cust.id,
"out_route_id": cls.rma_route_cust.id,
+ "repair_location_id": cls.repair_loc.id,
+ "repair_route_id": cls.repair_route.id,
}
)
# Create products
@@ -154,14 +190,6 @@ class TestRmaRepair(common.SingleTransactionCase):
cls.material.product_tmpl_id.standard_price = 10
cls.stock_location = cls.env.ref("stock.stock_location_stock")
- cls.env["stock.quant"].create(
- {
- "product_id": cls.material.id,
- "location_id": cls.stock_location.id,
- "quantity": 10,
- }
- )
-
def test_01_add_from_invoice_customer(self):
"""Test wizard to create RMA from a customer invoice."""
add_inv = self.rma_add_invoice_wiz.with_context(
@@ -217,6 +245,7 @@ class TestRmaRepair(common.SingleTransactionCase):
self.assertEqual(rma.repair_count, 0)
self.assertEqual(rma.qty_to_repair, 15.0)
self.assertEqual(rma.qty_repaired, 0.0)
+ self.assertEqual(rma.repair_transfer_count, 0)
make_repair = self.rma_make_repair_wiz.with_context(
**{
"customer": True,
@@ -225,6 +254,14 @@ class TestRmaRepair(common.SingleTransactionCase):
}
).new()
make_repair.make_repair_order()
+ rma._compute_repair_transfer_count()
+ self.assertEqual(rma.repair_transfer_count, 1)
+ repair_transfer_move = rma.move_ids.filtered(
+ lambda x: x.location_dest_id == self.repair_loc
+ )
+ self.assertEqual(repair_transfer_move.location_id, self.stock_rma_location)
+ self.assertEqual(repair_transfer_move.product_qty, 15.0)
+ self.assertEqual(repair_transfer_move.product_id, rma.product_id)
rma.repair_ids.action_repair_confirm()
self.assertEqual(rma.repair_count, 1)
self.assertEqual(rma.qty_to_repair, 0.0)
@@ -263,6 +300,7 @@ class TestRmaRepair(common.SingleTransactionCase):
for mv in picking.move_ids:
mv.quantity_done = mv.product_uom_qty
picking._action_done()
+ self.assertEqual(rma.repair_transfer_count, 0)
self.assertEqual(rma.qty_to_deliver, 0.0)
make_repair = self.rma_make_repair_wiz.with_context(
**{
@@ -272,6 +310,13 @@ class TestRmaRepair(common.SingleTransactionCase):
}
).new()
make_repair.make_repair_order()
+ rma._compute_repair_transfer_count()
+ self.assertEqual(rma.repair_transfer_count, 1)
+ repair_transfer_move = rma.move_ids.filtered(
+ lambda x: x.location_dest_id == self.repair_loc
+ )
+ self.assertEqual(repair_transfer_move.location_id, self.stock_rma_location)
+ self.assertEqual(repair_transfer_move.product_id, rma.product_id)
repair = rma.repair_ids
line = self.repair_line_obj.create(
{
@@ -290,6 +335,7 @@ class TestRmaRepair(common.SingleTransactionCase):
repair.invoice_method = "after_repair"
repair.action_repair_confirm()
repair.action_repair_start()
+ repair.action_force_availability()
repair.action_repair_end()
self.assertEqual(rma.qty_to_pay, 0.0)
repair.action_repair_invoice_create()
diff --git a/rma_repair/views/rma_operation_view.xml b/rma_repair/views/rma_operation_view.xml
index 10e29203..7ee81bc3 100644
--- a/rma_repair/views/rma_operation_view.xml
+++ b/rma_repair/views/rma_operation_view.xml
@@ -21,6 +21,7 @@
+
diff --git a/rma_repair/views/rma_order_line_view.xml b/rma_repair/views/rma_order_line_view.xml
index ba943994..a6a52716 100644
--- a/rma_repair/views/rma_order_line_view.xml
+++ b/rma_repair/views/rma_order_line_view.xml
@@ -13,7 +13,7 @@
class="oe_stat_button"
icon="fa-wrench"
groups="stock.group_stock_user"
- attrs="{'invisible':[('repair_count','=',0)]}"
+ attrs="{'invisible': [('repair_count', '=', 0)]}"
>
+
@@ -51,13 +65,13 @@
name="%(action_rma_order_line_make_repair)d"
string="Create Repair Order"
class="oe_highlight"
- attrs="{'invisible':['|', '|', ('type', '!=', 'customer'), ('qty_to_repair', '=', 0), ('state', '!=', 'approved')]}"
+ attrs="{'invisible':['|', '|', ('type', '!=', 'customer'), '|', ('qty_to_repair', '=', 0), ('qty_to_repair', '<', 0), ('state', '!=', 'approved')]}"
type="action"
/>
diff --git a/rma_repair/views/rma_order_view.xml b/rma_repair/views/rma_order_view.xml
index f3547211..60463e51 100644
--- a/rma_repair/views/rma_order_view.xml
+++ b/rma_repair/views/rma_order_view.xml
@@ -21,6 +21,20 @@
string="Repair Orders"
/>
+
diff --git a/rma_repair/wizards/rma_order_line_make_repair.py b/rma_repair/wizards/rma_order_line_make_repair.py
index 258886c0..2ac26989 100644
--- a/rma_repair/wizards/rma_order_line_make_repair.py
+++ b/rma_repair/wizards/rma_order_line_make_repair.py
@@ -1,8 +1,10 @@
# Copyright 2020 ForgeFlow S.L.
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
+import time
from odoo import _, api, fields, models
-from odoo.exceptions import ValidationError
+from odoo.exceptions import UserError, ValidationError
+from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT as DT_FORMAT
class RmaLineMakeRepair(models.TransientModel):
@@ -56,6 +58,13 @@ class RmaLineMakeRepair(models.TransientModel):
data = item._prepare_repair_order(rma_line)
repair = repair_obj.create(data)
res.append(repair.id)
+ if rma_line.location_id != repair.location_id:
+ item._run_procurement(
+ rma_line.operation_id.repair_route_id, repair.location_id
+ )
+ item._run_procurement(
+ rma_line.operation_id.repair_route_id, rma_line.location_id
+ )
return {
"domain": [("id", "in", res)],
"name": _("Repairs"),
@@ -72,7 +81,7 @@ class RmaLineMakeRepairItem(models.TransientModel):
_description = "RMA Line Make Repair Item"
@api.constrains("product_qty")
- def _check_prodcut_qty(self):
+ def _check_product_qty(self):
for rec in self:
if rec.product_qty <= 0.0:
raise ValidationError(_("Quantity must be positive."))
@@ -138,3 +147,76 @@ class RmaLineMakeRepairItem(models.TransientModel):
"partner_invoice_id": addr["invoice"],
"lot_id": rma_line.lot_id.id,
}
+
+ def _run_procurement(self, route, dest_location):
+ procurements = []
+ errors = []
+ procurement = self._prepare_procurement(route, dest_location)
+ procurements.append(procurement)
+ try:
+ self.env["procurement.group"].with_context(picking_type="internal").run(
+ procurements
+ )
+ except UserError as error:
+ errors.append(error.args[0])
+ if errors:
+ raise UserError("\n".join(errors))
+ return procurements
+
+ def find_procurement_group(self):
+ if self.line_id.rma_id:
+ return self.env["procurement.group"].search(
+ [("rma_id", "=", self.line_id.rma_id.id)], limit=1
+ )
+ else:
+ return self.env["procurement.group"].search(
+ [("rma_line_id", "=", self.line_id.id)], limit=1
+ )
+
+ def _get_procurement_group(self):
+ group_data = {
+ "partner_id": self.line_id.partner_id.id,
+ "name": self.line_id.rma_id.name or self.line_id.name,
+ "rma_id": self.line_id.rma_id and self.line_id.rma_id.id or False,
+ "rma_line_id": self.line_id.id if not self.line_id.rma_id else False,
+ }
+ return self.env["procurement.group"].create(group_data)
+
+ @api.model
+ def _get_procurement_data(self, route, dest_location):
+ if not route:
+ raise ValidationError(_("No route specified"))
+ group = self.find_procurement_group()
+ if not group:
+ group = self._get_procurement_group()
+ procurement_data = {
+ "name": self.line_id and self.line_id.name,
+ "group_id": group,
+ "warehouse": dest_location.warehouse_id,
+ "origin": self.line_id.name,
+ "date_planned": time.strftime(DT_FORMAT),
+ "product_id": self.product_id,
+ "product_qty": self.product_qty,
+ "product_uom": self.product_id.product_tmpl_id.uom_id.id,
+ "location_id": dest_location,
+ "partner_id": self.partner_id.id,
+ "route_ids": route,
+ "rma_line_id": self.line_id.id,
+ "is_rma_repair_transfer": True,
+ }
+ return procurement_data
+
+ @api.model
+ def _prepare_procurement(self, route, dest_location):
+ values = self._get_procurement_data(route, dest_location)
+ procurement = self.env["procurement.group"].Procurement(
+ self.product_id,
+ self.product_qty,
+ self.product_id.product_tmpl_id.uom_id,
+ dest_location,
+ values.get("origin"),
+ values.get("origin"),
+ self.env.company,
+ values,
+ )
+ return procurement
diff --git a/rma_repair/wizards/rma_order_line_make_repair_view.xml b/rma_repair/wizards/rma_order_line_make_repair_view.xml
index ca619669..3361fb52 100644
--- a/rma_repair/wizards/rma_order_line_make_repair_view.xml
+++ b/rma_repair/wizards/rma_order_line_make_repair_view.xml
@@ -12,14 +12,23 @@
-
-
+
+
-
-
+
+