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

« back to all changes in this revision

Viewing changes to consumption_calculation/expiry_calculation.py

  • Committer: Olivier DOSSMANN
  • Date: 2013-05-31 14:22:09 UTC
  • mto: This revision was merged to the branch mainline in revision 1687.
  • Revision ID: od@tempo-consulting.fr-20130531142209-sbcwvzuema11guzz
UF-1991 [FIX] Problem with wizard on "msg" field. Change it to "name".

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 MSF, TeMPO Consulting.
 
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, fields
 
23
from tools.translate import _
 
24
 
 
25
from mx.DateTime import *
 
26
from datetime import date, timedelta, datetime
 
27
 
 
28
import time
 
29
 
 
30
class expiry_quantity_report(osv.osv_memory):
 
31
    _name = 'expiry.quantity.report'
 
32
    _description = 'Products Expired'
 
33
    
 
34
    def _get_date_to(self, cr, uid, ids, field_name, arg, context=None):
 
35
        '''
 
36
        Compute the end date for the calculation
 
37
        '''
 
38
        if not context:
 
39
            context = {}
 
40
        
 
41
        res = {}
 
42
        
 
43
        for report in self.browse(cr, uid, ids, context=context):
 
44
            res[report.id] = (date.today() + timedelta(weeks=report.week_nb)).strftime('%Y-%m-%d')
 
45
            
 
46
        return res
 
47
    
 
48
    _columns = {
 
49
        'location_id': fields.many2one('stock.location', string='Location'),
 
50
        'input_output_ok': fields.boolean(string='Exclude Input and Output locations'),
 
51
        'week_nb': fields.integer(string='Period of calculation (Today till XX weeks)', required=True),
 
52
        'date_to': fields.function(_get_date_to, method=True, type='date', string='Limit date', readonly=True),
 
53
        'line_ids': fields.one2many('expiry.quantity.report.line', 'report_id', string='Products', readonly=True),
 
54
    }
 
55
    
 
56
    def print_report_wizard(self, cr, uid, ids, context=None):
 
57
        '''
 
58
        Print the report directly from the wizard
 
59
        '''
 
60
        self.process_lines(cr, uid, ids, context=context)
 
61
        return self.print_report(cr, uid, ids, context=context)
 
62
    
 
63
    def print_report(self, cr, uid, ids, context=None):
 
64
        '''
 
65
        Print the report of expiry report
 
66
        '''
 
67
        datas = {'ids': ids} 
 
68
        
 
69
        return {
 
70
            'type': 'ir.actions.report.xml',
 
71
            'report_name': 'expiry.report',
 
72
            'datas': datas,
 
73
            'nodestroy': True,
 
74
            'context': context,
 
75
        }
 
76
        
 
77
    
 
78
    def process_lines(self, cr, uid, ids, context=None):
 
79
        '''
 
80
        Creates all lines of expired products
 
81
        '''
 
82
        if not context:
 
83
            context = {}
 
84
        
 
85
        move_obj = self.pool.get('stock.move')
 
86
        lot_obj = self.pool.get('stock.production.lot')
 
87
        loc_obj = self.pool.get('stock.location')
 
88
        lots = {}
 
89
        loc_ids = []
 
90
        
 
91
        report = self.browse(cr, uid, ids[0], context=context)
 
92
        lot_ids = lot_obj.search(cr, uid, [('life_date', '<=', (date.today() + timedelta(weeks=report.week_nb)).strftime('%Y-%m-%d'))])
 
93
        domain = [('date', '<=', (date.today()  + timedelta(weeks=report.week_nb)).strftime('%Y-%m-%d')), ('state', '=', 'done'), ('prodlot_id', 'in', lot_ids)]
 
94
        domain_out = [('date', '<=', (date.today()  + timedelta(weeks=report.week_nb)).strftime('%Y-%m-%d')), ('state', '=', 'done'), ('prodlot_id', 'in', lot_ids)]
 
95
            
 
96
        not_loc_ids = []
 
97
        # Remove input and output location
 
98
        if report.input_output_ok:
 
99
            wh_ids = self.pool.get('stock.warehouse').search(cr, uid, [], context=context)
 
100
            for wh in self.pool.get('stock.warehouse').browse(cr, uid, wh_ids, context=context):
 
101
                not_loc_ids.extend(loc_obj.search(cr, uid, [('location_id', 'child_of', wh.lot_input_id.id)], context=context))
 
102
                not_loc_ids.extend(loc_obj.search(cr, uid, [('location_id', 'child_of', wh.lot_output_id.id)], context=context))
 
