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

« back to all changes in this revision

Viewing changes to specific_rules/specific_rules.py

  • Committer: chloups208
  • Date: 2011-08-23 06:36:05 UTC
  • mto: This revision was merged to the branch mainline in revision 253.
  • Revision ID: chloups208@chloups208-laptop-20110823063605-g92qqcpmnnb74iok
[UF-368]first commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: utf-8 -*-
2
 
##############################################################################
3
 
#
4
 
#    Copyright (C) 2011 MSF, TeMPO Consulting
5
 
#
6
 
#    This program is free software: you can redistribute it and/or modify
7
 
#    it under the terms of the GNU Affero General Public License as
8
 
#    published by the Free Software Foundation, either version 3 of the
9
 
#    License, or (at your option) any later version.
10
 
#
11
 
#    This program is distributed in the hope that it will be useful,
12
 
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 
#    GNU Affero General Public License for more details.
15
 
#
16
 
#    You should have received a copy of the GNU Affero General Public License
17
 
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
 
#
19
 
##############################################################################
20
 
 
21
 
import tools
22
 
from datetime import datetime, timedelta, date
23
 
from dateutil.relativedelta import relativedelta, relativedelta
24
 
from osv import osv, fields
25
 
from osv.orm import browse_record, browse_null
26
 
from tools.translate import _
27
 
 
28
 
import decimal_precision as dp
29
 
import netsvc
30
 
import pooler
31
 
import time
32
 
 
33
 
# warning messages
34
 
SHORT_SHELF_LIFE_MESS = 'Product with Short Shelf Life, check the accuracy of the order quantity, frequency and mode of transport.'
35
 
 
36
 
 
37
 
class sale_order_line(osv.osv):
38
 
    '''
39
 
    override to add message at sale order creation and update
40
 
    '''
41
 
    _inherit = 'sale.order.line'
42
 
    
43
 
    def _kc_dg(self, cr, uid, ids, name, arg, context=None):
44
 
        '''
45
 
        return 'KC' if cold chain or 'DG' if dangerous goods
46
 
        '''
47
 
        result = {}
48
 
        for id in ids:
49
 
            result[id] = ''
50
 
            
51
 
        for sol in self.browse(cr, uid, ids, context=context):
52
 
            if sol.product_id:
53
 
                if sol.product_id.heat_sensitive_item:
54
 
                    result[sol.id] = 'KC'
55
 
                elif sol.product_id.dangerous_goods:
56
 
                    result[sol.id] = 'DG'
57
 
        
58
 
        return result
59
 
        
60
 
    _columns = {'kc_dg': fields.function(_kc_dg, method=True, string='KC/DG', type='char'),}
61
 
    
62
 
    def product_id_change(self, cr, uid, ids, pricelist, product, qty=0,
63
 
            uom=False, qty_uos=0, uos=False, name='', partner_id=False,
64
 
            lang=False, update_tax=True, date_order=False, packaging=False, fiscal_position=False, flag=False):
65
 
        '''
66
 
        if the product is short shelf life we display a warning
67
 
        '''
68
 
        # call to super
69
 
        result = super(sale_order_line, self).product_id_change(cr, uid, ids, pricelist, product, qty,
70
 
            uom, qty_uos, uos, name, partner_id, lang, update_tax, date_order, packaging, fiscal_position, flag)
71
 
        
72
 
        # if the product is short shelf life, display a warning
73
 
        if product:
74
 
            prod_obj = self.pool.get('product.product')
75
 
            if prod_obj.browse(cr, uid, product).short_shelf_life:
76
 
                warning = {
77
 
                            'title': 'Short Shelf Life product',
78
 
                            'message': _(SHORT_SHELF_LIFE_MESS)
79
 
                            }
80
 
                result.update(warning=warning)
81
 
            
82
 
        return result
83
 
    
84
 
sale_order_line()
85
 
 
86
 
 
87
 
class sale_order(osv.osv):
88
 
    '''
89
 
    add message when so is written, i.e when we add new so lines
90
 
    '''
91
 
    _inherit = 'sale.order'
92
 
    
93
 
    def write(self, cr, uid, ids, vals, context=None):
94
 
        '''
95
 
        display message if contains short shelf life
96
 
        '''
97
 
        if isinstance(ids, (int, long)):
98
 
            ids = [ids]
99
 
            
100
 
        for obj in self.browse(cr, uid, ids, context=context):
101
 
            for line in obj.order_line:
102
 
                # log the message
103
 
                if line.product_id.short_shelf_life:
104
 
                    # log the message
105
 
                    self.log(cr, uid, obj.id, _(SHORT_SHELF_LIFE_MESS))
106
 
        
107
 
        return super(sale_order, self).write(cr, uid, ids, vals, context=context)
108
 
    
109
 
sale_order()
110
 
 
111
 
 
112
 
class purchase_order_line(osv.osv):
113
 
    '''
114
 
    override to add message at purchase order creation and update
115
 
    '''
116
 
    _inherit = 'purchase.order.line'
117
 
    
118
 
    def _kc_dg(self, cr, uid, ids, name, arg, context=None):
119
 
        '''
120
 
        return 'KC' if cold chain or 'DG' if dangerous goods
121
 
        '''
122
 
        result = {}
123
 
        for id in ids:
124
 
            result[id] = ''
125
 
            
126
 
        for pol in self.browse(cr, uid, ids, context=context):
127
 
            if pol.product_id:
128
 
                if pol.product_id.heat_sensitive_item:
129
 
                    result[pol.id] = 'KC'
130
 
                elif pol.product_id.dangerous_goods:
131
 
                    result[pol.id] = 'DG'
132
 
        
133
 
        return result
134
 
        
135
 
    _columns = {'kc_dg': fields.function(_kc_dg, method=True, string='KC/DG', type='char'),}
136
 
    
137
 
    def product_id_change(self, cr, uid, ids, pricelist, product, qty, uom,
138
 
            partner_id, date_order=False, fiscal_position=False, date_planned=False,
139
 
            name=False, price_unit=False, notes=False):
140
 
        '''
141
 
        if the product is short shelf life we display a warning
142
 
        '''
143
 
        # call to super
144
 
        result = super(purchase_order_line, self).product_id_change(cr, uid, ids, pricelist, product, qty, uom,
145
 
            partner_id, date_order, fiscal_position, date_planned,
146
 
            name, price_unit, notes)
147
 
        
148
 
        # if the product is short shelf life, display a warning
149
 
        if product:
150
 
            prod_obj = self.pool.get('product.product')
151
 
            if prod_obj.browse(cr, uid, product).short_shelf_life:
152
 
                warning = {
153
 
                            'title': 'Short Shelf Life product',
154
 
                            'message': _(SHORT_SHELF_LIFE_MESS)
155
 
                            }
156
 
                result.update(warning=warning)
157
 
            
158
 
        return result
159
 
    
160
 
purchase_order_line()
161
 
 
162
 
 
163
 
class purchase_order(osv.osv):
164
 
    '''
165
 
    add message when po is written, i.e when we add new po lines
166
 
    
167
 
    no need to modify the wkf_confirm_order as the wrtie method is called during the workflow
168
 
    '''
169
 
    _inherit = 'purchase.order'
170
 
    
171
 
    def write(self, cr, uid, ids, vals, context=None):
172
 
        '''
173
 
        display message if contains short shelf life
174
 
        '''
175
 
        if isinstance(ids, (int, long)):
176
 
            ids = [ids]
177
 
            
178
 
        for obj in self.browse(cr, uid, ids, context=context):
179
 
            for line in obj.order_line:
180
 
                # log the message
181
 
                if line.product_id.short_shelf_life:
182
 
                    # log the message
183
 
                    self.log(cr, uid, obj.id, _(SHORT_SHELF_LIFE_MESS))
184
 
        
185
 
        return super(purchase_order, self).write(cr, uid, ids, vals, context=context)
186
 
    
187
 
purchase_order()
188
 
 
189
 
 
190
 
class stock_warehouse_orderpoint(osv.osv):
191
 
    '''
192
 
    add message
193
 
    '''
194
 
    _inherit = 'stock.warehouse.orderpoint'
195
 
    
196
 
    def create(self, cr, uid, vals, context=None):
197
 
        '''
198
 
        add message
199
 
        '''
200
 
        new_id = super(stock_warehouse_orderpoint, self).create(cr, uid, vals, context=context)
201
 
        
202
 
        product_obj = self.pool.get('product.product')
203
 
        product_id = vals.get('product_id', False)
204
 
        if product_id:
205
 
            if product_obj.browse(cr, uid, product_id, context=context).short_shelf_life:
206
 
                self.log(cr, uid, new_id, _(SHORT_SHELF_LIFE_MESS))
207
 
                
208
 
        return new_id
209
 
    
210
 
    def write(self, cr, uid, ids, vals, context=None):
211
 
        '''
212
 
        add message
213
 
        '''
214
 
        result = super(stock_warehouse_orderpoint, self).write(cr, uid, ids, vals, context=context)
215
 
        
216
 
        if isinstance(ids, (int, long)):
217
 
            ids = [ids]
218
 
        
219
 
        product_obj = self.pool.get('product.product')
220
 
        product_id = vals.get('product_id', False)
221
 
        if product_id:
222
 
            if product_obj.browse(cr, uid, product_id, context=context).short_shelf_life:
223
 
                for obj in self.browse(cr, uid, ids, context=context):
224
 
                    self.log(cr, uid, obj.id, _(SHORT_SHELF_LIFE_MESS))
225
 
        
226
 
        return result
227
 
        
228
 
stock_warehouse_orderpoint()
229
 
 
230
 
 
231
 
class stock_warehouse_automatic_supply(osv.osv):
232
 
    '''
233
 
    add message
234
 
    '''
235
 
    _inherit = 'stock.warehouse.automatic.supply'
236
 
    
237
 
    def create(self, cr, uid, vals, context=None):
238
 
        '''
239
 
        add message
240
 
        '''
241
 
        new_id = super(stock_warehouse_automatic_supply, self).create(cr, uid, vals, context=context)
242
 
        
243
 
        product_obj = self.pool.get('product.product')
244
 
        product_id = vals.get('product_id', False)
245
 
        if product_id:
246
 
            if product_obj.browse(cr, uid, product_id, context=context).short_shelf_life:
247
 
                self.log(cr, uid, new_id, _(SHORT_SHELF_LIFE_MESS))
248
 
                
249
 
        return new_id
250
 
    
251
 
    def write(self, cr, uid, ids, vals, context=None):
252
 
        '''
253
 
        add message
254
 
        '''
255
 
        result = super(stock_warehouse_automatic_supply, self).write(cr, uid, ids, vals, context=context)
256
 
        
257
 
        if isinstance(ids, (int, long)):
258
 
            ids = [ids]
259
 
        
260
 
        product_obj = self.pool.get('product.product')
261
 
        product_id = vals.get('product_id', False)
262
 
        if product_id:
263
 
            if product_obj.browse(cr, uid, product_id, context=context).short_shelf_life:
264
 
                for obj in self.browse(cr, uid, ids, context=context):
265
 
                    self.log(cr, uid, obj.id, _(SHORT_SHELF_LIFE_MESS))
266
 
        
267
 
        return result
268
 
    
269
 
stock_warehouse_automatic_supply()
270
 
 
271
 
 
272
 
class stock_warehouse_order_cycle(osv.osv):
273
 
    '''
274
 
    add message
275
 
    '''
276
 
    _inherit = 'stock.warehouse.order.cycle'
277
 
    
278
 
    def create(self, cr, uid, vals, context=None):
279
 
        '''
280
 
        add message
281
 
        '''
282
 
        new_id = super(stock_warehouse_order_cycle, self).create(cr, uid, vals, context=context)
283
 
        
284
 
        product_obj = self.pool.get('product.product')
285
 
        product_id = vals.get('product_id', False)
286
 
        if product_id:
287
 
            if product_obj.browse(cr, uid, product_id, context=context).short_shelf_life:
288
 
                self.log(cr, uid, new_id, _(SHORT_SHELF_LIFE_MESS))
289
 
                
290
 
        return new_id
291
 
    
292
 
    def write(self, cr, uid, ids, vals, context=None):
293
 
        '''
294
 
        add message
295
 
        '''
296
 
        if context is None:
297
 
            context = {}
298
 
            
299
 
        result = super(stock_warehouse_order_cycle, self).write(cr, uid, ids, vals, context=context)
300
 
        
301
 
        if isinstance(ids, (int, long)):
302
 
            ids = [ids]
303
 
        
304
 
        product_obj = self.pool.get('product.product')
305
 
        product_id = vals.get('product_id', False)
306
 
        if product_id:
307
 
            if product_obj.browse(cr, uid, product_id, context=context).short_shelf_life:
308
 
                for obj in self.browse(cr, uid, ids, context=context):
309
 
                    self.log(cr, uid, obj.id, _(SHORT_SHELF_LIFE_MESS))
310
 
        
311
 
        return result
312
 
    
313
 
stock_warehouse_order_cycle()
314
 
 
315
 
 
316
 
class stock_picking(osv.osv):
317
 
    '''
318
 
    modify hook function
319
 
    '''
320
 
    _inherit = 'stock.picking'
321
 
    
322
 
    def _do_partial_hook(self, cr, uid, ids, context, *args, **kwargs):
323
 
        '''
324
 
        hook to update defaults data
325
 
        '''
326
 
        # variable parameters
327
 
        move = kwargs.get('move')
328
 
        assert move, 'missing move'
329
 
        partial_datas = kwargs.get('partial_datas')
330
 
        assert partial_datas, 'missing partial_datas'
331
 
        
332
 
        # calling super method
333
 
        defaults = super(stock_picking, self)._do_partial_hook(cr, uid, ids, context, *args, **kwargs)
334
 
        assetId = partial_datas.get('move%s'%(move.id), False).get('asset_id')
335
 
        if assetId:
336
 
            defaults.update({'asset_id': assetId})
337
 
        
338
 
        return defaults
339
 
    
340
 
    _columns = {}
341
 
    
342
 
stock_picking()
343
 
 
344
 
 
345
 
class stock_move(osv.osv):
346
 
    '''
347
 
    add kc/dg
348
 
    '''
349
 
    _inherit = 'stock.move'
350
 
    
351
 
    def create(self, cr, uid, vals, context=None):
352
 
        '''
353
 
        complete info normally generated by javascript on_change function
354
 
        '''
355
 
        prod_obj = self.pool.get('product.product')
356
 
        if vals.get('product_id', False):
357
 
            # complete hidden flags - needed if not created from GUI
358
 
            product = prod_obj.browse(cr, uid, vals.get('product_id'), context=context)
359
 
            if product.batch_management:
360
 
                vals.update(hidden_batch_management_mandatory=True)
361
 
            elif product.perishable:
362
 
                vals.update(hidden_perishable_mandatory=True)
363
 
            else:
364
 
                vals.update(hidden_batch_management_mandatory=False,
365
 
                            hidden_perishable_mandatory=False,
366
 
                            )
367
 
        # call super
368
 
        result = super(stock_move, self).create(cr, uid, vals, context=context)
369
 
        return result
370
 
    
371
 
    def write(self, cr, uid, ids, vals, context=None):
372
 
        '''
373
 
        complete info normally generated by javascript on_change function
374
 
        '''
375
 
        prod_obj = self.pool.get('product.product')
376
 
        if vals.get('product_id', False):
377
 
            # complete hidden flags - needed if not created from GUI
378
 
            product = prod_obj.browse(cr, uid, vals.get('product_id'), context=context)
379
 
            if product.batch_management:
380
 
                vals.update(hidden_batch_management_mandatory=True)
381
 
            elif product.perishable:
382
 
                vals.update(hidden_perishable_mandatory=True)
383
 
            else:
384
 
                vals.update(hidden_batch_management_mandatory=False,
385
 
                            hidden_perishable_mandatory=False,
386
 
                            )
387
 
        # call super
388
 
        result = super(stock_move, self).write(cr, uid, ids, vals, context=context)
389
 
        return result
390
 
 
391
 
    def _kc_dg(self, cr, uid, ids, name, arg, context=None):
392
 
        '''
393
 
        return 'KC' if cold chain or 'DG' if dangerous goods
394
 
        '''
395
 
        result = {}
396
 
        for id in ids:
397
 
            result[id] = ''
398
 
            
399
 
        for move in self.browse(cr, uid, ids, context=context):
400
 
            if move.product_id:
401
 
                if move.product_id.heat_sensitive_item:
402
 
                    result[move.id] = 'KC'
403
 
                elif move.product_id.dangerous_goods:
404
 
                    result[move.id] = 'DG'
405
 
        
406
 
        return result
407
 
    
408
 
    def _check_batch_management(self, cr, uid, ids, context=None):
409
 
        """
410
 
        check for batch management
411
 
        @return: True or False
412
 
        """
413
 
        for move in self.browse(cr, uid, ids, context=context):
414
 
            if move.state == 'done':
415
 
                if move.product_id.batch_management:
416
 
                    if not move.prodlot_id:
417
 
                        return False
418
 
        return True
419
 
    
420
 
    def _check_perishable(self, cr, uid, ids, context=None):
421
 
        """
422
 
        check for perishable
423
 
        @return: True or False
424
 
        """
425
 
        for move in self.browse(cr, uid, ids, context=context):
426
 
            if move.state == 'done':
427
 
                if move.product_id.perishable:
428
 
                    if not move.prodlot_id:
429
 
                        return False
430
 
        return True
431
 
    
432
 
    def _check_prodlot_need(self, cr, uid, ids, context=None):
433
 
        """
434
 
        If the move has a prodlot but does not need one, return False.
435
 
        """
436
 
        for move in self.browse(cr, uid, ids, context=context):
437
 
            if move.prodlot_id:
438
 
                if not move.product_id.perishable and not move.product_id.batch_management:
439
 
                    return False
440
 
        return True
441
 
    
442
 
    def _check_prodlot_need_batch_management(self, cr, uid, ids, context=None):
443
 
        """
444
 
        If the product is batch management while the selected prodlot is 'internal'.
445
 
        """
446
 
        for move in self.browse(cr, uid, ids, context=context):
447
 
            if move.prodlot_id:
448
 
                if move.prodlot_id.type == 'internal' and move.product_id.batch_management:
449
 
                    return False
450
 
        return True
451
 
    
452
 
    def _check_prodlot_need_perishable(self, cr, uid, ids, context=None):
453
 
        """
454
 
        If the product is perishable ONLY while the selected prodlot is 'standard'.
455
 
        """
456
 
        for move in self.browse(cr, uid, ids, context=context):
457
 
            if move.prodlot_id:
458
 
                if move.prodlot_id.type == 'standard' and not move.product_id.batch_management and move.product_id.perishable:
459
 
                    return False
460
 
        return True
461
 
    
462
 
    def onchange_product_id(self, cr, uid, ids, prod_id=False, loc_id=False, loc_dest_id=False, address_id=False):
463
 
        '''
464
 
        the product changes, set the hidden flag if necessary
465
 
        '''
466
 
        result = super(stock_move, self).onchange_product_id(cr, uid, ids, prod_id, loc_id,
467
 
                                                             loc_dest_id, address_id)
468
 
        
469
 
        # product changes, prodlot is always cleared
470
 
        result.setdefault('value', {})['prodlot_id'] = False
471
 
        # reset the hidden flag
472
 
        result.setdefault('value', {})['hidden_batch_management_mandatory'] = False
473
 
        result.setdefault('value', {})['hidden_perishable_mandatory'] = False
474
 
        if prod_id:
475
 
            product = self.pool.get('product.product').browse(cr, uid, prod_id)
476
 
            if product.batch_management:
477
 
                result.setdefault('value', {})['hidden_batch_management_mandatory'] = True
478
 
                result['warning'] = {'title': _('Info'),
479
 
                                     'message': _('The selected product is Batch Management.')}
480
 
            
481
 
            elif product.perishable:
482
 
                result.setdefault('value', {})['hidden_perishable_mandatory'] = True
483
 
                result['warning'] = {'title': _('Info'),
484
 
                                     'message': _('The selected product is Perishable.')}
485
 
            
486
 
        return result
487
 
    
488
 
    def _get_checks_all(self, cr, uid, ids, name, arg, context=None):
489
 
        '''
490
 
        function for KC/SSL/DG/NP products
491
 
        '''
492
 
        result = {}
493
 
        for id in ids:
494
 
            result[id] = {}
495
 
            for f in name:
496
 
                result[id].update({f: False})
497
 
        
498
 
        for obj in self.browse(cr, uid, ids, context=context):
499
 
            # keep cool
500
 
            if obj.product_id.heat_sensitive_item:
501
 
                result[obj.id]['kc_check'] = True
502
 
            # ssl
503
 
            if obj.product_id.short_shelf_life:
504
 
                result[obj.id]['ssl_check'] = True
505
 
            # dangerous goods
506
 
            if obj.product_id.dangerous_goods:
507
 
                result[obj.id]['dg_check'] = True
508
 
            # narcotic
509
 
            if obj.product_id.narcotic:
510
 
                result[obj.id]['np_check'] = True
511
 
            
512
 
        return result
513
 
            
514
 
    _columns = {'kc_dg': fields.function(_kc_dg, method=True, string='KC/DG', type='char'),
515
 
                # if prodlot needs to be mandatory, add 'required': ['|', ('hidden_batch_management_mandatory','=',True), ('hidden_perishable_mandatory','=',True)] in attrs
516
 
                'hidden_batch_management_mandatory': fields.boolean(string='Hidden Flag for Batch Management product',),
517
 
                'hidden_perishable_mandatory': fields.boolean(string='Hidden Flag for Perishable product',),
518
 
                'kc_check': fields.function(_get_checks_all, method=True, string='KC', type='boolean', readonly=True, multi="m"),
519
 
                'ssl_check': fields.function(_get_checks_all, method=True, string='SSL', type='boolean', readonly=True, multi="m"),
520
 
                'dg_check': fields.function(_get_checks_all, method=True, string='DG', type='boolean', readonly=True, multi="m"),
521
 
                'np_check': fields.function(_get_checks_all, method=True, string='NP', type='boolean', readonly=True, multi="m"),
522
 
                }
523
 
    
524
 
    _constraints = [(_check_batch_management,
525
 
                     'You must assign a Batch Number for this product (Batch Number Mandatory)',
526
 
                     ['prodlot_id']),
527
 
                    (_check_perishable,
528
 
                     'You must assign an Expiry Date for this product (Expiry Date Mandatory)',
529
 
                     ['prodlot_id']),
530
 
                    (_check_prodlot_need,
531
 
                     'The selected product is neither Batch Number Mandatory nor Expiry Date Mandatory',
532
 
                     ['prodlot_id']),
533
 
                    (_check_prodlot_need_batch_management,
534
 
                     'The selected product is Batch Number Mandatory while the selected Production Lot corresponds to Expiry Date Mandatory.',
535
 
                     ['prodlot_id']),
536
 
                    (_check_prodlot_need_perishable,
537
 
                     'The selected product is Expiry Date Mandatory while the selected Production Lot corresponds to Batch Number Mandatory.',
538
 
                     ['prodlot_id']),
539
 
                    ]
540
 
 
541
 
stock_move()
542
 
 
543
 
 
544
 
class stock_production_lot(osv.osv):
545
 
    '''
546
 
    productin lot modifications
547
 
    '''
548
 
    _inherit = 'stock.production.lot'
549
 
    
550
 
    def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
551
 
        """
552
 
        Correct fields in order to have those from account_statement_from_invoice_lines (in case where account_statement_from_invoice is used)
553
 
        """
554
 
        if context is None:
555
 
            context = {}
556
 
        # warehouse wizards or inventory screen
557
 
        if view_type == 'tree' and ((context.get('expiry_date_check', False) and not context.get('batch_number_check', False)) or context.get('hidden_perishable_mandatory')):
558
 
            view = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'specific_rules', 'view_production_lot_expiry_date_tree')
559
 
            if view:
560
 
                view_id = view[1]
561
 
        result = super(osv.osv, self).fields_view_get(cr, uid, view_id, view_type, context=context, toolbar=toolbar, submenu=submenu)
562
 
        return result
563
 
    
564
 
    def copy(self, cr, uid, id, default=None, context=None):
565
 
        '''
566
 
        increase the batch number
567
 
        create a new sequence
568
 
        '''
569
 
        if default is None:
570
 
            default = {}
571
 
            
572
 
        # original reference
573
 
        lot_name = self.read(cr, uid, id, ['name'])['name']
574
 
        default.update(name='%s (copy)'%lot_name, date=time.strftime('%Y-%m-%d'))
575
 
        
576
 
        return super(stock_production_lot, self).copy(cr, uid, id, default, context=context)
577
 
    
578
 
    def copy_data(self, cr, uid, id, default=None, context=None):
579
 
        '''
580
 
        clear the revisions
581
 
        '''
582
 
        if default is None:
583
 
            default = {}
584
 
        default.update(revisions=[])
585
 
        return super(stock_production_lot, self).copy_data(cr, uid, id, default, context=context)
586
 
    
587
 
    def create_sequence(self, cr, uid, vals, context=None):
588
 
        """
589
 
        Create new entry sequence for every new order
590
 
        @param cr: cursor to database
591
 
        @param user: id of current user
592
 
        @param ids: list of record ids to be process
593
 
        @param context: context arguments, like lang, time zone
594
 
        @return: return a result
595
 
        """
596
 
        seq_pool = self.pool.get('ir.sequence')
597
 
        seq_typ_pool = self.pool.get('ir.sequence.type')
598
 
 
599
 
        name = 'Production Lot'
600
 
        code = 'stock.production.lot'
601
 
 
602
 
        types = {
603
 
            'name': name,
604
 
            'code': code
605
 
        }
606
 
        seq_typ_pool.create(cr, uid, types)
607
 
 
608
 
        seq = {
609
 
            'name': name,
610
 
            'code': code,
611
 
            'prefix': '',
612
 
            'padding': 0,
613
 
        }
