63
91
return super(real_average_consumption, self).unlink(cr, uid, ids, context=context)
65
def copy(self, cr, uid, ids, defaults={}, context={}):
93
def copy(self, cr, uid, ids, default=None, context=None):
67
95
Unvalidate all lines of the duplicate report
69
97
# Change default values
70
if not 'picking_id' in defaults:
71
defaults['picking_id'] = False
72
if not 'created_ok' in defaults:
73
defaults['created_ok'] = False
74
if not 'valid_ok' in defaults:
75
defaults['valid_ok'] = False
102
if not 'picking_id' in default:
103
default['picking_id'] = False
77
defaults['name'] = self.pool.get('ir.sequence').get(cr, uid, 'consumption.report')
105
default['name'] = self.pool.get('ir.sequence').get(cr, uid, 'consumption.report')
80
res = super(real_average_consumption, self).copy(cr, uid, ids, defaults, context=context)
108
res = super(real_average_consumption, self).copy(cr, uid, ids, default, context=context)
82
110
# Unvalidate all lines of the report
83
111
for report in self.browse(cr, uid, [res], context=context):
85
113
for line in report.line_ids:
86
114
lines.append(line.id)
88
self.pool.get('real.average.consumption.line').write(cr, uid, lines, {'move_id': False}, context=context)
116
self.pool.get('real.average.consumption.line').write(cr, uid, lines, {'move_id': False}, context=context)
118
# update created_ok at this end to disable _check qty on line
119
self.write(cr, uid, res, {'created_ok': False})
120
self.button_update_stock(cr, uid, res)
124
def get_bool_values(self, cr, uid, ids, fields, arg, context=None):
126
if isinstance(ids, (int, long)):
128
for obj in self.browse(cr, uid, ids, context=context):
130
if any([item for item in obj.line_ids if item.to_correct_ok]):
94
135
'name': fields.char(size=64, string='Reference'),
95
136
'creation_date': fields.datetime(string='Creation date', required=1),
96
137
'cons_location_id': fields.many2one('stock.location', string='Consumer location', domain=[('usage', '=', 'internal')], required=True),
97
'activity_id': fields.many2one('stock.location', string='Activity', domain=[('usage', '=', 'customer')]),
138
'activity_id': fields.many2one('stock.location', string='Activity', domain=[('usage', '=', 'customer')], required=1),
98
139
'period_from': fields.date(string='Period from', required=True),
99
140
'period_to': fields.date(string='Period to', required=True),
100
141
'sublist_id': fields.many2one('product.list', string='List/Sublist'),
101
'nomen_id': fields.many2one('product.nomenclature', string='Products\' nomenclature level'),
102
142
'line_ids': fields.one2many('real.average.consumption.line', 'rac_id', string='Lines'),
103
143
'picking_id': fields.many2one('stock.picking', string='Picking', readonly=True),
104
'valid_ok': fields.boolean(string='Create and process out moves'),
105
144
'created_ok': fields.boolean(string='Out moves created'),
106
145
'nb_lines': fields.function(_get_nb_lines, method=True, type='integer', string='# lines', readonly=True,),
146
'nomen_manda_0': fields.many2one('product.nomenclature', 'Main Type'),
147
'nomen_manda_1': fields.many2one('product.nomenclature', 'Group'),
148
'nomen_manda_2': fields.many2one('product.nomenclature', 'Family'),
149
'nomen_manda_3': fields.many2one('product.nomenclature', 'Root'),
150
'hide_column_error_ok': fields.function(get_bool_values, method=True, readonly=True, type="boolean", string="Show column errors", store=False),
151
'state': fields.selection([('draft', 'Draft'), ('done', 'Closed'),('cancel','Cancelled')], string="State", readonly=True),
110
'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'consumption.report'),
155
#'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'consumption.report'),
111
156
'creation_date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
112
'activity_id': lambda obj, cr, uid, context: obj.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', 'stock_location_internal_cust')[1],
157
'activity_id': lambda obj, cr, uid, context: obj.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', 'stock_location_internal_customers')[1],
113
158
'period_to': lambda *a: time.strftime('%Y-%m-%d'),
114
'valid_ok': lambda *a: True,
159
'nb_lines': lambda *a: 0,
160
'state': lambda *a: 'draft',
164
('date_coherence', "check (period_from <= period_to)", '"Period from" must be less than or equal to "Period to"'),
168
(_check_active_product, "You cannot confirm this real consumption report because it contains a line with an inactive product", ['line_ids', 'created_ok']),
171
def create(self, cr, uid, vals, context=None):
173
Add name of the report at creation
178
if not 'name' in vals:
179
vals.update({'name': self.pool.get('ir.sequence').get(cr, uid, 'consumption.report')})
181
return super(real_average_consumption, self).create(cr, uid, vals, context=context)
183
def change_cons_location_id(self, cr, uid, ids, context=None):
185
Open the wizard to change the location
187
wiz_id = self.pool.get('real.consumption.change.location').create(cr, uid, {'report_id': ids[0]}, context=context)
188
return {'type': 'ir.actions.act_window',
189
'res_model': 'real.consumption.change.location',
195
def button_update_stock(self, cr, uid, ids, context=None):
196
if isinstance(ids, (int, long)):
199
for line in self.read(cr, uid, ids, ['created_ok','line_ids']):
200
if line['created_ok']:
202
to_update += line['line_ids']
205
self.pool.get('real.average.consumption.line')._check_qty(cr, uid, to_update, {'noraise': True})
117
def save_and_process(self, cr, uid, ids, context={}):
208
def save_and_process(self, cr, uid, ids, context=None):
119
210
Returns the wizard to confirm the process of all lines
214
self.check_lines_to_fix(cr, uid, ids, context)
121
215
view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'consumption_calculation', 'real_average_consumption_confirmation_view')[1],
123
217
return {'type': 'ir.actions.act_window',
302
515
_name = 'real.average.consumption.line'
303
516
_description = 'Real average consumption line'
304
517
_rec_name = 'product_id'
306
520
def _get_checks_all(self, cr, uid, ids, name, arg, context=None):
309
result[id] = {'batch_number_check': False, 'expiry_date_check': False, 'type_check': False}
523
result[id] = {'batch_number_check': False, 'expiry_date_check': False, 'type_check': False, 'to_correct_ok': False}
311
525
for out in self.browse(cr, uid, ids, context=context):
312
526
if out.product_id:
313
527
result[out.id]['batch_number_check'] = out.product_id.batch_management
314
528
result[out.id]['expiry_date_check'] = out.product_id.perishable
529
result[out.id]['asset_check'] = _get_asset_mandatory(out.product_id)
530
# the lines with to_correct_ok=True will be red
532
result[out.id]['to_correct_ok'] = True
535
def _get_qty(self, cr, uid, product, lot, location, uom):
536
if not product and not lot:
538
context = {'location_id': location, 'location': location, 'uom': uom, 'compute_child': False}
540
return self.pool.get('product.product').read(cr, uid, product, ['qty_available'], context=context)['qty_available']
318
def write(self, cr, uid, ids, vals, context={}):
320
Change the expiry date according to the prodlot_id
321
Change the product_qty if the product is changed
542
return self.pool.get('stock.production.lot').read(cr, uid, lot, ['stock_available'], context=context)['stock_available']
544
def _check_qty(self, cr, uid, ids, context=None):
548
noraise = context.get('noraise')
549
context.update({'error_message': ''})
550
if isinstance(ids, (int, long)):
553
for obj in self.browse(cr, uid, ids):
554
if obj.rac_id.created_ok:
557
# Prevent negative consumption qty.
558
if obj.consumed_qty < 0.00:
560
raise osv.except_osv(_('Error'), _('The consumed qty. must be positive or 0.00'))
561
elif context.get('import_in_progress'):
562
error_message.append(_('The consumed qty. must be positive or 0.00'))
563
context.update({'error_message': error_message})
565
location = obj.rac_id.cons_location_id.id
570
batch_mandatory = obj.product_id.batch_management
571
date_mandatory = obj.product_id.perishable
572
asset_mandatory = _get_asset_mandatory(obj.product_id)
574
if batch_mandatory and obj.consumed_qty != 0.00:
575
if not obj.prodlot_id:
577
raise osv.except_osv(_('Error'),
578
_("Product: %s, You must assign a Batch Number to process it.")%(obj.product_id.name,))
579
elif context.get('import_in_progress'):
580
error_message.append(_("You must assign a Batch Number to process it."))
581
context.update({'error_message': error_message})
583
prodlot_id = obj.prodlot_id.id
584
expiry_date = obj.prodlot_id.life_date
586
if date_mandatory and not batch_mandatory and obj.consumed_qty != 0.00:
587
prod_ids = self.pool.get('stock.production.lot').search(cr, uid, [('life_date', '=', obj.expiry_date),
588
('type', '=', 'internal'),
589
('product_id', '=', obj.product_id.id)], context=context)
590
expiry_date = obj.expiry_date or None # None because else it is False and a date can't have a boolean value
593
raise osv.except_osv(_('Error'),
594
_("Product: %s, no internal batch found for expiry (%s)")%(obj.product_id.name, obj.expiry_date or _('No expiry date set')))
595
elif context.get('import_in_progress'):
596
error_message.append(_("Line %s of the imported file: no internal batch number found for ED %s (please correct the data)"
597
) % (context.get('line_num', False), expiry_date and strptime(expiry_date, '%Y-%m-%d').strftime('%d-%m-%Y')))
598
context.update({'error_message': error_message})
600
prodlot_id = prod_ids[0]
602
if asset_mandatory and obj.consumed_qty != 0.00:
605
raise osv.except_osv(_('Error'),
606
_("Product: %s, You must assign an Asset to process it.")%(obj.product_id.name,))
607
elif context.get('import_in_progress'):
608
error_message.append(_("You must assign an Asset to process it."))
609
context.update({'error_message': error_message})
611
asset_id = obj.asset_id.id
613
product_qty = self._get_qty(cr, uid, obj.product_id.id, prodlot_id, location, obj.uom_id and obj.uom_id.id)
615
if prodlot_id and obj.consumed_qty > product_qty:
617
raise osv.except_osv(_('Error'),
618
_("Product: %s, Qty Consumed (%s) can't be greater than the Indicative Stock (%s)")%(obj.product_id.name, obj.consumed_qty, product_qty))
619
elif context.get('import_in_progress'):
620
error_message.append(_("Line %s of the imported file: Qty Consumed (%s) can't be greater than the Indicative Stock (%s)") % (context.get('line_num', False), obj.consumed_qty, product_qty))
621
context.update({'error_message': error_message})
622
# uf-1344 "quantity NOT in stock with this ED => line should be in red, no batch picked up"
624
#recursion: can't use write
625
cr.execute('UPDATE '+self._table+' SET product_qty=%s, batch_mandatory=%s, date_mandatory=%s, asset_mandatory=%s, prodlot_id=%s, expiry_date=%s, asset_id=%s where id=%s', (product_qty, batch_mandatory, date_mandatory, asset_mandatory, prodlot_id, expiry_date, asset_id, obj.id))
629
def _get_product(self, cr, uid, ids, context=None):
630
return self.pool.get('real.average.consumption.line').search(cr, uid, [('product_id', 'in', ids)], context=context)
632
def _get_inactive_product(self, cr, uid, ids, field_name, args, context=None):
634
Fill the error message if the product of the line is inactive
323
637
for line in self.browse(cr, uid, ids, context=context):
324
if line.batch_mandatory and 'prodlot_id' in vals:
325
life_date = self.pool.get('stock.production.lot').browse(cr, uid, vals['prodlot_id'], context=context).life_date
326
vals.update({'expiry_date': life_date})
328
rac = self.pool.get('real.average.consumption').browse(cr, uid, vals.get('rac_id', line.rac_id.id), context=context)
329
context.update({'location': rac.cons_location_id.id})
330
if 'product_id' in vals and vals.get('product_id') != line.product_id.id:
331
product = self.pool.get('product.product').browse(cr, uid, vals.get('product_id'), context=context)
332
vals.update({'product_qty': product.qty_available})
334
return super(real_average_consumption_line, self).write(cr, uid, ids, vals, context=context)
336
def create(self, cr, uid, vals, context={}):
338
Add the expiry date if a lot is set
340
if vals.get('batch_mandatory', False) == True and 'prodlot_id' in vals:
341
life_date = self.pool.get('stock.production.lot').browse(cr, uid, vals['prodlot_id'], context=context).life_date
342
vals.update({'expiry_date': life_date})
344
rac = self.pool.get('real.average.consumption').browse(cr, uid, vals.get('rac_id'), context=context)
346
context.update({'location': rac.cons_location_id.id})
347
product = self.pool.get('product.product').browse(cr, uid, vals.get('product_id'), context=context)
348
vals.update({'product_qty': product.qty_available})
350
return super(real_average_consumption_line, self).create(cr, uid, vals, context=context)
638
res[line.id] = {'inactive_product': False,
639
'inactive_error': ''}
640
if line.product_id and not line.product_id.active:
642
'inactive_product': True,
643
'inactive_error': _('The product in line is inactive !')
353
649
'product_id': fields.many2one('product.product', string='Product', required=True),
650
'ref': fields.related('product_id', 'default_code', type='char', size=64, readonly=True,
651
store={'product.product': (_get_product, ['default_code'], 10),
652
'real.average.consumption.line': (lambda self, cr, uid, ids, c=None: ids, ['product_id'], 20)}),
354
653
'uom_id': fields.many2one('product.uom', string='UoM', required=True),
355
654
'product_qty': fields.float(digits=(16,2), string='Indicative stock', readonly=True),
356
655
'consumed_qty': fields.float(digits=(16,2), string='Qty consumed', required=True),
357
656
'batch_number_check': fields.function(_get_checks_all, method=True, string='Batch Number Check', type='boolean', readonly=True, multi="m"),
358
657
'expiry_date_check': fields.function(_get_checks_all, method=True, string='Expiry Date Check', type='boolean', readonly=True, multi="m"),
658
'asset_check': fields.function(_get_checks_all, method=True, string='Asset Check', type='boolean', readonly=True, multi="m"),
359
659
'prodlot_id': fields.many2one('stock.production.lot', string='Batch number'),
360
660
'batch_mandatory': fields.boolean(string='BM'),
361
661
'expiry_date': fields.date(string='Expiry date'),
362
662
'date_mandatory': fields.boolean(string='DM'),
663
'asset_id': fields.char(size=32, string='Asset'),
664
'asset_mandatory': fields.boolean('AM'),
363
665
'remark': fields.char(size=256, string='Remark'),
364
666
'move_id': fields.many2one('stock.move', string='Move'),
365
667
'rac_id': fields.many2one('real.average.consumption', string='RAC', ondelete='cascade'),
668
'text_error': fields.text('Errors', readonly=True),
669
'to_correct_ok': fields.function(_get_checks_all, method=True, type="boolean", string="To correct", store=False, readonly=True, multi="m"),
670
'just_info_ok': fields.boolean(string='Just for info'),
671
'inactive_product': fields.function(_get_inactive_product, method=True, type='boolean', string='Product is inactive', store=False, multi='inactive'),
672
'inactive_error': fields.function(_get_inactive_product, method=True, type='char', string='Comment', store=False, multi='inactive'),
676
'inactive_product': False,
677
'inactive_error': lambda *a: '',
368
def change_expiry(self, cr, uid, id, expiry_date, product_id, location_id, context={}):
680
# uf-1344 => need to pass the context so we use create and write instead
682
# (_check_qty, "The Qty Consumed can't be greater than the Indicative Stock", ['consumed_qty']),
686
('unique_lot_poduct', "unique(product_id, prodlot_id, rac_id)", 'The couple product, batch number has to be unique'),
689
def create(self, cr, uid, vals=None, context=None):
695
res = super(real_average_consumption_line, self).create(cr, uid, vals, context=context)
696
check = self._check_qty(cr, uid, res, context)
698
raise osv.except_osv(_('Error'), _('The Qty Consumed cant\'t be greater than the Indicative Stock'))
699
if vals.get('uom_id') and vals.get('product_id'):
700
product_id = vals.get('product_id')
701
product_uom = vals.get('uom_id')
702
if not self.pool.get('uom.tools').check_uom(cr, uid, product_id, product_uom, context):
703
raise osv.except_osv(_('Warning !'), _("You have to select a product UOM in the same category than the purchase UOM of the product"))
706
def write(self, cr, uid, ids, vals, context=None):
709
if isinstance(ids, (int, long)):
711
if not context.get('import_in_progress') and not context.get('button'):
712
obj_data = self.pool.get('ir.model.data')
713
tbd_uom = obj_data.get_object_reference(cr, uid, 'msf_doc_import', 'uom_tbd')[1]
714
tbd_product = obj_data.get_object_reference(cr, uid, 'msf_doc_import', 'product_tbd')[1]
716
if vals.get('uom_id'):
717
if vals.get('uom_id') == tbd_uom:
718
message += _('You have to define a valid UOM, i.e. not "To be define".')
719
if vals.get('product_id'):
720
if vals.get('product_id') == tbd_product:
721
message += _('You have to define a valid product, i.e. not "To be define".')
722
if vals.get('uom_id') and vals.get('product_id'):
723
product_id = vals.get('product_id')
724
product_uom = vals.get('uom_id')
725
if not self.pool.get('uom.tools').check_uom(cr, uid, product_id, product_uom, context):
726
message += _("You have to select a product UOM in the same category than the purchase UOM of the product")
728
raise osv.except_osv(_('Warning !'), message)
730
vals['text_error'] = False
731
res = super(real_average_consumption_line, self).write(cr, uid, ids, vals, context=context)
732
check = self._check_qty(cr, uid, ids, context)
734
raise osv.except_osv(_('Error'), _('The Qty Consumed cant\'t be greater than the Indicative Stock'))
737
def change_expiry(self, cr, uid, id, expiry_date, product_id, location_id, uom, remark=False, context=None):
370
739
expiry date changes, find the corresponding internal prod lot
372
743
prodlot_obj = self.pool.get('stock.production.lot')
373
744
result = {'value':{}}
374
745
context.update({'location': location_id})
376
747
if expiry_date and product_id:
377
748
prod_ids = prodlot_obj.search(cr, uid, [('life_date', '=', expiry_date),
378
749
('type', '=', 'internal'),
379
750
('product_id', '=', product_id)], context=context)
382
result['warning'] = {'title': _('Error'),
383
'message': _('The selected Expiry Date does not exist in the system.')}
385
result['value'].update(expiry_date=False, prodlot_id=False)
753
result['warning'] = {'title': _('Error'),
754
'message': _('The selected Expiry Date does not exist in the system.')}
756
result['value'].update(expiry_date=False, prodlot_id=False)
387
758
# return first prodlot
388
result['value'].update(prodlot_id=prod_ids[0])
759
result = self.change_prodlot(cr, uid, id, product_id, prod_ids[0], expiry_date, location_id, uom, context={})
760
result.setdefault('value',{}).update(prodlot_id=prod_ids[0])
761
if remark and remark in ('You must assign a batch number', 'You must assign an expiry date') :
762
result['value']['remark'] = ''
391
766
# clear expiry date, we clear production lot
392
767
result['value'].update(prodlot_id=False)
769
context.update(uom=uom)
770
context.update({'compute_child': False})
394
771
product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
395
772
result['value'].update({'product_qty': product.qty_available})
399
def change_prodlot(self, cr, uid, ids, product_id, prodlot_id, expiry_date, location_id, context={}):
776
def change_asset(self, cr, uid, id, asset, product_id, location_id, uom, remark=False, context=None):
778
Asset change, remove the remark
783
result = {'value':{}}
784
if remark and remark == 'You must assign an asset':
785
result.setdefault('value', {}).update(remark='')
790
def change_qty(self, cr, uid, ids, qty, product_id, prodlot_id, location, uom, context=None):
796
stock_qty = self._get_qty(cr, uid, product_id, prodlot_id, location, uom)
797
warn_msg = {'title': _('Error'), 'message': _("The Qty Consumed is greater than the Indicative Stock")}
800
res = self.pool.get('product.uom')._change_round_up_qty(cr, uid, uom, qty, 'consumed_qty', result=res)
802
if prodlot_id and qty > stock_qty:
803
res.setdefault('warning', {}).update(warn_msg)
804
res.setdefault('value', {}).update({'consumed_qty': 0})
806
res.setdefault('warning', {}).update(warn_msg)
810
def change_prodlot(self, cr, uid, ids, product_id, prodlot_id, expiry_date, location_id, uom, remark=False, context=None):
401
812
Set the expiry date according to the prodlot
403
816
res = {'value': {}}
404
context.update({'location': location_id})
817
context.update({'location': location_id, 'uom': uom})
405
818
if prodlot_id and not expiry_date:
819
if remark and remark in ('You must assign a batch number', 'You must assign an expiry date') :
820
res['value']['remark'] = ''
406
821
res['value'].update({'expiry_date': self.pool.get('stock.production.lot').browse(cr, uid, prodlot_id, context=context).life_date})
407
822
elif not prodlot_id and expiry_date:
408
823
res['value'].update({'expiry_date': False})
410
product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
411
res['value'].update({'product_qty': product.qty_available})
415
def product_onchange(self, cr, uid, ids, product_id, location_id=False, prodlot_id=False, context={}):
826
context.update({'compute_child': False})
827
product_qty = self.pool.get('product.product').browse(cr, uid, product_id, context=context).qty_available
829
if remark and remark in ('You must assign a batch number', 'You must assign an expiry date') :
830
res['value']['remark'] = ''
831
context.update({'location_id': location_id})
832
product_qty = self.pool.get('stock.production.lot').browse(cr, uid, prodlot_id, context=context).stock_available
833
res['value'].update({'product_qty': product_qty})
837
def uom_onchange(self, cr, uid, ids, product_id, product_qty, location_id=False, uom=False, lot=False, context=None):
842
if uom and product_id:
843
qty_available = self._get_qty(cr, uid, product_id, lot, location_id, uom)
845
if not uom and product_id:
846
product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
847
d['uom_id'] = [('category_id', '=', product.uom_id.category_id.id)]
849
res = {'value': {'product_qty': qty_available}, 'domain': d}
852
res = self.pool.get('product.uom')._change_round_up_qty(cr, uid, uom, product_qty, 'consumed_qty', result=res)
856
def product_onchange(self, cr, uid, ids, product_id, location_id=False, uom=False, lot=False, context=None):
417
858
Set the product uom when the product change
862
product_obj = self.pool.get('product.product')
863
v = {'batch_mandatory': False, 'date_mandatory': False, 'asset_mandatory': False}
866
# Test the compatibility of the product with a consumption report
867
res, test = product_obj._on_change_restriction_error(cr, uid, product_id, field_name='product_id', values={'value': v}, vals={'constraints': 'consumption'}, context=context)
423
context.update({'location': location_id})
871
context.update({'location': location_id, 'uom': uom})
425
product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
873
context.update({'compute_child': False})
874
product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
875
qty_available = product.qty_available
877
if product.batch_management:
878
v.update({'batch_mandatory': True, 'remark': _('You must assign a batch')})
879
if product.perishable:
880
v.update({'date_mandatory': True, 'remark': _('You must assign an expiry date')})
881
if product.type == 'product' and product.subtype == 'asset':
882
v.update({'asset_mandatory': True, 'remark': _('You must assign an asset')})
427
884
uom = product.uom_id.id
428
if product.batch_management:
429
v.update({'batch_mandatory': True})
430
elif product.perishable:
431
v.update({'date_mandatory': True})
433
885
v.update({'uom_id': uom})
886
d['uom_id'] = [('category_id', '=', product.uom_id.category_id.id)]
436
v.update({'product_qty': product.qty_available})
888
v.update({'product_qty': qty_available})
438
890
v.update({'uom_id': False, 'product_qty': 0.00, 'prodlot_id': False, 'expiry_date': False, 'consumed_qty': 0.00})
892
return {'value': v, 'domain': d}
894
def copy(self, cr, uid, line_id, default=None, context=None):
901
default.update({'prodlot_id': False, 'expiry_date': False, 'asset_id': False})
903
if 'consumed_qty' in default and default['consumed_qty'] < 0.00:
904
default['consumed_qty'] = 0.00
906
return super(real_average_consumption_line, self).copy(cr, uid, line_id[0], default=default, context={'noraise': True})
442
908
real_average_consumption_line()
911
class real_consumption_change_location(osv.osv_memory):
912
_name = 'real.consumption.change.location'
915
'report_id': fields.many2one('real.average.consumption', string='Report'),
916
'location_id': fields.many2one('stock.location', string='Consumer location', required=True),
919
def change_location(self, cr, uid, ids, context=None):
921
Change location of the report and reload the report
923
if isinstance(ids, (int, long)):
926
wiz = self.browse(cr, uid, ids[0], context=context)
928
self.pool.get('real.average.consumption').write(cr, uid, [wiz.report_id.id], {'cons_location_id': wiz.location_id.id}, context=context)
929
self.pool.get('real.average.consumption').button_update_stock(cr, uid, [wiz.report_id.id], context=context)
931
return {'type': 'ir.actions.act_window',
932
'res_model': 'real.average.consumption',
934
'view_mode': 'form,tree',
935
'res_id': wiz.report_id.id,
938
real_consumption_change_location()
445
941
class monthly_review_consumption(osv.osv):
446
942
_name = 'monthly.review.consumption'
447
943
_description = 'Monthly review consumption'
448
944
_rec_name = 'creation_date'
450
def _get_nb_lines(self, cr, uid, ids, field_name, args, context={}):
946
def _get_nb_lines(self, cr, uid, ids, field_name, args, context=None):
452
948
Returns the # of lines on the monthly review consumption
489
1015
'context': context,
492
def export_fmc(self, cr, uid, ids, context={}):
494
Creates a CSV file and launches the wizard to save it
496
fmc = self.browse(cr, uid, ids[0], context=context)
498
export = 'Product reference;Product name;AMC;FMC;Valid until'
501
for line in fmc.line_ids:
502
export += '%s;%s;%s;%s;%s' % (line.name.default_code, line.name.name, line.amc, line.fmc, line.valid_until or '')
505
file = base64.encodestring(export.encode("utf-8"))
507
export_id = self.pool.get('wizard.export.fmc').create(cr, uid, {'fmc_id': ids[0], 'file': file,
508
'filename': 'fmc_%s.csv' % (time.strftime('%Y_%m_%d')),
509
'message': 'The FMC lines has been exported. Please click on Save As button to download the file'})
511
return {'type': 'ir.actions.act_window',
512
'res_model': 'wizard.export.fmc',
519
def fill_lines(self, cr, uid, ids, context={}):
1018
def export_fmc(self, cr, uid, ids, context=None):
1020
Return an xml file to open with Excel
1022
datas = {'ids': ids}
1024
return {'type': 'ir.actions.report.xml',
1025
'report_name': 'monthly.consumption.xls',
1028
# Creates a CSV file and launches the wizard to save it
1030
# if context is None:
1032
# fmc = self.browse(cr, uid, ids[0], context=context)
1034
# outfile = TemporaryFile('w+')
1035
# writer = csv.writer(outfile, quotechar='"', delimiter=',')
1036
# writer.writerow(['Product Code', 'Product Description', 'AMC', 'FMC', 'Valid until'])
1038
# for line in fmc.line_ids:
1039
# writer.writerow([line.name.default_code and line.name.default_code.encode('utf-8'), line.name.name and line.name.name.encode('utf-8'), line.amc, line.fmc, line.valid_until or ''])
1041
# file = base64.encodestring(outfile.read())
1044
# export_id = self.pool.get('wizard.export.fmc').create(cr, uid, {'fmc_id': ids[0], 'file': file,
1045
# 'filename': 'fmc_%s.csv' % (time.strftime('%Y_%m_%d')),
1046
# 'message': 'The FMC lines has been exported. Please click on Save As button to download the file'})
1048
# return {'type': 'ir.actions.act_window',
1049
# 'res_model': 'wizard.export.fmc',
1050
# 'res_id': export_id,
1051
# 'view_mode': 'form',
1052
# 'view_type': 'form',
1056
def fill_lines(self, cr, uid, ids, context=None):
521
1058
Fill all lines according to defined nomenclature level and sublist
523
1062
line_obj = self.pool.get('monthly.review.consumption.line')
524
1063
for report in self.browse(cr, uid, ids, context=context):
525
1064
product_ids = []
528
1066
# Get all products for the defined nomenclature
530
nomen_id = report.nomen_id.id
531
nomen = self.pool.get('product.nomenclature').browse(cr, uid, nomen_id, context=context)
532
if nomen.type == 'mandatory':
533
product_ids.extend(self.pool.get('product.product').search(cr, uid, [('nomen_manda_0', '=', nomen_id)], context=context))
534
product_ids.extend(self.pool.get('product.product').search(cr, uid, [('nomen_manda_1', '=', nomen_id)], context=context))
535
product_ids.extend(self.pool.get('product.product').search(cr, uid, [('nomen_manda_2', '=', nomen_id)], context=context))
536
product_ids.extend(self.pool.get('product.product').search(cr, uid, [('nomen_manda_3', '=', nomen_id)], context=context))
538
product_ids.extend(self.pool.get('product.product').search(cr, uid, [('nomen_sub_0', '=', nomen_id)], context=context))
539
product_ids.extend(self.pool.get('product.product').search(cr, uid, [('nomen_sub_1', '=', nomen_id)], context=context))
540
product_ids.extend(self.pool.get('product.product').search(cr, uid, [('nomen_sub_2', '=', nomen_id)], context=context))
541
product_ids.extend(self.pool.get('product.product').search(cr, uid, [('nomen_sub_3', '=', nomen_id)], context=context))
542
product_ids.extend(self.pool.get('product.product').search(cr, uid, [('nomen_sub_4', '=', nomen_id)], context=context))
543
product_ids.extend(self.pool.get('product.product').search(cr, uid, [('nomen_sub_5', '=', nomen_id)], context=context))
1069
if report.nomen_manda_3:
1070
nom = report.nomen_manda_3.id
1071
field = 'nomen_manda_3'
1072
elif report.nomen_manda_2:
1073
nom = report.nomen_manda_2.id
1074
field = 'nomen_manda_2'
1075
elif report.nomen_manda_1:
1076
nom = report.nomen_manda_1.id
1077
field = 'nomen_manda_1'
1078
elif report.nomen_manda_0:
1079
nom = report.nomen_manda_0.id
1080
field = 'nomen_manda_0'
1082
product_ids.extend(self.pool.get('product.product').search(cr, uid, [(field, '=', nom)], context=context))
545
1084
# Get all products for the defined list
546
1085
if report.sublist_id:
950
1642
except ValueError:
951
1643
from_date_str = strptime(from_date, '%Y-%m-%d %H:%M:%S')
953
date_diff = Age(to_date_str, from_date_str)
954
nb_months = date_diff.years*12 + date_diff.months + (date_diff.days/30)
1645
nb_months = self._get_date_diff(from_date_str, to_date_str)
956
1647
if not nb_months: nb_months = 1
958
uom_id = self.browse(cr, uid, ids[0], context=context).uom_id.id
1649
uom_id = self.read(cr, uid, ids[0], ['uom_id'], context=context)['uom_id'][0]
959
1650
res = res/nb_months
960
1651
res = self.pool.get('product.uom')._compute_qty(cr, uid, uom_id, res, uom_id)
1655
def _get_date_diff(self, from_date, to_date):
1657
Returns the number of months between to dates according to the number
1658
of days in the month.
1660
diff_date = Age(to_date, from_date)
1663
def days_in_month(month, year):
1665
Returns the # of days in the month
1668
if month == 2 and year%4 == 0:
1670
elif month == 2 and year%4 != 0:
1672
elif month in (1, 3, 5, 7, 8, 10, 12):
1676
while from_date <= to_date:
1677
# Add 12 months by years between the two dates
1679
res += diff_date.years*12
1680
from_date += RelativeDate(years=diff_date.years)
1681
diff_date = Age(to_date, from_date)
1683
# If two dates are in the same month
1684
if from_date.month == to_date.month:
1685
nb_days_in_month = days_in_month(from_date.month, from_date.year)
1686
# We divided the # of days between the two dates by the # of days in month
1687
# to have a percentage of the number of month
1688
res += round((to_date.day-from_date.day+1)/nb_days_in_month, 2)
1690
elif to_date.month - from_date.month > 1 or to_date.year - from_date.year > 0:
1692
from_date += RelativeDate(months=1)
1694
# Number of month till the end of from month
1695
fr_nb_days_in_month = days_in_month(from_date.month, from_date.year)
1696
nb_days = fr_nb_days_in_month - from_date.day + 1
1697
res += round(nb_days/fr_nb_days_in_month, 2)
1698
# Number of month till the end of from month
1699
to_nb_days_in_month = days_in_month(to_date.month, to_date.year)
1700
res += round(to_date.day/to_nb_days_in_month, 2)
1707
def _compute_product_amc(self, cr, uid, ids, field_name, args, ctx=None):
1710
context = ctx.copy()
1712
from_date = (DateFrom(time.strftime('%Y-%m-%d')) + RelativeDateTime(months=-3, day=1)).strftime('%Y-%m-%d')
1713
to_date = (DateFrom(time.strftime('%Y-%m-%d')) + RelativeDateTime(day=1, days=-1)).strftime('%Y-%m-%d')
1715
if context.get('from_date', False):
1716
from_date = (DateFrom(context.get('from_date')) + RelativeDateTime(day=1)).strftime('%Y-%m-%d')
1718
if context.get('to_date', False):
1719
to_date = (DateFrom(context.get('to_date')) + RelativeDateTime(months=1, day=1, days=-1)).strftime('%Y-%m-%d')
1721
context.update({'from_date': from_date})
1722
context.update({'to_date': to_date})
1725
res[product] = self.compute_amc(cr, uid, product, context=context)
1729
def _get_period_consumption(self, cr, uid, line, from_date, to_date, context=None):
1731
Returns the average quantity of product in the period
1733
# Compute the # of days in the report period
1736
from datetime import datetime
1737
report_from = datetime.strptime(line.rac_id.period_from, '%Y-%m-%d')
1738
report_to = datetime.strptime(line.rac_id.period_to, '%Y-%m-%d')
1739
dt_from_date = datetime.strptime(from_date, '%Y-%m-%d')
1740
dt_to_date = datetime.strptime(to_date, '%Y-%m-%d')
1741
delta = report_to - report_from
1743
# Add 1 to include the last day of report to
1744
report_nb_days = delta.days + 1
1747
# Case where the report is totally included in the period
1748
if line.rac_id.period_from >= from_date and line.rac_id.period_to <= to_date:
1749
return line.consumed_qty
1750
# Case where the report started before the period and done after the period
1751
elif line.rac_id.period_from <= from_date and line.rac_id.period_to >= to_date:
1752
# Compute the # of days of the period
1753
delta2 = dt_to_date - dt_from_date
1754
days_incl = delta2.days +1
1755
# Case where the report started before the period and done in the period
1756
elif line.rac_id.period_from <= from_date and line.rac_id.period_to <= to_date and line.rac_id.period_to >= from_date:
1757
# Compute the # of days of the report included in the period
1758
# Add 1 to include the last day of report to
1759
delta2 = report_to - dt_from_date
1760
days_incl = delta2.days +1
1761
# Case where the report started in the period and done after the period
1762
elif line.rac_id.period_from >= from_date and line.rac_id.period_to >= to_date and line.rac_id.period_from <= to_date:
1763
# Compute the # of days of the report included in the period
1764
# Add 1 to include the last day of to_date
1765
delta2 = dt_to_date - report_from
1766
days_incl = delta2.days +1
1768
# Compute the quantity consumed in the period for this line
1769
consumed_qty = (line.consumed_qty/report_nb_days)*days_incl
1770
return self.pool.get('product.uom')._compute_qty(cr, uid, line.uom_id.id, consumed_qty, line.uom_id.id)
966
1773
'procure_delay': fields.float(digits=(16,2), string='Procurement Lead Time',
967
1774
help='It\'s the default time to procure this product. This lead time will be used on the Order cycle procurement computation'),
968
'monthly_consumption': fields.function(compute_mac, method=True, type='float', string='Monthly consumption', readonly=True),
1775
'monthly_consumption': fields.function(compute_mac, method=True, type='float', string='Real Consumption', readonly=True),
1776
'product_amc': fields.function(_compute_product_amc, method=True, type='float', string='Monthly consumption', readonly=True),
969
1777
'reviewed_consumption': fields.function(_compute_fmc, method=True, type='float', string='Forecasted Monthly Consumption', readonly=True),
973
'procure_delay': lambda *a: 1,
1781
'procure_delay': lambda *a: 60,