103
 
 
104
        if report.location_id:
 
105
            # Search all children locations of the report location
 
106
            loc_ids = loc_obj.search(cr, uid, [('location_id', 'child_of', report.location_id.id), ('quarantine_location', '=', False), ('usage', '=', 'internal'), ('id', 'not in', not_loc_ids)], context=context)
 
107
        else:
 
108
            # Search all locations according to parameters
 
109
            loc_ids = loc_obj.search(cr, uid, [('usage', '=', 'internal'), ('quarantine_location', '=', False), ('id', 'not in', not_loc_ids)], context=context)
 
110
        
 
111
        # Return the good view
 
112
        view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'consumption_calculation', 'expiry_quantity_report_processed_loc_view')[1]
 
113
        domain.append(('location_dest_id', 'in', loc_ids))
 
114
        domain_out.append(('location_id', 'in', loc_ids))
 
115
 
 
116
        move_ids = move_obj.search(cr, uid, domain, context=context)
 
117
        for move in move_obj.browse(cr, uid, move_ids, context=context):
 
118
            if move.prodlot_id:
 
119
                lot_id = move.prodlot_id.id
 
120
                # Add the lot in the list
 
121
                if lot_id not in lots:
 
122
                    lots[lot_id] = {}
 
123
                
 
124
                # Add the location in the lot list
 
125
                if move.location_dest_id.id not in lots[lot_id]:
 
126
                    lots[lot_id][move.location_dest_id.id] = 0.00
 
127
 
 
128
                lots[lot_id][move.location_dest_id.id] += move.product_qty
 
129
 
 
130
 
 
131
        move_out_ids = move_obj.search(cr, uid, domain_out, context=context)
 
132
        for move in move_obj.browse(cr, uid, move_out_ids, context=context):
 
133
            if move.prodlot_id and move.prodlot_id.id in lots and move.location_id.id in lots[move.prodlot_id.id]:
 
134
                lots[move.prodlot_id.id][move.location_id.id] -= move.product_qty
 
135
                
 
136
        for lot_location in lots:
 
137
            lot_brw = lot_obj.browse(cr, uid, lot_location, context=context)
 
138
            for location in lots[lot_location]:
 
139
                if lots[lot_location][location] > 0.00:
 
140
                    context.update({'location': location, 'compute_child': False})
 
141
                    real_qty = lot_obj.browse(cr, uid, lot_location, context=context).product_id.qty_available
 
142
                    self.pool.get('expiry.quantity.report.line').create(cr, uid, {'product_id': lot_brw.product_id.id,
 
143
                                                                                  'uom_id': lot_brw.product_id.uom_id.id,
 
144
                                                                                  'real_stock': real_qty,
 
145
                                                                                  'expired_qty': lots[lot_location][location],
 
146
                                                                                  'batch_number': lot_brw.name,
 
147
                                                                                  'expiry_date': lot_brw.life_date,
 
148
                                                                                  'location_id': location,
 
149
                                                                                  'report_id': ids[0],
 
150
                                                                                  }, context=context)
 
151
            
 
152
        return {'type': 'ir.actions.act_window',
 
153
                'res_model': 'expiry.quantity.report',
 
154
                'view_type': 'form',
 
155
                'view_mode': 'form',
 
156
                'nodestroy': True,
 
157
                'view_id': [view_id],
 
158
                'res_id': ids[0],
 
159
        }
 
160
    
 
161
expiry_quantity_report()
 
162
 
 
163
 
 
164
class expiry_quantity_report_line(osv.osv_memory):
 
165
    _name = 'expiry.quantity.report.line'
 
166
    _description = 'Products expired line'
 
167
    _order = 'expiry_date, location_id, product_id asc'
 
168
    
 
169
    _columns = {
 
170
        'report_id': fields.many2one('expiry.quantity.report', string='Report', required=True),
 
171
        'product_id': fields.many2one('product.product', string='Product', required=True),
 
172
        'product_code': fields.related('product_id', 'default_code', string='Ref.', type='char'),
 
173
        'product_name': fields.related('product_id', 'name', string='Name', type='char'),
 
174
        'uom_id': fields.related('product_id', 'uom_id', string='UoM', type='many2one', relation='product.uom'),
 
175
        'real_stock': fields.float(digits=(16, 2), string='Real stock'),
 
176
        'expired_qty': fields.float(digits=(16, 2), string='Batch exp.'),
 
177
        #'batch_number': fields.many2one('production.lot', string='Batch'),
 
178
        'batch_number': fields.char(size=64, string='Batch'),
 
179
        'expiry_date': fields.date(string='Exp. date'),
 
180
        'location_id': fields.many2one('stock.location', string='Loc.'),
 
181
    }
 
