23
23
from osv import fields
24
24
from mx.DateTime import *
25
25
from lxml import etree
26
from tools.translate import _
30
HIST_STATUS = [('draft', 'Draft'), ('in_progress', 'In Progress'), ('ready', 'Ready')]
30
class product_history_consumption(osv.osv_memory):
32
class product_history_consumption(osv.osv):
31
33
_name = 'product.history.consumption'
34
_rec_name = 'location_id'
36
def _get_status(self, cr, uid, ids, field_name, args, context=None):
38
Return the same status as status
42
for obj in self.browse(cr, uid, ids, context=context):
43
res[obj.id] = obj.status
34
'date_from': fields.date(string='From date', required=True),
35
'date_to': fields.date(string='To date', required=True),
48
'date_from': fields.date(string='From date'),
49
'date_to': fields.date(string='To date'),
36
50
'month_ids': fields.one2many('product.history.consumption.month', 'history_id', string='Months'),
37
51
'consumption_type': fields.selection([('rac', 'Real Average Consumption'), ('amc', 'Average Monthly Consumption')],
38
string='Consumption type', required=True),
52
string='Consumption type'),
39
53
'location_id': fields.many2one('stock.location', string='Location', domain="[('usage', '=', 'internal')]"),
40
54
'sublist_id': fields.many2one('product.list', string='List/Sublist'),
41
55
'nomen_id': fields.many2one('product.nomenclature', string='Products\' nomenclature level'),
56
'nomen_manda_0': fields.many2one('product.nomenclature', 'Main Type'),
57
'nomen_manda_1': fields.many2one('product.nomenclature', 'Group'),
58
'nomen_manda_2': fields.many2one('product.nomenclature', 'Family'),
59
'nomen_manda_3': fields.many2one('product.nomenclature', 'Root'),
60
'requestor_id': fields.many2one('res.users', string='Requestor'),
61
'requestor_date': fields.datetime(string='Date of the demand'),
62
'fake_status': fields.function(_get_status, method=True, type='selection', selection=HIST_STATUS, readonly=True, string='Status'),
63
'status': fields.selection(HIST_STATUS, string='Status'),
45
'date_to': lambda *a: time.strftime('%Y-%m-%d'),
67
'date_to': lambda *a: (DateFrom(time.strftime('%Y-%m-%d')) + RelativeDateTime(months=1, day=1, days=-1)).strftime('%Y-%m-%d'),
68
'requestor_id': lambda obj, cr, uid, c: uid,
69
'requestor_date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
48
def open_history_consumption(self, cr, uid, ids, context={}):
73
def open_history_consumption(self, cr, uid, ids, context=None):
51
76
new_id = self.create(cr, uid, {}, context=context)
52
77
return {'type': 'ir.actions.act_window',
53
78
'res_model': 'product.history.consumption',
55
'context': {'active_id': new_id, 'active_ids': [new_id]},
80
'context': {'active_id': new_id, 'active_ids': [new_id], 'withnum': 1},
56
81
'view_type': 'form',
57
82
'view_mode': 'form',
60
def date_change(self, cr, uid, ids, date_from, date_to, context={}):
85
def date_change(self, cr, uid, ids, date_from, date_to, context=None):
62
87
Add the list of months in the defined period
67
92
month_obj = self.pool.get('product.history.consumption.month')
95
date_from = (DateFrom(date_from) + RelativeDateTime(day=1)).strftime('%Y-%m-%d')
96
res['value'].update({'date_from': date_from})
98
date_to = (DateFrom(date_to) + RelativeDateTime(months=1, day=1, days=-1)).strftime('%Y-%m-%d')
99
res['value'].update({'date_to': date_to})
69
101
# If a period is defined
70
102
if date_from and date_to:
71
res['value'] = {'month_ids': []}
103
res['value'].update({'month_ids': []})
72
104
current_date = DateFrom(date_from) + RelativeDateTime(day=1)
105
if current_date > (DateFrom(date_to) + RelativeDateTime(months=1, day=1, days=-1)):
106
return {'warning': {'title': _('Error'),
107
'message': _('The \'To Date\' should be greater than \'From Date\'')}}
73
108
# For all months in the period
74
109
while current_date <= (DateFrom(date_to) + RelativeDateTime(months=1, day=1, days=-1)):
75
110
search_ids = month_obj.search(cr, uid, [('name', '=', current_date.strftime('%m/%Y')), ('history_id', 'in', ids)], context=context)
76
111
# If the month is in the period and not in the list, create it
77
112
if not search_ids:
78
month_id = month_obj.create(cr, uid, {'name': current_date.strftime('%m/%Y'),
79
'date_from': current_date.strftime('%Y-%m-%d'),
80
'date_to': (current_date + RelativeDateTime(months=1, day=1, days=-1)).strftime('%Y-%m-%d'),
81
'history_id': ids[0]})
82
res['value']['month_ids'].append(month_id)
113
# month_id = month_obj.create(cr, uid, {'name': current_date.strftime('%m/%Y'),
114
# 'date_from': current_date.strftime('%Y-%m-%d'),
115
# 'date_to': (current_date + RelativeDateTime(months=1, day=1, days=-1)).strftime('%Y-%m-%d'),
116
# 'history_id': ids[0]}, context=context)
117
# res['value']['month_ids'].append(month_id)
118
res['value']['month_ids'].append({'name': current_date.strftime('%m/%Y'),
119
'date_from': current_date.strftime('%Y-%m-%d'),
120
'date_to': (current_date + RelativeDateTime(months=1, day=1, days=-1)).strftime('%Y-%m-%d')})
84
122
res['value']['month_ids'].extend(search_ids)
85
123
current_date = current_date + RelativeDateTime(months=1)
87
res['value'] = {'month_ids': [False]}
125
res['value'] = {'month_ids': []}
89
127
# Delete all months out of the period
119
157
nb_months = len(months)
120
158
total_consumption = {}
123
nomen_id = obj.nomen_id.id
124
nomen = self.pool.get('product.nomenclature').browse(cr, uid, nomen_id, context=context)
125
if nomen.type == 'mandatory':
126
product_ids.extend(self.pool.get('product.product').search(cr, uid, [('nomen_manda_0', '=', nomen_id)], context=context))
127
product_ids.extend(self.pool.get('product.product').search(cr, uid, [('nomen_manda_1', '=', nomen_id)], context=context))
128
product_ids.extend(self.pool.get('product.product').search(cr, uid, [('nomen_manda_2', '=', nomen_id)], context=context))
129
product_ids.extend(self.pool.get('product.product').search(cr, uid, [('nomen_manda_3', '=', nomen_id)], context=context))
130
# Prefill the search view
132
while nomen.parent_id:
133
tmp_nomen.append(nomen.parent_id.id)
134
nomen = nomen.parent_id
139
while i < len(tmp_nomen):
140
context.update({'search_default_nomen_manda_%s' %i: obj.sublist_id.id})
143
product_ids.extend(self.pool.get('product.product').search(cr, uid, [('nomen_sub_0', '=', nomen_id)], context=context))
144
product_ids.extend(self.pool.get('product.product').search(cr, uid, [('nomen_sub_1', '=', nomen_id)], context=context))
145
product_ids.extend(self.pool.get('product.product').search(cr, uid, [('nomen_sub_2', '=', nomen_id)], context=context))
146
product_ids.extend(self.pool.get('product.product').search(cr, uid, [('nomen_sub_3', '=', nomen_id)], context=context))
147
product_ids.extend(self.pool.get('product.product').search(cr, uid, [('nomen_sub_4', '=', nomen_id)], context=context))
148
product_ids.extend(self.pool.get('product.product').search(cr, uid, [('nomen_sub_5', '=', nomen_id)], context=context))
149
# Prefill the search view
151
while nomen.parent_id:
152
tmp_nomen.append(nomen.parent_id.id)
153
nomen = nomen.parent_id
158
while i < len(tmp_nomen):
160
context.update({'search_default_nomen_sub_%s' % i-3: obj.sublist_id.id})
162
context.update({'search_default_nomen_manda_%s' % i: obj.sublist_id.id})
161
raise osv.except_osv(_('Error'), _('You have to choose at least one month for consumption history'))
163
if obj.nomen_manda_0:
164
for report in self.browse(cr, uid, ids, context=context):
169
# Get all products for the defined nomenclature
170
if report.nomen_manda_3:
171
nom = report.nomen_manda_3.id
172
field = 'nomen_manda_3'
173
elif report.nomen_manda_2:
174
nom = report.nomen_manda_2.id
175
field = 'nomen_manda_2'
176
elif report.nomen_manda_1:
177
nom = report.nomen_manda_1.id
178
field = 'nomen_manda_1'
179
elif report.nomen_manda_0:
180
nom = report.nomen_manda_0.id
181
field = 'nomen_manda_0'
183
product_ids.extend(self.pool.get('product.product').search(cr, uid, [(field, '=', nom)], context=context))
185
for product in self.pool.get('product.product').browse(cr, uid, product_ids, context=context):
186
# Check if the product is not already on the report
187
if product.id not in products:
188
batch_mandatory = product.batch_management or product.perishable
189
date_mandatory = not product.batch_management and product.perishable
165
191
if obj.sublist_id:
166
192
context.update({'search_default_list_ids': obj.sublist_id.id})
170
196
domain = [('id', 'in', product_ids)]
172
if not obj.nomen_id and not obj.sublist_id:
198
if not obj.nomen_manda_0 and not obj.sublist_id:
175
201
new_context = context.copy()
176
new_context.update({'months': [], 'amc': obj.consumption_type == 'amc' and 'AMC' or 'RAC', 'obj_id': obj.id, 'history_cons': True})
202
new_context.update({'months': [], 'amc': obj.consumption_type == 'amc' and 'AMC' or 'RAC', 'obj_id': obj.id, 'history_cons': True, 'need_thread': True})
178
204
# For each month, compute the RAC
179
205
for month in self.pool.get('product.history.consumption.month').browse(cr, uid, months, context=context):
180
206
new_context['months'].append({'date_from': month.date_from, 'date_to': month.date_to})
209
return product_ids, domain, new_context
211
def create_lines(self, cr, uid, ids, context=None):
213
Create one line by product for the period
218
product_ids, domain, new_context = self.get_data(cr, uid, ids, context=context)
221
product_ids = self.pool.get('product.product').search(cr, uid, [], context=new_context)
224
self.write(cr, uid, ids, {'status': 'in_progress'}, context=context)
226
new_thread = threading.Thread(target=self._create_lines, args=(cr, uid, ids, product_ids, new_context))
228
new_thread.join(10.0)
229
if new_thread.isAlive():
230
view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'consumption_calculation', 'history_consumption_waiting_view')[1]
231
return {'type': 'ir.actions.act_window',
232
'res_model': 'product.history.consumption',
236
'view_id': [view_id],
237
'context': new_context,
240
return self.open_report(cr, uid, ids, context=new_context)
242
def _create_lines(self, cr, uid, ids, product_ids, context=None):
244
Create lines in background
247
new_cr = pooler.get_db(cr.dbname).cursor()
249
# split ids into slices to not read a lot record in the same time (memory)
250
ids_len = len(product_ids)
252
if ids_len > slice_len:
253
slice_count = ids_len / slice_len
254
if ids_len % slice_len:
255
slice_count = slice_count + 1
256
# http://www.garyrobinson.net/2008/04/splitting-a-pyt.html
257
slices = [product_ids[i::slice_count] for i in range(slice_count)]
259
slices = [product_ids]
261
for slice_ids in slices:
263
self.pool.get('product.product').read(new_cr, uid, slice_ids, ['average'], context=context)
266
self.write(new_cr, uid, ids, {'status': 'ready'}, context=context)
273
def open_report(self, cr, uid, ids, context=None):
280
product_ids, domain, new_context = self.get_data(cr, uid, ids, context=context)
281
if new_context is None:
283
new_context['search_default_average'] = 1 # UTP-501 positive Av.AMC/Av.RAC filter set to on by default
183
285
return {'type': 'ir.actions.act_window',
184
286
'res_model': 'product.product',
185
287
'domain': domain,
188
290
'context': new_context,
189
291
'target': 'dummy'}
293
def unlink(self, cr, uid, ids, context=None):
295
Remove the data saved in DB
297
hist_obj = self.pool.get('product.history.consumption.product')
298
for cons in self.browse(cr, uid, ids, context=context):
299
hist_ids = hist_obj.search(cr, uid, [('consumption_id', '=', cons.id)], context=context)
300
hist_obj.unlink(cr, uid, hist_ids, context=context)
301
return super(product_history_consumption, self).unlink(cr, uid, ids, context=context)
303
def in_progress(self, cr, uid, ids, context=None):
307
return self.go_to_list(cr, uid, ids, context=context)
309
def go_to_list(self, cr, uid, ids, context=None):
311
Returns to the list of reports
313
return {'type': 'ir.actions.act_window',
314
'res_model': 'product.history.consumption',
315
'view_mode': 'tree,form',
320
##############################################################################################################################
321
# The code below aims to enable filtering products regarding their nomenclature.
322
# NB: the difference with the other same kind of product filters (with nomenclature and sublist) is that here we are dealing with osv_memory
323
##############################################################################################################################
324
def onChangeSearchNomenclature(self, cr, uid, id, position, type, nomen_manda_0, nomen_manda_1, nomen_manda_2, nomen_manda_3, num=True, context=None):
325
res = self.pool.get('product.product').onChangeSearchNomenclature(cr, uid, 0, position, type, nomen_manda_0, nomen_manda_1, nomen_manda_2, nomen_manda_3, False, context={'withnum': 1})
328
def get_nomen(self, cr, uid, id, field):
329
return self.pool.get('product.nomenclature').get_nomen(cr, uid, self, id, field, context={'withnum': 1})
331
def write(self, cr, uid, ids, vals, context=None):
332
if vals.get('sublist_id',False):
333
vals.update({'nomen_manda_0':False,'nomen_manda_1':False,'nomen_manda_2':False,'nomen_manda_3':False})
334
if vals.get('nomen_manda_0',False):
335
vals.update({'sublist_id':False})
336
if vals.get('nomen_manda_1',False):
337
vals.update({'sublist_id':False})
338
ret = super(product_history_consumption, self).write(cr, uid, ids, vals, context=context)
340
##############################################################################
341
# END of the definition of the product filters and nomenclatures
342
##############################################################################
191
344
product_history_consumption()
194
class product_history_consumption_month(osv.osv_memory):
346
class product_history_consumption_month(osv.osv):
195
347
_name = 'product.history.consumption.month'
196
348
_order = 'name, date_from, date_to'
209
361
_name = 'product.product'
210
362
_inherit = 'product.product'
212
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context={}, toolbar=False, submenu=False):
216
res = super(product_product, self).fields_view_get(cr, uid, view_id, view_type, context=context, toolbar=toolbar, submenu=submenu)
364
def export_data(self, cr, uid, ids, fields_to_export, context=None):
366
Override the export_data function to add fictive fields
372
new_fields_to_export = []
375
default_code_index = False
376
remove_default_code = False
377
history_cons_in_context = context.get('history_cons', False)
380
if history_cons_in_context:
381
months = context.get('months', [])
382
del context['history_cons']
383
if context.get('amc', False) and 'average' in fields_to_export:
384
history_fields.append('average')
386
if 'default_code' not in fields_to_export:
387
fields_to_export.append('default_code')
388
remove_default_code = True
391
field_name = DateFrom(month.get('date_from')).strftime('%m_%Y')
392
if field_name in fields_to_export:
393
history_fields.append(field_name)
395
# Prepare normal fields to export to avoid error on export data with fictive fields
397
for f in fields_to_export:
398
if f not in history_fields:
399
new_fields_to_export.append(f)
400
if f == 'default_code':
401
default_code_index = to_export_iter
404
# We save the order of the fields to read them in the good order
405
fields_sort.update({sort_iter2: f})
408
new_fields_to_export = fields_to_export
410
res = super(product_product, self).export_data(cr, uid, ids, new_fields_to_export, context=context)
412
# Set the fields in the good order
413
if history_cons_in_context:
414
context['history_cons'] = True
416
for r in res['datas']:
418
product_id = self.search(cr, uid, [('default_code', '=', r[default_code_index])], context=context)
421
datas = self.read(cr, uid, product_id, history_fields + ['default_code', 'id'], context=context)[0]
424
for j in range(sort_iter2):
427
if f == 'default_code' and remove_default_code:
430
if f in history_fields:
431
new_r.append(str(datas.get(f, 0.00)))
433
new_r.append(r[iter_r])
435
new_data.append(new_r)
437
res['datas'] = new_data
441
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
446
if 'location' in context and type(context.get('location')) == type([]):
447
ctx.update({'location': context.get('location')[0]})
448
res = super(product_product, self).fields_view_get(cr, uid, view_id, view_type, context=ctx, toolbar=toolbar, submenu=submenu)
218
450
if context.get('history_cons', False) and view_type == 'tree':
219
451
line_view = """<tree string="Historical consumption">
292
539
raise osv.except_osv(_('Error'), _('No months found !'))
294
541
obj_id = context.get('obj_id')
297
543
total_consumption = 0.00
298
544
for month in context.get('months'):
299
field_name = DateFrom(month.get('date_from')).strftime('%m/%Y')
545
field_name = DateFrom(month.get('date_from')).strftime('%m_%Y')
300
546
cons_context = {'from_date': month.get('date_from'), 'to_date': month.get('date_to'), 'location_id': context.get('location_id')}
301
547
consumption = 0.00
548
cons_prod_domain = [('name', '=', field_name),
549
('product_id', '=', r['id']),
550
('consumption_id', '=', obj_id)]
302
551
if context.get('amc') == 'AMC':
303
consumption = self.pool.get('product.product').compute_amc(cr, uid, r['id'], context=cons_context)
552
cons_prod_domain.append(('cons_type', '=', 'amc'))
553
cons_id = cons_prod_obj.search(cr, uid, cons_prod_domain, context=context)
555
consumption = cons_prod_obj.browse(cr, uid, cons_id[0], context=context).value
557
consumption = self.pool.get('product.product').compute_amc(cr, uid, r['id'], context=cons_context) or 0.00
558
cons_prod_obj.create(cr, uid, {'name': field_name,
559
'product_id': r['id'],
560
'consumption_id': obj_id,
562
'value': consumption}, context=context)
305
consumption = self.pool.get('product.product').browse(cr, uid, r['id'], context=cons_context).monthly_consumption
564
cons_prod_domain.append(('cons_type', '=', 'fmc'))
565
cons_id = cons_prod_obj.search(cr, uid, cons_prod_domain, context=context)
567
consumption = cons_prod_obj.browse(cr, uid, cons_id[0], context=context).value
569
consumption = self.pool.get('product.product').browse(cr, uid, r['id'], context=cons_context).monthly_consumption or 0.00
570
cons_prod_obj.create(cr, uid, {'name': field_name,
571
'product_id': r['id'],
572
'consumption_id': obj_id,
574
'value': consumption}, context=context)
306
575
total_consumption += consumption
307
# Update the value for the month
576
# Update the value for the month
308
577
r.update({field_name: consumption})
310
# Update the average field
579
# Update the average field
580
cons_prod_domain = [('name', '=', 'average'),
581
('product_id', '=', r['id']),
582
('consumption_id', '=', obj_id),
583
('cons_type', '=', context.get('amc') == 'AMC' and 'amc' or 'fmc')]
311
584
r.update({'average': round(total_consumption/float(len(context.get('months'))),2)})
585
cons_id = cons_prod_obj.search(cr, uid, cons_prod_domain, context=context)
587
cons_prod_obj.write(cr, uid, cons_id, {'value': r['average']}, context=context)
589
cons_prod_obj.create(cr, uid, {'name': 'average',
590
'product_id': r['id'],
591
'consumption_id': obj_id,
592
'cons_type': context.get('amc') == 'AMC' and 'amc' or 'fmc',
593
'value': r['average']}, context=context)
313
595
res = super(product_product, self).read(cr, uid, ids, vals, context=context, load=load)
599
def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
601
Update the search method to sort by fictive fields if needed
606
average_domain = False
607
if context.get('history_cons', False):
608
"""UTP-501 'average' filter (filter button generated in fields_view_get)
609
if found, grab it, and remove it
610
(bc 'average' field is unknown in super(product_product, self))
614
if len(a) == 3 and a[0] == 'average':
620
hist_obj = self.pool.get('product.history.consumption.product')
622
res = super(product_product, self).search(cr, uid, args, offset, limit, order, context, count)
624
if context.get('history_cons', False) and context.get('obj_id', False):
625
if order or average_domain:
626
hist_domain = [('consumption_id', '=', context.get('obj_id'))]
627
if context.get('amc') == 'AMC':
628
hist_domain.append(('cons_type', '=', 'amc'))
630
hist_domain.append(('cons_type', '=', 'fmc'))
633
# UTP-501 'average' filter
635
('name', '=', 'average'),
636
('value', average_domain[1], average_domain[2])
640
# sorting with or without average_domain
641
for order_part in order.split(','):
642
order_split = order_part.strip().split(' ')
643
order_field = order_split[0]
644
order_direction = order_split[1].strip() if len(order_split) == 2 else ''
645
if order_field != 'id' and order_field not in self._columns and order_field not in self._inherit_fields:
646
hist_domain.append(('name', '=', order_field))
647
hist_ids = hist_obj.search(cr, uid, hist_domain, offset=offset, limit=limit, order='value %s' % order_direction, context=context)
648
res = list(x['product_id'][0] for x in hist_obj.read(cr, uid, hist_ids, ['product_id'], context=context))
651
# UTP-501 'average' filter without sorting
652
hist_ids = hist_obj.search(cr, uid, hist_domain, offset=offset, limit=limit, order=order, context=context)
653
res = [x['product_id'][0] for x in hist_obj.read(cr, uid, hist_ids, ['product_id'], context=context)]
317
657
product_product()
660
class product_history_consumption_product(osv.osv):
661
_name = 'product.history.consumption.product'
664
'consumption_id': fields.many2one('product.history.consumption', string='Consumption id', select=1, ondelete='cascade'),
665
'product_id': fields.many2one('product.product', string='Product'),
666
'name': fields.char(size=64, string='Name'),
667
'value': fields.float(digits=(16,2), string='Value', select=1),
668
'cons_type': fields.selection([('amc', 'AMC'), ('fmc', 'FMC')], string='Consumption type'),
671
def read(self, cr, uid, ids, fields, context=None, load='_classic_read'):
673
Return the result in the same order as given in ids
675
res = super(product_history_consumption_product, self).read(cr, uid, ids, fields, context=context, load=load)
677
res_final = [None]*len(ids)
679
r_index = ids.index(r['id'])
680
res_final[r_index] = r
684
product_history_consumption_product()
319
686
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: