[IMP] rma: use only procurement.group run to create stock transfers

Extra changes:
- Change reception_move_ids to reception_move_id
- Add test_rma_replace_pick_ship
- Code and method reduction to simplify logic
- Set route_ids (in/out) from procurements

Co-authored-by: Michael Tietz

TT48789

[UPD] Update rma.pot

[BOT] post-merge updates

Update translation files

Updated by "Update PO files to match POT (msgmerge)" hook in Weblate.

Translation: rma-17.0/rma-17.0-rma
Translate-URL: https://translation.odoo-community.org/projects/rma-17-0/rma-17-0-rma/
This commit is contained in:
Michael Tietz
2024-04-16 00:08:46 +02:00
committed by Víctor Martínez
parent b3d9634e37
commit 90ce4f4201
24 changed files with 814 additions and 372 deletions

View File

@@ -7,7 +7,7 @@ Return Merchandise Authorization Management
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:ed3081bf9b94660a0e6b35acbc7a874023c57e4bc3a87f3762eabbb1c75ce2f4
!! source digest: sha256:8f36869aece97a0f6af8aa5d76b446e9cf0bd589d914c1f5e12c628e87317021
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png
@@ -140,6 +140,9 @@ Known issues / Roadmap
- As soon as the picking is selected, the user should select the move,
but perhaps stock.move \_rec_name could be improved to better show
what the product of that move is.
- Add RMA reception and/or RMA delivery on several steps - 2 or 3 -
like normal receptions/deliveries. It should be a separate option
inside the warehouse definition.
Bug Tracker
===========
@@ -175,6 +178,8 @@ Contributors
- Antoni Marroig <amarroig@apsl.net>
- Michael Tietz (MT Software) mtietz@mt-software.de
Maintainers
-----------

View File

