[IMP] rma_sale: portal rma request single page view

Now it's possible to configure if the portal RMA request form is loaded
in a popup or in a single page.

In that page, we can add custom blocks (if the website is installed) a
customize the form text.

In this commit, we also add the possibility to extend the form view to allow
custom fields that will show up in the RMA description.

TT29670
This commit is contained in:
david
2021-06-22 13:24:30 +02:00
committed by Chafique
parent e74bd55be7
commit 35df5a65af
8 changed files with 371 additions and 229 deletions

View File

@@ -17,6 +17,7 @@
"views/rma_views.xml",
"views/sale_views.xml",
"views/sale_portal_template.xml",
"views/res_config_settings_views.xml",
"wizard/sale_order_rma_wizard_views.xml",
],
}

View File

@@ -30,12 +30,18 @@ class CustomerPortal(CustomerPortal):
}
# Set wizard line vals
mapped_vals = {}
custom_vals = {}
partner_shipping_id = post.pop("partner_shipping_id", False)
for name, value in post.items():
row, field_name = name.split("-", 1)
if wizard_line_field_types.get(field_name) == "many2one":
value = int(value) if value else False
mapped_vals.setdefault(row, {}).update({field_name: value})
try:
row, field_name = name.split("-", 1)
if wizard_line_field_types.get(field_name) == "many2one":
value = int(value) if value else False
mapped_vals.setdefault(row, {}).update({field_name: value})
# Catch possible form custom fields to add them to the RMA
# description values
except ValueError:
custom_vals.update({name: value})
# If no operation is filled, no RMA will be created
line_vals = [
(0, 0, vals) for vals in mapped_vals.values() if vals.get("operation_id")
@@ -43,11 +49,19 @@ class CustomerPortal(CustomerPortal):
# Create wizard an generate rmas
order = order_obj.browse(order_id).sudo()
location_id = order.warehouse_id.rma_loc_id.id
# Add custom fields text
custom_description = ""
if custom_vals:
custom_description = r"<br \>---<br \>"
custom_description += r"<br \>".join(
["{}: {}".format(x, y) for x, y in custom_vals.items()]
)
wizard = wizard_obj.with_context(active_id=order_id).create(
{
"line_ids": line_vals,
"location_id": location_id,
"partner_shipping_id": partner_shipping_id,
"custom_description": custom_description,
}
)
rma = wizard.sudo().create_rma(from_portal=True)
@@ -61,3 +75,27 @@ class CustomerPortal(CustomerPortal):
else:
route = "/my/rmas?sale_id=%d" % order_id
return request.redirect(route)
@http.route(
["/my/requestrma/<int:order_id>"], type="http", auth="public", website=True
)
def request_sale_rma(self, order_id, access_token=None, **kw):
"""Request RMA on a single page"""
try:
order_sudo = self._document_check_access(
"sale.order", order_id, access_token=access_token
)
except (AccessError, MissingError):
return request.redirect("/my")
if order_sudo.state in ("draft", "sent", "cancel"):
return request.redirect("/my")
values = {
"sale_order": order_sudo,
"page_name": "request_rma",
"default_url": order_sudo.get_portal_url(),
"token": access_token,
"partner_id": order_sudo.partner_id.id,
}
if order_sudo.company_id:
values["res_company"] = order_sudo.company_id
return request.render("rma_sale.request_rma_single_page", values)

View File

@@ -1,5 +1,6 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import res_company
from . import res_config_settings
from . import rma
from . import sale
from . import stock_move

View File

@@ -0,0 +1,13 @@
# Copyright 2021 Tecnativa - David Vidal
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import fields, models
class ResCompany(models.Model):
_inherit = "res.company"
show_full_page_sale_rma = fields.Boolean(
string="Full page RMA creation",
help="From the frontend sale order page go to a single RMA page "
"creation instead of the usual popup",
)

View File

@@ -0,0 +1,11 @@
# Copyright 2021 Tecnativa - David Vidal
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import fields, models
class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"
show_full_page_sale_rma = fields.Boolean(
related="company_id.show_full_page_sale_rma", readonly=False,
)

View File

@@ -0,0 +1,36 @@
<?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="inherit_id" ref="sale.res_config_settings_view_form" />
<field name="arch" type="xml">
<xpath
expr="//div[@data-key='sale_management']/div[hasclass('o_settings_container')]"
position="inside"
>
<div
class="col-12 col-lg-6 o_setting_box"
title="Show portal RMA request in a single page"
>
<div class="o_setting_left_pane">
<field name="show_full_page_sale_rma" />
</div>
<div class="o_setting_right_pane">
<label
for="show_full_page_sale_rma"
string="Single page RMA request"
/>
<span
class="fa fa-lg fa-building-o"
title="Values set here are company-specific."
groups="base.group_multi_company"
/>
<div class="text-muted">
When we hit the RMA request button from the portal sale page, open in a single page instead of a popup.
</div>
</div>
</div>
</xpath>
</field>
</record>
</odoo>

View File

@@ -1,4 +1,194 @@
<odoo>
<!-- Call this form via controller to add it to an independent page -->
<template id="sale_rma_request_form" name="RMA Request Form">
<form
id="form-request-rma"
method="POST"
t-attf-action="/my/orders/#{sale_order.id}/requestrma?access_token=#{sale_order.access_token}"
t-att-class="not single_page_mode and 'modal-content' or 'col-12'"
>
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()" />
<header class="modal-header" t-if="not single_page_mode">
<h4 class="modal-title">Request RMAs</h4>
<button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>&amp;times;</button>
</header>
<main
t-att-class="not single_page_mode and 'modal-body'"
id="modal-body-request-rma"
>
<div class="alert alert-info mb-2 mb-sm-1 oe_structure" role="alert">
<span>
You're about to perform an RMA request. Our team will process it an will reach you once it's validated. Keep in mind that:
<ul>
<li>Select the product quantity and the requested operation</li>
<li
>Use the comment button to add relevant information regarding the RMA, like returned serial numbers or a description of the issue</li>
<li
>If no requested operation is set, the RMA won't be correctly fulfilled</li>
<li
>You can only return as much product units as you received for this order</li>
<li
>The limit will decrease when the units in other RMAs are confirmed</li>
<li>You can send a message in every RMA sent</li>
</ul>
</span>
</div>
<t
t-set="delivery_addresses"
t-value="sale_order.partner_shipping_id | sale_order.partner_id.commercial_partner_id.mapped('child_ids').filtered(lambda x: x.type in ['contact', 'delivery'])"
/>
<button
class="btn btn-primary btn-block mb8"
type="button"
data-toggle="collapse"
data-target="#delivery_address_picker"
aria-expanded="false"
><i class="fa fa-truck" /> Choose a delivery address</button>
<div class="col-lg-12 collapse mt8" id="delivery_address_picker">
<div data-toggle="buttons" class="row">
<label
t-attf-class="card mr4 btn btn-light #{address == sale_order.partner_shipping_id and 'active' or ''}"
t-foreach="delivery_addresses"
t-as="address"
>
<input
class="d-none"
type="radio"
name="partner_shipping_id"
t-att-value="address.id"
>
<strong>
<i
t-attf-class="text-secondary fa #{address.type == 'delivery' and 'fa-truck' or 'fa-user'}"
/>
<t t-esc="address.name" />
</strong>
<pre><h6 t-esc="address.contact_address" /></pre>
</input>
</label>
</div>
</div>
<t t-set="data_list" t-value="sale_order.get_delivery_rma_data()" />
<t
t-set="operations"
t-value="sale_order.env['rma.operation'].search([])"
/>
<table class="table table-sm" id="request-rma-table">
<thead class="bg-100">
<tr>
<th class="text-left">Product</th>
<th class="text-right">Quantity</th>
<th class="text-left">Delivery</th>
<th class="text-left">Requested operation</th>
<th name="portal_rma_button_desc" />
</tr>
</thead>
<tbody class="request-rma-tbody">
<t t-foreach="data_list" t-as="data">
<t t-if="data['quantity'] > 0 and data['picking']">
<tr>
<td class="text-left">
<span t-esc="data['product'].display_name" />
<input
type="hidden"
t-attf-name="#{data_index}-product_id"
t-att-value="data['product'].id"
/>
<input
type="hidden"
t-if="data.get('sale_line_id')"
t-attf-name="#{data_index}-sale_line_id"
t-att-value="data['sale_line_id'].id"
/>
</td>
<td class="text-right">
<div id="delivery-rma-qty">
<input
type="number"
t-attf-name="#{data_index}-quantity"
class="o_input text-right"
placeholder="0"
min="0"
t-att-max="data['quantity']"
t-att-value="0"
style="max-width: 60px;"
/>
<span
t-esc="data['uom'].name"
groups="uom.group_uom"
/>
<input
type="hidden"
t-attf-name="#{data_index}-uom_id"
t-att-value="data['uom'].id"
/>
</div>
</td>
<td class="text-left">
<span
t-esc="data['picking'] and data['picking'].name"
/>
<input
type="hidden"
t-attf-name="#{data_index}-picking_id"
t-att-value="data['picking'] and data['picking'].id"
/>
</td>
<td class="text-left">
<select
t-attf-name="#{data_index}-operation_id"
class="form-control rma-operation"
>
<option value="">---</option>
<t t-foreach="operations" t-as="operation">
<option t-att-value="operation.id">
<t t-esc="operation.name" />
</option>
</t>
</select>
</td>
<td>
<button
class="btn btn-primary fa fa-comments"
type="button"
data-toggle="collapse"
t-attf-data-target="#comment-#{data_index}"
aria-expanded="false"
t-attf-aria-controls="comment-#{data_index}"
/>
</td>
</tr>
<tr class="collapse" t-attf-id="comment-#{data_index}">
<td colspan="5">
<textarea
class="form-control o_website_form_input"
t-attf-name="#{data_index}-description"
placeholder="Comment anything relevant to the return, like serial numbers, a description of the issue, etc"
/>
</td>
</tr>
</t>
</t>
</tbody>
</table>
</main>
<footer class="modal-footer">
<button
type="submit"
t-att-id="sale_order.id"
class="btn btn-primary"
><i class="fa fa-check" /> Request RMAs</button>
<button type="button" class="btn btn-danger" data-dismiss="modal"><i
class="fa fa-times"
/> Cancel</button>
</footer>
</form>
</template>
<template
id="sale_order_portal_template"
name="Request RMA"
@@ -13,236 +203,37 @@
class="list-group-item flex-grow-1"
id="li-request-rma"
>
<t t-if="sale_order.company_id.show_full_page_sale_rma">
<a
role="button"
class="btn btn-secondary btn-block mb8"
data-toggle="modal"
data-target="#modal-request-rma"
href="#"
>
role="button"
class="btn btn-secondary btn-block mb8"
t-attf-href="/my/requestrma/#{sale_order.id}"
>
<i class="fa fa-reply" /> Request RMAs
</a>
</t>
<t t-else="">
<a
role="button"
class="btn btn-secondary btn-block mb8"
data-toggle="modal"
data-target="#modal-request-rma"
href="#"
>
<i class="fa fa-reply" /> Request RMAs
</a>
</t>
</li>
</xpath>
<xpath expr="//div[@id='modaldecline']" position="after">
<div role="dialog" class="modal fade" id="modal-request-rma">
<div
role="dialog"
class="modal fade"
id="modal-request-rma"
t-if="not sale_order.company_id.show_full_page_sale_rma"
>
<div class="modal-dialog" style="max-width: 1000px;">
<form
id="form-request-rma"
method="POST"
t-attf-action="/my/orders/#{sale_order.id}/requestrma?access_token=#{sale_order.access_token}"
class="modal-content"
>
<input
type="hidden"
name="csrf_token"
t-att-value="request.csrf_token()"
/>
<header class="modal-header">
<h4 class="modal-title">Request RMAs</h4>
<button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>&amp;times;</button>
</header>
<main class="modal-body" id="modal-body-request-rma">
<div class="alert alert-info mb-2 mb-sm-1" role="alert">
<span>
You're about to perform an RMA request. Our team will process it an will reach you once it's validated. Keep in mind that:
<ul>
<li
>Select the product quantity and the requested operation</li>
<li
>Use the comment button to add relevant information regarding the RMA, like returned serial numbers or a description of the issue</li>
<li
>If no requested operation is set, the RMA won't be correctly fulfilled</li>
<li
>You can only return as much product units as you received for this order</li>
<li
>The limit will decrease when the units in other RMAs are confirmed</li>
<li
>You can send a message in every RMA sent</li>
</ul>
</span>
</div>
<t
t-set="delivery_addresses"
t-value="sale_order.partner_id.commercial_partner_id.mapped('child_ids').filtered(lambda x: x.type in ['contact', 'delivery'])"
/>
<button
class="btn btn-primary btn-block mb8"
type="button"
data-toggle="collapse"
data-target="#delivery_address_picker"
aria-expanded="false"
><i
class="fa fa-truck"
/> Choose a delivery address</button>
<div
class="col-lg-12 collapse mt8"
id="delivery_address_picker"
>
<div data-toggle="buttons" class="row">
<label
t-attf-class="card mr4 btn btn-light"
t-foreach="delivery_addresses"
t-as="address"
>
<input
class="d-none"
type="radio"
name="partner_shipping_id"
t-att-value="address.id"
>
<strong>
<i
t-attf-class="text-secondary fa #{address.type == 'delivery' and 'fa-truck' or 'fa-user'}"
/>
<t t-esc="address.name" />
</strong>
<pre><h6
t-esc="address.contact_address"
/></pre>
</input>
</label>
</div>
</div>
<t
t-set="data_list"
t-value="sale_order.get_delivery_rma_data()"
/>
<t
t-set="operations"
t-value="sale_order.env['rma.operation'].search([])"
/>
<table class="table table-sm" id="request-rma-table">
<thead class="bg-100">
<tr>
<th class="text-left">Product</th>
<th class="text-right">Quantity</th>
<th class="text-left">Delivery</th>
<th class="text-left">Requested operation</th>
<th name="portal_rma_button_desc" />
</tr>
</thead>
<tbody class="request-rma-tbody">
<t t-foreach="data_list" t-as="data">
<t
t-if="data['quantity'] > 0 and data['picking']"
>
<tr>
<td class="text-left">
<span
t-esc="data['product'].display_name"
/>
<input
type="hidden"
t-attf-name="#{data_index}-product_id"
t-att-value="data['product'].id"
/>
<input
type="hidden"
t-if="data.get('sale_line_id')"
t-attf-name="#{data_index}-sale_line_id"
t-att-value="data['sale_line_id'].id"
/>
</td>
<td class="text-right">
<div id="delivery-rma-qty">
<input
type="number"
t-attf-name="#{data_index}-quantity"
class="o_input text-right"
placeholder="0"
min="0"
t-att-max="data['quantity']"
t-att-value="0"
style="max-width: 60px;"
/>
<span
t-esc="data['uom'].name"
groups="uom.group_uom"
/>
<input
type="hidden"
t-attf-name="#{data_index}-uom_id"
t-att-value="data['uom'].id"
/>
</div>
</td>
<td class="text-left">
<span
t-esc="data['picking'] and data['picking'].name"
/>
<input
type="hidden"
t-attf-name="#{data_index}-picking_id"
t-att-value="data['picking'] and data['picking'].id"
/>
</td>
<td class="text-left">
<select
t-attf-name="#{data_index}-operation_id"
class="form-control rma-operation"
>
<option value="">---</option>
<t
t-foreach="operations"
t-as="operation"
>
<option
t-att-value="operation.id"
>
<t
t-esc="operation.name"
/>
</option>
</t>
</select>
</td>
<td>
<button
class="btn btn-primary fa fa-comments"
type="button"
data-toggle="collapse"
t-attf-data-target="#comment-#{data_index}"
aria-expanded="false"
t-attf-aria-controls="comment-#{data_index}"
/>
</td>
</tr>
<tr
class="collapse"
t-attf-id="comment-#{data_index}"
>
<td colspan="5">
<textarea
class="form-control o_website_form_input"
t-attf-name="#{data_index}-description"
placeholder="Comment anything relevant to the return, like serial numbers, a description of the issue, etc"
/>
</td>
</tr>
</t>
</t>
</tbody>
</table>
</main>
<footer class="modal-footer">
<button
type="submit"
t-att-id="sale_order.id"
class="btn btn-primary"
><i class="fa fa-check" /> Request RMAs</button>
<button
type="button"
class="btn btn-danger"
data-dismiss="modal"
><i class="fa fa-times" /> Cancel</button>
</footer>
</form>
<t t-call="rma_sale.sale_rma_request_form" />
</div>
</div>
</xpath>
@@ -288,4 +279,49 @@
</div>
</xpath>
</template>
<!-- Request RMA single view. When the website is installed, we can customize it adding blocks -->
<template id="request_rma_single_page" name="Request RMA (single page)">
<t t-call="portal.portal_layout">
<t t-call="portal.portal_record_layout">
<t t-set="card_header">
<h5 class="mb-0">
<span>
RMA request for order <t t-esc="sale_order.name" />
</span>
</h5>
</t>
<t t-set="card_body">
<div class="oe_structure" id="sale_rma_request_top_hook" />
<div id="request_form">
<div class="row">
<t t-call="rma_sale.sale_rma_request_form">
<t t-set="single_page_mode" t-value="True" />
</t>
</div>
</div>
<div class="oe_structure" id="sale_rma_request_bottom_hook" />
</t>
</t>
</t>
</template>
<!-- This way we can go back to the origin sale order easily -->
<template
id="portal_my_home_menu_sale"
inherit_id="sale.portal_my_home_menu_sale"
priority="99"
>
<xpath expr="//li[@t-if='sale_order']" position="before">
<t t-if="page_name != 'request_rma'" name="sale_breadcrumb" />
<t t-else="">
<li class="breadcrumb-item active">
<a t-if="sale_order" t-att-href="default_url"><t
t-esc="sale_order.name"
/></a>
</li>
</t>
</xpath>
<xpath expr="//t[@name='sale_breadcrumb']" position="inside">
<xpath expr="//li[@t-if='sale_order']" position="move" />
</xpath>
</template>
</odoo>

View File

@@ -37,6 +37,9 @@ class SaleOrderRmaWizard(models.TransientModel):
string="Shipping Address",
help="Will be used to return the goods when the RMA is completed",
)
custom_description = fields.Text(
help="Values coming from portal RMA request form custom fields",
)
def create_rma(self, from_portal=None):
self.ensure_one()
@@ -162,6 +165,9 @@ class SaleOrderLineRmaWizard(models.TransientModel):
partner_shipping = (
self.wizard_id.partner_shipping_id or self.order_id.partner_shipping_id
)
description = (self.description or "") + (
self.wizard_id.custom_description or ""
)
return {
"partner_id": self.order_id.partner_id.id,
"partner_invoice_id": self.order_id.partner_invoice_id.id,
@@ -176,5 +182,5 @@ class SaleOrderLineRmaWizard(models.TransientModel):
"product_uom_qty": self.quantity,
"product_uom": self.uom_id.id,
"operation_id": self.operation_id.id,
"description": self.description,
"description": description,
}