614
 
        return seq_pool.create(cr, uid, seq)
615
 
    
616
 
    def create(self, cr, uid, vals, context=None):
617
 
        '''
618
 
        create the sequence for the version management
619
 
        '''
620
 
        if context is None:
621
 
            context = {}
622
 
            
623
 
        sequence = self.create_sequence(cr, uid, vals, context=context)
624
 
        vals.update({'sequence_id': sequence,})
625
 
        
626
 
        if context.get('update_mode') in ['init', 'update']:
627
 
            if not vals.get('life_date'):
628
 
                # default value to today
629
 
                vals.update(life_date=time.strftime('%Y-%m-%d'))
630
 
        
631
 
        return super(stock_production_lot, self).create(cr, uid, vals, context=context)
632
 
    
633
 
    def write(self, cr, uid, ids, vals, context=None):
634
 
        '''
635
 
        update the sequence for the version management
636
 
        '''
637
 
        if isinstance(ids, (int, long)):
638
 
            ids = [ids]
639
 
        
640
 
        revision_obj = self.pool.get('stock.production.lot.revision')
641
 
        
642
 
        for lot in self.browse(cr, uid, ids, context=context):
643
 
           # create revision object for each lot
644
 
           version_number = lot.sequence_id.get_id(test='id', context=context)
645
 
           values = {'name': 'Auto Revision Logging',
646
 
                     'description': 'The production lot has been modified, this revision log has been created automatically.',
647
 
                     'date': time.strftime('%Y-%m-%d'),
648
 
                     'indice': version_number,
649
 
                     'author_id': uid,
650
 
                     'lot_id': lot.id,}
651
 
           revision_obj.create(cr, uid, values, context=context)
652
 
        
653
 
        return super(stock_production_lot, self).write(cr, uid, ids, vals, context=context)
654
 
    
655
 
    def remove_flag(self, flag, _list):
656
 
        '''
657
 
        if we do not remove the flag, we fall into an infinite loop
658
 
        '''
659
 
        args2 = []
660
 
        for arg in _list:
661
 
            if arg[0] != flag:
662
 
                args2.append(arg)
663
 
        return args2
664
 
    
665
 
    def search_check_type(self, cr, uid, obj, name, args, context=None):
666
 
        '''
667
 
        modify the query to take the type of prodlot into account according to product's attributes
668
 
        'Batch Number mandatory' and 'Expiry Date Mandatory'
669
 
        
670
 
        if batch management: display only 'standard' lot
671
 
        if expiry and not batch management: display only 'internal' lot
672
 
        else: display normally
673
 
        '''
674
 
        product_obj = self.pool.get('product.product')
675
 
        product_id = context.get('product_id', False)
676
 
        
677
 
        # remove flag avoid infinite loop
678
 
        args = self.remove_flag('check_type', args)
679
 
            
680
 
        if not product_id:
681
 
            return args
682
 
        
683
 
        # check the product
684
 
        product = product_obj.browse(cr, uid, product_id, context=context)
685
 
 
686
 
        if product.batch_management:
687
 
            # standard lots
688
 
            args.append(('type', '=', 'standard'))
689
 
        elif product.perishable:
690
 
            # internal lots
691
 
            args.append(('type', '=', 'internal'))
692
 
            
693
 
        return args
694
 
    
695
 
    def _get_false(self, cr, uid, ids, field_name, arg, context=None):
696
 
        '''
697
 
        return false for each id
698
 
        '''
699
 
        if isinstance(ids,(long, int)):
700
 
           ids = [ids]
701
 
        
702
 
        result = {}
703
 
        for id in ids:
704
 
          result[id] = False
705
 
        return result
706
 
    
707
 
    def _get_stock(self, cr, uid, ids, field_name, arg, context=None):
708
 
        """ Gets stock of products for locations
709
 
        @return: Dictionary of values
710
 
        """
711
 
        if context is None:
712
 
            context = {}
713
 
            
714
 
        if isinstance(ids, (int, long)):
715
 
            ids = [ids]
716
 
            
717
 
        product_obj = self.pool.get('product.product')
718
 
        
719
 
        result = {}
720
 
        for id in ids:
721
 
            result[id] = 0.0
722
 
        
723
 
        for lot in self.browse(cr, uid, ids, context=context):
724
 
            # because the lot_id changes we have to loop one lot id at a time
725
 
            c = context.copy()
726
 
            # if you remove the coma after done, it will no longer work properly
727
 
            c.update({'what': ('in', 'out'),
728
 
                      'prodlot_id': lot.id,
729
 
                      #'to_date': time.strftime('%Y-%m-%d %H:%M:%S'),
730
 
                      #'warehouse': warehouse_id,
731
 
                      #'uom': product_uom_id
732
 
                      })
733
 
            
734
 
            if field_name == 'stock_available':
735
 
                # available stock
736
 
                c.update(states=('confirmed','waiting','assigned','done'))
737
 
            elif field_name == 'stock_real':
738
 
                # real stock
739
 
                c.update(states=('done',))
740
 
            else:
741
 
                assert False, 'This line should not be reached: field_name: %s'%field_name
742
 
            
743
 
            qty = product_obj.get_product_available(cr, uid, [lot.product_id.id], context=c)
744
 
            overall_qty = sum(qty.values())
745
 
            result[lot.id] = overall_qty
746
 
        
747
 
        return result
748
 
    
749
 
    def _get_checks_all(self, cr, uid, ids, name, arg, context=None):
750
 
        '''
751
 
        function for KC/SSL/DG/NP products
752
 
        '''
753
 
        result = {}
754
 
        for id in ids:
755
 
            result[id] = {}
756
 
            for f in name:
757
 
                result[id].update({f: False})
758
 
            
759
 
        for obj in self.browse(cr, uid, ids, context=context):
760
 
            # keep cool
761
 
            if obj.product_id.heat_sensitive_item:
762
 
                result[obj.id]['kc_check'] = True
763
 
            # ssl
764
 
            if obj.product_id.short_shelf_life:
765
 
                result[obj.id]['ssl_check'] = True
766
 
            # dangerous goods
767
 
            if obj.product_id.dangerous_goods:
768
 
                result[obj.id]['dg_check'] = True
769
 
            # narcotic
770
 
            if obj.product_id.narcotic:
771
 
                result[obj.id]['np_check'] = True
772
 
            
773
 
        return result
