mirror of
https://github.com/OCA/rma.git
synced 2025-02-16 17:11:47 +02:00
[ADD] rma: new module
[UPD] Update rma.pot Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translation: rma-12.0/rma-12.0-rma Translate-URL: https://translation.odoo-community.org/projects/rma-12-0/rma-12-0-rma/
This commit is contained in:
committed by
Nikolaus Weingartmair
parent
a7bddf7db9
commit
42c8f0630a
12
rma/models/__init__.py
Normal file
12
rma/models/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
from . import account_invoice
|
||||
from . import rma
|
||||
from . import rma_operation
|
||||
from . import rma_team
|
||||
from . import res_company
|
||||
from . import res_partner
|
||||
from . import res_users
|
||||
from . import stock_move
|
||||
from . import stock_picking
|
||||
from . import stock_warehouse
|
||||
35
rma/models/account_invoice.py
Normal file
35
rma/models/account_invoice.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# Copyright 2020 Tecnativa - Ernesto Tejeda
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import _, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tools import float_compare
|
||||
|
||||
|
||||
class AccountInvoice(models.Model):
|
||||
_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.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."))
|
||||
res = super().action_invoice_open()
|
||||
self.mapped('invoice_line_ids.rma_id').write({'state': 'refunded'})
|
||||
return res
|
||||
|
||||
|
||||
class AccountInvoiceLine(models.Model):
|
||||
_inherit = 'account.invoice.line'
|
||||
|
||||
rma_id = fields.Many2one(
|
||||
comodel_name='rma',
|
||||
string='RMA',
|
||||
)
|
||||
23
rma/models/res_company.py
Normal file
23
rma/models/res_company.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Copyright 2020 Tecnativa - Ernesto Tejeda
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import api, models, _
|
||||
|
||||
|
||||
class Company(models.Model):
|
||||
_inherit = "res.company"
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
company = super(Company, self).create(vals)
|
||||
company.create_rma_index()
|
||||
return company
|
||||
|
||||
def create_rma_index(self):
|
||||
self.env['ir.sequence'].sudo().create({
|
||||
'name': _('RMA Code'),
|
||||
'prefix': 'RMA',
|
||||
'code': 'rma',
|
||||
'padding': 4,
|
||||
'company_id': self.id,
|
||||
})
|
||||
41
rma/models/res_partner.py
Normal file
41
rma/models/res_partner.py
Normal file
@@ -0,0 +1,41 @@
|
||||
# Copyright 2020 Tecnativa - Ernesto Tejeda
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ResPartner(models.Model):
|
||||
_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',
|
||||
)
|
||||
|
||||
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])
|
||||
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]
|
||||
rma = self.rma_ids
|
||||
if len(rma) == 1:
|
||||
action.update(
|
||||
res_id=rma.id,
|
||||
view_mode="form",
|
||||
view_id=False,
|
||||
views=False,
|
||||
)
|
||||
else:
|
||||
action['domain'] = [('partner_id', 'in', self.ids)]
|
||||
return action
|
||||
14
rma/models/res_users.py
Normal file
14
rma/models/res_users.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# Copyright 2020 Tecnativa - Ernesto Tejeda
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ResUsers(models.Model):
|
||||
_inherit = 'res.users'
|
||||
|
||||
rma_team_id = fields.Many2one(
|
||||
comodel_name='rma.team',
|
||||
string="RMA Team",
|
||||
help='RMA Team the user is member of.',
|
||||
)
|
||||
1144
rma/models/rma.py
Normal file
1144
rma/models/rma.py
Normal file
File diff suppressed because it is too large
Load Diff
15
rma/models/rma_operation.py
Normal file
15
rma/models/rma_operation.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# Copyright 2020 Tecnativa - Ernesto Tejeda
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class RmaOperation(models.Model):
|
||||
_name = "rma.operation"
|
||||
_description = "RMA requested operation"
|
||||
|
||||
name = fields.Char(required=True, translate=True)
|
||||
|
||||
_sql_constraints = [
|
||||
('name_uniq', 'unique (name)', "That operation name already exists !"),
|
||||
]
|
||||
56
rma/models/rma_team.py
Normal file
56
rma/models/rma_team.py
Normal file
@@ -0,0 +1,56 @@
|
||||
# Copyright 2020 Tecnativa - Ernesto Tejeda
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import _, fields, models
|
||||
|
||||
|
||||
class RmaTeam(models.Model):
|
||||
_name = "rma.team"
|
||||
_inherit = ['mail.alias.mixin', 'mail.thread']
|
||||
_description = "RMA Team"
|
||||
_order = "name"
|
||||
|
||||
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.",
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
comodel_name="res.company",
|
||||
string="Company",
|
||||
default=lambda self: self.env.user.company_id,
|
||||
)
|
||||
user_id = fields.Many2one(
|
||||
comodel_name="res.users",
|
||||
string="Team Leader",
|
||||
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',
|
||||
)
|
||||
|
||||
def copy(self, default=None):
|
||||
if default is None:
|
||||
default = {}
|
||||
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)
|
||||
return team
|
||||
|
||||
def get_alias_model_name(self, vals):
|
||||
return vals.get('alias_model', 'rma')
|
||||
|
||||
def get_alias_values(self):
|
||||
values = super().get_alias_values()
|
||||
values['alias_defaults'] = {'team_id': self.id}
|
||||
return values
|
||||
96
rma/models/stock_move.py
Normal file
96
rma/models/stock_move.py
Normal file
@@ -0,0 +1,96 @@
|
||||
# Copyright 2020 Tecnativa - Ernesto Tejeda
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class StockMove(models.Model):
|
||||
_inherit = "stock.move"
|
||||
|
||||
# RMAs that were created from the delivery move
|
||||
rma_ids = fields.One2many(
|
||||
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',
|
||||
copy=False,
|
||||
)
|
||||
# RMA that create the delivery movement to the customer
|
||||
rma_id = fields.Many2one(
|
||||
comodel_name='rma',
|
||||
string='RMA return',
|
||||
copy=False,
|
||||
)
|
||||
|
||||
def unlink(self):
|
||||
rma_receiver = self.mapped('rma_receiver_ids')
|
||||
rma = self.mapped('rma_id')
|
||||
res = super().unlink()
|
||||
rma_receiver.write({'state': 'draft'})
|
||||
rma.update_received_state()
|
||||
rma.update_replaced_state()
|
||||
return res
|
||||
|
||||
def _action_cancel(self):
|
||||
res = super()._action_cancel()
|
||||
cancelled_moves = self.filtered(lambda r: r.state == 'cancel')
|
||||
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):
|
||||
""" Avoids to validate stock.move with less quantity than the
|
||||
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')):
|
||||
rma_receiver = move.rma_receiver_ids
|
||||
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).")
|
||||
% (move.product_id.name, move.rma_receiver_ids.name)
|
||||
)
|
||||
res = super()._action_done()
|
||||
move_done = self.filtered(lambda r: r.state == 'done')
|
||||
# set RMAs as received
|
||||
to_be_received = move_done.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()
|
||||
return res
|
||||
|
||||
@api.model
|
||||
def _prepare_merge_moves_distinct_fields(self):
|
||||
""" 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']
|
||||
|
||||
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.rma_id.id
|
||||
return res
|
||||
|
||||
|
||||
class StockRule(models.Model):
|
||||
_inherit = 'stock.rule'
|
||||
|
||||
def _get_custom_move_fields(self):
|
||||
return super()._get_custom_move_fields() + ['rma_id']
|
||||
41
rma/models/stock_picking.py
Normal file
41
rma/models/stock_picking.py
Normal file
@@ -0,0 +1,41 @@
|
||||
# Copyright 2020 Tecnativa - Ernesto Tejeda
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class StockPicking(models.Model):
|
||||
_inherit = 'stock.picking'
|
||||
|
||||
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'))
|
||||
|
||||
def copy(self, default=None):
|
||||
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
|
||||
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')
|
||||
if len(rma) == 1:
|
||||
action.update(
|
||||
res_id=rma.id,
|
||||
view_mode="form",
|
||||
view_id=False,
|
||||
views=False,
|
||||
)
|
||||
else:
|
||||
action['domain'] = [('id', 'in', rma.ids)]
|
||||
return action
|
||||
118
rma/models/stock_warehouse.py
Normal file
118
rma/models/stock_warehouse.py
Normal file
@@ -0,0 +1,118 @@
|
||||
# Copyright 2020 Tecnativa - Ernesto Tejeda
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import fields, models, _
|
||||
|
||||
|
||||
class StockWarehouse(models.Model):
|
||||
_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',
|
||||
default=True,
|
||||
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',
|
||||
)
|
||||
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',
|
||||
)
|
||||
|
||||
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,
|
||||
},
|
||||
})
|
||||
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,
|
||||
},
|
||||
})
|
||||
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_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': True,
|
||||
'use_existing_lots': False,
|
||||
'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,
|
||||
},
|
||||
})
|
||||
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)
|
||||
})
|
||||
return data
|
||||
Reference in New Issue
Block a user