diff --git a/inventory.py b/inventory.py
index 58ff199282..c1c0cc80a6 100644
--- a/inventory.py
+++ b/inventory.py
@@ -18,7 +18,7 @@ class Inventory(OSV):
location = fields.Many2One(
'stock.location', 'Location', required=True,
domain="[('type', '=', 'storage')]", states=STATES)
- date = fields.DateTime('Date', readonly=True)
+ date = fields.Date('Date')
lost_found = fields.Many2One(
'stock.location', 'Lost and Found', required=True,
domain="[('type', '=', 'lost_found')]", states=STATES)
@@ -50,7 +50,7 @@ class Inventory(OSV):
return 'open'
def default_date(self, cursor, user, context=None):
- return datetime.datetime.today()
+ return datetime.date.today()
def default_company(self, cursor, user, context=None):
if context.get('company'):
@@ -88,29 +88,16 @@ class Inventory(OSV):
location_ids = []
inv_by_loc = {}
- for inventory in inventories:
- location_ids.append(inventory.location.id)
- inv_by_loc[inventory.location.id] = inventory.id
- raw_data = product_obj.raw_products_by_location(cursor, user,
- location_ids, context=context)
-
- indexed_data = {}
- for location, product, uom, qty in raw_data:
- indexed_data.setdefault(location,{})[(product, uom)] = qty
-
for inventory in inventories:
if inventory.state != 'open':
continue
moves = []
- location_data = indexed_data.get(inventory.location.id, {})
for line in inventory.lines:
- key = (line.product.id, line.uom.id)
- expected_qty = location_data.get(key, 0.0)
- delta_qty = line.quantity - expected_qty
- location_data[key] = 0
-
- from_location = inventory.lost_found.id
- to_location = inventory.location.id
+ delta_qty = line.expected_quantity - line.quantity
+ if delta_qty == 0.0:
+ continue
+ from_location = inventory.location.id
+ to_location = inventory.lost_found.id
if delta_qty < 0:
(from_location, to_location, delta_qty) = \
(to_location, from_location, -delta_qty)
@@ -125,31 +112,11 @@ class Inventory(OSV):
'state': 'done',
}, context=context)
moves.append(move_id)
- # Create move for all missing products
- for (product, uom), qty in location_data.iteritems():
- if qty == 0:
- continue
- from_location = inventory.location.id
- to_location = inventory.lost_found.id
- if qty < 0:
- (from_location, to_location, qty) = \
- (to_location, from_location, -qty)
- move_id = move_obj.create(cursor, user, {
- 'from_location': from_location,
- 'to_location': to_location,
- 'quantity': qty,
- 'product': product,
- 'uom': uom,
- 'company': inventory.company.id,
- 'state': 'done',
+ self.write(cursor, user, ids, {
+ 'date': datetime.date.today(),
+ 'moves': [('add', x) for x in moves],
}, context=context)
- moves.append(move_id)
-
- return self.write(cursor, user, ids, {
- 'date': datetime.datetime.today(),
- 'moves': [('add', x) for x in moves],
- }, context=context)
def write(self, cursor, user, ids, vals, context=None):
if 'state' in vals:
@@ -169,9 +136,12 @@ class InventoryLine(OSV):
_description = __doc__
_rec_name = 'product'
- product = fields.Many2One(
- 'product.product', 'Product', required=True, on_change=['product'])
- uom = fields.Many2One('product.uom', 'Uom', required=True, select=1)
+ product = fields.Many2One('product.product', 'Product', required=True,
+ on_change=['product'])
+ uom = fields.Many2One('product.uom', 'UOM', required=True, select=1,
+ readonly=True)
+ expected_quantity = fields.Float('Expected Quantity', digits=(12, 6),
+ readonly=True)
quantity = fields.Float('Quantity', digits=(12, 6))
inventory = fields.Many2One('stock.inventory', 'Inventory')
@@ -211,35 +181,66 @@ class CompleteInventory(Wizard):
line_obj = self.pool.get('stock.inventory.line')
inventory_obj = self.pool.get('stock.inventory')
product_obj = self.pool.get('product.product')
+ uom_obj = self.pool.get('product.uom')
inventories = inventory_obj.browse(cursor, user, data['ids'],
context=context)
- location_ids = []
- inv_by_loc = {}
- for inventory in inventories:
- location_ids.append(inventory.location.id)
- inv_by_loc[inventory.location.id] = inventory.id
-
- pbl = product_obj.products_by_location(cursor, user, location_ids,
- context=context)
- indexed_data = {}
- for line in pbl:
- indexed_data.setdefault(line['location'], []).append(
- (line['product'], line['uom'], line['quantity']))
for inventory in inventories:
- products = [line.product.id for line in inventory.lines]
- for (product, uom, qty) in indexed_data.get(inventory.location.id,[]):
- # Skip products already in the inventory:
- if product in products:
- continue
- if qty <= 0.0:
- qty = 0
- line_obj.create(cursor, user, {
+ # Compute product quantities
+ if inventory.date:
+ context = context.copy()
+ context['stock_date'] = inventory.date
+ pbl = product_obj.products_by_location(
+ cursor, user, [inventory.location.id],
+ context=context)
+ else:
+ pbl = product_obj.products_by_location(
+ cursor, user, [inventory.location.id], context=context)
+ # Index some data
+ uom_by_id = {}
+ for uom in uom_obj.browse(
+ cursor, user, [line['uom'] for line in pbl], context=context):
+ uom_by_id[uom.id] = uom
+ product_qty = {}
+ for line in pbl:
+ product_qty[line['product']] = (line['quantity'],
+ uom_by_id[line['uom']])
+ # Update existing lines
+ for line in inventory.lines:
+ if line.product.id in product_qty:
+ quantity, uom = product_qty[line.product.id]
+ del product_qty[line.product.id]
+ # if nothing as changed, continue
+ if line.quantity == line.expected_quantity == quantity \
+ and line.uom.id == uom.id:
+ continue
+ values = {'expected_quantity': quantity,
+ 'uom': uom.id}
+ # update also quantity field if not edited
+ if line.quantity == line.expected_quantity:
+ values['quantity'] = quantity
+ else:
+ values = {'expected_quantity': 0.0,}
+ if line.quantity == line.expected_quantity:
+ values['quantity'] = 0
+
+
+ line_obj.write(
+ cursor, user, line.id, values, context=context)
+
+ # Create lines if needed
+ for product in product_qty:
+ quantity, uom = product_qty[product]
+ values = {
'product': product,
- 'uom': uom,
- 'quantity': qty,
+ 'expected_quantity': quantity,
+ 'quantity': quantity,
+ 'uom': uom.id,
'inventory': inventory.id,
- }, context=context)
+ }
+ line_obj.create(
+ cursor, user, values, context=context)
+
return {}
CompleteInventory()
diff --git a/inventory.xml b/inventory.xml
index 2e0ed18d63..da3a46fc29 100644
--- a/inventory.xml
+++ b/inventory.xml
@@ -87,13 +87,15 @@
form
+
@@ -107,6 +109,7 @@
+
diff --git a/location.py b/location.py
index e32713270d..f5c0aec536 100644
--- a/location.py
+++ b/location.py
@@ -2,6 +2,7 @@
"Wharehouse"
from trytond.osv import fields, OSV
from trytond.wizard import Wizard, WizardOSV
+import datetime
STATES = {
'readonly': "not active",
@@ -77,11 +78,13 @@ class Location(OSV):
if (not context) or (not context.get('product')):
return dict([(i,0) for i in ids])
+ if name != 'forecast_quantity' and context.get('stock_date'):
+ if context['stock_date'] != datetime.date.today():
+ context = context.copy()
+ del context['stock_date']
pbl = product_obj.products_by_location(cursor, user, location_ids=ids,
- product_ids=[context['product']],
- forecast=(name == 'forecast_quantity'), with_childs=True,
+ product_ids=[context['product']], with_childs=True,
context=context)
-
qty_by_ltn = dict([(i['location'], i['quantity']) for i in pbl])
res = {}
for location_id in ids:
@@ -130,14 +133,14 @@ class Party(OSV):
Party()
-class ChooseForecatsDateInit(WizardOSV):
- _name = 'stock.location_forecast_date.init'
+class ChooseStockDateInit(WizardOSV):
+ _name = 'stock.location_stock_date.init'
forecast_date = fields.Date(
'Forecast Date', help='Allow to compute expected '\
'stock quantities for this date.\n'\
'* An empty value is an infinite date in the future.\n'\
'* A date in the past will provide historical values.')
-ChooseForecatsDateInit()
+ChooseStockDateInit()
class OpenProduct(Wizard):
@@ -147,7 +150,7 @@ class OpenProduct(Wizard):
'init': {
'result': {
'type': 'form',
- 'object': 'stock.location_forecast_date.init',
+ 'object': 'stock.location_stock_date.init',
'state': [
('end', 'Cancel', 'tryton-cancel'),
('open', 'Open', 'tryton-ok', True),
@@ -175,9 +178,10 @@ class OpenProduct(Wizard):
context=context)
res = act_window_obj.read(cursor, user, model_data.db_id, context=context)
- context = {'locations': data['ids']}
+ if context == None: context = {}
+ context['locations'] = data['ids']
if data['form']['forecast_date']:
- context['forecast_date'] = data['form']['forecast_date']
+ context['stock_date'] = data['form']['forecast_date']
res['context'] = str(context)
return res
diff --git a/location.xml b/location.xml
index 041c0d0469..9c2197372c 100644
--- a/location.xml
+++ b/location.xml
@@ -170,8 +170,8 @@
lost_found
-
- stock.location_forecast_date.init
+
+ stock.location_stock_date.init
form
=", "<=", ">", "<", "!="):
return False
@@ -52,21 +54,35 @@ class Product(OSV):
context=context)
if name == 'forecast_quantity':
+ pbl = self.products_by_location(cursor, user,
+ location_ids=context['locations'], with_childs=True,
+ skip_zero=False, context=context)
for line in pbl:
line['forecast_quantity'] = line['quantity']
del line['quantity']
-
+ else:
+ if context.get('stock_date'):
+ if context['stock_date'] != datetime.date.today():
+ context = context.copy()
+ del context['stock_date']
+ pbl = self.products_by_location(cursor, user,
+ location_ids=context['locations'], with_childs=True,
+ skip_zero=False, context=context)
res= [line['product'] for line in pbl \
if self._search_quantity_eval_domain(line, domain)]
return [('id', 'in', res)]
def raw_products_by_location(self, cursor, user, location_ids,
- product_ids=None, forecast=False, with_childs=False, context=None):
+ product_ids=None, with_childs=False, context=None):
"""
Return a list like : [(location, product, uom, qty)] for each
- location and product given as argument. Null qty are not
- returned and the tuple (location, product,uom) is unique.
+ location and product given as argument.
+ Null qty are not returned and the tuple (location, product,uom)
+ is unique.
+ If with_childs, childs location are also computed.
+ The key stock_date in context can be used to compute stock for
+ other date than today.
"""
location_obj = self.pool.get('stock.location')
if not location_ids:
@@ -75,8 +91,8 @@ class Product(OSV):
rule_obj = self.pool.get('ir.rule')
move_query, move_val = rule_obj.domain_get(cursor, user, 'stock.move')
- if forecast and not (context.get('forecast_date') and \
- context['forecast_date'] < datetime.date.today()):
+ if context.get('stock_date') and \
+ context['stock_date'] > datetime.date.today():
in_states = ['done', 'assigned', 'draft']
out_states = ['done', 'assigned', 'draft']
else:
@@ -120,15 +136,15 @@ class Product(OSV):
",".join(["%s" for i in product_ids]) + ")"
where_vals += product_ids
- if forecast and context.get('forecast_date'):
- where_clause += ' AND ('\
- '(effective_date IS NULL '\
- 'AND ( planned_date <= %s or planned_date IS NULL)) '\
- 'OR effective_date <= %s'\
- ')'
- where_date = [context['forecast_date'],context['forecast_date']]
+ where_clause += ' AND ('\
+ '(effective_date IS NULL '\
+ 'AND ( planned_date <= %s or planned_date IS NULL)) '\
+ 'OR effective_date <= %s'\
+ ')'
+ if context.get('stock_date'):
+ where_date = [context['stock_date'], context['stock_date']]
else:
- where_date = []
+ where_date = [datetime.date.today(), datetime.date.today()]
cursor.execute(select_clause % (
",".join(["%s" for i in in_states]), where_clause,
",".join(["%s" for i in out_states]), where_clause),
@@ -138,8 +154,16 @@ class Product(OSV):
return cursor.fetchall()
def products_by_location(self, cursor, user, location_ids,
- product_ids=None, forecast=False, with_childs=False, context=None):
-
+ product_ids=None, with_childs=False, skip_zero=True, context=None):
+ """
+ Return a list like :
+ [{location: 1, product: 1, uom: 1, qty: 2}]
+ for each location and product given as argument.
+ The key stock_date in context can be used to compute stock for other
+ date than today.
+ If with_childs, childs locations are also computed.
+ If skip_zero, list item with quantity equal to zero are not returned.
+ """
uom_obj = self.pool.get("product.uom")
product_obj = self.pool.get("product.product")
@@ -148,8 +172,7 @@ class Product(OSV):
res = {}
raw_lines = self.raw_products_by_location(cursor, user, location_ids,
- product_ids, forecast=forecast, with_childs=with_childs,
- context=context)
+ product_ids, with_childs=with_childs, context=context)
uom_ids = []
product_ids = []
@@ -168,12 +191,27 @@ class Product(OSV):
res.setdefault(key, 0.0)
res[key] += uom_obj.compute_qty(cursor, user, uom_by_id[uom],
quantity, default_uom[product], context=context)
- return [{
- 'location': key[0],
- 'product':key[1],
- 'uom': key[2],
- 'quantity': val,
- } for key, val in res.iteritems()]
+ # Remove seen products from product_ids
+ if key[1] in product_ids:
+ product_ids.remove(key[1])
+
+ res = [{'location': key[0],
+ 'product': key[1],
+ 'uom': key[2],
+ 'quantity': val} for key, val in res.iteritems()]
+
+ # Complete result with missing products if asked
+ if not skip_zero:
+ for location_id in location_ids:
+ for product_id in product_ids:
+ res.append({
+ 'location': location_id,
+ 'product': product_id,
+ 'uom': default_uom[product_id].id,
+ 'quantity': 0.0,
+ })
+
+ return res
def view_header_get(self, cursor, user, value, view_type='form',
context=None):
@@ -187,14 +225,14 @@ class Product(OSV):
Product()
-class ChooseForecatsDateInit(WizardOSV):
- _name = 'stock.product_forecast_date.init'
+class ChooseStockDateInit(WizardOSV):
+ _name = 'stock.product_stock_date.init'
forecast_date = fields.Date(
'Forecast Date', help='Allow to compute expected '\
'stock quantities for this date.\n'\
'* An empty value is an infinite date in the future.\n'\
'* A date in the past will provide historical values.')
-ChooseForecatsDateInit()
+ChooseStockDateInit()
class OpenLocation(Wizard):
'Products by Locations'
@@ -203,7 +241,7 @@ class OpenLocation(Wizard):
'init': {
'result': {
'type': 'form',
- 'object': 'stock.product_forecast_date.init',
+ 'object': 'stock.product_stock_date.init',
'state': [
('end', 'Cancel', 'tryton-cancel'),
('open', 'Open', 'tryton-ok', True),
@@ -231,9 +269,10 @@ class OpenLocation(Wizard):
context=context)
res = act_window_obj.read(cursor, user, model_data.db_id, context=context)
- context = {'product': data['id']}
+ if context == None: context = {}
+ context['product'] = data['id']
if data['form']['forecast_date']:
- context['forecast_date'] = data['form']['forecast_date']
+ context['stock_date'] = data['form']['forecast_date']
res['context'] = str(context)
return res
diff --git a/product.xml b/product.xml
index d878b62229..08cb3618d9 100644
--- a/product.xml
+++ b/product.xml
@@ -57,8 +57,8 @@
-
- product_forecast_date.init
+
+ product_stock_date.init
form