~unifield-team/unifield-wm/us-826

« back to all changes in this revision

Viewing changes to consumption_calculation/consumption_calculation.py

  • Committer: jf
  • Date: 2011-03-23 13:23:55 UTC
  • Revision ID: jf@tempo4-20110323132355-agyf1soy7m5ewatr
Initial Import

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: utf-8 -*-
2
 
##############################################################################
3
 
#
4
 
#    OpenERP, Open Source Management Solution
5
 
#    Copyright (C) 2011 TeMPO Consulting, MSF 
6
 
#
7
 
#    This program is free software: you can redistribute it and/or modify
8
 
#    it under the terms of the GNU Affero General Public License as
9
 
#    published by the Free Software Foundation, either version 3 of the
10
 
#    License, or (at your option) any later version.
11
 
#
12
 
#    This program is distributed in the hope that it will be useful,
13
 
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 
#    GNU Affero General Public License for more details.
16
 
#
17
 
#    You should have received a copy of the GNU Affero General Public License
18
 
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
 
#
20
 
##############################################################################
21
 
 
22
 
from osv import osv
23
 
from osv import fields
24
 
from mx.DateTime import *
25
 
 
26
 
from tools.translate import _
27
 
 
28
 
import time
29
 
import base64
30
 
import netsvc
31
 
 
32
 
import csv
33
 
from tempfile import TemporaryFile
34
 
from product._common import rounding
35
 
from spreadsheet_xml.spreadsheet_xml_write import SpreadsheetCreator
36
 
 
37
 
 
38
 
def _get_asset_mandatory(product):
39
 
    return product.type == 'product' and product.subtype == 'asset'
40
 
 
41
 
 
42
 
class real_average_consumption(osv.osv):
43
 
    _name = 'real.average.consumption'
44
 
    _description = 'Real Average Consumption'
45
 
    
46
 
    def _get_nb_lines(self, cr, uid, ids, field_name, args, context=None):
47
 
        '''
48
 
        Returns the # of lines on the real average consumption
49
 
        '''
50
 
        res = {}
51
 
        
52
 
        for mrc in self.browse(cr, uid, ids, context=context):
53
 
            res[mrc.id] = len(mrc.line_ids)
54
 
            
55
 
        return res
56
 
 
57
 
    def _check_active_product(self, cr, uid, ids, context=None):
58
 
        '''
59
 
        Check if the real consumption report contains a line with an inactive product
60
 
        '''
61
 
        inactive_lines = self.pool.get('real.average.consumption.line').search(cr, uid, [
62
 
            ('product_id.active', '=', False),
63
 
            ('rac_id', 'in', ids),
64
 
            ('rac_id.created_ok', '=', True)
65
 
        ], context=context)
66
 
 
67
 
        if inactive_lines:
68
 
            plural = len(inactive_lines) == 1 and _('A product has') or _('Some products have')
69
 
            l_plural = len(inactive_lines) == 1 and _('line') or _('lines')
70
 
            p_plural = len(inactive_lines) == 1 and _('this inactive product') or _('those inactive products')
71
 
            raise osv.except_osv(_('Error'), _('%s been inactivated. If you want to validate this document you have to remove/correct the %s containing %s (see red %s of the document)') % (plural, l_plural, p_plural, l_plural))
72
 
            return False
73
 
        return True
74
 
 
75
 
    def unlink(self, cr, uid, ids, context=None):
76
 
        '''
77
 
        Display a message to the user if the report has been confirmed
78
 
        and stock moves has been generated
79
 
        '''
80
 
        for report in self.browse(cr, uid, ids, context=context):
81
 
            if report.state == 'done':
82
 
                raise osv.except_osv(_('Error'), _('This report is closed. You cannot delete it !'))
83
 
            if report.created_ok and report.picking_id:
84
 
                if report.picking_id.state != 'cancel':
85
 
                    raise osv.except_osv(_('Error'), _('You cannot delete this report because stock moves has been generated and validated from this report !'))
86
 
                else:
87
 
                    for move in report.picking_id.move_lines:
88
 
                        if move.state != 'cancel':
89
 
                            raise osv.except_osv(_('Error'), _('You cannot delete this report because stock moves has been generated and validated from this report !'))
90
 
 
91
 
        return super(real_average_consumption, self).unlink(cr, uid, ids, context=context)
92
 
 
93
 
    def copy(self, cr, uid, ids, default=None, context=None):
94
 
        '''
95
 
        Unvalidate all lines of the duplicate report
96
 
        '''
97
 
        # Change default values
98
 
        if default is None:
99
 
            default = {}
100
 
        if context is None:
101
 
            context = {}
102
 
        if not 'picking_id' in default:
103
 
            default['picking_id'] = False
104
 
 
105
 
        default['name'] = self.pool.get('ir.sequence').get(cr, uid, 'consumption.report')
106
 
 
107
 
        # Copy the report
108
 
        res = super(real_average_consumption, self).copy(cr, uid, ids, default, context=context)
109
 
 
110
 
        # Unvalidate all lines of the report
111
 
        for report in self.browse(cr, uid, [res], context=context):
112
 
            lines = []
113
 
            for line in report.line_ids:
114
 
                lines.append(line.id)
115
 
            if lines:
116
 
                self.pool.get('real.average.consumption.line').write(cr, uid, lines, {'move_id': False}, context=context)
117
 
 
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)
121
 
        return res
122
 
 
123
 
 
124
 
    def get_bool_values(self, cr, uid, ids, fields, arg, context=None):
125
 
        res = {}
126
 
        if isinstance(ids, (int, long)):
127
 
            ids = [ids]
128
 
        for obj in self.browse(cr, uid, ids, context=context):
129
 
            res[obj.id] = False
130
 
            if any([item for item in obj.line_ids  if item.to_correct_ok]):
131
 
                res[obj.id] = True
132
 
        return res
133
 
 
134
 
    _columns = {
135
 
        'name': fields.char(size=64, string='Reference'),
136
 
        'creation_date': fields.datetime(string='Creation date', required=1),
137
 
        'cons_location_id': fields.many2one('stock.location', string='Consumer location', domain=[('usage', '=', 'internal')], required=True),
138
 
        'activity_id': fields.many2one('stock.location', string='Activity', domain=[('usage', '=', 'customer')], required=1),
139
 
        'period_from': fields.date(string='Period from', required=True),
140
 
        'period_to': fields.date(string='Period to', required=True),
141
 
        'sublist_id': fields.many2one('product.list', string='List/Sublist'),
142
 
        'line_ids': fields.one2many('real.average.consumption.line', 'rac_id', string='Lines'),
143
 
        'picking_id': fields.many2one('stock.picking', string='Picking', readonly=True),
144
 
        'created_ok': fields.boolean(string='Out moves created'),
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),
152
 
    }
153
 
 
154
 
    _defaults = {
155
 
        #'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'consumption.report'),
156
 
        'creation_date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
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],
158
 
        'period_to': lambda *a: time.strftime('%Y-%m-%d'),
159
 
        'nb_lines': lambda *a: 0,
160
 
        'state': lambda *a: 'draft',
161
 
    }
162
 
 
163
 
    _sql_constraints = [
164
 
        ('date_coherence', "check (period_from <= period_to)", '"Period from" must be less than or equal to "Period to"'),
165
 
    ]
166
 
 
167
 
    _constraints = [
168
 
        (_check_active_product, "You cannot confirm this real consumption report because it contains a line with an inactive product", ['line_ids', 'created_ok']),
169
 
    ]
170
 
 
171
 
    def create(self, cr, uid, vals, context=None):
172
 
        '''
173
 
        Add name of the report at creation
174
 
        '''
175
 
        if not vals:
176
 
            vals = {}
177
 
 
178
 
        if not 'name' in vals:
179
 
            vals.update({'name': self.pool.get('ir.sequence').get(cr, uid, 'consumption.report')})
180
 
 
181
 
        return super(real_average_consumption, self).create(cr, uid, vals, context=context)
182
 
 
183
 
    def change_cons_location_id(self, cr, uid, ids, context=None):
184
 
        '''
185
 
        Open the wizard to change the location
186
 
        '''
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',
190
 
                'view_mode': 'form',
191
 
                'view_type': 'form',
192
 
                'res_id': wiz_id,
193
 
                'target': 'new'}
194
 
 
195
 
    def button_update_stock(self, cr, uid, ids, context=None):
196
 
        if isinstance(ids, (int, long)):
197
 
            ids = [ids]
198
 
        to_update = []
199
 
        for line in self.read(cr, uid, ids, ['created_ok','line_ids']):
200
 
            if line['created_ok']:
201
 
                continue
202
 
            to_update += line['line_ids']
203
 
 
204
 
        if to_update:
205
 
            self.pool.get('real.average.consumption.line')._check_qty(cr, uid, to_update, {'noraise': True})
206
 
        return True
207
 
    
208
 
    def save_and_process(self, cr, uid, ids, context=None):
209
 
        '''
210
 
        Returns the wizard to confirm the process of all lines
211
 
        '''
212
 
        if context is None:
213
 
            context = {}
214
 
        self.check_lines_to_fix(cr, uid, ids, context)
215
 
        view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'consumption_calculation', 'real_average_consumption_confirmation_view')[1],
216
 
        
217
 
        return {'type': 'ir.actions.act_window',
218
 
                'res_model': 'real.average.consumption',
219
 
                'view_type': 'form',
220
 
                'view_mode': 'form',
221
 
                'target': 'new',
222
 
                'view_id': [view_id],
223
 
                'res_id': ids[0],
224
 
                }
225
 
        
226
 
    def draft_button(self, cr, uid, ids, context=None):
227
 
        if isinstance(ids, (int, long)):
228
 
            ids = [ids]
229
 
        if context is None:
230
 
            context = {}
231
 
 
232
 
        self.write(cr, uid, ids, {'state':'draft'}, context=context)
