[IMP] rma: black, isort, prettier

This commit is contained in:
Ernesto Tejeda
2020-10-23 15:28:05 -04:00
parent 8010b6d9f2
commit 5aef738160
37 changed files with 1883 additions and 1486 deletions

View File

@@ -10,10 +10,7 @@
"author": "Tecnativa, Odoo Community Association (OCA)", "author": "Tecnativa, Odoo Community Association (OCA)",
"maintainers": ["ernestotejeda"], "maintainers": ["ernestotejeda"],
"license": "AGPL-3", "license": "AGPL-3",
"depends": [ "depends": ["account", "stock",],
"account",
"stock",
],
"data": [ "data": [
"views/report_rma.xml", "views/report_rma.xml",
"report/report.xml", "report/report.xml",
@@ -32,6 +29,6 @@
"views/stock_picking_views.xml", "views/stock_picking_views.xml",
"views/stock_warehouse_views.xml", "views/stock_warehouse_views.xml",
], ],
'post_init_hook': 'post_init_hook', "post_init_hook": "post_init_hook",
"application": True, "application": True,
} }

View File

@@ -4,54 +4,53 @@
from odoo import _, exceptions, http from odoo import _, exceptions, http
from odoo.exceptions import AccessError, MissingError from odoo.exceptions import AccessError, MissingError
from odoo.http import request from odoo.http import request
from odoo.addons.portal.controllers.portal import CustomerPortal,\
pager as portal_pager
from odoo.tools import consteq from odoo.tools import consteq
from odoo.addons.portal.controllers.portal import CustomerPortal, pager as portal_pager
class PortalRma(CustomerPortal): class PortalRma(CustomerPortal):
def _prepare_portal_layout_values(self): def _prepare_portal_layout_values(self):
values = super()._prepare_portal_layout_values() values = super()._prepare_portal_layout_values()
if request.env['rma'].check_access_rights( if request.env["rma"].check_access_rights("read", raise_exception=False):
'read', raise_exception=False): values["rma_count"] = request.env["rma"].search_count([])
values['rma_count'] = request.env['rma'].search_count([])
else: else:
values['rma_count'] = 0 values["rma_count"] = 0
return values return values
def _rma_get_page_view_values(self, rma, access_token, **kwargs): def _rma_get_page_view_values(self, rma, access_token, **kwargs):
values = { values = {
'page_name': 'RMA', "page_name": "RMA",
'rma': rma, "rma": rma,
} }
return self._get_page_view_values( return self._get_page_view_values(
rma, access_token, values, 'my_rmas_history', False, **kwargs) rma, access_token, values, "my_rmas_history", False, **kwargs
)
def _get_filter_domain(self, kw): def _get_filter_domain(self, kw):
return [] return []
@http.route(['/my/rmas', '/my/rmas/page/<int:page>'], @http.route(
type='http', auth="user", website=True) ["/my/rmas", "/my/rmas/page/<int:page>"], type="http", auth="user", website=True
def portal_my_rmas(self, page=1, date_begin=None, date_end=None, )
sortby=None, **kw): def portal_my_rmas(self, page=1, date_begin=None, date_end=None, sortby=None, **kw):
values = self._prepare_portal_layout_values() values = self._prepare_portal_layout_values()
rma_obj = request.env['rma'] rma_obj = request.env["rma"]
domain = self._get_filter_domain(kw) domain = self._get_filter_domain(kw)
searchbar_sortings = { searchbar_sortings = {
'date': {'label': _('Date'), 'order': 'date desc'}, "date": {"label": _("Date"), "order": "date desc"},
'name': {'label': _('Name'), 'order': 'name desc'}, "name": {"label": _("Name"), "order": "name desc"},
'state': {'label': _('Status'), 'order': 'state'}, "state": {"label": _("Status"), "order": "state"},
} }
# default sort by order # default sort by order
if not sortby: if not sortby:
sortby = 'date' sortby = "date"
order = searchbar_sortings[sortby]['order'] order = searchbar_sortings[sortby]["order"]
archive_groups = self._get_archive_groups('rma', domain) archive_groups = self._get_archive_groups("rma", domain)
if date_begin and date_end: if date_begin and date_end:
domain += [ domain += [
('create_date', '>', date_begin), ("create_date", ">", date_begin),
('create_date', '<=', date_end), ("create_date", "<=", date_end),
] ]
# count for pager # count for pager
rma_count = rma_obj.search_count(domain) rma_count = rma_obj.search_count(domain)
@@ -59,77 +58,80 @@ class PortalRma(CustomerPortal):
pager = portal_pager( pager = portal_pager(
url="/my/rmas", url="/my/rmas",
url_args={ url_args={
'date_begin': date_begin, "date_begin": date_begin,
'date_end': date_end, "date_end": date_end,
'sortby': sortby, "sortby": sortby,
}, },
total=rma_count, total=rma_count,
page=page, page=page,
step=self._items_per_page step=self._items_per_page,
) )
# content according to pager and archive selected # content according to pager and archive selected
rmas = rma_obj.search( rmas = rma_obj.search(
domain, domain, order=order, limit=self._items_per_page, offset=pager["offset"]
order=order, )
limit=self._items_per_page, request.session["my_rmas_history"] = rmas.ids[:100]
offset=pager['offset'] values.update(
{
"date": date_begin,
"rmas": rmas,
"page_name": "RMA",
"pager": pager,
"archive_groups": archive_groups,
"default_url": "/my/rmas",
"searchbar_sortings": searchbar_sortings,
"sortby": sortby,
}
) )
request.session['my_rmas_history'] = rmas.ids[:100]
values.update({
'date': date_begin,
'rmas': rmas,
'page_name': 'RMA',
'pager': pager,
'archive_groups': archive_groups,
'default_url': '/my/rmas',
'searchbar_sortings': searchbar_sortings,
'sortby': sortby,
})
return request.render("rma.portal_my_rmas", values) return request.render("rma.portal_my_rmas", values)
@http.route(['/my/rmas/<int:rma_id>'], @http.route(["/my/rmas/<int:rma_id>"], type="http", auth="public", website=True)
type='http', auth="public", website=True) def portal_my_rma_detail(
def portal_my_rma_detail(self, rma_id, access_token=None, self, rma_id, access_token=None, report_type=None, download=False, **kw
report_type=None, download=False, **kw): ):
try: try:
rma_sudo = self._document_check_access('rma', rma_id, access_token) rma_sudo = self._document_check_access("rma", rma_id, access_token)
except (AccessError, MissingError): except (AccessError, MissingError):
return request.redirect('/my') return request.redirect("/my")
if report_type in ('html', 'pdf', 'text'): if report_type in ("html", "pdf", "text"):
return self._show_report( return self._show_report(
model=rma_sudo, model=rma_sudo,
report_type=report_type, report_type=report_type,
report_ref='rma.report_rma_action', report_ref="rma.report_rma_action",
download=download, download=download,
) )
values = self._rma_get_page_view_values(rma_sudo, access_token, **kw) values = self._rma_get_page_view_values(rma_sudo, access_token, **kw)
return request.render("rma.portal_rma_page", values) return request.render("rma.portal_rma_page", values)
@http.route(['/my/rma/picking/pdf/<int:rma_id>/<int:picking_id>'], @http.route(
type='http', auth="public", website=True) ["/my/rma/picking/pdf/<int:rma_id>/<int:picking_id>"],
def portal_my_rma_picking_report(self, rma_id, picking_id, type="http",
access_token=None, **kw): auth="public",
website=True,
)
def portal_my_rma_picking_report(self, rma_id, picking_id, access_token=None, **kw):
try: try:
picking_sudo = self._picking_check_access( picking_sudo = self._picking_check_access(
rma_id, picking_id, access_token=access_token) rma_id, picking_id, access_token=access_token
)
except exceptions.AccessError: except exceptions.AccessError:
return request.redirect('/my') return request.redirect("/my")
report_sudo = request.env.ref('stock.action_report_delivery').sudo() report_sudo = request.env.ref("stock.action_report_delivery").sudo()
pdf = report_sudo.render_qweb_pdf([picking_sudo.id])[0] pdf = report_sudo.render_qweb_pdf([picking_sudo.id])[0]
pdfhttpheaders = [ pdfhttpheaders = [
('Content-Type', 'application/pdf'), ("Content-Type", "application/pdf"),
('Content-Length', len(pdf)), ("Content-Length", len(pdf)),
] ]
return request.make_response(pdf, headers=pdfhttpheaders) return request.make_response(pdf, headers=pdfhttpheaders)
def _picking_check_access(self, rma_id, picking_id, access_token=None): def _picking_check_access(self, rma_id, picking_id, access_token=None):
rma = request.env['rma'].browse([rma_id]) rma = request.env["rma"].browse([rma_id])
picking = request.env['stock.picking'].browse([picking_id]) picking = request.env["stock.picking"].browse([picking_id])
picking_sudo = picking.sudo() picking_sudo = picking.sudo()
try: try:
picking.check_access_rights('read') picking.check_access_rights("read")
picking.check_access_rule('read') picking.check_access_rule("read")
except exceptions.AccessError: except exceptions.AccessError:
if not access_token or not consteq(rma.access_token, access_token): if not access_token or not consteq(rma.access_token, access_token):
raise raise

View File

@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<data noupdate="1"> <data noupdate="1">
<!-- rma-related subtypes for messaging / Chatter --> <!-- rma-related subtypes for messaging / Chatter -->
<record id="mt_rma_draft" model="mail.message.subtype"> <record id="mt_rma_draft" model="mail.message.subtype">
<field name="name">Draft RMA</field> <field name="name">Draft RMA</field>
<field name="res_model">rma</field> <field name="res_model">rma</field>
<field name="default" eval="False"/> <field name="default" eval="False" />
<field name="description">RMA in draft state</field> <field name="description">RMA in draft state</field>
</record> </record>
<!-- rma_team-related subtypes for messaging / Chatter --> <!-- rma_team-related subtypes for messaging / Chatter -->
@@ -12,25 +12,27 @@
<field name="name">Draft RMA</field> <field name="name">Draft RMA</field>
<field name="sequence">10</field> <field name="sequence">10</field>
<field name="res_model">rma.team</field> <field name="res_model">rma.team</field>
<field name="default" eval="True"/> <field name="default" eval="True" />
<field name="parent_id" eval="ref('rma.mt_rma_draft')"/> <field name="parent_id" eval="ref('rma.mt_rma_draft')" />
<field name="relation_field">team_id</field> <field name="relation_field">team_id</field>
</record> </record>
<!--RMA email template --> <!--RMA email template -->
<record id="mail_template_rma_notification" model="mail.template"> <record id="mail_template_rma_notification" model="mail.template">
<field name="name">RMA Notification</field> <field name="name">RMA Notification</field>
<field name="model_id" ref="model_rma"/> <field name="model_id" ref="model_rma" />
<field name="email_from">${object.user_id.email_formatted |safe}</field> <field name="email_from">${object.user_id.email_formatted |safe}</field>
<field name="partner_to">${object.partner_id.id}</field> <field name="partner_to">${object.partner_id.id}</field>
<field name="subject">${object.company_id.name} RMA (Ref ${object.name or 'n/a' })</field> <field
name="subject"
>${object.company_id.name} RMA (Ref ${object.name or 'n/a' })</field>
<field name="report_template" ref="report_rma_action" /> <field name="report_template" ref="report_rma_action" />
<field name="report_name">${(object.name or '')}</field> <field name="report_name">${(object.name or '')}</field>
<field name="lang">${object.partner_id.lang}</field> <field name="lang">${object.partner_id.lang}</field>
<field name="user_signature" eval="True"/> <field name="user_signature" eval="True" />
<field name="auto_delete" eval="True"/> <field name="auto_delete" eval="True" />
<field name="body_html" type="html"> <field name="body_html" type="html">
<div style="margin: 0px; padding: 0px;"> <div style="margin: 0px; padding: 0px;">
<p style="margin: 0px; padding: 0px; font-size: 13px;"> <p style="margin: 0px; padding: 0px; font-size: 13px;">
Dear ${object.partner_id.name} Dear ${object.partner_id.name}
% if object.partner_id.parent_id: % if object.partner_id.parent_id:
(${object.partner_id.parent_id.name}) (${object.partner_id.parent_id.name})
@@ -40,7 +42,7 @@
<br /><br /> <br /><br />
Do not hesitate to contact us if you have any question. Do not hesitate to contact us if you have any question.
</p> </p>
</div> </div>
</field> </field>
</record> </record>
</data> </data>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<data noupdate="1"> <data noupdate="1">
<record id="rma_operation_replace" model="rma.operation"> <record id="rma_operation_replace" model="rma.operation">
<field name="name">Replace</field> <field name="name">Replace</field>

View File

@@ -1,7 +1,7 @@
# Copyright 2020 Tecnativa - Ernesto Tejeda # Copyright 2020 Tecnativa - Ernesto Tejeda
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import api, SUPERUSER_ID from odoo import SUPERUSER_ID, api
def post_init_hook(cr, registry): def post_init_hook(cr, registry):
@@ -9,62 +9,65 @@ def post_init_hook(cr, registry):
def _get_next_picking_type_color(): def _get_next_picking_type_color():
""" Choose the next available color for the operation types.""" """ Choose the next available color for the operation types."""
stock_picking_type = env['stock.picking.type'] stock_picking_type = env["stock.picking.type"]
picking_type = stock_picking_type.search_read( picking_type = stock_picking_type.search_read(
[('warehouse_id', '!=', False), ('color', '!=', False)], [("warehouse_id", "!=", False), ("color", "!=", False)],
['color'], ["color"],
order='color', order="color",
) )
all_used_colors = [res['color'] for res in picking_type] all_used_colors = [res["color"] for res in picking_type]
available_colors = [color for color in range(0, 12) available_colors = [
if color not in all_used_colors] color for color in range(0, 12) if color not in all_used_colors
]
return available_colors[0] if available_colors else 0 return available_colors[0] if available_colors else 0
def create_rma_locations(warehouse): def create_rma_locations(warehouse):
stock_location = env['stock.location'] stock_location = env["stock.location"]
location_vals = warehouse._get_locations_values({}) location_vals = warehouse._get_locations_values({})
for field_name, values in location_vals.items(): for field_name, values in location_vals.items():
if field_name == 'rma_loc_id' and not warehouse.rma_loc_id: if field_name == "rma_loc_id" and not warehouse.rma_loc_id:
warehouse.rma_loc_id = stock_location.with_context( warehouse.rma_loc_id = (
active_test=False).create(values).id stock_location.with_context(active_test=False).create(values).id
)
def create_rma_picking_types(whs): def create_rma_picking_types(whs):
ir_sequence_sudo = env['ir.sequence'].sudo() ir_sequence_sudo = env["ir.sequence"].sudo()
stock_picking_type = env['stock.picking.type'] stock_picking_type = env["stock.picking.type"]
color = _get_next_picking_type_color() color = _get_next_picking_type_color()
stock_picking = stock_picking_type.search( stock_picking = stock_picking_type.search(
[('sequence', '!=', False)], limit=1, order='sequence desc') [("sequence", "!=", False)], limit=1, order="sequence desc"
)
max_sequence = stock_picking.sequence or 0 max_sequence = stock_picking.sequence or 0
create_data = whs._get_picking_type_create_values(max_sequence)[0] create_data = whs._get_picking_type_create_values(max_sequence)[0]
sequence_data = whs._get_sequence_values() sequence_data = whs._get_sequence_values()
data = {} data = {}
for picking_type, values in create_data.items(): for picking_type, values in create_data.items():
if (picking_type in ['rma_in_type_id', 'rma_out_type_id'] if (
and not whs[picking_type]): picking_type in ["rma_in_type_id", "rma_out_type_id"]
and not whs[picking_type]
):
picking_sequence = sequence_data[picking_type] picking_sequence = sequence_data[picking_type]
sequence = ir_sequence_sudo.create(picking_sequence) sequence = ir_sequence_sudo.create(picking_sequence)
values.update( values.update(
warehouse_id=whs.id, warehouse_id=whs.id, color=color, sequence_id=sequence.id,
color=color,
sequence_id=sequence.id,
) )
data[picking_type] = stock_picking_type.create(values).id data[picking_type] = stock_picking_type.create(values).id
rma_out_type = stock_picking_type.browse(data['rma_out_type_id']) rma_out_type = stock_picking_type.browse(data["rma_out_type_id"])
rma_out_type.write({ rma_out_type.write(
'return_picking_type_id': data.get('rma_in_type_id', False) {"return_picking_type_id": data.get("rma_in_type_id", False)}
}) )
rma_in_type = stock_picking_type.browse(data['rma_in_type_id']) rma_in_type = stock_picking_type.browse(data["rma_in_type_id"])
rma_in_type.write({ rma_in_type.write(
'return_picking_type_id': data.get('rma_out_type_id', False) {"return_picking_type_id": data.get("rma_out_type_id", False)}
}) )
whs.write(data) whs.write(data)
# Create rma locations and picking types # Create rma locations and picking types
warehouses = env['stock.warehouse'].search([]) warehouses = env["stock.warehouse"].search([])
for warehouse in warehouses: for warehouse in warehouses:
create_rma_locations(warehouse) create_rma_locations(warehouse)
create_rma_picking_types(warehouse) create_rma_picking_types(warehouse)
# Create rma sequence per company # Create rma sequence per company
for company in env['res.company'].search([]): for company in env["res.company"].search([]):
company.create_rma_index() company.create_rma_index()

View File

@@ -6,5 +6,4 @@ from openupgradelib import openupgrade
@openupgrade.migrate() @openupgrade.migrate()
def migrate(env, version): def migrate(env, version):
# Convert Text description field to Html # Convert Text description field to Html
openupgrade.convert_field_to_html( openupgrade.convert_field_to_html(env.cr, "rma", "description", "description")
env.cr, "rma", "description", "description")

View File

@@ -7,32 +7,41 @@ from odoo.tools import float_compare
class AccountInvoice(models.Model): class AccountInvoice(models.Model):
_inherit = 'account.invoice' _inherit = "account.invoice"
def action_invoice_open(self): def action_invoice_open(self):
""" Avoids to validate a refund with less quantity of product than """ Avoids to validate a refund with less quantity of product than
quantity in the linked RMA. quantity in the linked RMA.
""" """
precision = self.env['decimal.precision'].precision_get( precision = self.env["decimal.precision"].precision_get(
'Product Unit of Measure') "Product Unit of Measure"
if self.sudo().mapped('invoice_line_ids').filtered( )
lambda r: (r.rma_id and float_compare( if (
r.quantity, r.rma_id.product_uom_qty, precision) < 0)): self.sudo()
.mapped("invoice_line_ids")
.filtered(
lambda r: (
r.rma_id
and float_compare(r.quantity, r.rma_id.product_uom_qty, precision)
< 0
)
)
):
raise ValidationError( raise ValidationError(
_("There is at least one invoice lines whose quantity is " _(
"less than the quantity specified in its linked RMA.")) "There is at least one invoice lines whose quantity is "
"less than the quantity specified in its linked RMA."
)
)
return super().action_invoice_open() return super().action_invoice_open()
def unlink(self): def unlink(self):
rma = self.mapped('invoice_line_ids.rma_id') rma = self.mapped("invoice_line_ids.rma_id")
rma.write({'state': 'received'}) rma.write({"state": "received"})
return super().unlink() return super().unlink()
class AccountInvoiceLine(models.Model): class AccountInvoiceLine(models.Model):
_inherit = 'account.invoice.line' _inherit = "account.invoice.line"
rma_id = fields.Many2one( rma_id = fields.Many2one(comodel_name="rma", string="RMA",)
comodel_name='rma',
string='RMA',
)

View File

@@ -1,7 +1,7 @@
# Copyright 2020 Tecnativa - Ernesto Tejeda # Copyright 2020 Tecnativa - Ernesto Tejeda
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import api, models, _ from odoo import _, api, models
class Company(models.Model): class Company(models.Model):
@@ -14,10 +14,16 @@ class Company(models.Model):
return company return company
def create_rma_index(self): def create_rma_index(self):
return self.env['ir.sequence'].sudo().create({ return (
'name': _('RMA Code'), self.env["ir.sequence"]
'prefix': 'RMA', .sudo()
'code': 'rma', .create(
'padding': 4, {
'company_id': self.id, "name": _("RMA Code"),
}) "prefix": "RMA",
"code": "rma",
"padding": 4,
"company_id": self.id,
}
)
)

View File