182
    
 
183
expiry_quantity_report_line()
 
184
 
 
185
 
 
186
class product_likely_expire_report(osv.osv_memory):
 
187
    _name = 'product.likely.expire.report'
 
188
    _description = 'Products list likely to expire'
 
189
    
 
190
    _columns = {
 
191
        'location_id': fields.many2one('stock.location', string='Location'),
 
192
        'msf_instance': fields.char(size=64, string='Location', readonly=True),
 
193
        'input_output_ok': fields.boolean(string='Exclude Input and Output locations'),
 
194
        'date_from': fields.date(string='From', required=True, readonly=True),
 
195
        'date_to': fields.date(string='To', required=True),
 
196
        'consumption_type': fields.selection([('fmc', 'FMC -- Forecasted Monthly Consumption'), 
 
197
                                              ('amc', 'AMC -- Average Monthly Consumption'), 
 
198
                                              ('rac', 'RAC -- Real Average Consumption')], string='Consumption', required=True),
 
199
        'line_ids': fields.one2many('product.likely.expire.report.line', 'report_id', string='Lines', readonly=True),
 
200
        'consumption_from': fields.date(string='From'),
 
201
        'consumption_to': fields.date(string='To'),
 
202
        'only_non_zero': fields.boolean(string='Only products with total expired > 0'),
 
203
    }
 
204
    
 
205
    _defaults = {
 
206
        'date_from': lambda *a: time.strftime('%Y-%m-%d'),
 
207
        'consumption_to': lambda *a: time.strftime('%Y-%m-%d'),
 
208
        'consumption_type': lambda *a: 'fmc',
 
209
        'msf_instance': lambda *a: 'MSF Instance',
 
210
    }
 
211
 
 
212
    def period_change(self, cr, uid, ids, consumption_from, consumption_to, consumption_type, context=None):
 
213
        '''
 
214
        Get the first or last day of month
 
215
        '''
 
216
        res = {}
 
217
 
 
218
        if consumption_type == 'amc':
 
219
            if consumption_from:
 
220
                res.update({'consumption_from': (DateFrom(consumption_from) + RelativeDateTime(day=1)).strftime('%Y-%m-%d')})
 
221
            if consumption_to:
 
222
                res.update({'consumption_to': (DateFrom(consumption_to) + RelativeDateTime(months=1, day=1, days=-1)).strftime('%Y-%m-%d')})
 
223
 
 
224
        return {'value': res}
 
225
            
 
226
    
 
227
    def _get_average_consumption(self, cr, uid, product_id, consumption_type, date_from, date_to, context=None):
 
228
        '''
 
229
        Return the average consumption for all locations
 
230
        '''
 
231
        if not context:
 
232
            context = {}
 
233
        
 
234
        product_obj = self.pool.get('product.product')
 
235
        res = 0.00
 
236
 
 
237
        if context.get('manual_consumption'):
 
238
            return context.get('manual_consumption')
 
239
        
 
240
        new_context = context.copy()
 
241
        new_context.update({'from_date': date_from,
 
242
                            'to_date': date_to,
 
243
                            'average': True})
 
244
        
 
245
        if consumption_type == 'fmc':
 
246
            res = product_obj.browse(cr, uid, product_id, context=new_context).reviewed_consumption
 
247
        elif consumption_type == 'amc':
 
248
            res = product_obj.compute_amc(cr, uid, product_id, context=new_context)
 
249
        else:
 
250
            res = product_obj.browse(cr, uid, product_id, context=new_context).monthly_consumption
 
251
        
 
252
        return res
 
253
            
 
254
    def process_lines(self, cr, uid, ids, context=None):
 
255
        '''
 
256
        Creates all moves with expiry quantities for all
 
257
        lot life date
 
258
        '''
 
259
        if not context:
 
260
            context = {}
 
261
            
 
262
        if isinstance(ids, (int, long)):
 
263
            ids = [ids]
 
264
            
 
265
        move_obj = self.pool.get('stock.move')
 
266
        lot_obj = self.pool.get('stock.production.lot')
 
267
        loc_obj = self.pool.get('stock.location')
 
268
        product_obj = self.pool.get('product.product')
 
269
        line_obj = self.pool.get('product.likely.expire.report.line')
 
270
        item_obj = self.pool.get('product.likely.expire.report.item')
 
271
        item_line_obj = self.pool.get('product.likely.expire.report.item.line')
 
