Merge PR #231 into 13.0

Signed-off-by pedrobaeza
This commit is contained in:
OCA-git-bot
2021-06-29 15:36:11 +00:00
19 changed files with 663 additions and 243 deletions

View File

@@ -8,7 +8,7 @@
<field name="description">RMA in draft state</field>
</record>
<record id="mt_rma_notification" model="mail.message.subtype">
<field name="name">RMA Notificatoin</field>
<field name="name">RMA Notification</field>
<field name="res_model">rma</field>
<field name="default" eval="False" />
<field name="description">RMA automatic customer notifications</field>
@@ -59,4 +59,64 @@
</div>
</field>
</record>
<!--RMA receipt confirmation email template -->
<record id="mail_template_rma_receipt_notification" model="mail.template">
<field name="name">RMA Receipt Notification</field>
<field name="model_id" ref="model_rma" />
<field name="email_from">${object.user_id.email_formatted |safe}</field>
<field name="partner_to">${object.partner_id.id}</field>
<field
name="subject"
>${object.company_id.name} RMA (Ref ${object.name or 'n/a' }) products received</field>
<field name="report_template" ref="report_rma_action" />
<field name="report_name">${(object.name or '')}</field>
<field name="lang">${object.partner_id.lang}</field>
<field name="user_signature" eval="True" />
<field name="auto_delete" eval="True" />
<field name="body_html" type="html">
<div style="margin: 0px; padding: 0px;">
<p style="margin: 0px; padding: 0px; font-size: 13px;">
Dear ${object.partner_id.name}
% if object.partner_id.parent_id:
(${object.partner_id.parent_id.name})
% endif
<br /><br />
The products for your RMA <strong>${object.name}</strong>
from ${object.company_id.name} have been received in our warehouse.
<br /><br />
Do not hesitate to contact us if you have any question.
</p>
</div>
</field>
</record>
<record id="mail_template_rma_draft_notification" model="mail.template">
<field name="name">RMA Draft Notification</field>
<field name="model_id" ref="model_rma" />
<field name="email_from">${object.user_id.email_formatted |safe}</field>
<field name="partner_to">${object.partner_id.id}</field>
<field
name="subject"
>${object.company_id.name} Your RMA has been succesfully created (Ref ${object.name or 'n/a' })</field>
<field name="report_template" ref="report_rma_action" />
<field name="report_name">${(object.name or '')}</field>
<field name="lang">${object.partner_id.lang}</field>
<field name="user_signature" eval="True" />
<field name="auto_delete" eval="True" />
<field name="body_html" type="html">
<div style="margin: 0px; padding: 0px;">
<p style="margin: 0px; padding: 0px; font-size: 13px;">
Dear ${object.partner_id.name}
% if object.partner_id.parent_id:
(${object.partner_id.parent_id.name})
% endif
<br /><br />
You've succesfully placed your RMA <strong>${object.name}</strong>
on ${object.company_id.name}. Our team will check it and will validate
it as soon as possible.
<br /><br />
Do not hesitate to contact us if you have any question.
</p>
</div>
</field>
</record>
</data>

View File