233
 
        
234
 
        return {'type': 'ir.actions.act_window',
235
 
                'res_model': 'real.average.consumption',
236
 
                'view_type': 'form',
237
 
                'view_mode': 'form,tree',
238
 
                'target': 'dummy',
239
 
                'res_id': ids[0],
240
 
                }
241
 
 
242
 
    def cancel_button(self, cr, uid, ids, context=None):
243
 
        if isinstance(ids, (int, long)):
244
 
            ids = [ids]
245
 
        if context is None:
246
 
            context = {}
247
 
 
248
 
        self.write(cr, uid, ids, {'state':'cancel'}, context=context)
249
 
        
250
 
        return {'type': 'ir.actions.act_window',
251
 
                'res_model': 'real.average.consumption',
252
 
                'view_type': 'form',
253
 
                'view_mode': 'form,tree',
254
 
                'target': 'dummy',
255
 
                'res_id': ids[0],
256
 
                }
257
 
 
258
 
 
259
 
    def process_moves(self, cr, uid, ids, context=None):
260
 
        '''
261
 
        Creates all stock moves according to the report lines
262
 
        '''
263
 
        if isinstance(ids, (int, long)):
264
 
            ids = [ids]
265
 
        if context is None:
266
 
            context = {}
267
 
        
268
 
        move_obj = self.pool.get('stock.move')
269
 
        line_obj = self.pool.get('real.average.consumption.line')
270
 
        wf_service = netsvc.LocalService("workflow")
271
 
        
272
 
        reason_type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_consumption_report')[1]
273
 
 
274
 
        move_ids = []
275
 
       
276
 
        # check and update lines
277
 
        for rac in self.browse(cr, uid, ids, context=context):
278
 
            if DateFrom(rac.period_to) > now():
279
 
                raise osv.except_osv(_('Error'), _('"Period to" can\'t be in the future.'))
280
 
 
281
 
            if rac.created_ok:
282
 
                return {'type': 'ir.actions.close_window'}
283
 
            line_obj._check_qty(cr, uid, [x.id for x in rac.line_ids])
284
 
 
285
 
        partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.partner_id.id
286
 
        addresses = self.pool.get('res.partner').address_get(cr, uid, partner_id, ['delivery', 'default'])
287
 
        address_id = addresses.get('delivery') or addresses.get('default')
288
 
 
289
 
        for rac in self.browse(cr, uid, ids, context=context):
290
 
            date = '%s %s'%(rac.period_to, time.strftime('%H:%M:%S'))
291
 
            picking_id = self.pool.get('stock.picking').create(cr, uid, {'name': 'OUT-%s' % rac.name,
292
 
                                                                         'origin': rac.name,
293
 
                                                                         'partner_id': partner_id,
294
 
                                                                         'address_id': address_id,
295
 
                                                                         'type': 'out',
296
 
                                                                         'subtype': 'standard',
297
 
                                                                         'state': 'auto',
298
 
                                                                         'move_type': 'one',
299
 
                                                                         'invoice_state': 'none',
300
 
                                                                         'date': date,
301
 
                                                                         'reason_type_id': reason_type_id}, context=context)
302
 
            
303
 
            self.write(cr, uid, [rac.id], {'created_ok': True}, context=context)
304
 
            for line in rac.line_ids:
305
 
                if line.consumed_qty != 0.00:
306
 
                    move_id = move_obj.create(cr, uid, {'name': '%s/%s' % (rac.name, line.product_id.name),
307
 
                                                        'picking_id': picking_id,
308
 
                                                        'product_uom': line.uom_id.id,
309
 
                                                        'product_id': line.product_id.id,
310
 
                                                        'date_expected': date,
311
 
                                                        'date': date,
312
 
                                                        'product_qty': line.consumed_qty,
313
 
                                                        'prodlot_id': line.prodlot_id.id,
314
 
                                                        'expiry_date': line.expiry_date,
315
 
                                                        'asset_id': line.asset_id.id,
316
 
                                                        'location_id': rac.cons_location_id.id,
317
 
                                                        'location_dest_id': rac.activity_id.id,
318
 
                                                        'state': 'done',
319
 
                                                        'reason_type_id': reason_type_id})
320
 
                    move_ids.append(move_id)
321
 
                    line_obj.write(cr, uid, [line.id], {'move_id': move_id})
322
 
 
323
 
            self.write(cr, uid, [rac.id], {'picking_id': picking_id, 'state': 'done'}, context=context)
324
 
 
325
 
            # Confirm the picking
326
 
            wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
327
 
 
328
 
            # Confirm all moves
329
 
            move_obj.action_done(cr, uid, move_ids, context=context)
330
 
            #move_obj.write(cr, uid, move_ids, {'date': rac.period_to}, context=context)
331
 
            
332
 
        
333
 
        return {'type': 'ir.actions.act_window',
334
 
                'res_model': 'real.average.consumption',
335
 
                'view_type': 'form',
336
 
                'view_mode': 'form,tree',
337
 
                'target': 'dummy',
338
 
                'res_id': ids[0],
339
 
                }
340
 
        
341
 
    def import_rac(self, cr, uid, ids, context=None):
342
 
        '''
343
 
        Launches the wizard to import lines from a file
344
 
        '''
345
 
        if context is None:
346
 
            context = {}
347
 
        context.update({'active_id': ids[0]})
348
 
        
349
 
        return {'type': 'ir.actions.act_window',
350
 
                'res_model': 'wizard.import.rac',
351
 
                'view_type': 'form',
352
 
                'view_mode': 'form',
353
 
                'target': 'new',
354
 
                'context': context,
355
 
                }
356
 
        
357
 
    def export_rac(self, cr, uid, ids, context=None):
358
 
        '''
359
 
        Creates an XML file and launches the wizard to save it
360
 
        '''
361
 
        if context is None:
362
 
            context = {}
363
 
        rac = self.browse(cr, uid, ids[0], context=context)
364
 
        header_columns = [(_('Product Code'), 'string'), (_('Product Description'),'string'),(_('Product UoM'), 'string'), (_('Batch Number'),'string'), (_('Expiry Date'), 'string'), (_('Asset'), 'string'), (_('Consumed Qty'),'string'), (_('Remark'), 'string')]
365
 
        list_of_lines = []
366
 
        for line in rac.line_ids:
367
 
            list_of_lines.append([line.product_id.default_code, line.product_id.name, line.uom_id.name, line.prodlot_id and line.prodlot_id.name, 
368
 
                                  line.expiry_date and strptime(line.expiry_date,'%Y-%m-%d').strftime('%d/%m/%Y') or '', line.asset_id and line.asset_id.name, line.consumed_qty, line.remark or ''])
369
 
        instanciate_class = SpreadsheetCreator('RAC', header_columns, list_of_lines)
370
 
        file = base64.encodestring(instanciate_class.get_xml(default_filters=['decode.utf8']))
371
 
        
372
 
        export_id = self.pool.get('wizard.export.rac').create(cr, uid, {'rac_id': ids[0], 
373
 
                                                                        'file': file,
374
 
                                                                        'filename': 'rac_%s.xls' % (rac.cons_location_id.name.replace(' ', '_')), 
375
 
                                                                        'message': _('The RAC lines has been exported. Please click on Save As button to download the file'),}, context=context)
376
 
        
377
 
        return {'type': 'ir.actions.act_window',
378
 
                'res_model': 'wizard.export.rac',
379
 
                'res_id': export_id,
380
 
                'view_mode': 'form',
381
 
                'view_type': 'form',
382
 
                'target': 'new',
383
 
                'context': context,
384
 
                }
385
 
 
386
 
    def fill_lines(self, cr, uid, ids, context=None):
387
 
        '''
388
 
        Fill all lines according to defined nomenclature level and sublist
389
 
        '''
390
 
        if context is None:
391
 
            context = {}
392
 
        self.write(cr, uid, ids, {'created_ok': True})    
393
 
        for report in self.browse(cr, uid, ids, context=context):
394
 
            product_ids = []
395
 
            products = []
396
 
 
397
 
            nom = False
398
 
            # Get all products for the defined nomenclature
399
 
            if report.nomen_manda_3:
400
 
                nom = report.nomen_manda_3.id
401
 
                field = 'nomen_manda_3'
402
 
            elif report.nomen_manda_2:
403
 
                nom = report.nomen_manda_2.id
404
 
                field = 'nomen_manda_2'
405
 
            elif report.nomen_manda_1:
406
 
                nom = report.nomen_manda_1.id
407
 
                field = 'nomen_manda_1'
408
 
            elif report.nomen_manda_0:
409
 
                nom = report.nomen_manda_0.id
410
 
                field = 'nomen_manda_0'
411
 
            if nom:
412
 
                product_ids.extend(self.pool.get('product.product').search(cr, uid, [(field, '=', nom)], context=context))
413
 
 
414
 
            # Get all products for the defined list
415
 
            if report.sublist_id:
416
 
                for line in report.sublist_id.product_ids:
417
 
                    product_ids.append(line.name.id)
418
 
 
419
 
            # Check if products in already existing lines are in domain
420
 
            products = []
421
 
            for line in report.line_ids:
422
 
                if line.product_id.id in product_ids:
423
 
                    products.append(line.product_id.id)
424
 
                else:
425
 
                    self.pool.get('real.average.consumption.line').unlink(cr, uid, line.id, context=context)
426
 
 
427
 
            for product in self.pool.get('product.product').browse(cr, uid, product_ids, context=context):
428
 
                # Check if the product is not already on the report
429
 
                if product.id not in products:
430
 
                    batch_mandatory = product.batch_management
431
 
                    date_mandatory = product.perishable
432
 
                    asset_mandatory = _get_asset_mandatory(product)
433
 
                    values = {'product_id': product.id,
434
 
                              'uom_id': product.uom_id.id,
435
 
                              'consumed_qty': 0.00,
436
 
                              'batch_mandatory': batch_mandatory,
437
 
                              'date_mandatory': date_mandatory,
438
 
                              'asset_mandatory': asset_mandatory,
439
 
                              'rac_id': report.id,}