272
        
 
273
        view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'consumption_calculation', 'product_likely_expire_report_form_processed')[1]
 
274
        report = self.browse(cr, uid, ids[0], context=context)
 
275
        
 
276
        if report.date_to <= report.date_from:
 
277
            raise osv.except_osv(_('Error'), _('You cannot have \'To date\' older than \'From date\''))
 
278
        
 
279
        if report.consumption_type in ('amc', 'rac') and report.consumption_from > report.consumption_to:
 
280
            raise osv.except_osv(_('Error'), _('You cannot have \'To date\' older than \'From date\''))
 
281
            
 
282
        if report.consumption_type in ('amc', 'rac'):
 
283
            context.update({'from': report.consumption_from, 'to': report.consumption_to})
 
284
        else:
 
285
            context.update({'from': report.date_from, 'to': report.date_to})
 
286
        
 
287
        location_ids = []
 
288
        not_loc_ids = []
 
289
        only_product_ids = []
 
290
 
 
291
        if context.get('only_product_ids'):
 
292
            only_product_ids = context.get('only_product_ids')
 
293
 
 
294
        if report.input_output_ok:
 
295
            wh_ids = self.pool.get('stock.warehouse').search(cr, uid, [], context=context)
 
296
            for wh in self.pool.get('stock.warehouse').browse(cr, uid, wh_ids, context=context):
 
297
                not_loc_ids.extend(loc_obj.search(cr, uid, [('location_id', 'child_of', wh.lot_input_id.id)], context=context))
 
298
                not_loc_ids.extend(loc_obj.search(cr, uid, [('location_id', 'child_of', wh.lot_output_id.id)], context=context))
 
299
        
 
300
        if report.location_id:
 
301
            # Get all locations
 
302
            location_ids = loc_obj.search(cr, uid, [('location_id', 'child_of', report.location_id.id), ('quarantine_location', '=', False), ('id', 'not in', not_loc_ids)], order='location_id', context=context)
 
303
        else:
 
304
            # Get all locations
 
305
            wh_location_ids = loc_obj.search(cr, uid, [('usage', '=', 'internal'), ('quarantine_location', '=', False), ('id', 'not in', not_loc_ids)], order='location_id', context=context)
 
306
 
 
307
            move_ids = move_obj.search(cr, uid, [('prodlot_id', '!=', False)], context=context)
 
308
            for move in move_obj.browse(cr, uid, move_ids, context=context):
 
309
                if move.location_id.id not in location_ids:
 
310
                    if move.location_id.usage == 'internal' and not move.location_id.quarantine_location and move.location_id.id in wh_location_ids:
 
311
                        location_ids.append(move.location_id.id)
 
312
                if move.location_dest_id.id not in location_ids and not move.location_dest_id.quarantine_location and move.location_dest_id.id in wh_location_ids:
 
313
                    if move.location_dest_id.usage == 'internal':
 
314
                        location_ids.append(move.location_dest_id.id)
 
315
            
 
316
        context.update({'location_id': location_ids, 'location': location_ids})
 
317
 
 
318
        lot_domain = [('stock_available', '>', 0.00)]
 
319
        if only_product_ids:
 
320
            lot_domain.append(('product_id', 'in', only_product_ids))
 
321
        
 
322
        lot_ids = lot_obj.search(cr, uid, lot_domain, order='product_id, life_date', context=context)
 
323
        
 
324
        from_date = DateFrom(report.date_from)
 
325
        to_date = DateFrom(report.date_to) + RelativeDateTime(day=1, months=1, days=-1)
 
326
        
 
327
        # Set all months between from_date and to_date
 
328
        dates = []
 
329
        while (from_date < to_date):
 
330
            dates.append(from_date)
 
331
            from_date = from_date + RelativeDateTime(months=1, day=1)
 
332
            
 
333
        # Create a report line for each product
 
334
        products = {}
 
335
        for lot in lot_obj.browse(cr, uid, lot_ids, context=context):
 
336
            if lot.product_id and lot.product_id.id not in products:
 
337
                products.update({lot.product_id.id: {}})
 
338
                consumption = self._get_average_consumption(cr, uid, lot.product_id.id, 
 
339
                                                                     report.consumption_type,
 
340
                                                                     context.get('from', report.date_from),
 
341
                                                                     context.get('to', report.date_to),
 
342
                                                                     context=context)
 
343
                
 
344
                products[lot.product_id.id].update({'line_id': line_obj.create(cr, uid, {'report_id': report.id,
 
345
                                                                                         'product_id': lot.product_id.id,
 
346
                                                                                         'in_stock': lot.product_id.qty_available,
 
347
                                                                                         'total_expired': 0.00,
 
348
                                                                                         'consumption': consumption,}, context=context)})
 
