IMP rma add the ability to relate RMA's to pickings.

Additionally, improve tests to add lines by the wizard instead of manually (especially since manually is not an option in the UI).
This commit is contained in:
Jared Kipe
2018-08-16 14:24:32 -07:00
parent 41d19da933
commit 8b6c6a7ce2
12 changed files with 371 additions and 15 deletions

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from . import controllers
from . import models
from . import wizard

View File

@@ -18,6 +18,7 @@
'security/ir.model.access.csv',
'views/rma_views.xml',
'views/stock_picking_views.xml',
'wizard/rma_lines_views.xml',
],
'demo': [
'data/rma_demo.xml',

View File

@@ -9,4 +9,16 @@
<field name="out_location_dest_id" ref="stock.stock_location_customers"/>
<field name="out_procure_method">make_to_stock</field>
</record>
<record id="template_picking_return" model="rma.template">
<field name="name">Picking Return</field>
<field name="usage">stock_picking</field>
<field name="valid_days" eval="10"/>
<field name="create_in_picking" eval="True"/>
<field name="in_type_id" ref="stock.picking_type_in"/>
<field name="in_location_id" ref="stock.stock_location_customers"/>
<field name="in_location_dest_id" ref="stock.stock_location_stock"/>
<field name="in_procure_method">make_to_stock</field>
<field name="in_require_return" eval="True"/>
</record>
</odoo>

View File

@@ -11,7 +11,9 @@ class RMATemplate(models.Model):
_name = 'rma.template'
name = fields.Char(string='Name')
usage = fields.Selection([], string='Applies To')
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')
@@ -30,7 +32,6 @@ class RMATemplate(models.Model):
('make_to_stock', 'Take from Stock'),
('make_to_order', 'Apply Procurements')
], string="Inbound Procurement Method", default='make_to_stock')
in_to_refund_so = fields.Boolean(string='Inbound mark refund SO')
out_location_id = fields.Many2one('stock.location', string='Outbound Source Location')
out_location_dest_id = fields.Many2one('stock.location', string='Outbound Destination Location')
@@ -103,6 +104,8 @@ class RMA(models.Model):
], string='State', default='draft', copy=False)
company_id = fields.Many2one('res.company', 'Company')
template_id = fields.Many2one('rma.template', string='Type', required=True)
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')
@@ -130,9 +133,18 @@ class RMA(models.Model):
@api.multi
def _onchange_template_usage(self):
now = datetime.now()
for rma in self.filtered(lambda r: r.template_id.valid_days):
rma.validity_date = now + timedelta(days=rma.template_id.valid_days)
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')
@api.multi
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')
@api.multi
@@ -162,6 +174,29 @@ class RMA(models.Model):
str(attachment.id) + '&e=' + str(e_expires) + \
'&h=' + create_hmac(secret, attachment.id, e_expires)
@api.multi
@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
@api.multi
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}
}
@api.model
def create(self, vals):
if vals.get('name', _('New')) == _('New'):
@@ -242,6 +277,17 @@ class RMA(models.Model):
if rma.in_picking_id and rma.in_picking_carrier_id:
rma.in_picking_id.send_to_shipper()
@api.multi
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
@api.multi
def unlink(self):
for rma in self:
@@ -249,6 +295,115 @@ class RMA(models.Model):
raise UserError(_('You can not delete a non-draft RMA.'))
return super(RMA, self).unlink()
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)
values.update({'group_id': group_id})
move_lines = []
for l1, l2, vals in values['move_lines']:
vals.update({'group_id': group_id})
move_lines.append((l1, l2, vals))
values['move_lines'] = move_lines
return self.env['stock.picking'].sudo().create(values)
lines = self.lines.filtered(lambda l: l.product_uom_qty >= 1)
if not lines:
raise UserError(_('You have no lines with positive quantity.'))
old_picking = self.stock_picking_id
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.template_id.in_carrier_id.id if self.template_id.in_carrier_id else 0,
'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)
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]
return_move.copy({
'name': self.name + ' IN: ' + l.product_id.name_get()[0][1],
'product_id': l.product_id.id,
'product_uom_qty': l.product_uom_qty,
'picking_id': new_picking.id,
'state': 'draft',
'location_id': return_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': return_move.id,
'procure_method': self.template_id.in_procure_method,
'move_dest_id': False,
})
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)
values.update({'group_id': group_id})
move_lines = []
for l1, l2, vals in values['move_lines']:
vals.update({'group_id': group_id})
move_lines.append((l1, l2, vals))
values['move_lines'] = move_lines
return self.env['stock.picking'].sudo().create(values)
lines = self.lines.filtered(lambda l: l.product_uom_qty >= 1)
if not lines:
raise UserError(_('You have no lines with positive quantity.'))
old_picking = self.stock_picking_id
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.template_id.out_carrier_id.id if self.template_id.out_carrier_id else 0,
'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)
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]
return_move.copy({
'name': self.name + ' OUT: ' + l.product_id.name_get()[0][1],
'product_id': l.product_id.id,
'product_uom_qty': l.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,
'move_dest_id': False,
})
return new_picking
class RMALine(models.Model):
_name = 'rma.line'

