diff --git a/rma/__init__.py b/rma/__init__.py index 336227cc..25d6cd09 100644 --- a/rma/__init__.py +++ b/rma/__init__.py @@ -3,4 +3,4 @@ from . import controllers from . import models from . import wizard -from .hooks import post_init_hook +from .hooks import post_init_hook diff --git a/rma/__manifest__.py b/rma/__manifest__.py index fe9c9af8..5ecc5d93 100644 --- a/rma/__manifest__.py +++ b/rma/__manifest__.py @@ -10,10 +10,7 @@ "author": "Tecnativa, Odoo Community Association (OCA)", "maintainers": ["ernestotejeda"], "license": "AGPL-3", - "depends": [ - "account", - "stock", - ], + "depends": ["account", "stock",], "data": [ "views/report_rma.xml", "report/report.xml", @@ -32,6 +29,6 @@ "views/stock_picking_views.xml", "views/stock_warehouse_views.xml", ], - 'post_init_hook': 'post_init_hook', + "post_init_hook": "post_init_hook", "application": True, } diff --git a/rma/controllers/main.py b/rma/controllers/main.py index d590b0f0..a262f83a 100644 --- a/rma/controllers/main.py +++ b/rma/controllers/main.py @@ -4,54 +4,53 @@ from odoo import _, exceptions, http from odoo.exceptions import AccessError, MissingError from odoo.http import request -from odoo.addons.portal.controllers.portal import CustomerPortal,\ - pager as portal_pager from odoo.tools import consteq +from odoo.addons.portal.controllers.portal import CustomerPortal, pager as portal_pager + class PortalRma(CustomerPortal): - def _prepare_portal_layout_values(self): values = super()._prepare_portal_layout_values() - if request.env['rma'].check_access_rights( - 'read', raise_exception=False): - values['rma_count'] = request.env['rma'].search_count([]) + if request.env["rma"].check_access_rights("read", raise_exception=False): + values["rma_count"] = request.env["rma"].search_count([]) else: - values['rma_count'] = 0 + values["rma_count"] = 0 return values def _rma_get_page_view_values(self, rma, access_token, **kwargs): values = { - 'page_name': 'RMA', - 'rma': rma, + "page_name": "RMA", + "rma": rma, } 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): return [] - @http.route(['/my/rmas', '/my/rmas/page/'], - type='http', auth="user", website=True) - def portal_my_rmas(self, page=1, date_begin=None, date_end=None, - sortby=None, **kw): + @http.route( + ["/my/rmas", "/my/rmas/page/"], type="http", auth="user", website=True + ) + def portal_my_rmas(self, page=1, date_begin=None, date_end=None, sortby=None, **kw): values = self._prepare_portal_layout_values() - rma_obj = request.env['rma'] + rma_obj = request.env["rma"] domain = self._get_filter_domain(kw) searchbar_sortings = { - 'date': {'label': _('Date'), 'order': 'date desc'}, - 'name': {'label': _('Name'), 'order': 'name desc'}, - 'state': {'label': _('Status'), 'order': 'state'}, + "date": {"label": _("Date"), "order": "date desc"}, + "name": {"label": _("Name"), "order": "name desc"}, + "state": {"label": _("Status"), "order": "state"}, } # default sort by order if not sortby: - sortby = 'date' - order = searchbar_sortings[sortby]['order'] - archive_groups = self._get_archive_groups('rma', domain) + sortby = "date" + order = searchbar_sortings[sortby]["order"] + archive_groups = self._get_archive_groups("rma", domain) if date_begin and date_end: domain += [ - ('create_date', '>', date_begin), - ('create_date', '<=', date_end), + ("create_date", ">", date_begin), + ("create_date", "<=", date_end), ] # count for pager rma_count = rma_obj.search_count(domain) @@ -59,77 +58,80 @@ class PortalRma(CustomerPortal): pager = portal_pager( url="/my/rmas", url_args={ - 'date_begin': date_begin, - 'date_end': date_end, - 'sortby': sortby, + "date_begin": date_begin, + "date_end": date_end, + "sortby": sortby, }, total=rma_count, page=page, - step=self._items_per_page + step=self._items_per_page, ) # content according to pager and archive selected rmas = rma_obj.search( - domain, - order=order, - limit=self._items_per_page, - offset=pager['offset'] + domain, order=order, limit=self._items_per_page, offset=pager["offset"] + ) + 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, + } ) - 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) - @http.route(['/my/rmas/'], - type='http', auth="public", website=True) - def portal_my_rma_detail(self, rma_id, access_token=None, - report_type=None, download=False, **kw): + @http.route(["/my/rmas/"], type="http", auth="public", website=True) + def portal_my_rma_detail( + self, rma_id, access_token=None, report_type=None, download=False, **kw + ): 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): - return request.redirect('/my') - if report_type in ('html', 'pdf', 'text'): + return request.redirect("/my") + if report_type in ("html", "pdf", "text"): return self._show_report( model=rma_sudo, report_type=report_type, - report_ref='rma.report_rma_action', + report_ref="rma.report_rma_action", download=download, ) values = self._rma_get_page_view_values(rma_sudo, access_token, **kw) return request.render("rma.portal_rma_page", values) - @http.route(['/my/rma/picking/pdf//'], - type='http', auth="public", website=True) - def portal_my_rma_picking_report(self, rma_id, picking_id, - access_token=None, **kw): + @http.route( + ["/my/rma/picking/pdf//"], + type="http", + auth="public", + website=True, + ) + def portal_my_rma_picking_report(self, rma_id, picking_id, access_token=None, **kw): try: 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: - return request.redirect('/my') - report_sudo = request.env.ref('stock.action_report_delivery').sudo() + return request.redirect("/my") + report_sudo = request.env.ref("stock.action_report_delivery").sudo() pdf = report_sudo.render_qweb_pdf([picking_sudo.id])[0] pdfhttpheaders = [ - ('Content-Type', 'application/pdf'), - ('Content-Length', len(pdf)), + ("Content-Type", "application/pdf"), + ("Content-Length", len(pdf)), ] return request.make_response(pdf, headers=pdfhttpheaders) def _picking_check_access(self, rma_id, picking_id, access_token=None): - rma = request.env['rma'].browse([rma_id]) - picking = request.env['stock.picking'].browse([picking_id]) + rma = request.env["rma"].browse([rma_id]) + picking = request.env["stock.picking"].browse([picking_id]) picking_sudo = picking.sudo() try: - picking.check_access_rights('read') - picking.check_access_rule('read') + picking.check_access_rights("read") + picking.check_access_rule("read") except exceptions.AccessError: if not access_token or not consteq(rma.access_token, access_token): raise diff --git a/rma/data/mail_data.xml b/rma/data/mail_data.xml index 7c118726..3a0c32c2 100644 --- a/rma/data/mail_data.xml +++ b/rma/data/mail_data.xml @@ -1,10 +1,10 @@ - + Draft RMA rma - + RMA in draft state @@ -12,25 +12,27 @@ Draft RMA 10 rma.team - - + + team_id RMA Notification - + ${object.user_id.email_formatted |safe} ${object.partner_id.id} - ${object.company_id.name} RMA (Ref ${object.name or 'n/a' }) + ${object.company_id.name} RMA (Ref ${object.name or 'n/a' }) ${(object.name or '')} ${object.partner_id.lang} - - + + -
-

+

+

Dear ${object.partner_id.name} % if object.partner_id.parent_id: (${object.partner_id.parent_id.name}) @@ -40,7 +42,7 @@

Do not hesitate to contact us if you have any question.