349
                
 
350
                # Create an item for each date
 
351
                seq = 0
 
352
                total_cons = 0.00
 
353
                already_cons = 0.00
 
354
                rest = 0.00
 
355
                total_expired = 0.00
 
356
                start_month_flag = True
 
357
                last_expiry_date = False
 
358
                for month in dates:
 
359
                    # Remove one day to include the expiry date as possible consumable day
 
360
                    if not last_expiry_date: last_expiry_date = month - RelativeDateTime(days=1)
 
361
                    
 
362
                    item_id = item_obj.create(cr, uid, {'name': month.strftime('%m/%y'), 
 
363
                                                        'line_id': products[lot.product_id.id]['line_id']}, context=context)
 
364
                    available_qty = 0.00
 
365
                    expired_qty = 0.00
 
366
                    seq += 1
 
367
                    
 
368
                    # Create a line for each lot which expired in this month
 
369
                    domain = [('product_id', '=', lot.product_id.id),
 
370
                             ('stock_available', '>', 0.00),
 
371
                             ('life_date', '<', (month + RelativeDateTime(months=1, day=1)).strftime('%Y-%m-%d'))]
 
372
 
 
373
                    if not start_month_flag:
 
374
                        domain.append(('life_date', '>=', month.strftime('%Y-%m-%d')))
 
375
                        item_obj.write(cr, uid, [item_id], {'period_start': (month + RelativeDateTime(day=1)).strftime('%Y-%m-%d')}, context=context)
 
376
                    else:
 
377
                        item_obj.write(cr, uid, [item_id], {'period_start': report.date_from}, context=context)
 
378
                        # Uncomment the first line if you want products already expired in the first month
 
379
                        #domain.append(('life_date', '>=', month.strftime('%Y-%m-01')))
 
380
                        # Comment line if you want all products already expired
 
381
                        #if not context.get('only_product_ids'):
 
382
                        #    domain.append(('life_date', '>=', month.strftime('%Y-%m-%d')))
 
383
                        
 
384
 
 
385
                    # Remove the token after the first month processing
 
386
                    start_month_flag = False
 
387
 
 
388
                    product_lot_ids = lot_obj.search(cr, uid, domain, order='life_date', context=context)
 
389
                    
 
390
                    # Create an item line for each lot and each location
 
391
                    for product_lot in lot_obj.browse(cr, uid, product_lot_ids, context=context):
 
392
                        lot_days = Age(DateFrom(product_lot.life_date), last_expiry_date)
 
393
                        lot_coeff = (lot_days.years*365.0 + lot_days.months*30.0 + lot_days.days)/30.0
 
394
                        if lot_coeff >= 0.00: last_expiry_date = DateFrom(product_lot.life_date)
 
395
                        if lot_coeff < 0.00: lot_coeff = 0.00
 
396
                        lot_cons = self.pool.get('product.uom')._compute_qty(cr, uid, lot.product_id.uom_id.id, round(lot_coeff*consumption,2), lot.product_id.uom_id.id) + rest
 
397
                        
 
398
                        if lot_cons > 0.00:
 
399
                            if lot_cons >= product_lot.stock_available:
 
400
                                already_cons += product_lot.stock_available
 
401
                                rest = lot_cons - product_lot.stock_available
 
402
                                l_expired_qty = 0.00
 
403
                            else :
 
404
                                l_expired_qty = product_lot.stock_available - lot_cons
 
405
                                already_cons += lot_cons
 
406
                                rest = 0.00
 
407
                        else:
 
408
                            l_expired_qty = product_lot.stock_available
 
409
                        expired_qty += l_expired_qty
 
410
                        
 
411
                        lot_context = context.copy()
 
412
                        lot_context.update({'prodlot_id': product_lot.id})
 
413
                        product = product_obj.browse(cr, uid, lot.product_id.id, context=lot_context)
 
414
                        lot_expired_qty = l_expired_qty
 
415
                        for location in location_ids:
 
416
                            new_lot_context = lot_context.copy()
 
417
                            new_lot_context.update({'location': location, 'compute_child': False})
 
418
                            product2 = product_obj.browse(cr, uid, lot.product_id.id, context=new_lot_context)
 
419
                            if product2.qty_available > 0.00:
 
420
                                # Create the item line
 
421
                                if product2.qty_available <= lot_expired_qty:
 
422
                                    new_lot_expired = product2.qty_available
 
423
                                    lot_expired_qty -= product2.qty_available
 
