[IMP] rma_repair: create transfer from RMA to Repair location

This commit is contained in:
DavidJForgeFlow
2023-03-20 11:29:49 +01:00
committed by JasminSForgeFlow
parent 04766b4d1b
commit 24821fb3cd
12 changed files with 294 additions and 17 deletions

View File

@@ -4,3 +4,5 @@ from . import repair
from . import rma_order_line from . import rma_order_line
from . import rma_order from . import rma_order
from . import rma_operation from . import rma_operation
from . import stock_move
from . import stock_rule

View File

@@ -35,3 +35,8 @@ class RmaOperation(models.Model):
"respectively. 'No invoice' means you don't want to generate " "respectively. 'No invoice' means you don't want to generate "
"invoice for this repair order.", "invoice for this repair order.",
) )
repair_route_id = fields.Many2one(
comodel_name="stock.route",
string="Repair Route",
domain=[("rma_selectable", "=", True)],
)

View File

@@ -12,13 +12,37 @@ class RmaOrder(models.Model):
repairs = rma.mapped("rma_line_ids.repair_ids") repairs = rma.mapped("rma_line_ids.repair_ids")
rma.repair_count = len(repairs) 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( repair_count = fields.Integer(
compute="_compute_repair_count", string="# of Repairs" 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): def action_view_repair_order(self):
action = self.env.ref("repair.action_repair_order_tree") action = self.env.ref("repair.action_repair_order_tree")
result = action.sudo().read()[0] result = action.sudo().read()[0]
repair_ids = self.mapped("rma_line_ids.repair_ids").ids repair_ids = self.mapped("rma_line_ids.repair_ids").ids
result["domain"] = [("id", "in", repair_ids)] result["domain"] = [("id", "in", repair_ids)]
return result 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)

View File

@@ -36,6 +36,13 @@ class RmaOrderLine(models.Model):
for line in self: for line in self:
line.repair_count = len(line.repair_ids) 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( repair_ids = fields.One2many(
comodel_name="repair.order", comodel_name="repair.order",
inverse_name="rma_line_id", inverse_name="rma_line_id",
@@ -80,6 +87,10 @@ class RmaOrderLine(models.Model):
compute="_compute_repair_count", string="# of Repairs" 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( delivery_policy = fields.Selection(
selection_add=[("repair", "Based on Repair Quantities")], selection_add=[("repair", "Based on Repair Quantities")],
ondelete={"repair": lambda recs: recs.write({"delivery_policy": "no"})}, 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"): for rec in self.filtered(lambda l: l.delivery_policy == "repair"):
rec.qty_to_deliver = rec.qty_repaired - rec.qty_delivered rec.qty_to_deliver = rec.qty_repaired - rec.qty_delivered
return res 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

View File

@@ -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

View File

@@ -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

View File

@@ -28,6 +28,36 @@ class TestRmaRepair(common.SingleTransactionCase):
# Create partners # Create partners
cls.customer1 = cls.partner_obj.create({"name": "Customer 1"}) 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: # Create RMA group and operation:
cls.rma_group_customer = cls.rma_obj.create( cls.rma_group_customer = cls.rma_obj.create(
{"partner_id": cls.customer1.id, "type": "customer"} {"partner_id": cls.customer1.id, "type": "customer"}
@@ -41,6 +71,8 @@ class TestRmaRepair(common.SingleTransactionCase):
"repair_type": "received", "repair_type": "received",
"in_route_id": cls.rma_route_cust.id, "in_route_id": cls.rma_route_cust.id,
"out_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( cls.operation_2 = cls.rma_op.create(
@@ -52,6 +84,8 @@ class TestRmaRepair(common.SingleTransactionCase):
"repair_type": "ordered", "repair_type": "ordered",
"in_route_id": cls.rma_route_cust.id, "in_route_id": cls.rma_route_cust.id,
"out_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( cls.operation_3 = cls.rma_op.create(
@@ -64,6 +98,8 @@ class TestRmaRepair(common.SingleTransactionCase):
"delivery_policy": "repair", "delivery_policy": "repair",
"in_route_id": cls.rma_route_cust.id, "in_route_id": cls.rma_route_cust.id,
"out_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 # Create products
@@ -154,14 +190,6 @@ class TestRmaRepair(common.SingleTransactionCase):
cls.material.product_tmpl_id.standard_price = 10 cls.material.product_tmpl_id.standard_price = 10
cls.stock_location = cls.env.ref("stock.stock_location_stock") 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): def test_01_add_from_invoice_customer(self):
"""Test wizard to create RMA from a customer invoice.""" """Test wizard to create RMA from a customer invoice."""
add_inv = self.rma_add_invoice_wiz.with_context( 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.repair_count, 0)
self.assertEqual(rma.qty_to_repair, 15.0) self.assertEqual(rma.qty_to_repair, 15.0)
self.assertEqual(rma.qty_repaired, 0.0) self.assertEqual(rma.qty_repaired, 0.0)
self.assertEqual(rma.repair_transfer_count, 0)
make_repair = self.rma_make_repair_wiz.with_context( make_repair = self.rma_make_repair_wiz.with_context(
**{ **{
"customer": True, "customer": True,
@@ -225,6 +254,14 @@ class TestRmaRepair(common.SingleTransactionCase):
} }
).new() ).new()
make_repair.make_repair_order() 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() rma.repair_ids.action_repair_confirm()
self.assertEqual(rma.repair_count, 1) self.assertEqual(rma.repair_count, 1)
self.assertEqual(rma.qty_to_repair, 0.0) self.assertEqual(rma.qty_to_repair, 0.0)
@@ -263,6 +300,7 @@ class TestRmaRepair(common.SingleTransactionCase):
for mv in picking.move_ids: for mv in picking.move_ids:
mv.quantity_done = mv.product_uom_qty mv.quantity_done = mv.product_uom_qty
picking._action_done() picking._action_done()
self.assertEqual(rma.repair_transfer_count, 0)
self.assertEqual(rma.qty_to_deliver, 0.0) self.assertEqual(rma.qty_to_deliver, 0.0)
make_repair = self.rma_make_repair_wiz.with_context( make_repair = self.rma_make_repair_wiz.with_context(
**{ **{
@@ -272,6 +310,13 @@ class TestRmaRepair(common.SingleTransactionCase):
} }
).new() ).new()
make_repair.make_repair_order() 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 repair = rma.repair_ids
line = self.repair_line_obj.create( line = self.repair_line_obj.create(
{ {
@@ -290,6 +335,7 @@ class TestRmaRepair(common.SingleTransactionCase):
repair.invoice_method = "after_repair" repair.invoice_method = "after_repair"
repair.action_repair_confirm() repair.action_repair_confirm()
repair.action_repair_start() repair.action_repair_start()
repair.action_force_availability()
repair.action_repair_end() repair.action_repair_end()
self.assertEqual(rma.qty_to_pay, 0.0) self.assertEqual(rma.qty_to_pay, 0.0)
repair.action_repair_invoice_create() repair.action_repair_invoice_create()

View File

@@ -21,6 +21,7 @@
<group name="repair" string="Repair"> <group name="repair" string="Repair">
<field name="repair_location_id" /> <field name="repair_location_id" />
<field name="repair_invoice_method" /> <field name="repair_invoice_method" />
<field name="repair_route_id" />
</group> </group>
</group> </group>
<field name="delivery_policy" position="after"> <field name="delivery_policy" position="after">

View File

@@ -21,6 +21,20 @@
string="Repair Orders" string="Repair Orders"
/> />
</button> </button>
<button
type="object"
name="action_view_repair_transfers"
class="oe_stat_button"
icon="fa-truck"
groups="stock.group_stock_user"
attrs="{'invisible': [('repair_transfer_count', '=', 0)]}"
>
<field
name="repair_transfer_count"
widget="statinfo"
string="Repair Transfers"
/>
</button>
</div> </div>
<group name="quantities" position="inside"> <group name="quantities" position="inside">
<group attrs="{'invisible': [('repair_type', '=', 'no')]}"> <group attrs="{'invisible': [('repair_type', '=', 'no')]}">
@@ -51,13 +65,13 @@
name="%(action_rma_order_line_make_repair)d" name="%(action_rma_order_line_make_repair)d"
string="Create Repair Order" string="Create Repair Order"
class="oe_highlight" class="oe_highlight"
attrs="{'invisible':['|', '|', ('type', '!=', 'customer'), ('qty_to_repair', '=', 0), ('state', '!=', 'approved')]}" attrs="{'invisible':['|', '|', ('type', '!=', 'customer'), '|', ('qty_to_repair', '=', 0), ('qty_to_repair', '&lt;', 0), ('state', '!=', 'approved')]}"
type="action" type="action"
/> />
<button <button
name="%(action_rma_order_line_make_repair)d" name="%(action_rma_order_line_make_repair)d"
string="Create Repair Order" string="Create Repair Order"
attrs="{'invisible':['|', '|', ('type', '!=', 'customer'), ('qty_to_repair', '!=', 0), ('state', '!=', 'approved')]}" attrs="{'invisible':['|', '|', ('type', '!=', 'customer'), ('qty_to_repair', '>', 0), ('state', '!=', 'approved')]}"
type="action" type="action"
/> />
</header> </header>

View File

@@ -21,6 +21,20 @@
string="Repair Orders" string="Repair Orders"
/> />
</button> </button>
<button
type="object"
name="action_view_repair_transfers"
class="oe_stat_button"
icon="fa-truck"
groups="stock.group_stock_user"
attrs="{'invisible': [('repair_transfer_count', '=', 0)]}"
>
<field
name="repair_transfer_count"
widget="statinfo"
string="Repair Transfers"
/>
</button>
</div> </div>
<xpath expr="//field[@name='rma_line_ids']/tree" position="inside"> <xpath expr="//field[@name='rma_line_ids']/tree" position="inside">
<field name="repair_type" invisible="True" /> <field name="repair_type" invisible="True" />

View File

@@ -1,8 +1,10 @@
# Copyright 2020 ForgeFlow S.L. # Copyright 2020 ForgeFlow S.L.
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
import time
from odoo import _, api, fields, models 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): class RmaLineMakeRepair(models.TransientModel):
@@ -56,6 +58,13 @@ class RmaLineMakeRepair(models.TransientModel):
data = item._prepare_repair_order(rma_line) data = item._prepare_repair_order(rma_line)
repair = repair_obj.create(data) repair = repair_obj.create(data)
res.append(repair.id) 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 { return {
"domain": [("id", "in", res)], "domain": [("id", "in", res)],
"name": _("Repairs"), "name": _("Repairs"),
@@ -72,7 +81,7 @@ class RmaLineMakeRepairItem(models.TransientModel):
_description = "RMA Line Make Repair Item" _description = "RMA Line Make Repair Item"
@api.constrains("product_qty") @api.constrains("product_qty")
def _check_prodcut_qty(self): def _check_product_qty(self):
for rec in self: for rec in self:
if rec.product_qty <= 0.0: if rec.product_qty <= 0.0:
raise ValidationError(_("Quantity must be positive.")) raise ValidationError(_("Quantity must be positive."))
@@ -138,3 +147,76 @@ class RmaLineMakeRepairItem(models.TransientModel):
"partner_invoice_id": addr["invoice"], "partner_invoice_id": addr["invoice"],
"lot_id": rma_line.lot_id.id, "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

View File

@@ -12,14 +12,23 @@
<group> <group>
<field name="item_ids" nolabel="1" colspan="2"> <field name="item_ids" nolabel="1" colspan="2">
<tree name="Details" editable="bottom" create="false"> <tree name="Details" editable="bottom" create="false">
<field name="line_id" options="{'no_open': true}" /> <field
<field name="product_id" /> name="line_id"
force_save="1"
options="{'no_open': true}"
/>
<field name="product_id" force_save="1" />
<field name="product_qty" /> <field name="product_qty" />
<field name="product_uom_id" groups="uom.group_uom" /> <field
<field name="partner_id" /> name="product_uom_id"
force_save="1"
groups="uom.group_uom"
/>
<field name="partner_id" force_save="1" />
<field <field
name="location_id" name="location_id"
groups="stock.group_stock_multi_locations" groups="stock.group_stock_multi_locations"
force_save="1"
/> />
<field name="invoice_method" /> <field name="invoice_method" />
</tree> </tree>