[IMP] continued the stock_reserve_sale module

This commit is contained in:
Guewen Baconnier
2013-09-06 16:47:55 +02:00
parent 7c7d4a8bcd
commit 9442412b8e
8 changed files with 147 additions and 32 deletions

View File

@@ -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',
],

View File

@@ -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)

View File

@@ -18,7 +18,7 @@
<button name="open_move" type="object"
string="View Reservation Move"/>
<field name="state" widget="statusbar"
statusbar_visible="draft,done"/>
statusbar_visible="draft,assigned"/>
</header>
<sheet>
<group>
@@ -61,8 +61,9 @@
<field name="arch" type="xml">
<tree string="Stock Reservations" version="7.0"
colors="blue:state == 'draft';grey:state == 'cancel'" >
<field name="move_id" />
<field name="name" />
<field name="product_id" />
<field name="move_id" />
<field name="product_qty" sum="Total" />
<field name="product_uom" />
<field name="date_validity" />
@@ -88,11 +89,12 @@
domain="[('state', '=', 'draft')]"
help="Not already reserved"/>
<filter name="reserved" string="Reserved"
domain="[('state', '=', 'done')]"
domain="[('state', '=', 'assigned')]"
help="Moves are reserved."/>
<filter name="cancel" string="Released"
domain="[('state', '=', 'cancel')]"
help="Reservations have been released."/>
<field name="name" />
<field name="product_id" />
<field name="move_id" />
<group expand="0" string="Group By...">

View File

@@ -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',

View File

@@ -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)

View File

@@ -8,10 +8,12 @@
<field name="inherit_id" ref="sale_stock.view_order_form_inherit"/>
<field name="arch" type="xml">
<button name="action_quotation_send" position="before">
<button name="button_stock_reserve" type="object"
states="draft,sent"
<field name="is_stock_reservable" invisible="1"/>
<button name="%(action_sale_stock_reserve)d"
type="action"
string="Reserve Stock"
help="Pre-book products from stock"
attrs="{'invisible': [('is_stock_reservable', '=', False)]}"
/>
</button>
@@ -22,37 +24,45 @@
<xpath expr="//field[@name='order_line']/form//field[@name='state']" position="before">
<button name="%(action_sale_stock_reserve)d"
type="action"
states="draft"
string="Reserve Stock"
attrs="{'invisible': [('reservation_id', '!=', False)]}" />
attrs="{'invisible': ['|', ('reservation_id', '!=', False),
('state', '!=', 'draft')]}" />
<button name="release_stock_reservation"
type="object"
string="Release Reservation"
attrs="{'invisible': ['|', ('reservation_id', '=', False),
('state', '!=', 'draft')]}" />
</xpath>
<xpath expr="//field[@name='order_line']/tree/field[@name='price_subtotal']" position="after">
<field name="reservation_id" readonly="1"/>
<field name="reservation_id" invisible="1"/>
<field name="is_stock_reservable" invisible="1"/>
<button name="%(action_sale_stock_reserve)d"
type="action"
states="draft"
string="Reserve Stock"
icon="terp-locked"
attrs="{'invisible': [('reservation_id', '!=', False)]}" />
attrs="{'invisible': [('is_stock_reservable', '=', False)]}" />
<button name="release_stock_reservation"
type="object"
string="Release Reservation"
icon="gtk-undo"
attrs="{'invisible': [('reservation_id', '=', False)]}" />
</xpath>
<xpath expr="//field[@name='order_line']/form//field[@name='type']" position="after">
<label for="reservation_id" />
<div attrs="{'invisible': [('type', '=', 'make_to_order')]}">
<div attrs="{'invisible': [('reservation_id', '=', False)]}">
<field name="reservation_id" readonly="1"
class="oe_inline"/>
<button name="button_stock_reserve_update"
string="update"
type="object"
class="oe_link"
attrs="{'invisible': [('reservation_id', '=', False)]}"/>
class="oe_link" />
-
<button name="button_stock_reserve_cancel"
string="cancel"
type="object"
class="oe_link"
attrs="{'invisible': [('reservation_id', '=', False)]}"/>
class="oe_link" />
</div>
</xpath>
@@ -60,7 +70,7 @@
<label for="has_stock_reservation"/>
<div>
<field name="has_stock_reservation"/>
<button name="button_stock_reserve_cancel"
<button name="release_all_stock_reservation"
string="cancel all"
type="object" class="oe_link"
attrs="{'invisible': [('has_stock_reservation', '=', False)]}"/>

View File

@@ -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)

View File

@@ -13,8 +13,13 @@
the reservation will be released once the date has passed.
</p>
<group>
<field name="location_id"/>
<field name="location_dest_id"/>
<field name="date_validity"/>
</group>
<group name="note" string="Notes">
<field name="note" nolabel="1"/>
</group>
<footer>
<button string="Reserve"
name="button_reserve"