1 # -*- encoding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2008 Tiny SPRL (<http://tiny.be>). All Rights Reserved
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 ##############################################################################
25 from mx.DateTime import RelativeDateTime, now, DateTime, localtime
27 from osv import osv, fields
29 from tools.translate import _
30 from tools import config
33 def rounding(fl, round_value):
36 return round(fl / round_value) * round_value
38 # Object creating periods quickly
39 # changed that stop_date is created with hour 23:59:00 when it was 00:00:00 stop date was excluded from period
40 class stock_period_createlines(osv.osv_memory):
41 _name = "stock.period.createlines"
43 def _get_new_period_start(self, cr, uid, context=None):
44 cr.execute("select max(date_stop) from stock_period")
45 result = cr.fetchone()
46 last_date = result and result[0] or False
48 period_start = mx.DateTime.strptime(last_date,"%Y-%m-%d %H:%M:%S")+ RelativeDateTime(days=1)
49 period_start = period_start - RelativeDateTime(hours=period_start.hour, minutes=period_start.minute, seconds=period_start.second)
51 period_start = mx.DateTime.today()
52 return period_start.strftime('%Y-%m-%d')
56 'name': fields.char('Period Name', size=64),
57 'date_start': fields.date('Start Date', required=True),
58 'date_stop': fields.date('End Date', required=True),
59 'period_ids': fields.one2many('stock.period', 'planning_id', 'Periods'),
62 'date_start': _get_new_period_start,
65 def create_period_weekly(self, cr, uid, ids, context=None):
66 res = self.create_period(cr, uid, ids, context, 6, 'Weekly')
70 'res_model': 'stock.period',
71 'type': 'ir.actions.act_window',
74 def create_period_monthly(self,cr, uid, ids, interval=1, context=None):
75 for p in self.browse(cr, uid, ids, context=context):
77 ds = mx.DateTime.strptime(p.date_start, '%Y-%m-%d')
78 while ds.strftime('%Y-%m-%d') < p.date_stop:
79 de = ds + RelativeDateTime(months=interval, minutes=-1)
80 self.pool.get('stock.period').create(cr, uid, {
81 'name': ds.strftime('%Y/%m'),
82 'date_start': ds.strftime('%Y-%m-%d'),
83 'date_stop': de.strftime('%Y-%m-%d %H:%M:%S'),
85 ds = ds + RelativeDateTime(months=interval)
89 'res_model': 'stock.period',
90 'type': 'ir.actions.act_window',
93 def create_period(self, cr, uid, ids, interval=0, name='Daily', context=None):
94 for p in self.browse(cr, uid, ids, context=context):
96 ds = mx.DateTime.strptime(p.date_start, '%Y-%m-%d')
97 while ds.strftime('%Y-%m-%d') < p.date_stop:
98 de = ds + RelativeDateTime(days=interval, minutes =-1)
100 new_name=de.strftime('%Y-%m-%d')
102 new_name = de.strftime('%Y, week %W')
103 self.pool.get('stock.period').create(cr, uid, {
105 'date_start': ds.strftime('%Y-%m-%d'),
106 'date_stop': de.strftime('%Y-%m-%d %H:%M:%S'),
108 ds = ds + RelativeDateTime(days=interval) + 1
112 'res_model': 'stock.period',
113 'type': 'ir.actions.act_window',
115 stock_period_createlines()
117 # Periods have no company_id field as they can be shared across similar companies.
118 # If somone thinks different it can be improved.
119 class stock_period(osv.osv):
120 _name = "stock.period"
121 _description = "stock period"
123 'name': fields.char('Period Name', size=64, required=True),
124 'date_start': fields.datetime('Start Date', required=True),
125 'date_stop': fields.datetime('End Date', required=True),
126 'state': fields.selection([('draft','Draft'), ('open','Open'),('close','Close')], 'State')
134 # Creates forecasts records for products from selected Product Category for selected 'Warehouse - Period'
135 # Object added by contributor in ver 1.1
136 class stock_sale_forecast_createlines(osv.osv_memory):
137 _name = "stock.sale.forecast.createlines"
138 _description = "stock.sale.forecast.createlines"
140 # FIXME Add some period sugestion like below
142 # def _get_latest_period(self,cr,uid,context={}):
143 # cr.execute("select max(date_stop) from stock_period")
144 # result=cr.fetchone()
145 # return result and result[0] or False
149 'company_id': fields.many2one('res.company', 'Company', required=True, select=1),
150 'warehouse_id1': fields.many2one('stock.warehouse' , 'Warehouse', required=True, \
151 help='Warehouse which forecasts will concern. '\
152 'If during stock planning you will need sales forecast for all warehouses choose any warehouse now.'),
153 'period_id1': fields.many2one('stock.period' , 'Period', required=True, help = 'Period which forecasts will concern.' ),
154 'product_categ_id1': fields.many2one('product.category' , 'Product Category', required=True, \
155 help ='Product Category of products which created forecasts will concern.'),
156 'copy_forecast': fields.boolean('Copy Last Forecast', help="Copy quantities from last Stock and Sale Forecast."),
160 'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.sale.forecast.createlines', context=c),
163 def create_forecast(self, cr, uid, ids, context=None):
164 product_obj = self.pool.get('product.product')
165 forecast_obj = self.pool.get('stock.sale.forecast')
166 mod_obj = self.pool.get('ir.model.data')
167 prod_categ_obj = self.pool.get('product.category')
168 template_obj = self.pool.get('product.template')
169 for f in self.browse(cr, uid, ids, context=context):
170 categ_ids = f.product_categ_id1.id and [f.product_categ_id1.id] or []
171 prod_categ_ids = prod_categ_obj.search(cr, uid, [('parent_id','child_of', categ_ids)])
172 templates_ids = template_obj.search(cr, uid, [('categ_id','in',prod_categ_ids)])
173 products_ids = product_obj.search(cr, uid, [('product_tmpl_id','in',templates_ids)])
174 if len(products_ids) == 0:
175 raise osv.except_osv(_('Error !'), _('No products in selected category !'))
176 copy = f.copy_forecast
177 for p in product_obj.browse(cr, uid, products_ids,{}):
178 if len(forecast_obj.search(cr, uid, [('product_id','=',p.id) , \
179 ('period_id','=',f.period_id1.id), \
180 ('user_id','=',uid), \
181 ('warehouse_id','=',f.warehouse_id1.id)]))== 0:
183 # Not sure if it is expected quantity for this feature (copying forecast from previous period)
184 # because it can take incidental forecast of this warehouse, this product and this user (creating, writing or validating forecast).
185 # It takes only one forecast line (no sum). If user creates only one forecast per period it will be OK. If not I have no idea how to count it.
189 cr.execute("SELECT period.date_stop, forecast.product_qty, forecast.product_uom \
190 FROM stock_sale_forecast AS forecast \
191 LEFT JOIN stock_period AS period \
192 ON forecast.period_id = period.id \
193 WHERE (forecast.user_id = %s OR forecast.create_uid = %s OR forecast.write_uid = %s) \
194 AND forecast.warehouse_id = %s AND forecast.product_id = %s \
195 AND period.date_stop < %s \
196 ORDER BY period.date_stop DESC",
197 (uid, uid, uid, f.warehouse_id1.id, p.id, f.period_id1.date_stop) )
200 forecast_qty = ret[1]
202 prod_uom = prod_uom or p.uom_id.id
203 prod_uos_categ = False
205 prod_uos_categ = p.uos_id.category_id.id
206 forecast_obj.create(cr, uid, {
207 'company_id': f.warehouse_id1.company_id.id,
208 'period_id': f.period_id1.id,
209 'warehouse_id': f.warehouse_id1.id,
211 'product_qty': forecast_qty,
213 'product_uom': prod_uom,
214 'active_uom': prod_uom,
215 'product_uom_categ': p.uom_id.category_id.id,
216 'product_uos_categ': prod_uos_categ,
218 result = mod_obj._get_id(cr, uid, 'stock_planning', 'view_stock_sale_forecast_filter')
219 id = mod_obj.read(cr, uid, result, ['res_id'], context=context)
224 'res_model': 'stock.sale.forecast',
225 'type': 'ir.actions.act_window',
226 'search_view_id': id['res_id'],
229 stock_sale_forecast_createlines()
232 # Stock and Sales Forecast object. Previously stock_planning_sale_prevision.
233 # A lot of changes in 1.1
234 class stock_sale_forecast(osv.osv):
235 _name = "stock.sale.forecast"
238 'company_id':fields.many2one('res.company', 'Company', required=True),
239 'create_uid': fields.many2one('res.users', 'Responsible'),
240 'name': fields.char('Name', size=64, readonly=True, states={'draft': [('readonly',False)]}),
241 'user_id': fields.many2one('res.users' , 'Created/Validated by',readonly=True, \
242 help='Shows who created this forecast, or who validated.'),
243 'warehouse_id': fields.many2one('stock.warehouse' , 'Warehouse', required=True, readonly=True, states={'draft': [('readonly',False)]}, \
244 help='Shows which warehouse this forecast concerns. '\
245 'If during stock planning you will need sales forecast for all warehouses choose any warehouse now.'),
246 'period_id': fields.many2one('stock.period' , 'Period', required=True, readonly=True, states={'draft':[('readonly',False)]}, \
247 help = 'Shows which period this forecast concerns.'),
248 'product_id': fields.many2one('product.product' , 'Product', readonly=True, required=True, states={'draft':[('readonly',False)]}, \
249 help = 'Shows which product this forecast concerns.'),
250 'product_qty': fields.float('Product Quantity', required=True, readonly=True, states={'draft':[('readonly',False)]}, \
251 help= 'Forecasted quantity.'),
252 'product_amt': fields.float('Product Amount', readonly=True, states={'draft':[('readonly',False)]}, \
253 help='Forecast value which will be converted to Product Quantity according to prices.'),
254 'product_uom_categ': fields.many2one('product.uom.categ', 'Product UoM Category'), # Invisible field for product_uom domain
255 'product_uom': fields.many2one('product.uom', 'Product UoM', required=True, readonly=True, states={'draft':[('readonly',False)]}, \
256 help = "Unit of Measure used to show the quanities of stock calculation." \
257 "You can use units form default category or from second category (UoS category)."),
258 'product_uos_categ' : fields.many2one('product.uom.categ', 'Product UoS Category'), # Invisible field for product_uos domain
259 # Field used in onchange_uom to check what uom was before change and recalculate quantities acording to old uom (active_uom) and new uom.
260 'active_uom': fields.many2one('product.uom', string = "Active UoM"),
261 'state': fields.selection([('draft','Draft'),('validated','Validated')],'State',readonly=True),
262 'analyzed_period1_id': fields.many2one('stock.period' , 'Period1', readonly=True, states={'draft':[('readonly',False)]},),
263 'analyzed_period2_id': fields.many2one('stock.period' , 'Period2', readonly=True, states={'draft':[('readonly',False)]},),
264 'analyzed_period3_id': fields.many2one('stock.period' , 'Period3', readonly=True, states={'draft':[('readonly',False)]},),
265 'analyzed_period4_id': fields.many2one('stock.period' , 'Period4', readonly=True, states={'draft':[('readonly',False)]},),
266 'analyzed_period5_id': fields.many2one('stock.period' , 'Period5', readonly=True, states={'draft':[('readonly',False)]},),
267 'analyzed_user_id': fields.many2one('res.users' , 'This User', required=False, readonly=True, states={'draft':[('readonly',False)]},),
268 'analyzed_dept_id': fields.many2one('hr.department' , 'This Department', required=False, \
269 readonly=True, states={'draft':[('readonly',False)]},),
270 'analyzed_warehouse_id': fields.many2one('stock.warehouse' , 'This Warehouse', required=False, \
271 readonly=True, states={'draft':[('readonly',False)]}),
272 'analyze_company': fields.boolean('Per Company', readonly=True, states={'draft':[('readonly',False)]}, \
273 help = "Check this box to see the sales for whole company."),
274 'analyzed_period1_per_user': fields.float('This User Period1', readonly=True),
275 'analyzed_period2_per_user': fields.float('This User Period2', readonly=True),
276 'analyzed_period3_per_user': fields.float('This User Period3', readonly=True),
277 'analyzed_period4_per_user': fields.float('This User Period4', readonly=True),
278 'analyzed_period5_per_user': fields.float('This User Period5', readonly=True),
279 'analyzed_period1_per_dept': fields.float('This Dept Period1', readonly=True),
280 'analyzed_period2_per_dept': fields.float('This Dept Period2', readonly=True),
281 'analyzed_period3_per_dept': fields.float('This Dept Period3', readonly=True),
282 'analyzed_period4_per_dept': fields.float('This Dept Period4', readonly=True),
283 'analyzed_period5_per_dept': fields.float('This Dept Period5', readonly=True),
284 'analyzed_period1_per_warehouse': fields.float('This Warehouse Period1', readonly=True),
285 'analyzed_period2_per_warehouse': fields.float('This Warehouse Period2', readonly=True),
286 'analyzed_period3_per_warehouse': fields.float('This Warehouse Period3', readonly=True),
287 'analyzed_period4_per_warehouse': fields.float('This Warehouse Period4', readonly=True),
288 'analyzed_period5_per_warehouse': fields.float('This Warehouse Period5', readonly=True),
289 'analyzed_period1_per_company': fields.float('This Copmany Period1', readonly=True),
290 'analyzed_period2_per_company': fields.float('This Company Period2', readonly=True),
291 'analyzed_period3_per_company': fields.float('This Company Period3', readonly=True),
292 'analyzed_period4_per_company': fields.float('This Company Period4', readonly=True),
293 'analyzed_period5_per_company': fields.float('This Company Period5', readonly=True),
296 'user_id': lambda obj, cr, uid, context: uid,
298 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.sale.forecast', context=c),
301 def action_validate(self, cr, uid, ids, *args):
302 self.write(cr, uid, ids, {'state':'validated','user_id':uid})
305 def unlink(self, cr, uid, ids, context=None):
306 forecasts = self.read(cr, uid, ids, ['state'])
309 if t['state'] in ('draft'):
310 unlink_ids.append(t['id'])
312 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Validated Sale Forecasts !'))
313 osv.osv.unlink(self, cr, uid, unlink_ids,context=context)
316 def onchange_company(self, cr, uid, ids, company_id=False):
320 result['warehouse_id'] = False
321 result['analyzed_user_id'] = False
322 result['analyzed_dept_id'] = False
323 result['analyzed_warehouse_id'] = False
324 return {'value': result}
326 def product_id_change(self, cr, uid, ids, product_id=False):
329 product_rec = self.pool.get('product.product').browse(cr, uid, product_id)
330 ret['product_uom'] = product_rec.uom_id.id
331 ret['product_uom_categ'] = product_rec.uom_id.category_id.id
332 ret['product_uos_categ'] = product_rec.uos_id and product_rec.uos_id.category_id.id or False
333 ret['active_uom'] = product_rec.uom_id.id
335 ret['product_uom'] = False
336 ret['product_uom_categ'] = False
337 ret['product_uos_categ'] = False
341 def onchange_uom(self, cr, uid, ids, product_uom=False, product_qty=0.0, active_uom=False):
346 val1 = self.browse(cr, uid, ids)
348 coeff_uom2def = self._to_default_uom_factor(cr, uid, val, active_uom, {})
349 coeff_def2uom, round_value = self._from_default_uom_factor( cr, uid, val, product_uom, {})
350 coeff = coeff_uom2def * coeff_def2uom
351 ret['product_qty'] = rounding(coeff * product_qty, round_value)
352 ret['active_uom'] = product_uom
353 return {'value': ret}
355 def product_amt_change(self, cr, uid, ids, product_amt = 0.0, product_uom=False):
362 val1 = self.browse(cr, uid, ids)
364 if (product_uom != val.product_id.uom_id.id):
365 coeff_def2uom, round_value = self._from_default_uom_factor( cr, uid, val, product_uom, {})
366 ret['product_qty'] = rounding(coeff_def2uom * product_amt/(val.product_id.product_tmpl_id.list_price), round_value)
370 def _to_default_uom_factor(self, cr, uid, val, uom_id, context=None):
371 uom_obj = self.pool.get('product.uom')
372 uom = uom_obj.browse(cr, uid, uom_id, context=context)
374 if uom.category_id.id <> val.product_id.uom_id.category_id.id:
375 coef = coef / val.product_id.uos_coeff
376 return val.product_id.uom_id.factor / coef
378 def _from_default_uom_factor(self, cr, uid, val, uom_id, context):
379 uom_obj = self.pool.get('product.uom')
380 uom = uom_obj.browse(cr, uid, uom_id, context=context)
382 if uom.category_id.id <> val.product_id.uom_id.category_id.id:
383 res = res / val.product_id.uos_coeff
384 return res / val.product_id.uom_id.factor, uom.rounding
386 def _sales_per_users(self, cr, uid, so, so_line, company, users):
387 cr.execute("SELECT sum(sol.product_uom_qty) FROM sale_order_line AS sol LEFT JOIN sale_order AS s ON (s.id = sol.order_id) " \
388 "WHERE (sol.id IN %s) AND (s.state NOT IN (\'draft\',\'cancel\')) AND (s.id IN %s) AND (s.company_id=%s) " \
389 "AND (s.user_id IN %s) " ,(tuple(so_line), tuple(so), company, tuple(users)))
390 ret = cr.fetchone()[0] or 0.0
393 def _sales_per_warehouse(self, cr, uid, so, so_line, company, shops):
394 cr.execute("SELECT sum(sol.product_uom_qty) FROM sale_order_line AS sol LEFT JOIN sale_order AS s ON (s.id = sol.order_id) " \
395 "WHERE (sol.id IN %s) AND (s.state NOT IN (\'draft\',\'cancel\')) AND (s.id IN %s)AND (s.company_id=%s) " \
396 "AND (s.shop_id IN %s)" ,(tuple(so_line), tuple(so), company, tuple(shops)))
397 ret = cr.fetchone()[0] or 0.0
400 def _sales_per_company(self, cr, uid, so, so_line, company):
401 cr.execute("SELECT sum(sol.product_uom_qty) FROM sale_order_line AS sol LEFT JOIN sale_order AS s ON (s.id = sol.order_id) " \
402 "WHERE (sol.id IN %s) AND (s.state NOT IN (\'draft\',\'cancel\')) AND (s.id IN %s) AND (s.company_id=%s)", (tuple(so_line), tuple(so), company))
403 ret = cr.fetchone()[0] or 0.0
406 def calculate_sales_history(self, cr, uid, ids, context, *args):
407 sales = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],]
408 for obj in self.browse(cr, uid, ids):
409 periods = obj.analyzed_period1_id, obj.analyzed_period2_id, obj.analyzed_period3_id, obj.analyzed_period4_id, obj.analyzed_period5_id
410 so_obj = self.pool.get('sale.order')
411 so_line_obj = self.pool.get('sale.order.line')
412 so_line_product_ids = so_line_obj.search(cr, uid, [('product_id','=', obj.product_id.id)], context = context)
413 if so_line_product_ids:
414 so_line_product_set = ','.join(map(str,so_line_product_ids))
415 if obj.analyzed_warehouse_id:
416 shops = self.pool.get('sale.shop').search(cr, uid,[('warehouse_id','=', obj.analyzed_warehouse_id.id)], context = context)
417 shops_set = ','.join(map(str,shops))
420 if obj.analyzed_dept_id:
421 dept_obj = self.pool.get('hr.department')
422 dept_id = obj.analyzed_dept_id.id and [obj.analyzed_dept_id.id] or []
423 dept_ids = dept_obj.search(cr,uid,[('parent_id','child_of',dept_id)])
424 # dept_ids_set = ','.join(map(str,dept_ids))
425 cr.execute("SELECT user_id FROM hr_department_user_rel WHERE (department_id IN %s)" ,(tuple(dept_ids),))
426 dept_users = [x for x, in cr.fetchall()]
427 dept_users_set = ','.join(map(str,dept_users))
430 factor, round_value = self._from_default_uom_factor(cr, uid, obj, obj.product_uom.id, context=context)
431 for i, period in enumerate(periods):
433 so_period_ids = so_obj.search(cr, uid, [('date_order','>=',period.date_start),('date_order','<=',period.date_stop) ], context = context)
435 # so_period_set = ','.join(map(str,so_period_ids))
436 if obj.analyzed_user_id:
437 user_set = str(obj.analyzed_user_id.id)
439 sales[i][0] = self._sales_per_users(cr, uid, so_period_ids, so_line_product_ids, obj.company_id.id, user_set)
440 sales[i][0] *= factor
442 sales[i][1] = self._sales_per_users(cr, uid, so_period_ids, so_line_product_ids, obj.company_id.id, dept_users_set)
443 sales[i][1] *= factor
445 sales[i][2] = self._sales_per_warehouse(cr, uid, so_period_ids, so_line_product_ids, obj.company_id.id, shops_set)
446 sales[i][2] *= factor
447 if obj.analyze_company:
448 sales[i][3] = self._sales_per_company(cr, uid, so_period_ids, so_line_product_ids, obj.company_id.id, )
449 sales[i][3] *= factor
450 self.write(cr, uid, ids, {
451 'analyzed_period1_per_user': sales[0][0],
452 'analyzed_period2_per_user': sales[1][0],
453 'analyzed_period3_per_user': sales[2][0],
454 'analyzed_period4_per_user': sales[3][0],
455 'analyzed_period5_per_user': sales[4][0],
456 'analyzed_period1_per_dept': sales[0][1],
457 'analyzed_period2_per_dept': sales[1][1],
458 'analyzed_period3_per_dept': sales[2][1],
459 'analyzed_period4_per_dept': sales[3][1],
460 'analyzed_period5_per_dept': sales[4][1],
461 'analyzed_period1_per_warehouse': sales[0][2],
462 'analyzed_period2_per_warehouse': sales[1][2],
463 'analyzed_period3_per_warehouse': sales[2][2],
464 'analyzed_period4_per_warehouse': sales[3][2],
465 'analyzed_period5_per_warehouse': sales[4][2],
466 'analyzed_period1_per_company': sales[0][3],
467 'analyzed_period2_per_company': sales[1][3],
468 'analyzed_period3_per_company': sales[2][3],
469 'analyzed_period4_per_company': sales[3][3],
470 'analyzed_period5_per_company': sales[4][3],
475 stock_sale_forecast()
477 # Creates stock planning records for products from selected Product Category for selected 'Warehouse - Period'
478 # Object added by contributor in ver 1.1
479 class stock_planning_createlines(osv.osv_memory):
480 _name = "stock.planning.createlines"
482 def onchange_company(self, cr, uid, ids, company_id=False):
485 result['warehouse_id2'] = False
486 return {'value': result}
489 'company_id': fields.many2one('res.company', 'Company', required=True),
490 'period_id2': fields.many2one('stock.period' , 'Period', required=True, help = 'Period which planning will concern.'),
491 'warehouse_id2': fields.many2one('stock.warehouse' , 'Warehouse', required=True, help = 'Warehouse which planning will concern.'),
492 'product_categ_id2': fields.many2one('product.category' , 'Product Category', \
493 help = 'Planning will be created for products from Product Category selected by this field. '\
494 'This field is ignored when you check \"All Forecasted Product\" box.' ),
495 'forecasted_products': fields.boolean('All Products with Forecast', \
496 help = "Check this box to create planning for all products having any forecast for selected Warehouse and Period. "\
497 "Product Category field will be ignored."),
501 'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.planning', context=c),
504 def create_planning(self,cr, uid, ids, context=None):
507 product_obj = self.pool.get('product.product')
508 planning_obj = self.pool.get('stock.planning')
509 mod_obj = self.pool.get('ir.model.data')
510 for f in self.browse(cr, uid, ids, context=context):
511 if f.forecasted_products:
512 cr.execute("SELECT product_id \
513 FROM stock_sale_forecast \
514 WHERE (period_id = %s) AND (warehouse_id = %s)", (f.period_id2.id, f.warehouse_id2.id))
515 products_id1 = [x for x, in cr.fetchall()]
517 prod_categ_obj = self.pool.get('product.category')
518 template_obj = self.pool.get('product.template')
519 categ_ids = f.product_categ_id2.id and [f.product_categ_id2.id] or []
520 prod_categ_ids = prod_categ_obj.search(cr,uid,[('parent_id','child_of',categ_ids)])
521 templates_ids = template_obj.search(cr,uid,[('categ_id','in',prod_categ_ids)])
522 products_id1 = product_obj.search(cr,uid,[('product_tmpl_id','in',templates_ids)])
523 if len(products_id1)==0:
524 raise osv.except_osv(_('Error !'), _('No forecasts for selected period or no products in selected category !'))
526 for p in product_obj.browse(cr, uid, products_id1,context=context):
527 if len(planning_obj.search(cr, uid, [('product_id','=',p.id),
528 ('period_id','=',f.period_id2.id),
529 ('warehouse_id','=',f.warehouse_id2.id)]))== 0:
530 cr.execute("SELECT period.date_stop, planning.product_uom, planning.planned_outgoing, planning.to_procure, \
531 planning.stock_only, planning.procure_to_stock, planning.confirmed_forecasts_only, \
532 planning.supply_warehouse_id, planning.stock_supply_location \
533 FROM stock_planning AS planning \
534 LEFT JOIN stock_period AS period \
535 ON planning.period_id = period.id \
536 WHERE (planning.create_uid = %s OR planning.write_uid = %s) \
537 AND planning.warehouse_id = %s AND planning.product_id = %s \
538 AND period.date_stop < %s \
539 ORDER BY period.date_stop DESC",
540 (uid, uid, f.warehouse_id2.id, p.id, f.period_id2.date_stop) )
542 # forecast_qty = ret and ret[0] or 0.0
544 # raise osv.except_osv(_('Error !'), _('ret is %s %s %s %s %s %s')%(ret[0],ret[2],ret[3],ret[4],ret[5],ret[6],))
549 procure_to_stock = ret[5]
550 confirmed_forecasts_only = ret[6]
551 supply_warehouse_id = ret[7]
552 stock_supply_location = ret[8]
554 prod_uom = p.uom_id.id
558 procure_to_stock = False
559 confirmed_forecasts_only = False
560 supply_warehouse_id = False
561 stock_supply_location = False
562 prod_uos_categ = False
564 prod_uos_categ = p.uos_id.category_id.id
565 planning_obj.create(cr, uid, {
566 'company_id' : f.warehouse_id2.company_id.id,
567 'period_id': f.period_id2.id,
568 'warehouse_id' : f.warehouse_id2.id,
570 'product_uom' : prod_uom,
571 'product_uom_categ' : p.uom_id.category_id.id,
572 'product_uos_categ' : prod_uos_categ,
573 'active_uom' : prod_uom,
574 'planned_outgoing': planned_out,
575 'to_procure': to_procure,
576 'stock_only': stock_only,
577 'procure_to_stock': procure_to_stock,
578 'confirmed_forecasts_only': confirmed_forecasts_only,
579 'supply_warehouse_id': supply_warehouse_id,
580 'stock_supply_location': stock_supply_location,
583 result = mod_obj._get_id(cr, uid, 'stock_planning', 'view_stock_planning_filter')
584 id = mod_obj.read(cr, uid, result, ['res_id'], context=context)
588 'res_model': 'stock.planning',
589 'type': 'ir.actions.act_window',
590 'search_view_id': id['res_id'],
592 stock_planning_createlines()
595 # The main Stock Planning object
596 # A lot of changes by contributor in ver 1.1
597 class stock_planning(osv.osv):
598 _name = "stock.planning"
600 def _get_in_out(self, cr, uid, val, date_start, date_stop, direction, done, context=None):
604 product_obj = self.pool.get('product.product')
606 'field': "incoming_qty",
607 'adapter': lambda x: x,
610 'field': "outgoing_qty",
611 'adapter': lambda x: -x,
614 context['from_date'] = date_start
615 context['to_date'] = date_stop
616 locations = [val.warehouse_id.lot_stock_id.id,]
617 if not val.stock_only:
618 locations.extend([val.warehouse_id.lot_input_id.id, val.warehouse_id.lot_output_id.id])
619 context['location'] = locations
620 context['compute_child'] = True
621 prod_id = val.product_id.id
624 c1.update({ 'states':('done',), 'what':(direction,) })
626 st = product_obj.get_product_available(cr,uid, prod_ids, context=c1)
627 res = mapping[direction]['adapter'](st.get(prod_id,0.0))
629 product = product_obj.read(cr, uid, prod_id,[], context)
630 product_qty = product[mapping[direction]['field']]
631 res = mapping[direction]['adapter'](product_qty)
632 # res[val.id] = product_obj['incoming_qty']
635 def _get_outgoing_before(self, cr, uid, val, date_start, date_stop, context=None):
636 cr.execute("SELECT sum(planning.planned_outgoing), planning.product_uom \
637 FROM stock_planning AS planning \
638 LEFT JOIN stock_period AS period \
639 ON (planning.period_id = period.id) \
640 WHERE (period.date_stop >= %s) AND (period.date_stop <= %s) \
641 AND (planning.product_id = %s) AND (planning.company_id = %s) \
642 GROUP BY planning.product_uom", \
643 (date_start, date_stop, val.product_id.id, val.company_id.id,))
644 planning_qtys = cr.fetchall()
645 res = self._to_planning_uom(cr, uid, val, planning_qtys, context)
648 def _to_planning_uom(self, cr, uid, val, qtys, context):
651 uom_obj = self.pool.get('product.uom')
652 for qty, prod_uom in qtys:
653 coef = self._to_default_uom_factor(cr, uid, val, prod_uom, context=context)
654 res_coef, round_value = self._from_default_uom_factor(cr, uid, val, val.product_uom.id, context=context)
655 coef = coef * res_coef
656 res_qty += rounding(qty * coef, round_value)
660 def _get_forecast(self, cr, uid, ids, field_names, arg, context=None):
662 for val in self.browse(cr, uid, ids):
664 valid_part = val.confirmed_forecasts_only and " AND state = 'validated'" or ""
665 cr.execute('SELECT sum(product_qty), product_uom \
666 FROM stock_sale_forecast \
667 WHERE product_id = %s AND period_id = %s AND company_id = %s '+valid_part+ \
668 'GROUP BY product_uom', \
669 (val.product_id.id,val.period_id.id, val.company_id.id))
670 company_qtys = cr.fetchall()
671 res[val.id]['company_forecast'] = self._to_planning_uom(cr, uid, val, company_qtys, context)
673 cr.execute('SELECT sum(product_qty), product_uom \
674 FROM stock_sale_forecast \
675 WHERE product_id = %s and period_id = %s AND warehouse_id = %s ' + valid_part + \
676 'GROUP BY product_uom', \
677 (val.product_id.id,val.period_id.id, val.warehouse_id.id))
678 warehouse_qtys = cr.fetchall()
679 res[val.id]['warehouse_forecast'] = self._to_planning_uom(cr, uid, val, warehouse_qtys, context)
680 res[val.id]['warehouse_forecast'] = rounding(res[val.id]['warehouse_forecast'], val.product_id.uom_id.rounding)
683 def _get_stock_start(self, cr, uid, val, date, context=None):
684 context['from_date'] = None
685 context['to_date'] = date
686 locations = [val.warehouse_id.lot_stock_id.id,]
687 if not val.stock_only:
688 locations.extend([val.warehouse_id.lot_input_id.id, val.warehouse_id.lot_output_id.id])
689 context['location'] = locations
690 context['compute_child'] = True
691 product_obj = self.pool.get('product.product').read(cr, uid,val.product_id.id,[], context)
692 res = product_obj['qty_available'] # value for stock_start
695 def _get_past_future(self, cr, uid, ids, field_names, arg, context):
697 for val in self.browse(cr, uid, ids, context=context):
698 if val.period_id.date_stop < time.strftime('%Y-%m-%d'):
701 res[val.id] = 'Future'
704 def _get_op(self, cr, uid, ids, field_names, arg, context=None): # op = OrderPoint
706 for val in self.browse(cr, uid, ids, context=context):
708 cr.execute("SELECT product_min_qty, product_max_qty, product_uom \
709 FROM stock_warehouse_orderpoint \
710 WHERE warehouse_id = %s AND product_id = %s AND active = 'TRUE'", (val.warehouse_id.id, val.product_id.id))
711 ret = cr.fetchone() or [0.0,0.0,False]
715 coef = self._to_default_uom_factor(cr, uid, val, ret[2], context)
716 res_coef, round_value = self._from_default_uom_factor(cr, uid, val, val.product_uom.id, context=context)
717 coef = coef * res_coef
718 res[val.id]['minimum_op'] = rounding(ret[0]*coef, round_value)
719 res[val.id]['maximum_op'] = ret[1]*coef
722 def onchange_company(self, cr, uid, ids, company_id=False):
725 result['warehouse_id'] = False
726 return {'value': result}
728 def onchange_uom(self, cr, uid, ids, product_uom=False):
734 val1 = self.browse(cr, uid, ids)
737 coeff_uom2def = self._to_default_uom_factor(cr, uid, val, val.active_uom.id, {})
738 coeff_def2uom, round_value = self._from_default_uom_factor( cr, uid, val, product_uom, {})
739 coeff = coeff_uom2def * coeff_def2uom
740 ret['planned_outgoing'] = rounding(coeff * val.planned_outgoing, round_value)
741 ret['to_procure'] = rounding(coeff * val.to_procure, round_value)
742 ret['active_uom'] = product_uom
743 return {'value': ret}
746 'company_id': fields.many2one('res.company', 'Company', required = True),
747 'history': fields.text('Procurement History', readonly=True, help = "History of procurement or internal supply of this planning line."),
748 'state' : fields.selection([('draft','Draft'),('done','Done')],'State',readonly=True),
749 'period_id': fields.many2one('stock.period' , 'Period', required=True, \
750 help = 'Period for this planning. Requisition will be created for beginning of the period.'),
751 'warehouse_id': fields.many2one('stock.warehouse','Warehouse', required=True),
752 'product_id': fields.many2one('product.product' , 'Product', required=True, help = 'Product which this planning is created for.'),
753 'product_uom_categ' : fields.many2one('product.uom.categ', 'Product UoM Category'), # Invisible field for product_uom domain
754 'product_uom': fields.many2one('product.uom', 'UoM', required=True, help = "Unit of Measure used to show the quanities of stock calculation." \
755 "You can use units form default category or from second category (UoS category)."),
756 'product_uos_categ': fields.many2one('product.uom.categ', 'Product UoM Category'), # Invisible field for product_uos domain
757 # Field used in onchange_uom to check what uom was before change to recalculate quantities acording to old uom (active_uom) and new uom.
758 'active_uom': fields.many2one('product.uom', string = "Active UoM"),
759 'planned_outgoing': fields.float('Planned Out', required=True, \
760 help = 'Enter planned outgoing quantity from selected Warehouse during the selected Period of selected Product. '\
761 'To plan this value look at Confirmed Out or Sales Forecasts. This value should be equal or greater than Confirmed Out.'),
762 'company_forecast': fields.function(_get_forecast, method=True, string ='Company Forecast', multi = 'company', \
763 help = 'All sales forecasts for whole company (for all Warehouses) of selected Product during selected Period.'),
764 'warehouse_forecast': fields.function(_get_forecast, method=True, string ='Warehouse Forecast', multi = 'warehouse',\
765 help = 'All sales forecasts for selected Warehouse of selected Product during selected Period.'),
766 'stock_simulation': fields.float('Stock Simulation', readonly =True, \
767 help = 'Stock simulation at the end of selected Period.\n For current period it is: \n' \
768 'Initial Stock - Already Out + Already In - Expected Out + Incoming Left.\n' \
769 'For periods ahead it is: \nInitial Stock - Planned Out Before + Incoming Before - Planned Out + Planned In.'),
770 'incoming': fields.float('Confirmed In', readonly=True, \
771 help = 'Quantity of all confirmed incoming moves in calculated Period.'),
772 'outgoing': fields.float('Confirmed Out', readonly=True, \
773 help = 'Quantity of all confirmed outgoing moves in calculated Period.'),
774 'incoming_left': fields.float('Incoming Left', readonly=True, \
775 help = 'Quantity left to Planned incoming quantity. This is calculated difference between Planned In and Confirmed In. ' \
776 'For current period Already In is also calculated. This value is used to create procurement for lacking quantity.'),
777 'outgoing_left': fields.float('Expected Out', readonly=True, \
778 help = 'Quantity expected to go out in selected period. As a difference between Planned Out and Confirmed Out. ' \
779 'For current period Already Out is also calculated'),
780 'to_procure': fields.float(string='Planned In', required=True, \
781 help = 'Enter quantity which (by your plan) should come in. Change this value and observe Stock simulation. ' \
782 'This value should be equal or greater than Confirmed In.'),
783 'line_time': fields.function(_get_past_future, method=True,type='char', string='Past/Future'),
784 'minimum_op': fields.function(_get_op, method=True, type='float', string = 'Minimum Rule', multi= 'minimum', \
785 help = 'Minimum quantity set in Minimum Stock Rules for this Warhouse'),
786 'maximum_op': fields.function(_get_op, method=True, type='float', string = 'Maximum Rule', multi= 'maximum', \
787 help = 'Maximum quantity set in Minimum Stock Rules for this Warhouse'),
788 'outgoing_before': fields.float('Planned Out Before', readonly=True, \
789 help= 'Planned Out in periods before calculated. '\
790 'Between start date of current period and one day before start of calculated period.'),
791 'incoming_before': fields.float('Incoming Before', readonly = True, \
792 help= 'Confirmed incoming in periods before calculated (Including Already In). '\
793 'Between start date of current period and one day before start of calculated period.'),
794 'stock_start': fields.float('Initial Stock', readonly=True, \
795 help= 'Stock quantity one day before current period.'),
796 'already_out': fields.float('Already Out', readonly=True, \
797 help= 'Quantity which is already dispatched out of this warehouse in current period.'),
798 'already_in': fields.float('Already In', readonly=True, \
799 help= 'Quantity which is already picked up to this warehouse in current period.'),
800 'stock_only': fields.boolean("Stock Location Only", help = "Check to calculate stock location of selected warehouse only. " \
801 "If not selected calculation is made for input, stock and output location of warehouse."),
802 "procure_to_stock": fields.boolean("Procure To Stock Location", help = "Chect to make procurement to stock location of selected warehouse. " \
803 "If not selected procurement will be made into input location of warehouse."),
804 "confirmed_forecasts_only": fields.boolean("Validated Forecasts", help = "Check to take validated forecasts only. " \
805 "If not checked system takes validated and draft forecasts."),
806 'supply_warehouse_id': fields.many2one('stock.warehouse','Source Warehouse', help = "Warehouse used as source in supply pick move created by 'Supply from Another Warhouse'."),
807 "stock_supply_location": fields.boolean("Stock Supply Location", help = "Check to supply from Stock location of Supply Warehouse. " \
808 "If not checked supply will be made from Output location of Supply Warehouse. Used in 'Supply from Another Warhouse' with Supply Warehouse."),
813 'state': lambda *args: 'draft' ,
815 'planned_outgoing': 0.0,
816 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.planning', context=c),
821 def _to_default_uom_factor(self, cr, uid, val, uom_id, context=None):
822 uom_obj = self.pool.get('product.uom')
823 uom = uom_obj.browse(cr, uid, uom_id, context=context)
825 if uom.category_id.id != val.product_id.uom_id.category_id.id:
826 coef = coef / val.product_id.uos_coeff
827 return val.product_id.uom_id.factor / coef
830 def _from_default_uom_factor(self, cr, uid, val, uom_id, context=None):
831 uom_obj = self.pool.get('product.uom')
832 uom = uom_obj.browse(cr, uid, uom_id, context=context)
834 if uom.category_id.id != val.product_id.uom_id.category_id.id:
835 res = res / val.product_id.uos_coeff
836 return res / val.product_id.uom_id.factor, uom.rounding
838 def calculate_planning(self, cr, uid, ids, context, *args):
839 one_minute = RelativeDateTime(minutes=1)
840 current_date_beginning_c = mx.DateTime.today()
841 current_date_end_c = current_date_beginning_c + RelativeDateTime(days=1, minutes=-1) # to get hour 23:59:00
842 current_date_beginning = current_date_beginning_c.strftime('%Y-%m-%d %H:%M:%S')
843 current_date_end = current_date_end_c.strftime('%Y-%m-%d %H:%M:%S')
844 for val in self.browse(cr, uid, ids, context=context):
845 day = mx.DateTime.strptime(val.period_id.date_start, '%Y-%m-%d %H:%M:%S')
846 dbefore = mx.DateTime.DateTime(day.year, day.month, day.day) - one_minute
847 day_before_calculated_period = dbefore.strftime('%Y-%m-%d %H:%M:%S') # one day before start of calculated period
848 cr.execute("SELECT date_start \
849 FROM stock_period AS period \
850 LEFT JOIN stock_planning AS planning \
851 ON (planning.period_id = period.id) \
852 WHERE (period.date_stop >= %s) AND (period.date_start <= %s) AND \
853 planning.product_id = %s", (current_date_end, current_date_end, val.product_id.id,)) #
855 start_date_current_period = date and date[0] or False
856 start_date_current_period = start_date_current_period or current_date_beginning
857 day = mx.DateTime.strptime(start_date_current_period, '%Y-%m-%d %H:%M:%S')
858 dbefore = mx.DateTime.DateTime(day.year, day.month, day.day) - one_minute
859 date_for_start = dbefore.strftime('%Y-%m-%d %H:%M:%S') # one day before current period
860 already_out = self._get_in_out(cr, uid, val, start_date_current_period, current_date_end, direction='out', done = True, context=context),
861 already_in = self._get_in_out(cr, uid, val, start_date_current_period, current_date_end, direction='in', done = True, context=context),
862 outgoing = self._get_in_out(cr, uid, val, val.period_id.date_start, val.period_id.date_stop, direction='out', done = False, context=context),
863 incoming = self._get_in_out(cr, uid, val, val.period_id.date_start, val.period_id.date_stop, direction='in', done = False, context=context),
864 outgoing_before = self._get_outgoing_before(cr, uid, val, start_date_current_period, day_before_calculated_period, context=context),
865 incoming_before = self._get_in_out(cr, uid, val, start_date_current_period, day_before_calculated_period, direction='in', done = False, context=context),
866 stock_start = self._get_stock_start(cr, uid, val, date_for_start, context=context),
867 if start_date_current_period == val.period_id.date_start: # current period is calculated
871 factor, round_value = self._from_default_uom_factor(cr, uid, val, val.product_uom.id, context=context)
872 self.write(cr, uid, ids, {
873 'already_out': rounding(already_out[0]*factor,round_value),
874 'already_in': rounding(already_in[0]*factor,round_value),
875 'outgoing': rounding(outgoing[0]*factor,round_value),
876 'incoming': rounding(incoming[0]*factor,round_value),
877 'outgoing_before' : rounding(outgoing_before[0]*factor,round_value),
878 'incoming_before': rounding((incoming_before[0]+ (not current and already_in[0]))*factor,round_value),
879 'outgoing_left': rounding(val.planned_outgoing - (outgoing[0] + (current and already_out[0]))*factor,round_value),
880 'incoming_left': rounding(val.to_procure - (incoming[0] + (current and already_in[0]))*factor,round_value),
881 'stock_start': rounding(stock_start[0]*factor,round_value),
882 'stock_simulation': rounding(val.to_procure - val.planned_outgoing + (stock_start[0]+ incoming_before[0] - outgoing_before[0] \
883 + (not current and already_in[0]))*factor,round_value),
887 # method below converts quantities and uoms to general OpenERP standard with UoM Qty, UoM, UoS Qty, UoS.
888 # from stock_planning standard where you have one Qty and one UoM (any from UoM or UoS category)
889 # so if UoM is from UoM category it is used as UoM in standard and if product has UoS the UoS will be calcualated.
890 # If UoM is from UoS category it is recalculated to basic UoS from product (in planning you can use any UoS from UoS category)
891 # and basic UoM is calculated.
892 def _qty_to_standard(self, cr, uid, val, context):
895 if val.product_uom.category_id.id == val.product_id.uom_id.category_id.id:
896 uom_qty = val.incoming_left
897 uom = val.product_uom.id
898 if val.product_id.uos_id:
899 uos = val.product_id.uos_id.id
900 coeff_uom2def = self._to_default_uom_factor(cr, uid, val, val.product_uom.id, {})
901 coeff_def2uom, round_value = self._from_default_uom_factor(cr, uid, val, uos, {})
902 uos_qty = rounding(val.incoming_left * coeff_uom2def * coeff_def2uom, round_value)
903 elif val.product_uom.category_id.id == val.product_id.uos_id.category_id.id:
904 coeff_uom2def = self._to_default_uom_factor(cr, uid, val, val.product_uom.id, {})
905 uos = val.product_id.uos_id.id
906 coeff_def2uom, round_value = self._from_default_uom_factor(cr, uid, val, uos, {})
907 uos_qty = rounding(val.incoming_left * coeff_uom2def * coeff_def2uom, round_value)
908 uom = val.product_id.uom_id.id
909 coeff_def2uom, round_value = self._from_default_uom_factor(cr, uid, val, uom, {})
910 uom_qty = rounding(val.incoming_left * coeff_uom2def * coeff_def2uom, round_value)
911 return uom_qty, uom, uos_qty, uos
913 def procure_incomming_left(self, cr, uid, ids, context, *args):
914 for obj in self.browse(cr, uid, ids):
915 if obj.incoming_left <= 0:
916 raise osv.except_osv(_('Error !'), _('Incoming Left must be greater than 0 !'))
917 uom_qty, uom, uos_qty, uos = self._qty_to_standard(cr, uid, obj, context)
918 user = self.pool.get('res.users').browse(cr, uid, uid, context)
919 proc_id = self.pool.get('procurement.order').create(cr, uid, {
920 'company_id' : obj.company_id.id,
921 'name': _('Manual planning for ') + obj.period_id.name,
922 'origin': _('MPS(') + str(user.login) +') '+ obj.period_id.name,
923 'date_planned': obj.period_id.date_start,
924 'product_id': obj.product_id.id,
925 'product_qty': uom_qty,
927 'product_uos_qty': uos_qty,
929 'location_id': obj.procure_to_stock and obj.warehouse_id.lot_stock_id.id or obj.warehouse_id.lot_input_id.id,
930 'procure_method': 'make_to_order',
931 'note' : _("Procurement created in MPS by user: ") + str(user.login) + _(" Creation Date: ") + \
932 time.strftime('%Y-%m-%d %H:%M:%S') + \
933 _("\nFor period: ") + obj.period_id.name + _(" according to state:") + \
934 _("\n Warehouse Forecast: ") + str(obj.warehouse_forecast) + \
935 _("\n Initial Stock: ") + str(obj.stock_start) + \
936 _("\n Planned Out: ") + str(obj.planned_outgoing) + _(" Planned In: ") + str(obj.to_procure) + \
937 _("\n Already Out: ") + str(obj.already_out) + _(" Already In: ") + str(obj.already_in) + \
938 _("\n Confirmed Out: ") + str(obj.outgoing) + _(" Confirmed In: ") + str(obj.incoming) + \
939 _("\n Planned Out Before: ") + str(obj.outgoing_before) + _(" Confirmed In Before: ") + \
940 str(obj.incoming_before) + \
941 _("\n Expected Out: ") + str(obj.outgoing_left) + _(" Incoming Left: ") + str(obj.incoming_left) + \
942 _("\n Stock Simulation: ") + str(obj.stock_simulation) + _(" Minimum stock: ") + str(obj.minimum_op),
945 wf_service = netsvc.LocalService("workflow")
946 wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr)
947 self.calculate_planning(cr, uid, ids, context)
948 prev_text = obj.history or ""
949 self.write(cr, uid, ids, {
950 'history' : prev_text + _('Requisition (') + str(user.login) + ", " + time.strftime('%Y.%m.%d %H:%M) ') + str(obj.incoming_left) + \
951 " " + obj.product_uom.name + "\n",
955 def internal_supply(self, cr, uid, ids, context, *args):
956 for obj in self.browse(cr, uid, ids):
957 if obj.incoming_left <= 0:
958 raise osv.except_osv(_('Error !'), _('Incoming Left must be greater than 0 !'))
959 if not obj.supply_warehouse_id:
960 raise osv.except_osv(_('Error !'), _('You must specify a Source Warehouse !'))
961 if obj.supply_warehouse_id.id == obj.warehouse_id.id:
962 raise osv.except_osv(_('Error !'), _('You must specify a Source Warehouse different than calculated (destination) Warehouse !'))
963 uom_qty, uom, uos_qty, uos = self._qty_to_standard(cr, uid, obj, context)
964 user = self.pool.get('res.users').browse(cr, uid, uid, context)
965 picking_id = self.pool.get('stock.picking').create(cr, uid, {
966 'origin': _('MPS(') + str(user.login) +') '+ obj.period_id.name,
969 'date' : obj.period_id.date_start,
970 'move_type': 'direct',
971 'invoice_state': 'none',
972 'company_id': obj.company_id.id,
973 'note': _("Pick created from MPS by user: ") + str(user.login) + _(" Creation Date: ") + \
974 time.strftime('%Y-%m-%d %H:%M:%S') + \
975 _("\nFor period: ") + obj.period_id.name + _(" according to state:") + \
976 _("\n Warehouse Forecast: ") + str(obj.warehouse_forecast) + \
977 _("\n Initial Stock: ") + str(obj.stock_start) + \
978 _("\n Planned Out: ") + str(obj.planned_outgoing) + _(" Planned In: ") + str(obj.to_procure) + \
979 _("\n Already Out: ") + str(obj.already_out) + _(" Already In: ") + str(obj.already_in) + \
980 _("\n Confirmed Out: ") + str(obj.outgoing) + _(" Confirmed In: ") + str(obj.incoming) + \
981 _("\n Planned Out Before: ") + str(obj.outgoing_before) + _(" Confirmed In Before: ") + \
982 str(obj.incoming_before) + \
983 _("\n Expected Out: ") + str(obj.outgoing_left) + _(" Incoming Left: ") + str(obj.incoming_left) + \
984 _("\n Stock Simulation: ") + str(obj.stock_simulation) + _(" Minimum stock: ") + str(obj.minimum_op),
987 move_id = self.pool.get('stock.move').create(cr, uid, {
988 'name': _('MPS(') + str(user.login) +') '+ obj.period_id.name,
989 'picking_id': picking_id,
990 'product_id': obj.product_id.id,
991 'date_planned': obj.period_id.date_start,
992 'product_qty': uom_qty,
994 'product_uos_qty': uos_qty,
996 'location_id': obj.stock_supply_location and obj.supply_warehouse_id.lot_stock_id.id or \
997 obj.supply_warehouse_id.lot_output_id.id,
998 'location_dest_id': obj.procure_to_stock and obj.warehouse_id.lot_stock_id.id or \
999 obj.warehouse_id.lot_input_id.id,
1000 'tracking_id': False,
1001 'company_id': obj.company_id.id,
1003 wf_service = netsvc.LocalService("workflow")
1004 wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
1006 self.calculate_planning(cr, uid, ids, context)
1007 prev_text = obj.history or ""
1008 pick_name = self.pool.get('stock.picking').browse(cr, uid, picking_id).name
1009 self.write(cr, uid, ids, {
1010 'history' : prev_text + _('Pick List ')+ pick_name + " (" + str(user.login) + ", " + time.strftime('%Y.%m.%d %H:%M) ') \
1011 + str(obj.incoming_left) +" " + obj.product_uom.name + "\n",
1016 def product_id_change(self, cr, uid, ids, product_id):
1019 product_rec = self.pool.get('product.product').browse(cr, uid, product_id)
1020 ret['product_uom'] = product_rec.uom_id.id
1021 ret['active_uom'] = product_rec.uom_id.id
1022 ret['product_uom_categ'] = product_rec.uom_id.category_id.id
1023 ret['product_uos_categ'] = product_rec.uos_id and product_rec.uos_id.category_id.id or False
1025 ret['product_uom'] = False
1026 ret['product_uom_categ'] = False
1027 ret['product_uos_categ'] = False
1028 res = {'value': ret}
1033 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: