diff --git a/rma_repair/README.rst b/rma_repair/README.rst new file mode 100644 index 00000000..83a71dc9 --- /dev/null +++ b/rma_repair/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_repair/__init__.py b/rma_repair/__init__.py new file mode 100644 index 00000000..4105ff51 --- /dev/null +++ b/rma_repair/__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_repair/__openerp__.py b/rma_repair/__openerp__.py new file mode 100644 index 00000000..fdc480a7 --- /dev/null +++ b/rma_repair/__openerp__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# 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 Repair", + "version": "9.0.1.0.0", + "license": "LGPL-3", + "category": "RMA", + "summary": "Links RMA with Repairs.", + "author": "Eficent", + "website": "http://www.github.com/OCA/rma", + "depends": ["rma_account", "mrp_repair_refurbish"], + "data": ["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_repair/models/__init__.py b/rma_repair/models/__init__.py new file mode 100644 index 00000000..f5e18c3a --- /dev/null +++ b/rma_repair/models/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +# 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 mrp_repair +from . import rma_order_line +from . import rma_order +from . import rma_operation diff --git a/rma_repair/models/mrp_repair.py b/rma_repair/models/mrp_repair.py new file mode 100644 index 00000000..7f346a9d --- /dev/null +++ b/rma_repair/models/mrp_repair.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# 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 import api, fields, models + + +class MrpRepair(models.Model): + _inherit = "mrp.repair" + + rma_line_id = fields.Many2one( + comodel_name='rma.order.line', string='RMA', ondelete='restrict') diff --git a/rma_repair/models/rma_operation.py b/rma_repair/models/rma_operation.py new file mode 100644 index 00000000..730e5ce2 --- /dev/null +++ b/rma_repair/models/rma_operation.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# 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 import fields, models + + +class RmaOperation(models.Model): + _inherit = 'rma.operation' + + repair_type = fields.Selection([ + ('no', 'Not required'), ('ordered', 'Based on Ordered Quantities'), + ('received', 'Based on Received Quantities')], + string="Repair Policy", default='no') diff --git a/rma_repair/models/rma_order.py b/rma_repair/models/rma_order.py new file mode 100644 index 00000000..7dd90913 --- /dev/null +++ b/rma_repair/models/rma_order.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# 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 import api, fields, models + + +class RmaOrder(models.Model): + _inherit = "rma.order" + + @api.multi + def _compute_repair_count(self): + for rma in self: + repairs = rma.mapped('rma_line_ids.repair_ids') + rma.rma_count = len(repairs) + + repair_count = fields.Integer( + compute=_compute_repair_count, string='# of Repairs') + + @api.multi + def action_view_repair_order(self): + action = self.env.ref('mrp_repair.action_repair_order_tree') + result = action.read()[0] + repair_ids = self.mapped('rma_line_ids.repair_ids').ids + result['domain'] = [('id', 'in', repair_ids)] + return result diff --git a/rma_repair/models/rma_order_line.py b/rma_repair/models/rma_order_line.py new file mode 100644 index 00000000..5be63526 --- /dev/null +++ b/rma_repair/models/rma_order_line.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# 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 import api, fields, models +from openerp.addons import decimal_precision as dp + + +class RmaOrderLine(models.Model): + _inherit = "rma.order.line" + + @api.one + @api.depends('repair_ids', 'repair_ids.state') + def _compute_qty_to_repair(self): + if self.repair_type == 'no': + self.qty_to_repair = 0.0 + elif self.repair_type == 'ordered': + qty = self._get_rma_repaired_qty() + self.qty_to_repair = self.product_qty - qty + elif self.repair_type == 'received': + qty = self._get_rma_repaired_qty() + self.qty_to_repair = self.qty_received - qty + else: + self.qty_to_repair = 0.0 + + @api.one + @api.depends('repair_ids', 'repair_type', 'repair_ids.state') + def _compute_qty_repaired(self): + self.qty_repaired = self._get_rma_repaired_qty() + + @api.multi + def _compute_repair_count(self): + for line in self: + line.repair_count = len(line.repair_ids) + + repair_ids = fields.One2many( + comodel_name='mrp.repair', inverse_name='rma_line_id', + string='Repair Orders', readonly=True, + states={'draft': [('readonly', False)]}, copy=False) + qty_to_repair = fields.Float( + string='Qty To Sell', copy=False, + digits=dp.get_precision('Product Unit of Measure'), + readonly=True, compute=_compute_qty_to_repair, + store=True) + qty_repaired = fields.Float( + string='Qty Sold', copy=False, + digits=dp.get_precision('Product Unit of Measure'), + readonly=True, compute=_compute_qty_repaired, + store=True) + repair_type = fields.Selection(selection=[ + ('no', 'Not required'), ('ordered', 'Based on Ordered Quantities'), + ('received', 'Based on Received Quantities')], + string="Repair Policy", default='no', required=True) + repair_count = fields.Integer( + compute=_compute_repair_count, string='# of Repairs') + + @api.multi + def action_view_repair_order(self): + action = self.env.ref('mrp_repair.action_repair_order_tree') + result = action.read()[0] + result['domain'] = [('id', 'in', self.repair_ids.ids)] + return result + + @api.multi + def _get_rma_repaired_qty(self): + self.ensure_one() + qty = 0.0 + for repair in self.repair_ids.filtered( + lambda p: p.state != 'cancel'): + repair_qty = self.env['product.uom']._compute_qty_obj( + self.uom_id, + repair.product_uom_qty, + repair.product_uom, + ) + qty += repair_qty + return qty diff --git a/rma_repair/views/mrp_repair_view.xml b/rma_repair/views/mrp_repair_view.xml new file mode 100644 index 00000000..3f3bb77b --- /dev/null +++ b/rma_repair/views/mrp_repair_view.xml @@ -0,0 +1,16 @@ + + + + + mrp.repair.form + mrp.repair + + + + + + + + + + diff --git a/rma_repair/views/rma_operation_view.xml b/rma_repair/views/rma_operation_view.xml new file mode 100644 index 00000000..d52fc2d3 --- /dev/null +++ b/rma_repair/views/rma_operation_view.xml @@ -0,0 +1,28 @@ + + + + + + rma.operation.tree + rma.operation + + + + + + + + + + rma.operation.form + rma.operation + + + + + + + + + + diff --git a/rma_repair/views/rma_order_line_view.xml b/rma_repair/views/rma_order_line_view.xml new file mode 100644 index 00000000..439f0c67 --- /dev/null +++ b/rma_repair/views/rma_order_line_view.xml @@ -0,0 +1,52 @@ + + + + + + rma.order.line.form + rma.order.line + + +
+ +
+ + + + + + + + + + + + + + + +
+ + + rma.order.line.form + rma.order.line + + +
+
+
+
+ +
+
diff --git a/rma_repair/views/rma_order_view.xml b/rma_repair/views/rma_order_view.xml new file mode 100644 index 00000000..9ac98e2a --- /dev/null +++ b/rma_repair/views/rma_order_view.xml @@ -0,0 +1,21 @@ + + + + + rma.order.form + rma.order + + +
+ +
+
+
+
+
diff --git a/rma_repair/wizards/__init__.py b/rma_repair/wizards/__init__.py new file mode 100644 index 00000000..40e8ecb8 --- /dev/null +++ b/rma_repair/wizards/__init__.py @@ -0,0 +1,8 @@ +# -*- 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_line_make_sale_order +from . import rma_make_picking +from . import rma_refund +from . import rma_add_sale diff --git a/rma_repair/wizards/rma_add_sale.py b/rma_repair/wizards/rma_add_sale.py new file mode 100644 index 00000000..64c4873a --- /dev/null +++ b/rma_repair/wizards/rma_add_sale.py @@ -0,0 +1,115 @@ +# -*- 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.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 = { + '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_repair/wizards/rma_add_sale.xml b/rma_repair/wizards/rma_add_sale.xml new file mode 100644 index 00000000..2a0c75b7 --- /dev/null +++ b/rma_repair/wizards/rma_add_sale.xml @@ -0,0 +1,80 @@ + + + + + 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 + + + + + + +
diff --git a/rma_repair/wizards/rma_make_picking.py b/rma_repair/wizards/rma_make_picking.py new file mode 100644 index 00000000..848e6a11 --- /dev/null +++ b/rma_repair/wizards/rma_make_picking.py @@ -0,0 +1,21 @@ +# -*- 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 RmaMakePicking(models.TransientModel): + _inherit = 'rma_make_picking.wizard' + + @api.returns('rma.order.line') + def _prepare_item(self, line): + res = super(RmaMakePicking, self)._prepare_item(line) + res['sale_line_id'] = line.sale_line_id.id + return res + + +class RmaMakePickingItem(models.TransientModel): + _inherit = "rma_make_picking.wizard.item" + + sale_line_id = fields.Many2one( + comodel_name='sale.order.line', string='Sale Line') diff --git a/rma_repair/wizards/rma_order_line_make_sale_order.py b/rma_repair/wizards/rma_order_line_make_sale_order.py new file mode 100644 index 00000000..e8eb9f76 --- /dev/null +++ b/rma_repair/wizards/rma_order_line_make_sale_order.py @@ -0,0 +1,153 @@ +# -*- 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 RmaLineMakeSaleOrder(models.TransientModel): + _name = "rma.order.line.make.sale.order" + _description = "Make Sales Order from RMA Line" + + partner_id = fields.Many2one( + comodel_name='res.partner', string='Customer', required=False, + domain=[('supplier', '=', True)]) + item_ids = fields.One2many( + comodel_name='rma.order.line.make.sale.order.item', + inverse_name='wiz_id', string='Items') + sale_order_id = fields.Many2one( + comodel_name='sale.order', string='Sales Order', required=False, + domain=[('state', '=', 'draft')]) + + @api.model + def _prepare_item(self, line): + return { + 'line_id': line.id, + 'rma_line_id': line.id, + 'product_id': line.product_id.id, + 'name': line.product_id.name, + 'product_qty': line.qty_to_sell, + 'rma_id': line.rma_id.id, + 'out_warehouse_id': line.out_warehouse_id.id, + 'out_route_id': line.out_route_id.id, + 'product_uom_id': line.uom_id.id, + } + + @api.model + def default_get(self, fields): + res = super(RmaLineMakeSaleOrder, 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) + for line in lines: + items.append([0, 0, self._prepare_item(line)]) + customers = lines.mapped('partner_id') + if len(customers) == 1: + res['partner_id'] = customers.id + else: + raise exceptions.Warning( + _('Only RMA lines from the same partner can be processed at ' + 'the same time')) + res['item_ids'] = items + return res + + @api.model + def _prepare_sale_order(self, out_warehouse, company): + if not self.partner_id: + raise exceptions.Warning( + _('Enter a customer.')) + customer = self.partner_id + data = { + 'origin': '', + 'partner_id': customer.id, + 'warehouse_id': out_warehouse.id, + 'company_id': company.id, + } + return data + + @api.model + def _prepare_sale_order_line(self, so, item): + product = item.product_id + vals = { + 'name': product.name, + 'order_id': so.id, + 'product_id': product.id, + 'product_uom': product.uom_po_id.id, + 'route_id': item.out_route_id.id, + 'product_uom_qty': item.product_qty, + 'rma_line_id': item.line_id.id + } + if item.free_of_charge: + vals['price_unit'] = 0.0 + return vals + + @api.multi + def make_sale_order(self): + res = [] + sale_obj = self.env['sale.order'] + so_line_obj = self.env['sale.order.line'] + sale = False + + for item in self.item_ids: + line = item.line_id + if item.product_qty <= 0.0: + raise exceptions.Warning( + _('Enter a positive quantity.')) + + if self.sale_order_id: + sale = self.sale_order_id + if not sale: + po_data = self._prepare_sale_order(line.out_warehouse_id, + line.company_id) + sale = sale_obj.create(po_data) + + so_line_data = self._prepare_sale_order_line(sale, item) + so_line_obj.create(so_line_data) + res.append(sale.id) + + return { + 'domain': "[('id','in', ["+','.join(map(str, res))+"])]", + 'name': _('Quotations'), + 'view_type': 'form', + 'view_mode': 'tree,form', + 'res_model': 'sale.order', + 'view_id': False, + 'context': False, + 'type': 'ir.actions.act_window' + } + + +class RmaLineMakeSaleOrderItem(models.TransientModel): + _name = "rma.order.line.make.sale.order.item" + _description = "RMA Line Make Sale Order Item" + + wiz_id = fields.Many2one( + comodel_name='rma.order.line.make.sale.order', string='Wizard', + required=True, readonly=True) + line_id = fields.Many2one( + comodel_name='rma.order.line', string='RMA Line', required=True) + rma_id = fields.Many2one( + comodel_name='rma.order', related='line_id.rma_id', + string='RMA Order', readonly=True) + product_id = fields.Many2one( + comodel_name='product.product', string='Product', readonly=True) + name = fields.Char(string='Description', required=True, readonly=True) + product_qty = fields.Float( + string='Quantity to sell', digits=dp.get_precision('Product UoS')) + product_uom_id = fields.Many2one( + comodel_name='product.uom', string='UoM', readonly=True) + out_warehouse_id = fields.Many2one( + comodel_name='stock.warehouse', string='Outbound Warehouse') + free_of_charge = fields.Boolean(string='Free of Charge') + out_route_id = fields.Many2one( + comodel_name='stock.location.route', string='Outbound Route', + domain=[('rma_selectable', '=', True)]) diff --git a/rma_repair/wizards/rma_order_line_make_sale_order_view.xml b/rma_repair/wizards/rma_order_line_make_sale_order_view.xml new file mode 100644 index 00000000..395e6275 --- /dev/null +++ b/rma_repair/wizards/rma_order_line_make_sale_order_view.xml @@ -0,0 +1,75 @@ + + + + + + RMA Line Make Sale Order + rma.order.line.make.sale.order + form + +
+ + + + /> + + + + + + + + + + + + + + + + + + + + + + +