[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)", "author": "Tecnativa, Odoo Community Association (OCA)",
"maintainers": ["chienandalu"], "maintainers": ["chienandalu"],
"license": "AGPL-3", "license": "AGPL-3",
"depends": [ "depends": ["rma_sale", "mrp"],
"rma_sale",
"mrp",
],
"data": [ "data": [
"views/sale_order_portal_template.xml", "views/sale_order_portal_template.xml",
"views/rma_views.xml", "views/rma_views.xml",

View File

@@ -10,10 +10,14 @@ class AccountInvoice(models.Model):
def _check_rma_invoice_lines_qty(self): def _check_rma_invoice_lines_qty(self):
"""For those with differences, check if the kit quantity is the same""" """For those with differences, check if the kit quantity is the same"""
precision = self.env["decimal.precision"].precision_get( precision = self.env["decimal.precision"].precision_get(
"Product Unit of Measure") "Product Unit of Measure"
)
lines = super()._check_rma_invoice_lines_qty() lines = super()._check_rma_invoice_lines_qty()
if lines: if lines:
return lines.sudo().filtered( return lines.sudo().filtered(
lambda r: (r.rma_id.phantom_bom_product and float_compare( lambda r: (
r.quantity, r.rma_id.kit_qty, precision) < 0)) r.rma_id.phantom_bom_product
and float_compare(r.quantity, r.rma_id.kit_qty, precision) < 0
)
)
return lines return lines

View File

@@ -2,6 +2,7 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import _, fields, models from odoo import _, fields, models
from odoo.exceptions import UserError from odoo.exceptions import UserError
import odoo.addons.decimal_precision as dp import odoo.addons.decimal_precision as dp
@@ -9,57 +10,57 @@ class Rma(models.Model):
_inherit = "rma" _inherit = "rma"
phantom_bom_product = fields.Many2one( phantom_bom_product = fields.Many2one(
comodel_name="product.product", comodel_name="product.product", string="Related kit product", readonly=True,
string="Related kit product",
readonly=True,
) )
kit_qty = fields.Float( kit_qty = fields.Float(
string="Kit quantity", string="Kit quantity",
digits=dp.get_precision("Product Unit of Measure"), digits=dp.get_precision("Product Unit of Measure"),
readonly=True, readonly=True,
help="To how many kits this components corresponds to. Used mainly " 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) rma_kit_register = fields.Char(readonly=True)
def _get_refund_line_quantity(self): def _get_refund_line_quantity(self):
"""Refund the kit, not the component""" """Refund the kit, not the component"""
if self.phantom_bom_product: if self.phantom_bom_product:
uom = ( uom = self.sale_line_id.product_uom or self.phantom_bom_product.uom_id
self.sale_line_id.product_uom
or self.phantom_bom_product.uom_id
)
return (self.kit_qty, uom) return (self.kit_qty, uom)
return (self.product_uom_qty, self.product_uom) return (self.product_uom_qty, self.product_uom)
def action_refund(self): def action_refund(self):
"""We want to process them altogether""" """We want to process them altogether"""
phantom_rmas = self.filtered("phantom_bom_product") phantom_rmas = self.filtered("phantom_bom_product")
phantom_rmas |= self.search([ phantom_rmas |= self.search(
("rma_kit_register", "in", phantom_rmas.mapped("rma_kit_register")), [
("id", "not in", phantom_rmas.ids), ("rma_kit_register", "in", phantom_rmas.mapped("rma_kit_register")),
]) ("id", "not in", phantom_rmas.ids),
]
)
self -= phantom_rmas self -= phantom_rmas
for rma_kit_register in phantom_rmas.mapped( for rma_kit_register in phantom_rmas.mapped("rma_kit_register"):
"rma_kit_register"):
# We want to avoid refunding kits that aren't completely processed # We want to avoid refunding kits that aren't completely processed
rmas_by_register = phantom_rmas.filtered( 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")): if any(rmas_by_register.filtered(lambda x: x.state != "received")):
raise UserError(_( raise UserError(
"You can't refund a kit in wich some RMAs aren't received" _("You can't refund a kit in wich some RMAs aren't received")
)) )
self |= rmas_by_register[0] self |= rmas_by_register[0]
super().action_refund() super().action_refund()
# We can just link the line to an RMA but we can link several RMAs # We can just link the line to an RMA but we can link several RMAs
# to one invoice line. # to one invoice line.
for rma_kit_register in set(phantom_rmas.mapped("rma_kit_register")): for rma_kit_register in set(phantom_rmas.mapped("rma_kit_register")):
grouped_rmas = phantom_rmas.filtered( 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") lead_rma = grouped_rmas.filtered("refund_line_id")
grouped_rmas -= lead_rma grouped_rmas -= lead_rma
grouped_rmas.write({ grouped_rmas.write(
"refund_line_id": lead_rma.refund_line_id.id, {
"refund_id": lead_rma.refund_id.id, "refund_line_id": lead_rma.refund_line_id.id,
"state": "refunded", "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): def get_delivery_rma_data(self):
"""Get the phantom lines we'll be showing in the wizard""" """Get the phantom lines we'll be showing in the wizard"""
data_list = super().get_delivery_rma_data() data_list = super().get_delivery_rma_data()
kit_products = set( kit_products = {
[ (x.get("phantom_bom_product"), x.get("sale_line_id"))
( for x in data_list
x.get("phantom_bom_product"), if 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 # For every unique phantom product we'll create a phantom line wich
# will be using as the control in frontend and for display purposes # will be using as the control in frontend and for display purposes
# in backend # in backend
@@ -34,35 +30,40 @@ class SaleOrder(models.Model):
order_line_obj = self.env["sale.order.line"] order_line_obj = self.env["sale.order.line"]
product_obj = self.env["product.product"] product_obj = self.env["product.product"]
first_component_dict = next( first_component_dict = next(
x for x in data_list if x.get( x
"phantom_bom_product", product_obj for x in data_list
) == product and x.get( if x.get("phantom_bom_product", product_obj) == product
"sale_line_id", order_line_obj and x.get("sale_line_id", order_line_obj) == sale_line_id
) == sale_line_id) )
component_index = data_list.index(first_component_dict) component_index = data_list.index(first_component_dict)
# Prevent miscalculation if there partial deliveries # Prevent miscalculation if there partial deliveries
quantity = sum([ quantity = sum(
x.get("quantity", 0) for x in data_list [
if x.get("sale_line_id") x.get("quantity", 0)
and x.get("product") == first_component_dict.get("product") for x in data_list
and x.get("sale_line_id") == first_component_dict.get( if x.get("sale_line_id")
"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( data_list.insert(
component_index, { component_index,
{
"product": product, "product": product,
"quantity": ( "quantity": (
first_component_dict.get("per_kit_quantity") and ( first_component_dict.get("per_kit_quantity")
quantity and (quantity / first_component_dict.get("per_kit_quantity"))
/ first_component_dict.get("per_kit_quantity")
)
), ),
"uom": first_component_dict.get( "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, "phantom_kit_line": True,
"picking": False, "picking": False,
"sale_line_id": first_component_dict.get( "sale_line_id": first_component_dict.get(
"sale_line_id", order_line_obj), "sale_line_id", order_line_obj
} ),
},
) )
return data_list return data_list
@@ -74,12 +75,17 @@ class SaleOrderLine(models.Model):
self.ensure_one() self.ensure_one()
if self.product_id and not self.product_id._is_phantom_bom(): if self.product_id and not self.product_id._is_phantom_bom():
return super().get_delivery_move() return super().get_delivery_move()
return self.move_ids.filtered(lambda m: ( return self.move_ids.filtered(
m.state == "done" and not m.scrapped lambda m: (
and m.location_dest_id.usage == "customer" m.state == "done"
and ( and not m.scrapped
not m.origin_returned_move_id and m.location_dest_id.usage == "customer"
or (m.origin_returned_move_id and m.to_refund)))) and (
not m.origin_returned_move_id
or (m.origin_returned_move_id and m.to_refund)
)
)
)
def prepare_sale_rma_data(self): def prepare_sale_rma_data(self):
"""We'll take both the sale order product and the phantom one so we """We'll take both the sale order product and the phantom one so we
@@ -91,7 +97,7 @@ class SaleOrderLine(models.Model):
d.update( d.update(
{ {
"phantom_bom_product": self.product_id, "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 return data
@@ -105,6 +111,7 @@ class SaleOrderLine(models.Model):
return 0 return 0
component_demand = sum( component_demand = sum(
self.move_ids.filtered( self.move_ids.filtered(
lambda x: x.product_id == product_id lambda x: x.product_id == product_id and not x.origin_returned_move_id
and not x.origin_returned_move_id).mapped("product_uom_qty")) ).mapped("product_uom_qty")
)
return component_demand / self.product_uom_qty return component_demand / self.product_uom_qty

View File

@@ -1,7 +1,7 @@
# Copyright 2020 Tecnativa - David Vidal # Copyright 2020 Tecnativa - David Vidal
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). # 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.exceptions import UserError, ValidationError
from odoo.tests import Form, SavepointCase
class TestRmaSaleMrp(SavepointCase): class TestRmaSaleMrp(SavepointCase):
@@ -11,39 +11,38 @@ class TestRmaSaleMrp(SavepointCase):
cls.res_partner = cls.env["res.partner"] cls.res_partner = cls.env["res.partner"]
cls.product_product = cls.env["product.product"] cls.product_product = cls.env["product.product"]
cls.sale_order = cls.env["sale.order"] cls.sale_order = cls.env["sale.order"]
cls.product_kit = cls.product_product.create({ cls.product_kit = cls.product_product.create(
"name": "Product test 1", {"name": "Product test 1", "type": "consu"}
"type": "consu", )
}) cls.product_kit_comp_1 = cls.product_product.create(
cls.product_kit_comp_1 = cls.product_product.create({ {"name": "Product Component 1", "type": "product"}
"name": "Product Component 1", )
"type": "product", cls.product_kit_comp_2 = cls.product_product.create(
}) {"name": "Product Component 2", "type": "product"}
cls.product_kit_comp_2 = cls.product_product.create({ )
"name": "Product Component 2", cls.bom = cls.env["mrp.bom"].create(
"type": "product", {
}) "product_id": cls.product_kit.id,
cls.bom = cls.env["mrp.bom"].create({ "product_tmpl_id": cls.product_kit.product_tmpl_id.id,
"product_id": cls.product_kit.id, "type": "phantom",
"product_tmpl_id": cls.product_kit.product_tmpl_id.id, "bom_line_ids": [
"type": "phantom", (
"bom_line_ids": [ 0,
(0, 0, { 0,
"product_id": cls.product_kit_comp_1.id, {"product_id": cls.product_kit_comp_1.id, "product_qty": 2},
"product_qty": 2, ),
}), (
(0, 0, { 0,
"product_id": cls.product_kit_comp_2.id, 0,
"product_qty": 4, {"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.product_2 = cls.product_product.create(
}) {"name": "Product test 2", "type": "product"}
cls.partner = cls.res_partner.create({ )
"name": "Partner test", cls.partner = cls.res_partner.create({"name": "Partner test"})
})
order_form = Form(cls.sale_order) order_form = Form(cls.sale_order)
order_form.partner_id = cls.partner order_form.partner_id = cls.partner
with order_form.order_line.new() as line_form: 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 # Maybe other modules create additional lines in the create
# method in sale.order model, so let's find the correct line. # method in sale.order model, so let's find the correct line.
cls.order_line = cls.sale_order.order_line.filtered( 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 cls.order_out_picking = cls.sale_order.picking_ids
# Confirm but leave a backorder to split moves so we can test that # Confirm but leave a backorder to split moves so we can test that
# the wizard correctly creates the RMAs with the proper quantities # 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.assertTrue(rma.picking_id in out_pickings)
self.assertEqual(rmas.mapped("phantom_bom_product"), self.product_kit) self.assertEqual(rmas.mapped("phantom_bom_product"), self.product_kit)
self.assertEqual( self.assertEqual(
rmas.mapped("product_id"), rmas.mapped("product_id"), self.product_kit_comp_1 + self.product_kit_comp_2
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_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) 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( move_2 = out_pickings.mapped("move_lines").filtered(
lambda x: x.product_id == self.product_kit_comp_2 lambda x: x.product_id == self.product_kit_comp_2
) )
self.assertEqual( self.assertEqual(sum(rma_1.mapped("product_uom_qty")), 8)
sum(rma_1.mapped("product_uom_qty")), self.assertEqual(rma_1.mapped("product_uom"), move_1.mapped("product_uom"))
8 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_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.state, "confirmed")
self.assertEqual( self.assertEqual(
rma_1.mapped("reception_move_id.origin_returned_move_id"), rma_1.mapped("reception_move_id.origin_returned_move_id"), move_1,
move_1,
) )
self.assertEqual( self.assertEqual(
rma_2.mapped("reception_move_id.origin_returned_move_id"), rma_2.mapped("reception_move_id.origin_returned_move_id"), move_2,
move_2,
) )
self.assertEqual( self.assertEqual(
rmas.mapped("reception_move_id.picking_id") rmas.mapped("reception_move_id.picking_id")
+ self.order_out_picking + self.backorder, + self.order_out_picking
+ self.backorder,
order.picking_ids, order.picking_ids,
) )
# Refund the RMA # Refund the RMA
user = self.env["res.users"].create( 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 order.user_id = user.id
rma.reception_move_id.quantity_done = rma.product_uom_qty rma.reception_move_id.quantity_done = rma.product_uom_qty
@@ -139,15 +122,15 @@ class TestRmaSaleMrp(SavepointCase):
rmas_left = rmas - rma rmas_left = rmas - rma
for additional_rma in rmas_left: for additional_rma in rmas_left:
additional_rma.reception_move_id.quantity_done = ( 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() additional_rma.reception_move_id.picking_id.action_done()
rma.action_refund() rma.action_refund()
self.assertEqual(rma.refund_id.user_id, user) self.assertEqual(rma.refund_id.user_id, user)
# The component RMAs get automatically refunded # The component RMAs get automatically refunded
self.assertEqual(rma.refund_id, rmas_left.mapped("refund_id")) self.assertEqual(rma.refund_id, rmas_left.mapped("refund_id"))
# The refund product is the kit, not the components # The refund product is the kit, not the components
self.assertEqual( self.assertEqual(rma.refund_id.invoice_line_ids.product_id, self.product_kit)
rma.refund_id.invoice_line_ids.product_id, self.product_kit)
rma.refund_id.action_invoice_open() rma.refund_id.action_invoice_open()
# We can still return another kit # We can still return another kit
wizard_id = order.action_create_rma()["res_id"] 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>
<odoo> <odoo>
<template id="report_rma_document" inherit_id="rma_sale.report_rma_document"> <template id="report_rma_document" inherit_id="rma_sale.report_rma_document">
@@ -6,17 +6,29 @@
<t t-if="doc.sudo().phantom_bom_product"> <t t-if="doc.sudo().phantom_bom_product">
<strong class="d-block mt32 mb-1">Kit information</strong> <strong class="d-block mt32 mb-1">Kit information</strong>
<div class="row mb32" id="kit_information"> <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> <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>
<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> <strong>Kit Quantity:</strong>
<p class="m-0" t-field="doc.kit_qty"/> <p class="m-0" t-field="doc.kit_qty" />
</div> </div>
</div> </div>
<strong class="d-block mb-1">Related Kit Components RMAs</strong> <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"> <table class="table table-sm">
<thead> <thead>
<th> <th>
@@ -35,19 +47,25 @@
<tbody> <tbody>
<tr t-foreach="related_kit_rmas" t-as="kit_rma"> <tr t-foreach="related_kit_rmas" t-as="kit_rma">
<td> <td>
<span t-field="kit_rma.name"/> <span t-field="kit_rma.name" />
</td> </td>
<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>
<td> <td>
<t t-if="kit_rma.product_id"> <t t-if="kit_rma.product_id">
<span t-field="kit_rma.product_uom_qty"/> <span t-field="kit_rma.product_uom_qty" />
<span t-field="kit_rma.uom_id" groups="uom.group_uom"/> <span
t-field="kit_rma.uom_id"
groups="uom.group_uom"
/>
</t> </t>
</td> </td>
<td> <td>
<span t-field="kit_rma.state"/> <span t-field="kit_rma.state" />
</td> </td>
</tr> </tr>
</tbody> </tbody>

View File

@@ -1,28 +1,51 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <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"> <xpath expr="//input[@t-attf-name='#{data_index}-product_id']" position="after">
<input type="hidden" <input
t-if="data.get('phantom_bom_product')" type="hidden"
t-attf-name="#{data_index}-phantom_bom_product" t-if="data.get('phantom_bom_product')"
t-att-value="data['phantom_bom_product'].id"/> t-attf-name="#{data_index}-phantom_bom_product"
<input type="hidden" t-att-value="data['phantom_bom_product'].id"
t-if="data.get('per_kit_quantity')" />
t-attf-name="#{data_index}-per_kit_quantity" <input
t-att-value="data['per_kit_quantity']"/> type="hidden"
<input type="hidden" t-if="data.get('per_kit_quantity')"
t-if="data.get('phantom_kit_line')" t-attf-name="#{data_index}-per_kit_quantity"
t-attf-name="#{data_index}-phantom_kit_line" t-att-value="data['per_kit_quantity']"
t-att-value="1"/> />
<input
type="hidden"
t-if="data.get('phantom_kit_line')"
t-attf-name="#{data_index}-phantom_kit_line"
t-att-value="1"
/>
</xpath> </xpath>
<!-- TODO: We could give a clue about what's to be returned, with readonly detailed lines --> <!-- 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"> <xpath
<attribute name="t-attf-class">#{data.get('phantom_bom_product') and 'd-none'}</attribute> 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>
<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"> <xpath
<attribute name="t-if">data['quantity'] > 0 and (data['picking'] or data.get('phantom_kit_line'))</attribute> 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>
<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> <attribute name="t-if">not data.get('phantom_bom_product')</attribute>
</xpath> </xpath>
</template> </template>
@@ -34,55 +57,89 @@
<strong><i class="fa fa-th-large text-info" /> Kit</strong> <strong><i class="fa fa-th-large text-info" /> Kit</strong>
</div> </div>
<div class="col-12 col-sm-8"> <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> </div>
<div class="row mb-2 mb-sm-1"> <div class="row mb-2 mb-sm-1">
<div class="col-12 col-sm-4"> <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>
<div class="col-12 col-sm-8"> <div class="col-12 col-sm-8">
<span t-esc="rma.kit_qty"/> <span t-esc="rma.kit_qty" />
</div> </div>
</div> </div>
</t> </t>
</xpath> </xpath>
<xpath expr="//section[@id='reception_section']" position="before"> <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> <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"> <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> <div>
<i class="fa fa-reply mr-1" role="img"/> <i class="fa fa-reply mr-1" role="img" />
<span t-esc="kit_rma.name" class="mr-lg-3"/> <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 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> </div>
<t t-if="kit_rma.state in ['confirmed', 'received', 'returned', 'replaced', 'locked', 'refunded']"> <t
<span class="badge badge-success label-text-align"><i class="fa fa-fw fa-reply"/> <t t-field="kit_rma.state" /></span> 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 t-if="kit_rma.state in ['waiting_return', 'waiting_replacement']"> <t
<span class="badge badge-warning label-text-align"><i class="fa fa-fw fa-clock-o"/> Waiting</span> 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 t-if="kit_rma.state == 'cancelled'"> <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 t-if="kit_rma.state == 'draft'"> <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> </t>
</a> </a>
</t> </t>
</section> </section>
</xpath> </xpath>
</template> </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"> <xpath expr="//th[@name='th_product']" position="after">
<th name="th_kit">Kit</th> <th name="th_kit">Kit</th>
</xpath> </xpath>
<xpath expr="//td[@name='td_product']" position="after"> <xpath expr="//td[@name='td_product']" position="after">
<td name="td_kit"> <td name="td_kit">
<t t-if="rma.sudo().phantom_bom_product"> <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> <span> (<t t-esc="rma.kit_qty" />)</span>
</t> </t>
</td> </td>

View File

@@ -20,17 +20,18 @@ class SaleOrderRmaWizard(models.TransientModel):
"""Split component lines""" """Split component lines"""
if "line_ids" in vals and vals.get("line_ids"): if "line_ids" in vals and vals.get("line_ids"):
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") if not x[2].get("phantom_bom_product")
] ]
component_line_ids = [ 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") if x[2].get("phantom_bom_product")
] ]
vals.update({ vals.update(
"line_ids": line_ids, {"line_ids": line_ids, "component_line_ids": component_line_ids}
"component_line_ids": component_line_ids, )
})
return super().create(vals) return super().create(vals)
def create_rma(self, from_portal=None): def create_rma(self, from_portal=None):
@@ -52,24 +53,28 @@ class SaleOrderRmaWizard(models.TransientModel):
lambda x: x.product_id == product and x.quantity lambda x: x.product_id == product and x.quantity
) )
if not product_kit_component_lines: if not product_kit_component_lines:
raise ValidationError(_( raise ValidationError(
"The kit corresponding to the product %s can't be " _(
"put in the RMA. Either all or some of the components " "The kit corresponding to the product %s can't be "
"where already put in another RMA" "put in the RMA. Either all or some of the components "
) % line.product_id.name) "where already put in another RMA"
)
% line.product_id.name
)
qty_to_return = ( qty_to_return = (
product_kit_component_lines[0].per_kit_quantity product_kit_component_lines[0].per_kit_quantity * line.quantity
* line.quantity
) )
while qty_to_return > 0: while qty_to_return > 0:
for kit_line in product_kit_component_lines: for kit_line in product_kit_component_lines:
kit_line.quantity = min(qty_to_return, kit_line.quantity) kit_line.quantity = min(qty_to_return, kit_line.quantity)
kit_line.operation_id = line.operation_id kit_line.operation_id = line.operation_id
kit_line.kit_qty_done = ( 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 qty_to_return -= kit_line.quantity
kit_line_vals += [ 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}) self.update({"line_ids": kit_line_vals})
# We don't need the phantom lines anymore as we already have the # We don't need the phantom lines anymore as we already have the
# kit component ones. # kit component ones.
@@ -80,16 +85,12 @@ class SaleOrderRmaWizard(models.TransientModel):
class SaleOrderLineRmaWizard(models.TransientModel): class SaleOrderLineRmaWizard(models.TransientModel):
_inherit = "sale.order.line.rma.wizard" _inherit = "sale.order.line.rma.wizard"
phantom_bom_product = fields.Many2one( phantom_bom_product = fields.Many2one(comodel_name="product.product",)
comodel_name="product.product",
)
kit_qty_done = fields.Float( kit_qty_done = fields.Float(
readonly=True, readonly=True,
help="Used to inform kit qty used in the rma. Will be useful to refund", help="Used to inform kit qty used in the rma. Will be useful to refund",
) )
per_kit_quantity = fields.Float( per_kit_quantity = fields.Float(readonly=True,)
readonly=True,
)
phantom_kit_line = fields.Boolean(readonly=True) phantom_kit_line = fields.Boolean(readonly=True)
@api.depends("picking_id") @api.depends("picking_id")
@@ -97,32 +98,33 @@ class SaleOrderLineRmaWizard(models.TransientModel):
"""We need to process kit components separately so we can match them """We need to process kit components separately so we can match them
against their phantom product""" against their phantom product"""
not_kit = self.filtered( not_kit = self.filtered(
lambda x: not x.phantom_bom_product and lambda x: not x.phantom_bom_product and not x.product_id._is_phantom_bom()
not x.product_id._is_phantom_bom()) )
super(SaleOrderLineRmaWizard, not_kit)._compute_move_id() super(SaleOrderLineRmaWizard, not_kit)._compute_move_id()
for line in self.filtered( for line in self.filtered(lambda x: x.phantom_bom_product and x.picking_id):
lambda x: x.phantom_bom_product and x.picking_id):
line.move_id = line.picking_id.move_lines.filtered( line.move_id = line.picking_id.move_lines.filtered(
lambda ml: ( lambda ml: (
ml.product_id == line.product_id ml.product_id == line.product_id
and ml.sale_line_id == line.sale_line_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.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): def _prepare_rma_values(self):
"""It will be used as a reference for the components""" """It will be used as a reference for the components"""
res = super()._prepare_rma_values() res = super()._prepare_rma_values()
if self.phantom_bom_product: if self.phantom_bom_product:
unique_register = "{}-{}-{}".format( unique_register = "{}-{}-{}".format(
self.wizard_id.id, self.wizard_id.id, self.phantom_bom_product.id, self.sale_line_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 return res

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record id="sale_order_rma_wizard_form_view" model="ir.ui.view"> <record id="sale_order_rma_wizard_form_view" model="ir.ui.view">
@@ -9,7 +9,9 @@
<field name="phantom_kit_line" invisible="1" /> <field name="phantom_kit_line" invisible="1" />
</xpath> </xpath>
<xpath expr="//tree/field[@name='picking_id']" position="attributes"> <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> <attribute name="force_save">1</attribute>
</xpath> </xpath>
</field> </field>