mirror of
https://github.com/OCA/rma.git
synced 2025-02-16 17:11:47 +02:00
@@ -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>
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
13
rma_sale/models/res_company.py
Normal file
13
rma_sale/models/res_company.py
Normal 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",
|
||||
)
|
||||
11
rma_sale/models/res_config_settings.py
Normal file
11
rma_sale/models/res_config_settings.py
Normal 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,
|
||||
)
|
||||
36
rma_sale/views/res_config_settings_views.xml
Normal file
36
rma_sale/views/res_config_settings_views.xml
Normal 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>
|
||||
@@ -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"
|
||||
>&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"
|
||||
>&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>
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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="data['quantity'] > 0 and data['picking']"]/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']"
|
||||
|
||||
Reference in New Issue
Block a user