View File

@@ -7,12 +7,14 @@ class TestRMA(common.TransactionCase):
def setUp(self):
super(TestRMA, self).setUp()
self.product1 = self.env.ref('product.product_product_24')
self.template1 = self.env.ref('rma.template_missing_item')
self.template_missing = self.env.ref('rma.template_missing_item')
self.template_return = self.env.ref('rma.template_picking_return')
self.partner1 = self.env.ref('base.res_partner_2')
def test_00_basic_rma(self):
self.template_missing.usage = False
rma = self.env['rma.rma'].create({
'template_id': self.template1.id,
'template_id': self.template_missing.id,
'partner_id': self.partner1.id,
'partner_shipping_id': self.partner1.id,
})
@@ -41,8 +43,9 @@ class TestRMA(common.TransactionCase):
self.assertEqual(rma.state, 'done')
def test_10_rma_cancel(self):
self.template_missing.usage = False
rma = self.env['rma.rma'].create({
'template_id': self.template1.id,
'template_id': self.template_missing.id,
'partner_id': self.partner1.id,
'partner_shipping_id': self.partner1.id,
})
@@ -58,3 +61,65 @@ class TestRMA(common.TransactionCase):
self.assertEqual(rma.out_picking_id.move_lines.state, 'assigned')
rma.action_cancel()
self.assertEqual(rma.out_picking_id.move_lines.state, 'cancel')
def test_20_picking_rma(self):
type_out = self.env.ref('stock.picking_type_out')
location = self.env.ref('stock.stock_location_stock')
location_customer = self.env.ref('stock.stock_location_customers')
self.product1.tracking = 'serial'
picking_out = self.env['stock.picking'].create({
'partner_id': self.partner1.id,
'name': 'testpicking',
'picking_type_id': type_out.id,
'location_id': location.id,
'location_dest_id': location_customer.id,
})
self.env['stock.move'].create({
'name': self.product1.name,
'product_id': self.product1.id,
'product_uom_qty': 1.0,
'product_uom': self.product1.uom_id.id,
'picking_id': picking_out.id,
'location_id': location.id,
'location_dest_id': location_customer.id,
})
picking_out.action_confirm()
# Try to RMA item not delivered yet
rma = self.env['rma.rma'].create({
'template_id': self.template_return.id,
'partner_id': self.partner1.id,
'partner_shipping_id': self.partner1.id,
'stock_picking_id': picking_out.id,
})
self.assertEqual(rma.state, 'draft')
wizard = self.env['rma.picking.make.lines'].create({
'rma_id': rma.id,
})
wizard.line_ids.product_uom_qty = 1.0
wizard.add_lines()
self.assertEqual(len(rma.lines), 1)
with self.assertRaises(UserError):
rma.action_confirm()
picking_out.force_assign()
pack_opt = self.env['stock.pack.operation'].search([('picking_id', '=', picking_out.id)], limit=1)
lot = self.env['stock.production.lot'].create({'product_id': self.product1.id, 'name': 'X100'})
self.env['stock.pack.operation.lot'].create({'operation_id': pack_opt.id, 'lot_id': lot.id, 'qty': 1.0})
pack_opt.qty_done = 1.0
picking_out.do_transfer()
self.assertEqual(picking_out.state, 'done')
rma.action_confirm()
self.assertEqual(rma.in_picking_id.state, 'assigned')
pack_opt = self.env['stock.pack.operation'].search([('picking_id', '=', rma.in_picking_id.id)], limit=1)
self.assertEqual(pack_opt.pack_lot_ids.lot_id, lot)
with self.assertRaises(UserError):
rma.action_done()
pack_opt.pack_lot_ids.qty = 1.0
pack_opt.qty_done = 1.0
rma.in_picking_id.do_transfer()
rma.action_done()

View File

@@ -13,7 +13,12 @@
<field name="state" widget="statusbar" on_change="1" modifiers="{'readonly': true}"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box"/>
<div class="oe_button_box" name="button_box">
<button class="oe_stat_button" name="open_stock_picking_rmas" icon="fa-cubes"
type="object" attrs="{'invisible': ['|', ('stock_picking_id', '=', False), ('stock_picking_rma_count', '&lt;=', 1)]}">
<field name="stock_picking_rma_count" string="Pick RMAs" widget="statinfo" />
</button>
</div>
<div class="oe_title">
<h1>
<field name="name" readonly="1" modifiers="{'readonly': true, 'required': true}"/>
@@ -23,6 +28,9 @@
<group>
<field name="template_usage" invisible="1"/>
<field name="template_id" options="{'no_create': True}" attrs="{'readonly': [('state', 'in', ('confirmed', 'done', 'cancel'))]}"/>
<field name="stock_picking_id" options="{'no_create': True}" attrs="{'invisible': [('template_usage', '!=', 'stock_picking')], 'required': [('template_usage', '=', 'stock_picking')], 'readonly': [('state', 'in', ('confirmed', 'done', 'cancel'))]}"/>
<br/>
<button string="Add lines" type="object" name="action_add_picking_lines" attrs="{'invisible': ['|', ('stock_picking_id', '=', False), ('state', '!=', 'draft')]}"/>
</group>
<group>
<field name="validity_date"/>
@@ -106,6 +114,7 @@
<tree colors="blue:state == 'draft';gray:state in ('cancel', 'done');orange:validity_date and validity_date &lt; current_date;">
<field name="name"/>
<field name="template_id"/>
<field name="stock_picking_id"/>
<field name="partner_id"/>
<field name="create_date"/>
<field name="validity_date"/>
@@ -122,6 +131,7 @@
<field name="name"/>
<field name="partner_id"/>
<field name="template_id"/>
<field name="stock_picking_id"/>
<separator/>
<filter string="New" name="new" domain="[('state', '=', 'draft')]"/>
<filter string="Confirmed" name="confirmed" domain="[('state', '=', 'confirmed')]"/>
@@ -161,7 +171,6 @@
<field name="in_carrier_id" attrs="{'invisible': [('create_in_picking', '=', False)]}"/>
<field name="in_require_return" attrs="{'invisible': [('create_in_picking', '=', False)]}"/>
<field name="in_procure_method" attrs="{'invisible': [('create_in_picking', '=', False)]}"/>
<field name="in_to_refund_so" attrs="{'invisible': [('create_in_picking', '=', False)]}"/>
</group>
<group>
<field name="create_out_picking"/>

2
rma/wizard/__init__.py Normal file
View File

@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import rma_lines

59
rma/wizard/rma_lines.py Normal file
View File