774
 
    
775
 
    _columns = {'check_type': fields.function(_get_false, fnct_search=search_check_type, string='Check Type', type="boolean", readonly=True, method=True),
776
 
                'type': fields.selection([('standard', 'Standard'),('internal', 'Internal'),], string="Type"),
777
 
                #'expiry_date': fields.date('Expiry Date'),
778
 
                'name': fields.char('Batch Number', size=1024, required=True, help="Unique production lot, will be displayed as: PREFIX/SERIAL [INT_REF]"),
779
 
                'date': fields.datetime('Auto Creation Date', required=True),
780
 
                'sequence_id': fields.many2one('ir.sequence', 'Lot Sequence', required=True,),
781
 
                'stock_available': fields.function(_get_stock, method=True, type="float", string="Available", select=True,
782
 
                                                   help="Current quantity of products with this Production Lot Number available in company warehouses",
783
 
                                                   digits_compute=dp.get_precision('Product UoM'), readonly=True,),
784
 
                'stock_real': fields.function(_get_stock, method=True, type="float", string="Real", select=True,
785
 
                                                   help="Current quantity of products with this Production Lot Number available in company warehouses",
786
 
                                                   digits_compute=dp.get_precision('Product UoM'), readonly=True,),
787
 
                'kc_check': fields.function(_get_checks_all, method=True, string='KC', type='boolean', readonly=True, multi="m"),
788
 
                'ssl_check': fields.function(_get_checks_all, method=True, string='SSL', type='boolean', readonly=True, multi="m"),
789
 
                'dg_check': fields.function(_get_checks_all, method=True, string='DG', type='boolean', readonly=True, multi="m"),
790
 
                'np_check': fields.function(_get_checks_all, method=True, string='NP', type='boolean', readonly=True, multi="m"),
791
 
                }
792
 
    
793
 
    _defaults = {'type': 'standard',
794
 
                 'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'stock.production.lot', context=c),
795
 
                 'name': False,
796
 
                 'life_date': False,
797
 
                 }
798
 
    
799
 
    _sql_constraints = [('name_uniq', 'unique (name)', 'The Batch Number must be unique !'),
800
 
                        ]
801
 
    
802
 
    def search(self, cr, uid, args=[], offset=0, limit=None, order=None, context={}, count=False):
803
 
        '''
804
 
        search function of production lot
805
 
        '''
806
 
        result = super(stock_production_lot, self).search(cr, uid, args, offset, limit, order, context, count)
807
 
        
808
 
        return result
809
 
    
810
 
    def name_get(self, cr, uid, ids, context=None):
811
 
        if not ids:
812
 
            return []
813
 
        reads = self.read(cr, uid, ids, ['name', 'prefix', 'ref'], context)
814
 
        res = []
815
 
        for record in reads:
816
 
            name = record['name']
817
 
            res.append((record['id'], name))
818
 
        return res
819
 
    
820
 
stock_production_lot()
821
 
 
822
 
 
823
 
class stock_production_lot_revision(osv.osv):
824
 
    _inherit = 'stock.production.lot.revision'
825
 
    _order = 'indice desc'
826
 
    
827
 
stock_production_lot_revision()
828
 
 
829
 
 
830
 
class stock_inventory(osv.osv):
831
 
    '''
832
 
    override the action_confirm to create the production lot if needed
833
 
    '''
834
 
    _inherit = 'stock.inventory'
835
 
    
836
 
    def action_confirm(self, cr, uid, ids, context=None):
837
 
        '''
838
 
        if the line is perishable without prodlot, we create the prodlot
839
 
        '''
840
 
        prodlot_obj = self.pool.get('stock.production.lot')
841
 
        # treat the needed production lot
842
 
        for obj in self.browse(cr, uid, ids, context=context):
843
 
            for line in obj.inventory_line_id:
844
 
                # if perishable product
845
 
                if line.hidden_perishable_mandatory and not line.hidden_batch_management_mandatory:
846
 
                    # integrity test
847
 
                    assert line.product_id.perishable, 'product is not perishable but line is'
848
 
                    assert line.expiry_date, 'expiry date is not set'
849
 
                    # if no production lot, we create a new one
850
 
                    if not line.prod_lot_id:
851
 
                        # double check to find the corresponding prodlot
852
 
                        prodlot_ids = prodlot_obj.search(cr, uid, [('life_date', '=', line.expiry_date),
853
 
                                                                   ('type', '=', 'internal'),
854
 
                                                                   ('product_id', '=', line.product_id.id)], context=context)
855
 
                        # no prodlot, create a new one
856
 
                        if not prodlot_ids:
857
 
                            vals = {'product_id': line.product_id.id,
858
 
                                    'life_date': line.expiry_date,
859
 
                                    'name': self.pool.get('ir.sequence').get(cr, uid, 'stock.lot.serial'),
860
 
                                    'type': 'internal',
861
 
                                    }
862
 
                            prodlot_id = prodlot_obj.create(cr, uid, vals, context=context)
863
 
                        else:
864
 
                            prodlot_id = prodlot_ids[0]
865
 
                        # update the line
866
 
                        line.write({'prod_lot_id': prodlot_id,},)
867
 
        
868
 
        # super function after production lot creation - production lot are therefore taken into account at stock move creation
869
 
        result = super(stock_inventory, self).action_confirm(cr, uid, ids, context=context)      
870
 
        return result
871
 
                        
872
 
stock_inventory()
873
 
 
874
 
 
875
 
class stock_inventory_line(osv.osv):
876
 
    '''
877
 
    add mandatory or readonly behavior to prodlot
878
 
    '''
879
 
    _inherit = 'stock.inventory.line'
880
 
    
881
 
    def change_lot(self, cr, uid, id, prod_lot_id, context=None):
882
 
        '''
883
 
        prod lot changes, update the expiry date
884
 
        '''
885
 
        prodlot_obj = self.pool.get('stock.production.lot')
886
 
        result = {'value':{}}
887
 
        
888
 
        if prod_lot_id:
889
 
            result['value'].update(expiry_date=prodlot_obj.browse(cr, uid, prod_lot_id, context).life_date)
890
 
        else:
891
 
            result['value'].update(expiry_date=False)
892
 
        
893
 
        return result
894
 
    
895
 
    def change_expiry(self, cr, uid, id, expiry_date, product_id, type_check, context=None):
896
 
        '''
897
 
        expiry date changes, find the corresponding internal prod lot
898
 
        '''
899
 
        prodlot_obj = self.pool.get('stock.production.lot')
900
 
        result = {'value':{}}
901
 
        
902
 
        if expiry_date and product_id:
903
 
            prod_ids = prodlot_obj.search(cr, uid, [('life_date', '=', expiry_date),
904
 
                                                    ('type', '=', 'internal'),
905
 
                                                    ('product_id', '=', product_id)], context=context)
