diff --git a/rma_sale/README.rst b/rma_sale/README.rst new file mode 100644 index 00000000..83a71dc9 --- /dev/null +++ b/rma_sale/README.rst @@ -0,0 +1,57 @@ +.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg + :alt: License LGPL-3 + +======== +RMA Sale +======== + +This module allows you to: + +#. Import sales order lines into RMA lines +#. Create a sales order and/or sales order line from one or more RMA lines + +Usage +===== + +**Import existing sales order lines into an RMA:** + +This feature is useful when you create an RMA associated to a product that +was shipped and you have as a reference the customer PO number. + +#. Access to a customer RMA. +#. Fill the customer. +#. Press the button *Add from Sales Order*. +#. In the wizard add a sales order and click on *add item* to select the + lines you want to add to the RMA. + +**Create a sales order and/or sales order line from RMA lines:** + +#. Go to a approved RMA line. +#. Click on *Create a Sales Quotation*. +#. In the wizard, select an *Existing Quotation to update* or leave it empty + if you want to create a new one. +#. Fill the quantity to sell in the lines. +#. Hit *Create a Sales Quotation*. + +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 +* Lois Rilo + +Maintainer +---------- + +This module is maintained by Eficent diff --git a/rma_sale/__init__.py b/rma_sale/__init__.py new file mode 100644 index 00000000..44815ecb --- /dev/null +++ b/rma_sale/__init__.py @@ -0,0 +1,4 @@ +# Copyright 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_sale/__manifest__.py b/rma_sale/__manifest__.py new file mode 100644 index 00000000..4d107f8d --- /dev/null +++ b/rma_sale/__manifest__.py @@ -0,0 +1,24 @@ +# Copyright 2017 Eficent Business and IT Consulting Services S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) + +{ + 'name': 'RMA Sale', + 'version': '12.0.1.0.0', + 'license': 'LGPL-3', + 'category': 'RMA', + 'summary': 'Links RMA with Sales Orders', + 'author': "Eficent, Odoo Community Association (OCA)", + 'website': 'http://www.github.com/OCA/rma', + 'depends': ['rma_account', 'sale_stock'], + 'data': [ + 'data/rma_operation.xml', + 'views/rma_order_view.xml', + 'views/rma_operation_view.xml', + 'views/sale_order_view.xml', + 'wizards/rma_order_line_make_sale_order_view.xml', + 'wizards/rma_add_sale.xml', + 'views/rma_order_line_view.xml', + ], + 'installable': True, + 'auto_install': True, +} diff --git a/rma_sale/data/rma_operation.xml b/rma_sale/data/rma_operation.xml new file mode 100644 index 00000000..b0fcfd39 --- /dev/null +++ b/rma_sale/data/rma_operation.xml @@ -0,0 +1,28 @@ + + + + + Sale after receive + SL-C + received + ordered + no + no + customer + + + + + + Advanced Refund + AR-C + received + ordered + no + received + customer + + + + + diff --git a/rma_sale/models/__init__.py b/rma_sale/models/__init__.py new file mode 100644 index 00000000..6108253c --- /dev/null +++ b/rma_sale/models/__init__.py @@ -0,0 +1,6 @@ +# Copyright 2017 Eficent Business and IT Consulting Services S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) +from . import sale_order_line +from . import rma_order_line +from . import rma_order +from . import rma_operation diff --git a/rma_sale/models/rma_operation.py b/rma_sale/models/rma_operation.py new file mode 100644 index 00000000..14e086f8 --- /dev/null +++ b/rma_sale/models/rma_operation.py @@ -0,0 +1,12 @@ +# Copyright 2017 Eficent Business and IT Consulting Services S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) +from odoo import fields, models + + +class RmaOperation(models.Model): + _inherit = 'rma.operation' + + sale_policy = fields.Selection([ + ('no', 'Not required'), ('ordered', 'Based on Ordered Quantities'), + ('received', 'Based on Received Quantities')], + string="Sale Policy", default='no') diff --git a/rma_sale/models/rma_order.py b/rma_sale/models/rma_order.py new file mode 100644 index 00000000..f5236329 --- /dev/null +++ b/rma_sale/models/rma_order.py @@ -0,0 +1,35 @@ +# Copyright 2017 Eficent Business and IT Consulting Services S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) +from odoo import api, fields, models + + +class RmaOrder(models.Model): + _inherit = "rma.order" + + @api.depends('rma_line_ids', 'rma_line_ids.sale_line_id', + 'rma_line_ids.sale_line_id.order_id') + def _compute_sales_count(self): + for rma in self: + sales = rma.mapped('rma_line_ids.sale_line_id.order_id') + rma.sale_count = len(sales) + + sale_count = fields.Integer( + compute='_compute_sales_count', string='# of Sales') + + @api.model + def _get_line_domain(self, rma_id, line): + if line.sale_line_id and line.sale_line_id.id: + domain = [('rma_id', '=', rma_id.id), + ('type', '=', 'supplier'), + ('sale_line_id', '=', line.sale_line_id.id)] + else: + domain = super(RmaOrder, self)._get_line_domain(rma_id, line) + return domain + + @api.multi + def action_view_sale_order(self): + action = self.env.ref('sale.action_quotations') + result = action.read()[0] + so_ids = self.mapped('rma_line_ids.sale_line_id.order_id').ids + result['domain'] = [('id', 'in', so_ids)] + return result diff --git a/rma_sale/models/rma_order_line.py b/rma_sale/models/rma_order_line.py new file mode 100644 index 00000000..d4745bd8 --- /dev/null +++ b/rma_sale/models/rma_order_line.py @@ -0,0 +1,180 @@ +# Copyright 2017 Eficent Business and IT Consulting Services S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) +from odoo import api, fields, models, _ +from odoo.exceptions import ValidationError +from odoo.addons import decimal_precision as dp + + +class RmaOrderLine(models.Model): + _inherit = "rma.order.line" + + @api.depends('sale_line_ids', 'sale_policy', 'sales_count', + 'sale_line_ids.state', 'qty_received', 'product_qty') + def _compute_qty_to_sell(self): + for rec in self: + if rec.sale_policy == 'ordered': + qty = rec._get_rma_sold_qty() + rec.qty_to_sell = rec.product_qty - qty + elif rec.sale_policy == 'received': + qty = rec._get_rma_sold_qty() + rec.qty_to_sell = rec.qty_received - qty + else: + rec.qty_to_sell = 0.0 + + @api.depends('sale_line_ids', 'sale_policy', 'sales_count', + 'sale_line_ids.state') + def _compute_qty_sold(self): + for rec in self: + rec.qty_sold = rec._get_rma_sold_qty() + + @api.depends('sale_line_ids', 'sale_line_ids.order_id') + def _compute_sales_count(self): + for line in self: + sales = line.mapped('sale_line_ids.order_id') + line.sales_count = len(sales) + + sale_line_id = fields.Many2one( + comodel_name='sale.order.line', string='Originating Sales Order Line', + ondelete='restrict', copy=False, + readonly=True, states={'draft': [('readonly', False)]}, + ) + sale_id = fields.Many2one( + string="Source Sales Order", related='sale_line_id.order_id', + ) + sale_line_ids = fields.One2many( + comodel_name='sale.order.line', inverse_name='rma_line_id', + string='Sales Order Lines', readonly=True, + states={'draft': [('readonly', False)]}, copy=False) + qty_to_sell = fields.Float( + string='Qty To Sell', copy=False, + digits=dp.get_precision('Product Unit of Measure'), + readonly=True, compute='_compute_qty_to_sell', + store=True) + qty_sold = fields.Float( + string='Qty Sold', copy=False, + digits=dp.get_precision('Product Unit of Measure'), + readonly=True, compute='_compute_qty_sold', + store=True) + sale_policy = fields.Selection(selection=[ + ('no', 'Not required'), ('ordered', 'Based on Ordered Quantities'), + ('received', 'Based on Received Quantities')], + string="Sale Policy", default='no', required=True, + readonly=True, states={'draft': [('readonly', False)]}) + sales_count = fields.Integer( + compute='_compute_sales_count', string='# of Sales') + + @api.onchange('product_id', 'partner_id') + def _onchange_product_id(self): + """Domain for sale_line_id is computed here to make it dynamic.""" + res = super(RmaOrderLine, self)._onchange_product_id() + if not res.get('domain'): + res['domain'] = {} + domain = [ + '|', + ('order_id.partner_id', '=', self.partner_id.id), + ('order_id.partner_id', 'child_of', self.partner_id.id)] + if self.product_id: + domain.append(('product_id', '=', self.product_id.id)) + res['domain']['sale_line_id'] = domain + return res + + @api.onchange('operation_id') + def _onchange_operation_id(self): + res = super(RmaOrderLine, self)._onchange_operation_id() + if self.operation_id: + self.sale_policy = self.operation_id.sale_policy or 'no' + return res + + @api.multi + def _prepare_rma_line_from_sale_order_line(self, line): + self.ensure_one() + if not self.type: + self.type = self._get_default_type() + operation = line.product_id.rma_customer_operation_id + if not operation: + operation = line.product_id.categ_id.rma_customer_operation_id + if not operation: + operation = self.env['rma.operation'].search( + [('type', '=', self.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")) + if not operation.in_warehouse_id or not operation.out_warehouse_id: + warehouse = self.env['stock.warehouse'].search( + [('company_id', '=', self.company_id.id), + ('lot_rma_id', '!=', False)], limit=1) + if not warehouse: + raise ValidationError(_( + "Please define a warehouse with a default RMA location.")) + data = { + 'product_id': line.product_id.id, + 'origin': line.order_id.name, + 'uom_id': line.product_uom.id, + 'operation_id': operation.id, + 'product_qty': line.product_uom_qty, + 'delivery_address_id': line.order_id.partner_id.id, + 'invoice_address_id': line.order_id.partner_id.id, + 'price_unit': line.currency_id.compute( + line.price_unit, line.currency_id, round=False), + 'in_route_id': operation.in_route_id.id or route.id, + 'out_route_id': operation.out_route_id.id or route.id, + 'receipt_policy': operation.receipt_policy, + 'currency_id': line.currency_id.id, + 'location_id': (operation.location_id.id or + operation.in_warehouse_id.lot_rma_id.id or + warehouse.lot_rma_id.id), + 'refund_policy': operation.refund_policy, + 'delivery_policy': operation.delivery_policy, + 'in_warehouse_id': operation.in_warehouse_id.id or warehouse.id, + 'out_warehouse_id': operation.out_warehouse_id.id or warehouse.id, + } + return data + + @api.onchange('sale_line_id') + def _onchange_sale_line_id(self): + if not self.sale_line_id: + return + data = self._prepare_rma_line_from_sale_order_line( + self.sale_line_id) + self.update(data) + self._remove_other_data_origin('sale_line_id') + + @api.multi + def _remove_other_data_origin(self, exception): + res = super(RmaOrderLine, self)._remove_other_data_origin(exception) + if not exception == 'sale_line_id': + self.sale_line_id = False + return res + + @api.multi + @api.constrains('sale_line_id', 'partner_id') + def _check_sale_partner(self): + for rec in self: + if (rec.sale_line_id and + rec.sale_line_id.order_id.partner_id != rec.partner_id and + rec.sale_line_id.order_id.partner_id.parent_id != + rec.partner_id): + raise ValidationError(_( + "RMA customer and originating sales order line customer " + "doesn't match.")) + + @api.multi + def action_view_sale_order(self): + action = self.env.ref('sale.action_quotations') + result = action.read()[0] + order_ids = self.mapped('sale_line_ids.order_id').ids + result['domain'] = [('id', 'in', order_ids)] + return result + + @api.multi + def _get_rma_sold_qty(self): + self.ensure_one() + qty = 0.0 + for sale_line in self.sale_line_ids.filtered( + lambda p: p.state not in ('draft', 'sent', 'cancel')): + qty += sale_line.product_uom_qty + return qty diff --git a/rma_sale/models/sale_order_line.py b/rma_sale/models/sale_order_line.py new file mode 100644 index 00000000..645fc4fa --- /dev/null +++ b/rma_sale/models/sale_order_line.py @@ -0,0 +1,57 @@ +# Copyright 2017 Eficent Business and IT Consulting Services S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) +from odoo import api, fields, models + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + @api.model + def name_search(self, name='', args=None, operator='ilike', limit=100): + """Allows to search by SO reference.""" + if not args: + args = [] + args += ['|', + (self._rec_name, operator, name), + ('order_id.name', operator, name)] + return super(SaleOrderLine, self).name_search( + name=name, args=args, operator=operator, limit=limit) + + @api.model + def _name_search(self, name='', args=None, operator='ilike', + limit=100, name_get_uid=None): + """Typed text is cleared here for better extensibility.""" + return super(SaleOrderLine, self)._name_search( + name='', args=args, operator=operator, limit=limit, + name_get_uid=name_get_uid) + + @api.multi + def name_get(self): + res = [] + if self.env.context.get('rma'): + for sale in self: + if sale.order_id.name: + res.append( + (sale.id, "SO:%s | INV: %s, | PART:%s | QTY:%s" % ( + sale.order_id.name, + " ".join(str(x) for x in [ + inv.number + for inv in sale.order_id.invoice_ids]), + sale.product_id.name, sale.product_uom_qty))) + else: + res.append(super(SaleOrderLine, sale).name_get()[0]) + return res + else: + return super(SaleOrderLine, self).name_get() + + rma_line_id = fields.Many2one( + comodel_name='rma.order.line', string='RMA', ondelete='restrict') + + @api.multi + def _prepare_order_line_procurement(self, group_id=False): + vals = super(SaleOrderLine, self)._prepare_order_line_procurement( + group_id=group_id) + vals.update({ + 'rma_line_id': self.rma_line_id.id + }) + return vals diff --git a/rma_sale/tests/__init__.py b/rma_sale/tests/__init__.py new file mode 100644 index 00000000..fae781a4 --- /dev/null +++ b/rma_sale/tests/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2017 Eficent Business and IT Consulting Services S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) + +from . import test_rma_sale diff --git a/rma_sale/tests/test_rma_sale.py b/rma_sale/tests/test_rma_sale.py new file mode 100644 index 00000000..5cc3096d --- /dev/null +++ b/rma_sale/tests/test_rma_sale.py @@ -0,0 +1,153 @@ +# Copyright 2017 Eficent Business and IT Consulting Services S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) + +from openerp.tests import common + + +class TestRmaSale(common.SingleTransactionCase): + + @classmethod + def setUpClass(cls): + super(TestRmaSale, cls).setUpClass() + + cls.rma_obj = cls.env['rma.order'] + cls.rma_line_obj = cls.env['rma.order.line'] + cls.rma_op_obj = cls.env['rma.operation'] + cls.rma_add_sale_wiz = cls.env['rma_add_sale'] + cls.rma_make_sale_wiz = cls.env['rma.order.line.make.sale.order'] + cls.so_obj = cls.env['sale.order'] + cls.sol_obj = cls.env['sale.order.line'] + cls.product_obj = cls.env['product.product'] + cls.partner_obj = cls.env['res.partner'] + + cls.rma_route_cust = cls.env.ref('rma.route_rma_customer') + + # Create customer + customer1 = cls.partner_obj.create({'name': 'Customer 1'}) + + # Create products + cls.product_1 = cls.product_obj.create({ + 'name': 'Test Product 1', + 'type': 'product', + 'list_price': 100.0, + }) + cls.product_2 = cls.product_obj.create({ + 'name': 'Test Product 2', + 'type': 'product', + 'list_price': 150.0, + }) + + # Create SO: + cls.so = cls.so_obj.create({ + 'partner_id': customer1.id, + 'partner_invoice_id': customer1.id, + 'partner_shipping_id': customer1.id, + 'order_line': [ + (0, 0, { + 'name': cls.product_1.name, + 'product_id': cls.product_1.id, + 'product_uom_qty': 20.0, + 'product_uom': cls.product_1.uom_id.id, + 'price_unit': cls.product_1.list_price + }), + (0, 0, { + 'name': cls.product_2.name, + 'product_id': cls.product_2.id, + 'product_uom_qty': 18.0, + 'product_uom': cls.product_2.uom_id.id, + 'price_unit': cls.product_2.list_price + }), + ], + 'pricelist_id': cls.env.ref('product.list0').id, + }) + + # Create RMA group and operation: + cls.rma_group = cls.rma_obj.create({ + 'partner_id': customer1.id, + }) + cls.operation_1 = cls.rma_op_obj.create({ + 'code': 'TEST', + 'name': 'Sale afer receive', + 'type': 'customer', + 'receipt_policy': 'ordered', + 'sale_policy': 'received', + 'in_route_id': cls.rma_route_cust.id, + 'out_route_id': cls.rma_route_cust.id, + }) + cls.operation_2 = cls.rma_op_obj.create({ + 'code': 'TEST', + 'name': 'Receive and Sale', + 'type': 'customer', + 'receipt_policy': 'ordered', + 'sale_policy': 'ordered', + 'in_route_id': cls.rma_route_cust.id, + 'out_route_id': cls.rma_route_cust.id, + }) + + def test_01_add_from_sale_order(self): + """Test wizard to create RMA from Sales Orders.""" + add_sale = self.rma_add_sale_wiz.with_context({ + 'customer': True, + 'active_ids': self.rma_group.id, + 'active_model': 'rma.order', + }).create({ + 'sale_id': self.so.id, + 'sale_line_ids': [(6, 0, self.so.order_line.ids)], + }) + add_sale.add_lines() + self.assertEqual(len(self.rma_group.rma_line_ids), 2) + + def test_02_rma_sale_operation(self): + """Test RMA quantities using sale operations.""" + # Received sale_policy: + rma_1 = self.rma_group.rma_line_ids.filtered( + lambda r: r.product_id == self.product_1) + rma_1.write({ + 'operation_id': self.operation_1.id, + }) + rma_1._onchange_operation_id() + self.assertEqual(rma_1.sale_policy, 'received') + self.assertEqual(rma_1.qty_to_sell, 0.0) + # TODO: receive and check qty_to_sell is 20.0 + # Ordered sale_policy: + rma_2 = self.rma_group.rma_line_ids.filtered( + lambda r: r.product_id == self.product_2) + rma_2.write({ + 'operation_id': self.operation_2.id, + }) + rma_2._onchange_operation_id() + self.assertEqual(rma_2.sale_policy, 'ordered') + self.assertEqual(rma_2.qty_to_sell, 18.0) + + def test_03_rma_create_sale(self): + """Generate a Sales Order from a customer RMA.""" + rma = self.rma_group.rma_line_ids.filtered( + lambda r: r.product_id == self.product_2) + self.assertEqual(rma.sales_count, 0) + self.assertEqual(rma.qty_to_sell, 18.0) + self.assertEqual(rma.qty_sold, 0.0) + make_sale = self.rma_make_sale_wiz.with_context({ + 'customer': True, + 'active_ids': rma.id, + 'active_model': 'rma.order.line', + }).create({ + 'partner_id': rma.partner_id.id, + }) + make_sale.make_sale_order() + self.assertEqual(rma.sales_count, 1) + rma.sale_line_ids.order_id.action_confirm() + self.assertEqual(rma.qty_to_sell, 0.0) + self.assertEqual(rma.qty_sold, 18.0) + + def test_04_fill_rma_from_so_line(self): + """Test filling a RMA (line) from a Sales Order line.""" + so_line = self.so.order_line.filtered( + lambda r: r.product_id == self.product_1) + rma = self.rma_line_obj.new({ + 'partner_id': self.so.partner_id.id, + 'sale_line_id': so_line.id, + }) + self.assertFalse(rma.product_id) + rma._onchange_sale_line_id() + self.assertEqual(rma.product_id, self.product_1) + self.assertEqual(rma.product_qty, 20.0) diff --git a/rma_sale/views/rma_operation_view.xml b/rma_sale/views/rma_operation_view.xml new file mode 100644 index 00000000..4ab4a56d --- /dev/null +++ b/rma_sale/views/rma_operation_view.xml @@ -0,0 +1,26 @@ + + + + + rma.operation.tree + rma.operation + + + + + + + + + + rma.operation.form + rma.operation + + + + + + + + + diff --git a/rma_sale/views/rma_order_line_view.xml b/rma_sale/views/rma_order_line_view.xml new file mode 100644 index 00000000..c6d0f5e5 --- /dev/null +++ b/rma_sale/views/rma_order_line_view.xml @@ -0,0 +1,113 @@ + + + + + rma.order.line.form + rma.order.line + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + rma.order.line.supplier.form + rma.order.line + + +
+ +
+ + + + + + + + + + + + + + + +
+ + + rma.order.line.form + rma.order.line + + +
+
+
+
+ + rma.order.line.supplier.form + rma.order.line + + +
+
+
+
+ + + rma.order.line.select - rma_sale + rma.order.line + + + + + + + + + + +
diff --git a/rma_sale/views/rma_order_view.xml b/rma_sale/views/rma_order_view.xml new file mode 100644 index 00000000..f3fd9c47 --- /dev/null +++ b/rma_sale/views/rma_order_view.xml @@ -0,0 +1,19 @@ + + + + rma.order.form + rma.order + + +
+ +
+
+
+
diff --git a/rma_sale/views/sale_order_view.xml b/rma_sale/views/sale_order_view.xml new file mode 100644 index 00000000..f45b0852 --- /dev/null +++ b/rma_sale/views/sale_order_view.xml @@ -0,0 +1,17 @@ + + + + sale.order.form + sale.order + + + + + + + + diff --git a/rma_sale/wizards/__init__.py b/rma_sale/wizards/__init__.py new file mode 100644 index 00000000..631e0330 --- /dev/null +++ b/rma_sale/wizards/__init__.py @@ -0,0 +1,7 @@ +# Copyright 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_line_make_sale_order +from . import rma_make_picking +from . import rma_refund +from . import rma_add_sale diff --git a/rma_sale/wizards/rma_add_sale.py b/rma_sale/wizards/rma_add_sale.py new file mode 100644 index 00000000..0946b082 --- /dev/null +++ b/rma_sale/wizards/rma_add_sale.py @@ -0,0 +1,115 @@ +# Copyright 2017 Eficent Business and IT Consulting Services S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class RmaAddSale(models.TransientModel): + _name = 'rma_add_sale' + _description = 'Wizard to add rma lines from SO lines' + + @api.model + def default_get(self, fields): + res = super(RmaAddSale, 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['sale_id'] = False + res['sale_line_ids'] = False + return res + + rma_id = fields.Many2one( + comodel_name='rma.order', string='RMA Order', readonly=True) + partner_id = fields.Many2one(comodel_name='res.partner', string='Partner', + readonly=True) + sale_id = fields.Many2one(comodel_name='sale.order', string='Order') + sale_line_ids = fields.Many2many('sale.order.line', + 'rma_add_sale_add_line_rel', + 'sale_line_id', 'rma_add_sale_id', + readonly=False, + string='Sale Lines') + + def _prepare_rma_line_from_sale_order_line(self, line): + operation = line.product_id.rma_customer_operation_id + if not operation: + operation = line.product_id.categ_id.rma_customer_operation_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")) + if not operation.in_warehouse_id or not operation.out_warehouse_id: + warehouse = self.env['stock.warehouse'].search( + [('company_id', '=', self.rma_id.company_id.id), + ('lot_rma_id', '!=', False)], limit=1) + if not warehouse: + raise ValidationError(_("Please define a warehouse with a " + "default rma location.")) + data = { + 'partner_id': self.partner_id.id, + 'sale_line_id': line.id, + 'product_id': line.product_id.id, + 'origin': line.order_id.name, + 'uom_id': line.product_uom.id, + 'operation_id': operation.id, + 'product_qty': line.product_uom_qty, + 'delivery_address_id': self.sale_id.partner_id.id, + 'invoice_address_id': self.sale_id.partner_id.id, + 'price_unit': line.currency_id.compute( + line.price_unit, line.currency_id, round=False), + 'rma_id': self.rma_id.id, + 'in_route_id': operation.in_route_id.id or route.id, + 'out_route_id': operation.out_route_id.id or route.id, + 'receipt_policy': operation.receipt_policy, + 'location_id': (operation.location_id.id or + operation.in_warehouse_id.lot_rma_id.id or + warehouse.lot_rma_id.id), + 'refund_policy': operation.refund_policy, + 'delivery_policy': operation.delivery_policy, + 'in_warehouse_id': operation.in_warehouse_id.id or warehouse.id, + 'out_warehouse_id': operation.out_warehouse_id.id or warehouse.id, + } + return data + + @api.model + def _get_rma_data(self): + data = { + 'date_rma': fields.Datetime.now(), + 'delivery_address_id': self.sale_id.partner_id.id, + 'invoice_address_id': self.sale_id.partner_id.id + } + return data + + @api.model + def _get_existing_sale_lines(self): + existing_sale_lines = [] + for rma_line in self.rma_id.rma_line_ids: + existing_sale_lines.append(rma_line.sale_line_id) + return existing_sale_lines + + @api.multi + def add_lines(self): + rma_line_obj = self.env['rma.order.line'] + existing_sale_lines = self._get_existing_sale_lines() + for line in self.sale_line_ids: + # Load a PO line only once + if line not in existing_sale_lines: + data = self._prepare_rma_line_from_sale_order_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_sale/wizards/rma_add_sale.xml b/rma_sale/wizards/rma_add_sale.xml new file mode 100644 index 00000000..1f0bd4d8 --- /dev/null +++ b/rma_sale/wizards/rma_add_sale.xml @@ -0,0 +1,79 @@ + + + + + rma.add.sale + rma_add_sale + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + Add Sale Order + ir.actions.act_window + rma_add_sale + rma.order + form + form + new + + + + + + + rma.order.form - sale wizard + rma.order + + + +