@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models
class RMAPickingMakeLines(models.TransientModel):
_name = 'rma.picking.make.lines'
_description = 'Add Picking Lines'
rma_id = fields.Many2one('rma.rma', string='RMA')
line_ids = fields.One2many('rma.picking.make.lines.line', 'rma_make_lines_id', string='Lines')
@api.model
def create(self, vals):
maker = super(RMAPickingMakeLines, self).create(vals)
maker._create_lines()
return maker
def _line_values(self, move):
return {
'rma_make_lines_id': self.id,
'product_id': move.product_id.id,
'qty_ordered': move.ordered_qty,
'qty_delivered': move.product_uom_qty,
'product_uom_qty': 0.0,
'product_uom_id': move.product_uom.id,
}
def _create_lines(self):
make_lines_obj = self.env['rma.picking.make.lines.line']
if self.rma_id.template_usage == 'stock_picking' and self.rma_id.stock_picking_id:
for l in self.rma_id.stock_picking_id.move_lines:
self.line_ids |= make_lines_obj.create(self._line_values(l))
@api.multi
def add_lines(self):
rma_line_obj = self.env['rma.line']
for o in self:
lines = o.line_ids.filtered(lambda l: l.product_uom_qty > 0.0)
for l in lines:
rma_line_obj.create({
'rma_id': o.rma_id.id,
'product_id': l.product_id.id,
'product_uom_id': l.product_uom_id.id,
'product_uom_qty': l.product_uom_qty,
})
class RMAPickingMakeLinesLine(models.TransientModel):
_name = 'rma.picking.make.lines.line'
rma_make_lines_id = fields.Many2one('rma.picking.make.lines')
product_id = fields.Many2one('product.product', string="Product")
qty_ordered = fields.Float(string='Ordered')
qty_delivered = fields.Float(string='Delivered')
product_uom_qty = fields.Float(string='QTY')
product_uom_id = fields.Many2one('product.uom', 'UOM')

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_rma_add_lines_form" model="ir.ui.view">
<field name="name">view.rma.add.lines.form</field>
<field name="model">rma.picking.make.lines</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form>
<field name="line_ids">
<tree editable="top">
<field name="product_id" readonly="1"/>
<field name="qty_ordered" readonly="1"/>
<field name="qty_delivered" readonly="1"/>
<field name="product_uom_qty"/>
<field name="product_uom_id" readonly="1"/>
</tree>
</field>
<footer>
<button class="oe_highlight"
name="add_lines"
type="object"
string="Add" />
<button class="oe_link"
special="cancel"
string="Cancel" />
</footer>
</form>
</field>
</record>
<record id="action_rma_add_lines" model="ir.actions.act_window">
<field name="name">Add RMA Lines</field>
<field name="res_model">rma.picking.make.lines</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="view_id" ref="view_rma_add_lines_form" />
<field name="target">new</field>
</record>
</odoo>

View File

@@ -8,6 +8,7 @@ class RMATemplate(models.Model):
_inherit = 'rma.template'
usage = fields.Selection(selection_add=[('sale_order', 'Sale Order')])
in_to_refund_so = fields.Boolean(string='Inbound mark refund SO')
class RMA(models.Model):

View File

@@ -6,7 +6,7 @@ class TestRMASale(TestRMA):
def setUp(self):
super(TestRMASale, self).setUp()
self.template2 = self.env.ref('rma_sale.template_sale_return')
self.template_sale_return = self.env.ref('rma_sale.template_sale_return')
def test_20_sale_return(self):
self.product1.tracking = 'serial'
@@ -27,18 +27,18 @@ class TestRMASale(TestRMA):
# Try to RMA item not delivered yet
rma = self.env['rma.rma'].create({
'template_id': self.template2.id,
'template_id': self.template_sale_return.id,
'partner_id': self.partner1.id,
'partner_shipping_id': self.partner1.id,
'sale_order_id': order.id,
})
self.assertEqual(rma.state, 'draft')
rma_line = self.env['rma.line'].create({
wizard = self.env['rma.sale.make.lines'].create({
'rma_id': rma.id,
'product_id': self.product1.id,
'product_uom_id': self.product1.uom_id.id,
'product_uom_qty': 1.0,
})
wizard.line_ids.product_uom_qty = 1.0
wizard.add_lines()
self.assertEqual(len(rma.lines), 1)
with self.assertRaises(UserError):
rma.action_confirm()

View File

@@ -1,5 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- RMA Template -->
<record id="view_rma_template_form_sale" model="ir.ui.view">
<field name="name">rma.template.form.sale</field>
<field name="model">rma.template</field>
<field name="inherit_id" ref="rma.view_rma_template_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='in_procure_method']" position="after">
<field name="in_to_refund_so" attrs="{'invisible': [('create_in_picking', '=', False)]}"/>
</xpath>
</field>
</record>
<!-- RMA -->
<record id="view_rma_rma_form_sale" model="ir.ui.view">
<field name="name">rma.rma.form.sale</field>
<field name="model">rma.rma</field>