424
                                else:
 
425
                                    new_lot_expired = lot_expired_qty
 
426
                                    lot_expired_qty = 0.00
 
427
                                item_line_obj.create(cr, uid, {'item_id': item_id,
 
428
                                                               'lot_id': product_lot.id,
 
429
                                                               'location_id': location,
 
430
                                                               'available_qty': product2.qty_available,
 
431
                                                               'expired_qty': new_lot_expired}, context=context)
 
432
                            
 
433
                        available_qty += product.qty_available
 
434
                            
 
435
                    item_obj.write(cr, uid, [item_id], {'available_qty': available_qty,
 
436
                                                        'expired_qty': expired_qty}, context=context)
 
437
                    total_expired += expired_qty
 
438
                    
 
439
                if report.only_non_zero and total_expired <= 0.00:
 
440
                    line_obj.unlink(cr, uid, [products[lot.product_id.id]['line_id']], context=context)
 
441
                else:
 
442
                    line_obj.write(cr, uid, [products[lot.product_id.id]['line_id']], {'total_expired': total_expired}, context=context)
 
443
            
 
444
        new_date = []        
 
445
        for date in dates:
 
446
            new_date.append(date.strftime('%m/%y'))
 
447
            
 
448
        context.update({'dates': new_date})
 
449
                    
 
450
        return {'type': 'ir.actions.act_window',
 
451
                'res_model': 'product.likely.expire.report',
 
452
                'res_id': report.id,
 
453
                'view_id': [view_id],
 
454
                'view_type': 'form',
 
455
                'view_mode': 'form',
 
456
                'context': context,
 
457
                'target': 'dummy'}
 
458
        
 
459
        
 
460
    def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
 
461
        if not context:
 
462
            context = {}
 
463
            
 
464
        res = super(product_likely_expire_report, self).fields_view_get(cr, uid, view_id, view_type, context=context)
 
465
        
 
466
        line_view = """<tree string="Expired products">
 
467
                <field name="product_id"/>
 
468
                <field name="consumption"/>
 
469
                """
 
470
                
 
471
        dates = context.get('dates', [])
 
472
        for month in dates:
 
473
            line_view += '<field name="%s" />' % month
 
474
            line_view += '<button name="go_to_item_%s" type="object" string="Go to item" icon="gtk-info" context="{item_date: %s}" />' % (month, month)
 
475
            
 
476
        line_view += """<field name="in_stock"/>
 
477
                        <field name="total_expired" />
 
478
                        </tree>"""
 
479
                        
 
480
        if res['fields'].get('line_ids', {}).get('views', {}).get('tree', {}).get('arch', {}):
 
481
            res['fields']['line_ids']['views']['tree']['arch'] = line_view
 
482
             
 
483
        return res
 
484
                
 
485
product_likely_expire_report()
 
486
 
 
487
 
 
488
class product_likely_expire_report_line(osv.osv_memory):
 
489
    _name = 'product.likely.expire.report.line'
 
490
    
 
491
    _columns = {
 
492
            'report_id': fields.many2one('product.likely.expire.report', string='Report', required=True, ondelete='cascade'),
 
493
            'product_id': fields.many2one('product.product', string='Product', required=True),
 
494
            'consumption': fields.float(digits=(16,2), string='Monthly Consumption', required=True),
 
495
            'in_stock': fields.float(digits=(16,2), string='In stock'),
 
496
            'total_expired': fields.float(digits=(16,2), string='Total expired'),
 
497
    }
 
498
    
 
499
    def __getattr__(self, name, *args, **kwargs):
 
500
        if name[:11] == 'go_to_item_':
 
501
            date = name[11:]
 
502
            self.date = date
 
503
            return self.go_to_item
 
504
        else:
 
505
            return super(product_likely_expire_report_line, self).__getattr__(name, *args, **kwargs)
 
506
    
 
507
    def fields_get(self, cr, uid, fields=None, context=None):
 
508
        if not context:
 
509
            context = {}
 
510
            
 
511
        res = super(product_likely_expire_report_line, self).fields_get(cr, uid, fields, context)
 
512
        dates = context.get('dates', [])
 
513
        
 
514
        for month in dates:
 
515
            res.update({month: {'selectable': True,
 
516
                               'type': 'many2one',
 
517
                               'relation': 'product.likely.expire.report.item',
 
518
                               'string': month}})
 
519
            
 
520
        return res
 
521
    
 
522
    def go_to_item(self, cr, uid, ids, context=None):
 
523
        if not context:
 
524
            context = {}
 
525
            
 
526
        if not context.get('item_date', self.date):
 