906
 
            if not prod_ids:
907
 
                if type_check == 'in':
908
 
                    # the corresponding production lot will be created afterwards
909
 
                    result['warning'] = {'title': _('Info'),
910
 
                                     'message': _('The selected Expiry Date does not exist in the system. It will be created during validation process.')}
911
 
                    # clear prod lot
912
 
                    result['value'].update(prod_lot_id=False)
913
 
                else:
914
 
                    # display warning
915
 
                    result['warning'] = {'title': _('Error'),
916
 
                                         'message': _('The selected Expiry Date does not exist in the system.')}
917
 
                    # clear date
918
 
                    result['value'].update(expiry_date=False, prod_lot_id=False)
919
 
            else:
920
 
                # return first prodlot
921
 
                result['value'].update(prod_lot_id=prod_ids[0])
922
 
                
923
 
        else:
924
 
            # clear expiry date, we clear production lot
925
 
            result['value'].update(prod_lot_id=False,
926
 
                                   expiry_date=False,
927
 
                                   )
928
 
        
929
 
        return result
930
 
    
931
 
    def _get_checks_all(self, cr, uid, ids, name, arg, context=None):
932
 
        '''
933
 
        function for KC/SSL/DG/NP products
934
 
        '''
935
 
        result = {}
936
 
        for id in ids:
937
 
            result[id] = {}
938
 
            for f in name:
939
 
                result[id].update({f: False,})
940
 
            
941
 
        for obj in self.browse(cr, uid, ids, context=context):
942
 
            # keep cool
943
 
            if obj.product_id.heat_sensitive_item:
944
 
                result[obj.id]['kc_check'] = True
945
 
            # ssl
946
 
            if obj.product_id.short_shelf_life:
947
 
                result[obj.id]['ssl_check'] = True
948
 
            # dangerous goods
949
 
            if obj.product_id.dangerous_goods:
950
 
                result[obj.id]['dg_check'] = True
951
 
            # narcotic
952
 
            if obj.product_id.narcotic:
953
 
                result[obj.id]['np_check'] = True
954
 
            
955
 
        return result
956
 
    
957
 
    def _check_batch_management(self, cr, uid, ids, context=None):
958
 
        '''
959
 
        check for batch management
960
 
        '''
961
 
        for obj in self.browse(cr, uid, ids, context=context):
962
 
            if obj.product_id.batch_management:
963
 
                if not obj.prod_lot_id or obj.prod_lot_id.type != 'standard':
964
 
                    return False
965
 
        return True
966
 
    
967
 
    def _check_perishable(self, cr, uid, ids, context=None):
968
 
        """
969
 
        check for perishable ONLY
970
 
        """
971
 
        for obj in self.browse(cr, uid, ids, context=context):
972
 
            if obj.product_id.perishable and not obj.product_id.batch_management:
973
 
                if (not obj.prod_lot_id and not obj.expiry_date) or (obj.prod_lot_id and obj.prod_lot_id.type != 'internal'):
974
 
                    return False
975
 
        return True
976
 
    
977
 
    def _check_prodlot_need(self, cr, uid, ids, context=None):
978
 
        """
979
 
        If the inv line has a prodlot but does not need one, return False.
980
 
        """
981
 
        for obj in self.browse(cr, uid, ids, context=context):
982
 
            if obj.prod_lot_id:
983
 
                if not obj.product_id.perishable and not obj.product_id.batch_management:
984
 
                    return False
985
 
        return True
986
 
    
987
 
    _columns = {'hidden_perishable_mandatory': fields.boolean(string='Hidden Flag for Perishable product',),
988
 
                'hidden_batch_management_mandatory': fields.boolean(string='Hidden Flag for Batch Management product',),
989
 
                'expiry_date': fields.date(string='Expiry Date'),
990
 
                'type_check': fields.char(string='Type Check', size=1024,),
991
 
                'kc_check': fields.function(_get_checks_all, method=True, string='KC', type='boolean', readonly=True, multi="m"),
992
 
                'ssl_check': fields.function(_get_checks_all, method=True, string='SSL', type='boolean', readonly=True, multi="m"),
993
 
                'dg_check': fields.function(_get_checks_all, method=True, string='DG', type='boolean', readonly=True, multi="m"),
994
 
                'np_check': fields.function(_get_checks_all, method=True, string='NP', type='boolean', readonly=True, multi="m"),
995
 
                }
996
 
    
997
 
    _defaults = {# in is used, meaning a new prod lot will be created if the specified expiry date does not exist
998
 
                 'type_check': 'in',
999
 
                 }
1000
 
    
1001
 
    _constraints = [(_check_batch_management,
1002
 
                     'You must assign a Production Lot which corresponds to Batch Number Mandatory Products.',
1003
 
                     ['prod_lot_id']),
1004
 
                    (_check_perishable,
1005
 
                     'You must assign a Production Lot which corresponds to Expiry Date Mandatory Products.',
1006
 
                     ['prod_lot_id']),
1007
 
                    (_check_prodlot_need,
1008
 
                     'The selected product is neither Batch Number Mandatory nor Expiry Date Mandatory',
1009
 
                     ['prod_lot_id']),
1010
 
                    ]
1011
 
    
1012
 
    def on_change_product_id(self, cr, uid, ids, location_id, product, uom=False, to_date=False):
1013
 
        '''
1014
 
        the product changes, set the hidden flag if necessary
1015
 
        '''
1016
 
        result = super(stock_inventory_line, self).on_change_product_id(cr, uid, ids, location_id, product, uom, to_date)
1017
 
        
1018
 
        # product changes, prodlot is always cleared
1019
 
        result.setdefault('value', {})['prod_lot_id'] = False
1020
 
        result.setdefault('value', {})['expiry_date'] = False
1021
 
        # reset the hidden flags
1022
 
        result.setdefault('value', {})['hidden_batch_management_mandatory'] = False
1023
 
        result.setdefault('value', {})['hidden_perishable_mandatory'] = False
1024
 
        if product:
1025
 
            product_obj = self.pool.get('product.product').browse(cr, uid, product)
1026
 
            if product_obj.batch_management:
1027
 
                result.setdefault('value', {})['hidden_batch_management_mandatory'] = True
1028
 
            elif product_obj.perishable:
1029
 
                result.setdefault('value', {})['hidden_perishable_mandatory'] = True
1030
 
            
1031
 
        return result
1032
 
    
1033
 
    def create(self, cr, uid, vals, context=None):
1034
 
        '''
1035
 
        complete info normally generated by javascript on_change function
1036
 
        '''
1037
 
        prod_obj = self.pool.get('product.product')