440
 
                    v = self.pool.get('real.average.consumption.line').product_onchange(cr, uid, [], product.id, report.cons_location_id.id,
441
 
                                                                                        product.uom_id.id, False, context=context)['value']
442
 
                    values.update(v)
443
 
                    if batch_mandatory:
444
 
                        values.update({'remark': _('You must assign a batch number')})
445
 
                    if date_mandatory:
446
 
                        values.update({'remark': _('You must assign an expiry date')})
447
 
                    if asset_mandatory:
448
 
                        values.update({'remark': _('You must assign an asset')})
449
 
                    self.pool.get('real.average.consumption.line').create(cr, uid, values)
450
 
        
451
 
        self.write(cr, uid, ids, {'created_ok': False})    
452
 
        return {'type': 'ir.actions.act_window',
453
 
                'res_model': 'real.average.consumption',
454
 
                'view_type': 'form',
455
 
                'view_mode': 'form',
456
 
                'res_id': ids[0],
457
 
                'target': 'dummy',
458
 
                'context': context}
459
 
        
460
 
    def get_nomen(self, cr, uid, id, field):
461
 
        return self.pool.get('product.nomenclature').get_nomen(cr, uid, self, id, field, context={'withnum': 1})
462
 
 
463
 
    def onChangeSearchNomenclature(self, cr, uid, id, position, type, nomen_manda_0, nomen_manda_1, nomen_manda_2, nomen_manda_3, num=True, context=None):
464
 
        return 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})
465
 
    
466
 
    def write(self, cr, uid, ids, vals, context=None):
467
 
        if vals.get('sublist_id',False):
468
 
            vals.update({'nomen_manda_0':False,'nomen_manda_1':False,'nomen_manda_2':False,'nomen_manda_3':False})
469
 
        if vals.get('nomen_manda_0',False):
470
 
            vals.update({'sublist_id':False})
471
 
        ret = super(real_average_consumption, self).write(cr, uid, ids, vals, context=context)
472
 
        return ret
473
 
    
474
 
    def button_remove_lines(self, cr, uid, ids, context=None):
475
 
        '''
476
 
        Remove lines
477
 
        '''
478
 
        if context is None:
479
 
            context = {}
480
 
        if isinstance(ids, (int, long)):
481
 
            ids = [ids]
482
 
        vals = {}
483
 
        vals['line_ids'] = []
484
 
        for line in self.browse(cr, uid, ids, context=context):
485
 
            line_browse_list = line.line_ids
486
 
            for var in line_browse_list:
487
 
                vals['line_ids'].append((2, var.id))
488
 
            self.write(cr, uid, ids, vals, context=context)
489
 
        return True
490
 
 
491
 
    def check_lines_to_fix(self, cr, uid, ids, context=None):
492
 
        """
493
 
        Check the lines that need to be corrected
494
 
        """
495
 
        if isinstance(ids, (int, long)):
496
 
            ids = [ids]
497
 
        obj_data = self.pool.get('ir.model.data')
498
 
        product_tbd = obj_data.get_object_reference(cr, uid, 'msf_doc_import', 'product_tbd')[1]
499
 
        uom_tbd = obj_data.get_object_reference(cr, uid, 'msf_doc_import', 'uom_tbd')[1]
500
 
 
501
 
        for var in self.browse(cr, uid, ids, context=context):
502
 
            # we check the lines that need to be fixed
503
 
            if var.line_ids:
504
 
                for var in var.line_ids:
505
 
                    if var.consumed_qty and var.to_correct_ok:
506
 
                        raise osv.except_osv(_('Warning !'), _('Some lines need to be fixed before.'))
507
 
                    else:
508
 
                        self.pool.get('real.average.consumption.line').write(cr, uid, var.id, {},context)
509
 
        return True
510
 
 
511
 
real_average_consumption()
512
 
 
513
 
 
514
 
class real_average_consumption_line(osv.osv):
515
 
    _name = 'real.average.consumption.line'
516
 
    _description = 'Real average consumption line'
517
 
    _rec_name = 'product_id'
518
 
    _order = 'id, ref'
519
 
 
520
 
    def _get_checks_all(self, cr, uid, ids, name, arg, context=None):
521
 
        result = {}
522
 
        for id in ids:
523
 
            result[id] = {'batch_number_check': False, 'expiry_date_check': False, 'type_check': False, 'to_correct_ok': False}
524
 
            
525
 
        for out in self.browse(cr, uid, ids, context=context):
526
 
            if out.product_id:
527
 
                result[out.id]['batch_number_check'] = out.product_id.batch_management
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
531
 
            if out.text_error:
532
 
                result[out.id]['to_correct_ok'] = True
533
 
        return result
534
 
 
535
 
    def _get_qty(self, cr, uid, product, lot, location, uom):
536
 
        if not product and not lot:
537
 
            return False
538
 
        context = {'location_id': location, 'location': location, 'uom': uom, 'compute_child': False}
539
 
        if not lot:
540
 
            return self.pool.get('product.product').read(cr, uid, product, ['qty_available'], context=context)['qty_available']
541
 
            
542
 
        return self.pool.get('stock.production.lot').read(cr, uid, lot, ['stock_available'], context=context)['stock_available']
543
 
 
544
 
    def _check_qty(self, cr, uid, ids, context=None):
545
 
       
546
 
        if context is None:
547
 
            context = {}
548
 
        noraise = context.get('noraise')
549
 
        context.update({'error_message': ''})
550
 
        if isinstance(ids, (int, long)):
551
 
            ids = [ids]
552
 
        error_message = []
553
 
        for obj in self.browse(cr, uid, ids):
554
 
            if obj.rac_id.created_ok:
555
 
                continue
556
 
 
557
 
            # Prevent negative consumption qty.
558
 
            if obj.consumed_qty < 0.00:
559
 
                if not noraise:
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})
564
 
 
565
 
            location = obj.rac_id.cons_location_id.id
566
 
            prodlot_id = None
567
 
            expiry_date = None
568
 
            asset_id = None
569
 
 
570
 
            batch_mandatory = obj.product_id.batch_management
571
 
            date_mandatory = obj.product_id.perishable
572
 
            asset_mandatory = _get_asset_mandatory(obj.product_id)
573
 
        
574
 
            if batch_mandatory and obj.consumed_qty != 0.00:
575
 
                if not obj.prodlot_id:
576
 
                    if not noraise:
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})
582
 
                elif obj.prodlot_id:
583
 
                    prodlot_id = obj.prodlot_id.id
584
 
                    expiry_date = obj.prodlot_id.life_date
585
 
 
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
591
 
                if not prod_ids:
592
 
                    if not noraise:
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})
599
 
                else:
600
 
                    prodlot_id = prod_ids[0]
601
 
 
602
 
            if asset_mandatory and obj.consumed_qty != 0.00:
603
 
                if not obj.asset_id:
604
 
                    if not noraise:
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})
610
 
                elif obj.asset_id:
611
 
                    asset_id = obj.asset_id.id
612
 
 
613
 
            product_qty = self._get_qty(cr, uid, obj.product_id.id, prodlot_id, location, obj.uom_id and obj.uom_id.id)
614
 
 
615
 
            if prodlot_id and obj.consumed_qty > product_qty:
616
 
                if not noraise:
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"
623
 
                    prodlot_id = None
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))
626
 
 
627
 
        return True
628
 
 
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)
631
 
 
632
 
    def _get_inactive_product(self, cr, uid, ids, field_name, args, context=None):
633
 
        '''
634
 
        Fill the error message if the product of the line is inactive
635
 
        '''
636
 
        res = {}
637
 
        for line in self.browse(cr, uid, ids, context=context):
638
 
            res[line.id] = {'inactive_product': False,
639
 
                            'inactive_error': ''}
640
 
            if line.product_id and not line.product_id.active:
641
 
                res[line.id] = {
642
 
                    'inactive_product': True,
643
 
                    'inactive_error': _('The product in line is inactive !')
644
 
                }
645
 
 
646
 
        return res
647
 
 
648
 
    _columns = {
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)}),
653
 
        'uom_id': fields.many2one('product.uom', string='UoM', required=True),
654
 
        'product_qty': fields.float(digits=(16,2), string='Indicative stock', readonly=True),
655
 
        'consumed_qty': fields.float(digits=(16,2), string='Qty consumed', required=True),
656
 
        'batch_number_check': fields.function(_get_checks_all, method=True, string='Batch Number Check', type='boolean', readonly=True, multi="m"),
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"),
659
 
        'prodlot_id': fields.many2one('stock.production.lot', string='Batch number'),
660
 
        'batch_mandatory': fields.boolean(string='BM'),
661
 
        'expiry_date': fields.date(string='Expiry date'),
662
 
        'date_mandatory': fields.boolean(string='DM'),
663
 
        'asset_id': fields.char(size=32, string='Asset'),
664
 
        'asset_mandatory': fields.boolean('AM'),
665
 
        'remark': fields.char(size=256, string='Remark'),
666
 
        'move_id': fields.many2one('stock.move', string='Move'),
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'),
673
 
        }
674
 
 
675
 
    _defaults = {
676
 
        'inactive_product': False,
677
 
        'inactive_error': lambda *a: '',
678
 
    }
679
 
 
680
 
# uf-1344 => need to pass the context so we use create and write instead
681
 
#    _constraints = [
682
 
#        (_check_qty, "The Qty Consumed can't be greater than the Indicative Stock", ['consumed_qty']),
683
 
#    ]
684
 
 
685
 
    _sql_constraints = [
686
 
        ('unique_lot_poduct', "unique(product_id, prodlot_id, rac_id)", 'The couple product, batch number has to be unique'),
687
 
    ]
688
 
 
689
 
    def create(self, cr, uid, vals=None, context=None):
690
 
        '''
691
 
        Call the constraint
692
 
        '''