@@ -13,11 +13,32 @@ class Company(models.Model):
except ValueError:
return False
def _default_rma_mail_receipt_template(self):
try:
return self.env.ref("rma.mail_template_rma_receipt_notification").id
except ValueError:
return False
def _default_rma_mail_draft_template(self):
try:
return self.env.ref("rma.mail_template_rma_draft_notification").id
except ValueError:
return False
send_rma_confirmation = fields.Boolean(
string="Send RMA Confirmation",
help="When the delivery is confirmed, send a confirmation email "
"to the customer.",
)
send_rma_receipt_confirmation = fields.Boolean(
string="Send RMA Receipt Confirmation",
help="When the RMA receipt is confirmed, send a confirmation email "
"to the customer.",
)
send_rma_draft_confirmation = fields.Boolean(
string="Send RMA draft Confirmation",
help="When a customer places an RMA, send a notification with it",
)
rma_mail_confirmation_template_id = fields.Many2one(
comodel_name="mail.template",
string="Email Template confirmation for RMA",
@@ -25,6 +46,20 @@ class Company(models.Model):
default=_default_rma_mail_confirmation_template,
help="Email sent to the customer once the RMA is confirmed.",
)
rma_mail_receipt_confirmation_template_id = fields.Many2one(
comodel_name="mail.template",
string="Email Template receipt confirmation for RMA",
domain="[('model', '=', 'rma')]",
default=_default_rma_mail_receipt_template,
help="Email sent to the customer once the RMA products are received.",
)
rma_mail_draft_confirmation_template_id = fields.Many2one(
comodel_name="mail.template",
string="Email Template draft notification for RMA",
domain="[('model', '=', 'rma')]",
default=_default_rma_mail_draft_template,
help="Email sent to the customer when they place " "an RMA from the portal",
)
@api.model
def create(self, vals):

View File

@@ -12,3 +12,15 @@ class ResConfigSettings(models.TransientModel):
rma_mail_confirmation_template_id = fields.Many2one(
related="company_id.rma_mail_confirmation_template_id", readonly=False,
)
send_rma_receipt_confirmation = fields.Boolean(
related="company_id.send_rma_receipt_confirmation", readonly=False,
)
rma_mail_receipt_confirmation_template_id = fields.Many2one(
related="company_id.rma_mail_receipt_confirmation_template_id", readonly=False,
)
send_rma_draft_confirmation = fields.Boolean(
related="company_id.send_rma_draft_confirmation", readonly=False,
)
rma_mail_draft_confirmation_template_id = fields.Many2one(
related="company_id.rma_mail_draft_confirmation_template_id", readonly=False,
)

View File

@@ -493,7 +493,13 @@ class Rma(models.Model):
# Assign a default team_id which will be the first in the sequence
if "team_id" not in vals:
vals["team_id"] = self.env["rma.team"].search([], limit=1).id
return super().create(vals_list)
rmas = super().create(vals_list)
# Send acknowledge when the RMA is created from the portal and the
# company has the proper setting active. This context is set by the
# `rma_sale` module.
if self.env.context.get("from_portal"):
rmas._send_draft_email()
return rmas
def copy(self, default=None):
team = super().copy(default)
@@ -511,6 +517,16 @@ class Rma(models.Model):
)
return super().unlink()
def _send_draft_email(self):
"""Send customer notifications they place the RMA from the portal"""
for rma in self.filtered("company_id.send_rma_draft_confirmation"):
rma_template_id = rma.company_id.rma_mail_draft_confirmation_template_id.id
rma.with_context(
force_send=True,
mark_rma_as_sent=True,
default_subtype_id=self.env.ref("rma.mt_rma_notification").id,
).message_post_with_template(rma_template_id)
def _send_confirmation_email(self):
"""Auto send notifications"""
for rma in self.filtered(lambda p: p.company_id.send_rma_confirmation):
@@ -521,6 +537,18 @@ class Rma(models.Model):
default_subtype_id=self.env.ref("rma.mt_rma_notification").id,
).message_post_with_template(rma_template_id)
def _send_receipt_confirmation_email(self):
"""Send customer notifications when the products are received"""
for rma in self.filtered("company_id.send_rma_receipt_confirmation"):
rma_template_id = (
rma.company_id.rma_mail_receipt_confirmation_template_id.id
)
rma.with_context(
force_send=True,
mark_rma_as_sent=True,
default_subtype_id=self.env.ref("rma.mt_rma_notification").id,
).message_post_with_template(rma_template_id)
# Action methods
def action_rma_send(self):
self.ensure_one()
@@ -1217,6 +1245,16 @@ class Rma(models.Model):
return "RMA Report - %s" % self.name
# Other business methods
def update_received_state_on_reception(self):
""" Invoked by:
[stock.move]._action_done
Here we can attach methods to trigger when the customer products
are received on the RMA location, such as automatic notifications
"""
self.write({"state": "received"})
self._send_receipt_confirmation_email()
def update_received_state(self):
""" Invoked by:
[stock.move].unlink

View File

@@ -13,6 +13,9 @@ class RmaTag(models.Model):
help="The active field allows you to hide the category without " "removing it.",
)
name = fields.Char(string="Tag Name", required=True, translate=True, copy=False,)
is_public = fields.Boolean(
string="Public Tag", help="The tag is visible in the portal view",
)
color = fields.Integer(string="Color Index")
rma_ids = fields.Many2many(comodel_name="rma")

View File

@@ -68,7 +68,7 @@ class StockMove(models.Model):
.mapped("rma_receiver_ids")
.filtered(lambda r: r.state == "confirmed")
)
to_be_received.write({"state": "received"})
to_be_received.update_received_state_on_reception()
# Set RMAs as delivered
move_done.mapped("rma_id").update_replaced_state()
move_done.mapped("rma_id").update_returned_state()

View File

@@ -665,19 +665,50 @@ class TestRma(SavepointCase):
self.assertEqual(rma.product_id.qty_available, 0)
def test_autoconfirm_email(self):
rma = self._create_rma(self.partner, self.product, 10, self.rma_loc)
rma.company_id.send_rma_confirmation = True
rma.company_id.rma_mail_confirmation_template_id = self.env.ref(
self.company.send_rma_confirmation = True
self.company.send_rma_receipt_confirmation = True
self.company.send_rma_draft_confirmation = True
self.company.rma_mail_confirmation_template_id = self.env.ref(
"rma.mail_template_rma_notification"
)
self.company.rma_mail_receipt_confirmation_template_id = self.env.ref(
"rma.mail_template_rma_receipt_notification"
)
self.company.rma_mail_draft_confirmation_template_id = self.env.ref(
"rma.mail_template_rma_draft_notification"
)
previous_mails = self.env["mail.mail"].search(
[("partner_ids", "in", self.partner.ids)]
)
self.assertFalse(previous_mails)
rma.action_confirm()
mail = self.env["mail.message"].search(
# Force the context to mock an RMA created from the portal, which is
# feature that we get on `rma_sale`. We drop it after the RMA creation
# to avoid uncontrolled side effects
ctx = self.env.context
self.env.context = dict(ctx, from_portal=True)
rma = self._create_rma(self.partner, self.product, 10, self.rma_loc)
self.env.context = ctx
mail_draft = self.env["mail.message"].search(
[("partner_ids", "in", self.partner.ids)]
)
self.assertTrue(rma.name in mail.subject)
self.assertTrue(rma.name in mail.body)
self.assertEqual(self.env.ref("rma.mt_rma_notification"), mail.subtype_id)
rma.action_confirm()
mail_confirm = (
self.env["mail.message"].search([("partner_ids", "in", self.partner.ids)])
- mail_draft
)
self.assertTrue(rma.name in mail_confirm.subject)
self.assertTrue(rma.name in mail_confirm.body)
self.assertEqual(
self.env.ref("rma.mt_rma_notification"), mail_confirm.subtype_id
)
# Now we'll confirm the incoming goods picking and the automatic
# reception notification should be sent
rma.reception_move_id.quantity_done = rma.product_uom_qty
rma.reception_move_id.picking_id.button_validate()
mail_receipt = (
self.env["mail.message"].search([("partner_ids", "in", self.partner.ids)])
- mail_draft
- mail_confirm
)
self.assertTrue(rma.name in mail_receipt.subject)
self.assertTrue("products received" in mail_receipt.subject)

View File

@@ -46,6 +46,82 @@
</div>
</div>
</div>
<div
class="col-12 col-lg-6 o_setting_box"
title="Send automatic RMA products reception notification to customer"
>
<div class="o_setting_left_pane">
<field name="send_rma_receipt_confirmation" />
</div>
<div class="o_setting_right_pane">
<label
for="send_rma_receipt_confirmation"
string="RMA Receipt Confirmation Email"
/>
<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 the RMA products are received, send an automatic information email.
</div>
<div
class="row mt16"
attrs="{'invisible': [('send_rma_receipt_confirmation', '=', False)]}"
>
<label
for="rma_mail_receipt_confirmation_template_id"
string="Email Template"
class="col-lg-4 o_light_label"
/>
<field
name="rma_mail_receipt_confirmation_template_id"
class="oe_inline"
attrs="{'required': [('send_rma_receipt_confirmation', '=', True)]}"
context="{'default_model': 'rma'}"
/>
</div>
</div>
</div>
<div
class="col-12 col-lg-6 o_setting_box"
title="Send automatic notification when the customer places an RMA"
>
<div class="o_setting_left_pane">
<field name="send_rma_draft_confirmation" />
</div>
<div class="o_setting_right_pane">
<label
for="send_rma_draft_confirmation"
string="RMA draft notification Email"
/>
<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 customers themselves place an RMA from the portal, send an automatic notification acknowleging it.
</div>
<div
class="row mt16"
attrs="{'invisible': [('send_rma_draft_confirmation', '=', False)]}"
>
<label
for="rma_mail_draft_confirmation_template_id"
string="Email Template"
class="col-lg-4 o_light_label"
/>
<field
name="rma_mail_draft_confirmation_template_id"
class="oe_inline"
attrs="{'required': [('send_rma_draft_confirmation', '=', True)]}"
context="{'default_model': 'rma'}"
/>
</div>
</div>
</div>
</xpath>
</field>
</record>

View File

@@ -115,6 +115,18 @@
</a>
</span>
<span class="float-right">
<!-- Tags -->
<t
t-set="tags"
t-value="rma.tag_ids.filtered('is_public')"
/>
<!-- We don't have the color o_tag_color_# classes available in the frontend -->
<t t-foreach="tags" t-as="tag">
<span
class="badge badge-pill badge-info label-text-align"
t-esc="tag.name"
/>
</t>
<t t-if="rma.state in ['refunded', 'returned', 'replaced']">
<span
class="small text-success orders_label_text_align"

View File

@@ -39,6 +39,7 @@
</div>
<group>
<field name="name" />
<field name="is_public" />
</group>
</sheet>
</form>
@@ -51,6 +52,7 @@
<field name="arch" type="xml">
<tree string="RMA Tags">
<field name="name" />
<field name="is_public" />
<field name="active" />
</tree>
</field>

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,
}

View File

@@ -3,7 +3,7 @@
<template
id="sale_order_portal_template"
name="Request RMA MRP"
inherit_id="rma_sale.sale_order_portal_template"
inherit_id="rma_sale.sale_rma_request_form"
>
<xpath expr="//input[@t-attf-name='#{data_index}-product_id']" position="after">
<input
@@ -25,6 +25,15 @@
t-att-value="1"
/>
</xpath>
<!-- The component lines are splitted on the wizard so we should set the quantity properly -->
<xpath
expr="//input[@t-attf-name='#{data_index}-quantity']"
position="attributes"
>
<attribute
name="t-att-value"
>data.get('phantom_bom_product') and data['quantity'] or 0</attribute>
</xpath>
<!-- TODO: We could give a clue about what's to be returned, with readonly detailed lines -->
<xpath
expr="//tbody[hasclass('request-rma-tbody')]//t[@t-if=&quot;data[&apos;quantity&apos;] &gt; 0 and data[&apos;picking&apos;]&quot;]/tr"
@@ -95,14 +104,14 @@
<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>
/> (<span t-field="kit_rma.product_uom_qty" />)</div>
</div>
<t
t-if="kit_rma.state in ['confirmed', 'received', 'returned', 'replaced', 'locked', 'refunded']"
>
<span class="badge badge-success label-text-align"><i
class="fa fa-fw fa-reply"
/> <t t-field="kit_rma.state" /></span>
/> <span t-field="kit_rma.state" /></span>
</t>
<t
t-if="kit_rma.state in ['waiting_return', 'waiting_replacement']"