[ADD] started stock_reserve_sale to reserve lines of quotations

This commit is contained in:
Guewen Baconnier
2013-09-05 17:35:11 +02:00
committed by Carlos Roca
parent bb8d68159b
commit 5437d1aa75
12 changed files with 802 additions and 0 deletions

View File

@@ -0,0 +1,23 @@
# -*- 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 . import model
from . import wizard

View File

@@ -0,0 +1,65 @@
# -*- 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/>.
#
##############################################################################
{'name': 'Stock Reserve Sales',
'version': '0.1',
'author': 'Camptocamp',
'category': 'Warehouse',
'license': 'AGPL-3',
'complexity': 'normal',
'images': [],
'website': "http://www.camptocamp.com",
'description': """
Stock Reserve Sales
===================
Allows to create stock reservations for quotation lines before the
confirmation of the quotation. The reservations might have a validity
date and in any case they are lifted when the quotation is canceled or
confirmed.
Reservations can be done only on "make to stock" and stockable products.
The reserved products are substracted from the virtual stock. It means
that if you reserved a quantity of products which bring the virtual
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.
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',
],
'demo': [],
'data': ['wizard/sale_stock_reserve_view.xml',
'view/sale.xml',
'view/stock_reserve.xml',
],
'auto_install': False,
'test': ['test/sale_reserve.yml',
'test/sale_line_reserve.yml',
],
'installable': True,
}

View File

@@ -0,0 +1,23 @@
# -*- 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 . import sale
from . import stock_reserve

View File

@@ -0,0 +1,184 @@
# -*- 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 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_ids:
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.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'),
}
def release_all_stock_reservation(self, cr, uid, ids, context=None):
sales = self.browse(cr, uid, ids, context=context)
line_ids = [line.id for sale in sales for line in sale.order_line]
line_obj = self.pool.get('sale.order.line')
line_obj.release_stock_reservation(cr, uid, line_ids, context=context)
return True
def action_button_confirm(self, cr, uid, ids, context=None):
self.release_all_stock_reservation(cr, uid, ids, context=context)
return super(sale_order, self).action_button_confirm(
cr, uid, ids, context=context)
def action_cancel(self, cr, uid, ids, context=None):
self.release_all_stock_reservation(cr, uid, ids, context=context)
return super(sale_order, self).action_cancel(
cr, uid, ids, context=context)
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_ids:
result[line.id] = True
return result
_columns = {
'reservation_ids': fields.one2many(
'stock.reservation',
'sale_line_id',
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_ids'] = False
return super(sale_order_line, self).copy_data(
cr, uid, id, default=default, context=context)
def release_stock_reservation(self, cr, uid, ids, context=None):
lines = self.browse(cr, uid, ids, context=context)
reserv_ids = [reserv.id for line in lines
for reserv in line.reservation_ids]
reserv_obj = self.pool.get('stock.reservation')
reserv_obj.release(cr, uid, reserv_ids, context=context)
return True
def product_id_change(self, cr, uid, ids, pricelist, product, qty=0,
uom=False, qty_uos=0, uos=False, name='', partner_id=False,
lang=False, update_tax=True, date_order=False, packaging=False, fiscal_position=False, flag=False, context=None):
result = super(sale_order_line, self).product_id_change(
cr, uid, ids, pricelist, product, qty=qty, uom=uom,
qty_uos=qty_uos, uos=uos, name=name, partner_id=partner_id,
lang=lang, update_tax=update_tax, date_order=date_order,
packaging=packaging, fiscal_position=fiscal_position,
flag=flag, context=context)
if not ids: # warn only if we change an existing line
return result
assert len(ids) == 1, "Expected 1 ID, got %r" % ids
line = self.browse(cr, uid, ids[0], context=context)
if qty != line.product_uom_qty and line.reservation_ids:
msg = _("As you changed the quantity of the line, "
"the quantity of the stock reservation will "
"be automatically adjusted to %.2f.") % qty
msg += "\n\n"
result.setdefault('warning', {})
if result['warning'].get('message'):
result['warning']['message'] += msg
else:
result['warning'] = {
'title': _('Configuration Error!'),
'message': msg,
}
return result
def write(self, cr, uid, ids, vals, context=None):
block_on_reserve = ('product_id', 'product_uom', 'product_uos',
'type')
update_on_reserve = ('price_unit', 'product_uom_qty', 'product_uos_qty')
keys = set(vals.keys())
test_block = keys.intersection(block_on_reserve)
test_update = keys.intersection(update_on_reserve)
if test_block:
for line in self.browse(cr, uid, ids, context=context):
if not line.reservation_ids:
continue
raise orm.except_orm(
_('Error'),
_('You cannot change the product or unit of measure '
'of lines with a stock reservation. '
'Release the reservation '
'before changing the product.'))
res = super(sale_order_line, self).write(cr, uid, ids, vals, context=context)
if test_update:
for line in self.browse(cr, uid, ids, context=context):
if not line.reservation_ids:
continue
if len(line.reservation_ids) > 1:
raise orm.except_orm(
_('Error'),
_('Several stock reservations are linked with the '
'line. Impossible to adjust their quantity. '
'Please release the reservation '
'before changing the quantity.'))
line.reservation_ids[0].write(
{'price_unit': line.price_unit,
'product_qty': line.product_uom_qty,
'product_uos_qty': line.product_uos_qty,
}
)
return res

View File

@@ -0,0 +1,50 @@
# -*- 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
class stock_reservation(orm.Model):
_inherit = 'stock.reservation'
_columns = {
'sale_line_id': fields.many2one(
'sale.order.line',
string='Sale Order Line',
ondelete='cascade'),
'sale_id': fields.related(
'sale_line_id', 'order_id',
type='many2one',
relation='sale.order',
string='Sale Order')
}
def release(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'sale_line_id': False}, context=context)
return super(stock_reservation, self).release(
cr, uid, ids, context=context)
def copy_data(self, cr, uid, id, default=None, context=None):
if default is None:
default = {}
default['sale_line_id'] = False
return super(stock_reservation, self).copy_data(
cr, uid, id, default=default, context=context)

View File

@@ -0,0 +1,118 @@
-
I create a product to test the stock reservation
-
!record {model: product.product, id: product_yogurt}:
default_code: 001yogurt
name: yogurt
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 yogurt with 10 kgm
-
!record {model: stock.change.product.qty, id: change_qty}:
new_quantity: 10
product_id: product_yogurt
-
!python {model: stock.change.product.qty}: |
context['active_id'] = ref('product_yogurt')
self.change_product_qty(cr, uid, [ref('change_qty')], context=context)
-
In order to test reservation of the sales order, I create a sales order
-
!record {model: sale.order, id: sale_reserve_02}:
partner_id: base.res_partner_2
payment_term: account.account_payment_term
-
And I create a sales order line
-
!record {model: sale.order.line, id: sale_line_reserve_02_01, view: sale.view_order_line_tree}:
name: Yogurt
product_id: product_yogurt
product_uom_qty: 4
product_uom: product.product_uom_kgm
order_id: sale_reserve_02
-
And I create a stock reserve for this line
-
!record {model: sale.stock.reserve, id: wizard_reserve_02_01}:
note: Reservation for the sales order line
-
I call the wizard to reserve the products of the sales order
-
!python {model: sale.stock.reserve}: |
active_id = ref('sale_line_reserve_02_01')
context['active_id'] = active_id
context['active_ids'] = [active_id]
context['active_model'] = 'sale.order.line'
self.button_reserve(cr, uid, [ref('wizard_reserve_02_01')], context=context)
-
I check Virtual stock of yogurt after update reservation
-
!python {model: product.product}: |
product = self.browse(cr, uid, ref('product_yogurt'), context=context)
assert product.virtual_available == 6, "Stock is not updated."
-
And I create a MTO sales order line
-
!record {model: sale.order.line, id: sale_line_reserve_02_02, view: sale.view_order_line_tree}:
order_id: sale_reserve_02
name: Mouse, Wireless
product_id: product.product_product_12
type: make_to_order
product_uom_qty: 4
product_uom: product.product_uom_kgm
-
And I try to create a stock reserve for this MTO line
-
!record {model: sale.stock.reserve, id: wizard_reserve_02_02}:
note: Reservation for the sales order line
-
I call the wizard to reserve the products of the sales order
-
!python {model: sale.stock.reserve}: |
active_id = ref('sale_line_reserve_02_02')
context['active_id'] = active_id
context['active_ids'] = [active_id]
context['active_model'] = 'sale.order.line'
self.button_reserve(cr, uid, [ref('wizard_reserve_02_02')], context=context)
-
I should not have a stock reservation for a MTO line
-
!python {model: stock.reservation}: |
reserv_ids = self.search(
cr, uid,
[('sale_line_id', '=', ref('sale_line_reserve_02_02'))],
context=context)
assert not reserv_ids, "No stock reservation should be created for MTO lines"
-
And I change the quantity in the first line
-
!record {model: sale.order.line, id: sale_line_reserve_02_01, view: sale.view_order_line_tree}:
product_uom_qty: 5
-
I check Virtual stock of yogurt after change of reservations
-
!python {model: product.product}: |
product = self.browse(cr, uid, ref('product_yogurt'), context=context)
assert product.virtual_available == 5, "Stock is not updated."
-
I release the sales order's reservations for the first line
-
!python {model: sale.order.line}: |
self.release_stock_reservation(cr, uid, [ref('sale_line_reserve_02_01')], context=context)
-
I check Virtual stock of yogurt after release of reservations
-
!python {model: product.product}: |
product = self.browse(cr, uid, ref('product_yogurt'), context=context)
assert product.virtual_available == 10, "Stock is not updated."

View File

@@ -0,0 +1,65 @@
-
I create a product to test the stock reservation
-
!record {model: product.product, id: product_gelato}:
default_code: 001GELATO
name: Gelato
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 Gelato with 10 kgm
-
!record {model: stock.change.product.qty, id: change_qty}:
new_quantity: 10
product_id: product_gelato
-
!python {model: stock.change.product.qty}: |
context['active_id'] = ref('product_gelato')
self.change_product_qty(cr, uid, [ref('change_qty')], context=context)
-
In order to test reservation of the sales order, I create a sales order
-
!record {model: sale.order, id: sale_reserve_01}:
partner_id: base.res_partner_2
payment_term: account.account_payment_term
order_line:
- product_id: product_gelato
product_uom_qty: 4
-
I call the wizard to reserve the products of the sales order
-
!record {model: sale.stock.reserve, id: wizard_reserve_01}:
note: Reservation for the sales order
-
!python {model: sale.stock.reserve}: |
active_id = ref('sale_reserve_01')
context['active_id'] = active_id
context['active_ids'] = [active_id]
context['active_model'] = 'sale.order'
self.button_reserve(cr, uid, [ref('wizard_reserve_01')], context=context)
-
I check Virtual stock of Gelato after update reservation
-
!python {model: product.product}: |
product = self.browse(cr, uid, ref('product_gelato'), context=context)
assert product.virtual_available == 6, "Stock is not updated."
-
I release the sales order's reservations
-
!python {model: sale.order}: |
self.release_all_stock_reservation(cr, uid, [ref('sale_reserve_01')], context=context)
-
I check Virtual stock of Gelato after release of reservations
-
!python {model: product.product}: |
product = self.browse(cr, uid, ref('product_gelato'), context=context)
assert product.virtual_available == 10, "Stock is not updated."

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="0">
<record id="view_order_form_reserve" model="ir.ui.view">
<field name="name">sale.order.form.reserve</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale_stock.view_order_form_inherit"/>
<field name="arch" type="xml">
<button name="action_quotation_send" position="before">
<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>
<field name="order_line" position="attributes">
<attribute name="options">{"reload_on_button": 1}</attribute>
</field>
<xpath expr="//field[@name='order_line']/form//field[@name='state']" position="before">
<field name="reservation_ids" invisible="1"/>
<button name="%(action_sale_stock_reserve)d"
type="action"
string="Reserve Stock"
attrs="{'invisible': ['|', ('reservation_ids', '!=', []),
('state', '!=', 'draft')]}" />
<button name="release_stock_reservation"
type="object"
string="Release Reservation"
attrs="{'invisible': ['|', ('reservation_ids', '=', []),
('state', '!=', 'draft')]}" />
</xpath>
<xpath expr="//field[@name='order_line']/tree/field[@name='price_subtotal']" position="after">
<field name="reservation_ids" invisible="1"/>
<field name="is_stock_reservable" invisible="1"/>
<button name="%(action_sale_stock_reserve)d"
type="action"
string="Reserve Stock"
icon="terp-locked"
attrs="{'invisible': [('is_stock_reservable', '=', False)]}" />
<button name="release_stock_reservation"
type="object"
string="Release Reservation"
icon="gtk-undo"
attrs="{'invisible': [('reservation_ids', '=', [])]}" />
</xpath>
<field name="invoiced" position="before">
<label for="has_stock_reservation"/>
<div>
<field name="has_stock_reservation"/>
<button name="release_all_stock_reservation"
string="cancel all"
type="object" class="oe_link"
attrs="{'invisible': [('has_stock_reservation', '=', False)]}"/>
</div>
</field>
</field>
</record>
</data>
</openerp>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="0">
<record id="view_stock_reservation_form" model="ir.ui.view">
<field name="name">stock.reservation.form</field>
<field name="model">stock.reservation</field>
<field name="inherit_id" ref="stock_reserve.view_stock_reservation_form"/>
<field name="arch" type="xml">
<group name="location" position="after">
<group name="sale" string="Sales">
<field name="sale_id"/>
<field name="sale_line_id"/>
</group>
</group>
</field>
</record>
<record id="view_stock_reservation_tree" model="ir.ui.view">
<field name="name">stock.reservation.tree</field>
<field name="model">stock.reservation</field>
<field name="inherit_id" ref="stock_reserve.view_stock_reservation_tree"/>
<field name="arch" type="xml">
<field name="move_id" position="before">
<field name="sale_id"/>
</field>
</field>
</record>
</data>
</openerp>

View File

@@ -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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import sale_stock_reserve

View File

@@ -0,0 +1,111 @@
# -*- 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
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):
product_uos = line.product_uos.id if line.product_uos else False
return {'product_id': line.product_id.id,
'product_uom': line.product_uom.id,
'product_qty': line.product_uom_qty,
'date_validity': 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,
'product_uos_qty': line.product_uos_qty,
'product_uos': product_uos,
'price_unit': line.price_unit,
'sale_line_id': line.id,
}
def stock_reserve(self, cr, uid, ids, line_ids, context=None):
assert len(ids) == 1, "Expected 1 ID, got %r" % ids
reserv_obj = self.pool.get('stock.reservation')
line_obj = self.pool.get('sale.order.line')
form = self.browse(cr, uid, ids[0], context=context)
lines = line_obj.browse(cr, uid, line_ids, context=context)
for line in lines:
if not line.is_stock_reservable:
continue
vals = self._prepare_stock_reservation(cr, uid, form, line,
context=context)
reserv_id = reserv_obj.create(cr, uid, vals, context=context)
reserv_obj.reserve(cr, uid, [reserv_id], context=context)
return True
def button_reserve(self, cr, uid, ids, context=None):
assert len(ids) == 1, "Expected 1 ID, got %r" % ids
if context is None:
context = {}
close = {'type': 'ir.actions.act_window_close'}
active_model = context.get('active_model')
active_ids = context.get('active_ids')
if not (active_model and active_ids):
return close
line_obj = self.pool.get('sale.order.line')
if active_model == 'sale.order':
sale_obj = self.pool.get('sale.order')
sales = sale_obj.browse(cr, uid, active_ids, context=context)
line_ids = [line.id for sale in sales for line in sale.order_line]
if active_model == 'sale.order.line':
line_ids = active_ids
self.stock_reserve(cr, uid, ids, line_ids, context=context)
return close

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="0">
<record id="view_sale_stock_reserve_form" model="ir.ui.view">
<field name="name">sale.stock.reserve.form</field>
<field name="model">sale.stock.reserve</field>
<field name="arch" type="xml">
<form string="Reserve Stock" version="7.0">
<p class="oe_grey">
A stock reservation will be created for the products
of the selected quotation lines. If a validity date is specified,
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"
type="object"
class="oe_highlight" />
or
<button special="cancel" class="oe_link" string="Cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_sale_stock_reserve" model="ir.actions.act_window">
<field name="name">Reserve Stock for Quotation Lines</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">sale.stock.reserve</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</data>
</openerp>