693
 
        if context is None:
694
 
            context = {}
695
 
        res = super(real_average_consumption_line, self).create(cr, uid, vals, context=context)
696
 
        check = self._check_qty(cr, uid, res, context)
697
 
        if not check:
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"))
704
 
        return res
705
 
 
706
 
    def write(self, cr, uid, ids, vals, context=None):
707
 
        if context is None:
708
 
            context = {}
709
 
        if isinstance(ids, (int, long)):
710
 
            ids = [ids]
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]
715
 
            message = ''
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")
727
 
            if message:
728
 
                raise osv.except_osv(_('Warning !'), message)
729
 
            else:
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)
733
 
        if not check:
734
 
            raise osv.except_osv(_('Error'), _('The Qty Consumed cant\'t be greater than the Indicative Stock'))
735
 
        return res
736
 
 
737
 
    def change_expiry(self, cr, uid, id, expiry_date, product_id, location_id, uom, remark=False, context=None):
738
 
        '''
739
 
        expiry date changes, find the corresponding internal prod lot
740
 
        '''
741
 
        if context is None:
742
 
            context = {}
743
 
        prodlot_obj = self.pool.get('stock.production.lot')
744
 
        result = {'value':{}}
745
 
        context.update({'location': location_id})
746
 
       
747
 
        if expiry_date and product_id:
748
 
            prod_ids = prodlot_obj.search(cr, uid, [('life_date', '=', expiry_date),
749
 
                                                    ('type', '=', 'internal'),
750
 
                                                    ('product_id', '=', product_id)], context=context)
751
 
            if not prod_ids:
752
 
                # display warning
753
 
                result['warning'] = {'title': _('Error'),
754
 
                                     'message': _('The selected Expiry Date does not exist in the system.')}
755
 
                # clear date
756
 
                result['value'].update(expiry_date=False, prodlot_id=False)
757
 
            else:
758
 
                # return first prodlot
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'] = ''
763
 
                return result
764
 
                
765
 
        else:
766
 
            # clear expiry date, we clear production lot
767
 
            result['value'].update(prodlot_id=False)
768
 
   
769
 
        context.update(uom=uom)
770
 
        context.update({'compute_child': False})
771
 
        product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
772
 
        result['value'].update({'product_qty': product.qty_available})
773
 
        
774
 
        return result
775
 
 
776
 
    def change_asset(self, cr, uid, id, asset, product_id, location_id, uom, remark=False, context=None):
777
 
        '''
778
 
        Asset change, remove the remark
779
 
        '''
780
 
        if context is None:
781
 
            context = {}
782
 
 
783
 
        result = {'value':{}}
784
 
        if remark and remark == 'You must assign an asset':
785
 
            result.setdefault('value', {}).update(remark='')
786
 
 
787
 
        return result
788
 
 
789
 
 
790
 
    def change_qty(self, cr, uid, ids, qty, product_id, prodlot_id, location, uom, context=None):
791
 
        if context is None:
792
 
            context = {}
793
 
 
794
 
        res = {'value': {}}
795
 
 
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")}
798
 
        
799
 
        if qty:
800
 
            res = self.pool.get('product.uom')._change_round_up_qty(cr, uid, uom, qty, 'consumed_qty', result=res)
801
 
 
802
 
        if prodlot_id and qty > stock_qty:
803
 
            res.setdefault('warning', {}).update(warn_msg)
804
 
            res.setdefault('value', {}).update({'consumed_qty': 0})
805
 
        if qty > stock_qty:
806
 
            res.setdefault('warning', {}).update(warn_msg)
807
 
        
808
 
        return res
809
 
 
810
 
    def change_prodlot(self, cr, uid, ids, product_id, prodlot_id, expiry_date, location_id, uom, remark=False, context=None):
811
 
        '''
812
 
        Set the expiry date according to the prodlot
813
 
        '''
814
 
        if context is None:
815
 
            context = {}
816
 
        res = {'value': {}}
817
 
        context.update({'location': location_id, 'uom': uom})
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'] = ''
821
 
            res['value'].update({'expiry_date': self.pool.get('stock.production.lot').browse(cr, uid, prodlot_id, context=context).life_date})
822
 
        elif not prodlot_id and expiry_date:
823
 
            res['value'].update({'expiry_date': False})
824
 
 
825
 
        if not prodlot_id:
826
 
            context.update({'compute_child': False})
827
 
            product_qty = self.pool.get('product.product').browse(cr, uid, product_id, context=context).qty_available
828
 
        else:
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})
834
 
 
835
 
        return res
836
 
   
837
 
    def uom_onchange(self, cr, uid, ids, product_id, product_qty, location_id=False, uom=False, lot=False, context=None):
838
 
        if context is None:
839
 
            context = {}
840
 
        qty_available = 0
841
 
        d = {}
842
 
        if uom and product_id:
843
 
            qty_available = self._get_qty(cr, uid, product_id, lot, location_id, uom)
844
 
 
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)]
848
 
 
849
 
        res = {'value': {'product_qty': qty_available}, 'domain': d}
850
 
 
851
 
        if product_qty:
852
 
            res = self.pool.get('product.uom')._change_round_up_qty(cr, uid, uom, product_qty, 'consumed_qty', result=res)
853
 
 
854
 
        return res
855
 
 
856
 
    def product_onchange(self, cr, uid, ids, product_id, location_id=False, uom=False, lot=False, context=None):
857
 
        '''
858
 
        Set the product uom when the product change
859
 
        '''
860
 
        if context is None:
861
 
            context = {}
862
 
        product_obj = self.pool.get('product.product')
863
 
        v = {'batch_mandatory': False, 'date_mandatory': False, 'asset_mandatory': False}
864
 
        d = {'uom_id': []} 
865
 
        if product_id:
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)
868
 
            if test:
869
 
                return res
870
 
            if location_id:
871
 
                context.update({'location': location_id, 'uom': uom})
872
 
 
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
876
 
                
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')})
883
 
 
884
 
            uom = product.uom_id.id
885
 
            v.update({'uom_id': uom})
886
 
            d['uom_id'] = [('category_id', '=', product.uom_id.category_id.id)]
887
 
            if location_id:
888
 
                v.update({'product_qty': qty_available})
889
 
        else:
890
 
            v.update({'uom_id': False, 'product_qty': 0.00, 'prodlot_id': False, 'expiry_date': False, 'consumed_qty': 0.00})
891
 
        
892
 
        return {'value': v, 'domain': d}
893
 
 
894
 
    def copy(self, cr, uid, line_id, default=None, context=None):
895
 
        if not context:
896
 
            context = {}
897
 
 
898
 
        if not default:
899
 
            default = {}
900
 
 
901
 
        default.update({'prodlot_id': False, 'expiry_date': False, 'asset_id': False})
902
 
 
903
 
        if 'consumed_qty' in default and default['consumed_qty'] < 0.00:
904
 
            default['consumed_qty'] = 0.00
905
 
 
906
 
        return super(real_average_consumption_line, self).copy(cr, uid, line_id[0], default=default, context={'noraise': True})
907
 
 
908
 
real_average_consumption_line()
909
 
 
910
 
 
911
 
class real_consumption_change_location(osv.osv_memory):
912
 
    _name = 'real.consumption.change.location'
913
 
 
914
 
    _columns = {
915
 
        'report_id': fields.many2one('real.average.consumption', string='Report'),
916
 
        'location_id': fields.many2one('stock.location', string='Consumer location', required=True),
917
 
    }
918
 
 
919
 
    def change_location(self, cr, uid, ids, context=None):
920
 
        '''
921
 
        Change location of the report and reload the report
922
 
        '''
923
 
        if isinstance(ids, (int, long)):
924
 
            ids = [ids]
925
 
 
926
 
        wiz = self.browse(cr, uid, ids[0], context=context)
927
 
 
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)
930
 
 
931
 
        return {'type': 'ir.actions.act_window',
932
 
                'res_model': 'real.average.consumption',
933
 
                'view_type': 'form',
934
 
                'view_mode': 'form,tree',
935
 
                'res_id': wiz.report_id.id,
936
 
                'target': 'dummy'}
937
 
 
938
 
real_consumption_change_location()
939
 
 
940
 
 
941
 
class monthly_review_consumption(osv.osv):
942
 
    _name = 'monthly.review.consumption'
943
 
    _description = 'Monthly review consumption'
944
 
    _rec_name = 'creation_date'
945
 
    
946
 
    def _get_nb_lines(self, cr, uid, ids, field_name, args, context=None):
947
 
        '''
948
 
        Returns the # of lines on the monthly review consumption
949
 
        '''
950
 
        res = {}
951
 
        
952
 
        for mrc in self.browse(cr, uid, ids, context=context):
953
 
            res[mrc.id] = len(mrc.line_ids)
954
 
            
955
 
        return res
956
 
 
957
 
    def get_bool_values(self, cr, uid, ids, fields, arg, context=None):
958
 
        res = {}
959
 
        if isinstance(ids, (int, long)):
960
 
            ids = [ids]
961
 
        for obj in self.browse(cr, uid, ids, context=context):
962
 
            res[obj.id] = False
963
 
            if any([item for item in obj.line_ids  if item.to_correct_ok]):
964
 
                res[obj.id] = True
965
 
        return res
966
 
 
967
 
    _columns = {
968
 
        'creation_date': fields.date(string='Creation date'),
969
 
        'cons_location_id': fields.char(size=256, string='Location', readonly=True),
970
 
        'period_from': fields.date(string='Period from', required=True),
971
 
        'period_to': fields.date(string='Period to', required=True),
972
 
        'sublist_id': fields.many2one('product.list', string='List/Sublist'),
973
 
        'nomen_id': fields.many2one('product.nomenclature', string='Products\' nomenclature level'),
974
 
        'line_ids': fields.one2many('monthly.review.consumption.line', 'mrc_id', string='Lines'),
975
 
        'nb_lines': fields.function(_get_nb_lines, method=True, type='integer', string='# lines', readonly=True,),
976
 
        'nomen_manda_0': fields.many2one('product.nomenclature', 'Main Type'),
977
 
        'nomen_manda_1': fields.many2one('product.nomenclature', 'Group'),
978
 
        'nomen_manda_2': fields.many2one('product.nomenclature', 'Family'),
979
 
        'nomen_manda_3': fields.many2one('product.nomenclature', 'Root'),
980
 
        'hide_column_error_ok': fields.function(get_bool_values, method=True, readonly=True, type="boolean", string="Show column errors", store=False),
981
 
    }
