From 70aea6b24b082866ebfd65a34f2a8f2c7fb1c966 Mon Sep 17 00:00:00 2001 From: Jordi Ballester Date: Thu, 27 Jul 2017 18:17:19 +0200 Subject: [PATCH] init branch --- rma_account/README.rst | 51 +++++ rma_account/__init__.py | 5 + rma_account/__openerp__.py | 24 ++ rma_account/demo/rma_operation.xml | 44 ++++ rma_account/models/__init__.py | 7 + rma_account/models/invoice.py | 87 +++++++ rma_account/models/rma_operation.py | 13 ++ rma_account/models/rma_order.py | 145 ++++++++++++ rma_account/models/rma_order_line.py | 132 +++++++++++ rma_account/views/invoice_view.xml | 73 ++++++ rma_account/views/rma_operation_view.xml | 26 +++ rma_account/views/rma_order_line_view.xml | 89 ++++++++ rma_account/views/rma_order_view.xml | 68 ++++++ rma_account/wizards/__init__.py | 7 + rma_account/wizards/rma_add_invoice.py | 112 +++++++++ rma_account/wizards/rma_add_invoice.xml | 90 ++++++++ .../rma_order_line_make_supplier_rma.py | 19 ++ rma_account/wizards/rma_refund.py | 216 ++++++++++++++++++ rma_account/wizards/rma_refund.xml | 85 +++++++ 19 files changed, 1293 insertions(+) create mode 100644 rma_account/README.rst create mode 100644 rma_account/__init__.py create mode 100644 rma_account/__openerp__.py create mode 100644 rma_account/demo/rma_operation.xml create mode 100644 rma_account/models/__init__.py create mode 100644 rma_account/models/invoice.py create mode 100644 rma_account/models/rma_operation.py create mode 100644 rma_account/models/rma_order.py create mode 100644 rma_account/models/rma_order_line.py create mode 100644 rma_account/views/invoice_view.xml create mode 100644 rma_account/views/rma_operation_view.xml create mode 100644 rma_account/views/rma_order_line_view.xml create mode 100644 rma_account/views/rma_order_view.xml create mode 100644 rma_account/wizards/__init__.py create mode 100644 rma_account/wizards/rma_add_invoice.py create mode 100644 rma_account/wizards/rma_add_invoice.xml create mode 100644 rma_account/wizards/rma_order_line_make_supplier_rma.py create mode 100644 rma_account/wizards/rma_refund.py create mode 100644 rma_account/wizards/rma_refund.xml diff --git a/rma_account/README.rst b/rma_account/README.rst new file mode 100644 index 00000000..eadfe985 --- /dev/null +++ b/rma_account/README.rst @@ -0,0 +1,51 @@ +.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg + :alt: License LGPL-3 + +=========== +RMA Account +=========== + +This module integrates Return Merchandise Authorizations (RMA) with invoices, + allowing to: + +#. Create complete RMA's using existing invoices as a reference. +#. Create refunds from RMA. + +Usage +===== + +RMA are accessible though Inventory menu. There's four menus, divided by type +. Users can access to the list of RMA or RMA lines. + +Create an RMA: +#. Select a partner. Fill the rma lines by selecting an invoice. +#. Request approval and approve. +#. Click on RMA Lines button. +#. Click on more and select an option: "Receive products", "Create Delivery + Order, Create Refund". +#. Go back to the RMA. Set the RMA to done if not further action is required. + + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smashing it by providing a detailed and welcomed feedback. + + +Credits +======= + +Contributors +------------ + +* Jordi Ballester Alomar +* Aaron Henriquez + + +Maintainer +---------- + +This module is maintained by Eficent. diff --git a/rma_account/__init__.py b/rma_account/__init__.py new file mode 100644 index 00000000..4105ff51 --- /dev/null +++ b/rma_account/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# © 2017 Eficent Business and IT Consulting Services S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) +from . import models +from . import wizards diff --git a/rma_account/__openerp__.py b/rma_account/__openerp__.py new file mode 100644 index 00000000..43522ee7 --- /dev/null +++ b/rma_account/__openerp__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# © 2017 Eficent Business and IT Consulting Services S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) + +{ + 'name': 'RMA Account', + 'version': '9.0.1.0.0', + 'license': 'LGPL-3', + 'category': 'RMA', + 'summary': 'Integrates RMA with Invoice Processing', + 'author': "Eficent", + 'website': 'http://www.github.com/OCA/rma', + 'depends': ['account', 'rma'], + 'demo': ['demo/rma_operation.xml'], + 'data': ['views/rma_order_view.xml', + 'views/rma_operation_view.xml', + 'views/rma_order_line_view.xml', + 'views/invoice_view.xml', + 'wizards/rma_add_invoice.xml', + 'wizards/rma_refund.xml', + ], + 'installable': True, + 'auto_install': True, +} diff --git a/rma_account/demo/rma_operation.xml b/rma_account/demo/rma_operation.xml new file mode 100644 index 00000000..a312d6db --- /dev/null +++ b/rma_account/demo/rma_operation.xml @@ -0,0 +1,44 @@ + + + + + no + + + + no + + + + Refund before receive + RFC + ordered + no + no + customer + + + + + Refund Before deliver + RFS + ordered + no + no + supplier + + + + + no + + + + no + + + + no + + + diff --git a/rma_account/models/__init__.py b/rma_account/models/__init__.py new file mode 100644 index 00000000..90fbe336 --- /dev/null +++ b/rma_account/models/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +# © 2017 Eficent Business and IT Consulting Services S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) +from . import rma_order +from . import rma_order_line +from . import rma_operation +from . import invoice diff --git a/rma_account/models/invoice.py b/rma_account/models/invoice.py new file mode 100644 index 00000000..9f33a501 --- /dev/null +++ b/rma_account/models/invoice.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +# © 2017 Eficent Business and IT Consulting Services S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) + +from openerp import api, fields, models + + +class AccountInvoice(models.Model): + + _inherit = "account.invoice" + + @api.one + def _compute_rma_count(self): + rma_list = [] + for invl in self.invoice_line_ids: + for rmal in invl.rma_line_ids: + rma_list.append(rmal.rma_id.id) + self.rma_count = len(list(set(rma_list))) + + rma_count = fields.Integer(compute=_compute_rma_count, + string='# of RMA', + copy=False) + + @api.multi + def action_view_rma_supplier(self): + action = self.env.ref('rma.action_rma_supplier') + result = action.read()[0] + rma_list = [] + for invl in self.invoice_line_ids: + for rmal in invl.rma_line_ids: + rma_list.append(rmal.rma_id.id) + self.rma_count = len(list(set(rma_list))) + # choose the view_mode accordingly + if len(rma_list) != 1: + result['domain'] = "[('id', 'in', " + \ + str(rma_list) + ")]" + elif len(rma_list) == 1: + res = self.env.ref('rma.view_rma_supplier_form', False) + result['views'] = [(res and res.id or False, 'form')] + result['res_id'] = rma_list[0] + return result + + @api.multi + def action_view_rma(self): + action = self.env.ref('rma.action_rma_customer') + result = action.read()[0] + rma_list = [] + for invl in self.invoice_line_ids: + for rmal in invl.rma_line_ids: + rma_list.append(rmal.rma_id.id) + self.rma_count = len(list(set(rma_list))) + # choose the view_mode accordingly + if len(rma_list) != 1: + result['domain'] = "[('id', 'in', " + \ + str(rma_list) + ")]" + elif len(rma_list) == 1: + res = self.env.ref('rma.view_rma_form', False) + result['views'] = [(res and res.id or False, 'form')] + result['res_id'] = rma_list[0] + return result + + +class AccountInvoiceLine(models.Model): + + _inherit = "account.invoice.line" + + @api.multi + def _compute_rma_count(self): + rma_list = [] + for invl in self: + for rmal in invl.rma_line_ids: + rma_list.append(rmal.rma_id.id) + invl.rma_count = len(list(set(rma_list))) + + rma_count = fields.Integer(compute=_compute_rma_count, + string='# of RMA', + copy=False) + rma_line_ids = fields.One2many( + comodel_name='rma.order.line', inverse_name='invoice_line_id', + string="RMA", readonly=True, + help="This will contain the RMA lines for the invoice line") + + rma_line_id = fields.Many2one( + comodel_name='rma.order.line', + string="RMA line refund", + ondelete="set null", + help="This will contain the rma line that originated the refund line") diff --git a/rma_account/models/rma_operation.py b/rma_account/models/rma_operation.py new file mode 100644 index 00000000..a42c69c7 --- /dev/null +++ b/rma_account/models/rma_operation.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# © 2017 Eficent Business and IT Consulting Services S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) +from openerp import _, api, fields, models + + +class RmaOperation(models.Model): + _inherit = 'rma.operation' + + refund_policy = fields.Selection([ + ('no', 'No refund'), ('ordered', 'Based on Ordered Quantities'), + ('received', 'Based on Received Quantities')], string="Refund Policy", + default='no') diff --git a/rma_account/models/rma_order.py b/rma_account/models/rma_order.py new file mode 100644 index 00000000..fdcba820 --- /dev/null +++ b/rma_account/models/rma_order.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- +# © 2017 Eficent Business and IT Consulting Services S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) +from openerp import _, api, fields, models +from openerp.addons import decimal_precision as dp +from openerp.exceptions import UserError +from dateutil.relativedelta import relativedelta +from openerp.tools import DEFAULT_SERVER_DATE_FORMAT +from datetime import datetime + + +class RmaOrder(models.Model): + _inherit = "rma.order" + + @api.multi + def _compute_invoice_refund_count(self): + self.ensure_one() + invoice_list = [] + for line in self.rma_line_ids: + for refund in line.refund_line_ids: + invoice_list.append(refund.invoice_id.id) + self.invoice_refund_count = len(list(set(invoice_list))) + + @api.multi + def _compute_invoice_count(self): + self.ensure_one() + invoice_list = [] + for line in self.rma_line_ids: + if line.invoice_line_id and line.invoice_line_id.id: + invoice_list.append(line.invoice_line_id.invoice_id.id) + self.invoice_count = len(list(set(invoice_list))) + + add_invoice_id = fields.Many2one('account.invoice', string='Add Invoice', + ondelete='set null', readonly=True, + states={'draft': [('readonly', False)]}) + invoice_refund_count = fields.Integer( + compute=_compute_invoice_refund_count, + string='# of Refunds', + copy=False) + invoice_count = fields.Integer(compute=_compute_invoice_count, + string='# of Incoming Shipments', + copy=False) + + def _prepare_rma_line_from_inv_line(self, line): + operation = line.product_id.rma_operation_id and \ + line.product_id.rma_operation_id.id or False + if not operation: + operation = line.product_id.categ_id.rma_operation_id and \ + line.product_id.categ_id.rma_operation_id.id or False + data = { + 'invoice_line_id': line.id, + 'product_id': line.product_id.id, + 'name': line.name, + 'origin': line.invoice_id.number, + 'uom_id': line.uom_id.id, + 'operation_id': operation, + 'product_qty': line.quantity, + 'price_unit': line.invoice_id.currency_id.compute( + line.price_unit, line.currency_id, round=False), + 'rma_id': self._origin.id + } + return data + + @api.onchange('add_invoice_id') + def on_change_invoice(self): + if not self.add_invoice_id: + return {} + if not self.partner_id: + self.partner_id = self.add_invoice_id.partner_id.id + new_lines = self.env['rma.order.line'] + for line in self.add_invoice_id.invoice_line_ids: + # Load a PO line only once + if line in self.rma_line_ids.mapped('invoice_line_id'): + continue + data = self._prepare_rma_line_from_inv_line(line) + new_line = new_lines.new(data) + new_lines += new_line + + self.rma_line_ids += new_lines + self.date_rma = fields.Datetime.now() + self.delivery_address_id = self.add_invoice_id.partner_id.id + self.invoice_address_id = self.add_invoice_id.partner_id.id + self.add_invoice_id = False + return {} + + @api.model + def prepare_rma_line(self, origin_rma, rma_id, line): + line_values = super(RmaOrder, self).prepare_rma_line( + origin_rma, rma_id, line) + line_values['invoice_address_id'] = line.invoice_address_id.id + return line_values + + @api.model + def _prepare_rma_data(self, partner, origin_rma): + res = super(RmaOrder, self)._prepare_rma_data(partner, origin_rma) + res['invoice_address_id'] = partner.id + return res + + @api.multi + def action_view_invoice_refund(self): + """ + This function returns an action that display existing vendor refund + bills of given purchase order id. + When only one found, show the vendor bill immediately. + """ + action = self.env.ref('account.action_invoice_tree2') + result = action.read()[0] + invoice_list = [] + for line in self.rma_line_ids: + for refund in line.refund_line_ids: + invoice_list.append(refund.invoice_id.id) + invoice_ids = list(set(invoice_list)) + # choose the view_mode accordingly + if len(invoice_ids) != 1: + result['domain'] = "[('id', 'in', " + \ + str(invoice_ids) + ")]" + elif len(invoice_ids) == 1: + res = self.env.ref('account.invoice_supplier_form', False) + result['views'] = [(res and res.id or False, 'form')] + result['res_id'] = invoice_ids[0] + return result + + @api.multi + def action_view_invoice(self): + if self.type == "supplier": + action = self.env.ref('account.action_invoice_tree2') + else: + action = self.env.ref('account.action_invoice_tree') + result = action.read()[0] + invoice_list = [] + for line in self.rma_line_ids: + invoice_list.append(line.invoice_id.id) + invoice_ids = list(set(invoice_list)) + # choose the view_mode accordingly + if len(invoice_ids) != 1: + result['domain'] = "[('id', 'in', " + \ + str(invoice_ids) + ")]" + elif len(invoice_ids) == 1: + if self.type == "supplier": + res = self.env.ref('account.invoice_supplier_form', False) + else: + res = self.env.ref('account.invoice_form', False) + result['views'] = [(res and res.id or False, 'form')] + result['res_id'] = invoice_ids[0] + return result diff --git a/rma_account/models/rma_order_line.py b/rma_account/models/rma_order_line.py new file mode 100644 index 00000000..9f396edd --- /dev/null +++ b/rma_account/models/rma_order_line.py @@ -0,0 +1,132 @@ +# -*- coding: utf-8 -*- +# © 2017 Eficent Business and IT Consulting Services S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) +from openerp import _, api, fields, models +from openerp.addons import decimal_precision as dp +from openerp.exceptions import UserError + + +class RmaOrderLine(models.Model): + _inherit = "rma.order.line" + + @api.model + def _default_invoice_address(self): + partner_id = self.env.context.get('partner_id', False) + if partner_id: + return self.env['res.partner'].browse(partner_id) + return False + + @api.multi + @api.depends('refund_line_ids', 'refund_line_ids.invoice_id.state', + 'refund_policy', 'type') + def _compute_qty_refunded(self): + for rec in self: + rec.qty_refunded = sum(rec.refund_line_ids.filtered( + lambda i: i.invoice_id.state in ('open', 'paid')).mapped( + 'quantity')) + + @api.one + @api.depends('refund_line_ids', 'refund_line_ids.invoice_id.state', + 'refund_policy', 'move_ids', 'move_ids.state', 'type') + def _compute_qty_to_refund(self): + qty = 0.0 + if self.refund_policy == 'ordered': + qty = self.product_qty - self.qty_refunded + elif self.refund_policy == 'received': + qty = self.qty_received - self.qty_refunded + self.qty_to_refund = qty + + @api.multi + def _compute_refund_count(self): + for rec in self: + rec.refund_count = len(rec.refund_line_ids.mapped('invoice_id')) + + invoice_address_id = fields.Many2one( + 'res.partner', string='Partner invoice address', + default=_default_invoice_address, + help="Invoice address for current rma order.") + + refund_count = fields.Integer(compute=_compute_refund_count, + string='# of Refunds', copy=False, default=0) + + invoice_line_id = fields.Many2one('account.invoice.line', + string='Invoice Line', + ondelete='restrict', + index=True) + refund_line_ids = fields.One2many(comodel_name='account.invoice.line', + inverse_name='rma_line_id', + string='Refund Lines', + copy=False, index=True, readonly=True) + invoice_id = fields.Many2one('account.invoice', string='Source', + related='invoice_line_id.invoice_id', + index=True, readonly=True) + refund_policy = fields.Selection([ + ('no', 'No refund'), ('ordered', 'Based on Ordered Quantities'), + ('received', 'Based on Received Quantities')], string="Refund Policy", + required=True, default='no') + qty_to_refund = fields.Float( + string='Qty To Refund', copy=False, + digits=dp.get_precision('Product Unit of Measure'), readonly=True, + compute=_compute_qty_to_refund, store=True) + qty_refunded = fields.Float( + string='Qty Refunded', copy=False, + digits=dp.get_precision('Product Unit of Measure'), + readonly=True, compute=_compute_qty_refunded, store=True) + + @api.onchange('operation_id') + def _onchange_operation_id(self): + result = super(RmaOrderLine, self)._onchange_operation_id() + if not self.operation_id: + return result + self.refund_policy = self.operation_id.refund_policy + return result + + @api.onchange('invoice_line_id') + def _onchange_invoice_line_id(self): + result = {} + if not self.invoice_line_id: + return result + self.origin = self.invoice_id.number + return result + + @api.multi + @api.constrains('invoice_line_id') + def _check_duplicated_lines(self): + for line in self: + matching_inv_lines = self.env['account.invoice.line'].search([( + 'id', '=', line.invoice_line_id.id)]) + if len(matching_inv_lines) > 1: + raise UserError( + _("There's an rma for the invoice line %s " + "and invoice %s" % + (line.invoice_line_id, + line.invoice_line_id.invoice_id))) + return {} + + @api.multi + def action_view_invoice(self): + action = self.env.ref('account.action_invoice_tree') + result = action.read()[0] + res = self.env.ref('account.invoice_form', False) + result['views'] = [(res and res.id or False, 'form')] + result['view_id'] = res and res.id or False + result['res_id'] = self.invoice_line_id.invoice_id.id + + return result + + @api.multi + def action_view_refunds(self): + action = self.env.ref('account.action_invoice_tree2') + result = action.read()[0] + invoice_ids = [] + for inv_line in self.refund_line_ids: + invoice_ids.append(inv_line.invoice_id.id) + # choose the view_mode accordingly + if len(invoice_ids) != 1: + result['domain'] = "[('id', 'in', " + \ + str(invoice_ids) + ")]" + elif len(invoice_ids) == 1: + res = self.env.ref('account.invoice_supplier_form', False) + result['views'] = [(res and res.id or False, 'form')] + result['res_id'] = invoice_ids[0] + return result diff --git a/rma_account/views/invoice_view.xml b/rma_account/views/invoice_view.xml new file mode 100644 index 00000000..647c5043 --- /dev/null +++ b/rma_account/views/invoice_view.xml @@ -0,0 +1,73 @@ + + + + + + account.invoice.form + account.invoice + + + +
+ +
+
+
+
+ + + account.invoice.supplier.form + account.invoice + + + +
+ +
+
+
+
+ + + rma.invoice.line.form + account.invoice.line + + + + + + + + + + + + + + + + +
+ + + Invoice Line + account.invoice.line + form + form + + + +
diff --git a/rma_account/views/rma_operation_view.xml b/rma_account/views/rma_operation_view.xml new file mode 100644 index 00000000..671a2a0e --- /dev/null +++ b/rma_account/views/rma_operation_view.xml @@ -0,0 +1,26 @@ + + + + + rma.operation.tree + rma.operation + + + + + + + + + + rma.operation.form + rma.operation + + + + + + + + + diff --git a/rma_account/views/rma_order_line_view.xml b/rma_account/views/rma_order_line_view.xml new file mode 100644 index 00000000..0e8ba106 --- /dev/null +++ b/rma_account/views/rma_order_line_view.xml @@ -0,0 +1,89 @@ + + + + + rma.order.line.supplier.form + rma.order.line + + + + + + + + + + + + + + + + + + + + + + + + + + + rma.order.line.form + rma.order.line + + + + + + + + + + + + + + + + + + + + + + + + + + + rma.order.line.select + rma.order.line + + + + + + + + + + diff --git a/rma_account/views/rma_order_view.xml b/rma_account/views/rma_order_view.xml new file mode 100644 index 00000000..3e568fb4 --- /dev/null +++ b/rma_account/views/rma_order_view.xml @@ -0,0 +1,68 @@ + + + + + + rma.order.form + rma.order + + + + + + + + + + + rma.order.supplier.form + rma.order + + + + + + + + + + + diff --git a/rma_account/wizards/__init__.py b/rma_account/wizards/__init__.py new file mode 100644 index 00000000..0e1ebd69 --- /dev/null +++ b/rma_account/wizards/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +# © 2017 Eficent Business and IT Consulting Services S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) + +from . import rma_refund +from . import rma_add_invoice +from . import rma_order_line_make_supplier_rma diff --git a/rma_account/wizards/rma_add_invoice.py b/rma_account/wizards/rma_add_invoice.py new file mode 100644 index 00000000..45a7e662 --- /dev/null +++ b/rma_account/wizards/rma_add_invoice.py @@ -0,0 +1,112 @@ +# -*- coding: utf-8 -*- +# © 2017 Eficent Business and IT Consulting Services S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) + +import time +from openerp import models, fields, exceptions, api, _ +from openerp.exceptions import ValidationError +from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT as DT_FORMAT +import openerp.addons.decimal_precision as dp + + +class RmaAddinvoice(models.TransientModel): + _name = 'rma_add_invoice' + _description = 'Wizard to add rma lines' + + @api.model + def default_get(self, fields): + res = super(RmaAddinvoice, self).default_get(fields) + rma_obj = self.env['rma.order'] + rma_id = self.env.context['active_ids'] or [] + active_model = self.env.context['active_model'] + if not rma_id: + return res + assert active_model == 'rma.order', 'Bad context propagation' + + rma = rma_obj.browse(rma_id) + res['rma_id'] = rma.id + res['partner_id'] = rma.partner_id.id + res['invoice_id'] = False + res['invoice_line_ids'] = False + return res + + rma_id = fields.Many2one('rma.order', string='RMA Order', readonly=True, + ondelete='cascade') + partner_id = fields.Many2one(comodel_name='res.partner', string='Partner', + readonly=True) + invoice_id = fields.Many2one(comodel_name='account.invoice', + string='Invoice') + invoice_line_ids = fields.Many2many('account.invoice.line', + 'rma_add_invoice_add_line_rel', + 'invoice_line_id', + 'rma_add_invoice_id', + string='Invoice Lines') + + def _prepare_rma_line_from_inv_line(self, line): + operation = line.product_id.rma_operation_id or \ + line.product_id.categ_id.rma_operation_id + data = { + 'invoice_line_id': line.id, + 'product_id': line.product_id.id, + 'origin': line.invoice_id.number, + 'uom_id': line.uom_id.id, + 'operation_id': operation.id, + 'product_qty': line.quantity, + 'price_unit': line.invoice_id.currency_id.compute( + line.price_unit, line.currency_id, round=False), + 'delivery_address_id': self.invoice_id.partner_id.id, + 'invoice_address_id': self.invoice_id.partner_id.id, + 'rma_id': self.rma_id.id + } + if not operation: + operation = self.env['rma.operation'].search( + [('type', '=', self.rma_id.type)], limit=1) + if not operation: + raise ValidationError("Please define an operation first") + if not operation.in_route_id or not operation.out_route_id: + route = self.env['stock.location.route'].search( + [('rma_selectable', '=', True)], limit=1) + if not route: + raise ValidationError("Please define an rma route") + data.update( + {'in_route_id': operation.in_route_id.id, + 'out_route_id': operation.out_route_id.id, + 'in_warehouse_id': operation.in_warehouse_id.id, + 'out_warehouse_id': operation.out_warehouse_id.id, + 'receipt_policy': operation.receipt_policy, + 'location_id': operation.location_id.id, + 'operation_id': operation.id, + 'refund_policy': operation.refund_policy, + 'delivery_policy': operation.delivery_policy + }) + return data + + @api.model + def _get_rma_data(self): + data = { + 'date_rma': fields.Datetime.now(), + 'delivery_address_id': self.invoice_id.partner_id.id, + 'invoice_address_id': self.invoice_id.partner_id.id + } + return data + + @api.model + def _get_existing_invoice_lines(self): + existing_invoice_lines = [] + for rma_line in self.rma_id.rma_line_ids: + existing_invoice_lines.append(rma_line.invoice_line_id) + return existing_invoice_lines + + @api.multi + def add_lines(self): + rma_line_obj = self.env['rma.order.line'] + existing_invoice_lines = self._get_existing_invoice_lines() + for line in self.invoice_line_ids: + # Load a PO line only once + if line not in existing_invoice_lines: + data = self._prepare_rma_line_from_inv_line(line) + rma_line_obj.create(data) + rma = self.rma_id + data_rma = self._get_rma_data() + rma.write(data_rma) + return {'type': 'ir.actions.act_window_close'} diff --git a/rma_account/wizards/rma_add_invoice.xml b/rma_account/wizards/rma_add_invoice.xml new file mode 100644 index 00000000..83446712 --- /dev/null +++ b/rma_account/wizards/rma_add_invoice.xml @@ -0,0 +1,90 @@ + + + + + rma.add.invoice + rma_add_invoice + +
+ + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + Add Invoice + ir.actions.act_window + rma_add_invoice + rma.order + form + form + new + + + + + + + rma.order.line.form + rma.order + + + + + + + + rma.order.line.supplier.form + rma.order + + + + + + + +
diff --git a/rma_account/wizards/rma_order_line_make_supplier_rma.py b/rma_account/wizards/rma_order_line_make_supplier_rma.py new file mode 100644 index 00000000..9315161a --- /dev/null +++ b/rma_account/wizards/rma_order_line_make_supplier_rma.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 Eficent Business and IT Consulting Services S.L. +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl-3.0). + +import openerp.addons.decimal_precision as dp +from openerp import _, api, exceptions, fields, models + + +class RmaLineMakeSupplierRma(models.TransientModel): + _inherit = "rma.order.line.make.supplier.rma" + + @api.model + def _prepare_supplier_rma_line(self, rma, item): + res = super(RmaLineMakeSupplierRma, self)._prepare_supplier_rma_line( + rma, item) + if res['operation_id']: + operation = self.env['rma.operation'].browse(res['operation_id']) + res['refund_policy'] = operation.refund_policy + return res diff --git a/rma_account/wizards/rma_refund.py b/rma_account/wizards/rma_refund.py new file mode 100644 index 00000000..1b76ee9e --- /dev/null +++ b/rma_account/wizards/rma_refund.py @@ -0,0 +1,216 @@ +# -*- coding: utf-8 -*- +# © 2017 Eficent Business and IT Consulting Services S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) +from openerp import models, fields, exceptions, api, _ +import openerp.addons.decimal_precision as dp + + +class RmaRefund(models.TransientModel): + _name = "rma.refund" + + @api.model + def _get_reason(self): + context = dict(self._context or {}) + active_ids = context.get('active_ids', False) + if active_ids: + rma_lines = self.env['rma.order.line'].browse(active_ids[0]) + return rma_lines.rma_id.name + return '' + + @api.returns('rma.order.line') + def _prepare_item(self, line): + values = {'product_id': line.product_id.id, + 'name': line.name, + 'product_qty': line.product_qty, + 'uom_id': line.uom_id.id, + 'qty_to_refund': line.qty_to_refund, + 'refund_policy': line.refund_policy, + 'invoice_address_id': line.invoice_address_id.id, + 'line_id': line.id, + 'rma_id': line.rma_id.id, + 'wiz_id': self.env.context['active_id']} + return values + + @api.model + def default_get(self, fields): + """Default values for wizard, if there is more than one supplier on + lines the supplier field is empty otherwise is the unique line + supplier. + """ + res = super(RmaRefund, self).default_get( + fields) + rma_line_obj = self.env['rma.order.line'] + rma_line_ids = self.env.context['active_ids'] or [] + active_model = self.env.context['active_model'] + + if not rma_line_ids: + return res + assert active_model == 'rma.order.line', \ + 'Bad context propagation' + + items = [] + lines = rma_line_obj.browse(rma_line_ids) + if len(lines.mapped('partner_id')) > 1: + raise exceptions.Warning( + _('Only RMA lines from the same partner can be processed at ' + 'the same time')) + for line in lines: + items.append([0, 0, self._prepare_item(line)]) + res['item_ids'] = items + return res + + date_invoice = fields.Date(string='Refund Date', + default=fields.Date.context_today, + required=True) + date = fields.Date(string='Accounting Date') + description = fields.Char(string='Reason', required=True, + default=_get_reason) + item_ids = fields.One2many( + 'rma.refund.item', + 'wiz_id', string='Items') + + @api.multi + def compute_refund(self): + for wizard in self: + first = self.item_ids[0] + values = self._prepare_refund(wizard, first.rma_id) + if len(first.line_id.invoice_address_id): + values['partner_id'] = first.line_id.invoice_address_id.id + else: + values['partner_id'] = first.rma_id.partner_id.id + new_refund = self.env['account.invoice'].create(values) + for item in self.item_ids: + refund_line_values = self.prepare_refund_line(item, new_refund) + self.env['account.invoice.line'].create( + refund_line_values) + # Put the reason in the chatter + subject = _("Invoice refund") + body = self.item_ids[0].rma_id.name + new_refund.message_post(body=body, subject=subject) + return new_refund + + @api.multi + def invoice_refund(self): + rma_line_ids = self.env['rma.order.line'].browse( + self.env.context['active_ids']) + for line in rma_line_ids: + if line.refund_policy == 'no': + raise exceptions.Warning( + _('The operation is not refund for at least one line')) + if line.state != 'approved': + raise exceptions.Warning( + _('RMA %s is not approved') % + line.rma_id.name) + new_invoice = self.compute_refund() + action = 'action_invoice_tree1' if ( + new_invoice.type in ['out_refund', 'out_invoice']) \ + else 'action_invoice_tree2' + result = self.env.ref('account.%s' % action).read()[0] + invoice_domain = eval(result['domain']) + invoice_domain.append(('id', '=', new_invoice.id)) + result['domain'] = invoice_domain + return result + + @api.model + def prepare_refund_line(self, item, refund): + accounts = item.product_id.product_tmpl_id._get_product_accounts() + if item.line_id.type == 'customer': + account = accounts['stock_output'] + else: + account = accounts['stock_input'] + if not account: + raise exceptions.ValidationError("Accounts are not configure for " + "this product") + values = { + 'name': item.rma_id.name, + 'origin': item.rma_id.name, + 'account_id': account.id, + 'price_unit': item.line_id.price_unit, + 'uom_id': item.line_id.uom_id.id, + 'product_id': item.product_id.id, + 'rma_line_id': item.line_id.id, + 'quantity': item.qty_to_refund, + 'invoice_id': refund.id + } + return values + + @api.model + def _prepare_refund(self, wizard, order): + # origin_invoices = self._get_invoice(order) + if order.type == 'customer': + journal = self.env['account.journal'].search( + [('type', '=', 'sale')], limit=1) + else: + journal = self.env['account.journal'].search( + [('type', '=', 'purchase')], limit=1) + values = { + 'name': order.name, + 'origin': order.name, + 'reference': False, + # 'account_id': account.id, + 'journal_id': journal.id, + 'partner_id': order.partner_id.id, + 'currency_id': order.partner_id.company_id.currency_id.id, + 'payment_term_id': False, + 'fiscal_position_id': + order.partner_id.property_account_position_id.id, + } + team_ids = self.env['crm.team'].search( + ['|', ('user_id', '=', self.env.uid), + ('member_ids', '=', self.env.uid)], limit=1) + team_id = team_ids[0] if team_ids else False + if team_id: + values['team_id'] = team_id.id + if order.type == 'customer': + values['type'] = 'out_refund' + else: + values['type'] = 'in_refund' + values['state'] = 'draft' + values['number'] = False + values['date'] = wizard.date_invoice + values['date_invoice'] = wizard.date or wizard.date_invoice + return values + + @api.constrains('item_ids') + @api.one + def check_unique_invoice_address_id(self): + addresses = self.item_ids.mapped('invoice_address_id') + if len(addresses) > 1: + raise exceptions.ValidationError('The invoice address must be the ' + 'same for all the lines') + return True + + +class RmaRefundItem(models.TransientModel): + _name = "rma.refund.item" + _description = "RMA Lines to refund" + + wiz_id = fields.Many2one( + 'rma.refund', + string='Wizard', required=True) + line_id = fields.Many2one('rma.order.line', + string='RMA order Line', + required=True, + readonly=True, + ondelete='cascade') + rma_id = fields.Many2one('rma.order', + related='line_id.rma_id', + string='RMA', + readonly=True) + product_id = fields.Many2one('product.product', string='Product', + readonly=True) + name = fields.Char(string='Description', required=True) + product_qty = fields.Float( + string='Quantity Ordered', copy=False, + digits=dp.get_precision('Product Unit of Measure'), + readonly=True) + invoice_address_id = fields.Many2one('res.partner', 'Invoice Address') + qty_to_refund = fields.Float( + string='Quantity To Refund', + digits=dp.get_precision('Product Unit of Measure')) + uom_id = fields.Many2one('product.uom', string='Unit of Measure', + readonly=True) + refund_policy = fields.Selection([ + ('no', 'Not required'), ('ordered', 'Based on Ordered Quantities'), + ('received', 'Based on Received Quantities')], + string="Refund Policy") diff --git a/rma_account/wizards/rma_refund.xml b/rma_account/wizards/rma_refund.xml new file mode 100644 index 00000000..a2363c48 --- /dev/null +++ b/rma_account/wizards/rma_refund.xml @@ -0,0 +1,85 @@ + + + + + + rma.refund.form + rma.refund + +
+ + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ + + Create Refund + rma.refund + form + tree,form + + + new + + + + rma.order.line.form + rma.order.line + + +
+
+
+
+ + + rma.order.line.supplier.form + rma.order.line + + +
+
+
+
+ + + + Create Refund + client_action_multi + + action + rma.order.line + + +
+