diff --git a/rma/README.rst b/rma/README.rst index 4fe6cf07..19617464 100644 --- a/rma/README.rst +++ b/rma/README.rst @@ -86,6 +86,14 @@ Create an RMA: Order". #. Go back to the RMA. Set the RMA to done if not further action is required. +Known issues and Roadmap +======================== + +* Picking operations report in customer RMA dropshipping case is showing + "Vendor Address" while it should be "Customer Address". +* Dropshipping always counted as a delivery on the smart buttons. +* Uninstall hook. +* Constraints instead of required fields on rma.order.line. Bug Tracker =========== diff --git a/rma/__manifest__.py b/rma/__manifest__.py index 66256b0e..7335d0d6 100644 --- a/rma/__manifest__.py +++ b/rma/__manifest__.py @@ -3,7 +3,7 @@ { 'name': 'RMA (Return Merchandise Authorization)', - 'version': '11.0.1.0.0', + 'version': '11.0.2.0.0', 'license': 'LGPL-3', 'category': 'RMA', 'summary': 'Introduces the return merchandise authorization (RMA) process ' diff --git a/rma/data/rma_operation.xml b/rma/data/rma_operation.xml index 27e9be66..9d1fc02f 100755 --- a/rma/data/rma_operation.xml +++ b/rma/data/rma_operation.xml @@ -1,8 +1,8 @@ - + Replace After Receive - RPLC + RPL-C ordered received customer @@ -12,7 +12,7 @@ Replace - RPLS + RPL-S ordered ordered supplier @@ -21,25 +21,25 @@ - Drop Ship - Replace deliver to vendor - DSRPLB + Dropship - Deliver to vendor + DS-RPL-C ordered no customer - True - + + - Drop Ship - Replace deliver to customer - DSRPC - ordered - no + Dropship - Deliver to customer + DS-RPL-S + no + ordered supplier - True - + + diff --git a/rma/data/stock_data.xml b/rma/data/stock_data.xml index ee943875..b9f100bd 100755 --- a/rma/data/stock_data.xml +++ b/rma/data/stock_data.xml @@ -188,7 +188,6 @@ Customer → Supplier move - make_to_stock @@ -200,7 +199,6 @@ Supplier → Customer move - make_to_stock diff --git a/rma/models/rma_operation.py b/rma/models/rma_operation.py index 9e9c12d6..bee15958 100644 --- a/rma/models/rma_operation.py +++ b/rma/models/rma_operation.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017 Eficent Business and IT Consulting Services S.L. +# Copyright 2017-18 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 @@ -23,6 +23,14 @@ class RmaOperation(models.Model): def _default_supplier_location_id(self): return self.env.ref('stock.stock_location_suppliers') or False + @api.model + def _default_routes(self): + op_type = self.env.context.get('default_type') + if op_type == 'customer': + return self.env.ref('rma.route_rma_customer') + elif op_type == 'supplier': + return self.env.ref('rma.route_rma_supplier') + name = fields.Char('Description', required=True) code = fields.Char('Code', required=True) active = fields.Boolean(string='Active', default=True) @@ -35,15 +43,21 @@ class RmaOperation(models.Model): ('received', 'Based on Received Quantities')], string="Delivery Policy", default='no') in_route_id = fields.Many2one( - 'stock.location.route', string='Inbound Route', - domain=[('rma_selectable', '=', True)]) + comodel_name='stock.location.route', string='Inbound Route', + domain=[('rma_selectable', '=', True)], + default=_default_routes, + ) out_route_id = fields.Many2one( - 'stock.location.route', string='Outbound Route', - domain=[('rma_selectable', '=', True)]) + comodel_name='stock.location.route', string='Outbound Route', + domain=[('rma_selectable', '=', True)], + default=_default_routes, + ) customer_to_supplier = fields.Boolean( - 'The customer will send to the supplier', default=False) + string='The customer will send to the supplier', + ) supplier_to_customer = fields.Boolean( - 'The supplier will send to the customer', default=False) + string='The supplier will send to the customer', + ) in_warehouse_id = fields.Many2one( comodel_name='stock.warehouse', string='Inbound Warehouse', default=_default_warehouse_id) @@ -55,5 +69,7 @@ class RmaOperation(models.Model): type = fields.Selection([ ('customer', 'Customer'), ('supplier', 'Supplier')], string="Used in RMA of this type", required=True) - rma_line_ids = fields.One2many('rma.order.line', 'operation_id', - 'RMA lines') + rma_line_ids = fields.One2many( + comodel_name='rma.order.line', inverse_name='operation_id', + string='RMA lines', + ) diff --git a/rma/models/rma_order_line.py b/rma/models/rma_order_line.py index 58287ba7..fceeaa94 100644 --- a/rma/models/rma_order_line.py +++ b/rma/models/rma_order_line.py @@ -46,18 +46,26 @@ class RmaOrderLine(models.Model): @api.multi def _compute_in_shipment_count(self): for line in self: - moves = self.env['stock.move'].search([ - ('rma_line_id', '=', line.id)]) - line.in_shipment_count = len(moves.mapped('picking_id').filtered( - lambda p: p.picking_type_code == 'incoming').ids) + picking_ids = [] + for move in line.move_ids: + if move.location_dest_id.usage == 'internal': + picking_ids.append(move.picking_id.id) + else: + if line.customer_to_supplier: + picking_ids.append(move.picking_id.id) + shipments = list(set(picking_ids)) + line.in_shipment_count = len(shipments) @api.multi def _compute_out_shipment_count(self): + picking_ids = [] for line in self: - moves = self.env['stock.move'].search([ - ('rma_line_id', '=', line.id)]) - line.out_shipment_count = len(moves.mapped('picking_id').filtered( - lambda p: p.picking_type_code == 'outgoing').ids) + for move in line.move_ids: + if move.location_dest_id.usage in ('supplier', 'customer'): + if not line.customer_to_supplier: + picking_ids.append(move.picking_id.id) + shipments = list(set(picking_ids)) + line.out_shipment_count = len(shipments) @api.multi def _get_rma_move_qty(self, states, direction='in'): @@ -82,9 +90,11 @@ class RmaOrderLine(models.Model): for rec in self: rec.qty_to_receive = 0.0 if rec.receipt_policy == 'ordered': - rec.qty_to_receive = rec.product_qty - rec.qty_received + rec.qty_to_receive = \ + rec.product_qty - rec.qty_incoming - rec.qty_received elif rec.receipt_policy == 'delivered': - rec.qty_to_receive = rec.qty_delivered - rec.qty_received + rec.qty_to_receive = \ + rec.qty_delivered - rec.qty_incoming - rec.qty_received @api.multi @api.depends('move_ids', 'move_ids.state', @@ -110,7 +120,10 @@ class RmaOrderLine(models.Model): @api.depends('move_ids', 'move_ids.state', 'type') def _compute_qty_received(self): for rec in self: - qty = rec._get_rma_move_qty('done', direction='in') + if rec.supplier_to_customer: + qty = rec._get_rma_move_qty('done', direction='out') + else: + qty = rec._get_rma_move_qty('done', direction='in') rec.qty_received = qty @api.multi @@ -125,7 +138,10 @@ class RmaOrderLine(models.Model): @api.depends('move_ids', 'move_ids.state', 'type') def _compute_qty_delivered(self): for rec in self: - qty = rec._get_rma_move_qty('done', direction='out') + if rec.supplier_to_customer: + qty = rec._get_rma_move_qty('done', direction='in') + else: + qty = rec._get_rma_move_qty('done', direction='out') rec.qty_delivered = qty @api.model @@ -140,9 +156,26 @@ class RmaOrderLine(models.Model): 'receipt_policy', 'product_qty', 'type') def _compute_qty_supplier_rma(self): for rec in self: - qty = rec._get_supplier_rma_qty() - rec.qty_to_supplier_rma = rec.product_qty - qty - rec.qty_in_supplier_rma = qty + if rec.customer_to_supplier: + supplier_rma_qty = rec._get_supplier_rma_qty() + rec.qty_to_supplier_rma = rec.product_qty - supplier_rma_qty + rec.qty_in_supplier_rma = supplier_rma_qty + else: + rec.qty_to_supplier_rma = 0.0 + rec.qty_in_supplier_rma = 0.0 + + @api.multi + def _compute_procurement_count(self): + for rec in self: + rec.procurement_count = len(rec.procurement_ids.filtered( + lambda p: p.state == 'exception')) + + @api.multi + def _compute_rma_line_count(self): + for rec in self.filtered(lambda r: r.type == 'customer'): + rec.rma_line_count = len(rec.supplier_rma_line_ids) + for rec in self.filtered(lambda r: r.type == 'supplier'): + rec.rma_line_count = len(rec.customer_rma_id) delivery_address_id = fields.Many2one( comodel_name='res.partner', string='Partner delivery address', @@ -180,9 +213,11 @@ class RmaOrderLine(models.Model): ) assigned_to = fields.Many2one( comodel_name='res.users', track_visibility='onchange', + default=lambda self: self.env.uid, ) requested_by = fields.Many2one( comodel_name='res.users', track_visibility='onchange', + default=lambda self: self.env.uid, ) partner_id = fields.Many2one( comodel_name='res.partner', required=True, store=True, @@ -217,10 +252,12 @@ class RmaOrderLine(models.Model): string='Price Unit', readonly=True, states={'draft': [('readonly', False)]}, ) + procurement_count = fields.Integer(compute=_compute_procurement_count, + string='# of Procurements', copy=False) in_shipment_count = fields.Integer(compute=_compute_in_shipment_count, - string='# of Shipments', default=0) + string='# of Shipments') out_shipment_count = fields.Integer(compute=_compute_out_shipment_count, - string='# of Deliveries', default=0) + string='# of Deliveries') move_ids = fields.One2many('stock.move', 'rma_line_id', string='Stock Moves', readonly=True, copy=False) @@ -293,11 +330,21 @@ class RmaOrderLine(models.Model): 'rma.order.line', string='Customer RMA line', ondelete='cascade') supplier_rma_line_ids = fields.One2many( 'rma.order.line', 'customer_rma_id') + rma_line_count = fields.Integer( + compute='_compute_rma_line_count', + string='# of RMA lines associated', + ) supplier_address_id = fields.Many2one( - 'res.partner', readonly=True, + comodel_name='res.partner', readonly=True, states={'draft': [('readonly', False)]}, string='Supplier Address', - help="This address of the supplier in case of Customer RMA operation " + help="Address of the supplier in case of Customer RMA operation " + "dropship.") + customer_address_id = fields.Many2one( + comodel_name='res.partner', readonly=True, + states={'draft': [('readonly', False)]}, + string='Customer Address', + help="Address of the customer in case of Supplier RMA operation " "dropship.") qty_to_receive = fields.Float( string='Qty To Receive', @@ -370,8 +417,9 @@ class RmaOrderLine(models.Model): [('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.")) + raise ValidationError( + _("Please define a warehouse with a default RMA " + "location.")) data = { 'product_id': sm.product_id.id, @@ -397,26 +445,32 @@ class RmaOrderLine(models.Model): @api.onchange('reference_move_id') def _onchange_reference_move_id(self): self.ensure_one() - for move in self.reference_move_id: - data = self._prepare_rma_line_from_stock_move(move, lot=False) + sm = self.reference_move_id + if not sm: + return + if sm.lot_ids: + if len(sm.lot_ids) > 1: + raise UserError(_('To manage lots use RMA groups.')) + else: + data = self._prepare_rma_line_from_stock_move( + sm, lot=sm.lot_ids[0]) + self.update(data) + else: + data = self._prepare_rma_line_from_stock_move( + sm, lot=False) self.update(data) - self._remove_other_data_origin('reference_move_id') - lot_ids = [x.lot_id.id for x in move.move_line_ids if x.lot_id] - return {'domain': {'lot_id': [('id', 'in', lot_ids)]}} + self._remove_other_data_origin('reference_move_id') @api.multi @api.constrains('reference_move_id', 'partner_id') def _check_move_partner(self): for rec in self: if (rec.reference_move_id and - (rec.reference_move_id.partner_id != rec.partner_id) and - (rec.reference_move_id.picking_id.partner_id != - rec.partner_id)): - raise ValidationError(_( - "RMA customer (%s) and originating stock move customer" - " (%s) doesn't match." % ( - rec.reference_move_id.partner_id.name, - rec.partner_id.name))) + rec.reference_move_id.picking_id.partner_id != + rec.partner_id): + raise ValidationError(_( + "RMA customer and originating stock move customer " + "doesn't match.")) @api.multi def _remove_other_data_origin(self, exception): @@ -429,7 +483,6 @@ class RmaOrderLine(models.Model): self.write({'state': 'to_approve'}) for rec in self: if rec.product_id.rma_approval_policy == 'one_step': - rec.write({'assigned_to': self.env.uid}) rec.action_rma_approve() return True @@ -467,16 +520,20 @@ class RmaOrderLine(models.Model): result = {} if not self.product_id: return result - self.product_qty = 1 self.uom_id = self.product_id.uom_id.id self.price_unit = self.product_id.standard_price + if not self.type: + self.type = self._get_default_type() if self.type == 'customer': self.operation_id = self.product_id.rma_customer_operation_id or \ self.product_id.categ_id.rma_customer_operation_id else: self.operation_id = self.product_id.rma_supplier_operation_id or \ self.product_id.categ_id.rma_supplier_operation_id - return result + if self.lot_id.product_id != self.product_id: + self.lot_id = False + return {'domain': { + 'lot_id': [('product_id', '=', self.product_id.id)]}} @api.onchange('operation_id') def _onchange_operation_id(self): @@ -491,10 +548,8 @@ class RmaOrderLine(models.Model): self.in_warehouse_id.lot_rma_id self.customer_to_supplier = self.operation_id.customer_to_supplier self.supplier_to_customer = self.operation_id.supplier_to_customer - if self.operation_id.in_route_id: - self.in_route_id = self.operation_id.in_route_id - if self.operation_id.out_route_id: - self.out_route_id = self.operation_id.out_route_id + self.in_route_id = self.operation_id.in_route_id + self.out_route_id = self.operation_id.out_route_id return result @api.onchange('customer_to_supplier', 'type') @@ -506,43 +561,92 @@ class RmaOrderLine(models.Model): @api.onchange("lot_id") def _onchange_lot_id(self): - if self.lot_id and self.reference_move_id: - data = self._prepare_rma_line_from_stock_move( - self.reference_move_id, lot=self.lot_id) - self.update(data) + product = self.lot_id.product_id + if product: + self.product_id = product + self.uom_id = product.uom_id @api.multi def action_view_in_shipments(self): action = self.env.ref('stock.action_picking_tree_all') result = action.read()[0] - moves = self.env['stock.move'].search([ - ('rma_line_id', 'in', self.ids)]) - picking_ids = moves.mapped('picking_id').filtered( - lambda p: p.picking_type_code == 'incoming').ids - if picking_ids: - # choose the view_mode accordingly - if len(picking_ids) > 1: - result['domain'] = [('id', 'in', picking_ids)] - else: - res = self.env.ref('stock.view_picking_form', False) - result['views'] = [(res and res.id or False, 'form')] - result['res_id'] = picking_ids and picking_ids[0] + picking_ids = [] + for line in self: + for move in line.move_ids: + if move.location_dest_id.usage == 'internal': + picking_ids.append(move.picking_id.id) + else: + if line.customer_to_supplier: + picking_ids.append(move.picking_id.id) + + shipments = list(set(picking_ids)) + # choose the view_mode accordingly + if len(shipments) != 1: + result['domain'] = "[('id', 'in', " + \ + str(shipments) + ")]" + elif len(shipments) == 1: + res = self.env.ref('stock.view_picking_form', False) + result['views'] = [(res and res.id or False, 'form')] + result['res_id'] = shipments[0] return result @api.multi def action_view_out_shipments(self): action = self.env.ref('stock.action_picking_tree_all') result = action.read()[0] - moves = self.env['stock.move'].search([ - ('rma_line_id', 'in', self.ids)]) - picking_ids = moves.mapped('picking_id').filtered( - lambda p: p.picking_type_code == 'outgoing').ids - if picking_ids: - # choose the view_mode accordingly - if len(picking_ids) > 1: - result['domain'] = [('id', 'in', picking_ids)] - else: - res = self.env.ref('stock.view_picking_form', False) - result['views'] = [(res and res.id or False, 'form')] - result['res_id'] = picking_ids and picking_ids[0] + picking_ids = [] + for line in self: + for move in line.move_ids: + if move.location_dest_id.usage in ('supplier', 'customer'): + if not line.customer_to_supplier: + picking_ids.append(move.picking_id.id) + shipments = list(set(picking_ids)) + # choose the view_mode accordingly + if len(shipments) != 1: + result['domain'] = "[('id', 'in', " + \ + str(shipments) + ")]" + elif len(shipments) == 1: + res = self.env.ref('stock.view_picking_form', False) + result['views'] = [(res and res.id or False, 'form')] + result['res_id'] = shipments[0] + return result + + @api.multi + def action_view_procurements(self): + action = self.env.ref( + 'procurement.procurement_order_action_exceptions') + result = action.read()[0] + procurements = self.procurement_ids.filtered( + lambda p: p.state == 'exception').ids + # choose the view_mode accordingly + if len(procurements) != 1: + result['domain'] = "[('id', 'in', " + \ + str(procurements) + ")]" + elif len(procurements) == 1: + res = self.env.ref('procurement.procurement_form_view', False) + result['views'] = [(res and res.id or False, 'form')] + result['res_id'] = procurements[0] + return result + + @api.multi + def action_view_rma_lines(self): + if self.type == 'customer': + # from customer we link to supplier rma + action = self.env.ref( + 'rma.action_rma_supplier_lines') + rma_lines = self.supplier_rma_line_ids.ids + res = self.env.ref('rma.view_rma_line_supplier_form', False) + else: + # from supplier we link to customer rma + action = self.env.ref( + 'rma.action_rma_customer_lines') + rma_lines = self.customer_rma_id.ids + res = self.env.ref('rma.view_rma_line_form', False) + result = action.read()[0] + # choose the view_mode accordingly + if rma_lines and len(rma_lines) != 1: + result['domain'] = rma_lines.ids + elif len(rma_lines) == 1: + result['views'] = [(res and res.id or False, 'form')] + result['res_id'] = rma_lines[0] return result diff --git a/rma/models/stock.py b/rma/models/stock.py index 1ccf52cc..aecb0cff 100644 --- a/rma/models/stock.py +++ b/rma/models/stock.py @@ -34,3 +34,10 @@ class StockMove(models.Model): if group.rma_line_id: vals['rma_line_id'] = group.rma_line_id.id return super(StockMove, self).create(vals) + + def _action_assign(self): + res = super(StockMove, self)._action_assign() + for move in self: + if move.rma_line_id: + move.partner_id = move.rma_line_id.partner_id.id or False + return res diff --git a/rma/models/stock_warehouse.py b/rma/models/stock_warehouse.py index 3111e137..50ba21d8 100644 --- a/rma/models/stock_warehouse.py +++ b/rma/models/stock_warehouse.py @@ -1,21 +1,255 @@ # Copyright (C) 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 +from odoo import _, api, fields, models class StockWarehouse(models.Model): _inherit = "stock.warehouse" - lot_rma_id = fields.Many2one('stock.location', 'RMA Location') - rma_cust_out_type_id = fields.Many2one('stock.picking.type', - 'RMA Customer out Type') - rma_sup_out_type_id = fields.Many2one('stock.picking.type', - 'RMA Supplier out Type') - rma_cust_in_type_id = fields.Many2one('stock.picking.type', - 'RMA Customer in Type') - rma_sup_in_type_id = fields.Many2one('stock.picking.type', - 'RMA Supplier in Type') + lot_rma_id = fields.Many2one( + comodel_name='stock.location', string='RMA Location', + ) # not readonly to have the possibility to edit location and + # propagate to rma rules (add a auto-update when writing this field?) + rma_cust_out_type_id = fields.Many2one( + comodel_name='stock.picking.type', string='RMA Customer out Type', + readonly=True, + ) + rma_sup_out_type_id = fields.Many2one( + comodel_name='stock.picking.type', string='RMA Supplier out Type', + readonly=True, + ) + rma_cust_in_type_id = fields.Many2one( + comodel_name='stock.picking.type', string='RMA Customer in Type', + readonly=True, + ) + rma_sup_in_type_id = fields.Many2one( + comodel_name='stock.picking.type', string='RMA Supplier in Type', + readonly=True, + ) + rma_in_this_wh = fields.Boolean( + string='RMA in this Warehouse', + help="If set, it will create RMA location, picking types and routes " + "for this warehouse.", + ) + rma_customer_in_pull_id = fields.Many2one( + comodel_name='procurement.rule', string="RMA Customer In Rule", + ) + rma_customer_out_pull_id = fields.Many2one( + comodel_name='procurement.rule', string="RMA Customer Out Rule", + ) + rma_supplier_in_pull_id = fields.Many2one( + comodel_name='procurement.rule', string="RMA Supplier In Rule", + ) + rma_supplier_out_pull_id = fields.Many2one( + comodel_name='procurement.rule', string="RMA Supplier Out Rule", + ) + + @api.multi + def _get_rma_types(self): + return [ + self.rma_cust_out_type_id, + self.rma_sup_out_type_id, + self.rma_cust_in_type_id, + self.rma_sup_in_type_id] + + @api.multi + def _rma_types_available(self): + self.ensure_one() + rma_types = self._get_rma_types() + for type in rma_types: + if not type: + return False + return True + + @api.multi + def write(self, vals): + if 'rma_in_this_wh' in vals: + if vals.get("rma_in_this_wh"): + for wh in self: + # RMA location: + if not wh.lot_rma_id: + wh.lot_rma_id = self.env['stock.location'].create({ + 'name': 'RMA', + 'usage': 'internal', + 'location_id': wh.lot_stock_id.id, + }) + # RMA types + if not wh._rma_types_available(): + wh._create_rma_picking_types() + else: + for type in wh._get_rma_types(): + if type: + type.active = True + # RMA rules: + wh._create_or_update_rma_pull() + else: + for wh in self: + for type in wh._get_rma_types(): + if type: + type.active = False + # Unlink rules: + self.mapped('rma_customer_in_pull_id').unlink() + self.mapped('rma_customer_out_pull_id').unlink() + self.mapped('rma_supplier_in_pull_id').unlink() + self.mapped('rma_supplier_out_pull_id').unlink() + return super(StockWarehouse, self).write(vals) + + def _create_rma_picking_types(self): + picking_type_obj = self.env['stock.picking.type'] + customer_loc, supplier_loc = self._get_partner_locations() + for wh in self: + other_pick_type = picking_type_obj.search( + [('warehouse_id', '=', wh.id)], order='sequence desc', + limit=1) + color = other_pick_type.color if other_pick_type else 0 + max_sequence = other_pick_type and other_pick_type.sequence or 0 + # create rma_cust_out_type_id: + rma_cust_out_type_id = picking_type_obj.create({ + 'name': _('Customer RMA Deliveries'), + 'warehouse_id': wh.id, + 'code': 'outgoing', + 'use_create_lots': True, + 'use_existing_lots': False, + 'sequence_id': self.env.ref( + 'rma.seq_picking_type_rma_cust_out').id, + 'default_location_src_id': wh.lot_rma_id.id, + 'default_location_dest_id': customer_loc.id, + 'sequence': max_sequence, + 'color': color, + }) + # create rma_sup_out_type_id: + rma_sup_out_type_id = picking_type_obj.create({ + 'name': _('Supplier RMA Deliveries'), + 'warehouse_id': wh.id, + 'code': 'outgoing', + 'use_create_lots': True, + 'use_existing_lots': False, + 'sequence_id': self.env.ref( + 'rma.seq_picking_type_rma_sup_out').id, + 'default_location_src_id': wh.lot_rma_id.id, + 'default_location_dest_id': supplier_loc.id, + 'sequence': max_sequence, + 'color': color, + }) + # create rma_cust_in_type_id: + rma_cust_in_type_id = picking_type_obj.create({ + 'name': _('Customer RMA Receipts'), + 'warehouse_id': wh.id, + 'code': 'incoming', + 'use_create_lots': True, + 'use_existing_lots': False, + 'sequence_id': self.env.ref( + 'rma.seq_picking_type_rma_cust_in').id, + 'default_location_src_id': customer_loc.id, + 'default_location_dest_id': wh.lot_rma_id.id, + 'sequence': max_sequence, + 'color': color, + }) + # create rma_sup_in_type_id: + rma_sup_in_type_id = picking_type_obj.create({ + 'name': _('Supplier RMA Receipts'), + 'warehouse_id': wh.id, + 'code': 'incoming', + 'use_create_lots': True, + 'use_existing_lots': False, + 'sequence_id': self.env.ref( + 'rma.seq_picking_type_rma_sup_in').id, + 'default_location_src_id': supplier_loc.id, + 'default_location_dest_id': wh.lot_rma_id.id, + 'sequence': max_sequence, + 'color': color, + }) + wh.write({ + 'rma_cust_out_type_id': rma_cust_out_type_id.id, + 'rma_sup_out_type_id': rma_sup_out_type_id.id, + 'rma_cust_in_type_id': rma_cust_in_type_id.id, + 'rma_sup_in_type_id': rma_sup_in_type_id.id, + }) + return True + + @api.multi + def get_rma_rules_dict(self): + self.ensure_one() + rma_rules = dict() + customer_loc, supplier_loc = self._get_partner_locations() + # TODO: company_id? + rma_rules['rma_customer_in'] = { + 'name': self._format_rulename(self, customer_loc, self.lot_rma_id), + 'action': 'move', + 'warehouse_id': self.id, + 'location_src_id': customer_loc.id, + 'location_id': self.lot_rma_id.id, + 'procure_method': 'make_to_stock', + 'route_id': self.env.ref('rma.route_rma_customer').id, + 'picking_type_id': self.rma_cust_in_type_id.id, + 'active': True, + } + rma_rules['rma_customer_out'] = { + 'name': self._format_rulename(self, self.lot_rma_id, customer_loc), + 'action': 'move', + 'warehouse_id': self.id, + 'location_src_id': self.lot_rma_id.id, + 'location_id': customer_loc.id, + 'procure_method': 'make_to_stock', + 'route_id': self.env.ref('rma.route_rma_customer').id, + 'picking_type_id': self.rma_cust_out_type_id.id, + 'active': True, + } + rma_rules['rma_supplier_in'] = { + 'name': self._format_rulename(self, supplier_loc, self.lot_rma_id), + 'action': 'move', + 'warehouse_id': self.id, + 'location_src_id': supplier_loc.id, + 'location_id': self.lot_rma_id.id, + 'procure_method': 'make_to_stock', + 'route_id': self.env.ref('rma.route_rma_supplier').id, + 'picking_type_id': self.rma_sup_in_type_id.id, + 'active': True, + } + rma_rules['rma_supplier_out'] = { + 'name': self._format_rulename(self, self.lot_rma_id, supplier_loc), + 'action': 'move', + 'warehouse_id': self.id, + 'location_src_id': self.lot_rma_id.id, + 'location_id': supplier_loc.id, + 'procure_method': 'make_to_stock', + 'route_id': self.env.ref('rma.route_rma_supplier').id, + 'picking_type_id': self.rma_sup_out_type_id.id, + 'active': True, + } + return rma_rules + + def _create_or_update_rma_pull(self): + rule_obj = self.env['procurement.rule'] + for wh in self: + rules_dict = wh.get_rma_rules_dict() + if wh.rma_customer_in_pull_id: + wh.rma_customer_in_pull_id.write(rules_dict['rma_customer_in']) + else: + wh.rma_customer_in_pull_id = rule_obj.create( + rules_dict['rma_customer_in']) + + if wh.rma_customer_out_pull_id: + wh.rma_customer_out_pull_id.write( + rules_dict['rma_customer_out']) + else: + wh.rma_customer_out_pull_id = rule_obj.create( + rules_dict['rma_customer_out']) + + if wh.rma_supplier_in_pull_id: + wh.rma_supplier_in_pull_id.write(rules_dict['rma_supplier_in']) + else: + wh.rma_supplier_in_pull_id = rule_obj.create( + rules_dict['rma_supplier_in']) + + if wh.rma_supplier_out_pull_id: + wh.rma_supplier_out_pull_id.write( + rules_dict['rma_supplier_out']) + else: + wh.rma_supplier_out_pull_id = rule_obj.create( + rules_dict['rma_supplier_out']) + return True class StockLocationRoute(models.Model): diff --git a/rma/views/product_view.xml b/rma/views/product_view.xml index a2810600..ffb37dcd 100755 --- a/rma/views/product_view.xml +++ b/rma/views/product_view.xml @@ -5,16 +5,17 @@ product.category - - - - - - - + + + + + + + @@ -22,16 +23,18 @@ product.template.stock.property.form.inherit product.template - - - - - - + + + + + + + - - + diff --git a/rma/views/rma_operation_view.xml b/rma/views/rma_operation_view.xml index e4fd4824..5de6a550 100755 --- a/rma/views/rma_operation_view.xml +++ b/rma/views/rma_operation_view.xml @@ -1,87 +1,90 @@ - + - - rma.operation.tree - rma.operation - - - - - - - - - - + + + rma.operation.tree + rma.operation + + + + + + + + + + - - rma.operation.form - rma.operation - -
- - - - - - + + rma.operation.form + rma.operation + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - -
-
+ + + - - Customer Operations - rma.operation - form - tree,form - {'default_type': "customer"} - [('type','=', 'customer')] - - + + Customer Operations + rma.operation + form + tree,form + {'default_type': "customer"} + [('type','=', 'customer')] + + - - Supplier Operations - rma.operation - form - tree,form - {'default_type': "supplier"} - [('type','=', 'supplier')] - - + + Supplier Operations + rma.operation + form + tree,form + {'default_type': "supplier"} + [('type','=', 'supplier')] + + - + - + + +
diff --git a/rma/views/rma_order_line_view.xml b/rma/views/rma_order_line_view.xml index 38093034..08b20fb0 100755 --- a/rma/views/rma_order_line_view.xml +++ b/rma/views/rma_order_line_view.xml @@ -83,6 +83,13 @@ +