@@ -5,37 +5,31 @@ from odoo import fields, models
class ResPartner(models.Model): class ResPartner(models.Model):
_inherit = 'res.partner' _inherit = "res.partner"
rma_ids = fields.One2many( rma_ids = fields.One2many(
comodel_name='rma', comodel_name="rma", inverse_name="partner_id", string="RMAs",
inverse_name='partner_id',
string='RMAs',
)
rma_count = fields.Integer(
string='RMA count',
compute='_compute_rma_count',
) )
rma_count = fields.Integer(string="RMA count", compute="_compute_rma_count",)
def _compute_rma_count(self): def _compute_rma_count(self):
rma_data = self.env['rma'].read_group( rma_data = self.env["rma"].read_group(
[('partner_id', 'in', self.ids)], ['partner_id'], ['partner_id']) [("partner_id", "in", self.ids)], ["partner_id"], ["partner_id"]
mapped_data = dict( )
[(r['partner_id'][0], r['partner_id_count']) for r in rma_data]) mapped_data = {
r["partner_id"][0]: r["partner_id_count"] for r in rma_data
}
for record in self: for record in self:
record.rma_count = mapped_data.get(record.id, 0) record.rma_count = mapped_data.get(record.id, 0)
def action_view_rma(self): def action_view_rma(self):
self.ensure_one() self.ensure_one()
action = self.env.ref('rma.rma_action').read()[0] action = self.env.ref("rma.rma_action").read()[0]
rma = self.rma_ids rma = self.rma_ids
if len(rma) == 1: if len(rma) == 1:
action.update( action.update(
res_id=rma.id, res_id=rma.id, view_mode="form", view_id=False, views=False,
view_mode="form",
view_id=False,
views=False,
) )
else: else:
action['domain'] = [('partner_id', 'in', self.ids)] action["domain"] = [("partner_id", "in", self.ids)]
return action return action

View File

@@ -5,10 +5,10 @@ from odoo import fields, models
class ResUsers(models.Model): class ResUsers(models.Model):
_inherit = 'res.users' _inherit = "res.users"
rma_team_id = fields.Many2one( rma_team_id = fields.Many2one(
comodel_name='rma.team', comodel_name="rma.team",
string="RMA Team", string="RMA Team",
help='RMA Team the user is member of.', help="RMA Team the user is member of.",
) )

File diff suppressed because it is too large Load Diff

View File

@@ -11,5 +11,5 @@ class RmaOperation(models.Model):
name = fields.Char(required=True, translate=True) name = fields.Char(required=True, translate=True)
_sql_constraints = [ _sql_constraints = [
('name_uniq', 'unique (name)', "That operation name already exists !"), ("name_uniq", "unique (name)", "That operation name already exists !"),
] ]

View File