982
 
    
983
 
    _defaults = {
984
 
        'period_to': lambda *a: (DateFrom(time.strftime('%Y-%m-%d')) + RelativeDateTime(months=1, day=1, days=-1)).strftime('%Y-%m-%d'),
985
 
        'creation_date': lambda *a: time.strftime('%Y-%m-%d'),
986
 
        'cons_location_id': lambda *a: 'MSF Instance',
987
 
    }
988
 
 
989
 
    def period_change(self, cr, uid, ids, period_from, period_to, context=None):
990
 
        '''
991
 
        Get the first day of month and the last day
992
 
        '''
993
 
        res = {}
994
 
 
995
 
        if period_from:
996
 
            res.update({'period_from': (DateFrom(period_from) + RelativeDateTime(day=1)).strftime('%Y-%m-%d')})
997
 
        if period_to:
998
 
            res.update({'period_to': (DateFrom(period_to) + RelativeDateTime(months=1, day=1, days=-1)).strftime('%Y-%m-%d')})
999
 
 
1000
 
        return {'value': res}
1001
 
    
1002
 
    def import_fmc(self, cr, uid, ids, context=None):
1003
 
        '''
1004
 
        Launches the wizard to import lines from a file
1005
 
        '''
1006
 
        if context is None:
1007
 
            context = {}
1008
 
        context.update({'active_id': ids[0]})
1009
 
        
1010
 
        return {'type': 'ir.actions.act_window',
1011
 
                'res_model': 'wizard.import.fmc',
1012
 
                'view_type': 'form',
1013
 
                'view_mode': 'form',
1014
 
                'target': 'new',
1015
 
                'context': context,
1016
 
                }
1017
 
        
1018
 
    def export_fmc(self, cr, uid, ids, context=None):
1019
 
        """
1020
 
        Return an xml file to open with Excel
1021
 
        """
1022
 
        datas = {'ids': ids}
1023
 
 
1024
 
        return {'type': 'ir.actions.report.xml',
1025
 
                'report_name': 'monthly.consumption.xls',
1026
 
                'datas': datas}
1027
 
#        '''
1028
 
#        Creates a CSV file and launches the wizard to save it
1029
 
#        '''
1030
 
#        if context is None:
1031
 
#            context = {}
1032
 
#        fmc = self.browse(cr, uid, ids[0], context=context)
1033
 
#        
1034
 
#        outfile = TemporaryFile('w+')
1035
 
#        writer = csv.writer(outfile, quotechar='"', delimiter=',')
1036
 
#        writer.writerow(['Product Code', 'Product Description', 'AMC', 'FMC', 'Valid until'])
1037
 
#        
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 ''])
1040
 
#        outfile.seek(0)    
1041
 
#        file = base64.encodestring(outfile.read())
1042
 
#        outfile.close()
1043
 
#        
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'})
1047
 
#        
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',
1053
 
#                'target': 'new',
1054
 
#                }
1055
 
        
1056
 
    def fill_lines(self, cr, uid, ids, context=None):
1057
 
        '''
1058
 
        Fill all lines according to defined nomenclature level and sublist
1059
 
        '''
1060
 
        if context is None:
1061
 
            context = {}
1062
 
        line_obj = self.pool.get('monthly.review.consumption.line')
1063
 
        for report in self.browse(cr, uid, ids, context=context):
1064
 
            product_ids = []
1065
 
            products = []
1066
 
            # Get all products for the defined nomenclature
1067
 
            nom = False
1068
 
            field = False
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'
1081
 
            if nom:
1082
 
                product_ids.extend(self.pool.get('product.product').search(cr, uid, [(field, '=', nom)], context=context))
1083
 
            
1084
 
            # Get all products for the defined list
1085
 
            if report.sublist_id:
1086
 
                for line in report.sublist_id.product_ids:
1087
 
                    product_ids.append(line.name.id)
1088
 
                    
1089
 
            # Check if products in already existing lines are in domain
1090
 
            products = []
1091
 
            for line in report.line_ids:
1092
 
                if line.name.id in product_ids:
1093
 
                    products.append(line.name.id)
1094
 
                else:
1095
 
                    self.pool.get('monthly.review.consumption.line').unlink(cr, uid, line.id, context=context)
1096
 
 
1097
 
            amc_context = context.copy()
1098
 
            amc_context.update({'from_date': report.period_from, 'to_date': report.period_to})
1099
 
            if amc_context.get('from_date', False):
1100
 
                from_date = (DateFrom(amc_context.get('from_date')) + RelativeDateTime(day=1)).strftime('%Y-%m-%d')
1101
 
                amc_context.update({'from_date': from_date})
1102
 
                                               
1103
 
            if amc_context.get('to_date', False):
1104
 
                to_date = (DateFrom(amc_context.get('to_date')) + RelativeDateTime(months=1, day=1, days=-1)).strftime('%Y-%m-%d')
1105
 
                amc_context.update({'to_date': to_date})
1106
 
                    
1107
 
            for product in self.pool.get('product.product').browse(cr, uid, product_ids, context=context):
1108
 
                # Check if the product is not already on the report
1109
 
                if product.id not in products:
1110
 
                    products.append(product.id)
1111
 
                    amc = self.pool.get('product.product').compute_amc(cr, uid, product.id, context=amc_context)
1112
 
                    last_fmc_reviewed = False
1113
 
                    line_ids = line_obj.search(cr, uid, [('name', '=', product.id), ('valid_ok', '=', True)], order='valid_until desc, id desc', context=context)
1114
 
                    if line_ids:
1115
 
                        for line in line_obj.browse(cr, uid, [line_ids[0]], context=context):
1116
 
                            last_fmc_reviewed = line.mrc_id.creation_date
1117
 
                    self.pool.get('monthly.review.consumption.line').create(cr, uid, {'name': product.id,
1118
 
                                                                                      'amc': amc,
1119
 
                                                                                      'fmc': amc,
1120
 
                                                                                      'fmc2': amc,
1121
 
                                                                                      'last_reviewed': last_fmc_reviewed,
1122
 
                                                                                      'last_reviewed2': last_fmc_reviewed,
1123
 
                                                                                      'mrc_id': report.id})
1124
 
        
1125
 
        return {'type': 'ir.actions.act_window',
1126
 
                'res_model': 'monthly.review.consumption',
1127
 
                'view_type': 'form',
1128
 
                'view_mode': 'form',
1129
 
                'res_id': ids[0],
1130
 
                'target': 'dummy',
1131
 
                'context': context}
1132
 
 
1133
 
 
1134
 
    def valid_multiple_lines(self, cr, uid, ids, context=None):
1135
 
        '''
1136
 
        Validate multiple lines
1137
 
        '''
1138
 
        if context is None:
1139
 
            context = {}
1140
 
        self.check_lines_to_fix(cr, uid, ids, context)
1141
 
        for report in self.browse(cr, uid, ids, context=context):
1142
 
            for line in report.line_ids:
1143
 
                if not line.valid_ok:
1144
 
                    self.pool.get('monthly.review.consumption.line').valid_line(cr, uid, line.id, context=context)
1145
 
 
1146
 
        return {'type': 'ir.actions.act_window',
1147
 
                'res_model': 'monthly.review.consumption',
1148
 
                'view_type': 'form',
1149
 
                'view_mode': 'form',
1150
 
                'res_id': ids[0],
1151
 
                'target': 'dummy',
1152
 
                'context': context}
1153
 
    
1154
 
    def write(self, cr, uid, ids, vals, context=None):
1155
 
        if vals.get('sublist_id',False):
1156
 
            vals.update({'nomen_manda_0':False,'nomen_manda_1':False,'nomen_manda_2':False,'nomen_manda_3':False})
1157
 
        if vals.get('nomen_manda_0',False):
1158
 
            vals.update({'sublist_id':False})
1159
 
        ret = super(monthly_review_consumption, self).write(cr, uid, ids, vals, context=context)
1160
 
        return ret
1161
 
    
1162
 
    def onChangeSearchNomenclature(self, cr, uid, id, position, type, nomen_manda_0, nomen_manda_1, nomen_manda_2, nomen_manda_3, num=True, context=None):
1163
 
        return 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})
1164
 
    
1165
 
    def get_nomen(self, cr, uid, id, field):
1166
 
        return self.pool.get('product.nomenclature').get_nomen(cr, uid, self, id, field, context={'withnum': 1})
1167
 
 
1168
 
    def button_remove_lines(self, cr, uid, ids, context=None):
1169
 
        '''
1170
 
        Remove lines
1171
 
        '''
1172
 
        if context is None:
1173
 
            context = {}
1174
 
        if isinstance(ids, (int, long)):
1175
 
            ids = [ids]
1176
 
        vals = {}
1177
 
        vals['line_ids'] = []
1178
 
        for line in self.browse(cr, uid, ids, context=context):
1179
 
            line_browse_list = line.line_ids
1180
 
            for var in line_browse_list:
1181
 
                vals['line_ids'].append((2, var.id))
1182
 
            self.write(cr, uid, ids, vals, context=context)
1183
 
        return True
1184
 
 
1185
 
    def check_lines_to_fix(self, cr, uid, ids, context=None):
1186
 
        """
1187
 
        Check the lines that need to be corrected.
1188
 
        """
1189
 
        if isinstance(ids, (int, long)):
