[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:
Ernesto Tejeda
2020-05-13 00:27:46 -04:00
committed by Nikolaus Weingartmair
parent a7bddf7db9
commit 42c8f0630a
48 changed files with 7643 additions and 0 deletions

12
rma/models/__init__.py Normal file
View 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

View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

View 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
View 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
View 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']

View 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

View 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