@@ -6,15 +6,12 @@ from odoo import _, api, fields, models
class RmaTeam(models.Model): class RmaTeam(models.Model):
_name = "rma.team" _name = "rma.team"
_inherit = ['mail.alias.mixin', 'mail.thread'] _inherit = ["mail.alias.mixin", "mail.thread"]
_description = "RMA Team" _description = "RMA Team"
_order = "sequence, name" _order = "sequence, name"
sequence = fields.Integer() sequence = fields.Integer()
name = fields.Char( name = fields.Char(required=True, translate=True,)
required=True,
translate=True,
)
active = fields.Boolean( active = fields.Boolean(
default=True, default=True,
help="If the active field is set to false, it will allow you " help="If the active field is set to false, it will allow you "
@@ -28,13 +25,11 @@ class RmaTeam(models.Model):
user_id = fields.Many2one( user_id = fields.Many2one(
comodel_name="res.users", comodel_name="res.users",
string="Team Leader", string="Team Leader",
domain=[('share', '=', False)], domain=[("share", "=", False)],
default=lambda self: self.env.user, default=lambda self: self.env.user,
) )
member_ids = fields.One2many( member_ids = fields.One2many(
comodel_name='res.users', comodel_name="res.users", inverse_name="rma_team_id", string="Team Members",
inverse_name='rma_team_id',
string='Team Members',
) )
@api.multi @api.multi
@@ -42,18 +37,20 @@ class RmaTeam(models.Model):
self.ensure_one() self.ensure_one()
if default is None: if default is None:
default = {} default = {}
if not default.get('name'): if not default.get("name"):
default['name'] = _("%s (copy)") % self.name default["name"] = _("%s (copy)") % self.name
team = super().copy(default) team = super().copy(default)
for follower in self.message_follower_ids: for follower in self.message_follower_ids:
team.message_subscribe(partner_ids=follower.partner_id.ids, team.message_subscribe(
subtype_ids=follower.subtype_ids.ids) partner_ids=follower.partner_id.ids,
subtype_ids=follower.subtype_ids.ids,
)
return team return team
def get_alias_model_name(self, vals): def get_alias_model_name(self, vals):
return vals.get('alias_model', 'rma') return vals.get("alias_model", "rma")
def get_alias_values(self): def get_alias_values(self):
values = super().get_alias_values() values = super().get_alias_values()
values['alias_defaults'] = {'team_id': self.id} values["alias_defaults"] = {"team_id": self.id}
return values return values

View File

@@ -10,32 +10,25 @@ class StockMove(models.Model):
# RMAs that were created from the delivery move # RMAs that were created from the delivery move
rma_ids = fields.One2many( rma_ids = fields.One2many(
comodel_name='rma', comodel_name="rma", inverse_name="move_id", string="RMAs", copy=False,
inverse_name='move_id',
string='RMAs',
copy=False,
) )
# RMAs linked to the incoming movement from client # RMAs linked to the incoming movement from client
rma_receiver_ids = fields.One2many( rma_receiver_ids = fields.One2many(
comodel_name='rma', comodel_name="rma",
inverse_name='reception_move_id', inverse_name="reception_move_id",
string='RMA receivers', string="RMA receivers",
copy=False, copy=False,
) )
# RMA that create the delivery movement to the customer # RMA that create the delivery movement to the customer
rma_id = fields.Many2one( rma_id = fields.Many2one(comodel_name="rma", string="RMA return", copy=False,)
comodel_name='rma',
string='RMA return',
copy=False,
)
def unlink(self): def unlink(self):
# A stock user could have no RMA permissions, so the ids wouldn't # A stock user could have no RMA permissions, so the ids wouldn't
# be accessible due to record rules. # be accessible due to record rules.
rma_receiver = self.sudo().mapped('rma_receiver_ids') rma_receiver = self.sudo().mapped("rma_receiver_ids")
rma = self.sudo().mapped('rma_id') rma = self.sudo().mapped("rma_id")
res = super().unlink() res = super().unlink()
rma_receiver.write({'state': 'draft'}) rma_receiver.write({"state": "draft"})
rma.update_received_state() rma.update_received_state()
rma.update_replaced_state() rma.update_replaced_state()
return res return res
@@ -44,10 +37,10 @@ class StockMove(models.Model):
res = super()._action_cancel() res = super()._action_cancel()
# A stock user could have no RMA permissions, so the ids wouldn't # A stock user could have no RMA permissions, so the ids wouldn't
# be accessible due to record rules. # be accessible due to record rules.
cancelled_moves = self.filtered(lambda r: r.state == 'cancel').sudo() cancelled_moves = self.filtered(lambda r: r.state == "cancel").sudo()
cancelled_moves.mapped('rma_receiver_ids').write({'state': 'draft'}) cancelled_moves.mapped("rma_receiver_ids").write({"state": "draft"})
cancelled_moves.mapped('rma_id').update_received_state() cancelled_moves.mapped("rma_id").update_received_state()
cancelled_moves.mapped('rma_id').update_replaced_state() cancelled_moves.mapped("rma_id").update_replaced_state()
return res return res
def _action_done(self): def _action_done(self):
@@ -55,27 +48,30 @@ class StockMove(models.Model):
quantity in the linked receiver RMA. It also set the appropriated quantity in the linked receiver RMA. It also set the appropriated
linked RMA to 'received' or 'delivered'. linked RMA to 'received' or 'delivered'.
""" """
for move in self.filtered( for move in self.filtered(lambda r: r.state not in ("done", "cancel")):
lambda r: r.state not in ('done', 'cancel')):
rma_receiver = move.sudo().rma_receiver_ids rma_receiver = move.sudo().rma_receiver_ids
if (rma_receiver if rma_receiver and move.quantity_done != rma_receiver.product_uom_qty:
and move.quantity_done != rma_receiver.product_uom_qty):
raise ValidationError( raise ValidationError(
_("The quantity done for the product '%s' must " _(
"The quantity done for the product '%s' must "
"be equal to its initial demand because the " "be equal to its initial demand because the "
"stock move is linked to an RMA (%s).") "stock move is linked to an RMA (%s)."
)
% (move.product_id.name, move.rma_receiver_ids.name) % (move.product_id.name, move.rma_receiver_ids.name)
) )
res = super()._action_done() res = super()._action_done()
move_done = self.filtered(lambda r: r.state == 'done').sudo() move_done = self.filtered(lambda r: r.state == "done").sudo()
# Set RMAs as received. We sudo so we can grant the operation even # Set RMAs as received. We sudo so we can grant the operation even
# if the stock user has no RMA permissions. # if the stock user has no RMA permissions.
to_be_received = move_done.sudo().mapped('rma_receiver_ids').filtered( to_be_received = (
lambda r: r.state == 'confirmed') move_done.sudo()
to_be_received.write({'state': 'received'}) .mapped("rma_receiver_ids")
.filtered(lambda r: r.state == "confirmed")
)
to_be_received.write({"state": "received"})
# Set RMAs as delivered # Set RMAs as delivered
move_done.mapped('rma_id').update_replaced_state() move_done.mapped("rma_id").update_replaced_state()
move_done.mapped('rma_id').update_returned_state() move_done.mapped("rma_id").update_returned_state()
return res return res
@api.model @api.model
@@ -83,14 +79,14 @@ class StockMove(models.Model):
""" The main use is that launched delivery RMAs doesn't merge """ The main use is that launched delivery RMAs doesn't merge
two moves if they are linked to a different RMAs. two moves if they are linked to a different RMAs.
""" """
return super()._prepare_merge_moves_distinct_fields() + ['rma_id'] return super()._prepare_merge_moves_distinct_fields() + ["rma_id"]
def _prepare_move_split_vals(self, qty): def _prepare_move_split_vals(self, qty):
""" Intended to the backport of picking linked to RMAs propagates the """ Intended to the backport of picking linked to RMAs propagates the
RMA link id. RMA link id.
""" """
res = super()._prepare_move_split_vals(qty) res = super()._prepare_move_split_vals(qty)
res['rma_id'] = self.sudo().rma_id.id res["rma_id"] = self.sudo().rma_id.id
return res return res
def _prepare_return_rma_vals(self, original_picking): def _prepare_return_rma_vals(self, original_picking):
@@ -98,30 +94,31 @@ class StockMove(models.Model):
""" """
self.ensure_one() self.ensure_one()
partner = original_picking.partner_id partner = original_picking.partner_id
if hasattr(original_picking, 'sale_id') and original_picking.sale_id: if hasattr(original_picking, "sale_id") and original_picking.sale_id:
partner_invoice_id = original_picking.sale_id.partner_invoice_id.id partner_invoice_id = original_picking.sale_id.partner_invoice_id.id
else: else:
partner_invoice_id = partner.address_get( partner_invoice_id = (
['invoice']).get('invoice', False), partner.address_get(["invoice"]).get("invoice", False),
)
return { return {
'user_id': self.env.user.id, "user_id": self.env.user.id,
'partner_id': partner.id, "partner_id": partner.id,
'partner_invoice_id': partner_invoice_id, "partner_invoice_id": partner_invoice_id,
'origin': original_picking.name, "origin": original_picking.name,
'picking_id': original_picking.id, "picking_id": original_picking.id,
'move_id': self.origin_returned_move_id.id, "move_id": self.origin_returned_move_id.id,
'product_id': self.origin_returned_move_id.product_id.id, "product_id": self.origin_returned_move_id.product_id.id,
'product_uom_qty': self.product_uom_qty, "product_uom_qty": self.product_uom_qty,
'product_uom': self.product_uom.id, "product_uom": self.product_uom.id,
'reception_move_id': self.id, "reception_move_id": self.id,
'company_id': self.company_id.id, "company_id": self.company_id.id,
'location_id': self.location_dest_id.id, "location_id": self.location_dest_id.id,
'state': 'confirmed', "state": "confirmed",
} }
class StockRule(models.Model): class StockRule(models.Model):
_inherit = 'stock.rule' _inherit = "stock.rule"
def _get_custom_move_fields(self): def _get_custom_move_fields(self):
return super()._get_custom_move_fields() + ['rma_id'] return super()._get_custom_move_fields() + ["rma_id"]

View File

@@ -5,39 +5,34 @@ from odoo import api, fields, models
class StockPicking(models.Model): class StockPicking(models.Model):
_inherit = 'stock.picking' _inherit = "stock.picking"
rma_count = fields.Integer( rma_count = fields.Integer(string="RMA count", compute="_compute_rma_count",)
string='RMA count',
compute='_compute_rma_count',
)
def _compute_rma_count(self): def _compute_rma_count(self):
for rec in self: for rec in self:
rec.rma_count = len(rec.move_lines.mapped('rma_ids')) rec.rma_count = len(rec.move_lines.mapped("rma_ids"))
@api.multi @api.multi
def copy(self, default=None): def copy(self, default=None):
self.ensure_one() self.ensure_one()
if self.env.context.get('set_rma_picking_type'): if self.env.context.get("set_rma_picking_type"):
location_dest_id = default['location_dest_id'] location_dest_id = default["location_dest_id"]
warehouse = self.env['stock.warehouse'].search( warehouse = self.env["stock.warehouse"].search(
[('rma_loc_id', 'parent_of', location_dest_id)], limit=1) [("rma_loc_id", "parent_of", location_dest_id)], limit=1
)
if warehouse: if warehouse:
default['picking_type_id'] = warehouse.rma_in_type_id.id default["picking_type_id"] = warehouse.rma_in_type_id.id
return super().copy(default) return super().copy(default)
def action_view_rma(self): def action_view_rma(self):
self.ensure_one() self.ensure_one()
action = self.env.ref('rma.rma_action').read()[0] action = self.env.ref("rma.rma_action").read()[0]
rma = self.move_lines.mapped('rma_ids') rma = self.move_lines.mapped("rma_ids")
if len(rma) == 1: if len(rma) == 1:
action.update( action.update(
res_id=rma.id, res_id=rma.id, view_mode="form", view_id=False, views=False,
view_mode="form",
view_id=False,
views=False,
) )
else: else:
action['domain'] = [('id', 'in', rma.ids)] action["domain"] = [("id", "in", rma.ids)]
return action return action

View File

@@ -1,118 +1,119 @@
# Copyright 2020 Tecnativa - Ernesto Tejeda # Copyright 2020 Tecnativa - Ernesto Tejeda
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import fields, models, _ from odoo import _, fields, models
class StockWarehouse(models.Model): class StockWarehouse(models.Model):
_inherit = 'stock.warehouse' _inherit = "stock.warehouse"
# This is a strategic field used to create an rma location # This is a strategic field used to create an rma location
# and rma operation types in existing warehouses when # and rma operation types in existing warehouses when
# installing this module. # installing this module.
rma = fields.Boolean( rma = fields.Boolean(
'RMA', "RMA",
default=True, default=True,
help="RMA related products can be stored in this warehouse.") help="RMA related products can be stored in this warehouse.",
)
rma_in_type_id = fields.Many2one( rma_in_type_id = fields.Many2one(
comodel_name='stock.picking.type', comodel_name="stock.picking.type", string="RMA In Type",
string='RMA In Type',
) )
rma_out_type_id = fields.Many2one( rma_out_type_id = fields.Many2one(
comodel_name='stock.picking.type', comodel_name="stock.picking.type", string="RMA Out Type",
string='RMA Out Type',
)
rma_loc_id = fields.Many2one(
comodel_name='stock.location',
string='RMA Location',
) )
rma_loc_id = fields.Many2one(comodel_name="stock.location", string="RMA Location",)
def _get_locations_values(self, vals): def _get_locations_values(self, vals):
values = super()._get_locations_values(vals) values = super()._get_locations_values(vals)
values.update({ values.update(
'rma_loc_id': { {
'name': 'RMA', "rma_loc_id": {
'active': True, "name": "RMA",
'return_location': True, "active": True,
'usage': 'internal', "return_location": True,
'company_id': vals.get('company_id', self.company_id.id), "usage": "internal",
'location_id': self.view_location_id.id, "company_id": vals.get("company_id", self.company_id.id),
"location_id": self.view_location_id.id,
}, },
}) }
)
return values return values
def _get_sequence_values(self): def _get_sequence_values(self):
values = super()._get_sequence_values() values = super()._get_sequence_values()
values.update({ values.update(
'rma_in_type_id': { {
'name': self.name + ' ' + _('Sequence RMA in'), "rma_in_type_id": {
'prefix': self.code + '/RMA/IN/', 'padding': 5, "name": self.name + " " + _("Sequence RMA in"),
'company_id': self.company_id.id, "prefix": self.code + "/RMA/IN/",
"padding": 5,
"company_id": self.company_id.id,
}, },
'rma_out_type_id': { "rma_out_type_id": {
'name': self.name + ' ' + _('Sequence RMA out'), "name": self.name + " " + _("Sequence RMA out"),
'prefix': self.code + '/RMA/OUT/', 'padding': 5, "prefix": self.code + "/RMA/OUT/",
'company_id': self.company_id.id, "padding": 5,
"company_id": self.company_id.id,
}, },
}) }
)
return values return values
def _update_name_and_code(self, new_name=False, new_code=False): def _update_name_and_code(self, new_name=False, new_code=False):
for warehouse in self: for warehouse in self:
sequence_data = warehouse._get_sequence_values() sequence_data = warehouse._get_sequence_values()
warehouse.rma_in_type_id.sequence_id.write( warehouse.rma_in_type_id.sequence_id.write(sequence_data["rma_in_type_id"])
sequence_data['rma_in_type_id'])
warehouse.rma_out_type_id.sequence_id.write( warehouse.rma_out_type_id.sequence_id.write(
sequence_data['rma_out_type_id']) sequence_data["rma_out_type_id"]
)
def _get_picking_type_create_values(self, max_sequence): def _get_picking_type_create_values(self, max_sequence):
data, next_sequence = super()._get_picking_type_create_values( data, next_sequence = super()._get_picking_type_create_values(max_sequence)
max_sequence) data.update(
data.update({ {
'rma_in_type_id': { "rma_in_type_id": {
'name': _('RMA Receipts'), "name": _("RMA Receipts"),
'code': 'incoming', "code": "incoming",
'use_create_lots': False, "use_create_lots": False,
'use_existing_lots': True, "use_existing_lots": True,
'default_location_src_id': False, "default_location_src_id": False,
'default_location_dest_id': self.rma_loc_id.id, "default_location_dest_id": self.rma_loc_id.id,
'sequence': max_sequence + 1, "sequence": max_sequence + 1,
}, },
'rma_out_type_id': { "rma_out_type_id": {
'name': _('RMA Delivery Orders'), "name": _("RMA Delivery Orders"),
'code': 'outgoing', "code": "outgoing",
'use_create_lots': False, "use_create_lots": False,
'use_existing_lots': True, "use_existing_lots": True,
'default_location_src_id': self.rma_loc_id.id, "default_location_src_id": self.rma_loc_id.id,
'default_location_dest_id': False, "default_location_dest_id": False,
'sequence': max_sequence + 2, "sequence": max_sequence + 2,
}, },
}) }
)
return data, max_sequence + 3 return data, max_sequence + 3
def _get_picking_type_update_values(self): def _get_picking_type_update_values(self):
data = super()._get_picking_type_update_values() data = super()._get_picking_type_update_values()
data.update({ data.update(
'rma_in_type_id': { {
'default_location_dest_id': self.rma_loc_id.id, "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,},
'rma_out_type_id': { }
'default_location_src_id': self.rma_loc_id.id, )
},
})
return data return data
def _create_or_update_sequences_and_picking_types(self): def _create_or_update_sequences_and_picking_types(self):
data = super()._create_or_update_sequences_and_picking_types() data = super()._create_or_update_sequences_and_picking_types()
stock_picking_type = self.env['stock.picking.type'] stock_picking_type = self.env["stock.picking.type"]
if 'out_type_id' in data: if "out_type_id" in data:
rma_out_type = stock_picking_type.browse(data['rma_out_type_id']) rma_out_type = stock_picking_type.browse(data["rma_out_type_id"])
rma_out_type.write({ rma_out_type.write(
'return_picking_type_id': data.get('rma_in_type_id', False) {"return_picking_type_id": data.get("rma_in_type_id", False)}
}) )
if 'rma_in_type_id' in data: if "rma_in_type_id" in data:
rma_in_type = stock_picking_type.browse(data['rma_in_type_id']) rma_in_type = stock_picking_type.browse(data["rma_in_type_id"])
rma_in_type.write({ rma_in_type.write(
'return_picking_type_id': data.get('rma_out_type_id', False) {"return_picking_type_id": data.get("rma_out_type_id", False)}
}) )
return data return data

View File

@@ -1,67 +1,86 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2020 Tecnativa - Ernesto Tejeda <!-- Copyright 2020 Tecnativa - Ernesto Tejeda
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). --> License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
<odoo> <odoo>
<!-- Application --> <!-- Application -->
<record id="rma_module_category" model="ir.module.category"> <record id="rma_module_category" model="ir.module.category">
<field name="name">RMA</field> <field name="name">RMA</field>
<field name="description">Manage Return Merchandise Authorizations (RMAs).</field> <field
name="description"
>Manage Return Merchandise Authorizations (RMAs).</field>
</record> </record>
<!-- Access Groups --> <!-- Access Groups -->
<record id="rma_group_user_own" model="res.groups"> <record id="rma_group_user_own" model="res.groups">
<field name="name">User: Own Documents Only</field> <field name="name">User: Own Documents Only</field>
<field name="category_id" ref="rma_module_category"/> <field name="category_id" ref="rma_module_category" />
<field name="implied_ids" eval="[(4, ref('base.group_user'))]"/> <field name="implied_ids" eval="[(4, ref('base.group_user'))]" />
<field name="comment">the user will have access to his own data in the RMA application.</field> <field
name="comment"
>the user will have access to his own data in the RMA application.</field>
</record> </record>
<record id="rma_group_user_all" model="res.groups"> <record id="rma_group_user_all" model="res.groups">
<field name="name">User: All Documents</field> <field name="name">User: All Documents</field>
<field name="category_id" ref="rma_module_category"/> <field name="category_id" ref="rma_module_category" />
<field name="implied_ids" eval="[(4, ref('rma_group_user_own'))]"/> <field name="implied_ids" eval="[(4, ref('rma_group_user_own'))]" />
<field name="comment">the user will have access to all records of everyone in the RMA application.</field> <field
name="comment"
>the user will have access to all records of everyone in the RMA application.</field>
</record> </record>
<record id="rma_group_manager" model="res.groups"> <record id="rma_group_manager" model="res.groups">
<field name="name">Manager</field> <field name="name">Manager</field>
<field name="comment">the user will have an access to the RMA configuration as well as statistic reports.</field> <field
<field name="category_id" ref="rma_module_category"/> name="comment"
<field name="implied_ids" eval="[(4, ref('rma_group_user_all'))]"/> >the user will have an access to the RMA configuration as well as statistic reports.</field>
<field name="users" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/> <field name="category_id" ref="rma_module_category" />
<field name="implied_ids" eval="[(4, ref('rma_group_user_all'))]" />
<field
name="users"
eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"
/>
</record> </record>
<!-- Record Rules --> <!-- Record Rules -->
<record id="rma_rule_user_own" model="ir.rule"> <record id="rma_rule_user_own" model="ir.rule">
<field name="name">Personal RMAs</field> <field name="name">Personal RMAs</field>
<field name="model_id" ref="model_rma"/> <field name="model_id" ref="model_rma" />
<field name="domain_force">['|',('user_id','=',user.id),('user_id','=',False)]</field> <field
<field name="groups" eval="[(4, ref('rma_group_user_own'))]"/> name="domain_force"
>['|',('user_id','=',user.id),('user_id','=',False)]</field>
<field name="groups" eval="[(4, ref('rma_group_user_own'))]" />
</record> </record>
<record id="rma_rule_user_all" model="ir.rule"> <record id="rma_rule_user_all" model="ir.rule">
<field name="name">All RMAs</field> <field name="name">All RMAs</field>
<field name="model_id" ref="model_rma"/> <field name="model_id" ref="model_rma" />
<field name="domain_force">[(1,'=',1)]</field> <field name="domain_force">[(1,'=',1)]</field>
<field name="groups" eval="[(4, ref('rma_group_user_all'))]"/> <field name="groups" eval="[(4, ref('rma_group_user_all'))]" />
</record> </record>
<!-- RMA model rules for portal users --> <!-- RMA model rules for portal users -->
<record id="rma_rule_portal" model="ir.rule"> <record id="rma_rule_portal" model="ir.rule">
<field name="name">RMA portal users</field> <field name="name">RMA portal users</field>
<field name="model_id" ref="rma.model_rma"/> <field name="model_id" ref="rma.model_rma" />
<field name="domain_force">[('message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id])]</field> <field
<field name="groups" eval="[(4, ref('base.group_portal'))]"/> name="domain_force"
>[('message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id])]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]" />
</record> </record>
<!-- Multi-Company Rules --> <!-- Multi-Company Rules -->
<record id="rma_rule_multi_company" model="ir.rule"> <record id="rma_rule_multi_company" model="ir.rule">
<field name="name">RMA multi-company</field> <field name="name">RMA multi-company</field>
<field name="model_id" ref="model_rma"/> <field name="model_id" ref="model_rma" />
<field name="global" eval="True"/> <field name="global" eval="True" />
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field> <field
name="domain_force"
>['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record> </record>
<record id="rma_team_rule_multi_company" model="ir.rule"> <record id="rma_team_rule_multi_company" model="ir.rule">
<field name="name">RMA team multi-company</field> <field name="name">RMA team multi-company</field>
<field name="model_id" ref="model_rma_team"/> <field name="model_id" ref="model_rma_team" />
<field name="global" eval="True"/> <field name="global" eval="True" />
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field> <field
name="domain_force"
>['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record> </record>
<!-- New users will belong to rma_group_user_own --> <!-- New users will belong to rma_group_user_own -->
<record id="base.default_user" model="res.users"> <record id="base.default_user" model="res.users">
<field name="groups_id" eval="[(4, ref('rma_group_user_own'))]"/> <field name="groups_id" eval="[(4, ref('rma_group_user_own'))]" />
</record> </record>
</odoo> </odoo>

View File

@@ -1,60 +1,67 @@
# Copyright 2020 Tecnativa - Ernesto Tejeda # Copyright 2020 Tecnativa - Ernesto Tejeda
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo.tests import Form, SavepointCase
from odoo.exceptions import UserError, ValidationError from odoo.exceptions import UserError, ValidationError
from odoo.tests import Form, SavepointCase
class TestRma(SavepointCase): class TestRma(SavepointCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
super(TestRma, cls).setUpClass() super(TestRma, cls).setUpClass()
cls.res_partner = cls.env['res.partner'] cls.res_partner = cls.env["res.partner"]
cls.product_product = cls.env['product.product'] cls.product_product = cls.env["product.product"]
cls.company = cls.env.user.company_id cls.company = cls.env.user.company_id
cls.warehouse_company = cls.env['stock.warehouse'].search( cls.warehouse_company = cls.env["stock.warehouse"].search(
[('company_id', '=', cls.company.id)], limit=1) [("company_id", "=", cls.company.id)], limit=1
)
cls.rma_loc = cls.warehouse_company.rma_loc_id cls.rma_loc = cls.warehouse_company.rma_loc_id
account_pay = cls.env['account.account'].create({ account_pay = cls.env["account.account"].create(
'code': 'X1111', {
'name': 'Creditors - (test)', "code": "X1111",
'user_type_id': cls.env.ref( "name": "Creditors - (test)",
'account.data_account_type_payable').id, "user_type_id": cls.env.ref("account.data_account_type_payable").id,
'reconcile': True, "reconcile": True,
}) }
cls.journal = cls.env['account.journal'].create({ )
'name': 'sale_0', cls.journal = cls.env["account.journal"].create(
'code': 'SALE0', {
'type': 'sale', "name": "sale_0",
'default_debit_account_id': account_pay.id, "code": "SALE0",
}) "type": "sale",
cls.product = cls.product_product.create({ "default_debit_account_id": account_pay.id,
'name': 'Product test 1', }
'type': 'product', )
}) cls.product = cls.product_product.create(
account_type = cls.env['account.account.type'].create({ {"name": "Product test 1", "type": "product",}
'name': 'RCV type', )
'type': 'receivable', account_type = cls.env["account.account.type"].create(
}) {"name": "RCV type", "type": "receivable",}
cls.account_receiv = cls.env['account.account'].create({ )
'name': 'Receivable', cls.account_receiv = cls.env["account.account"].create(
'code': 'RCV00', {
'user_type_id': account_type.id, "name": "Receivable",
'reconcile': True, "code": "RCV00",
}) "user_type_id": account_type.id,
cls.partner = cls.res_partner.create({ "reconcile": True,
'name': 'Partner test', }
'property_account_receivable_id': cls.account_receiv.id, )
}) cls.partner = cls.res_partner.create(
cls.partner_invoice = cls.res_partner.create({ {
'name': 'Partner invoice test', "name": "Partner test",
'parent_id': cls.partner.id, "property_account_receivable_id": cls.account_receiv.id,
'type': 'invoice', }
}) )
cls.partner_invoice = cls.res_partner.create(
{
"name": "Partner invoice test",
"parent_id": cls.partner.id,
"type": "invoice",
}
)
def _create_rma(self, partner=None, product=None, qty=None, location=None): def _create_rma(self, partner=None, product=None, qty=None, location=None):
rma_form = Form(self.env['rma']) rma_form = Form(self.env["rma"])
if partner: if partner:
rma_form.partner_id = partner rma_form.partner_id = partner
if product: if product:
@@ -65,8 +72,9 @@ class TestRma(SavepointCase):
rma_form.location_id = location rma_form.location_id = location
return rma_form.save() return rma_form.save()
def _create_confirm_receive(self, partner=None, product=None, qty=None, def _create_confirm_receive(
location=None): self, partner=None, product=None, qty=None, location=None
):
rma = self._create_rma(partner, product, qty, location) rma = self._create_rma(partner, product, qty, location)
rma.action_confirm() rma.action_confirm()
rma.reception_move_id.quantity_done = rma.product_uom_qty rma.reception_move_id.quantity_done = rma.product_uom_qty
@@ -76,35 +84,36 @@ class TestRma(SavepointCase):
def _test_readonly_fields(self, rma): def _test_readonly_fields(self, rma):
with Form(rma) as rma_form: with Form(rma) as rma_form:
with self.assertRaises(AssertionError): with self.assertRaises(AssertionError):
rma_form.partner_id = self.env['res.partner'] rma_form.partner_id = self.env["res.partner"]
with self.assertRaises(AssertionError): with self.assertRaises(AssertionError):
rma_form.partner_invoice_id = self.env['res.partner'] rma_form.partner_invoice_id = self.env["res.partner"]
with self.assertRaises(AssertionError): with self.assertRaises(AssertionError):
rma_form.picking_id = self.env['stock.picking'] rma_form.picking_id = self.env["stock.picking"]
with self.assertRaises(AssertionError): with self.assertRaises(AssertionError):
rma_form.move_id = self.env['stock.move'] rma_form.move_id = self.env["stock.move"]
with self.assertRaises(AssertionError): with self.assertRaises(AssertionError):
rma_form.product_id = self.env['product.product'] rma_form.product_id = self.env["product.product"]
with self.assertRaises(AssertionError): with self.assertRaises(AssertionError):
rma_form.product_uom_qty = 0 rma_form.product_uom_qty = 0
with self.assertRaises(AssertionError): with self.assertRaises(AssertionError):
rma_form.product_uom = self.env['uom.uom'] rma_form.product_uom = self.env["uom.uom"]
with self.assertRaises(AssertionError): with self.assertRaises(AssertionError):
rma_form.location_id = self.env['stock.location'] rma_form.location_id = self.env["stock.location"]
def _create_delivery(self): def _create_delivery(self):
picking_type = self.env['stock.picking.type'].search( picking_type = self.env["stock.picking.type"].search(
[ [
('code', '=', 'outgoing'), ("code", "=", "outgoing"),
'|', "|",
('warehouse_id.company_id', '=', self.company.id), ("warehouse_id.company_id", "=", self.company.id),
('warehouse_id', '=', False) ("warehouse_id", "=", False),
], ],
limit=1, limit=1,
) )
picking_form = Form( picking_form = Form(
recordp=self.env['stock.picking'].with_context( recordp=self.env["stock.picking"].with_context(
default_picking_type_id=picking_type.id), default_picking_type_id=picking_type.id
),
view="stock.view_picking_form", view="stock.view_picking_form",
) )
picking_form.company_id = self.company picking_form.company_id = self.company
@@ -114,7 +123,8 @@ class TestRma(SavepointCase):
move.product_uom_qty = 10 move.product_uom_qty = 10
with picking_form.move_ids_without_package.new() as move: with picking_form.move_ids_without_package.new() as move:
move.product_id = self.product_product.create( move.product_id = self.product_product.create(
{'name': 'Product 2 test', 'type': 'product'}) {"name": "Product 2 test", "type": "product"}
)
move.product_uom_qty = 20 move.product_uom_qty = 20
picking = picking_form.save() picking = picking_form.save()
picking.action_confirm() picking.action_confirm()
@@ -124,34 +134,35 @@ class TestRma(SavepointCase):
return picking return picking
def test_onchange(self): def test_onchange(self):
rma_form = Form(self.env['rma']) rma_form = Form(self.env["rma"])
# If partner changes, the invoice address is set # If partner changes, the invoice address is set
rma_form.partner_id = self.partner rma_form.partner_id = self.partner
self.assertEqual(rma_form.partner_invoice_id, self.partner_invoice) self.assertEqual(rma_form.partner_invoice_id, self.partner_invoice)
# If origin move changes, the product is set # If origin move changes, the product is set
uom_ten = self.env['uom.uom'].create({ uom_ten = self.env["uom.uom"].create(
'name': "Ten", {
'category_id': self.env.ref('uom.product_uom_unit').id, "name": "Ten",
'factor_inv': 10, "category_id": self.env.ref("uom.product_uom_unit").id,
'uom_type': 'bigger', "factor_inv": 10,
}) "uom_type": "bigger",
product_2 = self.product_product.create({ }
'name': 'Product test 2', )
'type': 'product', product_2 = self.product_product.create(
'uom_id': uom_ten.id, {"name": "Product test 2", "type": "product", "uom_id": uom_ten.id,}
}) )
outgoing_picking_type = self.env['stock.picking.type'].search( outgoing_picking_type = self.env["stock.picking.type"].search(
[ [
('code', '=', 'outgoing'), ("code", "=", "outgoing"),
'|', "|",
('warehouse_id.company_id', '=', self.company.id), ("warehouse_id.company_id", "=", self.company.id),
('warehouse_id', '=', False) ("warehouse_id", "=", False),
], ],
limit=1, limit=1,
) )
picking_form = Form( picking_form = Form(
recordp=self.env['stock.picking'].with_context( recordp=self.env["stock.picking"].with_context(
default_picking_type_id=outgoing_picking_type.id), default_picking_type_id=outgoing_picking_type.id
),
view="stock.view_picking_form", view="stock.view_picking_form",
) )
picking_form.company_id = self.company picking_form.company_id = self.company
@@ -169,7 +180,7 @@ class TestRma(SavepointCase):
self.assertEqual(rma_form.product_uom_qty, 15) self.assertEqual(rma_form.product_uom_qty, 15)
self.assertEqual(rma_form.product_uom, uom_ten) self.assertEqual(rma_form.product_uom, uom_ten)
# If product changes, unit of measure changes # If product changes, unit of measure changes
rma_form.picking_id = self.env['stock.picking'] rma_form.picking_id = self.env["stock.picking"]
rma_form.product_id = self.product rma_form.product_id = self.product
self.assertEqual(rma_form.product_id, self.product) self.assertEqual(rma_form.product_id, self.product)
self.assertEqual(rma_form.product_uom_qty, 15) self.assertEqual(rma_form.product_uom_qty, 15)
@@ -177,9 +188,10 @@ class TestRma(SavepointCase):
self.assertEqual(rma_form.product_uom, self.product.uom_id) self.assertEqual(rma_form.product_uom, self.product.uom_id)
rma = rma_form.save() rma = rma_form.save()
# If product changes, unit of measure domain should also change # If product changes, unit of measure domain should also change
domain = rma._onchange_product_id()['domain']['product_uom'] domain = rma._onchange_product_id()["domain"]["product_uom"]
self.assertListEqual( self.assertListEqual(
domain, [('category_id', '=', self.product.uom_id.category_id.id)]) domain, [("category_id", "=", self.product.uom_id.category_id.id)]
)
def test_ensure_required_fields_on_confirm(self): def test_ensure_required_fields_on_confirm(self):
rma = self._create_rma() rma = self._create_rma()
@@ -187,86 +199,84 @@ class TestRma(SavepointCase):
rma.action_confirm() rma.action_confirm()
self.assertEqual( self.assertEqual(
e.exception.name, e.exception.name,
"Required field(s):\nCustomer\nInvoice Address\nProduct\nLocation" "Required field(s):\nCustomer\nInvoice Address\nProduct\nLocation",
) )
with Form(rma) as rma_form: with Form(rma) as rma_form:
rma_form.partner_id = self.partner rma_form.partner_id = self.partner
with self.assertRaises(ValidationError) as e: with self.assertRaises(ValidationError) as e:
rma.action_confirm() rma.action_confirm()
self.assertEqual( self.assertEqual(e.exception.name, "Required field(s):\nProduct\nLocation")
e.exception.name, "Required field(s):\nProduct\nLocation")
with Form(rma) as rma_form: with Form(rma) as rma_form:
rma_form.product_id = self.product rma_form.product_id = self.product
rma_form.location_id = self.rma_loc rma_form.location_id = self.rma_loc
rma.action_confirm() rma.action_confirm()
self.assertEqual(rma.state, 'confirmed') self.assertEqual(rma.state, "confirmed")
def test_confirm_and_receive(self): def test_confirm_and_receive(self):
rma = self._create_rma(self.partner, self.product, 10, self.rma_loc) rma = self._create_rma(self.partner, self.product, 10, self.rma_loc)
rma.action_confirm() rma.action_confirm()
self.assertEqual(rma.reception_move_id.picking_id.state, 'assigned') self.assertEqual(rma.reception_move_id.picking_id.state, "assigned")
self.assertEqual(rma.reception_move_id.product_id, rma.product_id) self.assertEqual(rma.reception_move_id.product_id, rma.product_id)
self.assertEqual(rma.reception_move_id.product_uom_qty, 10) self.assertEqual(rma.reception_move_id.product_uom_qty, 10)
self.assertEqual(rma.reception_move_id.product_uom, rma.product_uom) self.assertEqual(rma.reception_move_id.product_uom, rma.product_uom)
self.assertEqual(rma.state, 'confirmed') self.assertEqual(rma.state, "confirmed")
self._test_readonly_fields(rma) self._test_readonly_fields(rma)
rma.reception_move_id.quantity_done = 9 rma.reception_move_id.quantity_done = 9
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
rma.reception_move_id.picking_id.action_done() rma.reception_move_id.picking_id.action_done()
rma.reception_move_id.quantity_done = 10 rma.reception_move_id.quantity_done = 10
rma.reception_move_id.picking_id.action_done() rma.reception_move_id.picking_id.action_done()
self.assertEqual(rma.reception_move_id.picking_id.state, 'done') self.assertEqual(rma.reception_move_id.picking_id.state, "done")
self.assertEqual(rma.reception_move_id.quantity_done, 10) self.assertEqual(rma.reception_move_id.quantity_done, 10)
self.assertEqual(rma.state, 'received') self.assertEqual(rma.state, "received")
self._test_readonly_fields(rma) self._test_readonly_fields(rma)
def test_cancel(self): def test_cancel(self):
# cancel a draft RMA # cancel a draft RMA
rma = self._create_rma(self.partner, self.product) rma = self._create_rma(self.partner, self.product)
rma.action_cancel() rma.action_cancel()
self.assertEqual(rma.state, 'cancelled') self.assertEqual(rma.state, "cancelled")
self._test_readonly_fields(rma) self._test_readonly_fields(rma)
# cancel a confirmed RMA # cancel a confirmed RMA
rma = self._create_rma(self.partner, self.product, 10, self.rma_loc) rma = self._create_rma(self.partner, self.product, 10, self.rma_loc)
rma.action_confirm() rma.action_confirm()
rma.action_cancel() rma.action_cancel()
self.assertEqual(rma.state, 'cancelled') self.assertEqual(rma.state, "cancelled")
# A RMA is only cancelled from draft and confirmed states # A RMA is only cancelled from draft and confirmed states
rma = self._create_confirm_receive(self.partner, self.product, 10, rma = self._create_confirm_receive(self.partner, self.product, 10, self.rma_loc)
self.rma_loc)
with self.assertRaises(UserError): with self.assertRaises(UserError):
rma.action_cancel() rma.action_cancel()
def test_lock_unlock(self): def test_lock_unlock(self):
# A RMA is only locked from 'received' state # A RMA is only locked from 'received' state
rma_1 = self._create_rma(self.partner, self.product, 10, self.rma_loc) rma_1 = self._create_rma(self.partner, self.product, 10, self.rma_loc)
rma_2 = self._create_confirm_receive(self.partner, self.product, 10, rma_2 = self._create_confirm_receive(
self.rma_loc) self.partner, self.product, 10, self.rma_loc
self.assertEqual(rma_1.state, 'draft') )
self.assertEqual(rma_2.state, 'received') self.assertEqual(rma_1.state, "draft")
self.assertEqual(rma_2.state, "received")
(rma_1 | rma_2).action_lock() (rma_1 | rma_2).action_lock()
self.assertEqual(rma_1.state, 'draft') self.assertEqual(rma_1.state, "draft")
self.assertEqual(rma_2.state, 'locked') self.assertEqual(rma_2.state, "locked")
# A RMA is only unlocked from 'lock' state and it will be set # A RMA is only unlocked from 'lock' state and it will be set
# to 'received' state # to 'received' state
(rma_1 | rma_2).action_unlock() (rma_1 | rma_2).action_unlock()
self.assertEqual(rma_1.state, 'draft') self.assertEqual(rma_1.state, "draft")
self.assertEqual(rma_2.state, 'received') self.assertEqual(rma_2.state, "received")
def test_action_refund(self): def test_action_refund(self):
rma = self._create_confirm_receive(self.partner, self.product, 10, rma = self._create_confirm_receive(self.partner, self.product, 10, self.rma_loc)
self.rma_loc) self.assertEqual(rma.state, "received")
self.assertEqual(rma.state, 'received')
self.assertTrue(rma.can_be_refunded) self.assertTrue(rma.can_be_refunded)
self.assertTrue(rma.can_be_returned) self.assertTrue(rma.can_be_returned)
self.assertTrue(rma.can_be_replaced) self.assertTrue(rma.can_be_replaced)
rma.action_refund() rma.action_refund()
self.assertEqual(rma.refund_id.type, 'out_refund') self.assertEqual(rma.refund_id.type, "out_refund")
self.assertEqual(rma.refund_id.state, 'draft') self.assertEqual(rma.refund_id.state, "draft")
self.assertEqual(rma.refund_line_id.product_id, rma.product_id) self.assertEqual(rma.refund_line_id.product_id, rma.product_id)
self.assertEqual(rma.refund_line_id.quantity, 10) self.assertEqual(rma.refund_line_id.quantity, 10)
self.assertEqual(rma.refund_line_id.uom_id, rma.product_uom) self.assertEqual(rma.refund_line_id.uom_id, rma.product_uom)
self.assertEqual(rma.state, 'refunded') self.assertEqual(rma.state, "refunded")
self.assertFalse(rma.can_be_refunded) self.assertFalse(rma.can_be_refunded)
self.assertFalse(rma.can_be_returned) self.assertFalse(rma.can_be_returned)
self.assertFalse(rma.can_be_replaced) self.assertFalse(rma.can_be_replaced)
@@ -282,59 +292,59 @@ class TestRma(SavepointCase):
def test_mass_refund(self): def test_mass_refund(self):
# Create, confirm and receive rma_1 # Create, confirm and receive rma_1
rma_1 = self._create_confirm_receive(self.partner, self.product, 10, rma_1 = self._create_confirm_receive(
self.rma_loc) self.partner, self.product, 10, self.rma_loc
)
# create, confirm and receive 3 more RMAs # create, confirm and receive 3 more RMAs
# rma_2: Same partner and same product as rma_1 # rma_2: Same partner and same product as rma_1
rma_2 = self._create_confirm_receive(self.partner, self.product, 15, rma_2 = self._create_confirm_receive(
self.rma_loc) self.partner, self.product, 15, self.rma_loc
)
# rma_3: Same partner and different product than rma_1 # rma_3: Same partner and different product than rma_1
product = self.product_product.create( product = self.product_product.create(
{'name': 'Product 2 test', 'type': 'product'}) {"name": "Product 2 test", "type": "product"}
rma_3 = self._create_confirm_receive(self.partner, product, 20, )
self.rma_loc) rma_3 = self._create_confirm_receive(self.partner, product, 20, self.rma_loc)
# rma_4: Different partner and same product as rma_1 # rma_4: Different partner and same product as rma_1
partner = self.res_partner.create({ partner = self.res_partner.create(
'name': 'Partner 2 test', {
'property_account_receivable_id': self.account_receiv.id, "name": "Partner 2 test",
'company_id': self.company.id, "property_account_receivable_id": self.account_receiv.id,
}) "company_id": self.company.id,
rma_4 = self._create_confirm_receive(partner, product, 25, }
self.rma_loc) )
rma_4 = self._create_confirm_receive(partner, product, 25, self.rma_loc)
# all rmas are ready to refund # all rmas are ready to refund
all_rmas = (rma_1 | rma_2 | rma_3 | rma_4) all_rmas = rma_1 | rma_2 | rma_3 | rma_4
self.assertEqual(all_rmas.mapped('state'), ['received']*4) self.assertEqual(all_rmas.mapped("state"), ["received"] * 4)
self.assertEqual(all_rmas.mapped('can_be_refunded'), [True]*4) self.assertEqual(all_rmas.mapped("can_be_refunded"), [True] * 4)
# Mass refund of those four RMAs # Mass refund of those four RMAs
action = self.env.ref('rma.rma_refund_action_server') action = self.env.ref("rma.rma_refund_action_server")
ctx = dict(self.env.context) ctx = dict(self.env.context)
ctx.update(active_ids=all_rmas.ids, active_model='rma') ctx.update(active_ids=all_rmas.ids, active_model="rma")
action.with_context(ctx).run() action.with_context(ctx).run()
# After that all RMAs are in 'refunded' state # After that all RMAs are in 'refunded' state
self.assertEqual(all_rmas.mapped('state'), ['refunded'] * 4) self.assertEqual(all_rmas.mapped("state"), ["refunded"] * 4)
# Two refunds were created # Two refunds were created
refund_1 = (rma_1 | rma_2 | rma_3).mapped('refund_id') refund_1 = (rma_1 | rma_2 | rma_3).mapped("refund_id")
refund_2 = rma_4.refund_id refund_2 = rma_4.refund_id
self.assertEqual(len(refund_1), 1) self.assertEqual(len(refund_1), 1)
self.assertEqual(len(refund_2), 1) self.assertEqual(len(refund_2), 1)
self.assertEqual((refund_1 | refund_2).mapped('state'), ['draft']*2) self.assertEqual((refund_1 | refund_2).mapped("state"), ["draft"] * 2)
# One refund per partner # One refund per partner
self.assertNotEqual(refund_1.partner_id, refund_2.partner_id) self.assertNotEqual(refund_1.partner_id, refund_2.partner_id)
self.assertEqual( self.assertEqual(
refund_1.partner_id, refund_1.partner_id, (rma_1 | rma_2 | rma_3).mapped("partner_invoice_id"),
(rma_1 | rma_2 | rma_3).mapped('partner_invoice_id'),
) )
self.assertEqual(refund_2.partner_id, rma_4.partner_invoice_id) self.assertEqual(refund_2.partner_id, rma_4.partner_invoice_id)
# Each RMA (rma_1, rma_2 and rma_3) is linked with a different # Each RMA (rma_1, rma_2 and rma_3) is linked with a different
# line of refund_1 # line of refund_1
self.assertEqual(len(refund_1.invoice_line_ids), 3) self.assertEqual(len(refund_1.invoice_line_ids), 3)
self.assertEqual( self.assertEqual(
refund_1.invoice_line_ids.mapped('rma_id'), refund_1.invoice_line_ids.mapped("rma_id"), (rma_1 | rma_2 | rma_3),
(rma_1 | rma_2 | rma_3),
) )
self.assertEqual( self.assertEqual(
(rma_1 | rma_2 | rma_3).mapped('refund_line_id'), (rma_1 | rma_2 | rma_3).mapped("refund_line_id"), refund_1.invoice_line_ids,
refund_1.invoice_line_ids,
) )
# rma_4 is linked with the unique line of refund_2 # rma_4 is linked with the unique line of refund_2
self.assertEqual(len(refund_2.invoice_line_ids), 1) self.assertEqual(len(refund_2.invoice_line_ids), 1)
@@ -355,15 +365,14 @@ class TestRma(SavepointCase):
def test_replace(self): def test_replace(self):
# Create, confirm and receive an RMA # Create, confirm and receive an RMA
rma = self._create_confirm_receive(self.partner, self.product, 10, rma = self._create_confirm_receive(self.partner, self.product, 10, self.rma_loc)
self.rma_loc)
# Replace with another product with quantity 2. # Replace with another product with quantity 2.
product_2 = self.product_product.create( product_2 = self.product_product.create(
{'name': 'Product 2 test', 'type': 'product'}) {"name": "Product 2 test", "type": "product"}
)
delivery_form = Form( delivery_form = Form(
self.env['rma.delivery.wizard'].with_context( self.env["rma.delivery.wizard"].with_context(
active_ids=rma.ids, active_ids=rma.ids, rma_delivery_type="replace",
rma_delivery_type='replace',
) )
) )
delivery_form.product_id = product_2 delivery_form.product_id = product_2
@@ -373,8 +382,8 @@ class TestRma(SavepointCase):
self.assertEqual(len(rma.delivery_move_ids.picking_id.move_lines), 1) self.assertEqual(len(rma.delivery_move_ids.picking_id.move_lines), 1)
self.assertEqual(rma.delivery_move_ids.product_id, product_2) self.assertEqual(rma.delivery_move_ids.product_id, product_2)
self.assertEqual(rma.delivery_move_ids.product_uom_qty, 2) self.assertEqual(rma.delivery_move_ids.product_uom_qty, 2)
self.assertTrue(rma.delivery_move_ids.picking_id.state, 'waiting') self.assertTrue(rma.delivery_move_ids.picking_id.state, "waiting")
self.assertEqual(rma.state, 'waiting_replacement') self.assertEqual(rma.state, "waiting_replacement")
self.assertFalse(rma.can_be_refunded) self.assertFalse(rma.can_be_refunded)
self.assertFalse(rma.can_be_returned) self.assertFalse(rma.can_be_returned)
self.assertTrue(rma.can_be_replaced) self.assertTrue(rma.can_be_replaced)
@@ -386,11 +395,11 @@ class TestRma(SavepointCase):
picking = first_move.picking_id picking = first_move.picking_id
# Replace again with another product with the remaining quantity # Replace again with another product with the remaining quantity
product_3 = self.product_product.create( product_3 = self.product_product.create(
{'name': 'Product 3 test', 'type': 'product'}) {"name": "Product 3 test", "type": "product"}
)
delivery_form = Form( delivery_form = Form(
self.env['rma.delivery.wizard'].with_context( self.env["rma.delivery.wizard"].with_context(
active_ids=rma.ids, active_ids=rma.ids, rma_delivery_type="replace",
rma_delivery_type='replace',
) )
) )
delivery_form.product_id = product_3 delivery_form.product_id = product_3
@@ -398,12 +407,12 @@ class TestRma(SavepointCase):
delivery_wizard.action_deliver() delivery_wizard.action_deliver()
second_move = rma.delivery_move_ids - first_move second_move = rma.delivery_move_ids - first_move
self.assertEqual(len(rma.delivery_move_ids), 2) self.assertEqual(len(rma.delivery_move_ids), 2)
self.assertEqual(rma.delivery_move_ids.mapped('picking_id'), picking) self.assertEqual(rma.delivery_move_ids.mapped("picking_id"), picking)
self.assertEqual(first_move.product_id, product_2) self.assertEqual(first_move.product_id, product_2)
self.assertEqual(first_move.product_uom_qty, 2) self.assertEqual(first_move.product_uom_qty, 2)
self.assertEqual(second_move.product_id, product_3) self.assertEqual(second_move.product_id, product_3)
self.assertEqual(second_move.product_uom_qty, 8) self.assertEqual(second_move.product_uom_qty, 8)
self.assertTrue(picking.state, 'waiting') self.assertTrue(picking.state, "waiting")
self.assertEqual(rma.delivered_qty, 10) self.assertEqual(rma.delivered_qty, 10)
self.assertEqual(rma.remaining_qty, 0) self.assertEqual(rma.remaining_qty, 0)
self.assertEqual(rma.delivered_qty_done, 0) self.assertEqual(rma.delivered_qty_done, 0)
@@ -413,13 +422,13 @@ class TestRma(SavepointCase):
first_move.quantity_done = 2 first_move.quantity_done = 2
second_move.quantity_done = 8 second_move.quantity_done = 8
picking.button_validate() picking.button_validate()
self.assertEqual(picking.state, 'done') self.assertEqual(picking.state, "done")
self.assertEqual(rma.delivered_qty, 10) self.assertEqual(rma.delivered_qty, 10)
self.assertEqual(rma.remaining_qty, 0) self.assertEqual(rma.remaining_qty, 0)
self.assertEqual(rma.delivered_qty_done, 10) self.assertEqual(rma.delivered_qty_done, 10)
self.assertEqual(rma.remaining_qty_to_done, 0) self.assertEqual(rma.remaining_qty_to_done, 0)
# The RMA is now in 'replaced' state # The RMA is now in 'replaced' state
self.assertEqual(rma.state, 'replaced') self.assertEqual(rma.state, "replaced")
self.assertFalse(rma.can_be_refunded) self.assertFalse(rma.can_be_refunded)
self.assertFalse(rma.can_be_returned) self.assertFalse(rma.can_be_returned)
# Despite being in 'replaced' state, # Despite being in 'replaced' state,
@@ -429,13 +438,11 @@ class TestRma(SavepointCase):
def test_return_to_customer(self): def test_return_to_customer(self):
# Create, confirm and receive an RMA # Create, confirm and receive an RMA
rma = self._create_confirm_receive(self.partner, self.product, 10, rma = self._create_confirm_receive(self.partner, self.product, 10, self.rma_loc)
self.rma_loc)
# Return the same product with quantity 2 to the customer. # Return the same product with quantity 2 to the customer.
delivery_form = Form( delivery_form = Form(
self.env['rma.delivery.wizard'].with_context( self.env["rma.delivery.wizard"].with_context(
active_ids=rma.ids, active_ids=rma.ids, rma_delivery_type="return",
rma_delivery_type='return',
) )
) )
delivery_form.product_uom_qty = 2 delivery_form.product_uom_qty = 2
@@ -445,8 +452,8 @@ class TestRma(SavepointCase):
self.assertEqual(len(picking.move_lines), 1) self.assertEqual(len(picking.move_lines), 1)
self.assertEqual(rma.delivery_move_ids.product_id, self.product) self.assertEqual(rma.delivery_move_ids.product_id, self.product)
self.assertEqual(rma.delivery_move_ids.product_uom_qty, 2) self.assertEqual(rma.delivery_move_ids.product_uom_qty, 2)
self.assertTrue(picking.state, 'waiting') self.assertTrue(picking.state, "waiting")
self.assertEqual(rma.state, 'waiting_return') self.assertEqual(rma.state, "waiting_return")
self.assertFalse(rma.can_be_refunded) self.assertFalse(rma.can_be_refunded)
self.assertFalse(rma.can_be_replaced) self.assertFalse(rma.can_be_replaced)
self.assertTrue(rma.can_be_returned) self.assertTrue(rma.can_be_returned)
@@ -459,16 +466,15 @@ class TestRma(SavepointCase):
# Validate the picking # Validate the picking
first_move.quantity_done = 2 first_move.quantity_done = 2
picking.button_validate() picking.button_validate()
self.assertEqual(picking.state, 'done') self.assertEqual(picking.state, "done")
self.assertEqual(rma.delivered_qty, 2) self.assertEqual(rma.delivered_qty, 2)
self.assertEqual(rma.remaining_qty, 8) self.assertEqual(rma.remaining_qty, 8)
self.assertEqual(rma.delivered_qty_done, 2) self.assertEqual(rma.delivered_qty_done, 2)
self.assertEqual(rma.remaining_qty_to_done, 8) self.assertEqual(rma.remaining_qty_to_done, 8)
# Return the remaining quantity to the customer # Return the remaining quantity to the customer
delivery_form = Form( delivery_form = Form(
self.env['rma.delivery.wizard'].with_context( self.env["rma.delivery.wizard"].with_context(
active_ids=rma.ids, active_ids=rma.ids, rma_delivery_type="return",
rma_delivery_type='return',
) )
) )
delivery_wizard = delivery_form.save() delivery_wizard = delivery_form.save()
@@ -479,18 +485,18 @@ class TestRma(SavepointCase):
self.assertEqual(rma.remaining_qty, 0) self.assertEqual(rma.remaining_qty, 0)
self.assertEqual(rma.delivered_qty_done, 2) self.assertEqual(rma.delivered_qty_done, 2)
self.assertEqual(rma.remaining_qty_to_done, 8) self.assertEqual(rma.remaining_qty_to_done, 8)
self.assertEqual(rma.state, 'waiting_return') self.assertEqual(rma.state, "waiting_return")
# remaining_qty is 0 but rma is not set to 'returned' until # remaining_qty is 0 but rma is not set to 'returned' until
# remaining_qty_to_done is less than or equal to 0 # remaining_qty_to_done is less than or equal to 0
picking_2 = second_move.picking_id picking_2 = second_move.picking_id
picking_2.button_validate() picking_2.button_validate()
self.assertEqual(picking_2.state, 'done') self.assertEqual(picking_2.state, "done")
self.assertEqual(rma.delivered_qty, 10) self.assertEqual(rma.delivered_qty, 10)
self.assertEqual(rma.remaining_qty, 0) self.assertEqual(rma.remaining_qty, 0)
self.assertEqual(rma.delivered_qty_done, 10) self.assertEqual(rma.delivered_qty_done, 10)
self.assertEqual(rma.remaining_qty_to_done, 0) self.assertEqual(rma.remaining_qty_to_done, 0)
# The RMA is now in 'returned' state # The RMA is now in 'returned' state
self.assertEqual(rma.state, 'returned') self.assertEqual(rma.state, "returned")
self.assertFalse(rma.can_be_refunded) self.assertFalse(rma.can_be_refunded)
self.assertFalse(rma.can_be_returned) self.assertFalse(rma.can_be_returned)
self.assertFalse(rma.can_be_replaced) self.assertFalse(rma.can_be_replaced)
@@ -498,53 +504,54 @@ class TestRma(SavepointCase):
def test_mass_return_to_customer(self): def test_mass_return_to_customer(self):
# Create, confirm and receive rma_1 # Create, confirm and receive rma_1
rma_1 = self._create_confirm_receive(self.partner, self.product, 10, rma_1 = self._create_confirm_receive(
self.rma_loc) self.partner, self.product, 10, self.rma_loc
)
# create, confirm and receive 3 more RMAs # create, confirm and receive 3 more RMAs
# rma_2: Same partner and same product as rma_1 # rma_2: Same partner and same product as rma_1
rma_2 = self._create_confirm_receive(self.partner, self.product, 15, rma_2 = self._create_confirm_receive(
self.rma_loc) self.partner, self.product, 15, self.rma_loc
)
# rma_3: Same partner and different product than rma_1 # rma_3: Same partner and different product than rma_1
product = self.product_product.create( product = self.product_product.create(
{'name': 'Product 2 test', 'type': 'product'}) {"name": "Product 2 test", "type": "product"}
rma_3 = self._create_confirm_receive(self.partner, product, 20, )
self.rma_loc) rma_3 = self._create_confirm_receive(self.partner, product, 20, self.rma_loc)
# rma_4: Different partner and same product as rma_1 # rma_4: Different partner and same product as rma_1
partner = self.res_partner.create({'name': 'Partner 2 test'}) partner = self.res_partner.create({"name": "Partner 2 test"})
rma_4 = self._create_confirm_receive(partner, product, 25, rma_4 = self._create_confirm_receive(partner, product, 25, self.rma_loc)
self.rma_loc)
# all rmas are ready to be returned to the customer # all rmas are ready to be returned to the customer
all_rmas = (rma_1 | rma_2 | rma_3 | rma_4) all_rmas = rma_1 | rma_2 | rma_3 | rma_4
self.assertEqual(all_rmas.mapped('state'), ['received'] * 4) self.assertEqual(all_rmas.mapped("state"), ["received"] * 4)
self.assertEqual(all_rmas.mapped('can_be_returned'), [True] * 4) self.assertEqual(all_rmas.mapped("can_be_returned"), [True] * 4)
# Mass return of those four RMAs # Mass return of those four RMAs
delivery_wizard = self.env['rma.delivery.wizard'].with_context( delivery_wizard = (
active_ids=all_rmas.ids, rma_delivery_type='return').create({}) self.env["rma.delivery.wizard"]
.with_context(active_ids=all_rmas.ids, rma_delivery_type="return")
.create({})
)
delivery_wizard.action_deliver() delivery_wizard.action_deliver()
# Two pickings were created # Two pickings were created
pick_1 = (rma_1 | rma_2 | rma_3).mapped('delivery_move_ids.picking_id') pick_1 = (rma_1 | rma_2 | rma_3).mapped("delivery_move_ids.picking_id")
pick_2 = rma_4.delivery_move_ids.picking_id pick_2 = rma_4.delivery_move_ids.picking_id
self.assertEqual(len(pick_1), 1) self.assertEqual(len(pick_1), 1)
self.assertEqual(len(pick_2), 1) self.assertEqual(len(pick_2), 1)
self.assertNotEqual(pick_1, pick_2) self.assertNotEqual(pick_1, pick_2)
self.assertEqual((pick_1 | pick_2).mapped('state'), ['assigned'] * 2) self.assertEqual((pick_1 | pick_2).mapped("state"), ["assigned"] * 2)
# One picking per partner # One picking per partner
self.assertNotEqual(pick_1.partner_id, pick_2.partner_id) self.assertNotEqual(pick_1.partner_id, pick_2.partner_id)
self.assertEqual( self.assertEqual(
pick_1.partner_id, pick_1.partner_id, (rma_1 | rma_2 | rma_3).mapped("partner_id"),
(rma_1 | rma_2 | rma_3).mapped('partner_id'),
) )
self.assertEqual(pick_2.partner_id, rma_4.partner_id) self.assertEqual(pick_2.partner_id, rma_4.partner_id)
# Each RMA of (rma_1, rma_2 and rma_3) is linked to a different # Each RMA of (rma_1, rma_2 and rma_3) is linked to a different
# line of picking_1 # line of picking_1
self.assertEqual(len(pick_1.move_lines), 3) self.assertEqual(len(pick_1.move_lines), 3)
self.assertEqual( self.assertEqual(
pick_1.move_lines.mapped('rma_id'), pick_1.move_lines.mapped("rma_id"), (rma_1 | rma_2 | rma_3),
(rma_1 | rma_2 | rma_3),
) )
self.assertEqual( self.assertEqual(
(rma_1 | rma_2 | rma_3).mapped('delivery_move_ids'), (rma_1 | rma_2 | rma_3).mapped("delivery_move_ids"), pick_1.move_lines,
pick_1.move_lines,
) )
# rma_4 is linked with the unique move of pick_2 # rma_4 is linked with the unique move of pick_2
self.assertEqual(len(pick_2.move_lines), 1) self.assertEqual(len(pick_2.move_lines), 1)
@@ -553,57 +560,56 @@ class TestRma(SavepointCase):
# Assert product and quantities are propagated correctly # Assert product and quantities are propagated correctly
for rma in all_rmas: for rma in all_rmas:
self.assertEqual(rma.product_id, rma.delivery_move_ids.product_id) self.assertEqual(rma.product_id, rma.delivery_move_ids.product_id)
self.assertEqual(rma.product_uom_qty, self.assertEqual(rma.product_uom_qty, rma.delivery_move_ids.product_uom_qty)
rma.delivery_move_ids.product_uom_qty) self.assertEqual(rma.product_uom, rma.delivery_move_ids.product_uom)
self.assertEqual(rma.product_uom,
rma.delivery_move_ids.product_uom)
rma.delivery_move_ids.quantity_done = rma.product_uom_qty rma.delivery_move_ids.quantity_done = rma.product_uom_qty
pick_1.button_validate() pick_1.button_validate()
pick_2.button_validate() pick_2.button_validate()
self.assertEqual(all_rmas.mapped('state'), ['returned'] * 4) self.assertEqual(all_rmas.mapped("state"), ["returned"] * 4)
def test_rma_from_picking_return(self): def test_rma_from_picking_return(self):
# Create a return from a delivery picking # Create a return from a delivery picking
origin_delivery = self._create_delivery() origin_delivery = self._create_delivery()
return_wizard = self.env['stock.return.picking'].with_context( return_wizard = (
active_id=origin_delivery.id, self.env["stock.return.picking"]
active_ids=origin_delivery.ids, .with_context(active_id=origin_delivery.id, active_ids=origin_delivery.ids,)
).create({'create_rma': True}) .create({"create_rma": True})
)
picking_action = return_wizard.create_returns() picking_action = return_wizard.create_returns()
# Each origin move is linked to a different RMA # Each origin move is linked to a different RMA
origin_moves = origin_delivery.move_lines origin_moves = origin_delivery.move_lines
self.assertTrue(origin_moves[0].rma_ids) self.assertTrue(origin_moves[0].rma_ids)
self.assertTrue(origin_moves[1].rma_ids) self.assertTrue(origin_moves[1].rma_ids)
rmas = origin_moves.mapped('rma_ids') rmas = origin_moves.mapped("rma_ids")
self.assertEqual(rmas.mapped('state'), ['confirmed']*2) self.assertEqual(rmas.mapped("state"), ["confirmed"] * 2)
# Each reception move is linked one of the generated RMAs # Each reception move is linked one of the generated RMAs
reception = self.env['stock.picking'].browse(picking_action['res_id']) reception = self.env["stock.picking"].browse(picking_action["res_id"])
reception_moves = reception.move_lines reception_moves = reception.move_lines
self.assertTrue(reception_moves[0].rma_receiver_ids) self.assertTrue(reception_moves[0].rma_receiver_ids)
self.assertTrue(reception_moves[1].rma_receiver_ids) self.assertTrue(reception_moves[1].rma_receiver_ids)
self.assertEqual(reception_moves.mapped('rma_receiver_ids'), rmas) self.assertEqual(reception_moves.mapped("rma_receiver_ids"), rmas)
# Validate the reception picking to set rmas to 'received' state # Validate the reception picking to set rmas to 'received' state
reception_moves[0].quantity_done = reception_moves[0].product_uom_qty reception_moves[0].quantity_done = reception_moves[0].product_uom_qty
reception_moves[1].quantity_done = reception_moves[1].product_uom_qty reception_moves[1].quantity_done = reception_moves[1].product_uom_qty
reception.button_validate() reception.button_validate()
self.assertEqual(rmas.mapped('state'), ['received'] * 2) self.assertEqual(rmas.mapped("state"), ["received"] * 2)
def test_split(self): def test_split(self):
origin_delivery = self._create_delivery() origin_delivery = self._create_delivery()
rma_form = Form(self.env['rma']) rma_form = Form(self.env["rma"])
rma_form.partner_id = self.partner rma_form.partner_id = self.partner
rma_form.picking_id = origin_delivery rma_form.picking_id = origin_delivery
rma_form.move_id = origin_delivery.move_lines.filtered( rma_form.move_id = origin_delivery.move_lines.filtered(
lambda r: r.product_id == self.product) lambda r: r.product_id == self.product
)
rma = rma_form.save() rma = rma_form.save()
rma.action_confirm() rma.action_confirm()
rma.reception_move_id.quantity_done = 10 rma.reception_move_id.quantity_done = 10
rma.reception_move_id.picking_id.action_done() rma.reception_move_id.picking_id.action_done()
# Return quantity 4 of the same product to the customer # Return quantity 4 of the same product to the customer
delivery_form = Form( delivery_form = Form(
self.env['rma.delivery.wizard'].with_context( self.env["rma.delivery.wizard"].with_context(
active_ids=rma.ids, active_ids=rma.ids, rma_delivery_type="return",
rma_delivery_type='return',
) )
) )
delivery_form.product_uom_qty = 4 delivery_form.product_uom_qty = 4
@@ -611,23 +617,24 @@ class TestRma(SavepointCase):
delivery_wizard.action_deliver() delivery_wizard.action_deliver()
rma.delivery_move_ids.quantity_done = 4 rma.delivery_move_ids.quantity_done = 4
rma.delivery_move_ids.picking_id.button_validate() rma.delivery_move_ids.picking_id.button_validate()
self.assertEqual(rma.state, 'waiting_return') self.assertEqual(rma.state, "waiting_return")
# Extract the remaining quantity to another RMA # Extract the remaining quantity to another RMA
self.assertTrue(rma.can_be_split) self.assertTrue(rma.can_be_split)
split_wizard = self.env['rma.split.wizard'].with_context( split_wizard = (
active_id=rma.id, self.env["rma.split.wizard"]
active_ids=rma.ids, .with_context(active_id=rma.id, active_ids=rma.ids,)
).create({}) .create({})
)
action = split_wizard.action_split() action = split_wizard.action_split()
# Check rma is set to 'returned' after split. Check new_rma values # Check rma is set to 'returned' after split. Check new_rma values
self.assertEqual(rma.state, 'returned') self.assertEqual(rma.state, "returned")
new_rma = self.env['rma'].browse(action['res_id']) new_rma = self.env["rma"].browse(action["res_id"])
self.assertEqual(new_rma.origin_split_rma_id, rma) self.assertEqual(new_rma.origin_split_rma_id, rma)
self.assertEqual(new_rma.delivered_qty, 0) self.assertEqual(new_rma.delivered_qty, 0)
self.assertEqual(new_rma.remaining_qty, 6) self.assertEqual(new_rma.remaining_qty, 6)
self.assertEqual(new_rma.delivered_qty_done, 0) self.assertEqual(new_rma.delivered_qty_done, 0)
self.assertEqual(new_rma.remaining_qty_to_done, 6) self.assertEqual(new_rma.remaining_qty_to_done, 6)
self.assertEqual(new_rma.state, 'received') self.assertEqual(new_rma.state, "received")
self.assertTrue(new_rma.can_be_refunded) self.assertTrue(new_rma.can_be_refunded)
self.assertTrue(new_rma.can_be_returned) self.assertTrue(new_rma.can_be_returned)
self.assertTrue(new_rma.can_be_replaced) self.assertTrue(new_rma.can_be_replaced)
@@ -638,21 +645,19 @@ class TestRma(SavepointCase):
self.assertEqual(new_rma.reception_move_id.quantity_done, 10) self.assertEqual(new_rma.reception_move_id.quantity_done, 10)
def test_rma_to_receive_on_delete_invoice(self): def test_rma_to_receive_on_delete_invoice(self):
rma = self._create_confirm_receive(self.partner, self.product, 10, rma = self._create_confirm_receive(self.partner, self.product, 10, self.rma_loc)
self.rma_loc)
rma.action_refund() rma.action_refund()
self.assertEqual(rma.state, 'refunded') self.assertEqual(rma.state, "refunded")
rma.refund_id.unlink() rma.refund_id.unlink()
self.assertFalse(rma.refund_id) self.assertFalse(rma.refund_id)
self.assertEqual(rma.state, 'received') self.assertEqual(rma.state, "received")
self.assertTrue(rma.can_be_refunded) self.assertTrue(rma.can_be_refunded)
self.assertTrue(rma.can_be_returned) self.assertTrue(rma.can_be_returned)
self.assertTrue(rma.can_be_replaced) self.assertTrue(rma.can_be_replaced)
def test_rma_picking_type_default_values(self): def test_rma_picking_type_default_values(self):
warehouse = self.env['stock.warehouse'].create({ warehouse = self.env["stock.warehouse"].create(
'name': 'Stock - RMA Test', {"name": "Stock - RMA Test", "code": "SRT",}
'code': 'SRT', )
})
self.assertFalse(warehouse.rma_in_type_id.use_create_lots) self.assertFalse(warehouse.rma_in_type_id.use_create_lots)
self.assertTrue(warehouse.rma_in_type_id.use_existing_lots) self.assertTrue(warehouse.rma_in_type_id.use_existing_lots)

View File

@@ -1,24 +1,19 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2020 Tecnativa - Ernesto Tejeda <!-- Copyright 2020 Tecnativa - Ernesto Tejeda
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). --> License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
<odoo> <odoo>
<menuitem <menuitem id="rma_menu" name="RMA" web_icon="rma,static/description/icon.png" />
id="rma_menu" <menuitem id="rma_orders_menu" parent="rma_menu" name="Orders" sequence="10" />
name="RMA"
web_icon="rma,static/description/icon.png"/>
<menuitem
id="rma_orders_menu"
parent="rma_menu"
name="Orders"
sequence="10"/>
<menuitem <menuitem
id="rma_reporting_menu" id="rma_reporting_menu"
parent="rma_menu" parent="rma_menu"
name="Reporting" name="Reporting"
sequence="20"/> sequence="20"
/>
<menuitem <menuitem
id="rma_configuration_menu" id="rma_configuration_menu"
parent="rma_menu" parent="rma_menu"
name="Configuration" name="Configuration"
sequence="30"/> sequence="30"
/>
</odoo> </odoo>

View File

@@ -5,51 +5,76 @@
<t t-set="doc" t-value="doc.with_context(lang=doc.partner_id.lang)" /> <t t-set="doc" t-value="doc.with_context(lang=doc.partner_id.lang)" />
<t t-if="doc.partner_id"> <t t-if="doc.partner_id">
<t t-set="address"> <t t-set="address">
<div t-field="doc.partner_id" <div
t-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": True}' /> t-field="doc.partner_id"
<p t-if="doc.partner_id.vat"><t t-esc="doc.company_id.country_id.vat_label or 'Tax ID'"/>: <span t-field="doc.partner_id.vat"/></p> t-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": True}'
/>
<p t-if="doc.partner_id.vat"><t
t-esc="doc.company_id.country_id.vat_label or 'Tax ID'"
/>: <span t-field="doc.partner_id.vat" /></p>
</t> </t>
</t> </t>
<t t-if="(doc.partner_id or doc.partner_invoice_id) and doc.partner_id != doc.partner_invoice_id"> <t
t-if="(doc.partner_id or doc.partner_invoice_id) and doc.partner_id != doc.partner_invoice_id"
>
<t t-set="information_block"> <t t-set="information_block">
<strong>Invoicing address:</strong> <strong>Invoicing address:</strong>
<div t-field="doc.partner_invoice_id" <div
t-options='{"widget": "contact", "fields": ["address", "name", "phone"], "no_marker": True, "phone_icons": True}'/> t-field="doc.partner_invoice_id"
t-options='{"widget": "contact", "fields": ["address", "name", "phone"], "no_marker": True, "phone_icons": True}'
/>
</t> </t>
</t> </t>
<div class="page"> <div class="page">
<h2 class="mt16"> <h2 class="mt16">
<span t-if="doc.state not in ['draft', 'cancelled']">RMA # </span> <span t-if="doc.state not in ['draft', 'cancelled']">RMA # </span>
<span t-field="doc.name"/> <span t-field="doc.name" />
</h2> </h2>
<div class="row mt32 mb32" id="general_information"> <div class="row mt32 mb32" id="general_information">
<div t-if="doc.origin" class="col-auto mw-100 mb-2"> <div t-if="doc.origin" class="col-auto mw-100 mb-2">
<strong>Origin:</strong> <strong>Origin:</strong>
<p class="m-0" t-field="doc.origin"/> <p class="m-0" t-field="doc.origin" />
</div> </div>
<div class="col-auto mw-100 mb-2"> <div class="col-auto mw-100 mb-2">
<strong>Date:</strong> <strong>Date:</strong>
<p class="m-0" t-field="doc.date"/> <p class="m-0" t-field="doc.date" />
</div> </div>
<div t-if="doc.deadline" class="col-auto mw-100 mb-2"> <div t-if="doc.deadline" class="col-auto mw-100 mb-2">
<strong>Deadline:</strong> <strong>Deadline:</strong>
<p class="m-0" t-field="doc.deadline"/> <p class="m-0" t-field="doc.deadline" />
</div> </div>
<div t-if="doc.user_id" class="col-auto mw-100 mb-2"> <div t-if="doc.user_id" class="col-auto mw-100 mb-2">
<strong>Responsible:</strong> <strong>Responsible:</strong>
<p class="m-0" t-field="doc.user_id"/> <p class="m-0" t-field="doc.user_id" />
</div> </div>
<div class="col-auto mw-100 mb-2"> <div class="col-auto mw-100 mb-2">
<strong>State:</strong> <strong>State:</strong>
<p class="m-0"> <p class="m-0">
<t t-if="doc.state in ['refunded', 'replaced', 'returned']"> <t t-if="doc.state in ['refunded', 'replaced', 'returned']">
<span class="small text-success orders_label_text_align"><i class="fa fa-fw fa-check"/> <b><span t-field="doc.state"/></b></span> <span
class="small text-success orders_label_text_align"
>
<i class="fa fa-fw fa-check" />
<b>
<span t-field="doc.state" />
</b>
</span>
</t> </t>
<t t-elif="doc.state in ['cancelled', 'locked']"> <t t-elif="doc.state in ['cancelled', 'locked']">
<span class="small text-danger orders_label_text_align"><i class="fa fa-fw fa-times"/> <b><span t-field="doc.state"/></b></span> <span class="small text-danger orders_label_text_align">
<i class="fa fa-fw fa-times" />
<b>
<span t-field="doc.state" />
</b>
</span>
</t> </t>
<t t-else=""> <t t-else="">
<span class="small text-info orders_label_text_align"><i class="fa fa-fw fa-clock-o"/> <b><span t-field="doc.state"/></b></span> <span class="small text-info orders_label_text_align">
<i class="fa fa-fw fa-clock-o" />
<b>
<span t-field="doc.state" />
</b>
</span>
</t> </t>
</p> </p>
</div> </div>
@@ -57,26 +82,26 @@
<div class="row mt32 mb32" id="product_information"> <div class="row mt32 mb32" id="product_information">
<div t-if="doc.picking_id" class="col-auto mw-100 mb-2"> <div t-if="doc.picking_id" class="col-auto mw-100 mb-2">
<strong>Origin delivery:</strong> <strong>Origin delivery:</strong>
<p class="m-0" t-field="doc.picking_id"/> <p class="m-0" t-field="doc.picking_id" />
</div> </div>
<div t-if="doc.move_id" class="col-auto mw-100 mb-2"> <div t-if="doc.move_id" class="col-auto mw-100 mb-2">
<strong>Move:</strong> <strong>Move:</strong>
<p class="m-0" t-field="doc.move_id"/> <p class="m-0" t-field="doc.move_id" />
</div> </div>
<div t-if="doc.product_id" class="col-auto mw-100 mb-2"> <div t-if="doc.product_id" class="col-auto mw-100 mb-2">
<strong>Product:</strong> <strong>Product:</strong>
<p class="m-0" t-field="doc.product_id"/> <p class="m-0" t-field="doc.product_id" />
</div> </div>
<div t-if="doc.product_id" class="col-auto mw-100 mb-2"> <div t-if="doc.product_id" class="col-auto mw-100 mb-2">
<strong>Quantity:</strong> <strong>Quantity:</strong>
<p class="m-0" t-field="doc.product_uom_qty"> <p class="m-0" t-field="doc.product_uom_qty">
<span t-field="doc.product_uom_qty"/> <span t-field="doc.product_uom_qty" />
<span t-field="doc.uom_id" groups="uom.group_uom"/> <span t-field="doc.uom_id" groups="uom.group_uom" />
</p> </p>
</div> </div>
<div t-if="doc.delivered_qty" class="col-auto mw-100 mb-2"> <div t-if="doc.delivered_qty" class="col-auto mw-100 mb-2">
<strong>Delivered qty:</strong> <strong>Delivered qty:</strong>
<p class="m-0" t-field="doc.delivered_qty"/> <p class="m-0" t-field="doc.delivered_qty" />
</div> </div>
</div> </div>
<div t-if="doc.description"> <div t-if="doc.description">

View File

@@ -1,22 +1,22 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2020 Tecnativa - Ernesto Tejeda <!-- Copyright 2020 Tecnativa - Ernesto Tejeda
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). --> License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
<odoo> <odoo>
<record id="view_partner_form" model="ir.ui.view"> <record id="view_partner_form" model="ir.ui.view">
<field name="name">res.partner.form</field> <field name="name">res.partner.form</field>
<field name="model">res.partner</field> <field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/> <field name="inherit_id" ref="base.view_partner_form" />
<field name="groups_id" eval="[(4, ref('rma.rma_group_user_own'))]"/> <field name="groups_id" eval="[(4, ref('rma.rma_group_user_own'))]" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<div name="button_box"> <div name="button_box">
<button name="action_view_rma" <button
name="action_view_rma"
type="object" type="object"
class="oe_stat_button" class="oe_stat_button"
icon="fa-reply" icon="fa-reply"
attrs="{'invisible': [('rma_count', '=', 0)]}"> attrs="{'invisible': [('rma_count', '=', 0)]}"
<field name="rma_count" >
widget="statinfo" <field name="rma_count" widget="statinfo" string="RMA" />
string="RMA"/>
</button> </button>
</div> </div>
</field> </field>

View File

@@ -1,33 +1,43 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2020 Tecnativa - Ernesto Tejeda <!-- Copyright 2020 Tecnativa - Ernesto Tejeda
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). --> License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
<odoo> <odoo>
<template id="portal_my_home_menu_rma" name="Portal layout : RMA menu entries" inherit_id="portal.portal_breadcrumbs" priority="35"> <template
id="portal_my_home_menu_rma"
name="Portal layout : RMA menu entries"
inherit_id="portal.portal_breadcrumbs"
priority="35"
>
<xpath expr="//ol[hasclass('o_portal_submenu')]" position="inside"> <xpath expr="//ol[hasclass('o_portal_submenu')]" position="inside">
<li t-if="page_name == 'RMA'" t-attf-class="breadcrumb-item #{'active ' if not rma else ''}"> <li
t-if="page_name == 'RMA'"
t-attf-class="breadcrumb-item #{'active ' if not rma else ''}"
>
<a t-if="rma" t-attf-href="/my/rmas?{{ keep_query() }}">RMA Orders</a> <a t-if="rma" t-attf-href="/my/rmas?{{ keep_query() }}">RMA Orders</a>
<t t-else="">RMA Orders</t> <t t-else="">RMA Orders</t>
</li> </li>
<li t-if="rma" class="breadcrumb-item active"> <li t-if="rma" class="breadcrumb-item active">
<t t-esc="rma.name"/> <t t-esc="rma.name" />
</li> </li>
</xpath> </xpath>
</template> </template>
<template
<template id="portal_my_home_rma" name="Portal My Home : RMA entries" inherit_id="portal.portal_my_home" priority="30"> id="portal_my_home_rma"
name="Portal My Home : RMA entries"
inherit_id="portal.portal_my_home"
priority="30"
>
<xpath expr="//div[hasclass('o_portal_docs')]" position="inside"> <xpath expr="//div[hasclass('o_portal_docs')]" position="inside">
<t t-call="portal.portal_docs_entry" t-if="rma_count"> <t t-call="portal.portal_docs_entry" t-if="rma_count">
<t t-set="title">RMA Orders</t> <t t-set="title">RMA Orders</t>
<t t-set="url" t-value="'/my/rmas'"/> <t t-set="url" t-value="'/my/rmas'" />
<t t-set="count" t-value="rma_count"/> <t t-set="count" t-value="rma_count" />
</t> </t>
</xpath> </xpath>
</template> </template>
<template id="portal_my_rmas" name="My RMA Orders"> <template id="portal_my_rmas" name="My RMA Orders">
<t t-call="portal.portal_layout"> <t t-call="portal.portal_layout">
<t t-set="breadcrumbs_searchbar" t-value="True"/> <t t-set="breadcrumbs_searchbar" t-value="True" />
<t t-call="portal.portal_searchbar"> <t t-call="portal.portal_searchbar">
<t t-set="title">RMA Orders</t> <t t-set="title">RMA Orders</t>
</t> </t>
@@ -45,16 +55,27 @@
<t t-foreach="rmas" t-as="rma"> <t t-foreach="rmas" t-as="rma">
<tr> <tr>
<td> <td>
<a t-att-href="rma.get_portal_url()" t-att-title="rma.name"> <a
<t t-esc="rma.name"/> t-att-href="rma.get_portal_url()"
t-att-title="rma.name"
>
<t t-esc="rma.name" />
</a> </a>
</td> </td>
<td class="d-none d-md-table-cell"><span t-field="rma.date"/></td> <td class="d-none d-md-table-cell">
<span t-field="rma.date" />
</td>
<!-- Portal users don't have access to unpublished products --> <!-- Portal users don't have access to unpublished products -->
<td><span t-esc="rma.sudo().product_id.display_name"/></td> <td>
<td class='text-right'><span t-field="rma.product_uom_qty"/></td> <span t-esc="rma.sudo().product_id.display_name" />
</td>
<td class='text-right'>
<span t-field="rma.product_uom_qty" />
</td>
<td class="d-none d-md-table-cell tx_status"> <td class="d-none d-md-table-cell tx_status">
<span class="badge badge-pill badge-secondary"><span t-field="rma.state"/></span> <span class="badge badge-pill badge-secondary">
<span t-field="rma.state" />
</span>
</td> </td>
</tr> </tr>
</t> </t>
@@ -62,35 +83,63 @@
</t> </t>
</t> </t>
</template> </template>
<template id="portal_rma_page" name="My RMA"> <template id="portal_rma_page" name="My RMA">
<t t-call="portal.portal_layout"> <t t-call="portal.portal_layout">
<t t-set="o_portal_fullwidth_alert" groups="rma.rma_group_user_own"> <t t-set="o_portal_fullwidth_alert" groups="rma.rma_group_user_own">
<t t-call="portal.portal_back_in_edit_mode"> <t t-call="portal.portal_back_in_edit_mode">
<t t-set="backend_url" t-value="'/web#return_label=Website&amp;model=rma&amp;id=%s&amp;view_type=form' % (rma.id)"/> <t
t-set="backend_url"
t-value="'/web#return_label=Website&amp;model=rma&amp;id=%s&amp;view_type=form' % (rma.id)"
/>
</t> </t>
</t> </t>
<t t-call="portal.portal_record_layout"> <t t-call="portal.portal_record_layout">
<t t-set="card_header"> <t t-set="card_header">
<h5 class="mb-0"> <h5 class="mb-0">
<span> <span>
RMA Order - <span t-field="rma.name"/> RMA Order - <span t-field="rma.name" />
</span> </span>
<span style="position: absolute; left: 50%;" class="d-none d-sm-inline"> <span
<a t-att-href="rma.get_portal_url(report_type='pdf', download=True)"> style="position: absolute; left: 50%;"
<i class="fa fa-download" role="img" aria-label="Download" title="Download"/> class="d-none d-sm-inline"
>
<a
t-att-href="rma.get_portal_url(report_type='pdf', download=True)"
>
<i
class="fa fa-download"
role="img"
aria-label="Download"
title="Download"
/>
</a> </a>
</span> </span>
<span class="float-right"> <span class="float-right">
<t t-if="rma.state in ['refunded', 'returned', 'replaced']"> <t t-if="rma.state in ['refunded', 'returned', 'replaced']">
<span class="small text-success orders_label_text_align"><i class="fa fa-fw fa-check"/> <b><span t-field="rma.state"/></b></span> <span
class="small text-success orders_label_text_align"
>
<i class="fa fa-fw fa-check" />
<b>
<span t-field="rma.state" />
</b>
</span>
</t> </t>
<t t-elif="rma.state in ['cancelled', 'locked']"> <t t-elif="rma.state in ['cancelled', 'locked']">
<span class="small text-danger orders_label_text_align"><i class="fa fa-fw fa-times"/> <b><span t-field="rma.state"/></b></span> <span class="small text-danger orders_label_text_align">
<i class="fa fa-fw fa-times" />
<b>
<span t-field="rma.state" />
</b>
</span>
</t> </t>
<t t-else=""> <t t-else="">
<span class="small text-info orders_label_text_align"><i class="fa fa-fw fa-clock-o"/> <b><span t-field="rma.state"/></b></span> <span class="small text-info orders_label_text_align">
<i class="fa fa-fw fa-clock-o" />
<b>
<span t-field="rma.state" />
</b>
</span>
</t> </t>
</span> </span>
</h5> </h5>
@@ -98,27 +147,60 @@
<t t-set="card_body"> <t t-set="card_body">
<div id="general_information"> <div id="general_information">
<div class="row mt4"> <div class="row mt4">
<div t-if="rma.partner_id" class="col-12 col-md-6 mb-4 mb-md-0"> <div
<h6><strong>Customer:</strong></h6> t-if="rma.partner_id"
class="col-12 col-md-6 mb-4 mb-md-0"
>
<h6>
<strong>Customer:</strong>
</h6>
<div class="row"> <div class="row">
<div class="col flex-grow-0 pr-3"> <div class="col flex-grow-0 pr-3">
<img t-if="rma.partner_id.image" class="rounded-circle mt-1 o_portal_contact_img" t-att-src="image_data_uri(rma.partner_id.image)" alt="Contact"/> <img
<img t-else="" class="rounded-circle mt-1 o_portal_contact_img" src="/web/static/src/img/user_menu_avatar.png" alt="Contact"/> t-if="rma.partner_id.image"
class="rounded-circle mt-1 o_portal_contact_img"
t-att-src="image_data_uri(rma.partner_id.image)"
alt="Contact"
/>
<img
t-else=""
class="rounded-circle mt-1 o_portal_contact_img"
src="/web/static/src/img/user_menu_avatar.png"
alt="Contact"
/>
</div> </div>
<div class="col pl-sm-0"> <div class="col pl-sm-0">
<address t-field="rma.partner_id" t-options='{"widget": "contact", "fields": ["name", "email", "phone"]}'/> <address
t-field="rma.partner_id"
t-options='{"widget": "contact", "fields": ["name", "email", "phone"]}'
/>
</div> </div>
</div> </div>
</div> </div>
<div t-if="rma.user_id" class="col-12 col-md-6"> <div t-if="rma.user_id" class="col-12 col-md-6">
<h6><strong>Responsible:</strong></h6> <h6>
<strong>Responsible:</strong>
</h6>
<div class="row"> <div class="row">
<div class="col flex-grow-0 pr-3"> <div class="col flex-grow-0 pr-3">
<img t-if="rma.user_id.image" class="rounded-circle mt-1 o_portal_contact_img" t-att-src="image_data_uri(rma.user_id.image)" alt="Contact"/> <img
<img t-else="" class="rounded-circle mt-1 o_portal_contact_img" src="/web/static/src/img/user_menu_avatar.png" alt="Contact"/> t-if="rma.user_id.image"
class="rounded-circle mt-1 o_portal_contact_img"
t-att-src="image_data_uri(rma.user_id.image)"
alt="Contact"
/>
<img
t-else=""
class="rounded-circle mt-1 o_portal_contact_img"
src="/web/static/src/img/user_menu_avatar.png"
alt="Contact"
/>
</div> </div>
<div class="col pl-sm-0"> <div class="col pl-sm-0">
<address t-field="rma.user_id" t-options='{"widget": "contact", "fields": ["name", "email", "phone"]}'/> <address
t-field="rma.user_id"
t-options='{"widget": "contact", "fields": ["name", "email", "phone"]}'
/>
</div> </div>
</div> </div>
</div> </div>
@@ -130,25 +212,33 @@
<strong>Origin delivery</strong> <strong>Origin delivery</strong>
</div> </div>
<div class="col-12 col-sm-8"> <div class="col-12 col-sm-8">
<span t-field="rma.picking_id"/> <span t-field="rma.picking_id" />
</div> </div>
</div> </div>
<!-- We need to prevent access errors if the product is <!-- We need to prevent access errors if the product is
unpublished--> unpublished-->
<div t-if="rma.sudo().product_id" class="row mb-2 mb-sm-1"> <div
t-if="rma.sudo().product_id"
class="row mb-2 mb-sm-1"
>
<div class="col-12 col-sm-4"> <div class="col-12 col-sm-4">
<strong>Product</strong> <strong>Product</strong>
</div> </div>
<div class="col-12 col-sm-8"> <div class="col-12 col-sm-8">
<span t-esc="rma.sudo().product_id.display_name"/> <span
t-esc="rma.sudo().product_id.display_name"
/>
</div> </div>
</div> </div>
<div t-if="rma.product_uom_qty" class="row mb-2 mb-sm-1"> <div
t-if="rma.product_uom_qty"
class="row mb-2 mb-sm-1"
>
<div class="col-12 col-sm-4"> <div class="col-12 col-sm-4">
<strong>Quantity</strong> <strong>Quantity</strong>
</div> </div>
<div class="col-12 col-sm-8"> <div class="col-12 col-sm-8">
<span t-field="rma.product_uom_qty"/> <span t-field="rma.product_uom_qty" />
</div> </div>
</div> </div>
<div t-if="rma.delivered_qty" class="row"> <div t-if="rma.delivered_qty" class="row">
@@ -156,7 +246,7 @@
<strong>Delivered quantity</strong> <strong>Delivered quantity</strong>
</div> </div>
<div class="col-12 col-sm-8"> <div class="col-12 col-sm-8">
<span t-field="rma.delivered_qty"/> <span t-field="rma.delivered_qty" />
</div> </div>
</div> </div>
</div> </div>
@@ -166,7 +256,10 @@
<strong>RMA Date</strong> <strong>RMA Date</strong>
</div> </div>
<div class="col-12 col-sm-8"> <div class="col-12 col-sm-8">
<span t-field="rma.date" t-options='{"widget": "date"}'/> <span
t-field="rma.date"
t-options='{"widget": "date"}'
/>
</div> </div>
</div> </div>
<div t-if="rma.deadline" class="row mb-2 mb-sm-1"> <div t-if="rma.deadline" class="row mb-2 mb-sm-1">
@@ -174,7 +267,10 @@
<strong>Deadline</strong> <strong>Deadline</strong>
</div> </div>
<div class="col-12 col-sm-8"> <div class="col-12 col-sm-8">
<span t-field="rma.deadline" t-options='{"widget": "date"}'/> <span
t-field="rma.deadline"
t-options='{"widget": "date"}'
/>
</div> </div>
</div> </div>
<div t-if="rma.origin" class="row"> <div t-if="rma.origin" class="row">
@@ -182,72 +278,170 @@
<strong>Origin</strong> <strong>Origin</strong>
</div> </div>
<div class="col-12 col-sm-8"> <div class="col-12 col-sm-8">
<span t-field="rma.origin"/> <span t-field="rma.origin" />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<section t-if="rma.reception_move_id" id="reception_section" style="page-break-inside: auto;" class="mt32"> <section
t-if="rma.reception_move_id"
id="reception_section"
style="page-break-inside: auto;"
class="mt32"
>
<strong class="d-block mb-1">Reception</strong> <strong class="d-block mb-1">Reception</strong>
<t t-set="picking" t-value="rma.reception_move_id.picking_id"/> <t t-set="picking" t-value="rma.reception_move_id.picking_id" />
<t t-set="report_url" t-value="'/my/rma/picking/pdf/%s/%s?%s' % (rma.id, picking.id, keep_query())"/> <t
<a class="list-group-item list-group-item-action d-flex flex-wrap align-items-center justify-content-between py-2 px-3" t-att-href="report_url"> t-set="report_url"
t-value="'/my/rma/picking/pdf/%s/%s?%s' % (rma.id, picking.id, keep_query())"
/>
<a
class="list-group-item list-group-item-action d-flex flex-wrap align-items-center justify-content-between py-2 px-3"
t-att-href="report_url"
>
<div> <div>
<i class="fa fa-truck mr-1" role="img" aria-label="Download" title="Download"/> <i
<span t-esc="picking.name" class="mr-lg-3"/> class="fa fa-truck mr-1"
<div class="d-lg-inline-block">Date: <span class="text-muted" t-field="picking.date"/></div> role="img"
aria-label="Download"
title="Download"
/>
<span t-esc="picking.name" class="mr-lg-3" />
<div class="d-lg-inline-block">Date: <span
class="text-muted"
t-field="picking.date"
/></div>
</div> </div>
<t t-if="picking.state == 'done'"> <t t-if="picking.state == 'done'">
<span class="badge badge-success label-text-align"><i class="fa fa-fw fa-truck"/> Shipped</span> <span class="badge badge-success label-text-align"><i
class="fa fa-fw fa-truck"
/> Shipped</span>
</t> </t>
<t t-if="picking.state == 'partially_available'"> <t t-if="picking.state == 'partially_available'">
<span class="badge badge-warning label-text-align"><i class="fa fa-fw fa-clock-o"/> Partially Available</span> <span class="badge badge-warning label-text-align"><i
class="fa fa-fw fa-clock-o"
/> Partially Available</span>
</t> </t>
<t t-if="picking.state == 'cancel'"> <t t-if="picking.state == 'cancel'">
<span class="badge badge-danger label-text-align"><i class="fa fa-fw fa-times"/> Cancelled</span> <span class="badge badge-danger label-text-align"><i
class="fa fa-fw fa-times"
/> Cancelled</span>
</t> </t>
<t t-if="picking.state in ['draft', 'waiting', 'confirmed', 'assigned']"> <t
<span class="badge badge-info label-text-align"><i class="fa fa-fw fa-clock-o"/> Preparation</span> t-if="picking.state in ['draft', 'waiting', 'confirmed', 'assigned']"
>
<span class="badge badge-info label-text-align"><i
class="fa fa-fw fa-clock-o"
/> Preparation</span>
</t> </t>
</a> </a>
</section> </section>
<section t-if="rma.refund_id" id="refund_section" style="page-break-inside: auto;" class="mt32"> <section
t-if="rma.refund_id"
id="refund_section"
style="page-break-inside: auto;"
class="mt32"
>
<strong class="d-block mb-1">Refund</strong> <strong class="d-block mb-1">Refund</strong>
<t t-set="refund" t-value="rma.refund_id"/> <t t-set="refund" t-value="rma.refund_id" />
<t t-set="report_url" t-value="refund.get_portal_url(report_type='pdf')"/> <t
<a class="list-group-item list-group-item-action d-flex flex-wrap align-items-center justify-content-between py-2 px-3" t-att-href="report_url"> t-set="report_url"
t-value="refund.get_portal_url(report_type='pdf')"
/>
<a
class="list-group-item list-group-item-action d-flex flex-wrap align-items-center justify-content-between py-2 px-3"
t-att-href="report_url"
>
<div> <div>
<i class="fa fa-pencil-square-o mr-1" role="img" aria-label="Download" title="Download"/> <i
<span t-esc="refund.number" class="mr-lg-3"/> class="fa fa-pencil-square-o mr-1"
<div class="d-lg-inline-block">Date: <span class="text-muted" t-field="refund.date_invoice"/></div> role="img"
aria-label="Download"
title="Download"
/>
<span t-esc="refund.number" class="mr-lg-3" />
<div class="d-lg-inline-block">Date: <span
class="text-muted"
t-field="refund.date_invoice"
/></div>
</div> </div>
<span t-if="refund.state == 'paid'" class="small text-success orders_label_text_align"><i class="fa fa-fw fa-check"/> <b>Paid</b></span> <span
<span t-else="" class="small text-info orders_label_text_align"><i class="fa fa-fw fa-clock-o"/> <b>Waiting Payment</b></span> t-if="refund.state == 'paid'"
class="small text-success orders_label_text_align"
>
<i class="fa fa-fw fa-check" />
<b>Paid</b>
</span>
<span
t-else=""
class="small text-info orders_label_text_align"
>
<i class="fa fa-fw fa-clock-o" />
<b>Waiting Payment</b>
</span>
</a> </a>
</section> </section>
<section t-if="rma.delivery_move_ids" id="reception_section" style="page-break-inside: auto;" class="mt32"> <section
t-if="rma.delivery_move_ids"
id="reception_section"
style="page-break-inside: auto;"
class="mt32"
>
<strong class="d-block mb-1">Delivery</strong> <strong class="d-block mb-1">Delivery</strong>
<ul class="list-group mb-4"> <ul class="list-group mb-4">
<t t-foreach="rma.delivery_move_ids.mapped('picking_id')" t-as="picking"> <t
<t t-set="report_url" t-value="'/my/rma/picking/pdf/%s/%s?%s' % (rma.id, picking.id, keep_query())"/> t-foreach="rma.delivery_move_ids.mapped('picking_id')"
<a class="list-group-item list-group-item-action d-flex flex-wrap align-items-center justify-content-between py-2 px-3" t-att-href="report_url"> t-as="picking"
>
<t
t-set="report_url"
t-value="'/my/rma/picking/pdf/%s/%s?%s' % (rma.id, picking.id, keep_query())"
/>
<a
class="list-group-item list-group-item-action d-flex flex-wrap align-items-center justify-content-between py-2 px-3"
t-att-href="report_url"
>
<div> <div>
<i class="fa fa-truck mr-1" role="img" aria-label="Download" title="Download"/> <i
<span t-esc="picking.name" class="mr-lg-3"/> class="fa fa-truck mr-1"
<div class="d-lg-inline-block">Date: <span class="text-muted" t-field="picking.date"/></div> role="img"
aria-label="Download"
title="Download"
/>
<span t-esc="picking.name" class="mr-lg-3" />
<div class="d-lg-inline-block">Date: <span
class="text-muted"
t-field="picking.date"
/></div>
</div> </div>
<t t-if="picking.state == 'done'"> <t t-if="picking.state == 'done'">
<span class="badge badge-success label-text-align"><i class="fa fa-fw fa-truck"/> Shipped</span> <span
class="badge badge-success label-text-align"
><i class="fa fa-fw fa-truck" /> Shipped</span>
</t> </t>
<t t-if="picking.state == 'partially_available'"> <t t-if="picking.state == 'partially_available'">
<span class="badge badge-warning label-text-align"><i class="fa fa-fw fa-clock-o"/> Partially Available</span> <span
class="badge badge-warning label-text-align"
><i
class="fa fa-fw fa-clock-o"
/> Partially Available</span>
</t> </t>
<t t-if="picking.state == 'cancel'"> <t t-if="picking.state == 'cancel'">
<span class="badge badge-danger label-text-align"><i class="fa fa-fw fa-times"/> Cancelled</span> <span
class="badge badge-danger label-text-align"
><i
class="fa fa-fw fa-times"
/> Cancelled</span>
</t> </t>
<t t-if="picking.state in ['draft', 'waiting', 'confirmed', 'assigned']"> <t
<span class="badge badge-info label-text-align"><i class="fa fa-fw fa-clock-o"/> Preparation</span> t-if="picking.state in ['draft', 'waiting', 'confirmed', 'assigned']"
>
<span
class="badge badge-info label-text-align"
><i
class="fa fa-fw fa-clock-o"
/> Preparation</span>
</t> </t>
</a> </a>
</t> </t>
@@ -255,8 +449,8 @@
</section> </section>
<section id="description" class="mt-5" t-if="rma.description"> <section id="description" class="mt-5" t-if="rma.description">
<h3 class="">Description</h3> <h3 class="">Description</h3>
<hr class="mt-0 mb-1"/> <hr class="mt-0 mb-1" />
<t t-raw="rma.description"/> <t t-raw="rma.description" />
</section> </section>
</t> </t>
</t> </t>
@@ -264,10 +458,10 @@
<div id="rma_communication" class="mt-4"> <div id="rma_communication" class="mt-4">
<h2>Communication</h2> <h2>Communication</h2>
<t t-call="portal.message_thread"> <t t-call="portal.message_thread">
<t t-set="object" t-value="rma"/> <t t-set="object" t-value="rma" />
<t t-set="token" t-value="rma.access_token"/> <t t-set="token" t-value="rma.access_token" />
<t t-set="pid" t-value="pid"/> <t t-set="pid" t-value="pid" />
<t t-set="hash" t-value="hash"/> <t t-set="hash" t-value="hash" />
</t> </t>
</div> </div>
</t> </t>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2020 Tecnativa - Ernesto Tejeda <!-- Copyright 2020 Tecnativa - Ernesto Tejeda
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). --> License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
<odoo> <odoo>
@@ -20,8 +20,17 @@
<form> <form>
<sheet> <sheet>
<div class="oe_button_box" name="button_box"> <div class="oe_button_box" name="button_box">
<button name="toggle_active" type="object" class="oe_stat_button" icon="fa-archive"> <button
<field name="active" widget="boolean_button" options='{"terminology": "archive"}'/> name="toggle_active"
type="object"
class="oe_stat_button"
icon="fa-archive"
>
<field
name="active"
widget="boolean_button"
options='{"terminology": "archive"}'
/>
</button> </button>
</div> </div>
<div class="oe_title"> <div class="oe_title">
@@ -33,22 +42,49 @@
<group> <group>
<group> <group>
<field name="user_id" /> <field name="user_id" />
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company" /> <field
name="company_id"
options="{'no_create': True}"
groups="base.group_multi_company"
/>
</group> </group>
</group> </group>
<notebook> <notebook>
<page name="members" string="Team Members"> <page name="members" string="Team Members">
<field name="member_ids" widget="many2many" options="{'not_delete': True}"> <field
<kanban quick_create="false" create="true" delete="true"> name="member_ids"
<field name="id"/> widget="many2many"
<field name="name"/> options="{'not_delete': True}"
>
<kanban
quick_create="false"
create="true"
delete="true"
>
<field name="id" />
<field name="name" />
<templates> <templates>
<t t-name="kanban-box"> <t t-name="kanban-box">
<div class="oe_kanban_global_click" style="max-width: 200px"> <div
class="oe_kanban_global_click"
style="max-width: 200px"
>
<div class="o_kanban_record_top"> <div class="o_kanban_record_top">
<img t-att-src="kanban_image('res.users', 'image_small', record.id.raw_value)" height="40" width="40" class="oe_avatar oe_kanban_avatar_smallbox mb0" alt="Avatar"/> <img
<div class="o_kanban_record_headings ml8"> t-att-src="kanban_image('res.users', 'image_small', record.id.raw_value)"
<strong class="o_kanban_record_title"><field name="name"/></strong> height="40"
width="40"
class="oe_avatar oe_kanban_avatar_smallbox mb0"
alt="Avatar"
/>
<div
class="o_kanban_record_headings ml8"
>
<strong
class="o_kanban_record_title"
>
<field name="name" />
</strong>
</div> </div>
</div> </div>
</div> </div>
@@ -57,30 +93,51 @@
</kanban> </kanban>
</field> </field>
</page> </page>
<page name="emails" string="Email" attrs="{'invisible': [('alias_domain', '=', False)]}"> <page
name="emails"
string="Email"
attrs="{'invisible': [('alias_domain', '=', False)]}"
>
<group name="group_alias"> <group name="group_alias">
<label for="alias_name" string="Email Alias"/> <label for="alias_name" string="Email Alias" />
<div name="alias_def"> <div name="alias_def">
<field name="alias_id" class="oe_read_only oe_inline" <field
string="Email Alias" required="0"/> name="alias_id"
<div class="oe_edit_only oe_inline" name="edit_alias" style="display: inline;" > class="oe_read_only oe_inline"
<field name="alias_name" class="oe_inline"/>@<field name="alias_domain" class="oe_inline" readonly="1"/> string="Email Alias"
required="0"
/>
<div
class="oe_edit_only oe_inline"
name="edit_alias"
style="display: inline;"
>
<field
name="alias_name"
class="oe_inline"
/>@<field
name="alias_domain"
class="oe_inline"
readonly="1"
/>
</div> </div>
</div> </div>
<field name="alias_contact" class="oe_inline oe_edit_only" <field
string="Accept Emails From"/> name="alias_contact"
class="oe_inline oe_edit_only"
string="Accept Emails From"
/>
</group> </group>
</page> </page>
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers" />
<field name="message_ids" widget="mail_thread"/> <field name="message_ids" widget="mail_thread" />
</div> </div>
</form> </form>
</field> </field>
</record> </record>
<record id="rma_team_action" model="ir.actions.act_window"> <record id="rma_team_action" model="ir.actions.act_window">
<field name="name">RMA team</field> <field name="name">RMA team</field>
<field name="res_model">rma.team</field> <field name="res_model">rma.team</field>
@@ -92,10 +149,10 @@
</p> </p>
</field> </field>
</record> </record>
<menuitem <menuitem
id="rma_configuration_rma_team_menu" id="rma_configuration_rma_team_menu"
name="RMA Team" name="RMA Team"
parent="rma_configuration_menu" parent="rma_configuration_menu"
action="rma_team_action"/> action="rma_team_action"
/>
</odoo> </odoo>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2020 Tecnativa - Ernesto Tejeda <!-- Copyright 2020 Tecnativa - Ernesto Tejeda
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). --> License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
<odoo> <odoo>
@@ -9,214 +9,290 @@
<search> <search>
<field name="name" /> <field name="name" />
<field name="user_id" /> <field name="user_id" />
<filter name="draft_filter" <filter
name="draft_filter"
string="Draft" string="Draft"
domain="[('state','=', 'draft')]" /> domain="[('state','=', 'draft')]"
<filter name="confirmed_filter" />
<filter
name="confirmed_filter"
string="Confirmed" string="Confirmed"
domain="[('state','=', 'confirmed')]" /> domain="[('state','=', 'confirmed')]"
<filter name="received_filter" />
<filter
name="received_filter"
string="Received" string="Received"
domain="[('state','=', 'received')]" /> domain="[('state','=', 'received')]"
/>
<separator /> <separator />
<filter string="Unresolved RMAs" <filter
string="Unresolved RMAs"
name="undone_rma" name="undone_rma"
domain="[('state', 'not in', ['refunded', 'returned', 'replaced', 'locked', 'cancelled'])]" domain="[('state', 'not in', ['refunded', 'returned', 'replaced', 'locked', 'cancelled'])]"
help="RMAs yet to be fully processed"/> help="RMAs yet to be fully processed"
<filter string="Late RMAs" />
<filter
string="Late RMAs"
name="late_rma" name="late_rma"
domain="[('deadline', '&lt;', context_today().strftime('%Y-%m-%d')), ('state', 'not in', ['refunded', 'returned', 'replaced', 'locked', 'cancelled'])]" domain="[('deadline', '&lt;', context_today().strftime('%Y-%m-%d')), ('state', 'not in', ['refunded', 'returned', 'replaced', 'locked', 'cancelled'])]"
help="RMAs which deadline has passed"/> help="RMAs which deadline has passed"
/>
<separator /> <separator />
<filter string="RMA Date" name="filter_rma_date" date="date"/> <filter string="RMA Date" name="filter_rma_date" date="date" />
<filter string="RMA Deadline" name="filter_rma_deadline" date="deadline"/> <filter
<filter name="no_user_id_filter" string="RMA Deadline"
name="filter_rma_deadline"
date="deadline"
/>
<filter
name="no_user_id_filter"
string="Unassigned RMAs" string="Unassigned RMAs"
domain="[('user_id','=', False)]" /> domain="[('user_id','=', False)]"
/>
<group string="Group By" name="group_by"> <group string="Group By" name="group_by">
<filter string="Partner" <filter
string="Partner"
name="partner_id_group_by" name="partner_id_group_by"
context="{'group_by':'partner_id'}" /> context="{'group_by':'partner_id'}"
<filter string="Responsible" />
<filter
string="Responsible"
name="user_id_group_by" name="user_id_group_by"
context="{'group_by':'user_id'}" /> context="{'group_by':'user_id'}"
<filter string="State" />
<filter
string="State"
name="state_group_by" name="state_group_by"
context="{'group_by':'state'}" /> context="{'group_by':'state'}"
<filter string="Date" />
<filter
string="Date"
name="date_group_by" name="date_group_by"
context="{'group_by':'date'}" /> context="{'group_by':'date'}"
<filter string="Deadline" />
<filter
string="Deadline"
name="deadline_group_by" name="deadline_group_by"
context="{'group_by':'deadline'}" /> context="{'group_by':'deadline'}"
/>
</group> </group>
</search> </search>
</field> </field>
</record> </record>
<record id="rma_view_tree" model="ir.ui.view"> <record id="rma_view_tree" model="ir.ui.view">
<field name="name">rma.view.tree</field> <field name="name">rma.view.tree</field>
<field name="model">rma</field> <field name="model">rma</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree decoration-muted="state in ['cancelled', 'locked']" <tree
decoration-muted="state in ['cancelled', 'locked']"
decoration-bf="state == 'draft' and product_id == False" decoration-bf="state == 'draft' and product_id == False"
decoration-danger="deadline and (deadline &lt; current_date)"> decoration-danger="deadline and (deadline &lt; current_date)"
<field name="name"/> >
<field name="origin"/> <field name="name" />
<field name="user_id"/> <field name="origin" />
<field name="partner_id"/> <field name="user_id" />
<field name="product_id"/> <field name="partner_id" />
<field name="product_uom_qty"/> <field name="product_id" />
<field name="product_uom" groups="uom.group_uom"/> <field name="product_uom_qty" />
<field name="date"/> <field name="product_uom" groups="uom.group_uom" />
<field name="deadline"/> <field name="date" />
<field name="state"/> <field name="deadline" />
<field name="state" />
</tree> </tree>
</field> </field>
</record> </record>
<record id="rma_view_form" model="ir.ui.view"> <record id="rma_view_form" model="ir.ui.view">
<field name="name">rma.view.form</field> <field name="name">rma.view.form</field>
<field name="model">rma</field> <field name="model">rma</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form> <form>
<header> <header>
<button name="%(portal.portal_share_action)d" <button
name="%(portal.portal_share_action)d"
string="Share" string="Share"
type="action" type="action"
class="oe_highlight oe_read_only"/> class="oe_highlight oe_read_only"
<button type="object" />
<button
type="object"
string="Send by Email" string="Send by Email"
name="action_rma_send" name="action_rma_send"
attrs="{'invisible':['|', ('sent','=',True), ('state', 'not in', ['draft', 'confirmed', 'received'])]}" attrs="{'invisible':['|', ('sent','=',True), ('state', 'not in', ['draft', 'confirmed', 'received'])]}"
class="btn-primary"/> class="btn-primary"
<button type="object" />
<button
type="object"
string="Send by Mail" string="Send by Mail"
name="action_rma_send" name="action_rma_send"
attrs="{'invisible':['|', ('sent','=',False), ('state', 'not in', ['draft', 'confirmed', 'received'])]}" /> attrs="{'invisible':['|', ('sent','=',False), ('state', 'not in', ['draft', 'confirmed', 'received'])]}"
<button type="object" />
<button
type="object"
string="Confirm" string="Confirm"
name="action_confirm" name="action_confirm"
states="draft" states="draft"
class="btn-primary"/> class="btn-primary"
<button type="object" />
<button
type="object"
string="To Refund" string="To Refund"
name="action_refund" name="action_refund"
attrs="{'invisible': [('can_be_refunded', '=', False)]}" attrs="{'invisible': [('can_be_refunded', '=', False)]}"
class="btn-primary"/> class="btn-primary"
<button type="object" />
<button
type="object"
string="Replace" string="Replace"
name="action_replace" name="action_replace"
attrs="{'invisible': [('can_be_replaced', '=', False)]}" attrs="{'invisible': [('can_be_replaced', '=', False)]}"
class="btn-primary"/> class="btn-primary"
<button type="object" />
<button
type="object"
string="Return to customer" string="Return to customer"
name="action_return" name="action_return"
attrs="{'invisible': [('can_be_returned', '=', False)]}" attrs="{'invisible': [('can_be_returned', '=', False)]}"
class="btn-primary"/> class="btn-primary"
<button type="object" />
<button
type="object"
string="Split" string="Split"
name="action_split" name="action_split"
attrs="{'invisible': [('can_be_split', '=', False)]}"/> attrs="{'invisible': [('can_be_split', '=', False)]}"
<button type="object" />
<button
type="object"
string="Cancel" string="Cancel"
name="action_cancel" name="action_cancel"
confirm="Are you sure you want to cancel this RMA" confirm="Are you sure you want to cancel this RMA"
states="draft,confirmed"/> states="draft,confirmed"
<button type="object" />
<button
type="object"
string="Set to draft" string="Set to draft"
name="action_draft" name="action_draft"
states="cancelled"/> states="cancelled"
<button type="object" />
<button
type="object"
string="Lock" string="Lock"
name="action_lock" name="action_lock"
attrs="{'invisible': [('can_be_locked', '=', False)]}"/> attrs="{'invisible': [('can_be_locked', '=', False)]}"
<button type="object" />
<button
type="object"
string="Unlock" string="Unlock"
name="action_unlock" name="action_unlock"
states="locked"/> states="locked"
<button type="object" />
string="Preview" <button type="object" string="Preview" name="action_preview" />
name="action_preview"/> <field
<field name="state" widget="statusbar" statusbar_visible="draft,confirmed,received"/> name="state"
widget="statusbar"
statusbar_visible="draft,confirmed,received"
/>
</header> </header>
<sheet> <sheet>
<div class="oe_button_box" name="button_box"> <div class="oe_button_box" name="button_box">
<button type="object" <button
type="object"
name="action_view_receipt" name="action_view_receipt"
string="Receipt" string="Receipt"
class="oe_stat_button" class="oe_stat_button"
icon="fa-truck" icon="fa-truck"
attrs="{'invisible': [('reception_move_id', '=', False)]}"> attrs="{'invisible': [('reception_move_id', '=', False)]}"
>
</button> </button>
<button type="object" <button
type="object"
name="action_view_delivery" name="action_view_delivery"
class="oe_stat_button" class="oe_stat_button"
icon="fa-truck" icon="fa-truck"
attrs="{'invisible': [('delivery_picking_count', '=', 0)]}"> attrs="{'invisible': [('delivery_picking_count', '=', 0)]}"
<field name="delivery_picking_count" widget="statinfo" string="Delivery"/> >
<field
name="delivery_picking_count"
widget="statinfo"
string="Delivery"
/>
</button> </button>
<button type="object" <button
type="object"
string="Refund" string="Refund"
name="action_view_refund" name="action_view_refund"
class="oe_stat_button" class="oe_stat_button"
icon="fa-pencil-square-o" icon="fa-pencil-square-o"
attrs="{'invisible': [('refund_id', '=', False)]}"> attrs="{'invisible': [('refund_id', '=', False)]}"
>
</button> </button>
</div> </div>
<div class="oe_title"> <div class="oe_title">
<h1> <h1>
<field name="name" readonly="1"/> <field name="name" readonly="1" />
</h1> </h1>
</div> </div>
<group> <group>
<group> <group>
<field name="partner_id" widget="res_partner_many2one" context="{'search_default_customer':1, 'show_address': 1, 'show_vat': True}" options="{'always_reload': True}"/> <field
<field name="partner_invoice_id"/> name="partner_id"
<field name="picking_id" widget="res_partner_many2one"
options="{'no_create': True}"/> context="{'search_default_customer':1, 'show_address': 1, 'show_vat': True}"
<field name="move_id" options="{'always_reload': True}"
/>
<field name="partner_invoice_id" />
<field name="picking_id" options="{'no_create': True}" />
<field
name="move_id"
attrs="{'required': [('picking_id', '!=', False)], 'readonly': ['|', ('picking_id', '=', False), ('state', '!=', 'draft')]}" attrs="{'required': [('picking_id', '!=', False)], 'readonly': ['|', ('picking_id', '=', False), ('state', '!=', 'draft')]}"
options="{'no_create': True}" options="{'no_create': True}"
force_save="1"/>
<field name="product_id"
force_save="1" force_save="1"
attrs="{'readonly': ['|', ('picking_id', '!=', False), ('state', '!=', 'draft')]}"/> />
<label for="product_uom_qty"/> <field
name="product_id"
force_save="1"
attrs="{'readonly': ['|', ('picking_id', '!=', False), ('state', '!=', 'draft')]}"
/>
<label for="product_uom_qty" />
<div class="o_row"> <div class="o_row">
<field name="product_uom_qty"/> <field name="product_uom_qty" />
<field name="product_uom" <field name="product_uom" groups="uom.group_uom" />
groups="uom.group_uom"/>
</div> </div>
<field name="delivered_qty" <field
attrs="{'invisible': [('delivered_qty', '=', 0.0)]}"/> name="delivered_qty"
attrs="{'invisible': [('delivered_qty', '=', 0.0)]}"
/>
</group> </group>
<group> <group>
<field name="date"/> <field name="date" />
<field name="user_id"/> <field name="user_id" />
<field name="team_id"/> <field name="team_id" />
<field name="origin"/> <field name="origin" />
<field name="operation_id"/> <field name="operation_id" />
<field name="company_id" <field
name="company_id"
options="{'no_create': True}" options="{'no_create': True}"
groups="base.group_multi_company"/> groups="base.group_multi_company"
/>
</group> </group>
</group> </group>
<notebook> <notebook>
<page name="page_other" <page name="page_other" string="Other Information">
string="Other Information">
<group> <group>
<group> <group>
<field name="procurement_group_id"/> <field name="procurement_group_id" />
<field name="location_id" <field
name="location_id"
options="{'no_create': True, 'no_open': True}" options="{'no_create': True, 'no_open': True}"
groups="stock.group_stock_multi_locations" /> groups="stock.group_stock_multi_locations"
/>
</group> </group>
<group> <group>
<field name="deadline"/> <field name="deadline" />
<field name="priority" widget="priority"/> <field name="priority" widget="priority" />
<field name="origin_split_rma_id" <field
attrs="{'invisible': [('origin_split_rma_id', '=', False)]}"/> name="origin_split_rma_id"
attrs="{'invisible': [('origin_split_rma_id', '=', False)]}"
/>
</group> </group>
</group> </group>
<group> <group>
@@ -224,59 +300,55 @@
</group> </group>
</page> </page>
</notebook> </notebook>
<field name="sent" invisible="1"/> <field name="sent" invisible="1" />
<field name="reception_move_id" invisible="1"/> <field name="reception_move_id" invisible="1" />
<field name="refund_id" invisible="1"/> <field name="refund_id" invisible="1" />
<field name="can_be_refunded" invisible="1"/> <field name="can_be_refunded" invisible="1" />
<field name="can_be_returned" invisible="1"/> <field name="can_be_returned" invisible="1" />
<field name="can_be_replaced" invisible="1"/> <field name="can_be_replaced" invisible="1" />
<field name="can_be_split" invisible="1"/> <field name="can_be_split" invisible="1" />
<field name="can_be_locked" invisible="1"/> <field name="can_be_locked" invisible="1" />
<field name="commercial_partner_id" invisible="1"/> <field name="commercial_partner_id" invisible="1" />
<field name="remaining_qty" invisible="1"/> <field name="remaining_qty" invisible="1" />
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/> <field name="message_follower_ids" widget="mail_followers" />
<field name="activity_ids" widget="mail_activity"/> <field name="activity_ids" widget="mail_activity" />
<field name="message_ids" widget="mail_thread"/> <field name="message_ids" widget="mail_thread" />
</div> </div>
</form> </form>
</field> </field>
</record> </record>
<record id="rma_view_pivot" model="ir.ui.view"> <record id="rma_view_pivot" model="ir.ui.view">
<field name="name">rma.pivot</field> <field name="name">rma.pivot</field>
<field name="model">rma</field> <field name="model">rma</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<pivot> <pivot>
<field name="date" type="row"/> <field name="date" type="row" />
<field name="product_uom_qty" type="measure"/> <field name="product_uom_qty" type="measure" />
<field name="delivered_qty" type="measure"/> <field name="delivered_qty" type="measure" />
</pivot> </pivot>
</field> </field>
</record> </record>
<record id="rma_view_calendar" model="ir.ui.view"> <record id="rma_view_calendar" model="ir.ui.view">
<field name="name">rma.calendar</field> <field name="name">rma.calendar</field>
<field name="model">rma</field> <field name="model">rma</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<calendar date_start="date" mode="month" color="state" quick_add="False"> <calendar date_start="date" mode="month" color="state" quick_add="False">
<field name="name"/> <field name="name" />
<field name="partner_id"/> <field name="partner_id" />
<field name="product_id"/> <field name="product_id" />
<field name="product_uom_qty" widget="monetary"/> <field name="product_uom_qty" widget="monetary" />
</calendar> </calendar>
</field> </field>
</record> </record>
<record id="rma_refund_action_server" model="ir.actions.server"> <record id="rma_refund_action_server" model="ir.actions.server">
<field name="name">To Refund</field> <field name="name">To Refund</field>
<field name="model_id" ref="model_rma"/> <field name="model_id" ref="model_rma" />
<field name="binding_model_id" ref="model_rma"/> <field name="binding_model_id" ref="model_rma" />
<field name="state">code</field> <field name="state">code</field>
<field name="code">records.action_refund()</field> <field name="code">records.action_refund()</field>
</record> </record>
<record id="rma_action" model="ir.actions.act_window"> <record id="rma_action" model="ir.actions.act_window">
<field name="name">RMA</field> <field name="name">RMA</field>
<field name="res_model">rma</field> <field name="res_model">rma</field>
@@ -289,8 +361,7 @@
</p> </p>
</field> </field>
</record> </record>
<record id="rma_orders_menu" model="ir.ui.menu"> <record id="rma_orders_menu" model="ir.ui.menu">
<field name="action" ref="rma_action"/> <field name="action" ref="rma_action" />
</record> </record>
</odoo> </odoo>

View File

@@ -1,22 +1,22 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2020 Tecnativa - Ernesto Tejeda <!-- Copyright 2020 Tecnativa - Ernesto Tejeda
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). --> License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
<odoo> <odoo>
<record id="view_picking_form" model="ir.ui.view"> <record id="view_picking_form" model="ir.ui.view">
<field name="name">stock.picking.form</field> <field name="name">stock.picking.form</field>
<field name="model">stock.picking</field> <field name="model">stock.picking</field>
<field name="inherit_id" ref="stock.view_picking_form"/> <field name="inherit_id" ref="stock.view_picking_form" />
<field name="groups_id" eval="[(4, ref('rma.rma_group_user_own'))]"/> <field name="groups_id" eval="[(4, ref('rma.rma_group_user_own'))]" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<div name="button_box"> <div name="button_box">
<button name="action_view_rma" <button
name="action_view_rma"
type="object" type="object"
class="oe_stat_button" class="oe_stat_button"
icon="fa-reply" icon="fa-reply"
attrs="{'invisible': [('rma_count', '=', 0)]}"> attrs="{'invisible': [('rma_count', '=', 0)]}"
<field name="rma_count" >
widget="statinfo" <field name="rma_count" widget="statinfo" string="RMA" />
string="RMA"/>
</button> </button>
</div> </div>
</field> </field>

View File

@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record id="view_warehouse_inherit_mrp" model="ir.ui.view"> <record id="view_warehouse_inherit_mrp" model="ir.ui.view">
<field name="name">Stock Warehouse Inherit MRP</field> <field name="name">Stock Warehouse Inherit MRP</field>
<field name="model">stock.warehouse</field> <field name="model">stock.warehouse</field>
<field name="inherit_id" ref="stock.view_warehouse"/> <field name="inherit_id" ref="stock.view_warehouse" />
<field name="groups_id" eval="[(4, ref('rma.rma_group_user_own'))]"/> <field name="groups_id" eval="[(4, ref('rma.rma_group_user_own'))]" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//field[@name='wh_output_stock_loc_id']/.."> <xpath expr="//field[@name='wh_output_stock_loc_id']/..">
<field name="rma_loc_id"/> <field name="rma_loc_id" />
</xpath> </xpath>
<xpath expr="//field[@name='out_type_id']/.."> <xpath expr="//field[@name='out_type_id']/..">
<field name="rma_in_type_id"/> <field name="rma_in_type_id" />
<field name="rma_out_type_id"/> <field name="rma_out_type_id" />
</xpath> </xpath>
</field> </field>
</record> </record>

View File

@@ -3,61 +3,52 @@
from odoo import _, api, fields, models from odoo import _, api, fields, models
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
import odoo.addons.decimal_precision as dp import odoo.addons.decimal_precision as dp
class RmaReDeliveryWizard(models.TransientModel): class RmaReDeliveryWizard(models.TransientModel):
_name = 'rma.delivery.wizard' _name = "rma.delivery.wizard"
_description = 'RMA Delivery Wizard' _description = "RMA Delivery Wizard"
rma_count = fields.Integer() rma_count = fields.Integer()
type = fields.Selection( type = fields.Selection(
selection=[ selection=[("replace", "Replace"), ("return", "Return to customer"),],
('replace', 'Replace'),
('return', 'Return to customer'),
],
string="Type", string="Type",
required=True, required=True,
) )
product_id = fields.Many2one( product_id = fields.Many2one(
comodel_name="product.product", comodel_name="product.product", string="Replace Product",
string="Replace Product",
) )
product_uom_qty = fields.Float( product_uom_qty = fields.Float(
string='Product qty', string="Product qty", digits=dp.get_precision("Product Unit of Measure"),
digits=dp.get_precision('Product Unit of Measure'),
)
product_uom = fields.Many2one(
comodel_name="uom.uom",
string="Unit of measure",
)
scheduled_date = fields.Datetime(
required=True,
default=fields.Datetime.now(),
) )
product_uom = fields.Many2one(comodel_name="uom.uom", string="Unit of measure",)
scheduled_date = fields.Datetime(required=True, default=fields.Datetime.now(),)
warehouse_id = fields.Many2one( warehouse_id = fields.Many2one(
comodel_name="stock.warehouse", comodel_name="stock.warehouse", string="Warehouse", required=True,
string='Warehouse',
required=True,
) )
@api.constrains('product_uom_qty') @api.constrains("product_uom_qty")
def _check_product_uom_qty(self): def _check_product_uom_qty(self):
self.ensure_one() self.ensure_one()
rma_ids = self.env.context.get('active_ids') rma_ids = self.env.context.get("active_ids")
if len(rma_ids) == 1 and self.product_uom_qty <= 0: if len(rma_ids) == 1 and self.product_uom_qty <= 0:
raise ValidationError(_('Quantity must be greater than 0.')) raise ValidationError(_("Quantity must be greater than 0."))
@api.model @api.model
def default_get(self, fields_list): def default_get(self, fields_list):
res = super().default_get(fields_list) res = super().default_get(fields_list)
rma_ids = self.env.context.get('active_ids') rma_ids = self.env.context.get("active_ids")
rma = self.env['rma'].browse(rma_ids) rma = self.env["rma"].browse(rma_ids)
warehouse_id = self.env['stock.warehouse'].search( warehouse_id = (
[('company_id', '=', rma[0].company_id.id)], limit=1).id self.env["stock.warehouse"]
delivery_type = self.env.context.get('rma_delivery_type') .search([("company_id", "=", rma[0].company_id.id)], limit=1)
.id
)
delivery_type = self.env.context.get("rma_delivery_type")
product_id = False product_id = False
if len(rma) == 1 and delivery_type == 'return': if len(rma) == 1 and delivery_type == "return":
product_id = rma.product_id.id product_id = rma.product_id.id
product_uom_qty = 0.0 product_uom_qty = 0.0
if len(rma) == 1 and rma.remaining_qty > 0.0: if len(rma) == 1 and rma.remaining_qty > 0.0:
@@ -76,18 +67,17 @@ class RmaReDeliveryWizard(models.TransientModel):
domain_product_uom = [] domain_product_uom = []
if self.product_id: if self.product_id:
domain_product_uom = [ domain_product_uom = [
('category_id', '=', self.product_id.uom_id.category_id.id) ("category_id", "=", self.product_id.uom_id.category_id.id)
] ]
if (not self.product_uom if not self.product_uom or self.product_id.uom_id.id != self.product_uom.id:
or self.product_id.uom_id.id != self.product_uom.id):
self.product_uom = self.product_id.uom_id self.product_uom = self.product_id.uom_id
return {'domain': {'product_uom': domain_product_uom}} return {"domain": {"product_uom": domain_product_uom}}
def action_deliver(self): def action_deliver(self):
self.ensure_one() self.ensure_one()
rma_ids = self.env.context.get('active_ids') rma_ids = self.env.context.get("active_ids")
rma = self.env['rma'].browse(rma_ids) rma = self.env["rma"].browse(rma_ids)
if self.type == 'replace': if self.type == "replace":
rma.create_replace( rma.create_replace(
self.scheduled_date, self.scheduled_date,
self.warehouse_id, self.warehouse_id,
@@ -95,7 +85,7 @@ class RmaReDeliveryWizard(models.TransientModel):
self.product_uom_qty, self.product_uom_qty,
self.product_uom, self.product_uom,
) )
elif self.type == 'return': elif self.type == "return":
qty = uom = None qty = uom = None
if self.rma_count == 1: if self.rma_count == 1:
qty, uom = self.product_uom_qty, self.product_uom qty, uom = self.product_uom_qty, self.product_uom

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2020 Tecnativa - Ernesto Tejeda <!-- Copyright 2020 Tecnativa - Ernesto Tejeda
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). --> License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
<odoo> <odoo>
@@ -9,35 +9,53 @@
<form> <form>
<group> <group>
<group> <group>
<field name="scheduled_date"/> <field name="scheduled_date" />
<field name="warehouse_id" <field
attrs="{'invisible': [('type', '!=', 'replace')]}"/> name="warehouse_id"
attrs="{'invisible': [('type', '!=', 'replace')]}"
/>
</group> </group>
<group> <group>
<field name="product_id" <field
attrs="{'invisible': ['|', ('type', '!=', 'replace'), ('rma_count', '>', 1)], 'required': [('type', '=', 'replace'), ('rma_count', '=', 1)]}"/> name="product_id"
<label for="product_uom_qty" attrs="{'invisible': ['|', ('type', '!=', 'replace'), ('rma_count', '>', 1)], 'required': [('type', '=', 'replace'), ('rma_count', '=', 1)]}"
attrs="{'invisible': [('rma_count', '>', 1)]}"/> />
<div class="o_row" <label
attrs="{'invisible': [('rma_count', '>', 1)]}"> for="product_uom_qty"
<field name="product_uom_qty" attrs="{'invisible': [('rma_count', '>', 1)]}"
attrs="{'required': [('rma_count', '=', 1)]}"/> />
<field name="product_uom" <div
class="o_row"
attrs="{'invisible': [('rma_count', '>', 1)]}"
>
<field
name="product_uom_qty"
attrs="{'required': [('rma_count', '=', 1)]}"
/>
<field
name="product_uom"
groups="uom.group_uom" groups="uom.group_uom"
attrs="{'required': [('rma_count', '=', 1)]}"/> attrs="{'required': [('rma_count', '=', 1)]}"
/>
</div> </div>
</group> </group>
</group> </group>
<field name="rma_count" invisible="1"/> <field name="rma_count" invisible="1" />
<field name="type" invisible="1"/> <field name="type" invisible="1" />
<footer> <footer>
<button name="action_deliver" string="Deliver" type="object" class="btn-primary"/> <button
name="action_deliver"
string="Deliver"
type="object"
class="btn-primary"
/>
<button string="Cancel" class="btn-secondary" special="cancel" /> <button string="Cancel" class="btn-secondary" special="cancel" />
</footer> </footer>
</form> </form>
</field> </field>
</record> </record>
<act_window id="rma_delivery_wizard_action" <act_window
id="rma_delivery_wizard_action"
name="Return to customer" name="Return to customer"
src_model="rma" src_model="rma"
res_model="rma.delivery.wizard" res_model="rma.delivery.wizard"
@@ -46,5 +64,6 @@
key2="client_action_multi" key2="client_action_multi"
target="new" target="new"
multi="True" multi="True"
context="{'rma_delivery_type': 'return'}"/> context="{'rma_delivery_type': 'return'}"
/>
</odoo> </odoo>

View File

@@ -2,52 +2,48 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models from odoo import _, api, fields, models
import odoo.addons.decimal_precision as dp import odoo.addons.decimal_precision as dp
class RmaReSplitWizard(models.TransientModel): class RmaReSplitWizard(models.TransientModel):
_name = 'rma.split.wizard' _name = "rma.split.wizard"
_description = 'RMA Split Wizard' _description = "RMA Split Wizard"
rma_id = fields.Many2one( rma_id = fields.Many2one(comodel_name="rma", string="RMA",)
comodel_name='rma',
string='RMA',
)
product_uom_qty = fields.Float( product_uom_qty = fields.Float(
string='Quantity to extract', string="Quantity to extract",
digits=dp.get_precision('Product Unit of Measure'), digits=dp.get_precision("Product Unit of Measure"),
required=True, required=True,
help="Quantity to extract to a new RMA." help="Quantity to extract to a new RMA.",
) )
product_uom = fields.Many2one( product_uom = fields.Many2one(
comodel_name='uom.uom', comodel_name="uom.uom", string="Unit of measure", required=True,
string='Unit of measure',
required=True,
) )
_sql_constraints = [ _sql_constraints = [
( (
'check_product_uom_qty_positive', "check_product_uom_qty_positive",
'CHECK(product_uom_qty > 0)', "CHECK(product_uom_qty > 0)",
'Quantity must be greater than 0.' "Quantity must be greater than 0.",
), ),
] ]
@api.model @api.model
def fields_get(self, allfields=None, attributes=None): def fields_get(self, allfields=None, attributes=None):
res = super().fields_get(allfields, attributes=attributes) res = super().fields_get(allfields, attributes=attributes)
rma_id = self.env.context.get('active_id') rma_id = self.env.context.get("active_id")
rma = self.env['rma'].browse(rma_id) rma = self.env["rma"].browse(rma_id)
res['product_uom']['domain'] = [ res["product_uom"]["domain"] = [
('category_id', '=', rma.product_uom.category_id.id) ("category_id", "=", rma.product_uom.category_id.id)
] ]
return res return res
@api.model @api.model
def default_get(self, fields_list): def default_get(self, fields_list):
res = super().default_get(fields_list) res = super().default_get(fields_list)
rma_id = self.env.context.get('active_id') rma_id = self.env.context.get("active_id")
rma = self.env['rma'].browse(rma_id) rma = self.env["rma"].browse(rma_id)
res.update( res.update(
rma_id=rma.id, rma_id=rma.id,
product_uom_qty=rma.remaining_qty, product_uom_qty=rma.remaining_qty,
@@ -58,13 +54,14 @@ class RmaReSplitWizard(models.TransientModel):
def action_split(self): def action_split(self):
self.ensure_one() self.ensure_one()
extracted_rma = self.rma_id.extract_quantity( extracted_rma = self.rma_id.extract_quantity(
self.product_uom_qty, self.product_uom) self.product_uom_qty, self.product_uom
)
return { return {
'name': _('Extracted RMA'), "name": _("Extracted RMA"),
'type': 'ir.actions.act_window', "type": "ir.actions.act_window",
'view_type': 'form', "view_type": "form",
'view_mode': 'form', "view_mode": "form",
'res_model': 'rma', "res_model": "rma",
'views': [(self.env.ref('rma.rma_view_form').id, 'form')], "views": [(self.env.ref("rma.rma_view_form").id, "form")],
'res_id': extracted_rma.id, "res_id": extracted_rma.id,
} }

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2020 Tecnativa - Ernesto Tejeda <!-- Copyright 2020 Tecnativa - Ernesto Tejeda
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). --> License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
<odoo> <odoo>
@@ -9,15 +9,20 @@
<form> <form>
<group> <group>
<group> <group>
<label for="product_uom_qty"/> <label for="product_uom_qty" />
<div class="o_row"> <div class="o_row">
<field name="product_uom_qty"/> <field name="product_uom_qty" />
<field name="product_uom" groups="uom.group_uom"/> <field name="product_uom" groups="uom.group_uom" />
</div> </div>
</group> </group>
</group> </group>
<footer> <footer>
<button name="action_split" string="Split" type="object" class="btn-primary"/> <button
name="action_split"
string="Split"
type="object"
class="btn-primary"
/>
<button string="Cancel" class="btn-secondary" special="cancel" /> <button string="Cancel" class="btn-secondary" special="cancel" />
</footer> </footer>
</form> </form>

View File

@@ -6,18 +6,16 @@ from odoo.exceptions import ValidationError
class ReturnPicking(models.TransientModel): class ReturnPicking(models.TransientModel):
_inherit = 'stock.return.picking' _inherit = "stock.return.picking"
create_rma = fields.Boolean( create_rma = fields.Boolean(string="Create RMAs")
string="Create RMAs"
)
picking_type_code = fields.Selection( picking_type_code = fields.Selection(
selection=[ selection=[
('incoming', 'Vendors'), ("incoming", "Vendors"),
('outgoing', 'Customers'), ("outgoing", "Customers"),
('internal', 'Internal'), ("internal", "Internal"),
], ],
related='picking_id.picking_type_id.code', related="picking_id.picking_type_id.code",
store=True, store=True,
readonly=True, readonly=True,
) )
@@ -27,16 +25,16 @@ class ReturnPicking(models.TransientModel):
if self.create_rma: if self.create_rma:
warehouse = self.picking_id.picking_type_id.warehouse_id warehouse = self.picking_id.picking_type_id.warehouse_id
self.location_id = warehouse.rma_loc_id.id self.location_id = warehouse.rma_loc_id.id
rma_loc = warehouse.search([]).mapped('rma_loc_id') rma_loc = warehouse.search([]).mapped("rma_loc_id")
rma_loc_domain = [('id', 'child_of', rma_loc.ids)] rma_loc_domain = [("id", "child_of", rma_loc.ids)]
else: else:
self.location_id = self.default_get(['location_id'])['location_id'] self.location_id = self.default_get(["location_id"])["location_id"]
rma_loc_domain = [ rma_loc_domain = [
'|', "|",
('id', '=', self.picking_id.location_id.id), ("id", "=", self.picking_id.location_id.id),
('return_location', '=', True), ("return_location", "=", True),
] ]
return {'domain': {'location_id': rma_loc_domain}} return {"domain": {"location_id": rma_loc_domain}}
def create_returns(self): def create_returns(self):
""" Override create_returns method for creating one or more """ Override create_returns method for creating one or more
@@ -52,13 +50,18 @@ class ReturnPicking(models.TransientModel):
self_with_context = self.with_context(set_rma_picking_type=True) self_with_context = self.with_context(set_rma_picking_type=True)
res = super(ReturnPicking, self_with_context).create_returns() res = super(ReturnPicking, self_with_context).create_returns()
if not self.picking_id.partner_id: if not self.picking_id.partner_id:
raise ValidationError(_( raise ValidationError(
_(
"You must specify the 'Customer' in the " "You must specify the 'Customer' in the "
"'Stock Picking' from which RMAs will be created")) "'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_lines] returned_picking = self.env["stock.picking"].browse(res["res_id"])
self.env['rma'].create(vals_list) vals_list = [
move._prepare_return_rma_vals(self.picking_id)
for move in returned_picking.move_lines
]
self.env["rma"].create(vals_list)
return res return res
else: else:
return super().create_returns() return super().create_returns()

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2020 Tecnativa - Ernesto Tejeda <!-- Copyright 2020 Tecnativa - Ernesto Tejeda
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). --> License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
<odoo> <odoo>
@@ -7,12 +7,17 @@
<field name="model">stock.return.picking</field> <field name="model">stock.return.picking</field>
<field name="inherit_id" ref="stock.view_stock_return_picking_form" /> <field name="inherit_id" ref="stock.view_stock_return_picking_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//group[.//field[@name='product_return_moves']]" position="before"> <xpath
expr="//group[.//field[@name='product_return_moves']]"
position="before"
>
<group name="group_rma"> <group name="group_rma">
<field name="create_rma" <field
attrs="{'invisible': [('picking_type_code', '!=', 'outgoing')]}"/> name="create_rma"
<field name="picking_id" invisible="1"/> attrs="{'invisible': [('picking_type_code', '!=', 'outgoing')]}"
<field name="picking_type_code" invisible="1"/> />
<field name="picking_id" invisible="1" />
<field name="picking_type_code" invisible="1" />
</group> </group>
</xpath> </xpath>
</field> </field>

View File

@@ -0,0 +1,2 @@
# addons listed in this file are ignored by
# setuptools-odoo-make-default (one addon per line)

2
setup/README Normal file
View File

@@ -0,0 +1,2 @@
To learn more about this directory, please visit
https://pypi.python.org/pypi/setuptools-odoo

1
setup/rma/odoo/addons/rma Symbolic link
View File

@@ -0,0 +1 @@
../../../../rma

6
setup/rma/setup.py Normal file
View File

@@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)