1190
 
            ids = [ids]
1191
 
        for var in self.browse(cr, uid, ids, context=context):
1192
 
            # we check the lines that need to be fixed
1193
 
            if var.line_ids:
1194
 
                for var in var.line_ids:
1195
 
                    if var.to_correct_ok:
1196
 
                        raise osv.except_osv(_('Warning !'), _('Some lines need to be fixed before.'))
1197
 
        return True
1198
 
 
1199
 
monthly_review_consumption()
1200
 
 
1201
 
 
1202
 
class monthly_review_consumption_line(osv.osv):
1203
 
    _name = 'monthly.review.consumption.line'
1204
 
    _description = 'Monthly review consumption line'
1205
 
    _order = 'ref'
1206
 
    
1207
 
    def _get_amc(self, cr, uid, ids, field_name, arg, ctx=None):
1208
 
        '''
1209
 
        Calculate the product AMC for the period
1210
 
        '''
1211
 
        if ctx is None:
1212
 
            ctx = {}
1213
 
        context = ctx.copy()
1214
 
        res = {}
1215
 
        
1216
 
        for line in self.browse(cr, uid, ids, context=context):
1217
 
            context.update({'from_date': line.mrc_id.period_from, 'to_date': line.mrc_id.period_to})
1218
 
            if context.get('from_date', False):
1219
 
                from_date = (DateFrom(context.get('from_date')) + RelativeDateTime(day=1)).strftime('%Y-%m-%d')
1220
 
                context.update({'from_date': from_date})
1221
 
                                               
1222
 
            if context.get('to_date', False):
1223
 
                to_date = (DateFrom(context.get('to_date')) + RelativeDateTime(months=1, day=1, days=-1)).strftime('%Y-%m-%d')
1224
 
                context.update({'to_date': to_date})
1225
 
                    
1226
 
            res[line.id] = self.pool.get('product.product').compute_amc(cr, uid, line.name.id, context=context)
1227
 
            
1228
 
        return res
1229
 
    
1230
 
    def _get_last_fmc(self, cr, uid, ids, field_name, args, context=None):
1231
 
        '''
1232
 
        Returns the last fmc date
1233
 
        '''
1234
 
        if context is None:
1235
 
            context = {}
1236
 
        res = {}
1237
 
        
1238
 
        for line in self.browse(cr, uid, ids, context=context):
1239
 
            res[line.id] = self.product_onchange(cr, uid, line.id, line.name.id, line.mrc_id.id, context=context).get('value', {}).get('last_reviewed', None)
1240
 
            
1241
 
        return res
1242
 
 
1243
 
    def create(self, cr, uid, vals, context=None):
1244
 
        if context is None:
1245
 
            context = {}
1246
 
        if 'fmc2' in vals:
1247
 
            vals.update({'fmc': vals.get('fmc2')})
1248
 
        if 'last_reviewed2' in vals:
1249
 
            vals.update({'last_reviewed': vals.get('last_reviewed2')})
1250
 
 
1251
 
        if vals.get('valid_ok') and not vals.get('last_reviewed'):
1252
 
            vals.update({'last_reviewed': time.strftime('%Y-%m-%d'),
1253
 
                         'last_reviewed2': time.strftime('%Y-%m-%d')})
1254
 
 
1255
 
        return super(monthly_review_consumption_line, self).create(cr, uid, vals, context=context)
1256
 
 
1257
 
    def write(self, cr, uid, ids, vals, context=None):
1258
 
        if context is None:
1259
 
            context = {}
1260
 
        if isinstance(ids, (int, long)):
1261
 
            ids = [ids]
1262
 
        if not context.get('import_in_progress') and not context.get('button'):
1263
 
            obj_data = self.pool.get('ir.model.data')
1264
 
            tbd_product = obj_data.get_object_reference(cr, uid, 'msf_doc_import', 'product_tbd')[1]
1265
 
            if vals.get('name'):
1266
 
                if vals.get('name') == tbd_product:
1267
 
                    raise osv.except_osv(_('Warning !'), _('You have to define a valid product, i.e. not "To be define".'))
1268
 
                else:
1269
 
                    vals['to_correct_ok'] = False
1270
 
                    vals['text_error'] = False
1271
 
        if 'fmc2' in vals:
1272
 
            vals.update({'fmc': vals.get('fmc2')})
1273
 
        if 'last_reviewed2' in vals:
1274
 
            vals.update({'last_reviewed': vals.get('last_reviewed2')})
1275
 
 
1276
 
        if vals.get('valid_ok') and not vals.get('last_reviewed'):
1277
 
            vals.update({'last_reviewed': time.strftime('%Y-%m-%d'),
1278
 
                         'last_reviewed2': time.strftime('%Y-%m-%d')})
1279
 
 
1280
 
        return super(monthly_review_consumption_line, self).write(cr, uid, ids, vals, context=context)
1281
 
 
1282
 
    def _get_mrc_change(self, cr, uid, ids, context=None):
1283
 
        '''
1284
 
        Returns MRC ids when Date change
1285
 
        '''
1286
 
        result = {}
1287
 
        for mrc in self.pool.get('monthly.review.consumption').browse(cr, uid, ids, context=context):
1288
 
            for line in mrc.line_ids:
1289
 
                result[line.id] = True
1290
 
                
1291
 
        return result.keys()
1292
 
    
1293
 
    def _get_product(self, cr, uid, ids, context=None):
1294
 
        return self.pool.get('monthly.review.consumption.line').search(cr, uid, [('name', 'in', ids)], context=context)
1295
 
 
1296
 
    def _get_checks_all(self, cr, uid, ids, name, arg, context=None):
1297
 
        result = {}
1298
 
        for id in ids:
1299
 
            result[id] = {'to_correct_ok': False}
1300
 
            
1301
 
        for out in self.browse(cr, uid, ids, context=context):
1302
 
            # the lines with to_correct_ok=True will be red
1303
 
            if out.text_error:
1304
 
                result[out.id]['to_correct_ok'] = True
1305
 
        return result
1306
 
 
1307
 
    _columns = {
1308
 
        'name': fields.many2one('product.product', string='Product', required=True),
1309
 
        'ref': fields.related('name', 'default_code', type='char', size=64, readonly=True,
1310
 
                              store={'product.product': (_get_product, ['default_code'], 10),
1311
 
                                     'monthly.review.consumption.line': (lambda self, cr, uid, ids, c=None: ids, ['name'], 20)}),
1312
 
        'amc': fields.function(_get_amc, string='AMC', method=True, readonly=True, 
1313
 
                               store={'monthly.review.consumption': (_get_mrc_change, ['period_from', 'period_to'], 20),
1314
 
                                      'monthly.review.consumption.line': (lambda self, cr, uid, ids, c=None: ids, [],20),}),
1315
 
        'fmc': fields.float(digits=(16,2), string='FMC'),
1316
 
        'fmc2': fields.float(digits=(16,2), string='FMC (hidden)'),
1317
 
        #'last_reviewed': fields.function(_get_last_fmc, method=True, type='date', string='Last reviewed on', readonly=True, store=True),
1318
 
        'last_reviewed': fields.date(string='Last reviewed on', readonly=True),
1319
 
        'last_reviewed2': fields.date(string='Last reviewed on (hidden)'),
1320
 
        'valid_until': fields.date(string='Valid until'),
1321
 
        'valid_ok': fields.boolean(string='Validated', readonly=False),
1322
 
        'mrc_id': fields.many2one('monthly.review.consumption', string='MRC', required=True, ondelete='cascade'),
1323
 
        'mrc_creation_date': fields.related('mrc_id', 'creation_date', type='date', store=True),
1324
 
        'text_error': fields.text('Errors', readonly=True),
1325
 
        'to_correct_ok': fields.function(_get_checks_all, method=True, type="boolean", string="To correct", store=False, readonly=True, multi="m"),
1326
 
    }
1327
 
    
1328
 
    def valid_line(self, cr, uid, ids, context=None):
1329
 
        '''
1330
 
        Valid the line and enter data in product form
1331
 
        '''
1332
 
        if not context:
1333
 
            context = {}
1334
 
            
1335
 
        if isinstance(ids, (int, long)):
1336
 
            ids = [ids]
1337
 
                
1338
 
        for line in self.browse(cr, uid, ids, context=context):
1339
 
            if line.valid_ok:
1340
 
                raise osv.except_osv(_('Error'), _('The line is already validated !'))
1341
 
            
1342
 
            self.write(cr, uid, [line.id], {'valid_ok': True, 
1343
 
                                            'last_reviewed': time.strftime('%Y-%m-%d'),
1344
 
                                            'last_reviewed2': time.strftime('%Y-%m-%d')}, context=context)
1345
 
            
1346
 
        return
1347
 
    
1348
 
    def display_graph(self, cr, uid, ids, context=None):
1349
 
        '''
1350
 
        Display the graph view of the line
1351
 
        '''
1352
 
        raise osv.except_osv('Error !', 'Not implemented yet !')
1353
 
 
1354
 
    def fmc_change(self, cr, uid, ids, amc, fmc, product_id, context=None):
1355
 
        '''
1356
 
        Valid the line if the FMC is manually changed
1357
 
        '''
1358
 
        if context is None:
1359
 
            context = {}
1360
 
        res = {}
1361
 
 
1362
 
        if fmc != amc:
1363
 
            res.update({'valid_ok': True, 'last_reviewed': time.strftime('%Y-%m-%d'), 'fmc2': fmc, 'last_reviewed2': time.strftime('%Y-%m-%d')})
1364
 
        else:
1365
 
            last_fmc_reviewed = False
1366
 
            domain = [('name', '=', product_id), ('valid_ok', '=', True)]
1367
 
            line_ids = self.search(cr, uid, domain, order='valid_until desc, mrc_creation_date desc', context=context)
1368
 
            
1369
 
            if line_ids:
1370
 
                for line in self.browse(cr, uid, [line_ids[0]], context=context):