@@ -109,7 +116,8 @@ + attrs="{'required': [('product_tracking', 'in', ('serial', 'lot'))]}" + domain="[('product_id', '=', product_id)]"/> @@ -125,32 +133,36 @@ - + - + - - - - - - - - - - - - - - + + + + + + + + + + + @@ -198,7 +210,7 @@ rma.order.line.form rma.order.line -
+
+

@@ -284,37 +303,39 @@ - + - - - - - - - - - - - - - - - - + + + + + + + + + + + + - + @@ -366,6 +387,14 @@ + + + + + + + + diff --git a/rma/wizards/rma_make_picking.py b/rma/wizards/rma_make_picking.py index dfbac614..54fae91d 100644 --- a/rma/wizards/rma_make_picking.py +++ b/rma/wizards/rma_make_picking.py @@ -78,10 +78,12 @@ class RmaMakePicking(models.TransientModel): @api.model def _get_address(self, item): - if item.line_id.delivery_address_id: - delivery_address = item.line_id.delivery_address_id - elif item.line_id.customer_to_supplier: + if item.line_id.customer_to_supplier: delivery_address = item.line_id.supplier_address_id + elif item.line_id.supplier_to_customer: + delivery_address = item.line_id.customer_address_id + elif item.line_id.delivery_address_id: + delivery_address = item.line_id.delivery_address_id elif item.line_id.partner_id: delivery_address = item.line_id.partner_id else: diff --git a/rma/wizards/rma_order_line_make_supplier_rma.py b/rma/wizards/rma_order_line_make_supplier_rma.py index f6def3b6..8a33ca7b 100644 --- a/rma/wizards/rma_order_line_make_supplier_rma.py +++ b/rma/wizards/rma_order_line_make_supplier_rma.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017 Eficent Business and IT Consulting Services S.L. +# Copyright 2017 Eficent Business and IT Consulting Services S.L. # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) import odoo.addons.decimal_precision as dp @@ -22,14 +22,28 @@ class RmaLineMakeSupplierRma(models.TransientModel): comodel_name='rma.order', string='Supplier RMA Order Group', ) + @api.model + def _get_default_operation(self): + """Dropshipping is the most common use case of this wizard, thus + trying to default to a dropshipping operation first.""" + operation = self.env['rma.operation'].search([ + ('type', '=', 'supplier'), + ('supplier_to_customer', '=', True)], limit=1) + if not operation: + operation = self.env['rma.operation'].search( + [('type', '=', 'supplier')], limit=1) + return operation + @api.model def _prepare_item(self, line): + operation = self._get_default_operation() return { 'line_id': line.id, 'product_id': line.product_id.id, 'name': line.name, 'product_qty': line.qty_to_supplier_rma, 'uom_id': line.uom_id.id, + 'operation_id': operation.id if operation else False, } @api.model @@ -48,15 +62,13 @@ class RmaLineMakeSupplierRma(models.TransientModel): lines = rma_line_obj.browse(rma_line_ids) for line in lines: items.append([0, 0, self._prepare_item(line)]) - suppliers = lines.mapped('supplier_address_id') - if len(suppliers) == 0: - pass - elif len(suppliers) == 1: - res['partner_id'] = suppliers.id - else: + suppliers = lines.mapped( + lambda r: r.supplier_address_id.parent_id or r.supplier_address_id) + if len(suppliers) > 1: raise ValidationError( - _('Only RMA lines from the same supplier address can be ' + _('Only RMA lines from the same supplier can be ' 'processed at the same time')) + res['partner_id'] = suppliers.id res['item_ids'] = items return res @@ -66,16 +78,17 @@ class RmaLineMakeSupplierRma(models.TransientModel): raise ValidationError(_('Enter a supplier.')) return { 'partner_id': self.partner_id.id, + 'delivery_address_id': self.partner_id.id, 'type': 'supplier', 'company_id': company.id, } @api.model def _prepare_supplier_rma_line(self, rma, item): - operation = item.line_id.product_id.rma_supplier_operation_id - if not operation: - operation = self.env['rma.operation'].search( - [('type', '=', 'supplier')], limit=1) + if item.operation_id: + operation = item.operation_id + else: + operation = self._get_default_operation() if not operation.in_route_id or not operation.out_route_id: route = self.env['stock.location.route'].search( [('rma_selectable', '=', True)], limit=1) @@ -92,12 +105,13 @@ class RmaLineMakeSupplierRma(models.TransientModel): 'partner_id': self.partner_id.id, 'type': 'supplier', 'origin': item.line_id.rma_id.name, - 'delivery_address_id': - item.line_id.delivery_address_id.id, + 'customer_address_id': + item.line_id.delivery_address_id.id or + item.line_id.partner_id.id, 'product_id': item.line_id.product_id.id, 'customer_rma_id': item.line_id.id, 'product_qty': item.product_qty, - 'rma_id': rma.id, + 'rma_id': rma.id if rma else False, 'uom_id': item.line_id.uom_id.id, 'operation_id': operation.id, 'receipt_policy': operation.receipt_policy, @@ -128,23 +142,34 @@ class RmaLineMakeSupplierRma(models.TransientModel): if self.supplier_rma_id: rma = self.supplier_rma_id - if not rma: + if not rma and len(self.item_ids) > 1: rma_data = self._prepare_supplier_rma(line.company_id) rma = rma_obj.create(rma_data) rma_line_data = self._prepare_supplier_rma_line(rma, item) - rma_line_obj.create(rma_line_data) - - return { - 'name': _('Supplier RMA'), - 'view_type': 'form', - 'view_mode': 'form', - 'res_model': 'rma.order', - 'view_id': False, - 'res_id': rma.id, - 'context': {'supplier': True, 'customer': False}, - 'type': 'ir.actions.act_window' - } + rma_line = rma_line_obj.create(rma_line_data) + if rma: + return { + 'name': _('Supplier RMA'), + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'rma.order', + 'view_id': False, + 'res_id': rma.id, + 'context': {'supplier': True, 'customer': False}, + 'type': 'ir.actions.act_window' + } + else: + return { + 'name': _('Supplier RMA Line'), + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'rma.order.line', + 'view_id': False, + 'res_id': rma_line.id, + 'context': {'supplier': True, 'customer': False}, + 'type': 'ir.actions.act_window' + } class RmaLineMakeRmaOrderItem(models.TransientModel): @@ -167,3 +192,7 @@ class RmaLineMakeRmaOrderItem(models.TransientModel): uom_id = fields.Many2one('product.uom', string='UoM', readonly=True) product_qty = fields.Float(string='Quantity', digits=dp.get_precision('Product UoS')) + operation_id = fields.Many2one( + comodel_name="rma.operation", string="Operation", + domain=[('type', '=', 'supplier')], + ) diff --git a/rma/wizards/rma_order_line_make_supplier_rma_view.xml b/rma/wizards/rma_order_line_make_supplier_rma_view.xml index 1ba47921..25ad8e38 100755 --- a/rma/wizards/rma_order_line_make_supplier_rma_view.xml +++ b/rma/wizards/rma_order_line_make_supplier_rma_view.xml @@ -29,6 +29,7 @@ options="{'no_open': true}"/> +