diff --git a/stock_reserve/__openerp__.py b/stock_reserve/__openerp__.py
index a2b6c0657..8633fcfcd 100644
--- a/stock_reserve/__openerp__.py
+++ b/stock_reserve/__openerp__.py
@@ -36,6 +36,12 @@ Allows to create stock reservation on a product.
Each reservation can have a validity date, once passed, the reservation
is automatically lifted.
+The reserved products are substracted from the virtual stock. It means
+that if you reserved too many products and the virtual stock goes below
+the minimum, the orderpoint will be trigged and new purchase orders will
+be generated. It also implies that the max may be exceeded if the
+reservations are canceled.
+
""",
'depends': ['stock',
],
diff --git a/stock_reserve/model/stock_reserve.py b/stock_reserve/model/stock_reserve.py
index de20bcc9d..f3b8241b1 100644
--- a/stock_reserve/model/stock_reserve.py
+++ b/stock_reserve/model/stock_reserve.py
@@ -59,7 +59,7 @@ class stock_reservation(orm.Model):
'date_validity': fields.date('Validity Date'),
}
- def _get_location_from_ref(self, cr, uid, ref, context=None):
+ def get_location_from_ref(self, cr, uid, ref, context=None):
""" Get a location from a xmlid if allowed
:param ref: tuple (module, xmlid)
"""
@@ -75,17 +75,20 @@ class stock_reservation(orm.Model):
return location_id
def _default_location_id(self, cr, uid, context=None):
- ref = ('stock', 'stock_location_stock')
- return self._get_location_from_ref(cr, uid, ref, context=context)
+ 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_dest_location_id(self, cr, uid, context=None):
+ 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)
+ return self.get_location_from_ref(cr, uid, ref, context=context)
_defaults = {
'type': 'internal',
'location_id': _default_location_id,
- 'location_dest_id': _default_dest_location_id,
+ 'location_dest_id': _default_location_dest_id,
'product_qty': 1.0,
}
@@ -118,7 +121,7 @@ class stock_reservation(orm.Model):
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', '=', 'done')]
+ ('state', '=', 'assigned')]
if ids:
domain.append(('id', 'in', ids))
reserv_ids = self.search(cr, uid, domain, context=context)
diff --git a/stock_reserve/view/stock_reserve.xml b/stock_reserve/view/stock_reserve.xml
index c65e4c6e4..a15d08c8c 100644
--- a/stock_reserve/view/stock_reserve.xml
+++ b/stock_reserve/view/stock_reserve.xml
@@ -18,7 +18,7 @@
+ statusbar_visible="draft,assigned"/>
@@ -61,8 +61,9 @@
-
+
+
@@ -88,11 +89,12 @@
domain="[('state', '=', 'draft')]"
help="Not already reserved"/>
+
diff --git a/stock_reserve_sale/__openerp__.py b/stock_reserve_sale/__openerp__.py
index 26664ef36..fcc7763b3 100644
--- a/stock_reserve_sale/__openerp__.py
+++ b/stock_reserve_sale/__openerp__.py
@@ -31,10 +31,23 @@
Stock Reserve Sale
==================
-Allows to create stock reservation for a quotation before it is
-confirmed. The reservation might have a validity date and in any
-case the reservations are lifted when the quotation is canceled or
-confirmed.
+Allows to create stock reservation for quotation lines before the
+quotation is confirmed. The reservation might have a validity date and
+in any case the reservations are lifted when the quotation is canceled
+or confirmed.
+
+Reservations can be done only on make to stock stockable products.
+
+The reserved products are substracted from the virtual stock. It means
+that if you reserved too many products and the virtual stock goes below
+the minimum, the orderpoint will be trigged and new purchase orders will
+be generated. It also implies that the max may be exceeded if the
+reservations are canceled.
+
+If you want to prevent sales orders to be confirmed when the stock is
+insufficient at the order date, you may want to install the
+'sale_exception_nostock' module.
+
""",
'depends': ['sale_stock',
'stock_reserve',
diff --git a/stock_reserve_sale/model/sale.py b/stock_reserve_sale/model/sale.py
index 7cb57e1b0..995d32bf5 100644
--- a/stock_reserve_sale/model/sale.py
+++ b/stock_reserve_sale/model/sale.py
@@ -25,16 +25,67 @@ from openerp.osv import orm, fields
class sale_order(orm.Model):
_inherit = 'sale.order'
+ def _stock_reservation(self, cr, uid, ids, fields, args, context=None):
+ result = {}
+ for order_id in ids:
+ result[order_id] = {'has_stock_reservation': False,
+ 'is_stock_reservable': False}
+ for sale in self.browse(cr, uid, ids, context=context):
+ for line in sale.order_line:
+ if line.reservation_id:
+ result[sale.id]['has_stock_reservation'] = True
+ if line.is_stock_reservable:
+ result[sale.id]['is_stock_reservable'] = True
+ if sale.state not in ('draft', 'sent'):
+ result[sale.id]['is_stock_reservable'] = False
+ return result
+
_columns = {
- 'has_stock_reservation': fields.boolean('Has Stock Reservations'),
+ 'has_stock_reservation': fields.function(
+ _stock_reservation,
+ type='boolean',
+ readonly=True,
+ multi='stock_reservation',
+ string='Has Stock Reservations'),
+ 'is_stock_reservable': fields.function(
+ _stock_reservation,
+ type='boolean',
+ readonly=True,
+ multi='stock_reservation',
+ string='Can Have Stock Reservations'),
}
class sale_order_line(orm.Model):
_inherit = 'sale.order.line'
+ def _is_stock_reservable(self, cr, uid, ids, fields, args, context=None):
+ result = {}.fromkeys(ids, False)
+ for line in self.browse(cr, uid, ids, context=context):
+ if line.state != 'draft':
+ continue
+ if line.type == 'make_to_order':
+ continue
+ if (not line.product_id or line.product_id.type == 'service'):
+ continue
+ if not line.reservation_id:
+ result[line.id] = True
+ return result
+
_columns = {
'reservation_id': fields.many2one(
'stock.reservation',
- string='Stock Reservation')
+ string='Stock Reservation'),
+ 'is_stock_reservable': fields.function(
+ _is_stock_reservable,
+ type='boolean',
+ readonly=True,
+ string='Can be reserved'),
}
+
+ def copy_data(self, cr, uid, id, default=None, context=None):
+ if default is None:
+ default = {}
+ default['reservation_id'] = False
+ return super(sale_order_line, self).copy_data(
+ cr, uid, id, default=default, context=context)
diff --git a/stock_reserve_sale/view/sale.xml b/stock_reserve_sale/view/sale.xml
index fc622dd03..0efb23bf2 100644
--- a/stock_reserve_sale/view/sale.xml
+++ b/stock_reserve_sale/view/sale.xml
@@ -8,10 +8,12 @@
@@ -22,37 +24,45 @@
+ attrs="{'invisible': ['|', ('reservation_id', '!=', False),
+ ('state', '!=', 'draft')]}" />
+
-
+
+
+ attrs="{'invisible': [('is_stock_reservable', '=', False)]}" />
+
-
+
+ class="oe_link" />
-
+ class="oe_link" />
@@ -60,7 +70,7 @@
-
diff --git a/stock_reserve_sale/wizard/sale_stock_reserve.py b/stock_reserve_sale/wizard/sale_stock_reserve.py
index 734e6010f..881ed854e 100644
--- a/stock_reserve_sale/wizard/sale_stock_reserve.py
+++ b/stock_reserve_sale/wizard/sale_stock_reserve.py
@@ -26,10 +26,34 @@ class sale_stock_reserve(orm.TransientModel):
_name = 'sale.stock.reserve'
_columns = {
+ 'location_id': fields.many2one(
+ 'stock.location',
+ 'Source Location',
+ required=True),
+ 'location_dest_id': fields.many2one(
+ 'stock.location',
+ 'Reservation Location',
+ required=True,
+ help="Location where the system will reserve the "
+ "products."),
'date_validity': fields.date(
"Validity Date",
help="If a date is given, the reservations will be released "
"at the end of the validity."),
+ 'note': fields.text('Notes'),
+ }
+
+ def _default_location_id(self, cr, uid, context=None):
+ reserv_obj = self.pool.get('stock.reservation')
+ return reserv_obj._default_location_id(cr, uid, context=context)
+
+ def _default_location_dest_id(self, cr, uid, context=None):
+ reserv_obj = self.pool.get('stock.reservation')
+ return reserv_obj._default_location_dest_id(cr, uid, context=context)
+
+ _defaults = {
+ 'location_id': _default_location_id,
+ 'location_dest_id': _default_location_dest_id,
}
def _prepare_stock_reservation(self, cr, uid, form, line, context=None):
@@ -38,6 +62,9 @@ class sale_stock_reserve(orm.TransientModel):
'product_qty': line.product_uom_qty,
'validity_date': form.date_validity,
'name': "{} ({})".format(line.order_id.name, line.name),
+ 'location_id': form.location_id.id,
+ 'location_dest_id': form.location_dest_id.id,
+ 'note': form.note,
}
def stock_reserve(self, cr, uid, ids, line_ids, context=None):
@@ -48,9 +75,7 @@ class sale_stock_reserve(orm.TransientModel):
form = self.browse(cr, uid, ids[0], context=context)
lines = line_obj.browse(cr, uid, line_ids, context=context)
for line in lines:
- if line.reservation_id:
- continue
- if not line.product_id:
+ if not line.is_stock_reservable:
continue
vals = self._prepare_stock_reservation(cr, uid, form, line,
context=context)
diff --git a/stock_reserve_sale/wizard/sale_stock_reserve_view.xml b/stock_reserve_sale/wizard/sale_stock_reserve_view.xml
index 66017c53a..5b3c39bcc 100644
--- a/stock_reserve_sale/wizard/sale_stock_reserve_view.xml
+++ b/stock_reserve_sale/wizard/sale_stock_reserve_view.xml
@@ -13,8 +13,13 @@
the reservation will be released once the date has passed.
+
+
+
+
+