diff --git a/__unported__/stock_reserve_sale/wizard/sale_stock_reserve.py b/__unported__/stock_reserve_sale/wizard/sale_stock_reserve.py
deleted file mode 100644
index 7aac6d605..000000000
--- a/__unported__/stock_reserve_sale/wizard/sale_stock_reserve.py
+++ /dev/null
@@ -1,110 +0,0 @@
-# -*- 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 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
-
- 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
diff --git a/__unported__/stock_reserve_sale/__init__.py b/stock_reserve_sale/__init__.py
similarity index 100%
rename from __unported__/stock_reserve_sale/__init__.py
rename to stock_reserve_sale/__init__.py
diff --git a/__unported__/stock_reserve_sale/__openerp__.py b/stock_reserve_sale/__openerp__.py
similarity index 99%
rename from __unported__/stock_reserve_sale/__openerp__.py
rename to stock_reserve_sale/__openerp__.py
index 8ee421e95..7d9d2b9b0 100644
--- a/__unported__/stock_reserve_sale/__openerp__.py
+++ b/stock_reserve_sale/__openerp__.py
@@ -57,9 +57,9 @@ insufficient at the order date, you may want to install the
'view/sale.xml',
'view/stock_reserve.xml',
],
- 'auto_install': False,
'test': ['test/sale_reserve.yml',
'test/sale_line_reserve.yml',
],
- 'installable': False,
+ 'installable': True,
+ 'auto_install': False,
}
diff --git a/__unported__/stock_reserve_sale/model/__init__.py b/stock_reserve_sale/model/__init__.py
similarity index 100%
rename from __unported__/stock_reserve_sale/model/__init__.py
rename to stock_reserve_sale/model/__init__.py
diff --git a/__unported__/stock_reserve_sale/model/sale.py b/stock_reserve_sale/model/sale.py
similarity index 50%
rename from __unported__/stock_reserve_sale/model/sale.py
rename to stock_reserve_sale/model/sale.py
index 5fe8e3ef5..1dd688a56 100644
--- a/__unported__/stock_reserve_sale/model/sale.py
+++ b/stock_reserve_sale/model/sale.py
@@ -19,102 +19,134 @@
#
##############################################################################
-from openerp.osv import orm, fields
+from openerp import models, fields, api
+from openerp.exceptions import except_orm
from openerp.tools.translate import _
-class sale_order(orm.Model):
+class SaleOrder(models.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):
+ @api.multi
+ @api.depends('state',
+ 'order_line.reservation_ids',
+ 'order_line.is_stock_reservable')
+ def _stock_reservation(self):
+ for sale in self:
+ has_stock_reservation = False
+ is_stock_reservable = False
for line in sale.order_line:
if line.reservation_ids:
- result[sale.id]['has_stock_reservation'] = True
+ has_stock_reservation = True
if line.is_stock_reservable:
- result[sale.id]['is_stock_reservable'] = True
+ is_stock_reservable = True
if sale.state not in ('draft', 'sent'):
- result[sale.id]['is_stock_reservable'] = False
- return result
+ is_stock_reservable = False
+ sale.is_stock_reservable = is_stock_reservable
+ sale.has_stock_reservation = has_stock_reservation
- _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'),
- }
+ has_stock_reservation = fields.Boolean(
+ compute='_stock_reservation',
+ readonly=True,
+ multi='stock_reservation',
+ store=True,
+ string='Has Stock Reservations')
+ is_stock_reservable = fields.Boolean(
+ compute='_stock_reservation',
+ readonly=True,
+ multi='stock_reservation',
+ store=True,
+ 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)
+ @api.multi
+ def release_all_stock_reservation(self):
+ line_ids = [line.id for order in self for line in order.order_line]
+ lines = self.env['sale.order.line'].browse(line_ids)
+ lines.release_stock_reservation()
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)
+ @api.multi
+ def action_button_confirm(self):
+ self.release_all_stock_reservation()
+ return super(SaleOrder, self).action_button_confirm()
- 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)
+ @api.multi
+ def action_cancel(self):
+ self.release_all_stock_reservation()
+ return super(SaleOrder, self).action_cancel()
-class sale_order_line(orm.Model):
+class SaleOrderLine(models.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
+ @api.multi
+ def _get_line_rule(self):
+ """ Get applicable rule for this product
- _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'),
- }
+ Reproduce get suitable rule from procurement
+ to predict source location """
+ ProcurementRule = self.env['procurement.rule']
+ product = self.product_id
+ product_route_ids = [x.id for x in product.route_ids +
+ product.categ_id.total_route_ids]
+ rules = ProcurementRule.search([('route_id', 'in', product_route_ids)],
+ order='route_sequence, sequence',
+ limit=1)
- 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)
+ if not rules:
+ warehouse = self.order_id.warehouse_id
+ wh_routes = warehouse.route_ids
+ wh_route_ids = [route.id for route in wh_routes]
+ domain = ['|', ('warehouse_id', '=', warehouse.id),
+ ('warehouse_id', '=', False),
+ ('route_id', 'in', wh_route_ids)]
- 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
+ rules = ProcurementRule.search(domain,
+ order='route_sequence, sequence')
+
+ if rules:
+ return rules[0]
+ return False
+
+ @api.multi
+ def _get_procure_method(self):
+ """ Get procure_method depending on product routes """
+ rule = self._get_line_rule()
+ if rule:
+ return rule.procure_method
+ return False
+
+ @api.multi
+ @api.depends('state',
+ 'product_id.route_ids',
+ 'product_id.type')
+ def _is_stock_reservable(self):
+ for line in self:
+ reservable = False
+ if (not (line.state != 'draft'
+ or line._get_procure_method() == 'make_to_order'
+ or not line.product_id
+ or line.product_id.type == 'service')
+ and not line.reservation_ids):
+ reservable = True
+ line.is_stock_reservable = reservable
+
+ reservation_ids = fields.One2many(
+ 'stock.reservation',
+ 'sale_line_id',
+ string='Stock Reservation',
+ copy=False)
+ is_stock_reservable = fields.Boolean(
+ compute='_is_stock_reservable',
+ readonly=True,
+ string='Can be reserved')
+
+ @api.multi
+ def release_stock_reservation(self):
+ reserv_ids = [reserv.id for line in self
for reserv in line.reservation_ids]
- reserv_obj = self.pool.get('stock.reservation')
- reserv_obj.release(cr, uid, reserv_ids, context=context)
+ reservations = self.env['stock.reservation'].browse(reserv_ids)
+ reservations.release()
return True
def product_id_change(self, cr, uid, ids,
@@ -133,7 +165,7 @@ class sale_order_line(orm.Model):
fiscal_position=False,
flag=False,
context=None):
- result = super(sale_order_line, self).product_id_change(
+ result = super(SaleOrderLine, 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,
@@ -158,7 +190,8 @@ class sale_order_line(orm.Model):
}
return result
- def write(self, cr, uid, ids, vals, context=None):
+ @api.multi
+ def write(self, vals):
block_on_reserve = ('product_id',
'product_uom',
'product_uos',
@@ -170,33 +203,31 @@ class sale_order_line(orm.Model):
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):
+ for line in self:
if not line.reservation_ids:
continue
- raise orm.except_orm(
+ raise 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)
+ res = super(SaleOrderLine, self).write(vals)
if test_update:
- for line in self.browse(cr, uid, ids, context=context):
+ for line in self:
if not line.reservation_ids:
continue
if len(line.reservation_ids) > 1:
- raise orm.except_orm(
+ raise 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(
+ line.reservation_ids.write(
{'price_unit': line.price_unit,
- 'product_qty': line.product_uom_qty,
+ 'product_uom_qty': line.product_uom_qty,
'product_uos_qty': line.product_uos_qty,
}
)
diff --git a/__unported__/stock_reserve_sale/model/stock_reserve.py b/stock_reserve_sale/model/stock_reserve.py
similarity index 51%
rename from __unported__/stock_reserve_sale/model/stock_reserve.py
rename to stock_reserve_sale/model/stock_reserve.py
index 0ea5fb958..db68449bb 100644
--- a/__unported__/stock_reserve_sale/model/stock_reserve.py
+++ b/stock_reserve_sale/model/stock_reserve.py
@@ -19,32 +19,24 @@
#
##############################################################################
-from openerp.osv import orm, fields
+from openerp import models, fields, api
-class stock_reservation(orm.Model):
+class StockReservation(models.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')
- }
+ sale_line_id = fields.Many2one(
+ 'sale.order.line',
+ string='Sale Order Line',
+ ondelete='cascade',
+ copy=False)
+ sale_id = fields.Many2one(
+ 'sale.order',
+ string='Sale Order',
+ related='sale_line_id.order_id')
- 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)
+ @api.multi
+ def release(self):
+ for rec in self:
+ rec.sale_line_id = False
+ return super(StockReservation, self).release()
diff --git a/__unported__/stock_reserve_sale/test/sale_line_reserve.yml b/stock_reserve_sale/test/sale_line_reserve.yml
similarity index 88%
rename from __unported__/stock_reserve_sale/test/sale_line_reserve.yml
rename to stock_reserve_sale/test/sale_line_reserve.yml
index 2dfbee6ef..421d5da2f 100644
--- a/__unported__/stock_reserve_sale/test/sale_line_reserve.yml
+++ b/stock_reserve_sale/test/sale_line_reserve.yml
@@ -10,7 +10,6 @@
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
@@ -60,6 +59,19 @@
!python {model: product.product}: |
product = self.browse(cr, uid, ref('product_yogurt'), context=context)
assert product.virtual_available == 6, "Stock is not updated."
+-
+ I set product_12 to MTO (doesn't work)
+-
+ !record {model: product.product, id: product.product_product_12}:
+ route_ids:
+ - stock.route_warehouse0_mto
+-
+ I set MTO for real
+-
+ !python {model: product.product}: |
+ product = self.browse(cr, uid, ref('product.product_product_12'), context=context)
+ self.write(cr, uid, ref('product.product_product_12'),
+ {'route_ids': [(6, False, [ref('stock.route_warehouse0_mto')])]}, context=context)
-
And I create a MTO sales order line
-
@@ -67,9 +79,8 @@
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
+ product_uom: product.product_uom_unit
-
And I try to create a stock reserve for this MTO line
-
diff --git a/__unported__/stock_reserve_sale/test/sale_reserve.yml b/stock_reserve_sale/test/sale_reserve.yml
similarity index 98%
rename from __unported__/stock_reserve_sale/test/sale_reserve.yml
rename to stock_reserve_sale/test/sale_reserve.yml
index 7fc9dff6d..2671f40e1 100644
--- a/__unported__/stock_reserve_sale/test/sale_reserve.yml
+++ b/stock_reserve_sale/test/sale_reserve.yml
@@ -10,7 +10,6 @@
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
diff --git a/__unported__/stock_reserve_sale/view/sale.xml b/stock_reserve_sale/view/sale.xml
similarity index 100%
rename from __unported__/stock_reserve_sale/view/sale.xml
rename to stock_reserve_sale/view/sale.xml
diff --git a/__unported__/stock_reserve_sale/view/stock_reserve.xml b/stock_reserve_sale/view/stock_reserve.xml
similarity index 100%
rename from __unported__/stock_reserve_sale/view/stock_reserve.xml
rename to stock_reserve_sale/view/stock_reserve.xml
diff --git a/__unported__/stock_reserve_sale/wizard/__init__.py b/stock_reserve_sale/wizard/__init__.py
similarity index 100%
rename from __unported__/stock_reserve_sale/wizard/__init__.py
rename to stock_reserve_sale/wizard/__init__.py
diff --git a/stock_reserve_sale/wizard/sale_stock_reserve.py b/stock_reserve_sale/wizard/sale_stock_reserve.py
new file mode 100644
index 000000000..2fc7d1ff1
--- /dev/null
+++ b/stock_reserve_sale/wizard/sale_stock_reserve.py
@@ -0,0 +1,103 @@
+# -*- 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 openerp import models, fields, api
+
+
+class SaleStockReserve(models.TransientModel):
+ _name = 'sale.stock.reserve'
+
+ @api.model
+ def _default_location_id(self):
+ return self.env['stock.reservation']._default_location_id()
+
+ @api.model
+ def _default_location_dest_id(self):
+ return self.env['stock.reservation']._default_location_dest_id()
+
+ location_id = fields.Many2one(
+ 'stock.location',
+ 'Source Location',
+ required=True,
+ default=_default_location_id)
+ location_dest_id = fields.Many2one(
+ 'stock.location',
+ 'Reservation Location',
+ required=True,
+ help="Location where the system will reserve the "
+ "products.",
+ default=_default_location_dest_id)
+ 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')
+
+ @api.multi
+ def _prepare_stock_reservation(self, line):
+ self.ensure_one()
+ 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_uom_qty': line.product_uom_qty,
+ 'date_validity': self.date_validity,
+ 'name': "%s (%s)" % (line.order_id.name, line.name),
+ 'location_id': self.location_id.id,
+ 'location_dest_id': self.location_dest_id.id,
+ 'note': self.note,
+ 'product_uos_qty': line.product_uos_qty,
+ 'product_uos': product_uos,
+ 'price_unit': line.price_unit,
+ 'sale_line_id': line.id,
+ }
+
+ @api.multi
+ def stock_reserve(self, line_ids):
+ self.ensure_one()
+
+ lines = self.env['sale.order.line'].browse(line_ids)
+ for line in lines:
+ if not line.is_stock_reservable:
+ continue
+ vals = self._prepare_stock_reservation(line)
+ reserv = self.env['stock.reservation'].create(vals)
+ reserv.reserve()
+ return True
+
+ @api.multi
+ def button_reserve(self):
+ env = self.env
+ self.ensure_one()
+ close = {'type': 'ir.actions.act_window_close'}
+ active_model = env.context.get('active_model')
+ active_ids = env.context.get('active_ids')
+ if not (active_model and active_ids):
+ return close
+
+ if active_model == 'sale.order':
+ sales = env['sale.order'].browse(active_ids)
+ 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(line_ids)
+ return close
diff --git a/__unported__/stock_reserve_sale/wizard/sale_stock_reserve_view.xml b/stock_reserve_sale/wizard/sale_stock_reserve_view.xml
similarity index 100%
rename from __unported__/stock_reserve_sale/wizard/sale_stock_reserve_view.xml
rename to stock_reserve_sale/wizard/sale_stock_reserve_view.xml