1371
 
                    last_fmc_reviewed = line.mrc_id.creation_date
1372
 
 
1373
 
            res.update({'last_reviewed': last_fmc_reviewed, 'last_reviewed2': last_fmc_reviewed, 'fmc2': fmc})
1374
 
 
1375
 
        return {'value': res}
1376
 
    
1377
 
    def product_onchange(self, cr, uid, ids, product_id, mrc_id=False, from_date=False, to_date=False, context=None):
1378
 
        '''
1379
 
        Fill data in the line
1380
 
        '''
1381
 
        if isinstance(ids, (int, long)):
1382
 
            ids = [ids]
1383
 
 
1384
 
        if not context:
1385
 
            context = {}
1386
 
 
1387
 
        # Compute the AMC on the period of the consumption report
1388
 
        context.update({'from_date': from_date, 'to_date': to_date})
1389
 
        
1390
 
        product_obj = self.pool.get('product.product')
1391
 
        line_obj = self.pool.get('monthly.review.consumption.line')
1392
 
        
1393
 
        last_fmc_reviewed = False
1394
 
        
1395
 
        if not product_id:
1396
 
            return {'value': {'amc': 0.00,
1397
 
                              'fmc': 0.00,
1398
 
                              'fmc2': 0.00,
1399
 
                              'last_reviewed2': 0.00,
1400
 
                              'last_reviewed': None,
1401
 
                              'valid_until': False,
1402
 
                              'valid_ok': False}}
1403
 
        
1404
 
        domain = [('name', '=', product_id), ('valid_ok', '=', True)]
1405
 
        
1406
 
        line_ids = line_obj.search(cr, uid, domain, order='valid_until desc, mrc_creation_date desc', context=context)
1407
 
            
1408
 
        if line_ids:
1409
 
            for line in self.browse(cr, uid, [line_ids[0]], context=context):
1410
 
                last_fmc_reviewed = line.mrc_id.creation_date
1411
 
 
1412
 
        if context.get('from_date', False):
1413
 
            from_date = (DateFrom(context.get('from_date')) + RelativeDateTime(day=1)).strftime('%Y-%m-%d')
1414
 
            context.update({'from_date': from_date})
1415
 
                                               
1416
 
        if context.get('to_date', False):
1417
 
            to_date = (DateFrom(context.get('to_date')) + RelativeDateTime(months=1, day=1, days=-1)).strftime('%Y-%m-%d')
1418
 
            context.update({'to_date': to_date})
1419
 
                
1420
 
        amc = product_obj.compute_amc(cr, uid, product_id, context=context)
1421
 
        return {'value': {'amc': amc,
1422
 
                          'fmc': amc,
1423
 
                          'fmc2': amc,
1424
 
                          'last_reviewed': last_fmc_reviewed,
1425
 
                          'last_reviewed2': last_fmc_reviewed,
1426
 
                          'valid_until': False,
1427
 
                          'valid_ok': False}}
1428
 
        
1429
 
    
1430
 
monthly_review_consumption_line()
1431
 
 
1432
 
 
1433
 
class product_product(osv.osv):
1434
 
    _name = 'product.product'
1435
 
    _inherit = 'product.product'
1436
 
    
1437
 
    def _compute_fmc(self, cr, uid, ids, field_name, args, context=None):
1438
 
        '''
1439
 
        Returns the last value of the FMC
1440
 
        '''
1441
 
        if not context:
1442
 
            context = {}
1443
 
            
1444
 
        res = {}
1445
 
        #fmc_obj = self.pool.get('monthly.review.consumption')
1446
 
        fmc_line_obj = self.pool.get('monthly.review.consumption.line')
1447
 
            
1448
 
        # Search all Review report for locations
1449
 
        #fmc_ids = fmc_obj.search(cr, uid, [], order='period_to desc, creation_date desc', limit=1, context=context)
1450
 
        
1451
 
        for product in ids:
1452
 
            res[product] = 0.00
1453
 
            
1454
 
            # Search all validated lines with the product
1455
 
            #line_ids = fmc_line_obj.search(cr, uid, [('name', '=', product), ('valid_ok', '=', True), ('mrc_id', 'in', fmc_ids)], context=context)
1456
 
            line_ids = fmc_line_obj.search(cr, uid, [('name', '=', product), ('valid_ok', '=', True)], order='last_reviewed desc, mrc_id desc', limit=1, context=context)
1457
 
            
1458
 
            # Get the last created line
1459
 
            for line in fmc_line_obj.browse(cr, uid, line_ids, context=context):
1460
 
                res[product] = line.fmc
1461
 
        
1462
 
        return res
1463
 
    
1464
 
    def compute_mac(self, cr, uid, ids, field_name, args, context=None):
1465
 
        '''
1466
 
        Compute the Real Average Consumption
1467
 
        '''
1468
 
        if isinstance(ids, (int, long)):
1469
 
            ids = [ids]
1470
 
        if context is None:
1471
 
            context = {}
1472
 
        
1473
 
        uom_obj = self.pool.get('product.uom')
1474
 
        
1475
 
        rac_domain = [('created_ok', '=', True)]
1476
 
        res = {}
1477
 
        
1478
 
        from_date = False
1479
 
        to_date = False
1480
 
 
1481
 
        location_ids = []
1482
 
        
1483
 
        # Read if a interval is defined
1484
 
        if context.get('from_date', False):
1485
 
            from_date = context.get('from_date')
1486
 
            rac_domain.append(('period_to', '>=', from_date))
1487
 
        
1488
 
        if context.get('to_date', False):
1489
 
            to_date = context.get('to_date')
1490
 
            rac_domain.append(('period_to', '<=', to_date))
1491
 
        
1492
 
        # Filter for one or some locations    
1493
 
        if context.get('location_id', False):
1494
 
            if type(context['location_id']) == type(1):
1495
 
                location_ids = [context['location_id']]
1496
 
            elif type(context['location_id']) in (type(''), type(u'')):
1497
 
                location_ids = self.pool.get('stock.location').search(cr, uid, [('name','ilike',context['location'])], context=context)
1498
 
            else:
1499
 
                location_ids = context.get('location_id', [])
1500
 
       
1501
 
        for id in ids:
1502
 
            res[id] = 0.00
1503
 
            if from_date and to_date:
1504
 
                rcr_domain = ['&', '&', ('product_id', 'in', ids), ('rac_id.cons_location_id', 'in', location_ids),
1505
 
                              # All lines with a report started out the period and finished in the period 
1506
 
                              '|', '&', ('rac_id.period_to', '>=', from_date), ('rac_id.period_to', '<=', to_date),
1507
 
                              # All lines with a report started in the period and finished out the period 
1508
 
                              '|', '&', ('rac_id.period_from', '<=', to_date), ('rac_id.period_from', '>=', from_date),
1509
 
                              # All lines with a report started before the period  and finished after the period
1510
 
                              '&', ('rac_id.period_from', '<=', from_date), ('rac_id.period_to', '>=', to_date)]
1511
 
            
1512
 
                rcr_line_ids = self.pool.get('real.average.consumption.line').search(cr, uid, rcr_domain, context=context)
1513
 
                for line in self.pool.get('real.average.consumption.line').browse(cr, uid, rcr_line_ids, context=context):
1514
 
                    cons = self._get_period_consumption(cr, uid, line, from_date, to_date, context=context)
1515
 
                    res[id] += uom_obj._compute_qty(cr, uid, line.uom_id.id, cons, line.product_id.uom_id.id)
1516
 
 
1517
 
                # We want the average for the entire period
1518
 
                if to_date < from_date:
1519
 
                    raise osv.except_osv(_('Error'), _('You cannot have a \'To Date\' younger than \'From Date\'.'))
1520
 
                # Calculate the # of months in the period
1521
 
                try:
1522
 
                    to_date_str = strptime(to_date, '%Y-%m-%d')
1523
 
                except ValueError:
1524
 
                    to_date_str = strptime(to_date, '%Y-%m-%d %H:%M:%S')
1525
 
                
1526
 
                try:
1527
 
                    from_date_str = strptime(from_date, '%Y-%m-%d')
1528
 
                except ValueError:
1529
 
                    from_date_str = strptime(from_date, '%Y-%m-%d %H:%M:%S')
1530
 
        
1531
 
                nb_months = self._get_date_diff(from_date_str, to_date_str)
1532
 
                
1533
 
                if not nb_months: nb_months = 1
1534
 
 
1535
 
                uom_id = self.browse(cr, uid, ids[0], context=context).uom_id.id
1536
 
                res[id] = res[id]/nb_months
1537
 
                res[id] = round(self.pool.get('product.uom')._compute_qty(cr, uid, uom_id, res[id], uom_id), 2)
1538
 
            
1539
 
        return res
1540
 
    
1541
 
    def compute_amc(self, cr, uid, ids, context=None):
1542
 
        '''
1543
 
        Compute the Average Monthly Consumption with this formula :
1544
 
            AMC = (sum(OUTGOING (except reason types Loan, Donation, Loss, Discrepancy))
1545
 
                  -
1546
 
                  sum(INCOMING with reason type Return from unit)) / Number of period's months
1547
 
            The AMC is the addition of all done stock moves for a product within a period.
1548
 
            For stock moves generated from a real consumption report, the qty of product is computed
1549
 
            according to the average of consumption for the time of the period.
1550
 
        '''
1551
 
        if not context:
1552
 
            context = {}
1553
 
        
1554
 
        if isinstance(ids, (int, long)):
1555
 
            ids = [ids]
1556
 
        
1557
 
        move_obj = self.pool.get('stock.move')
1558
 
        uom_obj = self.pool.get('product.uom')
1559
 
        
1560
 
        res = 0.00
1561
 
        
1562
 
        from_date = False
1563
 
        to_date = False
1564
 
        
1565
 
        # Read if a interval is defined
1566
 
        if context.get('from_date', False):
1567
 
            from_date = context.get('from_date')
