diff --git a/stock_reserve/__init__.py b/stock_reserve/__init__.py
new file mode 100644
index 000000000..643bee7ab
--- /dev/null
+++ b/stock_reserve/__init__.py
@@ -0,0 +1,22 @@
+# -*- 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 .
+#
+##############################################################################
+
+from . import model
diff --git a/stock_reserve/__openerp__.py b/stock_reserve/__openerp__.py
new file mode 100644
index 000000000..8b3d84116
--- /dev/null
+++ b/stock_reserve/__openerp__.py
@@ -0,0 +1,47 @@
+# -*- 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 .
+#
+##############################################################################
+
+{'name': 'Stock Reserve',
+ 'version': '0.1',
+ 'author': 'Camptocamp',
+ 'category': 'Warehouse',
+ 'license': 'AGPL-3',
+ 'complexity': 'normal',
+ 'images': [],
+ 'website': "http://www.camptocamp.com",
+ 'description': """
+Stock Reserve
+=============
+
+Allows to create stock reservation on a product or a selection of
+products. The reservations can be monitored and lifter on the product
+view. Each reservation can have a validity date, once reached, the
+reservation is automatically lifted.
+
+""",
+ 'depends': ['stock',
+ ],
+ 'demo': [],
+ 'data': [],
+ 'auto_install': False,
+ 'test': [],
+ 'installable': True,
+ }
diff --git a/stock_reserve/model/__init__.py b/stock_reserve/model/__init__.py
new file mode 100644
index 000000000..7202763ed
--- /dev/null
+++ b/stock_reserve/model/__init__.py
@@ -0,0 +1,22 @@
+# -*- 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 .
+#
+##############################################################################
+
+from . import stock_reserve
diff --git a/stock_reserve/model/stock_reserve.py b/stock_reserve/model/stock_reserve.py
new file mode 100644
index 000000000..b045a5ff0
--- /dev/null
+++ b/stock_reserve/model/stock_reserve.py
@@ -0,0 +1,124 @@
+# -*- 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 .
+#
+##############################################################################
+
+from datetime import datetime
+
+from openerp.osv import orm, fields
+from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT as DT_FMT
+
+
+class stock_reservation(orm.Model):
+ """ Allow to reserve products.
+
+ A stock reservation is basically a stock move,
+ but the reservation is handled by this model using
+ a ``_inherits``.
+ """
+ _name = 'stock.reservation'
+ _description = 'Stock Reservation'
+ _inherits = {'stock.move': 'move_id'}
+
+ _columns = {
+ 'move_id': fields.many2one('stock.move',
+ 'Move',
+ readonly=True,
+ required=True,
+ ondelete='cascade',
+ select=1),
+ 'date_validity': fields.datetime('Validity Date'),
+ }
+
+ def _prepare_reserve(self, cr, uid, product_id, quantity, location_id,
+ date_validity=None, context=None):
+ product_obj = self.pool.get('product.product')
+ product = product_obj.browse(cr, uid, product_id, context=context)
+ location_dest_id = self._get_reservation_location(
+ cr, uid, product_id, context=context)
+ vals = {'name': 'Reservation', # sequence?
+ 'product_id': product_id,
+ 'product_qty': quantity,
+ 'product_uom': product.uom_id.id,
+ 'location_id': location_id,
+ 'location_dest_id': location_dest_id,
+ 'price_unit': product.standard_price or 0.0,
+ }
+ if date_validity:
+ vals.update({'date_validity': date_validity,
+ 'date': date_validity,
+ 'date_expected': date_validity,
+ })
+ else:
+ today = datetime.now().strftime(DT_FMT)
+ vals.update({'date': today,
+ 'date_expected': today,
+ })
+ return vals
+
+ def _get_reservation_location(self, cr, uid, product_id, context=None):
+ """ Returns the appropriate destination location to
+ reserve a product
+ """
+ return 1
+
+ def reserve(self, cr, uid, product_id, quantity, location_id,
+ date_validity=None, context=None):
+ """ Reserve a product.
+
+ The reservation is done using the default UOM of the product.
+ A date until which the product is reserved can be specified.
+
+ :param product_id: id of the product to reserve
+ :param quantity: quantity of products to reserve
+ :param location_id: source location for the reservation
+ :param date_validity: optional datetime until which the reservation
+ is valid
+ :returns: id of the ``stock.reservation`` created
+ """
+ vals = self._prepare_reserve(cr, uid, product_id, quantity,
+ location_id,
+ date_validity=date_validity,
+ context=context)
+ reservation_id = self.create(cr, uid, vals, context=context)
+ move_id = self.read(cr, uid,
+ reservation_id,
+ ['move_id'],
+ context=context,
+ load='_classic_write')['move_id']
+ move_obj = self.pool.get('stock.move')
+ move_obj.action_confirm(cr, uid, [move_id], context=context)
+ # TODO: if no quantity in the location, it will stay 'confirmed'
+ # after action_confirm(), how to handle that?
+ move_obj.action_assign(cr, uid, [move_id])
+ return reservation_id
+
+ def release(self, cr, uid, ids, context=None):
+ """ Release a reservation """
+ return self.unlink(cr, uid, ids, context=context)
+
+ def unlink(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 super(stock_reservation, self).unlink(cr, uid, ids, context=context)