527
            raise osv.except_osv(_('Error'), _('You haven\'t choose an item to open'))
 
528
        
 
529
        item_date = context.get('item_date', self.date)
 
530
        item_ids = self.pool.get('product.likely.expire.report.item').search(cr, uid, [('name', '=', item_date), ('line_id', '=', ids[0])], context=context)
 
531
        if not item_ids:
 
532
            raise osv.except_osv(_('Error'), _('You haven\'t choose an item to open'))
 
533
        
 
534
        return {'type': 'ir.actions.act_window',
 
535
                'res_model': 'product.likely.expire.report.item',
 
536
                'res_id': item_ids[0],
 
537
                'view_type': 'form',
 
538
                'view_mode': 'form',
 
539
                'context': context,
 
540
                'target': 'new'}
 
541
        
 
542
            
 
543
    def read(self, cr, uid, ids, vals, context=None, load='_classic_read'):
 
544
        '''
 
545
        Set values for all dates
 
546
        '''
 
547
        
 
548
        res = super(product_likely_expire_report_line, self).read(cr, uid, ids, vals, context=context, load=load)
 
549
        
 
550
        item_obj = self.pool.get('product.likely.expire.report.item')
 
551
        for r in res:
 
552
            exp_ids = item_obj.search(cr, uid, [('line_id', '=', r['id'])], context=context)
 
553
            for exp in item_obj.browse(cr, uid, exp_ids, context=context):
 
554
                r.update({exp.name: ''})
 
555
                if exp.expired_qty > 0.00:
 
556
                    name = '%s (%s)' % (exp.available_qty, exp.expired_qty)
 
557
                else:
 
558
                    # Be careful to the undividable spaces
 
559
                    name = '      %s' % (exp.available_qty)
 
560
 
 
561
                r.update({exp.name: name})
 
562
                
 
563
        return res
 
564
 
 
565
        
 
566
product_likely_expire_report_line()
 
567
 
 
568
 
 
569
class product_likely_expire_report_item(osv.osv_memory):
 
570
    _name = 'product.likely.expire.report.item'
 
571
    
 
572
    _columns = {
 
573
            'line_id': fields.many2one('product.likely.expire.report.line', string='Line', ondelete='cascade'),
 
574
            'name': fields.char(size=64, string='Month'),
 
575
            'available_qty': fields.float(digits=(16,2), string='Available Qty.'),
 
576
            'expired_qty': fields.float(digits=(16,2), string='Expired Qty.'),
 
577
            'period_start': fields.date(string='Period start', readonly=True),
 
578
            'line_ids': fields.one2many('product.likely.expire.report.item.line', 'item_id', string='Batchs'),
 
579
    }
 
580
    
 
581
product_likely_expire_report_item()
 
582
 
 
583
 
 
584
class product_likely_expire_report_item_line(osv.osv_memory):
 
585
    _name = 'product.likely.expire.report.item.line'
 
586
    _order = 'expired_date, location_id'
 
587
    
 
588
    _columns = {
 
589
            'item_id': fields.many2one('product.likely.expire.report.item', strig='Item', ondelete='cascade'),
 
590
            'lot_id': fields.many2one('stock.production.lot', string='Batch number'),
 
591
            'location_id': fields.many2one('stock.location', string='Location'),
 
592
            'available_qty': fields.float(digits=(16,2), string='Available Qty.'),
 
593
            'expired_qty': fields.float(digits=(16,2), string='Expired Qty.'),
 
594
            'expired_date': fields.related('lot_id', 'life_date', type='date', string='Expiry date'),
 
595
    }
 
596
    
 
597
product_likely_expire_report_item_line()
 
598
     
 
599
 
 
600
class product_product(osv.osv):
 
601
    _name = 'product.product'
 
602
    _inherit = 'product.product'
 
603
    
 
604
    def get_expiry_qty(self, cr, uid, product_id, location_id, monthly_consumption, d_values=None, context=None):
 
605
        '''
 
606
        Get the expired quantity of product
 
607
        '''
 
608
        if context is None:
 
609
            context = {}
 
610
 
 
611
        if d_values is None:
 
612
            d_values = {}
 
613
        
 
614
        monthly_consumption = 'rac'
 
615
        
 
616
        # Get the monthly consumption
 
617
        if d_values.get('reviewed_consumption', False):
 
618
            monthly_consumption = 'fmc'
 
619
        elif d_values.get('past_consumption', False):
 
620
            monthly_consumption = 'amc'
 
621
        else:
 
622
            monthly_consumption = d_values.get('manual_consumption', 0.00)
 
