mirror of
https://github.com/ForgeFlow/stock-rma.git
synced 2025-01-21 12:57:49 +02:00
Merge pull request #525 from akretion/16-rma-phantom-bom-components-refactore
[16][rma_sale][rma_sale_mrp] Create rma line from stock move line instead of sale order line by default and when possible + manage kit components
This commit is contained in:
@@ -65,7 +65,10 @@ class TestRmaSale(common.SingleTransactionCase):
|
||||
"pricelist_id": cls.env.ref("product.list0").id,
|
||||
}
|
||||
)
|
||||
|
||||
cls.so.action_confirm()
|
||||
for move in cls.so.picking_ids.move_ids:
|
||||
move.write({"quantity_done": move.product_uom_qty})
|
||||
cls.so.picking_ids._action_done()
|
||||
# Create RMA group and operation:
|
||||
cls.rma_group = cls.rma_obj.create({"partner_id": customer1.id})
|
||||
cls.operation_1 = cls.rma_op_obj.create(
|
||||
|
||||
@@ -82,7 +82,9 @@ class RmaAddSale(models.TransientModel):
|
||||
"target": "new",
|
||||
}
|
||||
|
||||
def _prepare_rma_line_from_sale_order_line(self, line, lot=None):
|
||||
def _prepare_rma_line_from_sale_order_line(
|
||||
self, line, product, quantity, uom_id=False, lot=None
|
||||
):
|
||||
operation = self.rma_id.operation_default_id
|
||||
if not operation:
|
||||
operation = line.product_id.rma_customer_operation_id
|
||||
@@ -121,33 +123,26 @@ class RmaAddSale(models.TransientModel):
|
||||
or operation.in_warehouse_id.lot_rma_id
|
||||
or warehouse.lot_rma_id
|
||||
)
|
||||
product_qty = line.product_uom_qty
|
||||
if line.product_id.tracking == "serial":
|
||||
product_qty = 1
|
||||
elif line.product_id.tracking == "lot":
|
||||
product_qty = sum(
|
||||
line.mapped("move_ids.move_line_ids")
|
||||
.filtered(lambda x: x.lot_id.id == lot.id)
|
||||
.mapped("qty_done")
|
||||
)
|
||||
data = {
|
||||
"partner_id": self.partner_id.id,
|
||||
"description": self.rma_id.description,
|
||||
"sale_line_id": line.id,
|
||||
"product_id": line.product_id.id,
|
||||
"product_id": product.id,
|
||||
"lot_id": lot and lot.id or False,
|
||||
"origin": line.order_id.name,
|
||||
"uom_id": line.product_uom.id,
|
||||
"uom_id": uom_id or product.uom_id.id,
|
||||
"operation_id": operation.id,
|
||||
"product_qty": product_qty,
|
||||
"product_qty": quantity,
|
||||
"delivery_address_id": self.sale_id.partner_shipping_id.id,
|
||||
"invoice_address_id": self.sale_id.partner_invoice_id.id,
|
||||
"price_unit": line.currency_id._convert(
|
||||
"price_unit": line.product_id == product
|
||||
and line.currency_id._convert(
|
||||
line.price_unit,
|
||||
line.currency_id,
|
||||
line.company_id,
|
||||
line.order_id.date_order,
|
||||
),
|
||||
)
|
||||
or product.lst_price,
|
||||
"rma_id": self.rma_id.id,
|
||||
"in_route_id": operation.in_route_id.id or route.id,
|
||||
"out_route_id": operation.out_route_id.id or route.id,
|
||||
@@ -172,35 +167,84 @@ class RmaAddSale(models.TransientModel):
|
||||
existing_sale_lines.append(rma_line.sale_line_id)
|
||||
return existing_sale_lines
|
||||
|
||||
def _should_create_rma_line(self, line, existing_sale_line, lot=False):
|
||||
if not lot and line in existing_sale_line:
|
||||
return False
|
||||
if lot and (
|
||||
lot.id not in self.lot_ids.ids
|
||||
or lot.id in self.rma_id.rma_line_ids.mapped("lot_id").ids
|
||||
):
|
||||
return False
|
||||
return True
|
||||
|
||||
def _create_from_move_line(self, line):
|
||||
return True
|
||||
|
||||
def _get_lot_quantity_from_move_lines(self, sale_line):
|
||||
outgoing_lines = self.env["stock.move.line"]
|
||||
incoming_lines = self.env["stock.move.line"]
|
||||
sent_moves = sale_line.move_ids.filtered(
|
||||
lambda m: m.state == "done" and not m.scrapped
|
||||
)
|
||||
for move in sent_moves:
|
||||
if move.location_dest_id.usage == "customer" and (
|
||||
not move.origin_returned_move_id
|
||||
or (move.origin_returned_move_id and move.to_refund)
|
||||
):
|
||||
outgoing_lines |= move.move_line_ids
|
||||
elif move.location_dest_id.usage != "customer" and move.to_refund:
|
||||
incoming_lines |= move.move_line_ids
|
||||
sent_product_data = {}
|
||||
for line in outgoing_lines:
|
||||
key = (line.product_id, line.product_uom_id, line.lot_id)
|
||||
if key not in sent_product_data:
|
||||
sent_product_data[key] = 0.0
|
||||
sent_product_data[key] += line.qty_done
|
||||
for line in incoming_lines:
|
||||
key = (line.product_id, line.product_uom_id, line.lot_id)
|
||||
if key not in sent_product_data:
|
||||
sent_product_data[key] = 0.0
|
||||
sent_product_data[key] -= line.qty_done
|
||||
return sent_product_data
|
||||
|
||||
def add_lines(self):
|
||||
rma_line_obj = self.env["rma.order.line"]
|
||||
existing_sale_lines = self._get_existing_sale_lines()
|
||||
existing_sale_line = self._get_existing_sale_lines()
|
||||
for line in self.sale_line_ids:
|
||||
tracking_move = line.product_id.tracking in ("serial", "lot")
|
||||
# Load a PO line only once
|
||||
if line not in existing_sale_lines or tracking_move:
|
||||
if not tracking_move:
|
||||
data = self._prepare_rma_line_from_sale_order_line(line)
|
||||
if self._create_from_move_line(line):
|
||||
sent_produt_data = self._get_lot_quantity_from_move_lines(line)
|
||||
for (product, uom, lot), qty in sent_produt_data.items():
|
||||
if not self._should_create_rma_line(
|
||||
line, existing_sale_line, lot=lot
|
||||
):
|
||||
continue
|
||||
data = self._prepare_rma_line_from_sale_order_line(
|
||||
line, product, qty, uom_id=uom.id, lot=lot
|
||||
)
|
||||
rec = rma_line_obj.create(data)
|
||||
# Ensure that configuration on the operation is applied
|
||||
# TODO MIG: in v16 the usage of such onchange can be removed in
|
||||
# favor of (pre)computed stored editable fields for all policies
|
||||
# and configuration in the RMA operation.
|
||||
rec._onchange_operation_id()
|
||||
else:
|
||||
for lot in line.mapped("move_ids.move_line_ids.lot_id").filtered(
|
||||
lambda x: x.id in self.lot_ids.ids
|
||||
):
|
||||
if lot.id in self.rma_id.rma_line_ids.mapped("lot_id").ids:
|
||||
continue
|
||||
data = self._prepare_rma_line_from_sale_order_line(line, lot)
|
||||
rec = rma_line_obj.create(data)
|
||||
# Ensure that configuration on the operation is applied
|
||||
# TODO MIG: in v16 the usage of such onchange can be removed in
|
||||
# favor of (pre)computed stored editable fields for all policies
|
||||
# and configuration in the RMA operation.
|
||||
rec._onchange_operation_id()
|
||||
rec.price_unit = rec._get_price_unit()
|
||||
else:
|
||||
if not self._should_create_rma_line(line, existing_sale_line):
|
||||
continue
|
||||
# we can't have lot management based on sale order line
|
||||
data = self._prepare_rma_line_from_sale_order_line(
|
||||
line,
|
||||
line.product_id,
|
||||
line.product_uom_qty,
|
||||
uom_id=line.product_uom.id,
|
||||
lot=False,
|
||||
)
|
||||
rec = rma_line_obj.create(data)
|
||||
# Ensure that configuration on the operation is applied
|
||||
# TODO MIG: in v16 the usage of such onchange can be removed in
|
||||
# favor of (pre)computed stored editable fields for all policies
|
||||
# and configuration in the RMA operation.
|
||||
rec._onchange_operation_id()
|
||||
rec.price_unit = rec._get_price_unit()
|
||||
rma = self.rma_id
|
||||
data_rma = self._get_rma_data()
|
||||
rma.write(data_rma)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Copyright 2023 ForgeFlow S.L.
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
|
||||
from . import models
|
||||
from . import wizards
|
||||
|
||||
@@ -10,6 +10,6 @@
|
||||
"author": "ForgeFlow",
|
||||
"website": "https://github.com/ForgeFlow/stock-rma",
|
||||
"depends": ["rma_sale", "sale_mrp"],
|
||||
"data": [],
|
||||
"data": ["views/res_config_settings_views.xml"],
|
||||
"installable": True,
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Copyright 2023 ForgeFlow S.L.
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
|
||||
from . import rma_order_line
|
||||
from . import res_company
|
||||
from . import res_config_settings
|
||||
|
||||
9
rma_sale_mrp/models/res_company.py
Normal file
9
rma_sale_mrp/models/res_company.py
Normal file
@@ -0,0 +1,9 @@
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ResCompany(models.Model):
|
||||
_inherit = "res.company"
|
||||
|
||||
rma_add_component_from_sale = fields.Boolean()
|
||||
14
rma_sale_mrp/models/res_config_settings.py
Normal file
14
rma_sale_mrp/models/res_config_settings.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = "res.config.settings"
|
||||
|
||||
rma_add_component_from_sale = fields.Boolean(
|
||||
related="company_id.rma_add_component_from_sale",
|
||||
readonly=False,
|
||||
help="If active, when creating a rma from a sale order, in case the product "
|
||||
"is a kit, the delivered components will be added instead of the kit.",
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
Add an parameter at company level to choose if rma lines are created for a kit or for the components, in the case the rma lines are created from a sale order.
|
||||
|
||||
@@ -252,3 +252,38 @@ class TestRmaMrp(TransactionCase):
|
||||
self.assertEqual(
|
||||
150.0, sum(component_2_sm.mapped("stock_valuation_layer_ids.value"))
|
||||
)
|
||||
|
||||
def test_02_add_kit_from_sale(self):
|
||||
order_01 = self._make_sale_order(self.kit_product, 2, 30.0)
|
||||
self._do_picking(order_01.picking_ids, 2.0)
|
||||
rma = self.env["rma.order"].create({"partner_id": self.customer.id})
|
||||
add_sale = (
|
||||
self.env["rma_add_sale"]
|
||||
.with_context(active_model="rma.order", active_ids=rma.ids)
|
||||
.create(
|
||||
{
|
||||
"sale_id": order_01.id,
|
||||
"sale_line_ids": [(6, 0, order_01.order_line.ids)],
|
||||
}
|
||||
)
|
||||
)
|
||||
add_sale.add_lines()
|
||||
# component config is not set, we should create a rma line for the kit.
|
||||
self.assertEqual(len(rma.rma_line_ids), 1)
|
||||
self.assertEqual(rma.rma_line_ids.product_id, self.kit_product)
|
||||
self.assertEqual(rma.rma_line_ids.product_qty, 2.0)
|
||||
|
||||
# test with component config now
|
||||
rma.rma_line_ids.unlink()
|
||||
order_01.company_id.write({"rma_add_component_from_sale": True})
|
||||
add_sale.add_lines()
|
||||
self.assertEqual(len(rma.rma_line_ids), 2)
|
||||
line_component_1 = rma.rma_line_ids.filtered(
|
||||
lambda line: line.product_id == self.component_product_1
|
||||
)
|
||||
line_component_2 = rma.rma_line_ids.filtered(
|
||||
lambda line: line.product_id == self.component_product_2
|
||||
)
|
||||
self.assertTrue(line_component_1)
|
||||
self.assertEqual(line_component_1.product_qty, 2.0)
|
||||
self.assertTrue(line_component_2)
|
||||
|
||||
25
rma_sale_mrp/views/res_config_settings_views.xml
Normal file
25
rma_sale_mrp/views/res_config_settings_views.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
|
||||
<record id="res_config_settings_view_form" model="ir.ui.view">
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="priority" eval="10" />
|
||||
<field name="inherit_id" ref="rma.res_config_settings_view_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='rma_account']" position="after">
|
||||
<div name="kit_component" class="col-12 col-lg-6 o_setting_box">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="rma_add_component_from_sale" />
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="rma_add_component_from_sale" />
|
||||
<div class="text-muted">
|
||||
When adding rma lines from sale : add the components in case of a kit.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
1
rma_sale_mrp/wizards/__init__.py
Normal file
1
rma_sale_mrp/wizards/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import rma_add_sale
|
||||
16
rma_sale_mrp/wizards/rma_add_sale.py
Normal file
16
rma_sale_mrp/wizards/rma_add_sale.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# Copyright 2020 ForgeFlow S.L.
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html)
|
||||
|
||||
from odoo import models
|
||||
|
||||
|
||||
class RmaAddSale(models.TransientModel):
|
||||
_inherit = "rma_add_sale"
|
||||
|
||||
def _create_from_move_line(self, line):
|
||||
phantom_bom = line.move_ids.bom_line_id.bom_id.filtered(
|
||||
lambda bom: bom.type == "phantom"
|
||||
)
|
||||
if phantom_bom and not line.company_id.rma_add_component_from_sale:
|
||||
return False
|
||||
return super()._create_from_move_line(line)
|
||||
Reference in New Issue
Block a user