-
+
diff --git a/rma/data/rma_operation_data.xml b/rma/data/rma_operation_data.xml index 60ea6e04..a194b87d 100644 --- a/rma/data/rma_operation_data.xml +++ b/rma/data/rma_operation_data.xml @@ -1,4 +1,4 @@ - + Replace diff --git a/rma/hooks.py b/rma/hooks.py index f5e24d84..1c2791c8 100644 --- a/rma/hooks.py +++ b/rma/hooks.py @@ -1,7 +1,7 @@ # Copyright 2020 Tecnativa - Ernesto Tejeda # 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): @@ -9,62 +9,65 @@ def post_init_hook(cr, registry): def _get_next_picking_type_color(): """ 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( - [('warehouse_id', '!=', False), ('color', '!=', False)], - ['color'], - order='color', + [("warehouse_id", "!=", False), ("color", "!=", False)], + ["color"], + order="color", ) - all_used_colors = [res['color'] for res in picking_type] - available_colors = [color for color in range(0, 12) - if color not in all_used_colors] + all_used_colors = [res["color"] for res in picking_type] + available_colors = [ + color for color in range(0, 12) if color not in all_used_colors + ] return available_colors[0] if available_colors else 0 def create_rma_locations(warehouse): - stock_location = env['stock.location'] + stock_location = env["stock.location"] location_vals = warehouse._get_locations_values({}) for field_name, values in location_vals.items(): - if field_name == 'rma_loc_id' and not warehouse.rma_loc_id: - warehouse.rma_loc_id = stock_location.with_context( - active_test=False).create(values).id + if field_name == "rma_loc_id" and not warehouse.rma_loc_id: + warehouse.rma_loc_id = ( + stock_location.with_context(active_test=False).create(values).id + ) def create_rma_picking_types(whs): - ir_sequence_sudo = env['ir.sequence'].sudo() - stock_picking_type = env['stock.picking.type'] + ir_sequence_sudo = env["ir.sequence"].sudo() + stock_picking_type = env["stock.picking.type"] color = _get_next_picking_type_color() 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 create_data = whs._get_picking_type_create_values(max_sequence)[0] sequence_data = whs._get_sequence_values() data = {} for picking_type, values in create_data.items(): - if (picking_type in ['rma_in_type_id', 'rma_out_type_id'] - and not whs[picking_type]): + if ( + picking_type in ["rma_in_type_id", "rma_out_type_id"] + and not whs[picking_type] + ): picking_sequence = sequence_data[picking_type] sequence = ir_sequence_sudo.create(picking_sequence) values.update( - warehouse_id=whs.id, - color=color, - sequence_id=sequence.id, + warehouse_id=whs.id, color=color, sequence_id=sequence.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.write({ - '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.write({ - 'return_picking_type_id': data.get('rma_out_type_id', False) - }) + rma_out_type = stock_picking_type.browse(data["rma_out_type_id"]) + rma_out_type.write( + {"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.write( + {"return_picking_type_id": data.get("rma_out_type_id", False)} + ) whs.write(data) # Create rma locations and picking types - warehouses = env['stock.warehouse'].search([]) + warehouses = env["stock.warehouse"].search([]) for warehouse in warehouses: create_rma_locations(warehouse) create_rma_picking_types(warehouse) # Create rma sequence per company - for company in env['res.company'].search([]): + for company in env["res.company"].search([]): company.create_rma_index() diff --git a/rma/migrations/12.0.2.0.0/post-migration.py b/rma/migrations/12.0.2.0.0/post-migration.py index 77fe95b4..6056dcab 100644 --- a/rma/migrations/12.0.2.0.0/post-migration.py +++ b/rma/migrations/12.0.2.0.0/post-migration.py @@ -6,5 +6,4 @@ from openupgradelib import openupgrade @openupgrade.migrate() def migrate(env, version): # Convert Text description field to Html - openupgrade.convert_field_to_html( - env.cr, "rma", "description", "description") + openupgrade.convert_field_to_html(env.cr, "rma", "description", "description") diff --git a/rma/models/account_invoice.py b/rma/models/account_invoice.py index b3e86196..823a4989 100644 --- a/rma/models/account_invoice.py +++ b/rma/models/account_invoice.py @@ -7,32 +7,41 @@ from odoo.tools import float_compare class AccountInvoice(models.Model): - _inherit = 'account.invoice' + _inherit = "account.invoice" def action_invoice_open(self): """ Avoids to validate a refund with less quantity of product than quantity in the linked RMA. """ - precision = self.env['decimal.precision'].precision_get( - 'Product Unit of Measure') - if 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)): + precision = self.env["decimal.precision"].precision_get( + "Product Unit of Measure" + ) + if ( + 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( - _("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() def unlink(self): - rma = self.mapped('invoice_line_ids.rma_id') - rma.write({'state': 'received'}) + rma = self.mapped("invoice_line_ids.rma_id") + rma.write({"state": "received"}) return super().unlink() class AccountInvoiceLine(models.Model): - _inherit = 'account.invoice.line' + _inherit = "account.invoice.line" - rma_id = fields.Many2one( - comodel_name='rma', - string='RMA', - ) + rma_id = fields.Many2one(comodel_name="rma", string="RMA",) diff --git a/rma/models/res_company.py b/rma/models/res_company.py index cb65960c..a7129391 100644 --- a/rma/models/res_company.py +++ b/rma/models/res_company.py @@ -1,7 +1,7 @@ # Copyright 2020 Tecnativa - Ernesto Tejeda # 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): @@ -14,10 +14,16 @@ class Company(models.Model): return company def create_rma_index(self): - return self.env['ir.sequence'].sudo().create({ - 'name': _('RMA Code'), - 'prefix': 'RMA', - 'code': 'rma', - 'padding': 4, - 'company_id': self.id, - }) + return ( + self.env["ir.sequence"] + .sudo() + .create( + { + "name": _("RMA Code"), + "prefix": "RMA", + "code": "rma", + "padding": 4, + "company_id": self.id, + } + ) + ) diff --git a/rma/models/res_partner.py b/rma/models/res_partner.py index 494c267a..d26f3845 100644 --- a/rma/models/res_partner.py +++ b/rma/models/res_partner.py @@ -5,37 +5,31 @@ from odoo import fields, models class ResPartner(models.Model): - _inherit = 'res.partner' + _inherit = "res.partner" rma_ids = fields.One2many( - comodel_name='rma', - inverse_name='partner_id', - string='RMAs', - ) - rma_count = fields.Integer( - string='RMA count', - compute='_compute_rma_count', + comodel_name="rma", inverse_name="partner_id", string="RMAs", ) + rma_count = fields.Integer(string="RMA count", compute="_compute_rma_count",) def _compute_rma_count(self): - rma_data = self.env['rma'].read_group( - [('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]) + rma_data = self.env["rma"].read_group( + [("partner_id", "in", self.ids)], ["partner_id"], ["partner_id"] + ) + mapped_data = { + r["partner_id"][0]: r["partner_id_count"] for r in rma_data + } for record in self: record.rma_count = mapped_data.get(record.id, 0) def action_view_rma(self): 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 if len(rma) == 1: action.update( - res_id=rma.id, - view_mode="form", - view_id=False, - views=False, + res_id=rma.id, view_mode="form", view_id=False, views=False, ) else: - action['domain'] = [('partner_id', 'in', self.ids)] + action["domain"] = [("partner_id", "in", self.ids)] return action diff --git a/rma/models/res_users.py b/rma/models/res_users.py index 4cce3c9a..47ebd213 100644 --- a/rma/models/res_users.py +++ b/rma/models/res_users.py @@ -5,10 +5,10 @@ from odoo import fields, models class ResUsers(models.Model): - _inherit = 'res.users' + _inherit = "res.users" rma_team_id = fields.Many2one( - comodel_name='rma.team', + comodel_name="rma.team", string="RMA Team", - help='RMA Team the user is member of.', + help="RMA Team the user is member of.", ) diff --git a/rma/models/rma.py b/rma/models/rma.py index ee40d060..4c6251a1 100644 --- a/rma/models/rma.py +++ b/rma/models/rma.py @@ -1,13 +1,15 @@ # Copyright 2020 Tecnativa - Ernesto Tejeda # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from collections import Counter + from odoo import _, api, fields, models from odoo.exceptions import ValidationError from odoo.tests import Form from odoo.tools import html2plaintext + import odoo.addons.decimal_precision as dp from odoo.addons.stock.models.stock_move import PROCUREMENT_PRIORITIES -from collections import Counter class Rma(models.Model): @@ -17,25 +19,22 @@ class Rma(models.Model): _inherit = ["mail.thread", "portal.mixin", "mail.activity.mixin"] def _domain_location_id(self): - rma_loc = self.env['stock.warehouse'].search([]).mapped('rma_loc_id') - return [('id', 'child_of', rma_loc.ids)] + rma_loc = self.env["stock.warehouse"].search([]).mapped("rma_loc_id") + return [("id", "child_of", rma_loc.ids)] # General fields sent = fields.Boolean() name = fields.Char( - string='Name', + string="Name", index=True, readonly=True, - states={'draft': [('readonly', False)]}, + states={"draft": [("readonly", False)]}, copy=False, - default=lambda self: _('New'), + default=lambda self: _("New"), ) origin = fields.Char( - string='Source Document', - states={ - 'locked': [('readonly', True)], - 'cancelled': [('readonly', True)], - }, + string="Source Document", + states={"locked": [("readonly", True)], "cancelled": [("readonly", True)],}, help="Reference of the document that generated this RMA.", ) date = fields.Datetime( @@ -43,109 +42,95 @@ class Rma(models.Model): index=True, required=True, readonly=True, - states={'draft': [('readonly', False)]}, + states={"draft": [("readonly", False)]}, ) deadline = fields.Date( - states={ - 'locked': [('readonly', True)], - 'cancelled': [('readonly', True)], - }, + states={"locked": [("readonly", True)], "cancelled": [("readonly", True)],}, ) user_id = fields.Many2one( comodel_name="res.users", string="Responsible", track_visibility="always", - states={ - 'locked': [('readonly', True)], - 'cancelled': [('readonly', True)], - }, + states={"locked": [("readonly", True)], "cancelled": [("readonly", True)],}, ) team_id = fields.Many2one( comodel_name="rma.team", string="RMA team", index=True, - states={ - 'locked': [('readonly', True)], - 'cancelled': [('readonly', True)], - }, + states={"locked": [("readonly", True)], "cancelled": [("readonly", True)],}, ) company_id = fields.Many2one( comodel_name="res.company", default=lambda self: self.env.user.company_id, - states={ - 'locked': [('readonly', True)], - 'cancelled': [('readonly', True)], - }, + states={"locked": [("readonly", True)], "cancelled": [("readonly", True)],}, ) partner_id = fields.Many2one( string="Customer", comodel_name="res.partner", readonly=True, - states={'draft': [('readonly', False)]}, + states={"draft": [("readonly", False)]}, index=True, - track_visibility='always' + track_visibility="always", ) partner_invoice_id = fields.Many2one( string="Invoice Address", comodel_name="res.partner", readonly=True, - states={'draft': [('readonly', False)]}, - domain=[('customer', '=', True)], - help="Refund address for current RMA." + states={"draft": [("readonly", False)]}, + domain=[("customer", "=", True)], + help="Refund address for current RMA.", ) commercial_partner_id = fields.Many2one( - comodel_name="res.partner", - related="partner_id.commercial_partner_id", + comodel_name="res.partner", related="partner_id.commercial_partner_id", ) picking_id = fields.Many2one( comodel_name="stock.picking", string="Origin Delivery", domain="[" - " ('state', '=', 'done')," - " ('picking_type_id.code', '=', 'outgoing')," - " ('partner_id', 'child_of', commercial_partner_id)," - "]", + " ('state', '=', 'done')," + " ('picking_type_id.code', '=', 'outgoing')," + " ('partner_id', 'child_of', commercial_partner_id)," + "]", readonly=True, - states={'draft': [('readonly', False)]}, + states={"draft": [("readonly", False)]}, ) move_id = fields.Many2one( - comodel_name='stock.move', - string='Origin move', + comodel_name="stock.move", + string="Origin move", domain="[" - " ('picking_id', '=', picking_id)," - " ('picking_id', '!=', False)" - "]", + " ('picking_id', '=', picking_id)," + " ('picking_id', '!=', False)" + "]", readonly=True, - states={'draft': [('readonly', False)]}, + states={"draft": [("readonly", False)]}, ) product_id = fields.Many2one( - comodel_name="product.product", - domain=[('type', 'in', ['consu', 'product'])], + comodel_name="product.product", domain=[("type", "in", ["consu", "product"])], ) product_uom_qty = fields.Float( string="Quantity", required=True, default=1.0, - digits=dp.get_precision('Product Unit of Measure'), + digits=dp.get_precision("Product Unit of Measure"), readonly=True, - states={'draft': [('readonly', False)]}, + states={"draft": [("readonly", False)]}, ) product_uom = fields.Many2one( comodel_name="uom.uom", string="UoM", required=True, readonly=True, - states={'draft': [('readonly', False)]}, - default=lambda self: self.env.ref('uom.product_uom_unit').id, + states={"draft": [("readonly", False)]}, + default=lambda self: self.env.ref("uom.product_uom_unit").id, ) procurement_group_id = fields.Many2one( - comodel_name='procurement.group', - string='Procurement group', + comodel_name="procurement.group", + string="Procurement group", readonly=True, states={ - 'draft': [('readonly', False)], - 'confirmed': [('readonly', False)], - 'received': [('readonly', False)], + "draft": [("readonly", False)], + "confirmed": [("readonly", False)], + "received": [("readonly", False)], }, ) priority = fields.Selection( @@ -153,11 +138,10 @@ class Rma(models.Model): selection=PROCUREMENT_PRIORITIES, default="1", readonly=True, - states={'draft': [('readonly', False)]}, + states={"draft": [("readonly", False)]}, ) operation_id = fields.Many2one( - comodel_name='rma.operation', - string='Requested operation', + comodel_name="rma.operation", string="Requested operation", ) state = fields.Selection( [ @@ -177,120 +161,96 @@ class Rma(models.Model): track_visibility="onchange", ) description = fields.Html( - states={ - 'locked': [('readonly', True)], - 'cancelled': [('readonly', True)], - }, + states={"locked": [("readonly", True)], "cancelled": [("readonly", True)],}, ) # Reception fields location_id = fields.Many2one( - comodel_name='stock.location', + comodel_name="stock.location", domain=_domain_location_id, readonly=True, - states={'draft': [('readonly', False)]}, + states={"draft": [("readonly", False)]}, ) warehouse_id = fields.Many2one( - comodel_name='stock.warehouse', - compute="_compute_warehouse_id", - store=True, + comodel_name="stock.warehouse", compute="_compute_warehouse_id", store=True, ) reception_move_id = fields.Many2one( - comodel_name='stock.move', - string='Reception move', - copy=False, + comodel_name="stock.move", string="Reception move", copy=False, ) # Refund fields refund_id = fields.Many2one( - comodel_name='account.invoice', - string='Refund', - readonly=True, - copy=False, + comodel_name="account.invoice", string="Refund", readonly=True, copy=False, ) refund_line_id = fields.Many2one( - comodel_name='account.invoice.line', - string='Refund line', + comodel_name="account.invoice.line", + string="Refund line", readonly=True, copy=False, ) - can_be_refunded = fields.Boolean( - compute="_compute_can_be_refunded" - ) + can_be_refunded = fields.Boolean(compute="_compute_can_be_refunded") # Delivery fields delivery_move_ids = fields.One2many( - comodel_name='stock.move', - inverse_name='rma_id', - string='Delivery reservation', + comodel_name="stock.move", + inverse_name="rma_id", + string="Delivery reservation", readonly=True, copy=False, ) delivery_picking_count = fields.Integer( - string='Delivery count', - compute='_compute_delivery_picking_count', + string="Delivery count", compute="_compute_delivery_picking_count", ) delivered_qty = fields.Float( string="Delivered qty", - digits=dp.get_precision('Product Unit of Measure'), - compute='_compute_delivered_qty', + digits=dp.get_precision("Product Unit of Measure"), + compute="_compute_delivered_qty", store=True, ) delivered_qty_done = fields.Float( string="Delivered qty done", - digits=dp.get_precision('Product Unit of Measure'), - compute='_compute_delivered_qty', - ) - can_be_returned = fields.Boolean( - compute="_compute_can_be_returned", - ) - can_be_replaced = fields.Boolean( - compute="_compute_can_be_replaced", - ) - can_be_locked = fields.Boolean( - compute="_compute_can_be_locked", + digits=dp.get_precision("Product Unit of Measure"), + compute="_compute_delivered_qty", ) + can_be_returned = fields.Boolean(compute="_compute_can_be_returned",) + can_be_replaced = fields.Boolean(compute="_compute_can_be_replaced",) + can_be_locked = fields.Boolean(compute="_compute_can_be_locked",) remaining_qty = fields.Float( string="Remaining delivered qty", - digits=dp.get_precision('Product Unit of Measure'), - compute='_compute_remaining_qty', + digits=dp.get_precision("Product Unit of Measure"), + compute="_compute_remaining_qty", ) remaining_qty_to_done = fields.Float( string="Remaining delivered qty to done", - digits=dp.get_precision('Product Unit of Measure'), - compute='_compute_remaining_qty', + digits=dp.get_precision("Product Unit of Measure"), + compute="_compute_remaining_qty", ) # Split fields - can_be_split = fields.Boolean( - compute="_compute_can_be_split", - ) + can_be_split = fields.Boolean(compute="_compute_can_be_split",) origin_split_rma_id = fields.Many2one( - comodel_name='rma', - string='Extracted from', - readonly=True, - copy=False, + comodel_name="rma", string="Extracted from", readonly=True, copy=False, ) def _compute_delivery_picking_count(self): # It is enough to count the moves to know how many pickings # there are because there will be a unique move linked to the # same picking and the same rma. - rma_data = self.env['stock.move'].read_group( - [('rma_id', 'in', self.ids)], - ['rma_id', 'picking_id'], - ['rma_id', 'picking_id'], + rma_data = self.env["stock.move"].read_group( + [("rma_id", "in", self.ids)], + ["rma_id", "picking_id"], + ["rma_id", "picking_id"], lazy=False, ) - mapped_data = Counter(map(lambda r: r['rma_id'][0], rma_data)) + mapped_data = Counter(map(lambda r: r["rma_id"][0], rma_data)) for record in self: record.delivery_picking_count = mapped_data.get(record.id, 0) @api.depends( - 'delivery_move_ids', - 'delivery_move_ids.state', - 'delivery_move_ids.scrapped', - 'delivery_move_ids.product_uom_qty', - 'delivery_move_ids.reserved_availability', - 'delivery_move_ids.quantity_done', - 'delivery_move_ids.product_uom', - 'product_uom', + "delivery_move_ids", + "delivery_move_ids.state", + "delivery_move_ids.scrapped", + "delivery_move_ids.product_uom_qty", + "delivery_move_ids.reserved_availability", + "delivery_move_ids.quantity_done", + "delivery_move_ids.product_uom", + "product_uom", ) def _compute_delivered_qty(self): """ Compute 'delivered_qty' and 'delivered_qty_done' fields. @@ -309,23 +269,27 @@ class Rma(models.Model): delivered_qty = 0.0 delivered_qty_done = 0.0 for move in record.delivery_move_ids.filtered( - lambda r: r.state != 'cancel' and not r.scrapped): + lambda r: r.state != "cancel" and not r.scrapped + ): if move.quantity_done: quantity_done = move.product_uom._compute_quantity( - move.quantity_done, record.product_uom) - if move.state == 'done': + move.quantity_done, record.product_uom + ) + if move.state == "done": delivered_qty_done += quantity_done delivered_qty += quantity_done elif move.reserved_availability: delivered_qty += move.product_uom._compute_quantity( - move.reserved_availability, record.product_uom) + move.reserved_availability, record.product_uom + ) elif move.product_uom_qty: delivered_qty += move.product_uom._compute_quantity( - move.product_uom_qty, record.product_uom) + move.product_uom_qty, record.product_uom + ) record.delivered_qty = delivered_qty record.delivered_qty_done = delivered_qty_done - @api.depends('product_uom_qty', 'delivered_qty', 'delivered_qty_done') + @api.depends("product_uom_qty", "delivered_qty", "delivered_qty_done") def _compute_remaining_qty(self): """ Compute 'remaining_qty' and 'remaining_qty_to_done' fields. @@ -342,18 +306,16 @@ class Rma(models.Model): r.remaining_qty = r.product_uom_qty - r.delivered_qty r.remaining_qty_to_done = r.product_uom_qty - r.delivered_qty_done - @api.depends( - 'state', - ) + @api.depends("state",) def _compute_can_be_refunded(self): """ Compute 'can_be_refunded'. This field controls the visibility of 'Refund' button in the rma form view and determinates if an rma can be refunded. It is used in rma.action_refund method. """ for record in self: - record.can_be_refunded = record.state == 'received' + record.can_be_refunded = record.state == "received" - @api.depends('remaining_qty', 'state') + @api.depends("remaining_qty", "state") def _compute_can_be_returned(self): """ Compute 'can_be_returned'. This field controls the visibility of the 'Return to customer' button in the rma form @@ -363,10 +325,11 @@ class Rma(models.Model): rma._ensure_can_be_returned. """ for r in self: - r.can_be_returned = (r.state in ['received', 'waiting_return'] - and r.remaining_qty > 0) + r.can_be_returned = ( + r.state in ["received", "waiting_return"] and r.remaining_qty > 0 + ) - @api.depends('state') + @api.depends("state") def _compute_can_be_replaced(self): """ Compute 'can_be_replaced'. This field controls the visibility of 'Replace' button in the rma form @@ -376,11 +339,13 @@ class Rma(models.Model): rma._ensure_can_be_replaced. """ for r in self: - r.can_be_replaced = r.state in ['received', 'waiting_replacement', - 'replaced'] + r.can_be_replaced = r.state in [ + "received", + "waiting_replacement", + "replaced", + ] - @api.depends('product_uom_qty', 'state', 'remaining_qty', - 'remaining_qty_to_done') + @api.depends("product_uom_qty", "state", "remaining_qty", "remaining_qty_to_done") def _compute_can_be_split(self): """ Compute 'can_be_split'. This field controls the visibility of 'Split' button in the rma form view and @@ -389,54 +354,63 @@ class Rma(models.Model): rma._ensure_can_be_split """ for r in self: - if (r.product_uom_qty > 1 - and ((r.state == 'waiting_return' and r.remaining_qty > 0) - or (r.state == 'waiting_replacement' - and r.remaining_qty_to_done > 0))): + if r.product_uom_qty > 1 and ( + (r.state == "waiting_return" and r.remaining_qty > 0) + or (r.state == "waiting_replacement" and r.remaining_qty_to_done > 0) + ): r.can_be_split = True else: r.can_be_split = False - @api.depends('remaining_qty_to_done', 'state') + @api.depends("remaining_qty_to_done", "state") def _compute_can_be_locked(self): for r in self: - r.can_be_locked = (r.remaining_qty_to_done > 0 - and r.state in ['received', - 'waiting_return', - 'waiting_replacement']) + r.can_be_locked = r.remaining_qty_to_done > 0 and r.state in [ + "received", + "waiting_return", + "waiting_replacement", + ] - @api.depends('location_id') + @api.depends("location_id") def _compute_warehouse_id(self): - for record in self.filtered('location_id'): - record.warehouse_id = self.env['stock.warehouse'].search( - [('rma_loc_id', 'parent_of', record.location_id.id)], limit=1) + for record in self.filtered("location_id"): + record.warehouse_id = self.env["stock.warehouse"].search( + [("rma_loc_id", "parent_of", record.location_id.id)], limit=1 + ) def _compute_access_url(self): for record in self: - record.access_url = '/my/rmas/{}'.format(record.id) + record.access_url = "/my/rmas/{}".format(record.id) # Constrains methods (@api.constrains) - @api.constrains('state', 'partner_id', 'partner_invoice_id', 'product_id') + @api.constrains("state", "partner_id", "partner_invoice_id", "product_id") def _check_required_after_draft(self): """ Check that RMAs are being created or edited with the necessary fields filled out. Only applies to 'Draft' and 'Cancelled' states. """ - rma = self.filtered(lambda r: r.state not in ['draft', 'cancelled']) + rma = self.filtered(lambda r: r.state not in ["draft", "cancelled"]) rma._ensure_required_fields() # onchange methods (@api.onchange) @api.onchange("user_id") def _onchange_user_id(self): if self.user_id: - self.team_id = self.env['rma.team'].sudo().search([ - '|', - ('user_id', '=', self.user_id.id), - ('member_ids', '=', self.user_id.id), - '|', - ('company_id', '=', False), - ('company_id', 'child_of', self.company_id.ids) - ], limit=1) + self.team_id = ( + self.env["rma.team"] + .sudo() + .search( + [ + "|", + ("user_id", "=", self.user_id.id), + ("member_ids", "=", self.user_id.id), + "|", + ("company_id", "=", False), + ("company_id", "child_of", self.company_id.ids), + ], + limit=1, + ) + ) else: self.team_id = False @@ -445,8 +419,8 @@ class Rma(models.Model): self.picking_id = False partner_invoice_id = False if self.partner_id: - address = self.partner_id.address_get(['invoice']) - partner_invoice_id = address.get('invoice', False) + address = self.partner_id.address_get(["invoice"]) + partner_invoice_id = address.get("invoice", False) self.partner_invoice_id = partner_invoice_id @api.onchange("picking_id") @@ -472,31 +446,32 @@ class Rma(models.Model): if self.product_id: # Set UoM and UoM 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 - or self.product_id.uom_id.id != self.product_uom.id): + if not self.product_uom or self.product_id.uom_id.id != self.product_uom.id: self.product_uom = self.product_id.uom_id # Set stock location (location_id) user = self.env.user - if (not user.has_group('stock.group_stock_multi_locations') - and not self.location_id): + if ( + not user.has_group("stock.group_stock_multi_locations") + and not self.location_id + ): # If this condition is True, it is because a picking is not set company = self.company_id or self.env.user.company_id - warehouse = self.env['stock.warehouse'].search( - [('company_id', '=', company.id)], limit=1) + warehouse = self.env["stock.warehouse"].search( + [("company_id", "=", company.id)], limit=1 + ) self.location_id = warehouse.rma_loc_id.id - return {'domain': {'product_uom': domain_product_uom}} + return {"domain": {"product_uom": domain_product_uom}} # CRUD methods (ORM overrides) @api.model def create(self, vals): - if vals.get('name', _('New')) == _('New'): - ir_sequence = self.env['ir.sequence'] - if 'company_id' in vals: - ir_sequence = ir_sequence.with_context( - force_company=vals['company_id']) - vals['name'] = ir_sequence.next_by_code('rma') + if vals.get("name", _("New")) == _("New"): + ir_sequence = self.env["ir.sequence"] + if "company_id" in vals: + ir_sequence = ir_sequence.with_context(force_company=vals["company_id"]) + vals["name"] = ir_sequence.next_by_code("rma") # Assign a default team_id which will be the first in the sequence if "team_id" not in vals: vals["team_id"] = self.env["rma.team"].search([], limit=1).id @@ -506,55 +481,57 @@ class Rma(models.Model): def copy(self, default=None): team = super().copy(default) for follower in self.message_follower_ids: - team.message_subscribe(partner_ids=follower.partner_id.ids, - subtype_ids=follower.subtype_ids.ids) + team.message_subscribe( + partner_ids=follower.partner_id.ids, + subtype_ids=follower.subtype_ids.ids, + ) return team def unlink(self): - if self.filtered(lambda r: r.state != 'draft'): + if self.filtered(lambda r: r.state != "draft"): raise ValidationError( - _("You cannot delete RMAs that are not in draft state")) + _("You cannot delete RMAs that are not in draft state") + ) return super().unlink() # Action methods def action_rma_send(self): self.ensure_one() - template = self.env.ref('rma.mail_template_rma_notification', False) - form = self.env.ref('mail.email_compose_message_wizard_form', False) + template = self.env.ref("rma.mail_template_rma_notification", False) + form = self.env.ref("mail.email_compose_message_wizard_form", False) ctx = { - 'default_model': 'rma', - 'default_res_id': self.ids[0], - 'default_use_template': bool(template), - 'default_template_id': template and template.id or False, - 'default_composition_mode': 'comment', - 'mark_rma_as_sent': True, - 'model_description': 'RMA', - 'force_email': True + "default_model": "rma", + "default_res_id": self.ids[0], + "default_use_template": bool(template), + "default_template_id": template and template.id or False, + "default_composition_mode": "comment", + "mark_rma_as_sent": True, + "model_description": "RMA", + "force_email": True, } return { - 'type': 'ir.actions.act_window', - 'view_type': 'form', - 'view_mode': 'form', - 'res_model': 'mail.compose.message', - 'views': [(form.id, 'form')], - 'view_id': form.id, - 'target': 'new', - 'context': ctx, + "type": "ir.actions.act_window", + "view_type": "form", + "view_mode": "form", + "res_model": "mail.compose.message", + "views": [(form.id, "form")], + "view_id": form.id, + "target": "new", + "context": ctx, } def action_confirm(self): """Invoked when 'Confirm' button in rma form view is clicked.""" self.ensure_one() self._ensure_required_fields() - if self.state == 'draft': + if self.state == "draft": if self.picking_id: reception_move = self._create_receptions_from_picking() else: reception_move = self._create_receptions_from_product() - self.write({ - 'reception_move_id': reception_move.id, - 'state': 'confirmed', - }) + self.write( + {"reception_move_id": reception_move.id, "state": "confirmed",} + ) if self.partner_id not in self.message_partner_ids: self.message_subscribe([self.partner_id.id]) @@ -565,14 +542,16 @@ class Rma(models.Model): group_dict = {} for record in self.filtered("can_be_refunded"): key = (record.partner_invoice_id.id, record.company_id.id) - group_dict.setdefault(key, self.env['rma']) + group_dict.setdefault(key, self.env["rma"]) group_dict[key] |= record for rmas in group_dict.values(): - origin = ', '.join(rmas.mapped('name')) - invoice_form = Form(self.env['account.invoice'].with_context( - default_type='out_refund', - company_id=rmas[0].company_id.id, - ), "account.invoice_form") + origin = ", ".join(rmas.mapped("name")) + invoice_form = Form( + self.env["account.invoice"].with_context( + default_type="out_refund", company_id=rmas[0].company_id.id, + ), + "account.invoice_form", + ) rmas[0]._prepare_refund(invoice_form, origin) refund = invoice_form.save() for rma in rmas: @@ -583,18 +562,20 @@ class Rma(models.Model): # create method instead of save the form. We also need # the new refund line id to be linked to the rma. refund_vals = invoice_form._values_to_save(all_fields=True) - line_vals = refund_vals['invoice_line_ids'][-1][2] + line_vals = refund_vals["invoice_line_ids"][-1][2] line_vals.update(invoice_id=refund.id, rma_id=rma.id) - line = self.env['account.invoice.line'].create(line_vals) - rma.write({ - 'refund_line_id': line.id, - 'refund_id': refund.id, - 'state': 'refunded', - }) + line = self.env["account.invoice.line"].create(line_vals) + rma.write( + { + "refund_line_id": line.id, + "refund_id": refund.id, + "state": "refunded", + } + ) refund.message_post_with_view( - 'mail.message_origin_link', - values={'self': refund, 'origin': rmas}, - subtype_id=self.env.ref('mail.mt_note').id, + "mail.message_origin_link", + values={"self": refund, "origin": rmas}, + subtype_id=self.env.ref("mail.mt_note").id, ) def action_replace(self): @@ -603,14 +584,15 @@ class Rma(models.Model): self._ensure_can_be_replaced() # Force active_id to avoid issues when coming from smart buttons # in other models - action = self.env.ref("rma.rma_delivery_wizard_action").with_context( - active_id=self.id).read()[0] - action['name'] = 'Replace product(s)' - action['context'] = dict(self.env.context) - action['context'].update( - active_id=self.id, - active_ids=self.ids, - rma_delivery_type='replace', + action = ( + self.env.ref("rma.rma_delivery_wizard_action") + .with_context(active_id=self.id) + .read()[0] + ) + action["name"] = "Replace product(s)" + action["context"] = dict(self.env.context) + action["context"].update( + active_id=self.id, active_ids=self.ids, rma_delivery_type="replace", ) return action @@ -622,13 +604,14 @@ class Rma(models.Model): self._ensure_can_be_returned() # Force active_id to avoid issues when coming from smart buttons # in other models - action = self.env.ref("rma.rma_delivery_wizard_action").with_context( - active_id=self.id).read()[0] - action['context'] = dict(self.env.context) - action['context'].update( - active_id=self.id, - active_ids=self.ids, - rma_delivery_type='return', + action = ( + self.env.ref("rma.rma_delivery_wizard_action") + .with_context(active_id=self.id) + .read()[0] + ) + action["context"] = dict(self.env.context) + action["context"].update( + active_id=self.id, active_ids=self.ids, rma_delivery_type="return", ) return action @@ -638,37 +621,40 @@ class Rma(models.Model): self._ensure_can_be_split() # Force active_id to avoid issues when coming from smart buttons # in other models - action = self.env.ref("rma.rma_split_wizard_action").with_context( - active_id=self.id).read()[0] - action['context'] = dict(self.env.context) - action['context'].update(active_id=self.id, active_ids=self.ids) + action = ( + self.env.ref("rma.rma_split_wizard_action") + .with_context(active_id=self.id) + .read()[0] + ) + action["context"] = dict(self.env.context) + action["context"].update(active_id=self.id, active_ids=self.ids) return action def action_cancel(self): """Invoked when 'Cancel' button in rma form view is clicked.""" - self.mapped('reception_move_id')._action_cancel() - self.write({'state': 'cancelled'}) + self.mapped("reception_move_id")._action_cancel() + self.write({"state": "cancelled"}) def action_draft(self): - cancelled_rma = self.filtered(lambda r: r.state == 'cancelled') - cancelled_rma.write({'state': 'draft'}) + cancelled_rma = self.filtered(lambda r: r.state == "cancelled") + cancelled_rma.write({"state": "draft"}) def action_lock(self): """Invoked when 'Lock' button in rma form view is clicked.""" - self.filtered("can_be_locked").write({'state': 'locked'}) + self.filtered("can_be_locked").write({"state": "locked"}) def action_unlock(self): """Invoked when 'Unlock' button in rma form view is clicked.""" - locked_rma = self.filtered(lambda r: r.state == 'locked') - locked_rma.write({'state': 'received'}) + locked_rma = self.filtered(lambda r: r.state == "locked") + locked_rma.write({"state": "received"}) def action_preview(self): """Invoked when 'Preview' button in rma form view is clicked.""" self.ensure_one() return { - 'type': 'ir.actions.act_url', - 'target': 'self', - 'url': self.get_portal_url(), + "type": "ir.actions.act_url", + "target": "self", + "url": self.get_portal_url(), } def action_view_receipt(self): @@ -676,8 +662,11 @@ class Rma(models.Model): self.ensure_one() # Force active_id to avoid issues when coming from smart buttons # in other models - action = self.env.ref('stock.action_picking_tree_all').with_context( - active_id=self.id).read()[0] + action = ( + self.env.ref("stock.action_picking_tree_all") + .with_context(active_id=self.id) + .read()[0] + ) action.update( res_id=self.reception_move_id.picking_id.id, view_mode="form", @@ -690,28 +679,28 @@ class Rma(models.Model): """Invoked when 'Refund' smart button in rma form view is clicked.""" self.ensure_one() return { - 'name': _('Refund'), - 'type': 'ir.actions.act_window', - 'view_type': 'form', - 'view_mode': 'form', - 'res_model': 'account.invoice', - 'views': [(self.env.ref('account.invoice_form').id, 'form')], - 'res_id': self.refund_id.id, + "name": _("Refund"), + "type": "ir.actions.act_window", + "view_type": "form", + "view_mode": "form", + "res_model": "account.invoice", + "views": [(self.env.ref("account.invoice_form").id, "form")], + "res_id": self.refund_id.id, } def action_view_delivery(self): """Invoked when 'Delivery' smart button in rma form view is clicked.""" - action = self.env.ref('stock.action_picking_tree_all').with_context( - active_id=self.id).read()[0] - picking = self.delivery_move_ids.mapped('picking_id') + action = ( + self.env.ref("stock.action_picking_tree_all") + .with_context(active_id=self.id) + .read()[0] + ) + picking = self.delivery_move_ids.mapped("picking_id") if len(picking) > 1: - action['domain'] = [('id', 'in', picking.ids)] + action["domain"] = [("id", "in", picking.ids)] elif picking: action.update( - res_id=picking.id, - view_mode="form", - view_id=False, - views=False, + res_id=picking.id, view_mode="form", view_id=False, views=False, ) return action @@ -725,15 +714,14 @@ class Rma(models.Model): rma._check_required_after_draft rma.action_confirm """ - ir_translation = self.env['ir.translation'] - required = ['partner_id', 'partner_invoice_id', 'product_id', - 'location_id'] + ir_translation = self.env["ir.translation"] + required = ["partner_id", "partner_invoice_id", "product_id", "location_id"] for record in self: desc = "" for field in filter(lambda item: not record[item], required): desc += "\n%s" % ir_translation.get_field_string("rma")[field] if desc: - raise ValidationError(_('Required field(s):%s') % desc) + raise ValidationError(_("Required field(s):%s") % desc) def _ensure_can_be_returned(self): """ This method is intended to be invoked after user click on @@ -748,11 +736,9 @@ class Rma(models.Model): """ if len(self) == 1: if not self.can_be_returned: - raise ValidationError( - _("This RMA cannot perform a return.")) + raise ValidationError(_("This RMA cannot perform a return.")) elif not self.filtered("can_be_returned"): - raise ValidationError( - _("None of the selected RMAs can perform a return.")) + raise ValidationError(_("None of the selected RMAs can perform a return.")) def _ensure_can_be_replaced(self): """ This method is intended to be invoked after user click on @@ -765,11 +751,11 @@ class Rma(models.Model): """ if len(self) == 1: if not self.can_be_replaced: - raise ValidationError( - _("This RMA cannot perform a replacement.")) + raise ValidationError(_("This RMA cannot perform a replacement.")) elif not self.filtered("can_be_replaced"): raise ValidationError( - _("None of the selected RMAs can perform a replacement.")) + _("None of the selected RMAs can perform a replacement.") + ) def _ensure_can_be_split(self): """intended to be called before launch and after save the split wizard. @@ -790,8 +776,8 @@ class Rma(models.Model): qty = uom._compute_quantity(qty, self.product_uom) if qty > self.remaining_qty: raise ValidationError( - _("The quantity to return is greater than " - "remaining quantity.")) + _("The quantity to return is greater than " "remaining quantity.") + ) def _ensure_qty_to_extract(self, qty, uom): """ This method is intended to be invoked after confirm the wizard. @@ -802,8 +788,10 @@ class Rma(models.Model): to_split_uom_qty = uom._compute_quantity(qty, self.product_uom) if to_split_uom_qty > self.remaining_qty: raise ValidationError( - _("Quantity to extract cannot be greater than remaining " - "delivery quantity (%s %s)") + _( + "Quantity to extract cannot be greater than remaining " + "delivery quantity (%s %s)" + ) % (self.remaining_qty, self.product_uom.name) ) @@ -812,11 +800,12 @@ class Rma(models.Model): self.ensure_one() create_vals = {} if self.location_id: - create_vals['location_id'] = self.location_id.id - return_wizard = self.env['stock.return.picking'].with_context( - active_id=self.picking_id.id, - active_ids=self.picking_id.ids, - ).create(create_vals) + create_vals["location_id"] = self.location_id.id + return_wizard = ( + self.env["stock.return.picking"] + .with_context(active_id=self.picking_id.id, active_ids=self.picking_id.ids,) + .create(create_vals) + ) return_wizard.product_return_moves.filtered( lambda r: r.move_id != self.move_id ).unlink() @@ -825,10 +814,11 @@ class Rma(models.Model): # set_rma_picking_type is to override the copy() method of stock # picking and change the default picking type to rma picking type. picking_action = return_wizard.with_context( - set_rma_picking_type=True).create_returns() - picking_id = picking_action['res_id'] - picking = self.env['stock.picking'].browse(picking_id) - picking.origin = "%s (%s)" % (self.name, picking.origin) + set_rma_picking_type=True + ).create_returns() + picking_id = picking_action["res_id"] + picking = self.env["stock.picking"].browse(picking_id) + picking.origin = "{} ({})".format(self.name, picking.origin) move = picking.move_lines move.priority = self.priority return move @@ -836,8 +826,9 @@ class Rma(models.Model): def _create_receptions_from_product(self): self.ensure_one() picking_form = Form( - recordp=self.env['stock.picking'].with_context( - default_picking_type_id=self.warehouse_id.rma_in_type_id.id), + recordp=self.env["stock.picking"].with_context( + default_picking_type_id=self.warehouse_id.rma_in_type_id.id + ), view="stock.view_picking_form", ) self._prepare_picking(picking_form) @@ -845,9 +836,9 @@ class Rma(models.Model): picking.action_confirm() picking.action_assign() picking.message_post_with_view( - 'mail.message_origin_link', - values={'self': picking, 'origin': self}, - subtype_id=self.env.ref('mail.mt_note').id, + "mail.message_origin_link", + values={"self": picking, "origin": self}, + subtype_id=self.env.ref("mail.mt_note").id, ) return picking.move_lines @@ -868,31 +859,31 @@ class Rma(models.Model): self._ensure_qty_to_extract(qty, uom) self.product_uom_qty -= uom._compute_quantity(qty, self.product_uom) if self.remaining_qty_to_done <= 0: - if self.state == 'waiting_return': - self.state = 'returned' - elif self.state == 'waiting_replacement': - self.state = 'replaced' - extracted_rma = self.copy({ - 'origin': self.name, - 'product_uom_qty': qty, - 'product_uom': uom.id, - 'state': 'received', - 'reception_move_id': self.reception_move_id.id, - 'origin_split_rma_id': self.id, - }) + if self.state == "waiting_return": + self.state = "returned" + elif self.state == "waiting_replacement": + self.state = "replaced" + extracted_rma = self.copy( + { + "origin": self.name, + "product_uom_qty": qty, + "product_uom": uom.id, + "state": "received", + "reception_move_id": self.reception_move_id.id, + "origin_split_rma_id": self.id, + } + ) extracted_rma.message_post_with_view( - 'mail.message_origin_link', - values={'self': extracted_rma, 'origin': self}, - subtype_id=self.env.ref('mail.mt_note').id, + "mail.message_origin_link", + values={"self": extracted_rma, "origin": self}, + subtype_id=self.env.ref("mail.mt_note").id, ) self.message_post( body=_( 'Split: %s has been created.' - ) % ( - extracted_rma.id, - extracted_rma.name, ) + % (extracted_rma.id, extracted_rma.name,) ) return extracted_rma @@ -937,51 +928,51 @@ class Rma(models.Model): self._ensure_can_be_returned() self._ensure_qty_to_return(qty, uom) group_dict = {} - for record in self.filtered('can_be_returned'): - key = (record.partner_id.id, record.company_id.id, - record.warehouse_id) - group_dict.setdefault(key, self.env['rma']) + for record in self.filtered("can_be_returned"): + key = (record.partner_id.id, record.company_id.id, record.warehouse_id) + group_dict.setdefault(key, self.env["rma"]) group_dict[key] |= record for rmas in group_dict.values(): - origin = ', '.join(rmas.mapped('name')) + origin = ", ".join(rmas.mapped("name")) rma_out_type = rmas[0].warehouse_id.rma_out_type_id picking_form = Form( - recordp=self.env['stock.picking'].with_context( - default_picking_type_id=rma_out_type.id), + recordp=self.env["stock.picking"].with_context( + default_picking_type_id=rma_out_type.id + ), view="stock.view_picking_form", ) rmas[0]._prepare_returning_picking(picking_form, origin) picking = picking_form.save() for rma in rmas: with picking_form.move_ids_without_package.new() as move_form: - rma._prepare_returning_move( - move_form, scheduled_date, qty, uom) + rma._prepare_returning_move(move_form, scheduled_date, qty, uom) # rma_id is not present in the form view, so we need to get # the 'values to save' to add the rma id and use the # create method intead of save the form. picking_vals = picking_form._values_to_save(all_fields=True) - move_vals = picking_vals['move_ids_without_package'][-1][2] + move_vals = picking_vals["move_ids_without_package"][-1][2] move_vals.update( picking_id=picking.id, rma_id=rma.id, move_orig_ids=[(4, rma.reception_move_id.id)], company_id=picking.company_id.id, ) - self.env['stock.move'].sudo().create(move_vals) + self.env["stock.move"].sudo().create(move_vals) rma.message_post( body=_( 'Return: %s has been created.' - ) % (picking.id, picking.name) + ) + % (picking.id, picking.name) ) picking.action_confirm() picking.action_assign() picking.message_post_with_view( - 'mail.message_origin_link', - values={'self': picking, 'origin': rmas}, - subtype_id=self.env.ref('mail.mt_note').id, + "mail.message_origin_link", + values={"self": picking, "origin": rmas}, + subtype_id=self.env.ref("mail.mt_note").id, ) - self.write({'state': 'waiting_return'}) + self.write({"state": "waiting_return"}) def _prepare_returning_picking(self, picking_form, origin=None): picking_form.picking_type_id = self.warehouse_id.rma_out_type_id @@ -989,8 +980,9 @@ class Rma(models.Model): picking_form.origin = origin or self.name picking_form.partner_id = self.partner_id - def _prepare_returning_move(self, move_form, scheduled_date, - quantity=None, uom=None): + def _prepare_returning_move( + self, move_form, scheduled_date, quantity=None, uom=None + ): move_form.product_id = self.product_id move_form.product_uom_qty = quantity or self.product_uom_qty move_form.product_uom = uom or self.product_uom @@ -1002,58 +994,65 @@ class Rma(models.Model): self.ensure_one() self._ensure_can_be_replaced() moves_before = self.delivery_move_ids - self._action_launch_stock_rule(scheduled_date, warehouse, product, - qty, uom) + self._action_launch_stock_rule(scheduled_date, warehouse, product, qty, uom) new_move = self.delivery_move_ids - moves_before if new_move: self.reception_move_id.move_dest_ids = [(4, new_move.id)] self.message_post( body=_( - 'Replacement: ' + "Replacement: " 'Move %s (Picking %s) ' - 'has been created.' - ) % (new_move.id, new_move.name_get()[0][1], - new_move.picking_id.id, new_move.picking_id.name) + "has been created." + ) + % ( + new_move.id, + new_move.name_get()[0][1], + new_move.picking_id.id, + new_move.picking_id.name, + ) ) else: self.message_post( body=_( - 'Replacement:
' + "Replacement:
" 'Product %s
' - 'Quantity %f %s
' - 'This replacement did not create a new move, but one of ' - 'the previously created moves was updated with this data.' - ) % (product.id, product.display_name, qty, uom.name) + "Quantity %f %s
" + "This replacement did not create a new move, but one of " + "the previously created moves was updated with this data." + ) + % (product.id, product.display_name, qty, uom.name) ) - if self.state != 'waiting_replacement': - self.state = 'waiting_replacement' + if self.state != "waiting_replacement": + self.state = "waiting_replacement" def _action_launch_stock_rule( - self, - scheduled_date, - warehouse, - product, - qty, - uom, + self, scheduled_date, warehouse, product, qty, uom, ): """ Creates a delivery picking and launch stock rule. It is invoked by: rma.create_replace """ self.ensure_one() - if self.product_id.type not in ('consu', 'product'): + if self.product_id.type not in ("consu", "product"): return if not self.procurement_group_id: - self.procurement_group_id = self.env['procurement.group'].create({ - 'name': self.name, - 'move_type': 'direct', - 'partner_id': self.partner_id.id, - }).id + self.procurement_group_id = ( + self.env["procurement.group"] + .create( + { + "name": self.name, + "move_type": "direct", + "partner_id": self.partner_id.id, + } + ) + .id + ) values = self._prepare_procurement_values( - self.procurement_group_id, scheduled_date, warehouse) - self.env['procurement.group'].run( + self.procurement_group_id, scheduled_date, warehouse + ) + self.env["procurement.group"].run( product, qty, uom, @@ -1064,27 +1063,24 @@ class Rma(models.Model): ) def _prepare_procurement_values( - self, - group_id, - scheduled_date, - warehouse, + self, group_id, scheduled_date, warehouse, ): self.ensure_one() return { - 'company_id': self.company_id, - 'group_id': group_id, - 'date_planned': scheduled_date, - 'warehouse_id': warehouse, - 'partner_id': self.partner_id.id, - 'rma_id': self.id, - 'priority': self.priority, + "company_id": self.company_id, + "group_id": group_id, + "date_planned": scheduled_date, + "warehouse_id": warehouse, + "partner_id": self.partner_id.id, + "rma_id": self.id, + "priority": self.priority, } # Mail business methods def _track_subtype(self, init_values): self.ensure_one() - if 'state' in init_values and self.state == 'draft': - return 'rma.mt_rma_draft' + if "state" in init_values and self.state == "draft": + return "rma.mt_rma_draft" return super()._track_subtype(init_values) def message_new(self, msg_dict, custom_values=None): @@ -1093,38 +1089,38 @@ class Rma(models.Model): """ if custom_values is None: custom_values = {} - subject = msg_dict.get('subject', '') - body = html2plaintext(msg_dict.get('body', '')) + subject = msg_dict.get("subject", "") + body = html2plaintext(msg_dict.get("body", "")) desc = _("E-mail subject: %s\n\nE-mail body:\n%s") % (subject, body) defaults = { - 'description': desc, - 'name': _('New'), - 'origin': _('Incoming e-mail'), + "description": desc, + "name": _("New"), + "origin": _("Incoming e-mail"), } - if msg_dict.get('author_id'): - partner = self.env['res.partner'].browse(msg_dict.get('author_id')) + if msg_dict.get("author_id"): + partner = self.env["res.partner"].browse(msg_dict.get("author_id")) defaults.update( partner_id=partner.id, - partner_invoice_id=partner.address_get( - ['invoice']).get('invoice', False), + partner_invoice_id=partner.address_get(["invoice"]).get( + "invoice", False + ), ) if msg_dict.get("priority"): defaults["priority"] = msg_dict.get("priority") defaults.update(custom_values) rma = super().message_new(msg_dict, custom_values=defaults) - if (rma.user_id - and rma.user_id.partner_id not in rma.message_partner_ids): + if rma.user_id and rma.user_id.partner_id not in rma.message_partner_ids: rma.message_subscribe([rma.user_id.partner_id.id]) return rma - @api.returns('mail.message', lambda value: value.id) + @api.returns("mail.message", lambda value: value.id) def message_post(self, **kwargs): """ Set 'sent' field to True when an email is sent from rma form view. This field (sent) is used to set the appropriate style to the 'Send by Email' button in the rma form view. """ - if self.env.context.get('mark_rma_as_sent'): - self.write({'sent': True}) + if self.env.context.get("mark_rma_as_sent"): + self.write({"sent": True}) # mail_post_autofollow=True to include email recipient contacts as # RMA followers self_with_context = self.with_context(mail_post_autofollow=True) @@ -1133,7 +1129,7 @@ class Rma(models.Model): # Reporting business methods def _get_report_base_filename(self): self.ensure_one() - return 'RMA Report - %s' % self.name + return "RMA Report - %s" % self.name # Other business methods def update_received_state(self): @@ -1143,7 +1139,7 @@ class Rma(models.Model): """ rma = self.filtered(lambda r: r.delivered_qty == 0) if rma: - rma.write({'state': 'received'}) + rma.write({"state": "received"}) def update_replaced_state(self): """ Invoked by: @@ -1152,15 +1148,18 @@ class Rma(models.Model): [stock.move]._action_cancel """ rma = self.filtered( - lambda r: (r.state == 'waiting_replacement' - and 0 >= r.remaining_qty_to_done == r.remaining_qty)) + lambda r: ( + r.state == "waiting_replacement" + and 0 >= r.remaining_qty_to_done == r.remaining_qty + ) + ) if rma: - rma.write({'state': 'replaced'}) + rma.write({"state": "replaced"}) def update_returned_state(self): """ Invoked by [stock.move]._action_done""" rma = self.filtered( - lambda r: (r.state == 'waiting_return' - and r.remaining_qty_to_done <= 0)) + lambda r: (r.state == "waiting_return" and r.remaining_qty_to_done <= 0) + ) if rma: - rma.write({'state': 'returned'}) + rma.write({"state": "returned"}) diff --git a/rma/models/rma_operation.py b/rma/models/rma_operation.py index 85271aeb..616dc55e 100644 --- a/rma/models/rma_operation.py +++ b/rma/models/rma_operation.py @@ -11,5 +11,5 @@ class RmaOperation(models.Model): name = fields.Char(required=True, translate=True) _sql_constraints = [ - ('name_uniq', 'unique (name)', "That operation name already exists !"), + ("name_uniq", "unique (name)", "That operation name already exists !"), ] diff --git a/rma/models/rma_team.py b/rma/models/rma_team.py index 9a771705..11faca12 100644 --- a/rma/models/rma_team.py +++ b/rma/models/rma_team.py @@ -6,19 +6,16 @@ from odoo import _, api, fields, models class RmaTeam(models.Model): _name = "rma.team" - _inherit = ['mail.alias.mixin', 'mail.thread'] + _inherit = ["mail.alias.mixin", "mail.thread"] _description = "RMA Team" _order = "sequence, name" sequence = fields.Integer() - name = fields.Char( - required=True, - translate=True, - ) + name = fields.Char(required=True, translate=True,) active = fields.Boolean( default=True, help="If the active field is set to false, it will allow you " - "to hide the RMA Team without removing it.", + "to hide the RMA Team without removing it.", ) company_id = fields.Many2one( comodel_name="res.company", @@ -28,13 +25,11 @@ class RmaTeam(models.Model): user_id = fields.Many2one( comodel_name="res.users", string="Team Leader", - domain=[('share', '=', False)], + domain=[("share", "=", False)], default=lambda self: self.env.user, ) member_ids = fields.One2many( - comodel_name='res.users', - inverse_name='rma_team_id', - string='Team Members', + comodel_name="res.users", inverse_name="rma_team_id", string="Team Members", ) @api.multi @@ -42,18 +37,20 @@ class RmaTeam(models.Model): self.ensure_one() if default is None: default = {} - if not default.get('name'): - default['name'] = _("%s (copy)") % self.name + if not default.get("name"): + default["name"] = _("%s (copy)") % self.name team = super().copy(default) for follower in self.message_follower_ids: - team.message_subscribe(partner_ids=follower.partner_id.ids, - subtype_ids=follower.subtype_ids.ids) + team.message_subscribe( + partner_ids=follower.partner_id.ids, + subtype_ids=follower.subtype_ids.ids, + ) return team def get_alias_model_name(self, vals): - return vals.get('alias_model', 'rma') + return vals.get("alias_model", "rma") def get_alias_values(self): values = super().get_alias_values() - values['alias_defaults'] = {'team_id': self.id} + values["alias_defaults"] = {"team_id": self.id} return values diff --git a/rma/models/stock_move.py b/rma/models/stock_move.py index 217d4c6c..3912b122 100644 --- a/rma/models/stock_move.py +++ b/rma/models/stock_move.py @@ -10,32 +10,25 @@ class StockMove(models.Model): # RMAs that were created from the delivery move rma_ids = fields.One2many( - comodel_name='rma', - inverse_name='move_id', - string='RMAs', - copy=False, + comodel_name="rma", inverse_name="move_id", string="RMAs", copy=False, ) # RMAs linked to the incoming movement from client rma_receiver_ids = fields.One2many( - comodel_name='rma', - inverse_name='reception_move_id', - string='RMA receivers', + comodel_name="rma", + inverse_name="reception_move_id", + string="RMA receivers", copy=False, ) # RMA that create the delivery movement to the customer - rma_id = fields.Many2one( - comodel_name='rma', - string='RMA return', - copy=False, - ) + rma_id = fields.Many2one(comodel_name="rma", string="RMA return", copy=False,) def unlink(self): # A stock user could have no RMA permissions, so the ids wouldn't # be accessible due to record rules. - rma_receiver = self.sudo().mapped('rma_receiver_ids') - rma = self.sudo().mapped('rma_id') + rma_receiver = self.sudo().mapped("rma_receiver_ids") + rma = self.sudo().mapped("rma_id") res = super().unlink() - rma_receiver.write({'state': 'draft'}) + rma_receiver.write({"state": "draft"}) rma.update_received_state() rma.update_replaced_state() return res @@ -44,10 +37,10 @@ class StockMove(models.Model): res = super()._action_cancel() # A stock user could have no RMA permissions, so the ids wouldn't # be accessible due to record rules. - cancelled_moves = self.filtered(lambda r: r.state == 'cancel').sudo() - cancelled_moves.mapped('rma_receiver_ids').write({'state': 'draft'}) - cancelled_moves.mapped('rma_id').update_received_state() - cancelled_moves.mapped('rma_id').update_replaced_state() + cancelled_moves = self.filtered(lambda r: r.state == "cancel").sudo() + cancelled_moves.mapped("rma_receiver_ids").write({"state": "draft"}) + cancelled_moves.mapped("rma_id").update_received_state() + cancelled_moves.mapped("rma_id").update_replaced_state() return res def _action_done(self): @@ -55,27 +48,30 @@ class StockMove(models.Model): quantity in the linked receiver RMA. It also set the appropriated linked RMA to 'received' or 'delivered'. """ - for move in self.filtered( - lambda r: r.state not in ('done', 'cancel')): + for move in self.filtered(lambda r: r.state not in ("done", "cancel")): rma_receiver = move.sudo().rma_receiver_ids - if (rma_receiver - and move.quantity_done != rma_receiver.product_uom_qty): + if rma_receiver and move.quantity_done != rma_receiver.product_uom_qty: raise ValidationError( - _("The quantity done for the product '%s' must " - "be equal to its initial demand because the " - "stock move is linked to an RMA (%s).") + _( + "The quantity done for the product '%s' must " + "be equal to its initial demand because the " + "stock move is linked to an RMA (%s)." + ) % (move.product_id.name, move.rma_receiver_ids.name) ) 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 # if the stock user has no RMA permissions. - to_be_received = move_done.sudo().mapped('rma_receiver_ids').filtered( - lambda r: r.state == 'confirmed') - to_be_received.write({'state': 'received'}) + to_be_received = ( + move_done.sudo() + .mapped("rma_receiver_ids") + .filtered(lambda r: r.state == "confirmed") + ) + to_be_received.write({"state": "received"}) # Set RMAs as delivered - move_done.mapped('rma_id').update_replaced_state() - move_done.mapped('rma_id').update_returned_state() + move_done.mapped("rma_id").update_replaced_state() + move_done.mapped("rma_id").update_returned_state() return res @api.model @@ -83,14 +79,14 @@ class StockMove(models.Model): """ The main use is that launched delivery RMAs doesn't merge 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): """ Intended to the backport of picking linked to RMAs propagates the RMA link id. """ 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 def _prepare_return_rma_vals(self, original_picking): @@ -98,30 +94,31 @@ class StockMove(models.Model): """ self.ensure_one() 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 else: - partner_invoice_id = partner.address_get( - ['invoice']).get('invoice', False), + partner_invoice_id = ( + partner.address_get(["invoice"]).get("invoice", False), + ) return { - 'user_id': self.env.user.id, - 'partner_id': partner.id, - 'partner_invoice_id': partner_invoice_id, - 'origin': original_picking.name, - 'picking_id': original_picking.id, - 'move_id': self.origin_returned_move_id.id, - 'product_id': self.origin_returned_move_id.product_id.id, - 'product_uom_qty': self.product_uom_qty, - 'product_uom': self.product_uom.id, - 'reception_move_id': self.id, - 'company_id': self.company_id.id, - 'location_id': self.location_dest_id.id, - 'state': 'confirmed', + "user_id": self.env.user.id, + "partner_id": partner.id, + "partner_invoice_id": partner_invoice_id, + "origin": original_picking.name, + "picking_id": original_picking.id, + "move_id": self.origin_returned_move_id.id, + "product_id": self.origin_returned_move_id.product_id.id, + "product_uom_qty": self.product_uom_qty, + "product_uom": self.product_uom.id, + "reception_move_id": self.id, + "company_id": self.company_id.id, + "location_id": self.location_dest_id.id, + "state": "confirmed", } class StockRule(models.Model): - _inherit = 'stock.rule' + _inherit = "stock.rule" def _get_custom_move_fields(self): - return super()._get_custom_move_fields() + ['rma_id'] + return super()._get_custom_move_fields() + ["rma_id"] diff --git a/rma/models/stock_picking.py b/rma/models/stock_picking.py index bddb151d..449e1fe3 100644 --- a/rma/models/stock_picking.py +++ b/rma/models/stock_picking.py @@ -5,39 +5,34 @@ from odoo import api, fields, models class StockPicking(models.Model): - _inherit = 'stock.picking' + _inherit = "stock.picking" - 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): 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 def copy(self, default=None): self.ensure_one() - if self.env.context.get('set_rma_picking_type'): - location_dest_id = default['location_dest_id'] - warehouse = self.env['stock.warehouse'].search( - [('rma_loc_id', 'parent_of', location_dest_id)], limit=1) + if self.env.context.get("set_rma_picking_type"): + location_dest_id = default["location_dest_id"] + warehouse = self.env["stock.warehouse"].search( + [("rma_loc_id", "parent_of", location_dest_id)], limit=1 + ) 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) def action_view_rma(self): self.ensure_one() - action = self.env.ref('rma.rma_action').read()[0] - rma = self.move_lines.mapped('rma_ids') + action = self.env.ref("rma.rma_action").read()[0] + rma = self.move_lines.mapped("rma_ids") if len(rma) == 1: action.update( - res_id=rma.id, - view_mode="form", - view_id=False, - views=False, + res_id=rma.id, view_mode="form", view_id=False, views=False, ) else: - action['domain'] = [('id', 'in', rma.ids)] + action["domain"] = [("id", "in", rma.ids)] return action diff --git a/rma/models/stock_warehouse.py b/rma/models/stock_warehouse.py index cf2281c1..86fc2cf0 100644 --- a/rma/models/stock_warehouse.py +++ b/rma/models/stock_warehouse.py @@ -1,118 +1,119 @@ # Copyright 2020 Tecnativa - Ernesto Tejeda # 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): - _inherit = 'stock.warehouse' + _inherit = "stock.warehouse" # This is a strategic field used to create an rma location # and rma operation types in existing warehouses when # installing this module. rma = fields.Boolean( - 'RMA', + "RMA", 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( - comodel_name='stock.picking.type', - string='RMA In Type', + comodel_name="stock.picking.type", string="RMA In Type", ) rma_out_type_id = fields.Many2one( - comodel_name='stock.picking.type', - string='RMA Out Type', - ) - rma_loc_id = fields.Many2one( - comodel_name='stock.location', - string='RMA Location', + comodel_name="stock.picking.type", string="RMA Out Type", ) + rma_loc_id = fields.Many2one(comodel_name="stock.location", string="RMA Location",) def _get_locations_values(self, vals): values = super()._get_locations_values(vals) - values.update({ - 'rma_loc_id': { - 'name': 'RMA', - 'active': True, - 'return_location': True, - 'usage': 'internal', - 'company_id': vals.get('company_id', self.company_id.id), - 'location_id': self.view_location_id.id, - }, - }) + values.update( + { + "rma_loc_id": { + "name": "RMA", + "active": True, + "return_location": True, + "usage": "internal", + "company_id": vals.get("company_id", self.company_id.id), + "location_id": self.view_location_id.id, + }, + } + ) return values def _get_sequence_values(self): values = super()._get_sequence_values() - values.update({ - 'rma_in_type_id': { - 'name': self.name + ' ' + _('Sequence RMA in'), - 'prefix': self.code + '/RMA/IN/', 'padding': 5, - 'company_id': self.company_id.id, - }, - 'rma_out_type_id': { - 'name': self.name + ' ' + _('Sequence RMA out'), - 'prefix': self.code + '/RMA/OUT/', 'padding': 5, - 'company_id': self.company_id.id, - }, - }) + values.update( + { + "rma_in_type_id": { + "name": self.name + " " + _("Sequence RMA in"), + "prefix": self.code + "/RMA/IN/", + "padding": 5, + "company_id": self.company_id.id, + }, + "rma_out_type_id": { + "name": self.name + " " + _("Sequence RMA out"), + "prefix": self.code + "/RMA/OUT/", + "padding": 5, + "company_id": self.company_id.id, + }, + } + ) return values def _update_name_and_code(self, new_name=False, new_code=False): for warehouse in self: sequence_data = warehouse._get_sequence_values() - warehouse.rma_in_type_id.sequence_id.write( - sequence_data['rma_in_type_id']) + warehouse.rma_in_type_id.sequence_id.write(sequence_data["rma_in_type_id"]) warehouse.rma_out_type_id.sequence_id.write( - sequence_data['rma_out_type_id']) + sequence_data["rma_out_type_id"] + ) def _get_picking_type_create_values(self, max_sequence): - data, next_sequence = super()._get_picking_type_create_values( - max_sequence) - data.update({ - 'rma_in_type_id': { - 'name': _('RMA Receipts'), - 'code': 'incoming', - 'use_create_lots': False, - 'use_existing_lots': True, - 'default_location_src_id': False, - 'default_location_dest_id': self.rma_loc_id.id, - 'sequence': max_sequence + 1, - }, - 'rma_out_type_id': { - 'name': _('RMA Delivery Orders'), - 'code': 'outgoing', - 'use_create_lots': False, - 'use_existing_lots': True, - 'default_location_src_id': self.rma_loc_id.id, - 'default_location_dest_id': False, - 'sequence': max_sequence + 2, - }, - }) + data, next_sequence = super()._get_picking_type_create_values(max_sequence) + data.update( + { + "rma_in_type_id": { + "name": _("RMA Receipts"), + "code": "incoming", + "use_create_lots": False, + "use_existing_lots": True, + "default_location_src_id": False, + "default_location_dest_id": self.rma_loc_id.id, + "sequence": max_sequence + 1, + }, + "rma_out_type_id": { + "name": _("RMA Delivery Orders"), + "code": "outgoing", + "use_create_lots": False, + "use_existing_lots": True, + "default_location_src_id": self.rma_loc_id.id, + "default_location_dest_id": False, + "sequence": max_sequence + 2, + }, + } + ) return data, max_sequence + 3 def _get_picking_type_update_values(self): data = super()._get_picking_type_update_values() - data.update({ - 'rma_in_type_id': { - 'default_location_dest_id': self.rma_loc_id.id, - }, - 'rma_out_type_id': { - 'default_location_src_id': self.rma_loc_id.id, - }, - }) + data.update( + { + "rma_in_type_id": {"default_location_dest_id": self.rma_loc_id.id,}, + "rma_out_type_id": {"default_location_src_id": self.rma_loc_id.id,}, + } + ) return data def _create_or_update_sequences_and_picking_types(self): data = super()._create_or_update_sequences_and_picking_types() - stock_picking_type = self.env['stock.picking.type'] - if 'out_type_id' in data: - rma_out_type = stock_picking_type.browse(data['rma_out_type_id']) - rma_out_type.write({ - 'return_picking_type_id': data.get('rma_in_type_id', False) - }) - if 'rma_in_type_id' in data: - rma_in_type = stock_picking_type.browse(data['rma_in_type_id']) - rma_in_type.write({ - 'return_picking_type_id': data.get('rma_out_type_id', False) - }) + stock_picking_type = self.env["stock.picking.type"] + if "out_type_id" in data: + rma_out_type = stock_picking_type.browse(data["rma_out_type_id"]) + rma_out_type.write( + {"return_picking_type_id": data.get("rma_in_type_id", False)} + ) + if "rma_in_type_id" in data: + rma_in_type = stock_picking_type.browse(data["rma_in_type_id"]) + rma_in_type.write( + {"return_picking_type_id": data.get("rma_out_type_id", False)} + ) return data diff --git a/rma/security/rma_security.xml b/rma/security/rma_security.xml index 6604005f..81385aa1 100644 --- a/rma/security/rma_security.xml +++ b/rma/security/rma_security.xml @@ -1,67 +1,86 @@ - + RMA - Manage Return Merchandise Authorizations (RMAs). + Manage Return Merchandise Authorizations (RMAs). User: Own Documents Only - - - the user will have access to his own data in the RMA application. + + + the user will have access to his own data in the RMA application. User: All Documents - - - the user will have access to all records of everyone in the RMA application. + + + the user will have access to all records of everyone in the RMA application. Manager - the user will have an access to the RMA configuration as well as statistic reports. - - - + the user will have an access to the RMA configuration as well as statistic reports. + + + Personal RMAs - - ['|',('user_id','=',user.id),('user_id','=',False)] - + + ['|',('user_id','=',user.id),('user_id','=',False)] + All RMAs - + [(1,'=',1)] - + RMA portal users - - [('message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id])] - + + [('message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id])] + RMA multi-company - - - ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] + + + ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] RMA team multi-company - - - ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] + + + ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] - + diff --git a/rma/tests/test_rma.py b/rma/tests/test_rma.py index b6310ece..db80078b 100644 --- a/rma/tests/test_rma.py +++ b/rma/tests/test_rma.py @@ -1,60 +1,67 @@ # Copyright 2020 Tecnativa - Ernesto Tejeda # 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.tests import Form, SavepointCase class TestRma(SavepointCase): - @classmethod def setUpClass(cls): super(TestRma, cls).setUpClass() - cls.res_partner = cls.env['res.partner'] - cls.product_product = cls.env['product.product'] + cls.res_partner = cls.env["res.partner"] + cls.product_product = cls.env["product.product"] cls.company = cls.env.user.company_id - cls.warehouse_company = cls.env['stock.warehouse'].search( - [('company_id', '=', cls.company.id)], limit=1) + cls.warehouse_company = cls.env["stock.warehouse"].search( + [("company_id", "=", cls.company.id)], limit=1 + ) cls.rma_loc = cls.warehouse_company.rma_loc_id - account_pay = cls.env['account.account'].create({ - 'code': 'X1111', - 'name': 'Creditors - (test)', - 'user_type_id': cls.env.ref( - 'account.data_account_type_payable').id, - 'reconcile': True, - }) - cls.journal = cls.env['account.journal'].create({ - 'name': 'sale_0', - 'code': 'SALE0', - 'type': 'sale', - 'default_debit_account_id': account_pay.id, - }) - cls.product = cls.product_product.create({ - 'name': 'Product test 1', - 'type': 'product', - }) - account_type = cls.env['account.account.type'].create({ - 'name': 'RCV type', - 'type': 'receivable', - }) - cls.account_receiv = cls.env['account.account'].create({ - 'name': 'Receivable', - 'code': 'RCV00', - 'user_type_id': account_type.id, - 'reconcile': True, - }) - cls.partner = cls.res_partner.create({ - 'name': 'Partner test', - 'property_account_receivable_id': cls.account_receiv.id, - }) - cls.partner_invoice = cls.res_partner.create({ - 'name': 'Partner invoice test', - 'parent_id': cls.partner.id, - 'type': 'invoice', - }) + account_pay = cls.env["account.account"].create( + { + "code": "X1111", + "name": "Creditors - (test)", + "user_type_id": cls.env.ref("account.data_account_type_payable").id, + "reconcile": True, + } + ) + cls.journal = cls.env["account.journal"].create( + { + "name": "sale_0", + "code": "SALE0", + "type": "sale", + "default_debit_account_id": account_pay.id, + } + ) + cls.product = cls.product_product.create( + {"name": "Product test 1", "type": "product",} + ) + account_type = cls.env["account.account.type"].create( + {"name": "RCV type", "type": "receivable",} + ) + cls.account_receiv = cls.env["account.account"].create( + { + "name": "Receivable", + "code": "RCV00", + "user_type_id": account_type.id, + "reconcile": True, + } + ) + cls.partner = cls.res_partner.create( + { + "name": "Partner test", + "property_account_receivable_id": cls.account_receiv.id, + } + ) + 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): - rma_form = Form(self.env['rma']) + rma_form = Form(self.env["rma"]) if partner: rma_form.partner_id = partner if product: @@ -65,8 +72,9 @@ class TestRma(SavepointCase): rma_form.location_id = location return rma_form.save() - def _create_confirm_receive(self, partner=None, product=None, qty=None, - location=None): + def _create_confirm_receive( + self, partner=None, product=None, qty=None, location=None + ): rma = self._create_rma(partner, product, qty, location) rma.action_confirm() rma.reception_move_id.quantity_done = rma.product_uom_qty @@ -76,35 +84,36 @@ class TestRma(SavepointCase): def _test_readonly_fields(self, rma): with Form(rma) as rma_form: with self.assertRaises(AssertionError): - rma_form.partner_id = self.env['res.partner'] + rma_form.partner_id = self.env["res.partner"] 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): - rma_form.picking_id = self.env['stock.picking'] + rma_form.picking_id = self.env["stock.picking"] with self.assertRaises(AssertionError): - rma_form.move_id = self.env['stock.move'] + rma_form.move_id = self.env["stock.move"] with self.assertRaises(AssertionError): - rma_form.product_id = self.env['product.product'] + rma_form.product_id = self.env["product.product"] with self.assertRaises(AssertionError): rma_form.product_uom_qty = 0 with self.assertRaises(AssertionError): - rma_form.product_uom = self.env['uom.uom'] + rma_form.product_uom = self.env["uom.uom"] with self.assertRaises(AssertionError): - rma_form.location_id = self.env['stock.location'] + rma_form.location_id = self.env["stock.location"] def _create_delivery(self): - picking_type = self.env['stock.picking.type'].search( + picking_type = self.env["stock.picking.type"].search( [ - ('code', '=', 'outgoing'), - '|', - ('warehouse_id.company_id', '=', self.company.id), - ('warehouse_id', '=', False) + ("code", "=", "outgoing"), + "|", + ("warehouse_id.company_id", "=", self.company.id), + ("warehouse_id", "=", False), ], limit=1, ) picking_form = Form( - recordp=self.env['stock.picking'].with_context( - default_picking_type_id=picking_type.id), + recordp=self.env["stock.picking"].with_context( + default_picking_type_id=picking_type.id + ), view="stock.view_picking_form", ) picking_form.company_id = self.company @@ -114,7 +123,8 @@ class TestRma(SavepointCase): move.product_uom_qty = 10 with picking_form.move_ids_without_package.new() as move: move.product_id = self.product_product.create( - {'name': 'Product 2 test', 'type': 'product'}) + {"name": "Product 2 test", "type": "product"} + ) move.product_uom_qty = 20 picking = picking_form.save() picking.action_confirm() @@ -124,34 +134,35 @@ class TestRma(SavepointCase): return picking def test_onchange(self): - rma_form = Form(self.env['rma']) + rma_form = Form(self.env["rma"]) # If partner changes, the invoice address is set rma_form.partner_id = self.partner self.assertEqual(rma_form.partner_invoice_id, self.partner_invoice) # If origin move changes, the product is set - uom_ten = self.env['uom.uom'].create({ - 'name': "Ten", - 'category_id': self.env.ref('uom.product_uom_unit').id, - 'factor_inv': 10, - 'uom_type': 'bigger', - }) - product_2 = self.product_product.create({ - 'name': 'Product test 2', - 'type': 'product', - 'uom_id': uom_ten.id, - }) - outgoing_picking_type = self.env['stock.picking.type'].search( + uom_ten = self.env["uom.uom"].create( + { + "name": "Ten", + "category_id": self.env.ref("uom.product_uom_unit").id, + "factor_inv": 10, + "uom_type": "bigger", + } + ) + product_2 = self.product_product.create( + {"name": "Product test 2", "type": "product", "uom_id": uom_ten.id,} + ) + outgoing_picking_type = self.env["stock.picking.type"].search( [ - ('code', '=', 'outgoing'), - '|', - ('warehouse_id.company_id', '=', self.company.id), - ('warehouse_id', '=', False) + ("code", "=", "outgoing"), + "|", + ("warehouse_id.company_id", "=", self.company.id), + ("warehouse_id", "=", False), ], limit=1, ) picking_form = Form( - recordp=self.env['stock.picking'].with_context( - default_picking_type_id=outgoing_picking_type.id), + recordp=self.env["stock.picking"].with_context( + default_picking_type_id=outgoing_picking_type.id + ), view="stock.view_picking_form", ) 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, uom_ten) # 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 self.assertEqual(rma_form.product_id, self.product) 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) rma = rma_form.save() # 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( - 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): rma = self._create_rma() @@ -187,86 +199,84 @@ class TestRma(SavepointCase): rma.action_confirm() self.assertEqual( 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: rma_form.partner_id = self.partner with self.assertRaises(ValidationError) as e: rma.action_confirm() - self.assertEqual( - e.exception.name, "Required field(s):\nProduct\nLocation") + self.assertEqual(e.exception.name, "Required field(s):\nProduct\nLocation") with Form(rma) as rma_form: rma_form.product_id = self.product rma_form.location_id = self.rma_loc rma.action_confirm() - self.assertEqual(rma.state, 'confirmed') + self.assertEqual(rma.state, "confirmed") def test_confirm_and_receive(self): rma = self._create_rma(self.partner, self.product, 10, self.rma_loc) 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_uom_qty, 10) 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) rma.reception_move_id.quantity_done = 9 with self.assertRaises(ValidationError): rma.reception_move_id.picking_id.action_done() rma.reception_move_id.quantity_done = 10 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.state, 'received') + self.assertEqual(rma.state, "received") self._test_readonly_fields(rma) def test_cancel(self): # cancel a draft RMA rma = self._create_rma(self.partner, self.product) rma.action_cancel() - self.assertEqual(rma.state, 'cancelled') + self.assertEqual(rma.state, "cancelled") self._test_readonly_fields(rma) # cancel a confirmed RMA rma = self._create_rma(self.partner, self.product, 10, self.rma_loc) rma.action_confirm() rma.action_cancel() - self.assertEqual(rma.state, 'cancelled') + self.assertEqual(rma.state, "cancelled") # A RMA is only cancelled from draft and confirmed states - rma = self._create_confirm_receive(self.partner, self.product, 10, - self.rma_loc) + rma = self._create_confirm_receive(self.partner, self.product, 10, self.rma_loc) with self.assertRaises(UserError): rma.action_cancel() def test_lock_unlock(self): # A RMA is only locked from 'received' state rma_1 = self._create_rma(self.partner, self.product, 10, self.rma_loc) - rma_2 = self._create_confirm_receive(self.partner, self.product, 10, - self.rma_loc) - self.assertEqual(rma_1.state, 'draft') - self.assertEqual(rma_2.state, 'received') + rma_2 = self._create_confirm_receive( + self.partner, self.product, 10, self.rma_loc + ) + self.assertEqual(rma_1.state, "draft") + self.assertEqual(rma_2.state, "received") (rma_1 | rma_2).action_lock() - self.assertEqual(rma_1.state, 'draft') - self.assertEqual(rma_2.state, 'locked') + self.assertEqual(rma_1.state, "draft") + self.assertEqual(rma_2.state, "locked") # A RMA is only unlocked from 'lock' state and it will be set # to 'received' state (rma_1 | rma_2).action_unlock() - self.assertEqual(rma_1.state, 'draft') - self.assertEqual(rma_2.state, 'received') + self.assertEqual(rma_1.state, "draft") + self.assertEqual(rma_2.state, "received") def test_action_refund(self): - rma = self._create_confirm_receive(self.partner, self.product, 10, - self.rma_loc) - self.assertEqual(rma.state, 'received') + rma = self._create_confirm_receive(self.partner, self.product, 10, self.rma_loc) + self.assertEqual(rma.state, "received") self.assertTrue(rma.can_be_refunded) self.assertTrue(rma.can_be_returned) self.assertTrue(rma.can_be_replaced) rma.action_refund() - self.assertEqual(rma.refund_id.type, 'out_refund') - self.assertEqual(rma.refund_id.state, 'draft') + self.assertEqual(rma.refund_id.type, "out_refund") + self.assertEqual(rma.refund_id.state, "draft") 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.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_returned) self.assertFalse(rma.can_be_replaced) @@ -282,59 +292,59 @@ class TestRma(SavepointCase): def test_mass_refund(self): # Create, confirm and receive rma_1 - rma_1 = self._create_confirm_receive(self.partner, self.product, 10, - self.rma_loc) + rma_1 = self._create_confirm_receive( + self.partner, self.product, 10, self.rma_loc + ) # create, confirm and receive 3 more RMAs # rma_2: Same partner and same product as rma_1 - rma_2 = self._create_confirm_receive(self.partner, self.product, 15, - self.rma_loc) + rma_2 = self._create_confirm_receive( + self.partner, self.product, 15, self.rma_loc + ) # rma_3: Same partner and different product than rma_1 product = self.product_product.create( - {'name': 'Product 2 test', 'type': 'product'}) - rma_3 = self._create_confirm_receive(self.partner, product, 20, - self.rma_loc) + {"name": "Product 2 test", "type": "product"} + ) + rma_3 = self._create_confirm_receive(self.partner, product, 20, self.rma_loc) # rma_4: Different partner and same product as rma_1 - partner = self.res_partner.create({ - 'name': 'Partner 2 test', - '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) + partner = self.res_partner.create( + { + "name": "Partner 2 test", + "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) # all rmas are ready to refund - all_rmas = (rma_1 | rma_2 | rma_3 | rma_4) - self.assertEqual(all_rmas.mapped('state'), ['received']*4) - self.assertEqual(all_rmas.mapped('can_be_refunded'), [True]*4) + all_rmas = rma_1 | rma_2 | rma_3 | rma_4 + self.assertEqual(all_rmas.mapped("state"), ["received"] * 4) + self.assertEqual(all_rmas.mapped("can_be_refunded"), [True] * 4) # 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.update(active_ids=all_rmas.ids, active_model='rma') + ctx.update(active_ids=all_rmas.ids, active_model="rma") action.with_context(ctx).run() # 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 - 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 self.assertEqual(len(refund_1), 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 self.assertNotEqual(refund_1.partner_id, refund_2.partner_id) self.assertEqual( - refund_1.partner_id, - (rma_1 | rma_2 | rma_3).mapped('partner_invoice_id'), + refund_1.partner_id, (rma_1 | rma_2 | rma_3).mapped("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 # line of refund_1 self.assertEqual(len(refund_1.invoice_line_ids), 3) self.assertEqual( - refund_1.invoice_line_ids.mapped('rma_id'), - (rma_1 | rma_2 | rma_3), + refund_1.invoice_line_ids.mapped("rma_id"), (rma_1 | rma_2 | rma_3), ) self.assertEqual( - (rma_1 | rma_2 | rma_3).mapped('refund_line_id'), - refund_1.invoice_line_ids, + (rma_1 | rma_2 | rma_3).mapped("refund_line_id"), refund_1.invoice_line_ids, ) # rma_4 is linked with the unique line of refund_2 self.assertEqual(len(refund_2.invoice_line_ids), 1) @@ -355,15 +365,14 @@ class TestRma(SavepointCase): def test_replace(self): # Create, confirm and receive an RMA - rma = self._create_confirm_receive(self.partner, self.product, 10, - self.rma_loc) + rma = self._create_confirm_receive(self.partner, self.product, 10, self.rma_loc) # Replace with another product with quantity 2. product_2 = self.product_product.create( - {'name': 'Product 2 test', 'type': 'product'}) + {"name": "Product 2 test", "type": "product"} + ) delivery_form = Form( - self.env['rma.delivery.wizard'].with_context( - active_ids=rma.ids, - rma_delivery_type='replace', + self.env["rma.delivery.wizard"].with_context( + active_ids=rma.ids, rma_delivery_type="replace", ) ) 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(rma.delivery_move_ids.product_id, product_2) self.assertEqual(rma.delivery_move_ids.product_uom_qty, 2) - self.assertTrue(rma.delivery_move_ids.picking_id.state, 'waiting') - self.assertEqual(rma.state, 'waiting_replacement') + self.assertTrue(rma.delivery_move_ids.picking_id.state, "waiting") + self.assertEqual(rma.state, "waiting_replacement") self.assertFalse(rma.can_be_refunded) self.assertFalse(rma.can_be_returned) self.assertTrue(rma.can_be_replaced) @@ -386,11 +395,11 @@ class TestRma(SavepointCase): picking = first_move.picking_id # Replace again with another product with the remaining quantity product_3 = self.product_product.create( - {'name': 'Product 3 test', 'type': 'product'}) + {"name": "Product 3 test", "type": "product"} + ) delivery_form = Form( - self.env['rma.delivery.wizard'].with_context( - active_ids=rma.ids, - rma_delivery_type='replace', + self.env["rma.delivery.wizard"].with_context( + active_ids=rma.ids, rma_delivery_type="replace", ) ) delivery_form.product_id = product_3 @@ -398,12 +407,12 @@ class TestRma(SavepointCase): delivery_wizard.action_deliver() second_move = rma.delivery_move_ids - first_move 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_uom_qty, 2) self.assertEqual(second_move.product_id, product_3) 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.remaining_qty, 0) self.assertEqual(rma.delivered_qty_done, 0) @@ -413,13 +422,13 @@ class TestRma(SavepointCase): first_move.quantity_done = 2 second_move.quantity_done = 8 picking.button_validate() - self.assertEqual(picking.state, 'done') + self.assertEqual(picking.state, "done") self.assertEqual(rma.delivered_qty, 10) self.assertEqual(rma.remaining_qty, 0) self.assertEqual(rma.delivered_qty_done, 10) self.assertEqual(rma.remaining_qty_to_done, 0) # 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_returned) # Despite being in 'replaced' state, @@ -429,13 +438,11 @@ class TestRma(SavepointCase): def test_return_to_customer(self): # Create, confirm and receive an RMA - rma = self._create_confirm_receive(self.partner, self.product, 10, - self.rma_loc) + rma = self._create_confirm_receive(self.partner, self.product, 10, self.rma_loc) # Return the same product with quantity 2 to the customer. delivery_form = Form( - self.env['rma.delivery.wizard'].with_context( - active_ids=rma.ids, - rma_delivery_type='return', + self.env["rma.delivery.wizard"].with_context( + active_ids=rma.ids, rma_delivery_type="return", ) ) delivery_form.product_uom_qty = 2 @@ -445,8 +452,8 @@ class TestRma(SavepointCase): self.assertEqual(len(picking.move_lines), 1) self.assertEqual(rma.delivery_move_ids.product_id, self.product) self.assertEqual(rma.delivery_move_ids.product_uom_qty, 2) - self.assertTrue(picking.state, 'waiting') - self.assertEqual(rma.state, 'waiting_return') + self.assertTrue(picking.state, "waiting") + self.assertEqual(rma.state, "waiting_return") self.assertFalse(rma.can_be_refunded) self.assertFalse(rma.can_be_replaced) self.assertTrue(rma.can_be_returned) @@ -459,16 +466,15 @@ class TestRma(SavepointCase): # Validate the picking first_move.quantity_done = 2 picking.button_validate() - self.assertEqual(picking.state, 'done') + self.assertEqual(picking.state, "done") self.assertEqual(rma.delivered_qty, 2) self.assertEqual(rma.remaining_qty, 8) self.assertEqual(rma.delivered_qty_done, 2) self.assertEqual(rma.remaining_qty_to_done, 8) # Return the remaining quantity to the customer delivery_form = Form( - self.env['rma.delivery.wizard'].with_context( - active_ids=rma.ids, - rma_delivery_type='return', + self.env["rma.delivery.wizard"].with_context( + active_ids=rma.ids, rma_delivery_type="return", ) ) delivery_wizard = delivery_form.save() @@ -479,18 +485,18 @@ class TestRma(SavepointCase): self.assertEqual(rma.remaining_qty, 0) self.assertEqual(rma.delivered_qty_done, 2) 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_to_done is less than or equal to 0 picking_2 = second_move.picking_id 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.remaining_qty, 0) self.assertEqual(rma.delivered_qty_done, 10) self.assertEqual(rma.remaining_qty_to_done, 0) # 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_returned) self.assertFalse(rma.can_be_replaced) @@ -498,53 +504,54 @@ class TestRma(SavepointCase): def test_mass_return_to_customer(self): # Create, confirm and receive rma_1 - rma_1 = self._create_confirm_receive(self.partner, self.product, 10, - self.rma_loc) + rma_1 = self._create_confirm_receive( + self.partner, self.product, 10, self.rma_loc + ) # create, confirm and receive 3 more RMAs # rma_2: Same partner and same product as rma_1 - rma_2 = self._create_confirm_receive(self.partner, self.product, 15, - self.rma_loc) + rma_2 = self._create_confirm_receive( + self.partner, self.product, 15, self.rma_loc + ) # rma_3: Same partner and different product than rma_1 product = self.product_product.create( - {'name': 'Product 2 test', 'type': 'product'}) - rma_3 = self._create_confirm_receive(self.partner, product, 20, - self.rma_loc) + {"name": "Product 2 test", "type": "product"} + ) + rma_3 = self._create_confirm_receive(self.partner, product, 20, self.rma_loc) # rma_4: Different partner and same product as rma_1 - partner = self.res_partner.create({'name': 'Partner 2 test'}) - rma_4 = self._create_confirm_receive(partner, product, 25, - self.rma_loc) + partner = self.res_partner.create({"name": "Partner 2 test"}) + rma_4 = self._create_confirm_receive(partner, product, 25, self.rma_loc) # all rmas are ready to be returned to the customer - all_rmas = (rma_1 | rma_2 | rma_3 | rma_4) - self.assertEqual(all_rmas.mapped('state'), ['received'] * 4) - self.assertEqual(all_rmas.mapped('can_be_returned'), [True] * 4) + all_rmas = rma_1 | rma_2 | rma_3 | rma_4 + self.assertEqual(all_rmas.mapped("state"), ["received"] * 4) + self.assertEqual(all_rmas.mapped("can_be_returned"), [True] * 4) # Mass return of those four RMAs - delivery_wizard = self.env['rma.delivery.wizard'].with_context( - active_ids=all_rmas.ids, rma_delivery_type='return').create({}) + delivery_wizard = ( + self.env["rma.delivery.wizard"] + .with_context(active_ids=all_rmas.ids, rma_delivery_type="return") + .create({}) + ) delivery_wizard.action_deliver() # 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 self.assertEqual(len(pick_1), 1) self.assertEqual(len(pick_2), 1) 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 self.assertNotEqual(pick_1.partner_id, pick_2.partner_id) self.assertEqual( - pick_1.partner_id, - (rma_1 | rma_2 | rma_3).mapped('partner_id'), + pick_1.partner_id, (rma_1 | rma_2 | rma_3).mapped("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 # line of picking_1 self.assertEqual(len(pick_1.move_lines), 3) self.assertEqual( - pick_1.move_lines.mapped('rma_id'), - (rma_1 | rma_2 | rma_3), + pick_1.move_lines.mapped("rma_id"), (rma_1 | rma_2 | rma_3), ) self.assertEqual( - (rma_1 | rma_2 | rma_3).mapped('delivery_move_ids'), - pick_1.move_lines, + (rma_1 | rma_2 | rma_3).mapped("delivery_move_ids"), pick_1.move_lines, ) # rma_4 is linked with the unique move of pick_2 self.assertEqual(len(pick_2.move_lines), 1) @@ -553,57 +560,56 @@ class TestRma(SavepointCase): # Assert product and quantities are propagated correctly for rma in all_rmas: self.assertEqual(rma.product_id, rma.delivery_move_ids.product_id) - self.assertEqual(rma.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_qty, rma.delivery_move_ids.product_uom_qty) + self.assertEqual(rma.product_uom, rma.delivery_move_ids.product_uom) rma.delivery_move_ids.quantity_done = rma.product_uom_qty pick_1.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): # Create a return from a delivery picking origin_delivery = self._create_delivery() - return_wizard = self.env['stock.return.picking'].with_context( - active_id=origin_delivery.id, - active_ids=origin_delivery.ids, - ).create({'create_rma': True}) + return_wizard = ( + self.env["stock.return.picking"] + .with_context(active_id=origin_delivery.id, active_ids=origin_delivery.ids,) + .create({"create_rma": True}) + ) picking_action = return_wizard.create_returns() # Each origin move is linked to a different RMA origin_moves = origin_delivery.move_lines self.assertTrue(origin_moves[0].rma_ids) self.assertTrue(origin_moves[1].rma_ids) - rmas = origin_moves.mapped('rma_ids') - self.assertEqual(rmas.mapped('state'), ['confirmed']*2) + rmas = origin_moves.mapped("rma_ids") + self.assertEqual(rmas.mapped("state"), ["confirmed"] * 2) # Each reception move is linked one of the generated RMAs - reception = self.env['stock.picking'].browse(picking_action['res_id']) + reception = self.env["stock.picking"].browse(picking_action["res_id"]) reception_moves = reception.move_lines self.assertTrue(reception_moves[0].rma_receiver_ids) self.assertTrue(reception_moves[1].rma_receiver_ids) - self.assertEqual(reception_moves.mapped('rma_receiver_ids'), rmas) + self.assertEqual(reception_moves.mapped("rma_receiver_ids"), rmas) # Validate the reception picking to set rmas to 'received' state reception_moves[0].quantity_done = reception_moves[0].product_uom_qty reception_moves[1].quantity_done = reception_moves[1].product_uom_qty reception.button_validate() - self.assertEqual(rmas.mapped('state'), ['received'] * 2) + self.assertEqual(rmas.mapped("state"), ["received"] * 2) def test_split(self): 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.picking_id = origin_delivery 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.action_confirm() rma.reception_move_id.quantity_done = 10 rma.reception_move_id.picking_id.action_done() # Return quantity 4 of the same product to the customer delivery_form = Form( - self.env['rma.delivery.wizard'].with_context( - active_ids=rma.ids, - rma_delivery_type='return', + self.env["rma.delivery.wizard"].with_context( + active_ids=rma.ids, rma_delivery_type="return", ) ) delivery_form.product_uom_qty = 4 @@ -611,23 +617,24 @@ class TestRma(SavepointCase): delivery_wizard.action_deliver() rma.delivery_move_ids.quantity_done = 4 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 self.assertTrue(rma.can_be_split) - split_wizard = self.env['rma.split.wizard'].with_context( - active_id=rma.id, - active_ids=rma.ids, - ).create({}) + split_wizard = ( + self.env["rma.split.wizard"] + .with_context(active_id=rma.id, active_ids=rma.ids,) + .create({}) + ) action = split_wizard.action_split() # Check rma is set to 'returned' after split. Check new_rma values - self.assertEqual(rma.state, 'returned') - new_rma = self.env['rma'].browse(action['res_id']) + self.assertEqual(rma.state, "returned") + new_rma = self.env["rma"].browse(action["res_id"]) self.assertEqual(new_rma.origin_split_rma_id, rma) self.assertEqual(new_rma.delivered_qty, 0) self.assertEqual(new_rma.remaining_qty, 6) self.assertEqual(new_rma.delivered_qty_done, 0) 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_returned) self.assertTrue(new_rma.can_be_replaced) @@ -638,21 +645,19 @@ class TestRma(SavepointCase): self.assertEqual(new_rma.reception_move_id.quantity_done, 10) def test_rma_to_receive_on_delete_invoice(self): - rma = self._create_confirm_receive(self.partner, self.product, 10, - self.rma_loc) + rma = self._create_confirm_receive(self.partner, self.product, 10, self.rma_loc) rma.action_refund() - self.assertEqual(rma.state, 'refunded') + self.assertEqual(rma.state, "refunded") rma.refund_id.unlink() 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_returned) self.assertTrue(rma.can_be_replaced) def test_rma_picking_type_default_values(self): - warehouse = self.env['stock.warehouse'].create({ - 'name': 'Stock - RMA Test', - 'code': 'SRT', - }) + warehouse = self.env["stock.warehouse"].create( + {"name": "Stock - RMA Test", "code": "SRT",} + ) self.assertFalse(warehouse.rma_in_type_id.use_create_lots) self.assertTrue(warehouse.rma_in_type_id.use_existing_lots) diff --git a/rma/views/menus.xml b/rma/views/menus.xml index 568c906d..e5119894 100644 --- a/rma/views/menus.xml +++ b/rma/views/menus.xml @@ -1,24 +1,19 @@ - + - - + + + sequence="20" + /> + sequence="30" + /> diff --git a/rma/views/report_rma.xml b/rma/views/report_rma.xml index 2225bab3..2442589d 100644 --- a/rma/views/report_rma.xml +++ b/rma/views/report_rma.xml @@ -5,51 +5,76 @@ -
-

:

+
+

:

- + Invoicing address: -
+

RMA # - +

Origin: -

+

Date: -

+

Deadline: -

+

Responsible: -

+

State:

- + + + + + + - + + + + + + - + + + + + +

@@ -57,26 +82,26 @@
Origin delivery: -

+

Move: -

+

Product: -

+

Quantity:

- - + +

Delivered qty: -

+

diff --git a/rma/views/res_partner_views.xml b/rma/views/res_partner_views.xml index bdf58616..45d9474c 100644 --- a/rma/views/res_partner_views.xml +++ b/rma/views/res_partner_views.xml @@ -1,22 +1,22 @@ - + res.partner.form res.partner - - + +
-
diff --git a/rma/views/rma_portal_templates.xml b/rma/views/rma_portal_templates.xml index 33b6e98e..30b404fa 100644 --- a/rma/views/rma_portal_templates.xml +++ b/rma/views/rma_portal_templates.xml @@ -1,96 +1,145 @@ - + -