623
            context.update({'manual_consumption': monthly_consumption})
 
624
 
 
625
        product = self.browse(cr, uid, product_id, context=context)
 
626
        # Get the delivery lead time of the product if the leadtime is not defined in rule and no supplier found in product form
 
627
        delivery_leadtime = product.procure_delay and round(int(product.procure_delay)/30.0, 2) or 1
 
628
        # Get the leadtime of the rule if defined
 
629
        if 'leadtime' in d_values and d_values.get('leadtime', 0.00) != 0.00:
 
630
            delivery_leadtime = d_values.get('leadtime')
 
631
        elif product.seller_ids:
 
632
            # Get the supplier lead time if supplier is defined
 
633
            # The seller delay is defined in days, so divide it by 30.0 to have a LT in months
 
634
            delivery_leadtime = product.seller_delay and round(int(product.seller_delay)/30.0, 2) or 1
 
635
            
 
636
        delta = (delivery_leadtime + d_values.get('coverage', 0.00))*30.0
 
637
 
 
638
        report_data = {'date_from': today().strftime('%Y-%m-%d'),
 
639
                       'date_to': (today() + RelativeDateTime(days=delta)).strftime('%Y-%m-%d'),
 
640
                       'consumption_type': monthly_consumption,
 
641
                       'consumption_from': d_values.get('consumption_period_from'),
 
642
                       'consumption_to': d_values.get('consumption_period_to'),
 
643
                       'location_id': location_id}
 
644
 
 
645
        report_obj = self.pool.get('product.likely.expire.report')
 
646
        line_obj = self.pool.get('product.likely.expire.report.line')
 
647
 
 
648
        exp_context = context.copy()
 
649
        exp_context.update({'only_product_ids': [product_id]})
 
650
        report_id = report_obj.create(cr, uid, report_data, context=exp_context)
 
651
        # Process report
 
652
        report_obj.process_lines(cr, uid, report_id, context=exp_context)
 
653
        
 
654
        lines = line_obj.search(cr, uid, [('report_id', '=', report_id)], context=context)
 
655
        for line in line_obj.browse(cr, uid, lines, context=context):
 
656
            if line.product_id.id == product_id:
 
657
                return line.total_expired
 
658
        
 
659
            
 
660
#        location_ids = stock_obj.search(cr, uid, [('location_id', 'child_of', location_id)])
 
661
#            
 
662
#        move_ids = move_obj.search(cr, uid, ['|', ('location_id', 'in', location_ids), ('location_dest_id', 'in', location_ids), 
 
663
#                                             ('product_id', '=', product_id), ('prodlot_id', '!=', False)], context=context)
 
664
#        
 
665
#        lots = []
 
666
#        for move in move_obj.browse(cr, uid, move_ids, context=context):
 
667
#            if not move.prodlot_id.id in lots:
 
668
#                lots.append(move.prodlot_id.id)
 
669
#        
 
670
#        # Get all lots for the product product_id
 
671
#        lot_ids = lot_obj.search(cr, uid, [('product_id', '=', product_id), ('stock_available', '>', 0.00), ('id', 'in', lots)], \
 
672
#                                order='life_date', context=context)
 
673
#
 
674
#
 
675
#        
 
676
#        # Sum of months before expiry
 
677
#        sum_ni = 0.00      
 
678
#        expired_qty = 0.00
 
679
#        last_date = now()
 
680
#        last_qty = False
 
681
#        
 
682
#        for lot in lot_obj.browse(cr, uid, lot_ids, context=context):
 
683
#            life_date = strptime(lot.life_date, '%Y-%m-%d')
 
684
#            rel_time = RelativeDateDiff(life_date, now())
 
685
#            ni = round((rel_time.months*30 + rel_time.days)/30.0, 2)
 
686
#            if last_qty == False:
 
687
#                last_qty = uom_obj._compute_qty(cr, uid, lot.product_id.uom_id.id, (ni-sum_ni)*monthly_consumption, lot.product_id.uom_id.id)
 
688
#            if last_date > life_date:
 
689
#                expired_qty = lot.stock_available                
 
690
#            elif ni - sum_ni > 0.00:
 
691
#                expired_qty += last_qty
 
692
#                last_qty = uom_obj._compute_qty(cr, uid, lot.product_id.uom_id.id, (ni-sum_ni)*monthly_consumption, lot.product_id.uom_id.id)
 
693
#                sum_ni += ni
 
694
#            else:
 
695
#                break
 
696
#        return expired_qty
 
697
    
 
698
product_product()
 
699
 
 
700
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: