# Part of Hibou Suite Professional. See LICENSE_PROFESSIONAL file for full copyright and licensing details. from odoo import api, fields, models, _ from odoo.exceptions import UserError, ValidationError from ..controllers.main import create_hmac from datetime import timedelta, datetime from time import mktime class RMATemplate(models.Model): _name = 'rma.template' _description = 'RMA Template' name = fields.Char(string='Name') usage = fields.Selection([ ('stock_picking', 'Stock Picking'), ], string='Applies To') description = fields.Html(string='Internal Instructions') customer_description = fields.Html(string='Customer Instructions') valid_days = fields.Integer(string='Expire in Days') automatic_expire = fields.Boolean('Automatic Expire', help='RMAs with this template will automatically ' 'expire when past their expiration date.') invoice_done = fields.Boolean(string='Invoice on Completion') create_in_picking = fields.Boolean(string='Create Inbound Picking') create_out_picking = fields.Boolean(string='Create Outbound Picking') in_type_id = fields.Many2one('stock.picking.type', string='Inbound Picking Type') out_type_id = fields.Many2one('stock.picking.type', string='Outbound Picking Type') in_location_id = fields.Many2one('stock.location', string='Inbound Source Location') in_location_dest_id = fields.Many2one('stock.location', string='Inbound Destination Location') in_carrier_id = fields.Many2one('delivery.carrier', string='Inbound Carrier') in_require_return = fields.Boolean(string='Inbound Require return of picking') in_procure_method = fields.Selection([ ('make_to_stock', 'Take from Stock'), ('make_to_order', 'Apply Procurements') ], string="Inbound Procurement Method", default='make_to_stock') in_to_refund = fields.Boolean(string='Inbound Mark Refund') out_location_id = fields.Many2one('stock.location', string='Outbound Source Location') out_location_dest_id = fields.Many2one('stock.location', string='Outbound Destination Location') out_carrier_id = fields.Many2one('delivery.carrier', string='Outbound Carrier') out_require_return = fields.Boolean(string='Outbound Require picking to duplicate') out_procure_method = fields.Selection([ ('make_to_stock', 'Take from Stock'), ('make_to_order', 'Apply Procurements') ], string="Outbound Procurement Method", default='make_to_stock') out_to_refund = fields.Boolean(string='Outbound Mark Refund') portal_ok = fields.Boolean(string='Allow on Portal') company_id = fields.Many2one('res.company', 'Company') responsible_user_ids = fields.Many2many('res.users', string='Responsible Users', help='Users that get activities when creating RMA.') next_rma_template_id = fields.Many2one('rma.template', string='Next RMA Template') def _portal_try_create(self, request_user, res_id, **kw): if self.usage == 'stock_picking': prefix = 'move_' move_map = {int(key[len(prefix):]): float(kw[key]) for key in kw if key.find(prefix) == 0 and kw[key]} if move_map: picking = self.env['stock.picking'].browse(res_id) if picking.partner_id != request_user.partner_id: raise ValidationError('Invalid user for picking.') lines = [] for move_id, qty in move_map.items(): move = picking.move_lines.filtered(lambda l: l.id == move_id) if move: if not qty: continue if qty < 0.0 or move.quantity_done < qty: raise ValidationError('Invalid quantity.') lines.append((0, 0, { 'product_id': move.product_id.id, 'product_uom_id': move.product_uom.id, 'product_uom_qty': qty, })) if not lines: raise ValidationError('Missing product quantity.') rma = self.env['rma.rma'].create({ 'name': _('New'), 'stock_picking_id': picking.id, 'template_id': self.id, 'partner_id': request_user.partner_id.id, 'partner_shipping_id': request_user.partner_id.id, 'lines': lines, }) return rma def _portal_template(self, res_id=None): if self.usage == 'stock_picking': return 'rma.portal_new_stock_picking' def _portal_values(self, request_user, res_id=None): if self.usage == 'stock_picking': picking = None pickings = None if res_id: picking = self.env['stock.picking'].browse(res_id) if picking.partner_id != request_user.partner_id: picking = None else: pickings = self.env['stock.picking'].search([('partner_id', '=', request_user.partner_id.id)], limit=100) return { 'rma_template': self, 'pickings': pickings, 'picking': picking, } def _values_for_in_picking(self, rma): return { 'origin': rma.name, 'partner_id': rma.partner_shipping_id.id, 'picking_type_id': self.in_type_id.id, 'location_id': self.in_location_id.id, 'location_dest_id': self.in_location_dest_id.id, 'carrier_id': rma.initial_in_picking_carrier_id.id, 'move_lines': [(0, None, { 'name': rma.name + ' IN: ' + l.product_id.name_get()[0][1], 'product_id': l.product_id.id, 'product_uom_qty': l.product_uom_qty, 'product_uom': l.product_uom_id.id, 'procure_method': self.in_procure_method, 'to_refund': self.in_to_refund, }) for l in rma.lines.filtered(lambda l: l.product_id.type != 'service')], } def _values_for_out_picking(self, rma): return { 'origin': rma.name, 'partner_id': rma.partner_shipping_id.id, 'picking_type_id': self.out_type_id.id, 'location_id': self.out_location_id.id, 'location_dest_id': self.out_location_dest_id.id, 'carrier_id': rma.initial_out_picking_carrier_id.id, 'move_lines': [(0, None, { 'name': rma.name + ' OUT: ' + l.product_id.name_get()[0][1], 'product_id': l.product_id.id, 'product_uom_qty': l.product_uom_qty, 'product_uom': l.product_uom_id.id, 'procure_method': self.out_procure_method, 'to_refund': self.out_to_refund, }) for l in rma.lines.filtered(lambda l: l.product_id.type != 'service')], } def _schedule_responsible_activities(self, rma): model_id = self.env['ir.model']._get(rma._name).id activity_to_write = [] for user in self.responsible_user_ids: if rma.with_user(user).check_access_rights('read', raise_exception=False): activity_to_write.append((0, 0, { 'res_id': rma.id, 'res_model_id': model_id, 'summary': 'Review New RMA', 'activity_type_id': False, 'user_id': user.id, })) if activity_to_write: rma.write({ 'activity_ids': activity_to_write, }) def _rma_expire(self): templates = self.sudo().search([('automatic_expire', '=', True)]) if not templates: return True rmas = self.env['rma.rma'].sudo().search([ ('template_id', 'in', templates.ids), ('state', 'in', ('draft', 'confirmed',)), ('validity_date', '<', fields.Datetime.now()) ]) if rmas: return rmas._action_expire() return True class RMATag(models.Model): _name = "rma.tag" _description = "RMA Tag" name = fields.Char('Tag Name', required=True) color = fields.Integer('Color Index') _sql_constraints = [ ('name_uniq', 'unique (name)', "Tag name already exists !"), ] class RMA(models.Model): _name = 'rma.rma' _inherit = ['portal.mixin', 'mail.thread', 'mail.activity.mixin'] _description = 'RMA' _order = 'id desc' _mail_post_access = 'read' name = fields.Char(string='Number', copy=False) state = fields.Selection([ ('draft', 'New'), ('confirmed', 'Confirmed'), ('done', 'Done'), ('expired', 'Expired'), ('cancel', 'Cancelled'), ], string='State', default='draft', copy=False) company_id = fields.Many2one('res.company', 'Company') parent_id = fields.Many2one('rma.rma') template_id = fields.Many2one('rma.template', string='Type', required=True) template_create_in_picking = fields.Boolean(related='template_id.create_in_picking') template_create_out_picking = fields.Boolean(related='template_id.create_out_picking') stock_picking_id = fields.Many2one('stock.picking', string='Stock Picking') stock_picking_rma_count = fields.Integer('Number of RMAs for this Picking', compute='_compute_stock_picking_rma_count') partner_id = fields.Many2one('res.partner', string='Partner') partner_shipping_id = fields.Many2one('res.partner', string='Shipping') lines = fields.One2many('rma.line', 'rma_id', string='Lines') tag_ids = fields.Many2many('rma.tag', 'rma_tags_rel', 'rma_id', 'tag_id', string='Tags') description = fields.Html(string='Internal Instructions', related='template_id.description') customer_description = fields.Html(string='Customer Instructions', related='template_id.customer_description') template_usage = fields.Selection(string='Template Usage', related='template_id.usage') validity_date = fields.Datetime(string='Expiration Date') claim_number = fields.Char(string='Claim Number') invoice_ids = fields.Many2many('account.move', 'rma_invoice_rel', 'rma_id', 'invoice_id', string='Invoices') initial_in_picking_carrier_id = fields.Many2one('delivery.carrier', string='In Delivery Method') initial_out_picking_carrier_id = fields.Many2one('delivery.carrier', string='Out Delivery Method') in_picking_id = fields.Many2one('stock.picking', string='Inbound Picking', copy=False) out_picking_id = fields.Many2one('stock.picking', string='Outbound Picking', copy=False) in_picking_state = fields.Selection(string='In Picking State', related='in_picking_id.state') out_picking_state = fields.Selection(string='Out Picking State', related='out_picking_id.state') in_picking_carrier_id = fields.Many2one('delivery.carrier', related='in_picking_id.carrier_id', readonly=False) out_picking_carrier_id = fields.Many2one('delivery.carrier', related='out_picking_id.carrier_id', readonly=False) in_carrier_tracking_ref = fields.Char(related='in_picking_id.carrier_tracking_ref') in_label_url = fields.Char(compute='_compute_in_label_url') out_carrier_tracking_ref = fields.Char(related='out_picking_id.carrier_tracking_ref') def _compute_access_url(self): super(RMA, self)._compute_access_url() for rma in self: rma.access_url = '/my/rma/%s' % (rma.id) @api.onchange('template_id') def _onchange_template_id(self): for rma in self: rma.initial_in_picking_carrier_id = rma.template_id.in_carrier_id rma.initial_out_picking_carrier_id = rma.template_id.out_carrier_id @api.onchange('template_usage') def _onchange_template_usage(self): now = datetime.now() for rma in self: if rma.template_id.valid_days: rma.validity_date = now + timedelta(days=rma.template_id.valid_days) if rma.template_usage != 'stock_picking': rma.stock_picking_id = False @api.onchange('stock_picking_id') def _onchange_stock_picking_id(self): for rma in self.filtered(lambda rma: rma.stock_picking_id): rma.partner_id = rma.stock_picking_id.partner_id rma.partner_shipping_id = rma.stock_picking_id.partner_id @api.onchange('in_carrier_tracking_ref', 'validity_date') def _compute_in_label_url(self): config = self.env['ir.config_parameter'].sudo() secret = config.search([('key', '=', 'database.secret')], limit=1) secret = str(secret.value) if secret else '' base_url = config.search([('key', '=', 'web.base.url')], limit=1) base_url = str(base_url.value) if base_url else '' for rma in self: if not rma.in_picking_id: rma.in_label_url = '' continue if rma.validity_date: e_expires = int(mktime(fields.Datetime.from_string(rma.validity_date).timetuple())) else: year = datetime.now() + timedelta(days=365) e_expires = int(mktime(year.timetuple())) attachment = self.env['ir.attachment'].search([ ('res_model', '=', 'stock.picking'), ('res_id', '=', rma.in_picking_id.id), ('name', 'like', 'Label%')], limit=1) if not attachment: rma.in_label_url = '' continue rma.in_label_url = base_url + '/rma_label?a=' + \ str(attachment.id) + '&e=' + str(e_expires) + \ '&h=' + create_hmac(secret, attachment.id, e_expires) @api.depends('stock_picking_id') def _compute_stock_picking_rma_count(self): for rma in self: if rma.stock_picking_id: rma_data = self.read_group([('stock_picking_id', '=', rma.stock_picking_id.id), ('state', '!=', 'cancel')], ['stock_picking_id'], ['stock_picking_id']) if rma_data: rma.stock_picking_rma_count = rma_data[0]['stock_picking_id_count'] else: rma.stock_picking_rma_count = 0.0 else: rma.stock_picking_rma_count = 0.0 def open_stock_picking_rmas(self): return { 'type': 'ir.actions.act_window', 'name': _('Picking RMAs'), 'res_model': 'rma.rma', 'view_mode': 'tree,form', 'context': {'search_default_stock_picking_id': self[0].stock_picking_id.id} } def _action_expire(self): pickings_to_cancel = self.env['stock.picking'] rmas = self.filtered(lambda rma: rma.in_picking_state != 'done' and rma.out_picking_state != 'done') pickings_to_cancel += rmas.filtered(lambda r: r.in_picking_id).mapped('in_picking_id') pickings_to_cancel += rmas.filtered(lambda r: r.out_picking_id).mapped('out_picking_id') pickings_to_cancel.action_cancel() rmas.write({'state': 'expired'}) return True @api.model def create(self, vals): if vals.get('name', _('New')) == _('New'): if 'company_id' in vals: vals['name'] = self.env['ir.sequence'].with_context(force_company=vals['company_id']).next_by_code('rma.rma') or _('New') else: vals['name'] = self.env['ir.sequence'].next_by_code('rma.rma') or _('New') # Provide defaults on create (e.g. from portal) if vals.get('template_id'): template = self.env['rma.template'].browse(vals.get('template_id')) if 'initial_in_picking_carrier_id' not in vals: vals['initial_in_picking_carrier_id'] = template.in_carrier_id.id if 'initial_out_picking_carrier_id' not in vals: vals['initial_out_picking_carrier_id'] = template.out_carrier_id.id if template.valid_days and 'validity_date' not in vals: now = datetime.now() vals['validity_date'] = now + timedelta(days=template.valid_days) result = super(RMA, self).create(vals) result.template_id._schedule_responsible_activities(result) return result def action_confirm(self): for rma in self: in_picking_id = False out_picking_id = False if any((not rma.template_id, not rma.lines, not rma.partner_id, not rma.partner_shipping_id)): raise UserError(_('You can only confirm RMAs with lines, and partner information.')) if rma.template_id.create_in_picking: in_picking_id = rma._create_in_picking() if in_picking_id: in_picking_id.action_confirm() in_picking_id.action_assign() if rma.template_id.create_out_picking: out_picking_id = rma._create_out_picking() if out_picking_id: out_picking_id.action_confirm() out_picking_id.action_assign() rma.write({'state': 'confirmed', 'in_picking_id': in_picking_id.id if in_picking_id else False, 'out_picking_id': out_picking_id.id if out_picking_id else False}) def _next_rma_values(self): return { 'template_id': self.template_id.next_rma_template_id.id, # Partners should be set when confirming or using the RTV wizard # 'partner_id': self.partner_id.id, # 'partner_shipping_id': self.partner_shipping_id.id, 'parent_id': self.id, 'lines': [(0, 0, { 'product_id': l.product_id.id, 'product_uom_id': l.product_uom_id.id, 'product_uom_qty': l.product_uom_qty, }) for l in self.lines] } def _next_rma(self): if self.template_id.next_rma_template_id: # currently we do not want to automatically confirm them # this is because we want to mass confirm and set picking to one partner/vendor _ = self.create(self._next_rma_values()) def action_done(self): for rma in self: if rma.in_picking_id and rma.in_picking_id.state not in ('done', 'cancel'): raise UserError(_('Inbound picking not complete or cancelled.')) if rma.out_picking_id and rma.out_picking_id.state not in ('done', 'cancel'): raise UserError(_('Outbound picking not complete or cancelled.')) self.write({'state': 'done'}) self._done_invoice() self._next_rma() def _done_invoice(self): for rma in self.filtered(lambda r: r.template_id.invoice_done): # If you do NOT want to take part in the default invoicing functionality # then your usage method (e.g. _invoice_values_sale_order) should be # defined, and return nothing or extend _invoice_values to do the same usage = rma.template_usage or '' if hasattr(rma, '_invoice_values_' + usage): values = getattr(rma, '_invoice_values_' + usage)() else: values = rma._invoice_values() if values: if hasattr(rma, '_invoice_' + usage): getattr(rma, '_invoice_' + usage)(values) else: rma._invoice(values) def _invoice(self, invoice_values): self.invoice_ids += self.env['account.move'].with_context(default_move_type=invoice_values['move_type']).create( invoice_values) def _invoice_values(self): self.ensure_one() # special case for vendor return supplier = self._context.get('rma_supplier') if supplier is None and self.out_picking_id and self.out_picking_id.location_dest_id.usage == 'supplier': supplier = True fiscal_position_id = self.env['account.fiscal.position'].get_fiscal_position( self.partner_id.id, delivery_id=self.partner_shipping_id.id) invoice_values = { 'move_type': 'in_refund' if supplier else 'out_refund', 'partner_id': self.partner_id.id, 'fiscal_position_id': fiscal_position_id, } line_commands = [] for rma_line in self.lines: product = rma_line.product_id accounts = product.product_tmpl_id.get_product_accounts() account = accounts['expense'] if supplier else accounts['income'] qty = rma_line.product_uom_qty uom = rma_line.product_uom_id price = product.standard_price if supplier else product.lst_price if uom != product.uom_id: price = product.uom_id._compute_price(price, uom) line_commands.append((0, 0, { 'product_id': product.id, 'product_uom_id': uom.id, 'name': product.name, 'price_unit': price, 'quantity': qty, 'account_id': account.id, 'tax_ids': [(6, 0, product.taxes_id.ids)], })) if line_commands: invoice_values['invoice_line_ids'] = line_commands return invoice_values def action_cancel(self): for rma in self: rma.in_picking_id.action_cancel() rma.out_picking_id.action_cancel() self.write({'state': 'cancel'}) def action_draft(self): self.filtered(lambda l: l.state in ('cancel', 'expired')).write({ 'state': 'draft', 'in_picking_id': False, 'out_picking_id': False}) def _create_in_picking(self): if self._context.get('rma_in_picking_id'): # allow passing/setting by context to allow many RMA's to include the same pickings return self.env['stock.picking'].browse(self._context.get('rma_in_picking_id')) if self.template_usage and hasattr(self, '_create_in_picking_' + self.template_usage): return getattr(self, '_create_in_picking_' + self.template_usage)() values = self.template_id._values_for_in_picking(self) return self.env['stock.picking'].sudo().create(values) def _create_out_picking(self): if self._context.get('rma_out_picking_id'): # allow passing/setting by context to allow many RMA's to include the same pickings return self.env['stock.picking'].browse(self._context.get('rma_out_picking_id')) if self.template_usage and hasattr(self, '_create_out_picking_' + self.template_usage): return getattr(self, '_create_out_picking_' + self.template_usage)() values = self.template_id._values_for_out_picking(self) return self.env['stock.picking'].sudo().create(values) def _find_candidate_return_picking(self, product_ids, pickings, location_id): done_pickings = pickings.filtered(lambda p: p.state == 'done' and p.location_dest_id.id == location_id) for p in done_pickings: p_product_ids = p.move_lines.filtered(lambda l: l.state == 'done').mapped('product_id.id') if set(product_ids) & set(p_product_ids) == set(product_ids): return p return None def action_in_picking_send_to_shipper(self): for rma in self: if rma.in_picking_id and rma.in_picking_carrier_id: rma.in_picking_id.send_to_shipper() def action_add_picking_lines(self): make_line_obj = self.env['rma.picking.make.lines'] for rma in self: lines = make_line_obj.create({ 'rma_id': rma.id, }) action = self.env.ref('rma.action_rma_add_lines').read()[0] action['res_id'] = lines.id return action def unlink(self): for rma in self: if rma.state not in ('draft'): raise UserError(_('You can not delete a non-draft RMA.')) return super(RMA, self).unlink() def _picking_from_values(self, values, values_update, move_line_values_update): values.update(values_update) move_lines = [] for l1, l2, vals in values['move_lines']: vals.update(move_line_values_update) move_lines.append((l1, l2, vals)) values['move_lines'] = move_lines return self.env['stock.picking'].sudo().create(values) def _new_in_picking(self, old_picking): new_picking = old_picking.copy({ 'move_lines': [], 'picking_type_id': self.template_id.in_type_id.id, 'state': 'draft', 'origin': old_picking.name + ' ' + self.name, 'location_id': self.template_id.in_location_id.id, 'location_dest_id': self.template_id.in_location_dest_id.id, 'carrier_id': self.initial_in_picking_carrier_id.id, 'carrier_tracking_ref': False, 'carrier_price': False }) new_picking.message_post_with_view('mail.message_origin_link', values={'self': new_picking, 'origin': self}, subtype_id=self.env.ref('mail.mt_note').id) return new_picking def _new_in_move_vals(self, rma_line, new_picking, old_move): return { 'name': self.name + ' IN: ' + rma_line.product_id.name_get()[0][1], 'product_id': rma_line.product_id.id, 'product_uom_qty': rma_line.product_uom_qty, 'product_uom': rma_line.product_uom_id.id, 'picking_id': new_picking.id, 'state': 'draft', 'location_id': old_move.location_dest_id.id, 'location_dest_id': self.template_id.in_location_dest_id.id, 'picking_type_id': new_picking.picking_type_id.id, 'warehouse_id': new_picking.picking_type_id.warehouse_id.id, 'origin_returned_move_id': old_move.id, 'procure_method': self.template_id.in_procure_method, 'to_refund': self.template_id.in_to_refund, } def _new_in_moves(self, old_picking, new_picking, move_update): lines = self.lines.filtered(lambda l: l.product_uom_qty >= 1) if not lines: raise UserError(_('You have no lines with positive quantity.')) moves = self.env['stock.move'] for l in lines: return_move = old_picking.move_lines.filtered(lambda ol: ol.state == 'done' and ol.product_id.id == l.product_id.id)[0] copy_vals = self._new_in_move_vals(l, new_picking, return_move) copy_vals.update(move_update) r = return_move.copy(copy_vals) vals = {} # +--------------------------------------------------------------------------------------------------------+ # | picking_pick <--Move Orig-- picking_pack --Move Dest--> picking_ship # | | returned_move_ids ↑ | returned_move_ids # | ↓ | return_line.move_id ↓ # | return pick(Add as dest) return toLink return ship(Add as orig) # +--------------------------------------------------------------------------------------------------------+ move_orig_to_link = return_move.move_dest_ids.mapped('returned_move_ids') move_dest_to_link = return_move.move_orig_ids.mapped('returned_move_ids') vals['move_orig_ids'] = [(4, m.id) for m in move_orig_to_link | return_move] vals['move_dest_ids'] = [(4, m.id) for m in move_dest_to_link] r.write(vals) moves += r return moves def _new_out_picking(self, old_picking): new_picking = old_picking.copy({ 'move_lines': [], 'picking_type_id': self.template_id.out_type_id.id, 'state': 'draft', 'origin': old_picking.name + ' ' + self.name, 'location_id': self.template_id.out_location_id.id, 'location_dest_id': self.template_id.out_location_dest_id.id, 'carrier_id': self.initial_out_picking_carrier_id.id, 'carrier_tracking_ref': False, 'carrier_price': False }) new_picking.message_post_with_view('mail.message_origin_link', values={'self': new_picking, 'origin': self}, subtype_id=self.env.ref('mail.mt_note').id) return new_picking def _new_out_move_vals(self, rma_line, new_picking, old_move): return { 'name': self.name + ' OUT: ' + rma_line.product_id.name_get()[0][1], 'product_id': rma_line.product_id.id, 'product_uom_qty': rma_line.product_uom_qty, 'picking_id': new_picking.id, 'state': 'draft', 'location_id': self.template_id.out_location_id.id, 'location_dest_id': self.template_id.out_location_dest_id.id, 'picking_type_id': new_picking.picking_type_id.id, 'warehouse_id': new_picking.picking_type_id.warehouse_id.id, 'origin_returned_move_id': False, 'procure_method': self.template_id.out_procure_method, 'to_refund': self.template_id.out_to_refund, } def _new_out_moves(self, old_picking, new_picking, move_update): lines = self.lines.filtered(lambda l: l.product_uom_qty >= 1) if not lines: raise UserError(_('You have no lines with positive quantity.')) moves = self.env['stock.move'] for l in lines: return_move = old_picking.move_lines.filtered(lambda ol: ol.state == 'done' and ol.product_id.id == l.product_id.id)[0] copy_vals = self._new_out_move_vals(l, new_picking, return_move) copy_vals.update(move_update) moves += return_move.copy(copy_vals) return moves def _create_in_picking_stock_picking(self): if not self.stock_picking_id or self.stock_picking_id.state != 'done': raise UserError(_('You must have a completed stock picking for this RMA.')) if not self.template_id.in_require_return: group_id = self.stock_picking_id.group_id.id if self.stock_picking_id.group_id else 0 values = self.template_id._values_for_in_picking(self) update = {'group_id': group_id} return self._picking_from_values(values, update, update) old_picking = self.stock_picking_id new_picking = self._new_in_picking(old_picking) self._new_in_moves(old_picking, new_picking, {}) return new_picking def _create_out_picking_stock_picking(self): if not self.stock_picking_id or self.stock_picking_id.state != 'done': raise UserError(_('You must have a completed stock picking for this RMA.')) if not self.template_id.out_require_return: group_id = self.stock_picking_id.group_id.id if self.stock_picking_id.group_id else 0 values = self.template_id._values_for_out_picking(self) update = {'group_id': group_id} return self._picking_from_values(values, update, update) old_picking = self.stock_picking_id new_picking = self._new_out_picking(old_picking) self._new_out_moves(old_picking, new_picking, {}) return new_picking class RMALine(models.Model): _name = 'rma.line' _description = 'RMA Line' rma_id = fields.Many2one('rma.rma', string='RMA') product_id = fields.Many2one('product.product', 'Product') product_uom_id = fields.Many2one('uom.uom', 'UOM') product_uom_qty = fields.Float(string='QTY') rma_template_usage = fields.Selection(related='rma_id.template_usage') @api.onchange('product_id') def _onchange_product_id(self): for line in self: line.product_uom_id = line.product_id.uom_id