1568
 
        
1569
 
        if context.get('to_date', False):
1570
 
            to_date = context.get('to_date')
1571
 
            
1572
 
        # Get all reason types
1573
 
        loan_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_loan')[1]
1574
 
        donation_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_donation')[1]
1575
 
        donation_exp_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_donation_expiry')[1]
1576
 
        loss_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_loss')[1]
1577
 
        discrepancy_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_discrepancy')[1]
1578
 
        return_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_return_from_unit')[1]
1579
 
        return_good_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_goods_return')[1]
1580
 
        replacement_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_goods_replacement')[1]
1581
 
 
1582
 
        # Update the domain
1583
 
        domain = [('state', '=', 'done'), ('reason_type_id', 'not in', (loan_id, donation_id, donation_exp_id, loss_id, discrepancy_id)), ('product_id', 'in', ids)]
1584
 
        if to_date:
1585
 
            domain.append(('date', '<=', to_date))
1586
 
        if from_date:
1587
 
            domain.append(('date', '>=', from_date))
1588
 
        
1589
 
        locations = self.pool.get('stock.location').search(cr, uid, [('usage', 'in', ('internal', 'customer'))], context=context)
1590
 
        # Add locations filters in domain if locations are passed in context
1591
 
        domain.append(('location_id', 'in', locations))
1592
 
        domain.append(('location_dest_id', 'in', locations))
1593
 
        
1594
 
        # Search all real consumption line included in the period
1595
 
        # If no period found, take all stock moves
1596
 
        if from_date and to_date:
1597
 
            rcr_domain = ['&', ('product_id', 'in', ids),
1598
 
                          # All lines with a report started out the period and finished in the period 
1599
 
                          '|', '&', ('rac_id.period_to', '>=', from_date), ('rac_id.period_to', '<=', to_date),
1600
 
                          # All lines with a report started in the period and finished out the period 
1601
 
                          '|', '&', ('rac_id.period_from', '<=', to_date), ('rac_id.period_from', '>=', from_date),
1602
 
                          # All lines with a report started before the period  and finished after the period
1603
 
                          '&', ('rac_id.period_from', '<=', from_date), ('rac_id.period_to', '>=', to_date)]
1604
 
        
1605
 
            rcr_line_ids = self.pool.get('real.average.consumption.line').search(cr, uid, rcr_domain, context=context)
1606
 
            report_move_ids = []
1607
 
            for line in self.pool.get('real.average.consumption.line').browse(cr, uid, rcr_line_ids, context=context):
1608
 
                report_move_ids.append(line.move_id.id)
1609
 
                res += self._get_period_consumption(cr, uid, line, from_date, to_date, context=context)
1610
 
            
1611
 
            if report_move_ids:
1612
 
                domain.append(('id', 'not in', report_move_ids))
1613
 
        
1614
 
        out_move_ids = move_obj.search(cr, uid, domain, context=context)
1615
 
        
1616
 
        for move in move_obj.browse(cr, uid, out_move_ids, context=context):
1617
 
            if move.reason_type_id.id in (return_id, return_good_id, replacement_id) and move.location_id.usage == 'customer':
1618
 
                res -= uom_obj._compute_qty(cr, uid, move.product_uom.id, move.product_qty, move.product_id.uom_id.id)
1619
 
            elif move.location_dest_id.usage == 'customer':
1620
 
                res += uom_obj._compute_qty(cr, uid, move.product_uom.id, move.product_qty, move.product_id.uom_id.id)
1621
 
            
1622
 
            # Update the limit in time
1623
 
            if not context.get('from_date') and (not from_date or move.date < from_date):
1624
 
                from_date = move.date
1625
 
            if not context.get('to_date') and (not to_date or move.date > to_date):
1626
 
                to_date = move.date
1627
 
                
1628
 
        if not to_date or not from_date:
1629
 
            return 0.00
1630
 
            
1631
 
        # We want the average for the entire period
1632
 
        if to_date < from_date:
1633
 
            raise osv.except_osv(_('Error'), _('You cannot have a \'To Date\' younger than \'From Date\'.'))
1634
 
        # Calculate the # of months in the period
1635
 
        try:
1636
 
            to_date_str = strptime(to_date, '%Y-%m-%d')
1637
 
        except ValueError:
1638
 
            to_date_str = strptime(to_date, '%Y-%m-%d %H:%M:%S')
1639
 
        
1640
 
        try:
1641
 
            from_date_str = strptime(from_date, '%Y-%m-%d')
1642
 
        except ValueError:
1643
 
            from_date_str = strptime(from_date, '%Y-%m-%d %H:%M:%S')
1644
 
 
1645
 
        nb_months = self._get_date_diff(from_date_str, to_date_str)
1646
 
        
1647
 
        if not nb_months: nb_months = 1
1648
 
        
1649
 
        uom_id = self.read(cr, uid, ids[0], ['uom_id'], context=context)['uom_id'][0]
1650
 
        res = res/nb_months
1651
 
        res = self.pool.get('product.uom')._compute_qty(cr, uid, uom_id, res, uom_id)
1652
 
            
1653
 
        return res
1654
 
    
1655
 
    def _get_date_diff(self, from_date, to_date):
1656
 
        '''
1657
 
        Returns the number of months between to dates according to the number
1658
 
        of days in the month.
1659
 
        '''
1660
 
        diff_date = Age(to_date, from_date)
1661
 
        res = 0.0
1662
 
        
1663
 
        def days_in_month(month, year):
1664
 
            '''
1665
 
            Returns the # of days in the month
1666
 
            '''
1667
 
            res = 30
1668
 
            if month == 2 and year%4 == 0:
1669
 
                res = 29
1670
 
            elif month == 2 and year%4 != 0:
1671
 
                res = 28
1672
 
            elif month in (1, 3, 5, 7, 8, 10, 12):
1673
 
                res = 31
1674
 
            return res
1675
 
        
1676
 
        while from_date <= to_date:
1677
 
            # Add 12 months by years between the two dates
1678
 
            if diff_date.years:
1679
 
                res += diff_date.years*12
1680
 
                from_date += RelativeDate(years=diff_date.years)
1681
 
                diff_date = Age(to_date, from_date)
1682
 
            else:
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)
1689
 
                    break
1690
 
                elif to_date.month - from_date.month > 1 or to_date.year - from_date.year > 0:
1691
 
                    res += 1
1692
 
                    from_date += RelativeDate(months=1)
1693
 
                else:
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)
1701
 
                    break
1702
 
                    
1703
 
        return res
1704
 
                     
1705
 
            
1706
 
 
1707
 
    def _compute_product_amc(self, cr, uid, ids, field_name, args, ctx=None):
1708
 
        if ctx is None:
1709
 
            ctx = {}
1710
 
        context = ctx.copy()
1711
 
        res = {}
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')
1714
 
 
1715
 
        if context.get('from_date', False):
1716
 
            from_date = (DateFrom(context.get('from_date')) + RelativeDateTime(day=1)).strftime('%Y-%m-%d')
1717
 
                                               
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')
1720
 
 
1721
 
        context.update({'from_date': from_date})
1722
 
        context.update({'to_date': to_date})
1723
 
 
1724
 
        for product in ids:
1725
 
            res[product] = self.compute_amc(cr, uid, product, context=context)
1726
 
 
1727
 
        return res
1728
 
    
1729
 
    def _get_period_consumption(self, cr, uid, line, from_date, to_date, context=None):
1730
 
        '''
1731
 
        Returns the average quantity of product in the period
1732
 
        '''        
1733
 
        # Compute the # of days in the report period
1734
 
        if context is None:
1735
 
            context = {}
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
1742
 
 
1743
 
        # Add 1 to include the last day of report to        
1744
 
        report_nb_days = delta.days + 1
1745
 
        days_incl = 0
1746
 
        
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
1767
 
        
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)
1771
 
    
1772
 
    _columns = {
1773
 
        'procure_delay': fields.float(digits=(16,2), string='Procurement Lead Time', 
1774
 
                                        help='It\'s the default time to procure this product. This lead time will be used on the Order cycle procurement computation'),
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),
1777
 
        'reviewed_consumption': fields.function(_compute_fmc, method=True, type='float', string='Forecasted Monthly Consumption', readonly=True),
1778
 
    }
1779
 
    
1780
 
    _defaults = {
1781
 
        'procure_delay': lambda *a: 60,
1782
 
    }
1783
 
 
1784
 
    
1785
 
product_product()
1786
 
 
1787
 
 
1788
 
class stock_picking(osv.osv):
1789
 
    _inherit = 'stock.picking'
1790
 
    _name = 'stock.picking'
1791
 
 
1792
 
    def _hook_log_picking_modify_message(self, cr, uid, ids, context=None, message='', pick=False):
1793
 
        '''
1794
 
        Possibility to change the message
1795
 
        '''
1796
 
        report_ids = self.pool.get('real.average.consumption').search(cr, uid, [('picking_id', '=', pick.id)], context=context)
1797
 
        if report_ids:
1798
 
            name = self.pool.get('real.average.consumption').browse(cr, uid, report_ids[0], context=context).picking_id.name
1799
 
            return 'Delivery Order %s generated from the consumption report is closed.' % name
1800
 
        else:
1801
 
            return super(stock_picking, self)._hook_log_picking_modify_message(cr, uid, ids, context=context, message=message, pick=pick)
1802
 
 
1803
 
stock_picking()
1804
 
 
1805
 
class stock_location(osv.osv):
1806
 
    _inherit = 'stock.location'
1807
 
 
1808
 
    def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
1809
 
        if context is None:
1810
 
            context = {}
1811
 
        if context.get('no3buttons') and view_type == 'search':
1812
 
            view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'consumption_calculation', 'view_stock_location_without_buttons')
1813
 
        return super(stock_location, self).fields_view_get(cr, uid, view_id, view_type, context=context, toolbar=toolbar, submenu=submenu)
1814
 
stock_location()