mirror of
https://github.com/OCA/rma.git
synced 2025-02-16 17:11:47 +02:00
[IMP] rma_sale_mrp: black, isort, prettier
This commit is contained in:
@@ -10,10 +10,7 @@
|
||||
"author": "Tecnativa, Odoo Community Association (OCA)",
|
||||
"maintainers": ["chienandalu"],
|
||||
"license": "AGPL-3",
|
||||
"depends": [
|
||||
"rma_sale",
|
||||
"mrp",
|
||||
],
|
||||
"depends": ["rma_sale", "mrp"],
|
||||
"data": [
|
||||
"views/sale_order_portal_template.xml",
|
||||
"views/rma_views.xml",
|
||||
|
||||
@@ -10,10 +10,14 @@ class AccountInvoice(models.Model):
|
||||
def _check_rma_invoice_lines_qty(self):
|
||||
"""For those with differences, check if the kit quantity is the same"""
|
||||
precision = self.env["decimal.precision"].precision_get(
|
||||
"Product Unit of Measure")
|
||||
"Product Unit of Measure"
|
||||
)
|
||||
lines = super()._check_rma_invoice_lines_qty()
|
||||
if lines:
|
||||
return lines.sudo().filtered(
|
||||
lambda r: (r.rma_id.phantom_bom_product and float_compare(
|
||||
r.quantity, r.rma_id.kit_qty, precision) < 0))
|
||||
lambda r: (
|
||||
r.rma_id.phantom_bom_product
|
||||
and float_compare(r.quantity, r.rma_id.kit_qty, precision) < 0
|
||||
)
|
||||
)
|
||||
return lines
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
from odoo import _, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
import odoo.addons.decimal_precision as dp
|
||||
|
||||
|
||||
@@ -9,57 +10,57 @@ class Rma(models.Model):
|
||||
_inherit = "rma"
|
||||
|
||||
phantom_bom_product = fields.Many2one(
|
||||
comodel_name="product.product",
|
||||
string="Related kit product",
|
||||
readonly=True,
|
||||
comodel_name="product.product", string="Related kit product", readonly=True,
|
||||
)
|
||||
kit_qty = fields.Float(
|
||||
string="Kit quantity",
|
||||
digits=dp.get_precision("Product Unit of Measure"),
|
||||
readonly=True,
|
||||
help="To how many kits this components corresponds to. Used mainly "
|
||||
"for refunding the right quantity",
|
||||
"for refunding the right quantity",
|
||||
)
|
||||
rma_kit_register = fields.Char(readonly=True)
|
||||
|
||||
def _get_refund_line_quantity(self):
|
||||
"""Refund the kit, not the component"""
|
||||
if self.phantom_bom_product:
|
||||
uom = (
|
||||
self.sale_line_id.product_uom
|
||||
or self.phantom_bom_product.uom_id
|
||||
)
|
||||
uom = self.sale_line_id.product_uom or self.phantom_bom_product.uom_id
|
||||
return (self.kit_qty, uom)
|
||||
return (self.product_uom_qty, self.product_uom)
|
||||
|
||||
def action_refund(self):
|
||||
"""We want to process them altogether"""
|
||||
phantom_rmas = self.filtered("phantom_bom_product")
|
||||
phantom_rmas |= self.search([
|
||||
("rma_kit_register", "in", phantom_rmas.mapped("rma_kit_register")),
|
||||
("id", "not in", phantom_rmas.ids),
|
||||
])
|
||||
phantom_rmas |= self.search(
|
||||
[
|
||||
("rma_kit_register", "in", phantom_rmas.mapped("rma_kit_register")),
|
||||
("id", "not in", phantom_rmas.ids),
|
||||
]
|
||||
)
|
||||
self -= phantom_rmas
|
||||
for rma_kit_register in phantom_rmas.mapped(
|
||||
"rma_kit_register"):
|
||||
for rma_kit_register in phantom_rmas.mapped("rma_kit_register"):
|
||||
# We want to avoid refunding kits that aren't completely processed
|
||||
rmas_by_register = phantom_rmas.filtered(
|
||||
lambda x: x.rma_kit_register == rma_kit_register)
|
||||
lambda x: x.rma_kit_register == rma_kit_register
|
||||
)
|
||||
if any(rmas_by_register.filtered(lambda x: x.state != "received")):
|
||||
raise UserError(_(
|
||||
"You can't refund a kit in wich some RMAs aren't received"
|
||||
))
|
||||
raise UserError(
|
||||
_("You can't refund a kit in wich some RMAs aren't received")
|
||||
)
|
||||
self |= rmas_by_register[0]
|
||||
super().action_refund()
|
||||
# We can just link the line to an RMA but we can link several RMAs
|
||||
# to one invoice line.
|
||||
for rma_kit_register in set(phantom_rmas.mapped("rma_kit_register")):
|
||||
grouped_rmas = phantom_rmas.filtered(
|
||||
lambda x: x.rma_kit_register == rma_kit_register)
|
||||
lambda x: x.rma_kit_register == rma_kit_register
|
||||
)
|
||||
lead_rma = grouped_rmas.filtered("refund_line_id")
|
||||
grouped_rmas -= lead_rma
|
||||
grouped_rmas.write({
|
||||
"refund_line_id": lead_rma.refund_line_id.id,
|
||||
"refund_id": lead_rma.refund_id.id,
|
||||
"state": "refunded",
|
||||
})
|
||||
grouped_rmas.write(
|
||||
{
|
||||
"refund_line_id": lead_rma.refund_line_id.id,
|
||||
"refund_id": lead_rma.refund_id.id,
|
||||
"state": "refunded",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -18,15 +18,11 @@ class SaleOrder(models.Model):
|
||||
def get_delivery_rma_data(self):
|
||||
"""Get the phantom lines we'll be showing in the wizard"""
|
||||
data_list = super().get_delivery_rma_data()
|
||||
kit_products = set(
|
||||
[
|
||||
(
|
||||
x.get("phantom_bom_product"),
|
||||
x.get("sale_line_id")
|
||||
) for x in data_list
|
||||
if x.get("phantom_bom_product")
|
||||
]
|
||||
)
|
||||
kit_products = {
|
||||
(x.get("phantom_bom_product"), x.get("sale_line_id"))
|
||||
for x in data_list
|
||||
if x.get("phantom_bom_product")
|
||||
}
|
||||
# For every unique phantom product we'll create a phantom line wich
|
||||
# will be using as the control in frontend and for display purposes
|
||||
# in backend
|
||||
@@ -34,35 +30,40 @@ class SaleOrder(models.Model):
|
||||
order_line_obj = self.env["sale.order.line"]
|
||||
product_obj = self.env["product.product"]
|
||||
first_component_dict = next(
|
||||
x for x in data_list if x.get(
|
||||
"phantom_bom_product", product_obj
|
||||
) == product and x.get(
|
||||
"sale_line_id", order_line_obj
|
||||
) == sale_line_id)
|
||||
x
|
||||
for x in data_list
|
||||
if x.get("phantom_bom_product", product_obj) == product
|
||||
and x.get("sale_line_id", order_line_obj) == sale_line_id
|
||||
)
|
||||
component_index = data_list.index(first_component_dict)
|
||||
# Prevent miscalculation if there partial deliveries
|
||||
quantity = sum([
|
||||
x.get("quantity", 0) for x in data_list
|
||||
if x.get("sale_line_id")
|
||||
and x.get("product") == first_component_dict.get("product")
|
||||
and x.get("sale_line_id") == first_component_dict.get(
|
||||
"sale_line_id")])
|
||||
quantity = sum(
|
||||
[
|
||||
x.get("quantity", 0)
|
||||
for x in data_list
|
||||
if x.get("sale_line_id")
|
||||
and x.get("product") == first_component_dict.get("product")
|
||||
and x.get("sale_line_id")
|
||||
== first_component_dict.get("sale_line_id")
|
||||
]
|
||||
)
|
||||
data_list.insert(
|
||||
component_index, {
|
||||
component_index,
|
||||
{
|
||||
"product": product,
|
||||
"quantity": (
|
||||
first_component_dict.get("per_kit_quantity") and (
|
||||
quantity
|
||||
/ first_component_dict.get("per_kit_quantity")
|
||||
)
|
||||
first_component_dict.get("per_kit_quantity")
|
||||
and (quantity / first_component_dict.get("per_kit_quantity"))
|
||||
),
|
||||
"uom": first_component_dict.get(
|
||||
"sale_line_id", order_line_obj).product_uom,
|
||||
"sale_line_id", order_line_obj
|
||||
).product_uom,
|
||||
"phantom_kit_line": True,
|
||||
"picking": False,
|
||||
"sale_line_id": first_component_dict.get(
|
||||
"sale_line_id", order_line_obj),
|
||||
}
|
||||
"sale_line_id", order_line_obj
|
||||
),
|
||||
},
|
||||
)
|
||||
return data_list
|
||||
|
||||
@@ -74,12 +75,17 @@ class SaleOrderLine(models.Model):
|
||||
self.ensure_one()
|
||||
if self.product_id and not self.product_id._is_phantom_bom():
|
||||
return super().get_delivery_move()
|
||||
return self.move_ids.filtered(lambda m: (
|
||||
m.state == "done" and not m.scrapped
|
||||
and m.location_dest_id.usage == "customer"
|
||||
and (
|
||||
not m.origin_returned_move_id
|
||||
or (m.origin_returned_move_id and m.to_refund))))
|
||||
return self.move_ids.filtered(
|
||||
lambda m: (
|
||||
m.state == "done"
|
||||
and not m.scrapped
|
||||
and m.location_dest_id.usage == "customer"
|
||||
and (
|
||||
not m.origin_returned_move_id
|
||||
or (m.origin_returned_move_id and m.to_refund)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def prepare_sale_rma_data(self):
|
||||
"""We'll take both the sale order product and the phantom one so we
|
||||
@@ -91,7 +97,7 @@ class SaleOrderLine(models.Model):
|
||||
d.update(
|
||||
{
|
||||
"phantom_bom_product": self.product_id,
|
||||
"per_kit_quantity": self._get_kit_qty(d.get("product"))
|
||||
"per_kit_quantity": self._get_kit_qty(d.get("product")),
|
||||
}
|
||||
)
|
||||
return data
|
||||
@@ -105,6 +111,7 @@ class SaleOrderLine(models.Model):
|
||||
return 0
|
||||
component_demand = sum(
|
||||
self.move_ids.filtered(
|
||||
lambda x: x.product_id == product_id
|
||||
and not x.origin_returned_move_id).mapped("product_uom_qty"))
|
||||
lambda x: x.product_id == product_id and not x.origin_returned_move_id
|
||||
).mapped("product_uom_qty")
|
||||
)
|
||||
return component_demand / self.product_uom_qty
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Copyright 2020 Tecnativa - David Vidal
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
from odoo.tests import Form, SavepointCase
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.tests import Form, SavepointCase
|
||||
|
||||
|
||||
class TestRmaSaleMrp(SavepointCase):
|
||||
@@ -11,39 +11,38 @@ class TestRmaSaleMrp(SavepointCase):
|
||||
cls.res_partner = cls.env["res.partner"]
|
||||
cls.product_product = cls.env["product.product"]
|
||||
cls.sale_order = cls.env["sale.order"]
|
||||
cls.product_kit = cls.product_product.create({
|
||||
"name": "Product test 1",
|
||||
"type": "consu",
|
||||
})
|
||||
cls.product_kit_comp_1 = cls.product_product.create({
|
||||
"name": "Product Component 1",
|
||||
"type": "product",
|
||||
})
|
||||
cls.product_kit_comp_2 = cls.product_product.create({
|
||||
"name": "Product Component 2",
|
||||
"type": "product",
|
||||
})
|
||||
cls.bom = cls.env["mrp.bom"].create({
|
||||
"product_id": cls.product_kit.id,
|
||||
"product_tmpl_id": cls.product_kit.product_tmpl_id.id,
|
||||
"type": "phantom",
|
||||
"bom_line_ids": [
|
||||
(0, 0, {
|
||||
"product_id": cls.product_kit_comp_1.id,
|
||||
"product_qty": 2,
|
||||
}),
|
||||
(0, 0, {
|
||||
"product_id": cls.product_kit_comp_2.id,
|
||||
"product_qty": 4,
|
||||
})
|
||||
]})
|
||||
cls.product_2 = cls.product_product.create({
|
||||
"name": "Product test 2",
|
||||
"type": "product",
|
||||
})
|
||||
cls.partner = cls.res_partner.create({
|
||||
"name": "Partner test",
|
||||
})
|
||||
cls.product_kit = cls.product_product.create(
|
||||
{"name": "Product test 1", "type": "consu"}
|
||||
)
|
||||
cls.product_kit_comp_1 = cls.product_product.create(
|
||||
{"name": "Product Component 1", "type": "product"}
|
||||
)
|
||||
cls.product_kit_comp_2 = cls.product_product.create(
|
||||
{"name": "Product Component 2", "type": "product"}
|
||||
)
|
||||
cls.bom = cls.env["mrp.bom"].create(
|
||||
{
|
||||
"product_id": cls.product_kit.id,
|
||||
"product_tmpl_id": cls.product_kit.product_tmpl_id.id,
|
||||
"type": "phantom",
|
||||
"bom_line_ids": [
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{"product_id": cls.product_kit_comp_1.id, "product_qty": 2},
|
||||
),
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{"product_id": cls.product_kit_comp_2.id, "product_qty": 4},
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
cls.product_2 = cls.product_product.create(
|
||||
{"name": "Product test 2", "type": "product"}
|
||||
)
|
||||
cls.partner = cls.res_partner.create({"name": "Partner test"})
|
||||
order_form = Form(cls.sale_order)
|
||||
order_form.partner_id = cls.partner
|
||||
with order_form.order_line.new() as line_form:
|
||||
@@ -54,7 +53,8 @@ class TestRmaSaleMrp(SavepointCase):
|
||||
# Maybe other modules create additional lines in the create
|
||||
# method in sale.order model, so let's find the correct line.
|
||||
cls.order_line = cls.sale_order.order_line.filtered(
|
||||
lambda r: r.product_id == cls.product_kit)
|
||||
lambda r: r.product_id == cls.product_kit
|
||||
)
|
||||
cls.order_out_picking = cls.sale_order.picking_ids
|
||||
# Confirm but leave a backorder to split moves so we can test that
|
||||
# the wizard correctly creates the RMAs with the proper quantities
|
||||
@@ -82,8 +82,7 @@ class TestRmaSaleMrp(SavepointCase):
|
||||
self.assertTrue(rma.picking_id in out_pickings)
|
||||
self.assertEqual(rmas.mapped("phantom_bom_product"), self.product_kit)
|
||||
self.assertEqual(
|
||||
rmas.mapped("product_id"),
|
||||
self.product_kit_comp_1 + self.product_kit_comp_2
|
||||
rmas.mapped("product_id"), self.product_kit_comp_1 + self.product_kit_comp_2
|
||||
)
|
||||
rma_1 = rmas.filtered(lambda x: x.product_id == self.product_kit_comp_1)
|
||||
rma_2 = rmas.filtered(lambda x: x.product_id == self.product_kit_comp_2)
|
||||
@@ -93,42 +92,26 @@ class TestRmaSaleMrp(SavepointCase):
|
||||
move_2 = out_pickings.mapped("move_lines").filtered(
|
||||
lambda x: x.product_id == self.product_kit_comp_2
|
||||
)
|
||||
self.assertEqual(
|
||||
sum(rma_1.mapped("product_uom_qty")),
|
||||
8
|
||||
)
|
||||
self.assertEqual(
|
||||
rma_1.mapped("product_uom"),
|
||||
move_1.mapped("product_uom")
|
||||
)
|
||||
self.assertEqual(
|
||||
sum(rma_2.mapped("product_uom_qty")),
|
||||
16
|
||||
)
|
||||
self.assertEqual(
|
||||
rma_2.mapped("product_uom"),
|
||||
move_2.mapped("product_uom")
|
||||
)
|
||||
self.assertEqual(sum(rma_1.mapped("product_uom_qty")), 8)
|
||||
self.assertEqual(rma_1.mapped("product_uom"), move_1.mapped("product_uom"))
|
||||
self.assertEqual(sum(rma_2.mapped("product_uom_qty")), 16)
|
||||
self.assertEqual(rma_2.mapped("product_uom"), move_2.mapped("product_uom"))
|
||||
self.assertEqual(rma.state, "confirmed")
|
||||
self.assertEqual(
|
||||
rma_1.mapped("reception_move_id.origin_returned_move_id"),
|
||||
move_1,
|
||||
rma_1.mapped("reception_move_id.origin_returned_move_id"), move_1,
|
||||
)
|
||||
self.assertEqual(
|
||||
rma_2.mapped("reception_move_id.origin_returned_move_id"),
|
||||
move_2,
|
||||
rma_2.mapped("reception_move_id.origin_returned_move_id"), move_2,
|
||||
)
|
||||
self.assertEqual(
|
||||
rmas.mapped("reception_move_id.picking_id")
|
||||
+ self.order_out_picking + self.backorder,
|
||||
+ self.order_out_picking
|
||||
+ self.backorder,
|
||||
order.picking_ids,
|
||||
)
|
||||
# Refund the RMA
|
||||
user = self.env["res.users"].create(
|
||||
{
|
||||
"login": "test_refund_with_so",
|
||||
"name": "Test",
|
||||
}
|
||||
{"login": "test_refund_with_so", "name": "Test"}
|
||||
)
|
||||
order.user_id = user.id
|
||||
rma.reception_move_id.quantity_done = rma.product_uom_qty
|
||||
@@ -139,15 +122,15 @@ class TestRmaSaleMrp(SavepointCase):
|
||||
rmas_left = rmas - rma
|
||||
for additional_rma in rmas_left:
|
||||
additional_rma.reception_move_id.quantity_done = (
|
||||
additional_rma.product_uom_qty)
|
||||
additional_rma.product_uom_qty
|
||||
)
|
||||
additional_rma.reception_move_id.picking_id.action_done()
|
||||
rma.action_refund()
|
||||
self.assertEqual(rma.refund_id.user_id, user)
|
||||
# The component RMAs get automatically refunded
|
||||
self.assertEqual(rma.refund_id, rmas_left.mapped("refund_id"))
|
||||
# The refund product is the kit, not the components
|
||||
self.assertEqual(
|
||||
rma.refund_id.invoice_line_ids.product_id, self.product_kit)
|
||||
self.assertEqual(rma.refund_id.invoice_line_ids.product_id, self.product_kit)
|
||||
rma.refund_id.action_invoice_open()
|
||||
# We can still return another kit
|
||||
wizard_id = order.action_create_rma()["res_id"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<odoo>
|
||||
<template id="report_rma_document" inherit_id="rma_sale.report_rma_document">
|
||||
@@ -6,17 +6,29 @@
|
||||
<t t-if="doc.sudo().phantom_bom_product">
|
||||
<strong class="d-block mt32 mb-1">Kit information</strong>
|
||||
<div class="row mb32" id="kit_information">
|
||||
<div t-if="doc.sudo().phantom_bom_product" class="col-auto mw-100 mb-2">
|
||||
<div
|
||||
t-if="doc.sudo().phantom_bom_product"
|
||||
class="col-auto mw-100 mb-2"
|
||||
>
|
||||
<strong>Kit:</strong>
|
||||
<p class="m-0" t-field="doc.sudo().phantom_bom_product.display_name"/>
|
||||
<p
|
||||
class="m-0"
|
||||
t-field="doc.sudo().phantom_bom_product.display_name"
|
||||
/>
|
||||
</div>
|
||||
<div t-if="doc.sudo().phantom_bom_product" class="col-auto mw-100 mb-2">
|
||||
<div
|
||||
t-if="doc.sudo().phantom_bom_product"
|
||||
class="col-auto mw-100 mb-2"
|
||||
>
|
||||
<strong>Kit Quantity:</strong>
|
||||
<p class="m-0" t-field="doc.kit_qty"/>
|
||||
<p class="m-0" t-field="doc.kit_qty" />
|
||||
</div>
|
||||
</div>
|
||||
<strong class="d-block mb-1">Related Kit Components RMAs</strong>
|
||||
<t t-set="related_kit_rmas" t-value="doc.search([('rma_kit_register', '=', doc.rma_kit_register), ('id', '!=', doc.id)])" />
|
||||
<t
|
||||
t-set="related_kit_rmas"
|
||||
t-value="doc.search([('rma_kit_register', '=', doc.rma_kit_register), ('id', '!=', doc.id)])"
|
||||
/>
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<th>
|
||||
@@ -35,19 +47,25 @@
|
||||
<tbody>
|
||||
<tr t-foreach="related_kit_rmas" t-as="kit_rma">
|
||||
<td>
|
||||
<span t-field="kit_rma.name"/>
|
||||
<span t-field="kit_rma.name" />
|
||||
</td>
|
||||
<td>
|
||||
<span t-if="kit_rma.product_id" t-field="kit_rma.product_id"/>
|
||||
<span
|
||||
t-if="kit_rma.product_id"
|
||||
t-field="kit_rma.product_id"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<t t-if="kit_rma.product_id">
|
||||
<span t-field="kit_rma.product_uom_qty"/>
|
||||
<span t-field="kit_rma.uom_id" groups="uom.group_uom"/>
|
||||
<span t-field="kit_rma.product_uom_qty" />
|
||||
<span
|
||||
t-field="kit_rma.uom_id"
|
||||
groups="uom.group_uom"
|
||||
/>
|
||||
</t>
|
||||
</td>
|
||||
<td>
|
||||
<span t-field="kit_rma.state"/>
|
||||
<span t-field="kit_rma.state" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -1,28 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<template id="sale_order_portal_template" name="Request RMA MRP" inherit_id="rma_sale.sale_order_portal_template">
|
||||
<template
|
||||
id="sale_order_portal_template"
|
||||
name="Request RMA MRP"
|
||||
inherit_id="rma_sale.sale_order_portal_template"
|
||||
>
|
||||
<xpath expr="//input[@t-attf-name='#{data_index}-product_id']" position="after">
|
||||
<input type="hidden"
|
||||
t-if="data.get('phantom_bom_product')"
|
||||
t-attf-name="#{data_index}-phantom_bom_product"
|
||||
t-att-value="data['phantom_bom_product'].id"/>
|
||||
<input type="hidden"
|
||||
t-if="data.get('per_kit_quantity')"
|
||||
t-attf-name="#{data_index}-per_kit_quantity"
|
||||
t-att-value="data['per_kit_quantity']"/>
|
||||
<input type="hidden"
|
||||
t-if="data.get('phantom_kit_line')"
|
||||
t-attf-name="#{data_index}-phantom_kit_line"
|
||||
t-att-value="1"/>
|
||||
<input
|
||||
type="hidden"
|
||||
t-if="data.get('phantom_bom_product')"
|
||||
t-attf-name="#{data_index}-phantom_bom_product"
|
||||
t-att-value="data['phantom_bom_product'].id"
|
||||
/>
|
||||
<input
|
||||
type="hidden"
|
||||
t-if="data.get('per_kit_quantity')"
|
||||
t-attf-name="#{data_index}-per_kit_quantity"
|
||||
t-att-value="data['per_kit_quantity']"
|
||||
/>
|
||||
<input
|
||||
type="hidden"
|
||||
t-if="data.get('phantom_kit_line')"
|
||||
t-attf-name="#{data_index}-phantom_kit_line"
|
||||
t-att-value="1"
|
||||
/>
|
||||
</xpath>
|
||||
<!-- TODO: We could give a clue about what's to be returned, with readonly detailed lines -->
|
||||
<xpath expr="//tbody[hasclass('request-rma-tbody')]//t[@t-if="data['quantity'] > 0 and data['picking']"]/tr" position="attributes">
|
||||
<attribute name="t-attf-class">#{data.get('phantom_bom_product') and 'd-none'}</attribute>
|
||||
<xpath
|
||||
expr="//tbody[hasclass('request-rma-tbody')]//t[@t-if="data['quantity'] > 0 and data['picking']"]/tr"
|
||||
position="attributes"
|
||||
>
|
||||
<attribute
|
||||
name="t-attf-class"
|
||||
>#{data.get('phantom_bom_product') and 'd-none'}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//tbody[hasclass('request-rma-tbody')]//t[@t-if="data['quantity'] > 0 and data['picking']"]" position="attributes">
|
||||
<attribute name="t-if">data['quantity'] > 0 and (data['picking'] or data.get('phantom_kit_line'))</attribute>
|
||||
<xpath
|
||||
expr="//tbody[hasclass('request-rma-tbody')]//t[@t-if="data['quantity'] > 0 and data['picking']"]"
|
||||
position="attributes"
|
||||
>
|
||||
<attribute
|
||||
name="t-if"
|
||||
>data['quantity'] > 0 and (data['picking'] or data.get('phantom_kit_line'))</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//select[@t-attf-name='#{data_index}-operation_id']/option" position="attributes">
|
||||
<xpath
|
||||
expr="//select[@t-attf-name='#{data_index}-operation_id']/option"
|
||||
position="attributes"
|
||||
>
|
||||
<attribute name="t-if">not data.get('phantom_bom_product')</attribute>
|
||||
</xpath>
|
||||
</template>
|
||||
@@ -34,55 +57,89 @@
|
||||
<strong><i class="fa fa-th-large text-info" /> Kit</strong>
|
||||
</div>
|
||||
<div class="col-12 col-sm-8">
|
||||
<span t-esc="rma.sudo().phantom_bom_product.display_name"/>
|
||||
<span t-esc="rma.sudo().phantom_bom_product.display_name" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2 mb-sm-1">
|
||||
<div class="col-12 col-sm-4">
|
||||
<strong><i class="fa fa-th-large text-info" /> Kit Quantity</strong>
|
||||
<strong><i
|
||||
class="fa fa-th-large text-info"
|
||||
/> Kit Quantity</strong>
|
||||
</div>
|
||||
<div class="col-12 col-sm-8">
|
||||
<span t-esc="rma.kit_qty"/>
|
||||
<span t-esc="rma.kit_qty" />
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</xpath>
|
||||
<xpath expr="//section[@id='reception_section']" position="before">
|
||||
<section t-if="rma.sudo().phantom_bom_product" id="kits_sections" style="page-break-inside: auto;" class="mt32">
|
||||
<section
|
||||
t-if="rma.sudo().phantom_bom_product"
|
||||
id="kits_sections"
|
||||
style="page-break-inside: auto;"
|
||||
class="mt32"
|
||||
>
|
||||
<strong class="d-block mb-1">Related Kit Components RMAs</strong>
|
||||
<t t-set="related_kit_rmas" t-value="rma.search([('rma_kit_register', '=', rma.rma_kit_register), ('id', '!=', rma.id)])" />
|
||||
<t
|
||||
t-set="related_kit_rmas"
|
||||
t-value="rma.search([('rma_kit_register', '=', rma.rma_kit_register), ('id', '!=', rma.id)])"
|
||||
/>
|
||||
<t t-foreach="related_kit_rmas" t-as="kit_rma">
|
||||
<a class="list-group-item list-group-item-action d-flex flex-wrap align-items-center justify-content-between py-2 px-3" t-att-href="kit_rma.get_portal_url()" t-att-title="kit_rma.name">
|
||||
<a
|
||||
class="list-group-item list-group-item-action d-flex flex-wrap align-items-center justify-content-between py-2 px-3"
|
||||
t-att-href="kit_rma.get_portal_url()"
|
||||
t-att-title="kit_rma.name"
|
||||
>
|
||||
<div>
|
||||
<i class="fa fa-reply mr-1" role="img"/>
|
||||
<span t-esc="kit_rma.name" class="mr-lg-3"/>
|
||||
<div class="d-lg-inline-block"><span t-field="kit_rma.sudo().product_id.display_name"/> (<t t-field="kit_rma.product_uom_qty" />)</div>
|
||||
<i class="fa fa-reply mr-1" role="img" />
|
||||
<span t-esc="kit_rma.name" class="mr-lg-3" />
|
||||
<div class="d-lg-inline-block"><span
|
||||
t-field="kit_rma.sudo().product_id.display_name"
|
||||
/> (<t t-field="kit_rma.product_uom_qty" />)</div>
|
||||
</div>
|
||||
<t t-if="kit_rma.state in ['confirmed', 'received', 'returned', 'replaced', 'locked', 'refunded']">
|
||||
<span class="badge badge-success label-text-align"><i class="fa fa-fw fa-reply"/> <t t-field="kit_rma.state" /></span>
|
||||
<t
|
||||
t-if="kit_rma.state in ['confirmed', 'received', 'returned', 'replaced', 'locked', 'refunded']"
|
||||
>
|
||||
<span class="badge badge-success label-text-align"><i
|
||||
class="fa fa-fw fa-reply"
|
||||
/> <t t-field="kit_rma.state" /></span>
|
||||
</t>
|
||||
<t t-if="kit_rma.state in ['waiting_return', 'waiting_replacement']">
|
||||
<span class="badge badge-warning label-text-align"><i class="fa fa-fw fa-clock-o"/> Waiting</span>
|
||||
<t
|
||||
t-if="kit_rma.state in ['waiting_return', 'waiting_replacement']"
|
||||
>
|
||||
<span class="badge badge-warning label-text-align"><i
|
||||
class="fa fa-fw fa-clock-o"
|
||||
/> Waiting</span>
|
||||
</t>
|
||||
<t t-if="kit_rma.state == 'cancelled'">
|
||||
<span class="badge badge-danger label-text-align"><i class="fa fa-fw fa-times"/> Cancelled</span>
|
||||
<span class="badge badge-danger label-text-align"><i
|
||||
class="fa fa-fw fa-times"
|
||||
/> Cancelled</span>
|
||||
</t>
|
||||
<t t-if="kit_rma.state == 'draft'">
|
||||
<span class="badge badge-info label-text-align"><i class="fa fa-fw fa-clock-o"/> Draft</span>
|
||||
<span class="badge badge-info label-text-align"><i
|
||||
class="fa fa-fw fa-clock-o"
|
||||
/> Draft</span>
|
||||
</t>
|
||||
</a>
|
||||
</t>
|
||||
</section>
|
||||
</xpath>
|
||||
</template>
|
||||
<template id="portal_my_rmas" name="My RMA Orders MRP" inherit_id="rma.portal_my_rmas">
|
||||
<template
|
||||
id="portal_my_rmas"
|
||||
name="My RMA Orders MRP"
|
||||
inherit_id="rma.portal_my_rmas"
|
||||
>
|
||||
<xpath expr="//th[@name='th_product']" position="after">
|
||||
<th name="th_kit">Kit</th>
|
||||
</xpath>
|
||||
<xpath expr="//td[@name='td_product']" position="after">
|
||||
<td name="td_kit">
|
||||
<t t-if="rma.sudo().phantom_bom_product">
|
||||
<i class="fa fa-th-large text-info" /> <span t-esc="rma.sudo().phantom_bom_product.display_name"/>
|
||||
<i class="fa fa-th-large text-info" /> <span
|
||||
t-esc="rma.sudo().phantom_bom_product.display_name"
|
||||
/>
|
||||
<span> (<t t-esc="rma.kit_qty" />)</span>
|
||||
</t>
|
||||
</td>
|
||||
|
||||
@@ -20,17 +20,18 @@ class SaleOrderRmaWizard(models.TransientModel):
|
||||
"""Split component lines"""
|
||||
if "line_ids" in vals and vals.get("line_ids"):
|
||||
line_ids = [
|
||||
(x[0], x[1], x[2]) for x in vals.get("line_ids")
|
||||
(x[0], x[1], x[2])
|
||||
for x in vals.get("line_ids")
|
||||
if not x[2].get("phantom_bom_product")
|
||||
]
|
||||
component_line_ids = [
|
||||
(x[0], x[1], x[2]) for x in vals.get("line_ids")
|
||||
(x[0], x[1], x[2])
|
||||
for x in vals.get("line_ids")
|
||||
if x[2].get("phantom_bom_product")
|
||||
]
|
||||
vals.update({
|
||||
"line_ids": line_ids,
|
||||
"component_line_ids": component_line_ids,
|
||||
})
|
||||
vals.update(
|
||||
{"line_ids": line_ids, "component_line_ids": component_line_ids}
|
||||
)
|
||||
return super().create(vals)
|
||||
|
||||
def create_rma(self, from_portal=None):
|
||||
@@ -52,24 +53,28 @@ class SaleOrderRmaWizard(models.TransientModel):
|
||||
lambda x: x.product_id == product and x.quantity
|
||||
)
|
||||
if not product_kit_component_lines:
|
||||
raise ValidationError(_(
|
||||
"The kit corresponding to the product %s can't be "
|
||||
"put in the RMA. Either all or some of the components "
|
||||
"where already put in another RMA"
|
||||
) % line.product_id.name)
|
||||
raise ValidationError(
|
||||
_(
|
||||
"The kit corresponding to the product %s can't be "
|
||||
"put in the RMA. Either all or some of the components "
|
||||
"where already put in another RMA"
|
||||
)
|
||||
% line.product_id.name
|
||||
)
|
||||
qty_to_return = (
|
||||
product_kit_component_lines[0].per_kit_quantity
|
||||
* line.quantity
|
||||
product_kit_component_lines[0].per_kit_quantity * line.quantity
|
||||
)
|
||||
while qty_to_return > 0:
|
||||
for kit_line in product_kit_component_lines:
|
||||
kit_line.quantity = min(qty_to_return, kit_line.quantity)
|
||||
kit_line.operation_id = line.operation_id
|
||||
kit_line.kit_qty_done = (
|
||||
kit_line.quantity / kit_line.per_kit_quantity)
|
||||
kit_line.quantity / kit_line.per_kit_quantity
|
||||
)
|
||||
qty_to_return -= kit_line.quantity
|
||||
kit_line_vals += [
|
||||
(0, 0, x._convert_to_write(x._cache)) for x in kit_component_lines]
|
||||
(0, 0, x._convert_to_write(x._cache)) for x in kit_component_lines
|
||||
]
|
||||
self.update({"line_ids": kit_line_vals})
|
||||
# We don't need the phantom lines anymore as we already have the
|
||||
# kit component ones.
|
||||
@@ -80,16 +85,12 @@ class SaleOrderRmaWizard(models.TransientModel):
|
||||
class SaleOrderLineRmaWizard(models.TransientModel):
|
||||
_inherit = "sale.order.line.rma.wizard"
|
||||
|
||||
phantom_bom_product = fields.Many2one(
|
||||
comodel_name="product.product",
|
||||
)
|
||||
phantom_bom_product = fields.Many2one(comodel_name="product.product",)
|
||||
kit_qty_done = fields.Float(
|
||||
readonly=True,
|
||||
help="Used to inform kit qty used in the rma. Will be useful to refund",
|
||||
)
|
||||
per_kit_quantity = fields.Float(
|
||||
readonly=True,
|
||||
)
|
||||
per_kit_quantity = fields.Float(readonly=True,)
|
||||
phantom_kit_line = fields.Boolean(readonly=True)
|
||||
|
||||
@api.depends("picking_id")
|
||||
@@ -97,32 +98,33 @@ class SaleOrderLineRmaWizard(models.TransientModel):
|
||||
"""We need to process kit components separately so we can match them
|
||||
against their phantom product"""
|
||||
not_kit = self.filtered(
|
||||
lambda x: not x.phantom_bom_product and
|
||||
not x.product_id._is_phantom_bom())
|
||||
lambda x: not x.phantom_bom_product and not x.product_id._is_phantom_bom()
|
||||
)
|
||||
super(SaleOrderLineRmaWizard, not_kit)._compute_move_id()
|
||||
for line in self.filtered(
|
||||
lambda x: x.phantom_bom_product and x.picking_id):
|
||||
for line in self.filtered(lambda x: x.phantom_bom_product and x.picking_id):
|
||||
line.move_id = line.picking_id.move_lines.filtered(
|
||||
lambda ml: (
|
||||
ml.product_id == line.product_id
|
||||
and ml.sale_line_id == line.sale_line_id
|
||||
and ml.sale_line_id.product_id == line.phantom_bom_product
|
||||
and ml.sale_line_id.order_id == line.order_id))
|
||||
and ml.sale_line_id.order_id == line.order_id
|
||||
)
|
||||
)
|
||||
|
||||
def _prepare_rma_values(self):
|
||||
"""It will be used as a reference for the components"""
|
||||
res = super()._prepare_rma_values()
|
||||
if self.phantom_bom_product:
|
||||
unique_register = "{}-{}-{}".format(
|
||||
self.wizard_id.id,
|
||||
self.phantom_bom_product.id,
|
||||
self.sale_line_id.id
|
||||
self.wizard_id.id, self.phantom_bom_product.id, self.sale_line_id.id
|
||||
)
|
||||
res.update(
|
||||
{
|
||||
"phantom_bom_product": self.phantom_bom_product.id,
|
||||
"kit_qty": self.kit_qty_done,
|
||||
"rma_kit_register": unique_register,
|
||||
}
|
||||
)
|
||||
res.update({
|
||||
"phantom_bom_product": self.phantom_bom_product.id,
|
||||
"kit_qty": self.kit_qty_done,
|
||||
"rma_kit_register": unique_register,
|
||||
})
|
||||
return res
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
|
||||
<record id="sale_order_rma_wizard_form_view" model="ir.ui.view">
|
||||
@@ -9,7 +9,9 @@
|
||||
<field name="phantom_kit_line" invisible="1" />
|
||||
</xpath>
|
||||
<xpath expr="//tree/field[@name='picking_id']" position="attributes">
|
||||
<attribute name="attrs">{'readonly': [('phantom_kit_line', '=', True)]}</attribute>
|
||||
<attribute
|
||||
name="attrs"
|
||||
>{'readonly': [('phantom_kit_line', '=', True)]}</attribute>
|
||||
<attribute name="force_save">1</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
|
||||
Reference in New Issue
Block a user