Merge pull request #8 from yvaucher/8.0-stock_reserve

8.0 port module stock_reserve
This commit is contained in:
Leonardo Pistone
2014-09-11 15:11:57 +02:00
14 changed files with 446 additions and 318 deletions

View File

@@ -1,40 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Guewen Baconnier
# Copyright 2013 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import orm
class product_product(orm.Model):
_inherit = 'product.product'
def open_stock_reservation(self, cr, uid, ids, context=None):
assert len(ids) == 1, "Expected 1 ID, got %r" % ids
mod_obj = self.pool.get('ir.model.data')
act_obj = self.pool.get('ir.actions.act_window')
get_ref = mod_obj.get_object_reference
__, action_id = get_ref(cr, uid, 'stock_reserve',
'action_stock_reservation')
action = act_obj.read(cr, uid, action_id, context=context)
action['context'] = {'search_default_draft': 1,
'search_default_reserved': 1,
'default_product_id': ids[0],
'search_default_product_id': ids[0]}
return action

View File

@@ -1,181 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Guewen Baconnier
# Copyright 2013 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import orm, fields
from openerp.tools.translate import _
class stock_reservation(orm.Model):
""" Allow to reserve products.
The fields mandatory for the creation of a reservation are:
* product_id
* product_qty
* product_uom
* name
The following fields are required but have default values that you may
want to override:
* company_id
* location_id
* dest_location_id
Optionally, you may be interested to define:
* date_validity (once passed, the reservation will be released)
* note
"""
_name = 'stock.reservation'
_description = 'Stock Reservation'
_inherits = {'stock.move': 'move_id'}
_columns = {
'move_id': fields.many2one('stock.move',
'Reservation Move',
required=True,
readonly=True,
ondelete='cascade',
select=1),
'date_validity': fields.date('Validity Date'),
}
def get_location_from_ref(self, cr, uid, ref, context=None):
""" Get a location from a xmlid if allowed
:param ref: tuple (module, xmlid)
"""
location_obj = self.pool.get('stock.location')
data_obj = self.pool.get('ir.model.data')
get_ref = data_obj.get_object_reference
try:
__, location_id = get_ref(cr, uid, *ref)
location_obj.check_access_rule(cr, uid, [location_id],
'read', context=context)
except (orm.except_orm, ValueError):
location_id = False
return location_id
def _default_location_id(self, cr, uid, context=None):
if context is None:
context = {}
move_obj = self.pool.get('stock.move')
context['picking_type'] = 'internal'
return move_obj._default_location_source(cr, uid, context=context)
def _default_location_dest_id(self, cr, uid, context=None):
ref = ('stock_reserve', 'stock_location_reservation')
return self.get_location_from_ref(cr, uid, ref, context=context)
_defaults = {
'type': 'internal',
'location_id': _default_location_id,
'location_dest_id': _default_location_dest_id,
'product_qty': 1.0,
}
def reserve(self, cr, uid, ids, context=None):
""" Confirm a reservation
The reservation is done using the default UOM of the product.
A date until which the product is reserved can be specified.
"""
move_obj = self.pool.get('stock.move')
reservations = self.browse(cr, uid, ids, context=context)
move_ids = [reserv.move_id.id for reserv in reservations]
move_obj.write(cr, uid, move_ids,
{'date_expected': fields.datetime.now()},
context=context)
move_obj.action_confirm(cr, uid, move_ids, context=context)
move_obj.force_assign(cr, uid, move_ids, context=context)
return True
def release(self, cr, uid, ids, context=None):
if isinstance(ids, (int, long)):
ids = [ids]
reservations = self.read(cr, uid, ids, ['move_id'],
context=context, load='_classic_write')
move_obj = self.pool.get('stock.move')
move_ids = [reserv['move_id'] for reserv in reservations]
move_obj.action_cancel(cr, uid, move_ids, context=context)
return True
def release_validity_exceeded(self, cr, uid, ids=None, context=None):
""" Release all the reservation having an exceeded validity date """
domain = [('date_validity', '<', fields.date.today()),
('state', '=', 'assigned')]
if ids:
domain.append(('id', 'in', ids))
reserv_ids = self.search(cr, uid, domain, context=context)
self.release(cr, uid, reserv_ids, context=context)
return True
def unlink(self, cr, uid, ids, context=None):
""" Release the reservation before the unlink """
self.release(cr, uid, ids, context=context)
return super(stock_reservation, self).unlink(cr, uid, ids,
context=context)
def onchange_product_id(self, cr, uid, ids,
product_id=False,
context=None):
move_obj = self.pool.get('stock.move')
if ids:
reserv = self.read(cr, uid, ids, ['move_id'], context=context,
load='_classic_write')
move_ids = [rv['move_id'] for rv in reserv]
else:
move_ids = []
result = move_obj.onchange_product_id(
cr, uid, move_ids, prod_id=product_id, loc_id=False,
loc_dest_id=False, partner_id=False)
if result.get('value'):
# only keep the existing fields on the view
keep = ('product_uom', 'name')
result['value'] = dict((key, value) for key, value in
result['value'].iteritems() if
key in keep)
return result
def onchange_quantity(self, cr, uid, ids,
product_id,
product_qty,
context=None):
""" On change of product quantity avoid negative quantities """
if not product_id or product_qty <= 0.0:
return {'value': {'product_qty': 0.0}}
return {}
def open_move(self, cr, uid, ids, context=None):
assert len(ids) == 1, "1 ID expected, got %r" % ids
reserv = self.read(cr, uid, ids[0], ['move_id'], context=context,
load='_classic_write')
mod_obj = self.pool.get('ir.model.data')
act_obj = self.pool.get('ir.actions.act_window')
get_ref = mod_obj.get_object_reference
__, action_id = get_ref(cr, uid, 'stock', 'action_move_form2')
action = act_obj.read(cr, uid, action_id, context=context)
action['name'] = _('Reservation Move')
# open directly in the form view
__, view_id = get_ref(cr, uid, 'stock', 'view_move_form')
action['views'] = [(view_id, 'form')]
action['res_id'] = reserv['move_id']
return action

View File

@@ -1,63 +0,0 @@
-
I create a product to test the stock reservation
-
!record {model: product.product, id: product_sorbet}:
default_code: 001SORBET
name: Sorbet
type: product
categ_id: product.product_category_1
list_price: 100.0
standard_price: 70.0
uom_id: product.product_uom_kgm
uom_po_id: product.product_uom_kgm
procure_method: make_to_stock
valuation: real_time
cost_method: average
property_stock_account_input: account.o_expense
property_stock_account_output: account.o_income
-
I update the current stock of the Sorbet with 10 kgm
-
!record {model: stock.change.product.qty, id: change_qty}:
new_quantity: 10
product_id: product_sorbet
-
!python {model: stock.change.product.qty}: |
context['active_id'] = ref('stock_reserve.product_sorbet')
self.change_product_qty(cr, uid, [ref('change_qty')], context=context)
-
I check Virtual stock of Sorbet after update stock.
-
!python {model: product.product}: |
product = self.browse(cr, uid, ref('stock_reserve.product_sorbet'), context=context)
assert product.virtual_available == 10, "Stock is not updated."
-
I create a stock reservation for 5 kgm
-
!record {model: stock.reservation, id: reserv_sorbet1}:
product_id: product_sorbet
product_qty: 5.0
product_uom: product.product_uom_kgm
name: reserve 5 kgm of sorbet for test
-
I confirm the reservation
-
!python {model: stock.reservation}: |
self.reserve(cr, uid, [ref('reserv_sorbet1')], context=context)
-
I check Virtual stock of Sorbet after update reservation
-
!python {model: product.product}: |
product = self.browse(cr, uid, ref('stock_reserve.product_sorbet'), context=context)
assert product.virtual_available == 5, "Stock is not updated."
-
I release the reservation
-
!python {model: stock.reservation}: |
self.release(cr, uid, [ref('reserv_sorbet1')], context=context)
-
I check Virtual stock of Sorbet after update reservation
-
!python {model: product.product}: |
product = self.browse(cr, uid, ref('stock_reserve.product_sorbet'), context=context)
assert product.virtual_available == 10, "Stock is not updated."

View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="0">
<record model="ir.ui.view" id="product_form_view">
<field name="name">product.product.form.reserve</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="procurement.product_form_view_procurement_button"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='buttons']" position="inside">
<button string="Stock Reservations"
name="open_stock_reservation"
type="object"/>
</xpath>
</field>
</record>
</data>
</openerp>

View File