1038
 
        if vals.get('product_id', False):
1039
 
            # complete hidden flags - needed if not created from GUI
1040
 
            product = prod_obj.browse(cr, uid, vals.get('product_id'), context=context)
1041
 
            if product.batch_management:
1042
 
                vals.update(hidden_batch_management_mandatory=True)
1043
 
            elif product.perishable:
1044
 
                vals.update(hidden_perishable_mandatory=True)
1045
 
            else:
1046
 
                vals.update(hidden_batch_management_mandatory=False,
1047
 
                            hidden_perishable_mandatory=False,
1048
 
                            )
1049
 
        # complete expiry date from production lot - needed if not created from GUI
1050
 
        prodlot_obj = self.pool.get('stock.production.lot')
1051
 
        if vals.get('prod_lot_id', False):
1052
 
            vals.update(expiry_date=prodlot_obj.browse(cr, uid, vals.get('prod_lot_id'), context=context).life_date)
1053
 
        # call super
1054
 
        result = super(stock_inventory_line, self).create(cr, uid, vals, context=context)
1055
 
        return result
1056
 
    
1057
 
    def write(self, cr, uid, ids, vals, context=None):
1058
 
        '''
1059
 
        complete info normally generated by javascript on_change function
1060
 
        '''
1061
 
        prod_obj = self.pool.get('product.product')
1062
 
        if vals.get('product_id', False):
1063
 
            # complete hidden flags - needed if not created from GUI
1064
 
            product = prod_obj.browse(cr, uid, vals.get('product_id'), context=context)
1065
 
            if product.batch_management:
1066
 
                vals.update(hidden_batch_management_mandatory=True)
1067
 
            elif product.perishable:
1068
 
                vals.update(hidden_perishable_mandatory=True)
1069
 
            else:
1070
 
                vals.update(hidden_batch_management_mandatory=False,
1071
 
                            hidden_perishable_mandatory=False,
1072
 
                            )
1073
 
        # complete expiry date from production lot - needed if not created from GUI
1074
 
        prodlot_obj = self.pool.get('stock.production.lot')
1075
 
        if vals.get('prod_lot_id', False):
1076
 
            vals.update(expiry_date=prodlot_obj.browse(cr, uid, vals.get('prod_lot_id'), context=context).life_date)
1077
 
        
1078
 
        # call super
1079
 
        result = super(stock_inventory_line, self).write(cr, uid, ids, vals, context=context)
1080
 
        return result
1081
 
 
1082
 
stock_inventory_line()
1083
 
 
1084
 
class report_stock_inventory(osv.osv):
1085
 
    '''
1086
 
    UF-565: add group by expired_date
1087
 
    '''
1088
 
    _inherit = "report.stock.inventory"
1089
 
    
1090
 
    def init(self, cr):
1091
 
        tools.drop_view_if_exists(cr, 'report_stock_inventory')
1092
 
        cr.execute("""
1093
 
CREATE OR REPLACE view report_stock_inventory AS (
1094
 
    (SELECT
1095
 
        min(m.id) as id, m.date as date,
1096
 
        m.expired_date as expired_date,
1097
 
        m.address_id as partner_id, m.location_id as location_id,
1098
 
        m.product_id as product_id, pt.categ_id as product_categ_id, l.usage as location_type,
1099
 
        m.company_id,
1100
 
        m.state as state, m.prodlot_id as prodlot_id,
1101
 
        coalesce(sum(-pt.standard_price * m.product_qty)::decimal, 0.0) as value,
1102
 
        CASE when pt.uom_id = m.product_uom
1103
 
        THEN
1104
 
        coalesce(sum(-m.product_qty)::decimal, 0.0)
1105
 
        ELSE
1106
 
        coalesce(sum(-m.product_qty * pu.factor)::decimal, 0.0) END as product_qty
1107
 
    FROM
1108
 
        stock_move m
1109
 
            LEFT JOIN stock_picking p ON (m.picking_id=p.id)
1110
 
            LEFT JOIN product_product pp ON (m.product_id=pp.id)
1111
 
                LEFT JOIN product_template pt ON (pp.product_tmpl_id=pt.id)
1112
 
                LEFT JOIN product_uom pu ON (pt.uom_id=pu.id)
1113
 
            LEFT JOIN product_uom u ON (m.product_uom=u.id)
1114
 
            LEFT JOIN stock_location l ON (m.location_id=l.id)
1115
 
    GROUP BY
1116
 
        m.id, m.product_id, m.product_uom, pt.categ_id, m.address_id, m.location_id,  m.location_dest_id,
1117
 
        m.prodlot_id, m.expired_date, m.date, m.state, l.usage, m.company_id,pt.uom_id
1118
 
) UNION ALL (
1119
 
    SELECT
1120
 
        -m.id as id, m.date as date,
1121
 
        m.expired_date as expired_date,
1122
 
        m.address_id as partner_id, m.location_dest_id as location_id,
1123
 
        m.product_id as product_id, pt.categ_id as product_categ_id, l.usage as location_type,
1124
 
        m.company_id,
1125
 
        m.state as state, m.prodlot_id as prodlot_id,
1126
 
        coalesce(sum(pt.standard_price * m.product_qty )::decimal, 0.0) as value,
1127
 
        CASE when pt.uom_id = m.product_uom
1128
 
        THEN
1129
 
        coalesce(sum(m.product_qty)::decimal, 0.0)
1130
 
        ELSE
1131
 
        coalesce(sum(m.product_qty * pu.factor)::decimal, 0.0) END as product_qty
1132
 
    FROM
1133
 
        stock_move m
1134
 
            LEFT JOIN stock_picking p ON (m.picking_id=p.id)
1135
 
            LEFT JOIN product_product pp ON (m.product_id=pp.id)
1136
 
                LEFT JOIN product_template pt ON (pp.product_tmpl_id=pt.id)
1137
 
                LEFT JOIN product_uom pu ON (pt.uom_id=pu.id)
1138
 
            LEFT JOIN product_uom u ON (m.product_uom=u.id)
1139
 
            LEFT JOIN stock_location l ON (m.location_dest_id=l.id)
1140
 
    GROUP BY
1141
 
        m.id, m.product_id, m.product_uom, pt.categ_id, m.address_id, m.location_id, m.location_dest_id,
1142
 
        m.prodlot_id, m.expired_date, m.date, m.state, l.usage, m.company_id,pt.uom_id
1143
 
    )
1144
 
);
1145
 
        """)
1146
 
    
1147
 
    _columns = {'expired_date': fields.date(string='Expiry Date'),
1148
 
                }
1149
 
    
1150
 
report_stock_inventory()