mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
[IMP] continued the stock_reserve_sale module
This commit is contained in:
@@ -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',
|
||||
],
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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...">
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)]}"/>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user