@@ -19,7 +19,8 @@
#
##############################################################################
{'name': 'Stock Reserve',
{'name': 'Stock Reservation',
'summary': 'Stock reservations on products',
'version': '0.1',
'author': 'Camptocamp',
'category': 'Warehouse',
@@ -28,8 +29,8 @@
'images': [],
'website': "http://www.camptocamp.com",
'description': """
Stock Reserve
=============
Stock Reservation
=================
Allows to create stock reservations on products.
@@ -42,6 +43,12 @@ stock below the minimum, the orderpoint will be triggered and new
purchase orders will be generated. It also implies that the max may be
exceeded if the reservations are canceled.
Contributors
------------
* Guewen Baconnier <guewen.baconnier@camptocamp.com>
* Yannick Vaucher <yannick.vaucher@camptocamp.com>
""",
'depends': ['stock',
],
@@ -54,5 +61,5 @@ exceeded if the reservations are canceled.
'auto_install': False,
'test': ['test/stock_reserve.yml',
],
'installable': False,
'installable': True,
}

View File

@@ -0,0 +1,84 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Guewen Baconnier
# Copyright 2013 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp import models, fields, api
class ProductTemplate(models.Model):
_inherit = 'product.template'
reservation_count = fields.Integer(
compute='_reservation_count',
string='# Sales')
@api.multi
def _reservation_count(self):
StockReservation = self.env['stock.reservation']
product_ids = self._get_products()
domain = [('product_id', 'in', product_ids),
('state', 'in', ['draft', 'assigned'])]
reservations = StockReservation.search(domain)
self.reservation_count = sum(reserv.product_uom_qty
for reserv in reservations)
@api.multi
def action_view_reservations(self):
assert len(self._ids) == 1, "Expected 1 ID, got %r" % self._ids
ref = 'stock_reserve.action_stock_reservation_tree'
product_ids = self._get_products()
action_dict = self._get_act_window_dict(ref)
action_dict['domain'] = [('product_id', 'in', product_ids)]
action_dict['context'] = {
'search_default_draft': 1,
'search_default_reserved': 1
}
return action_dict
class ProductProduct(models.Model):
_inherit = 'product.product'
reservation_count = fields.Integer(
compute='_reservation_count',
string='# Sales')
@api.multi
def _reservation_count(self):
StockReservation = self.env['stock.reservation']
product_id = self._ids[0]
domain = [('product_id', '=', product_id),
('state', 'in', ['draft', 'assigned'])]
reservations = StockReservation.search(domain)
self.reservation_count = sum(reserv.product_uom_qty
for reserv in reservations)
@api.multi
def action_view_reservations(self):
assert len(self._ids) == 1, "Expected 1 ID, got %r" % self._ids
ref = 'stock_reserve.action_stock_reservation_tree'
product_id = self._ids[0]
action_dict = self.product_tmpl_id._get_act_window_dict(ref)
action_dict['domain'] = [('product_id', '=', product_id)]
action_dict['context'] = {
'search_default_draft': 1,
'search_default_reserved': 1
}
return action_dict

View File

@@ -0,0 +1,198 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Guewen Baconnier
# Copyright 2013 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp import models, fields, api
from openerp.exceptions import except_orm
from openerp.tools.translate import _
class StockReservation(models.Model):
""" Allow to reserve products.
The fields mandatory for the creation of a reservation are:
* product_id
* product_uom_qty
* product_uom
* name
The following fields are required but have default values that you may
want to override:
* company_id
* location_id
* dest_location_id
Optionally, you may be interested to define:
* date_validity (once passed, the reservation will be released)
* note
"""
_name = 'stock.reservation'
_description = 'Stock Reservation'
_inherits = {'stock.move': 'move_id'}
move_id = fields.Many2one(
'stock.move',
'Reservation Move',
required=True,
readonly=True,
ondelete='cascade',
select=1)
date_validity = fields.Date('Validity Date')
@api.model
def default_get(self, fields_list):
"""
Ensure default value of computed field `product_qty` is not set
as it would raise an error
"""
res = super(StockReservation, self).default_get(fields_list)
if 'product_qty' in res:
del res['product_qty']
return res
@api.model
def get_location_from_ref(self, ref):
""" Get a location from a xmlid if allowed
:param ref: tuple (module, xmlid)
"""
data_obj = self.env['ir.model.data']
try:
location = data_obj.xmlid_to_object(ref, raise_if_not_found=True)
location.check_access_rule('read')
location_id = location.id
except (except_orm, ValueError):
location_id = False
return location_id
@api.model
def _default_picking_type_id(self):
""" Search for an internal picking type
"""
type_obj = self.env['stock.picking.type']
types = type_obj.search([('code', '=', 'internal')], limit=1)
if types:
return types[0].id
return False
@api.model
def _default_location_id(self):
move_obj = self.env['stock.move']
picking_type_id = self._default_picking_type_id()
return (move_obj
.with_context(default_picking_type_id=picking_type_id)
._default_location_source())
@api.model
def _default_location_dest_id(self):
ref = 'stock_reserve.stock_location_reservation'
return self.get_location_from_ref(ref)
_defaults = {
'picking_type_id': _default_picking_type_id,
'location_id': _default_location_id,
'location_dest_id': _default_location_dest_id,
'product_uom_qty': 1.0,
}
@api.multi
def reserve(self):
""" Confirm a reservation
The reservation is done using the default UOM of the product.
A date until which the product is reserved can be specified.
"""
move_recs = self.move_id
move_recs.date_expected = fields.Datetime.now()
move_recs.action_confirm()
move_recs.force_assign()
return True
@api.multi
def release(self):
"""
Release moves from reservation
"""
move_recs = self.move_id
move_recs.action_cancel()
return True
@api.model
def release_validity_exceeded(self, ids=None):
""" Release all the reservation having an exceeded validity date """
domain = [('date_validity', '<', fields.date.today()),
('state', '=', 'assigned')]
if ids:
domain.append(('id', 'in', ids))
reserv_ids = self.search(domain)
reserv_ids.release()
return True
@api.multi
def unlink(self):
""" Release the reservation before the unlink """
self.release()
return super(StockReservation, self).unlink()
@api.onchange('product_id')
def _onchange_product_id(self):
""" set product_uom and name from product onchange """
# save value before reading of self.move_id as this last one erase
# product_id value
product = self.product_id
# WARNING this gettattr erase self.product_id
move = self.move_id
result = move.onchange_product_id(
prod_id=product.id, loc_id=False, loc_dest_id=False,
partner_id=False)
if result.get('value'):
vals = result['value']
# only keep the existing fields on the view
self.name = vals.get('name')
self.product_uom = vals.get('product_uom')
# repeat assignation of product_id so we don't loose it
self.product_id = product.id
@api.onchange('product_uom_qty')
def _onchange_quantity(self):
""" On change of product quantity avoid negative quantities """
if not self.product_id or self.product_uom_qty <= 0.0:
self.product_uom_qty = 0.0
@api.multi
def open_move(self):
assert len(self.ids) == 1, "1 ID expected, got %r" % self.ids
reserv = self.move_id
IrModelData = self.env['ir.model.data']
ref_form2 = 'stock.action_move_form2'
action = IrModelData.xmlid_to_object(ref_form2)
action_dict = action.read()[0]
action_dict['name'] = _('Reservation Move')
# open directly in the form view
ref_form = 'stock.view_move_form'
view_id = IrModelData.xmlid_to_res_id(ref_form)
action_dict.update(
views=[(view_id, 'form')],
res_id=reserv.id,
)
return action_dict

View File

@@ -0,0 +1,114 @@
-
I create a product to test the stock reservation
-
!record {model: product.product, id: product_sorbet}:
default_code: 001SORBET
name: Sorbet
type: product
categ_id: product.product_category_1
list_price: 100.0
standard_price: 70.0
uom_id: product.product_uom_kgm
uom_po_id: product.product_uom_kgm
-
I create a stock orderpoint for the product
-
!record {model: stock.warehouse.orderpoint, id: sorbet_orderpoint}:
warehouse_id: stock.warehouse0
location_id: stock.stock_location_stock
product_id: product_sorbet
product_uom: product.product_uom_kgm
product_min_qty: 4.0
product_max_qty: 15.0
-
I update the current stock of the Sorbet with 10 kgm
-
!record {model: stock.change.product.qty, id: change_qty}:
new_quantity: 10
product_id: product_sorbet
-
!python {model: stock.change.product.qty}: |
context['active_id'] = ref('stock_reserve.product_sorbet')
self.change_product_qty(cr, uid, [ref('change_qty')], context=context)
-
I check Virtual stock of Sorbet after update stock.
-
!python {model: product.product}: |
product = self.browse(cr, uid, ref('stock_reserve.product_sorbet'), context=context)
assert product.virtual_available == 10, "Stock is not updated."
-
I create a stock reservation for 6 kgm
-
!record {model: stock.reservation, id: reserv_sorbet1}:
product_id: product_sorbet
product_uom_qty: 6.0
product_uom: product.product_uom_kgm
name: reserve 6 kg of sorbet for test
-
I confirm the reservation
-
!python {model: stock.reservation}: |
self.reserve(cr, uid, [ref('reserv_sorbet1')], context=context)
-
I create a stock reservation for 500g
-
!record {model: stock.reservation, id: reserv_sorbet2}:
product_id: product_sorbet
product_uom_qty: 500
product_uom: product.product_uom_gram
name: reserve 500g of sorbet for test
-
I confirm the reservation
-
!python {model: stock.reservation}: |
self.reserve(cr, uid, [ref('reserv_sorbet2')], context=context)
-
I check Virtual stock of Sorbet after update reservation
-
!python {model: product.product}: |
product = self.browse(cr, uid, ref('stock_reserve.product_sorbet'), context=context)
assert product.virtual_available == 3.5, "Stock is not updated."
-
I run the scheduler
-
!python {model: procurement.order}: |
self.run_scheduler(cr, uid)
-
The procurement linked to the orderpoint must be in exception (no routes configured)
-
!python {model: stock.warehouse.orderpoint}: |
orderpoint = self.browse(cr, uid, ref('sorbet_orderpoint'))
assert orderpoint.procurement_ids[0].state == 'exception', 'procurement must be in exception as there is no rule for procurement'
import math
assert orderpoint.procurement_ids[0].product_qty == math.ceil(15 - 3.5), 'wrong product qty ordered'
-
I release the reservation
-
!python {model: stock.reservation}: |
self.release(cr, uid, [ref('reserv_sorbet1')], context=context)
-
I check Virtual stock of Sorbet after update reservation
-
!python {model: product.product}: |
product = self.browse(cr, uid, ref('stock_reserve.product_sorbet'), context=context)
assert product.virtual_available == 9.5, "Stock is not updated."
-
I set the validity of the second reservation to yesterday
-
!python {model: stock.reservation}: |
import datetime
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
yesterday = datetime.date.today() - datetime.timedelta(days=1)
yesterday = yesterday.strftime(DEFAULT_SERVER_DATE_FORMAT)
self.write(cr, uid, [ref('reserv_sorbet2')], {'date_validity': yesterday})
-
I call the function releasing expired reservations
-
!python {model: stock.reservation}: |
self.release_validity_exceeded(cr, uid, [])
-
I check Virtual stock of Sorbet after update reservation
-
!python {model: product.product}: |
product = self.browse(cr, uid, ref('stock_reserve.product_sorbet'), context=context)
assert product.virtual_available == 10.0, "Stock is not updated."

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="0">
<record model="ir.ui.view" id="product_template_form_view_reservation_button">
<field name="name">product.template.reservation.button</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_only_form_view"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='buttons']" position="inside">
<button class="oe_inline oe_stat_button" name="action_view_reservations"
type="object" groups="base.group_sale_salesman" icon="fa-lock">
<field string="Stock Reservations" name="reservation_count" widget="statinfo" />
</button>
</xpath>
</field>
</record>
<record model="ir.ui.view" id="product_product_form_view_reservation_button">
<field name="name">product.template.reservation.button</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_normal_form_view"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='buttons']" position="inside">
<button class="oe_inline oe_stat_button" name="action_view_reservations"
type="object" groups="base.group_sale_salesman" icon="fa-lock">
<field string="Stock Reservations" name="reservation_count" widget="statinfo" />
</button>
</xpath>
</field>
</record>
</data>
</openerp>

View File

@@ -23,14 +23,10 @@
<sheet>
<group>
<group name="main_grp" string="Details">
<field name="product_id"
on_change="onchange_product_id(product_id)"
/>
<label for="product_qty" />
<field name="product_id" />
<label for="product_uom_qty" />
<div>
<field name="product_qty"
on_change="onchange_quantity(product_id, product_qty)"
class="oe_inline"/>
<field name="product_uom_qty" class="oe_inline"/>
<field name="product_uom"
groups="product.group_uom" class="oe_inline"/>
</div>
@@ -64,7 +60,7 @@
<field name="name" />
<field name="product_id" />
<field name="move_id" />
<field name="product_qty" sum="Total" />
<field name="product_uom_qty" sum="Total" />
<field name="product_uom" />
<field name="date_validity" />
<field name="state"/>
@@ -112,11 +108,10 @@
</field>
</record>
<record id="action_stock_reservation" model="ir.actions.act_window">
<record id="action_stock_reservation_tree" model="ir.actions.act_window">
<field name="name">Stock Reservations</field>
<field name="res_model">stock.reservation</field>
<field name="type">ir.actions.act_window</field>
<field name="view_type">form</field>
<field name="view_id" ref="view_stock_reservation_tree"/>
<field name="search_view_id" ref="view_stock_reservation_search"/>
<field name="context">{'search_default_draft': 1,
@@ -132,7 +127,7 @@
</field>
</record>
<menuitem action="action_stock_reservation"
<menuitem action="action_stock_reservation_tree"
id="menu_action_stock_reservation"
parent="stock.menu_stock_inventory_control"
sequence="30"/>