@@ -5,7 +5,7 @@
{
"name": "Return Merchandise Authorization Management",
"summary": "Return Merchandise Authorization (RMA)",
"version": "17.0.1.1.1",
"version": "17.0.2.0.0",
"development_status": "Production/Stable",
"category": "RMA",
"website": "https://github.com/OCA/rma",

View File

@@ -1,4 +1,5 @@
# Copyright 2020 Tecnativa - Ernesto Tejeda
# Copyright 2023 Michael Tietz (MT Software) <mtietz@mt-software.de>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
@@ -20,7 +21,9 @@ def post_init_hook(env):
def create_rma_locations(warehouse):
stock_location = env["stock.location"]
if not warehouse.rma_loc_id:
rma_location_vals = warehouse._get_rma_location_values()
rma_location_vals = warehouse._get_rma_location_values(
{"company_id": warehouse.company_id.id}, warehouse.code
)
warehouse.rma_loc_id = (
stock_location.with_context(active_test=False)
.create(rma_location_vals)
@@ -57,11 +60,19 @@ def post_init_hook(env):
whs.rma_in_type_id.return_picking_type_id = whs.rma_out_type_id.id
whs.rma_out_type_id.return_picking_type_id = whs.rma_in_type_id.id
def create_rma_routes(warehouses):
"""Create initially rma in/out stock.location.routes and stock.rules"""
warehouses = warehouses.with_context(rma_post_init_hook=True)
for wh in warehouses:
route_vals = wh._create_or_update_route()
wh.write(route_vals)
# Create rma locations and picking types
warehouses = env["stock.warehouse"].search([])
for warehouse in warehouses:
create_rma_locations(warehouse)
create_rma_picking_types(warehouse)
create_rma_routes(warehouses)
# Create rma sequence per company
for company in env["res.company"].search([]):
company.create_rma_index()

View File

@@ -1531,11 +1531,21 @@ msgstr "Automatische RMA-Kundenbenachrichtigungen"
msgid "RMA count"
msgstr "RMA-Zählung"
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_warehouse__rma_in_route_id
msgid "RMA in Route"
msgstr ""
#. module: rma
#: model:mail.message.subtype,description:rma.mt_rma_draft
msgid "RMA in draft state"
msgstr "RMA im Entwurfszustand"
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_warehouse__rma_out_route_id
msgid "RMA out Route"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_move__rma_receiver_ids
msgid "RMA receivers"
@@ -1735,6 +1745,11 @@ msgstr ""
msgid "Return Picking"
msgstr "Inbound Kommissionierung"
#. module: rma
#: model:ir.model,name:rma.model_stock_return_picking_line
msgid "Return Picking Line"
msgstr ""
#. module: rma
#: model:ir.actions.act_window,name:rma.rma_delivery_wizard_action
#: model:ir.model.fields.selection,name:rma.selection__rma_delivery_wizard__type__return
@@ -1758,6 +1773,13 @@ msgstr ""
msgid "Returned"
msgstr "Ist zurückgekommen"
#. module: rma
#. odoo-python
#: code:addons/rma/wizard/stock_picking_return.py:0
#, python-format
msgid "Returned Picking"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_rma_tag__rma_ids
msgid "Rma"

View File

@@ -1445,11 +1445,21 @@ msgstr ""
msgid "RMA count"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_warehouse__rma_in_route_id
msgid "RMA in Route"
msgstr ""
#. module: rma
#: model:mail.message.subtype,description:rma.mt_rma_draft
msgid "RMA in draft state"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_warehouse__rma_out_route_id
msgid "RMA out Route"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_move__rma_receiver_ids
msgid "RMA receivers"
@@ -1641,6 +1651,11 @@ msgstr ""
msgid "Return Picking"
msgstr ""
#. module: rma
#: model:ir.model,name:rma.model_stock_return_picking_line
msgid "Return Picking Line"
msgstr ""
#. module: rma
#: model:ir.actions.act_window,name:rma.rma_delivery_wizard_action
#: model:ir.model.fields.selection,name:rma.selection__rma_delivery_wizard__type__return
@@ -1662,6 +1677,13 @@ msgstr ""
msgid "Returned"
msgstr ""
#. module: rma
#. odoo-python
#: code:addons/rma/wizard/stock_picking_return.py:0
#, python-format
msgid "Returned Picking"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_rma_tag__rma_ids
msgid "Rma"

View File

@@ -1598,11 +1598,21 @@ msgstr "Notificaciones automáticas de RMA"
msgid "RMA count"
msgstr "Cantidad de RMAs"
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_warehouse__rma_in_route_id
msgid "RMA in Route"
msgstr ""
#. module: rma
#: model:mail.message.subtype,description:rma.mt_rma_draft
msgid "RMA in draft state"
msgstr "RMA en estado borrador"
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_warehouse__rma_out_route_id
msgid "RMA out Route"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_move__rma_receiver_ids
msgid "RMA receivers"
@@ -1803,6 +1813,11 @@ msgstr "Lugar de devolución"
msgid "Return Picking"
msgstr "Albarán de devolución"
#. module: rma
#: model:ir.model,name:rma.model_stock_return_picking_line
msgid "Return Picking Line"
msgstr ""
#. module: rma
#: model:ir.actions.act_window,name:rma.rma_delivery_wizard_action
#: model:ir.model.fields.selection,name:rma.selection__rma_delivery_wizard__type__return
@@ -1826,6 +1841,13 @@ msgstr ""
msgid "Returned"
msgstr "Devuelto"
#. module: rma
#. odoo-python
#: code:addons/rma/wizard/stock_picking_return.py:0
#, python-format
msgid "Returned Picking"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_rma_tag__rma_ids
msgid "Rma"

View File

@@ -1584,11 +1584,21 @@ msgstr "Notifications RMA automatiques au client"
msgid "RMA count"
msgstr "Nombre de RMA"
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_warehouse__rma_in_route_id
msgid "RMA in Route"
msgstr ""
#. module: rma
#: model:mail.message.subtype,description:rma.mt_rma_draft
msgid "RMA in draft state"
msgstr "RMA au statut brouillon"
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_warehouse__rma_out_route_id
msgid "RMA out Route"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_move__rma_receiver_ids
msgid "RMA receivers"
@@ -1789,6 +1799,11 @@ msgstr "Emplacement de retour"
msgid "Return Picking"
msgstr "Ordres de retour"
#. module: rma
#: model:ir.model,name:rma.model_stock_return_picking_line
msgid "Return Picking Line"
msgstr ""
#. module: rma
#: model:ir.actions.act_window,name:rma.rma_delivery_wizard_action
#: model:ir.model.fields.selection,name:rma.selection__rma_delivery_wizard__type__return
@@ -1812,6 +1827,13 @@ msgstr ""
msgid "Returned"
msgstr "Retourné"
#. module: rma
#. odoo-python
#: code:addons/rma/wizard/stock_picking_return.py:0
#, python-format
msgid "Returned Picking"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_rma_tag__rma_ids
msgid "Rma"

View File

@@ -1591,11 +1591,21 @@ msgstr "Notifiche automatiche ai clienti RMA"
msgid "RMA count"
msgstr "Conteggio RMA"
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_warehouse__rma_in_route_id
msgid "RMA in Route"
msgstr ""
#. module: rma
#: model:mail.message.subtype,description:rma.mt_rma_draft
msgid "RMA in draft state"
msgstr "RMA in stato bozza"
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_warehouse__rma_out_route_id
msgid "RMA out Route"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_move__rma_receiver_ids
msgid "RMA receivers"
@@ -1795,6 +1805,11 @@ msgstr "Ubicazione di reso"
msgid "Return Picking"
msgstr "Prelievo di reso"
#. module: rma
#: model:ir.model,name:rma.model_stock_return_picking_line
msgid "Return Picking Line"
msgstr ""
#. module: rma
#: model:ir.actions.act_window,name:rma.rma_delivery_wizard_action
#: model:ir.model.fields.selection,name:rma.selection__rma_delivery_wizard__type__return
@@ -1818,6 +1833,13 @@ msgstr ""
msgid "Returned"
msgstr "Restituito"
#. module: rma
#. odoo-python
#: code:addons/rma/wizard/stock_picking_return.py:0
#, python-format
msgid "Returned Picking"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_rma_tag__rma_ids
msgid "Rma"

View File

@@ -1445,11 +1445,21 @@ msgstr ""
msgid "RMA count"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_warehouse__rma_in_route_id
msgid "RMA in Route"
msgstr ""
#. module: rma
#: model:mail.message.subtype,description:rma.mt_rma_draft
msgid "RMA in draft state"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_warehouse__rma_out_route_id
msgid "RMA out Route"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_move__rma_receiver_ids
msgid "RMA receivers"
@@ -1641,6 +1651,11 @@ msgstr ""
msgid "Return Picking"
msgstr ""
#. module: rma
#: model:ir.model,name:rma.model_stock_return_picking_line
msgid "Return Picking Line"
msgstr ""
#. module: rma
#: model:ir.actions.act_window,name:rma.rma_delivery_wizard_action
#: model:ir.model.fields.selection,name:rma.selection__rma_delivery_wizard__type__return
@@ -1662,6 +1677,13 @@ msgstr ""
msgid "Returned"
msgstr ""
#. module: rma
#. odoo-python
#: code:addons/rma/wizard/stock_picking_return.py:0
#, python-format
msgid "Returned Picking"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_rma_tag__rma_ids
msgid "Rma"

View File

@@ -1470,11 +1470,21 @@ msgstr "Notificações automáticas a clientes de RMA"
msgid "RMA count"
msgstr "Contagem de RMA"
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_warehouse__rma_in_route_id
msgid "RMA in Route"
msgstr ""
#. module: rma
#: model:mail.message.subtype,description:rma.mt_rma_draft
msgid "RMA in draft state"
msgstr "RMA em estado de rascunho"
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_warehouse__rma_out_route_id
msgid "RMA out Route"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_move__rma_receiver_ids
msgid "RMA receivers"
@@ -1666,6 +1676,11 @@ msgstr ""
msgid "Return Picking"
msgstr "Operação de Devolução"
#. module: rma
#: model:ir.model,name:rma.model_stock_return_picking_line
msgid "Return Picking Line"
msgstr ""
#. module: rma
#: model:ir.actions.act_window,name:rma.rma_delivery_wizard_action
#: model:ir.model.fields.selection,name:rma.selection__rma_delivery_wizard__type__return
@@ -1687,6 +1702,13 @@ msgstr ""
msgid "Returned"
msgstr "Devolvido"
#. module: rma
#. odoo-python
#: code:addons/rma/wizard/stock_picking_return.py:0
#, python-format
msgid "Returned Picking"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_rma_tag__rma_ids
msgid "Rma"

View File

@@ -1470,11 +1470,21 @@ msgstr ""
msgid "RMA count"
msgstr "Contagem de RMA"
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_warehouse__rma_in_route_id
msgid "RMA in Route"
msgstr ""
#. module: rma
#: model:mail.message.subtype,description:rma.mt_rma_draft
msgid "RMA in draft state"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_warehouse__rma_out_route_id
msgid "RMA out Route"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_move__rma_receiver_ids
msgid "RMA receivers"
@@ -1666,6 +1676,11 @@ msgstr ""
msgid "Return Picking"
msgstr "Retorno de Coleta"
#. module: rma
#: model:ir.model,name:rma.model_stock_return_picking_line
msgid "Return Picking Line"
msgstr ""
#. module: rma
#: model:ir.actions.act_window,name:rma.rma_delivery_wizard_action
#: model:ir.model.fields.selection,name:rma.selection__rma_delivery_wizard__type__return
@@ -1687,6 +1702,13 @@ msgstr ""
msgid "Returned"
msgstr "Retornado(a)"
#. module: rma
#. odoo-python
#: code:addons/rma/wizard/stock_picking_return.py:0
#, python-format
msgid "Returned Picking"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_rma_tag__rma_ids
msgid "Rma"

View File

@@ -1437,11 +1437,21 @@ msgstr ""
msgid "RMA count"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_warehouse__rma_in_route_id
msgid "RMA in Route"
msgstr ""
#. module: rma
#: model:mail.message.subtype,description:rma.mt_rma_draft
msgid "RMA in draft state"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_warehouse__rma_out_route_id
msgid "RMA out Route"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_move__rma_receiver_ids
msgid "RMA receivers"
@@ -1633,6 +1643,11 @@ msgstr ""
msgid "Return Picking"
msgstr ""
#. module: rma
#: model:ir.model,name:rma.model_stock_return_picking_line
msgid "Return Picking Line"
msgstr ""
#. module: rma
#: model:ir.actions.act_window,name:rma.rma_delivery_wizard_action
#: model:ir.model.fields.selection,name:rma.selection__rma_delivery_wizard__type__return
@@ -1654,6 +1669,13 @@ msgstr ""
msgid "Returned"
msgstr ""
#. module: rma
#. odoo-python
#: code:addons/rma/wizard/stock_picking_return.py:0
#, python-format
msgid "Returned Picking"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_rma_tag__rma_ids
msgid "Rma"

View File

@@ -1468,11 +1468,21 @@ msgstr ""
msgid "RMA count"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_warehouse__rma_in_route_id
msgid "RMA in Route"
msgstr ""
#. module: rma
#: model:mail.message.subtype,description:rma.mt_rma_draft
msgid "RMA in draft state"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_warehouse__rma_out_route_id
msgid "RMA out Route"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_move__rma_receiver_ids
msgid "RMA receivers"
@@ -1664,6 +1674,11 @@ msgstr ""
msgid "Return Picking"
msgstr ""
#. module: rma
#: model:ir.model,name:rma.model_stock_return_picking_line
msgid "Return Picking Line"
msgstr ""
#. module: rma
#: model:ir.actions.act_window,name:rma.rma_delivery_wizard_action
#: model:ir.model.fields.selection,name:rma.selection__rma_delivery_wizard__type__return
@@ -1685,6 +1700,13 @@ msgstr ""
msgid "Returned"
msgstr ""
#. module: rma
#. odoo-python
#: code:addons/rma/wizard/stock_picking_return.py:0
#, python-format
msgid "Returned Picking"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_rma_tag__rma_ids
msgid "Rma"

View File

@@ -1447,11 +1447,21 @@ msgstr ""
msgid "RMA count"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_warehouse__rma_in_route_id
msgid "RMA in Route"
msgstr ""
#. module: rma
#: model:mail.message.subtype,description:rma.mt_rma_draft
msgid "RMA in draft state"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_warehouse__rma_out_route_id
msgid "RMA out Route"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_stock_move__rma_receiver_ids
msgid "RMA receivers"
@@ -1643,6 +1653,11 @@ msgstr ""
msgid "Return Picking"
msgstr ""
#. module: rma
#: model:ir.model,name:rma.model_stock_return_picking_line
msgid "Return Picking Line"
msgstr ""
#. module: rma
#: model:ir.actions.act_window,name:rma.rma_delivery_wizard_action
#: model:ir.model.fields.selection,name:rma.selection__rma_delivery_wizard__type__return
@@ -1664,6 +1679,13 @@ msgstr ""
msgid "Returned"
msgstr ""
#. module: rma
#. odoo-python
#: code:addons/rma/wizard/stock_picking_return.py:0
#, python-format
msgid "Returned Picking"
msgstr ""
#. module: rma
#: model:ir.model.fields,field_description:rma.field_rma_tag__rma_ids
msgid "Rma"

View File

@@ -0,0 +1,17 @@
# Copyright 2024 Tecnativa - Víctor Martínez
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openupgradelib import openupgrade
@openupgrade.migrate()
def migrate(env, version):
"""Similar behavior to create_rma_routes of post_init_hook."""
warehouses = env["stock.warehouse"].search([])
warehouses = warehouses.with_context(rma_post_init_hook=True)
for wh in warehouses:
if not wh.rma_in_type_id or not wh.rma_out_type_id:
data = wh._create_or_update_sequences_and_picking_types()
wh.write(data)
route_vals = wh._create_or_update_route()
wh.write(route_vals)

View File

@@ -27,7 +27,7 @@ class ResPartner(models.Model):
def action_view_rma(self):
self.ensure_one()
action = self.sudo().env.ref("rma.rma_action").read()[0]
action = self.env["ir.actions.act_window"]._for_xml_id("rma.rma_action")
rma = self.rma_ids
if len(rma) == 1:
action.update(

View File

@@ -1,12 +1,13 @@
# Copyright 2020 Tecnativa - Ernesto Tejeda
# Copyright 2023 Tecnativa - Pedro M. Baeza
# Copyright 2023 Michael Tietz (MT Software) <mtietz@mt-software.de>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import logging
from collections import Counter
from collections import defaultdict
from itertools import groupby
from odoo import _, api, fields, models
from odoo.exceptions import AccessError, ValidationError
from odoo.tests import Form
from odoo.tools import html2plaintext
from odoo.addons.stock.models.stock_move import PROCUREMENT_PRIORITIES
@@ -259,18 +260,8 @@ class Rma(models.Model):
)
def _compute_delivery_picking_count(self):
# It is enough to count the moves to know how many pickings
# there are because there will be a unique move linked to the
# same picking and the same rma.
rma_data = self.env["stock.move"].read_group(
[("rma_id", "in", self.ids)],
["rma_id", "picking_id"],
["rma_id", "picking_id"],
lazy=False,
)
mapped_data = Counter(map(lambda r: r["rma_id"][0], rma_data))
for record in self:
record.delivery_picking_count = mapped_data.get(record.id, 0)
for rma in self:
rma.delivery_picking_count = len(rma.delivery_move_ids.picking_id)
@api.depends(
"delivery_move_ids",
@@ -609,22 +600,85 @@ class Rma(models.Model):
}
def _add_message_subscribe_partner(self):
self.ensure_one()
if self.partner_id and self.partner_id not in self.message_partner_ids:
self.message_subscribe([self.partner_id.id])
def _product_is_storable(self, product=None):
product = product or self.product_id
return product.type in ["product", "consu"]
def _prepare_procurement_group_vals(self):
return {
"move_type": "direct",
"partner_id": self and self.partner_shipping_id.id or False,
"name": self and ", ".join(self.mapped("name")) or False,
}
def _prepare_common_procurement_vals(
self, warehouse=None, scheduled_date=None, group=None
):
self.ensure_one()
group = group or self.procurement_group_id
if not group:
group = self.env["procurement.group"].create(
self._prepare_procurement_group_vals()
)
return {
"company_id": self.company_id,
"group_id": group,
"date_planned": scheduled_date or fields.Datetime.now(),
"warehouse_id": warehouse or self.warehouse_id,
"partner_id": group.partner_id.id,
"priority": self.priority,
}
def _prepare_reception_procurement_vals(self, group=None):
"""This method is used only for reception and a specific RMA IN route."""
vals = self._prepare_common_procurement_vals(group=group)
vals["route_ids"] = self.warehouse_id.rma_in_route_id
vals["rma_receiver_ids"] = [(6, 0, self.ids)]
if self.move_id:
vals["origin_returned_move_id"] = self.move_id.id
return vals
def _prepare_reception_procurements(self):
procurements = []
group_model = self.env["procurement.group"]
for rma in self:
if not rma._product_is_storable():
continue
group = rma.procurement_group_id
if not group:
group = group_model.create(rma._prepare_procurement_group_vals())
procurements.append(
group_model.Procurement(
rma.product_id,
rma.product_uom_qty,
rma.product_uom,
rma.location_id,
rma.product_id.display_name,
group.name,
rma.company_id,
rma._prepare_reception_procurement_vals(group),
)
)
return procurements
def action_confirm(self):
"""Invoked when 'Confirm' button in rma form view is clicked."""
self.ensure_one()
self._ensure_required_fields()
if self.state == "draft":
if self.picking_id:
reception_move = self._create_receptions_from_picking()
else:
reception_move = self._create_receptions_from_product()
reception_move.picked = True
self.write({"reception_move_id": reception_move.id, "state": "confirmed"})
self._add_message_subscribe_partner()
self._send_confirmation_email()
self = self.filtered(lambda rma: rma.state == "draft")
if not self:
return
procurements = self._prepare_reception_procurements()
if procurements:
self.env["procurement.group"].run(procurements)
self.reception_move_id.picking_id.action_assign()
self.write({"state": "confirmed"})
for rma in self:
rma._add_message_subscribe_partner()
self._send_confirmation_email()
def action_refund(self):
"""Invoked when 'Refund' button in rma form view is clicked
@@ -663,11 +717,8 @@ class Rma(models.Model):
self._ensure_can_be_replaced()
# Force active_id to avoid issues when coming from smart buttons
# in other models
action = (
self.env.ref("rma.rma_delivery_wizard_action")
.sudo()
.with_context(active_id=self.id)
.read()[0]
action = self.env["ir.actions.act_window"]._for_xml_id(
"rma.rma_delivery_wizard_action"
)
action["name"] = "Replace product(s)"
action["context"] = dict(self.env.context)
@@ -686,11 +737,8 @@ class Rma(models.Model):
self._ensure_can_be_returned()
# Force active_id to avoid issues when coming from smart buttons
# in other models
action = (
self.env.ref("rma.rma_delivery_wizard_action")
.sudo()
.with_context(active_id=self.id)
.read()[0]
action = self.env["ir.actions.act_window"]._for_xml_id(
"rma.rma_delivery_wizard_action"
)
action["context"] = dict(self.env.context)
action["context"].update(
@@ -706,11 +754,8 @@ class Rma(models.Model):
self._ensure_can_be_split()
# Force active_id to avoid issues when coming from smart buttons
# in other models
action = (
self.env.ref("rma.rma_split_wizard_action")
.sudo()
.with_context(active_id=self.id)
.read()[0]
action = self.env["ir.actions.act_window"]._for_xml_id(
"rma.rma_split_wizard_action"
)
action["context"] = dict(self.env.context)
action["context"].update(active_id=self.id, active_ids=self.ids)
@@ -722,11 +767,8 @@ class Rma(models.Model):
self._ensure_can_be_returned()
# Force active_id to avoid issues when coming from smart buttons
# in other models
action = (
self.env.ref("rma.rma_finalization_wizard_action")
.sudo()
.with_context(active_id=self.id)
.read()[0]
action = self.env["ir.actions.act_window"]._for_xml_id(
"rma.rma_finalization_wizard_action"
)
action["context"] = dict(self.env.context)
action["context"].update(active_id=self.id, active_ids=self.ids)
@@ -734,7 +776,7 @@ class Rma(models.Model):
def action_cancel(self):
"""Invoked when 'Cancel' button in rma form view is clicked."""
self.mapped("reception_move_id")._action_cancel()
self.reception_move_id._action_cancel()
self.write({"state": "cancelled"})
def action_draft(self):
@@ -759,25 +801,28 @@ class Rma(models.Model):
"url": self.get_portal_url(),
}
def action_view_receipt(self):
"""Invoked when 'Receipt' smart button in rma form view is clicked."""
def _action_view_pickings(self, pickings):
self.ensure_one()
# Force active_id to avoid issues when coming from smart buttons
# in other models
action = (
self.env.ref("stock.action_picking_tree_all")
.sudo()
.with_context(active_id=self.id)
.read()[0]
)
action.update(
res_id=self.reception_move_id.picking_id.id,
view_mode="form",
view_id=False,
views=False,
action = self.env["ir.actions.act_window"]._for_xml_id(
"stock.action_picking_tree_all"
)
if len(pickings) > 1:
action["domain"] = [("id", "in", pickings.ids)]
elif pickings:
action.update(
res_id=pickings.id,
view_mode="form",
view_id=False,
views=False,
)
return action
def action_view_receipt(self):
"""Invoked when 'Receipt' smart button in rma form view is clicked."""
return self._action_view_pickings(self.mapped("reception_move_id.picking_id"))
def action_view_refund(self):
"""Invoked when 'Refund' smart button in rma form view is clicked."""
self.ensure_one()
@@ -793,23 +838,7 @@ class Rma(models.Model):
def action_view_delivery(self):
"""Invoked when 'Delivery' smart button in rma form view is clicked."""
action = (
self.env.ref("stock.action_picking_tree_all")
.sudo()
.with_context(active_id=self.id)
.read()[0]
)
picking = self.delivery_move_ids.mapped("picking_id")
if len(picking) > 1:
action["domain"] = [("id", "in", picking.ids)]
elif picking:
action.update(
res_id=picking.id,
view_mode="form",
view_id=False,
views=False,
)
return action
return self._action_view_pickings(self.mapped("delivery_move_ids.picking_id"))
# Validation business methods
def _ensure_required_fields(self):
@@ -925,83 +954,6 @@ class Rma(models.Model):
)
)
# Reception business methods
def _create_receptions_from_picking(self):
self.ensure_one()
stock_return_picking_form = Form(
self.env["stock.return.picking"].with_context(
active_ids=self.picking_id.ids,
active_id=self.picking_id.id,
active_model="stock.picking",
)
)
return_wizard = stock_return_picking_form.save()
if self.location_id:
return_wizard.location_id = self.location_id
return_wizard.product_return_moves.filtered(
lambda r: r.move_id != self.move_id
).unlink()
return_line = return_wizard.product_return_moves
return_line.update(
{
"quantity": self.product_uom_qty,
# The to_refund field is now True by default, which isn't right in the
# RMA creation context
"to_refund": False,
}
)
# set_rma_picking_type is to override the copy() method of stock
# picking and change the default picking type to rma picking type.
picking_action = return_wizard.with_context(
set_rma_picking_type=True
).create_returns()
picking_id = picking_action["res_id"]
picking = self.env["stock.picking"].browse(picking_id)
picking.origin = f"{self.name} ({picking.origin})"
move = picking.move_ids
move.priority = self.priority
return move
def _create_receptions_from_product(self):
self.ensure_one()
picking = self.env["stock.picking"].create(self._prepare_picking_vals())
picking.action_confirm()
picking.action_assign()
picking.message_post_with_source(
"mail.message_origin_link",
render_values={"self": picking, "origin": self},
subtype_id=self.env["ir.model.data"]._xmlid_to_res_id("mail.mt_note"),
)
return picking.move_ids
def _prepare_picking_vals(self):
return {
"picking_type_id": self.warehouse_id.rma_in_type_id.id,
"origin": self.name,
"partner_id": self.partner_shipping_id.id,
"location_id": self.partner_shipping_id.property_stock_customer.id,
"location_dest_id": self.location_id.id,
"move_ids": [
(
0,
0,
{
"product_id": self.product_id.id,
# same text as origin move or product text in partner lang
"name": self.move_id.name
or self.product_id.with_context(
lang=self.partner_id.lang or "en_US"
).display_name,
"location_id": (
self.partner_shipping_id.property_stock_customer.id
),
"location_dest_id": self.location_id.id,
"product_uom_qty": self.product_uom_qty,
},
)
],
}
# Extract business methods
def extract_quantity(self, qty, uom):
self.ensure_one()
@@ -1075,44 +1027,101 @@ class Rma(models.Model):
"rma_id": self.id,
}
# Returning business methods
def create_return(self, scheduled_date, qty=None, uom=None):
"""Intended to be invoked by the delivery wizard"""
def _delivery_should_be_grouped(self):
"""Checks if the rmas should be grouped for the delivery process"""
group_returns = self.env.company.rma_return_grouping
if "rma_return_grouping" in self.env.context:
group_returns = self.env.context.get("rma_return_grouping")
return group_returns
def _delivery_group_key(self):
"""Returns a key by which the rmas should be grouped for the delivery process"""
self.ensure_one()
return (self.partner_shipping_id.id, self.company_id.id, self.warehouse_id.id)
def _group_delivery_if_needed(self):
"""Groups the given rmas by the returned key from _delivery_group_key
by setting the procurement_group_id on the each rma if there is not yet on
set"""
if not self._delivery_should_be_grouped():
return
grouped_rmas = groupby(
sorted(self, key=lambda rma: rma._delivery_group_key()),
key=lambda rma: [rma._delivery_group_key()],
)
for _group, rmas in grouped_rmas:
rmas = (
self.browse()
.concat(*list(rmas))
.filtered(lambda rma: not rma.procurement_group_id)
)
if not rmas:
continue
proc_group = self.env["procurement.group"].create(
rmas._prepare_procurement_group_vals()
)
rmas.write({"procurement_group_id": proc_group.id})
def _prepare_delivery_procurement_vals(self, scheduled_date=None):
"""This method is used only for Delivery (not replace). It is important to set
RMA Out route."""
vals = self._prepare_common_procurement_vals(scheduled_date=scheduled_date)
vals["rma_id"] = self.id
vals["route_ids"] = self.warehouse_id.rma_out_route_id
vals["move_orig_ids"] = [(6, 0, self.reception_move_id.ids)]
return vals
def _prepare_delivery_procurements(self, scheduled_date=None, qty=None, uom=None):
self._group_delivery_if_needed()
procurements = []
group_model = self.env["procurement.group"]
for rma in self:
if not rma.procurement_group_id:
rma.procurement_group_id = group_model.create(
rma._prepare_procurement_group_vals()
)
vals = rma._prepare_delivery_procurement_vals(scheduled_date)
group = vals.get("group_id")
procurements.append(
group_model.Procurement(
rma.product_id,
qty or rma.product_uom_qty,
uom or rma.product_uom,
rma.partner_shipping_id.property_stock_customer,
rma.product_id.display_name,
group.name,
rma.company_id,
vals,
)
)
return procurements
# Returning business methods
def create_return(self, scheduled_date, qty=None, uom=None):
"""Intended to be invoked by the delivery wizard"""
self._ensure_can_be_returned()
self._ensure_qty_to_return(qty, uom)
group_dict = {}
rmas_to_return = self.filtered("can_be_returned")
for record in rmas_to_return:
key = (
record.partner_shipping_id.id,
record.company_id.id,
record.warehouse_id,
rmas_to_return = self.filtered(
lambda rma: rma.can_be_returned and rma._product_is_storable()
)
procurements = rmas_to_return._prepare_delivery_procurements(
scheduled_date, qty, uom
)
if procurements:
self.env["procurement.group"].run(procurements)
pickings = defaultdict(lambda: self.browse())
for rma in rmas_to_return:
picking = rma.delivery_move_ids.picking_id.sorted("id", reverse=True)[0]
pickings[picking] |= rma
rma.message_post(
body=_(
'Return: <a href="#" data-oe-model="stock.picking" '
'data-oe-id="%(id)d">%(name)s</a> has been created.'
)
% ({"id": picking.id, "name": picking.name})
)
group_dict.setdefault(key, self.env["rma"])
group_dict[key] |= record
if group_returns:
grouped_rmas = group_dict.values()
else:
grouped_rmas = rmas_to_return
for rmas in grouped_rmas:
origin = ", ".join(rmas.mapped("name"))
picking_vals = rmas[0]._prepare_returning_picking_vals(origin)
for rma in rmas:
picking_vals["move_ids"].append(
(0, 0, rma._prepare_returning_move_vals(scheduled_date, qty, uom))
)
picking = self.env["stock.picking"].create(picking_vals)
for rma in rmas:
rma.message_post(
body=_(
'Return: <a href="#" data-oe-model="stock.picking" '
'data-oe-id="%(id)d">%(name)s</a> has been created.'
)
% ({"id": picking.id, "name": picking.name})
)
for picking, rmas in pickings.items():
picking.action_confirm()
picking.action_assign()
picking.message_post_with_source(
@@ -1122,42 +1131,53 @@ class Rma(models.Model):
)
rmas_to_return.write({"state": "waiting_return"})
def _prepare_returning_picking_vals(self, origin=None):
self.ensure_one()
return {
"picking_type_id": self.warehouse_id.rma_out_type_id.id,
"location_id": self.location_id.id,
"location_dest_id": self.reception_move_id.location_id.id,
"origin": origin or self.name,
"partner_id": self.partner_shipping_id.id,
"company_id": self.company_id.id,
"move_ids": [],
}
def _prepare_replace_procurement_vals(self, warehouse=None, scheduled_date=None):
"""This method is used only for Replace (not Delivery). We do not use any
specific route here."""
vals = self._prepare_common_procurement_vals(warehouse, scheduled_date)
vals["rma_id"] = self.id
return vals
def _prepare_returning_move_vals(self, scheduled_date, quantity=None, uom=None):
self.ensure_one()
return {
"product_id": self.product_id.id,
"name": self.product_id.with_context(
lang=self.partner_shipping_id.lang or "en_US"
).display_name,
"product_uom_qty": quantity or self.product_uom_qty,
"product_uom": uom and uom.id or self.product_uom.id,
"location_id": self.location_id.id,
"location_dest_id": self.reception_move_id.location_id.id,
"date": scheduled_date,
"rma_id": self.id,
"move_orig_ids": [(4, self.reception_move_id.id)],
"company_id": self.company_id.id,
}
def _prepare_replace_procurements(
self, warehouse, scheduled_date, product, qty, uom
):
procurements = []
group_model = self.env["procurement.group"]
for rma in self:
if not rma._product_is_storable(product):
continue
if not rma.procurement_group_id:
rma.procurement_group_id = group_model.create(
rma._prepare_procurement_group_vals()
)
vals = rma._prepare_replace_procurement_vals(warehouse, scheduled_date)
group = vals.get("group_id")
procurements.append(
group_model.Procurement(
product,
qty,
uom,
rma.partner_shipping_id.property_stock_customer,
product.display_name,
group.name,
rma.company_id,
vals,
)
)
return procurements
# Replacing business methods
def create_replace(self, scheduled_date, warehouse, product, qty, uom):
"""Intended to be invoked by the delivery wizard"""
self.ensure_one()
self._ensure_can_be_replaced()
moves_before = self.delivery_move_ids
self._action_launch_stock_rule(scheduled_date, warehouse, product, qty, uom)
procurements = self._prepare_replace_procurements(
warehouse, scheduled_date, product, qty, uom
)
if procurements:
self.env["procurement.group"].run(procurements)
new_moves = self.delivery_move_ids - moves_before
body = ""
# The product replacement could explode into several moves like in the case of
@@ -1181,6 +1201,12 @@ class Rma(models.Model):
)
+ "\n"
)
for rma in self:
rma._add_replace_message(body, qty, uom)
self.write({"state": "waiting_replacement"})
def _add_replace_message(self, body, qty, uom):
self.ensure_one()
self.message_post(
body=body
or _(
@@ -1193,74 +1219,13 @@ class Rma(models.Model):
)
% (
{
"id": product.id,
"name": product.display_name,
"id": self.id,
"name": self.display_name,
"qty": qty,
"uom": uom.name,
}
)
)
if self.state != "waiting_replacement":
self.state = "waiting_replacement"
def _action_launch_stock_rule(
self,
scheduled_date,
warehouse,
product,
qty,
uom,
):
"""Creates a delivery picking and launch stock rule. It is invoked by:
rma.create_replace
"""
self.ensure_one()
if self.product_id.type not in ("consu", "product"):
return
if not self.procurement_group_id:
self.procurement_group_id = (
self.env["procurement.group"]
.create(
{
"name": self.name,
"move_type": "direct",
"partner_id": self.partner_shipping_id.id,
}
)
.id
)
values = self._prepare_procurement_values(
self.procurement_group_id, scheduled_date, warehouse
)
procurement = self.env["procurement.group"].Procurement(
product,
qty,
uom,
self.partner_shipping_id.property_stock_customer,
self.product_id.display_name,
self.procurement_group_id.name,
self.company_id,
values,
)
self.env["procurement.group"].run([procurement])
return True
def _prepare_procurement_values(
self,
group_id,
scheduled_date,
warehouse,
):
self.ensure_one()
return {
"company_id": self.company_id,
"group_id": group_id,
"date_planned": scheduled_date,
"warehouse_id": warehouse,
"partner_id": self.partner_shipping_id.id,
"rma_id": self.id,
"priority": self.priority,
}
# Mail business methods
def _creation_subtype(self):

View File

@@ -1,4 +1,5 @@
# Copyright 2020 Tecnativa - Ernesto Tejeda
# Copyright 2023 Michael Tietz (MT Software) <mtietz@mt-software.de>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
@@ -23,7 +24,7 @@ class StockMove(models.Model):
string="RMA receivers",
copy=False,
)
# RMA that create the delivery movement to the customer
# RMA that creates the out move
rma_id = fields.Many2one(
comodel_name="rma",
string="RMA return",
@@ -33,8 +34,8 @@ class StockMove(models.Model):
def unlink(self):
# A stock user could have no RMA permissions, so the ids wouldn't
# be accessible due to record rules.
rma_receiver = self.sudo().mapped("rma_receiver_ids")
rma = self.sudo().mapped("rma_id")
rma_receiver = self.sudo().rma_receiver_ids
rma = self.sudo().rma_id
res = super().unlink()
rma_receiver.filtered(lambda x: x.state != "cancelled").write(
{"state": "draft"}
@@ -115,38 +116,22 @@ class StockMove(models.Model):
res["rma_id"] = self.sudo().rma_id.id
return res
def _prepare_return_rma_vals(self, original_picking):
"""hook method for preparing an RMA from the 'return picking wizard'."""
self.ensure_one()
partner = original_picking.partner_id
if hasattr(original_picking, "sale_id") and original_picking.sale_id:
partner_invoice_id = original_picking.sale_id.partner_invoice_id.id
partner_shipping_id = original_picking.sale_id.partner_shipping_id.id
else:
partner_invoice_id = partner.address_get(["invoice"]).get("invoice", False)
partner_shipping_id = partner.address_get(["delivery"]).get(
"delivery", False
)
return {
"user_id": self.env.user.id,
"partner_id": partner.id,
"partner_shipping_id": partner_shipping_id,
"partner_invoice_id": partner_invoice_id,
"origin": original_picking.name,
"picking_id": original_picking.id,
"move_id": self.origin_returned_move_id.id,
"product_id": self.origin_returned_move_id.product_id.id,
"product_uom_qty": self.product_uom_qty,
"product_uom": self.product_uom.id,
"reception_move_id": self.id,
"company_id": self.company_id.id,
"location_id": self.location_dest_id.id,
"state": "confirmed",
}
def _prepare_procurement_values(self):
res = super()._prepare_procurement_values()
if self.rma_id:
res["rma_id"] = self.rma_id.id
return res
class StockRule(models.Model):
_inherit = "stock.rule"
def _get_custom_move_fields(self):
return super()._get_custom_move_fields() + ["rma_id"]
move_fields = super()._get_custom_move_fields()
move_fields += [
"rma_id",
"origin_returned_move_id",
"move_orig_ids",
"rma_receiver_ids",
]
return move_fields

View File

@@ -1,7 +1,8 @@
# Copyright 2020 Tecnativa - Ernesto Tejeda
# Copyright 2023 Michael Tietz (MT Software) <mtietz@mt-software.de>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo import _, fields, models
class StockWarehouse(models.Model):
@@ -27,35 +28,39 @@ class StockWarehouse(models.Model):
comodel_name="stock.location",
string="RMA Location",
)
rma_in_route_id = fields.Many2one("stock.route", "RMA in Route")
rma_out_route_id = fields.Many2one("stock.route", "RMA out Route")
@api.model_create_multi
def create(self, vals_list):
"""To create an RMA location and link it with a new warehouse,
this method is overridden instead of '_get_locations_values'
method because the locations that are created with the
values returned by that method are forced to be children
of view_location_id, and we don't want that.
"""
res = super().create(vals_list)
stock_location = self.env["stock.location"]
for record in res:
rma_location_vals = record._get_rma_location_values()
record.rma_loc_id = stock_location.create(rma_location_vals).id
return res
def _get_rma_location_values(self):
def _get_rma_location_values(self, vals, code=False):
"""this method is intended to be used by 'create' method
to create a new RMA location to be linked to a new warehouse.
"""
company_id = vals.get(
"company_id", self.default_get(["company_id"])["company_id"]
)
code = vals.get("code") or code or ""
code = code.replace(" ", "").upper()
view_location_id = vals.get("view_location_id")
view_location = (
view_location_id
and self.view_location_id.browse(view_location_id)
or self.view_location_id
)
return {
"name": self.view_location_id.name,
"name": view_location.name,
"active": True,
"return_location": True,
"usage": "internal",
"company_id": self.company_id.id,
"company_id": company_id,
"location_id": self.env.ref("rma.stock_location_rma").id,
"barcode": self._valid_barcode(code + "-RMA", company_id),
}
def _get_locations_values(self, vals, code=False):
res = super()._get_locations_values(vals, code)
res["rma_loc_id"] = self._get_rma_location_values(vals, code)
return res
def _get_sequence_values(self, name=False, code=False):
values = super()._get_sequence_values(name=name, code=code)
values.update(
@@ -77,12 +82,14 @@ class StockWarehouse(models.Model):
return values
def _update_name_and_code(self, new_name=False, new_code=False):
res = super()._update_name_and_code(new_name, new_code)
for warehouse in self:
sequence_data = warehouse._get_sequence_values()
warehouse.rma_in_type_id.sequence_id.write(sequence_data["rma_in_type_id"])
warehouse.rma_out_type_id.sequence_id.write(
sequence_data["rma_out_type_id"]
)
return res
def _get_picking_type_create_values(self, max_sequence):
data, next_sequence = super()._get_picking_type_create_values(max_sequence)
@@ -116,12 +123,13 @@ class StockWarehouse(models.Model):
def _get_picking_type_update_values(self):
data = super()._get_picking_type_update_values()
data.update(
{
"rma_in_type_id": {"default_location_dest_id": self.rma_loc_id.id},
"rma_out_type_id": {"default_location_src_id": self.rma_loc_id.id},
}
)
picking_types = {
"rma_in_type_id": {"default_location_dest_id": self.rma_loc_id.id},
"rma_out_type_id": {"default_location_src_id": self.rma_loc_id.id},
}
if self.env.context.get("rma_post_init_hook"):
return picking_types
data.update(picking_types)
return data
def _create_or_update_sequences_and_picking_types(self):
@@ -138,3 +146,70 @@ class StockWarehouse(models.Model):
{"return_picking_type_id": data.get("rma_out_type_id", False)}
)
return data
def _get_routes_values(self):
res = super()._get_routes_values()
rma_routes = {
"rma_in_route_id": {
"routing_key": "rma_in",
"depends": ["active"],
"route_update_values": {
"name": self._format_routename("RMA In"),
"active": self.active,
},
"route_create_values": {
"warehouse_selectable": True,
"company_id": self.company_id.id,
"sequence": 100,
},
"rules_values": {
"active": True,
},
},
"rma_out_route_id": {
"routing_key": "rma_out",
"depends": ["active"],
"route_update_values": {
"name": self._format_routename("RMA Out"),
"active": self.active,
},
"route_create_values": {
"warehouse_selectable": True,
"company_id": self.company_id.id,
"sequence": 110,
},
"rules_values": {
"active": True,
},
},
}
if self.env.context.get("rma_post_init_hook"):
return rma_routes
res.update(rma_routes)
return res
def get_rules_dict(self):
res = super().get_rules_dict()
customer_loc, supplier_loc = self._get_partner_locations()
for warehouse in self:
res[warehouse.id].update(
{
"rma_in": [
self.Routing(
customer_loc,
warehouse.rma_loc_id,
warehouse.rma_in_type_id,
"pull",
)
],
"rma_out": [
self.Routing(
warehouse.rma_loc_id,
customer_loc,
warehouse.rma_out_type_id,
"pull",
)
],
}
)
return res

View File

@@ -7,3 +7,4 @@
- Giovanni Serra - Ooops \<<giovanni@ooops404.com>\>
- [APSL-Nagarro](https://www.apsl.tech):
- Antoni Marroig \<<amarroig@apsl.net>\>
- Michael Tietz (MT Software) <mtietz@mt-software.de>

View File

@@ -1,3 +1,6 @@
- As soon as the picking is selected, the user should select the move,
but perhaps stock.move \_rec_name could be improved to better show
what the product of that move is.
- Add RMA reception and/or RMA delivery on several steps - 2 or 3 - like
normal receptions/deliveries. It should be a separate option inside the
warehouse definition.

View File

@@ -366,7 +366,7 @@ ul.auto-toc {
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:ed3081bf9b94660a0e6b35acbc7a874023c57e4bc3a87f3762eabbb1c75ce2f4
!! source digest: sha256:8f36869aece97a0f6af8aa5d76b446e9cf0bd589d914c1f5e12c628e87317021
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Production/Stable" src="https://img.shields.io/badge/maturity-Production%2FStable-green.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/rma/tree/17.0/rma"><img alt="OCA/rma" src="https://img.shields.io/badge/github-OCA%2Frma-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/rma-17-0/rma-17-0-rma"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/rma&amp;target_branch=17.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This module allows you to manage <a class="reference external" href="https://en.wikipedia.org/wiki/Return_merchandise_authorization">Return Merchandise Authorization
@@ -493,6 +493,9 @@ team will be the default one if no team is set.</li>
<li>As soon as the picking is selected, the user should select the move,
but perhaps stock.move _rec_name could be improved to better show
what the product of that move is.</li>
<li>Add RMA reception and/or RMA delivery on several steps - 2 or 3 -
like normal receptions/deliveries. It should be a separate option
inside the warehouse definition.</li>
</ul>
</div>
<div class="section" id="bug-tracker">
@@ -527,6 +530,7 @@ If you spotted it first, help us to smash it by providing a detailed and welcome
<li>Antoni Marroig &lt;<a class="reference external" href="mailto:amarroig&#64;apsl.net">amarroig&#64;apsl.net</a>&gt;</li>
</ul>
</li>
<li>Michael Tietz (MT Software) <a class="reference external" href="mailto:mtietz&#64;mt-software.de">mtietz&#64;mt-software.de</a></li>
</ul>
</div>
<div class="section" id="maintainers">

View File

@@ -1,24 +1,20 @@
# Copyright 2020 Tecnativa - Ernesto Tejeda
# Copyright 2023 Michael Tietz (MT Software) <mtietz@mt-software.de>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo.exceptions import UserError, ValidationError
from odoo.tests import Form, TransactionCase, new_test_user, users
from odoo.tests import Form, new_test_user, users
from odoo.tools import mute_logger
from odoo.addons.base.tests.common import BaseCommon
from .. import hooks
class TestRma(TransactionCase):
class TestRma(BaseCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env = cls.env(
context=dict(
cls.env.context,
mail_create_nolog=True,
mail_create_nosubscribe=True,
mail_notrack=True,
no_reset_password=True,
tracking_disable=True,
)
)
cls.user_rma = new_test_user(
cls.env,
login="user_rma",
@@ -72,6 +68,7 @@ class TestRma(TransactionCase):
{"name": "[Test] It's out of warranty. To be scrapped"}
)
cls.env.ref("rma.group_rma_manual_finalization").users |= cls.env.user
cls.warehouse = cls.env.ref("stock.warehouse0")
# Ensure grouping
cls.env.company.rma_return_grouping = True
@@ -95,7 +92,7 @@ class TestRma(TransactionCase):
rma = self._create_rma(partner, product, qty, location)
rma.action_confirm()
rma.reception_move_id.quantity = rma.product_uom_qty
rma.reception_move_id.picking_id._action_done()
rma.reception_move_id.picking_id.button_validate()
return rma
def _create_delivery(self):
@@ -132,6 +129,49 @@ class TestRma(TransactionCase):
class TestRmaCase(TestRma):
def test_post_init_hook(self):
warehouse = self.env["stock.warehouse"].create(
{
"name": "Test warehouse",
"code": "code",
"company_id": self.env.company.id,
}
)
hooks.post_init_hook(self.env)
self.assertTrue(warehouse.rma_in_type_id)
self.assertEqual(
warehouse.rma_in_type_id.default_location_dest_id, warehouse.rma_loc_id
)
self.assertEqual(
warehouse.rma_out_type_id.default_location_src_id, warehouse.rma_loc_id
)
self.assertTrue(warehouse.rma_loc_id)
self.assertTrue(warehouse.rma_in_route_id)
self.assertTrue(warehouse.rma_out_route_id)
def test_rma_replace_pick_ship(self):
self.warehouse.write({"delivery_steps": "pick_ship"})
rma = self._create_rma(self.partner, self.product, 1, self.rma_loc)
rma.action_confirm()
rma.reception_move_id.quantity = 1
rma.reception_move_id.picking_id.button_validate()
self.assertEqual(rma.reception_move_id.picking_id.state, "done")
self.assertEqual(rma.state, "received")
res = rma.action_replace()
wizard_form = Form(self.env[res["res_model"]].with_context(**res["context"]))
wizard_form.product_id = self.product
wizard_form.product_uom_qty = rma.product_uom_qty
wizard = wizard_form.save()
wizard.action_deliver()
self.assertEqual(rma.delivery_picking_count, 2)
out_pickings = rma.mapped("delivery_move_ids.picking_id")
self.assertIn(
self.warehouse.pick_type_id, out_pickings.mapped("picking_type_id")
)
self.assertIn(
self.warehouse.out_type_id, out_pickings.mapped("picking_type_id")
)
def test_computed(self):
# If partner changes, the invoice address is set
rma = self.env["rma"].new()
@@ -169,7 +209,7 @@ class TestRmaCase(TestRma):
move.product_id = product_2
move.product_uom_qty = 15
picking = picking_form.save()
picking._action_done()
picking.button_validate()
rma.picking_id = picking
rma.move_id = picking.move_ids
self.assertEqual(rma.product_id, product_2)
@@ -207,13 +247,18 @@ class TestRmaCase(TestRma):
self.assertEqual(rma.state, "confirmed")
rma.reception_move_id.quantity = 9
with self.assertRaises(ValidationError):
rma.reception_move_id.picking_id._action_done()
res = rma.reception_move_id.picking_id.button_validate()
wizard = (
self.env[res["res_model"]].with_context(**res["context"]).create({})
)
wizard.process()
rma.reception_move_id.quantity = 10
rma.reception_move_id.picking_id._action_done()
rma.reception_move_id.picking_id.button_validate()
self.assertEqual(rma.reception_move_id.picking_id.state, "done")
self.assertEqual(rma.reception_move_id.quantity, 10)
self.assertEqual(rma.state, "received")
@mute_logger("odoo.models.unlink")
def test_cancel(self):
# cancel a draft RMA
rma = self._create_rma(self.partner, self.product)
@@ -333,7 +378,7 @@ class TestRmaCase(TestRma):
# line of refund_1
self.assertEqual(len(refund_1.invoice_line_ids), 3)
self.assertEqual(
refund_1.invoice_line_ids.mapped("rma_id"),
refund_1.invoice_line_ids.rma_id,
(rma_1 | rma_2 | rma_3),
)
self.assertEqual(
@@ -524,6 +569,13 @@ class TestRmaCase(TestRma):
all_rmas = rma_1 | rma_2 | rma_3 | rma_4
self.assertEqual(all_rmas.mapped("state"), ["received"] * 4)
self.assertEqual(all_rmas.mapped("can_be_returned"), [True] * 4)
all_in_pickings = all_rmas.mapped("reception_move_id.picking_id")
self.assertEqual(
all_in_pickings.mapped("picking_type_id"), self.warehouse.rma_in_type_id
)
self.assertEqual(
all_in_pickings.mapped("location_dest_id"), self.warehouse.rma_loc_id
)
# Mass return of those four RMAs
delivery_wizard = (
self.env["rma.delivery.wizard"]
@@ -534,6 +586,10 @@ class TestRmaCase(TestRma):
# Two pickings were created
pick_1 = (rma_1 | rma_2 | rma_3).mapped("delivery_move_ids.picking_id")
pick_2 = rma_4.delivery_move_ids.picking_id
self.assertEqual(pick_1.picking_type_id, self.warehouse.rma_out_type_id)
self.assertEqual(pick_1.location_id, self.warehouse.rma_loc_id)
self.assertEqual(pick_2.picking_type_id, self.warehouse.rma_out_type_id)
self.assertEqual(pick_2.location_id, self.warehouse.rma_loc_id)
self.assertEqual(len(pick_1), 1)
self.assertEqual(len(pick_2), 1)
self.assertNotEqual(pick_1, pick_2)
@@ -549,7 +605,7 @@ class TestRmaCase(TestRma):
# line of picking_1
self.assertEqual(len(pick_1.move_ids), 3)
self.assertEqual(
pick_1.move_ids.mapped("rma_id"),
pick_1.move_ids.rma_id,
(rma_1 | rma_2 | rma_3),
)
self.assertEqual(
@@ -620,14 +676,14 @@ class TestRmaCase(TestRma):
origin_moves = origin_delivery.move_ids
self.assertTrue(origin_moves[0].rma_ids)
self.assertTrue(origin_moves[1].rma_ids)
rmas = origin_moves.mapped("rma_ids")
rmas = origin_moves.rma_ids
self.assertEqual(rmas.mapped("state"), ["confirmed"] * 2)
# Each reception move is linked one of the generated RMAs
reception = self.env["stock.picking"].browse(picking_action["res_id"])
reception_moves = reception.move_ids
self.assertTrue(reception_moves[0].rma_receiver_ids)
self.assertTrue(reception_moves[1].rma_receiver_ids)
self.assertEqual(reception_moves.mapped("rma_receiver_ids"), rmas)
self.assertEqual(reception_moves.rma_receiver_ids, rmas)
# Validate the reception picking to set rmas to 'received' state
reception_moves[0].quantity = reception_moves[0].product_uom_qty
reception_moves[1].quantity = reception_moves[1].product_uom_qty
@@ -645,7 +701,7 @@ class TestRmaCase(TestRma):
rma = rma_form.save()
rma.action_confirm()
rma.reception_move_id.quantity = 10
rma.reception_move_id.picking_id._action_done()
rma.reception_move_id.picking_id.button_validate()
# Return quantity 4 of the same product to the customer
delivery_form = Form(
self.env["rma.delivery.wizard"].with_context(
@@ -686,6 +742,7 @@ class TestRmaCase(TestRma):
self.assertEqual(new_rma.move_id.quantity, 10)
self.assertEqual(new_rma.reception_move_id.quantity, 10)
@mute_logger("odoo.models.unlink")
def test_rma_to_receive_on_delete_invoice(self):
rma = self._create_confirm_receive(self.partner, self.product, 10, self.rma_loc)
rma.action_refund()

View File

@@ -1,8 +1,25 @@
# Copyright 2020 Tecnativa - Ernesto Tejeda
# Copyright 2023 Michael Tietz (MT Software) <mtietz@mt-software.de>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from copy import deepcopy
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
from odoo.tools import float_compare
class ReturnPickingLine(models.TransientModel):
_inherit = "stock.return.picking.line"
def _prepare_rma_vals(self):
self.ensure_one()
return {
"move_id": self.move_id.id,
"product_id": self.move_id.product_id.id,
"product_uom_qty": self.quantity,
"product_uom": self.product_id.uom_id.id,
"location_id": self.wizard_id.location_id.id or self.move_id.location_id.id,
}
class ReturnPicking(models.TransientModel):
@@ -48,6 +65,52 @@ class ReturnPicking(models.TransientModel):
location_id = return_picking_type.default_location_dest_id.id
self.location_id = location_id
def _prepare_rma_partner_values(self):
self.ensure_one()
partner = self.picking_id.partner_id
partner_address = partner.address_get(["invoice", "delivery"])
partner_invoice_id = partner_address.get("invoice", False)
partner_shipping_id = partner_address.get("delivery", False)
return (
partner,
partner_invoice_id and partner.browse(partner_invoice_id) or partner,
partner_shipping_id and partner.browse(partner_shipping_id) or partner,
)
def _prepare_rma_vals(self):
partner, partner_invoice, partner_shipping = self._prepare_rma_partner_values()
origin = self.picking_id.name
vals = self.env["rma"]._prepare_procurement_group_vals()
vals["partner_id"] = partner_shipping.id
vals["name"] = origin
group = self.env["procurement.group"].create(vals)
return {
"user_id": self.env.user.id,
"partner_id": partner.id,
"partner_shipping_id": partner_shipping.id,
"partner_invoice_id": partner_invoice.id,
"origin": origin,
"picking_id": self.picking_id.id,
"company_id": self.company_id.id,
"procurement_group_id": group.id,
}
def _prepare_rma_vals_list(self):
vals_list = []
for return_picking in self:
global_vals = return_picking._prepare_rma_vals()
for line in return_picking.product_return_moves:
if (
not line.move_id
or float_compare(line.quantity, 0, line.product_id.uom_id.rounding)
<= 0
):
continue
vals = deepcopy(global_vals)
vals.update(line._prepare_rma_vals())
vals_list.append(vals)
return vals_list
def create_returns(self):
"""Override create_returns method for creating one or more
'confirmed' RMAs after return a delivery picking in case
@@ -57,10 +120,6 @@ class ReturnPicking(models.TransientModel):
as the 'Receipt'.
"""
if self.create_rma:
# set_rma_picking_type is to override the copy() method of stock
# picking and change the default picking type to rma picking type
self_with_context = self.with_context(set_rma_picking_type=True)
res = super(ReturnPicking, self_with_context).create_returns()
if not self.picking_id.partner_id:
raise ValidationError(
_(
@@ -68,12 +127,30 @@ class ReturnPicking(models.TransientModel):
"'Stock Picking' from which RMAs will be created"
)
)
returned_picking = self.env["stock.picking"].browse(res["res_id"])
vals_list = [
move._prepare_return_rma_vals(self.picking_id)
for move in returned_picking.move_ids
]
self.env["rma"].create(vals_list)
return res
else:
return super().create_returns()
vals_list = self._prepare_rma_vals_list()
rmas = self.env["rma"].create(vals_list)
rmas.action_confirm()
picking = rmas.reception_move_id.picking_id
picking = picking and picking[0] or picking
ctx = dict(self.env.context)
ctx.update(
{
"default_partner_id": picking.partner_id.id,
"search_default_picking_type_id": picking.picking_type_id.id,
"search_default_draft": False,
"search_default_assigned": False,
"search_default_confirmed": False,
"search_default_ready": False,
"search_default_planning_issues": False,
"search_default_available": False,
}
)
return {
"name": _("Returned Picking"),
"view_mode": "form,tree,calendar",
"res_model": "stock.picking",
"res_id": picking.id,
"type": "ir.actions.act_window",
"context": ctx,
}
return super().create_returns()