[IMP] rma_sale_mrp: black, isort, prettier

This commit is contained in:
david
2021-04-30 18:29:34 +02:00
committed by Pedro M. Baeza
parent 6f0a240edf
commit 7c8e17779d
9 changed files with 287 additions and 216 deletions

View File

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

View File

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

View File

@@ -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",
}
)

View File

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

View File

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

View File

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

View File

@@ -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=&quot;data[&apos;quantity&apos;] &gt; 0 and data[&apos;picking&apos;]&quot;]/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=&quot;data[&apos;quantity&apos;] &gt; 0 and data[&apos;picking&apos;]&quot;]/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=&quot;data[&apos;quantity&apos;] &gt; 0 and data[&apos;picking&apos;]&quot;]" 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=&quot;data[&apos;quantity&apos;] &gt; 0 and data[&apos;picking&apos;]&quot;]"
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>

View File

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

View File

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