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

« back to all changes in this revision

Viewing changes to specific_rules/specific_rules.py

UF-73: [MERGE] Merge with unifield-wm branch

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
 
import logging
33
 
 
34
 
from mx import DateTime
35
 
 
36
 
# warning messages
37
 
SHORT_SHELF_LIFE_MESS = 'Product with Short Shelf Life, check the accuracy of the order quantity, frequency and mode of transport.'
38
 
 
39
 
 
40
 
class sale_order_line(osv.osv):
41
 
    '''
42
 
    override to add message at sale order creation and update
43
 
    '''
44
 
    _inherit = 'sale.order.line'
45
 
    
46
 
    def _kc_dg(self, cr, uid, ids, name, arg, context=None):
47
 
        '''
48
 
        return 'KC' if cold chain or 'DG' if dangerous goods
49
 
        '''
50
 
        result = {}
51
 
        for id in ids:
52
 
            result[id] = ''
53
 
            
54
 
        for sol in self.browse(cr, uid, ids, context=context):
55
 
            if sol.product_id:
56
 
                if sol.product_id.heat_sensitive_item:
57
 
                    result[sol.id] = 'KC'
58
 
                elif sol.product_id.dangerous_goods:
59
 
                    result[sol.id] = 'DG'
60
 
        
61
 
        return result
62
 
        
63
 
    _columns = {'kc_dg': fields.function(_kc_dg, method=True, string='KC/DG', type='char'),}
64
 
    
65
 
    def product_id_change(self, cr, uid, ids, pricelist, product, qty=0,
66
 
            uom=False, qty_uos=0, uos=False, name='', partner_id=False,
67
 
            lang=False, update_tax=True, date_order=False, packaging=False, fiscal_position=False, flag=False):
68
 
        '''
69
 
        if the product is short shelf life we display a warning
70
 
        '''
71
 
        # call to super
72
 
        result = super(sale_order_line, self).product_id_change(cr, uid, ids, pricelist, product, qty,
73
 
            uom, qty_uos, uos, name, partner_id, lang, update_tax, date_order, packaging, fiscal_position, flag)
74
 
        
75
 
        # if the product is short shelf life, display a warning
76
 
        if product:
77
 
            prod_obj = self.pool.get('product.product')
78
 
            if prod_obj.browse(cr, uid, product).short_shelf_life:
79
 
                warning = {
80
 
                            'title': 'Short Shelf Life product',
81
 
                            'message': _(SHORT_SHELF_LIFE_MESS)
82
 
                            }
83
 
                result.update(warning=warning)
84
 
            
85
 
        return result
86
 
    
87
 
sale_order_line()
88
 
 
89
 
 
90
 
class sale_order(osv.osv):
91
 
    '''
92
 
    add message when so is written, i.e when we add new so lines
93
 
    '''
94
 
    _inherit = 'sale.order'
95
 
    
96
 
    def write(self, cr, uid, ids, vals, context=None):
97
 
        '''
98
 
        display message if contains short shelf life
99
 
        '''
100
 
        if isinstance(ids, (int, long)):
101
 
            ids = [ids]
102
 
            
103
 
        for obj in self.browse(cr, uid, ids, context=context):
104
 
            for line in obj.order_line:
105
 
                # log the message
106
 
                if line.product_id.short_shelf_life:
107
 
                    # log the message
108
 
                    self.log(cr, uid, obj.id, _(SHORT_SHELF_LIFE_MESS))
109
 
        
110
 
        return super(sale_order, self).write(cr, uid, ids, vals, context=context)
111
 
    
112
 
sale_order()
113
 
 
114
 
 
115
 
class purchase_order_line(osv.osv):
116
 
    '''
117
 
    override to add message at purchase order creation and update
118
 
    '''
119
 
    _inherit = 'purchase.order.line'
120
 
    
121
 
    def _kc_dg(self, cr, uid, ids, name, arg, context=None):
122
 
        '''
123
 
        return 'KC' if cold chain or 'DG' if dangerous goods
124
 
        '''
125
 
        result = {}
126
 
        for id in ids:
127
 
            result[id] = ''
128
 
            
129
 
        for pol in self.browse(cr, uid, ids, context=context):
130
 
            if pol.product_id:
131
 
                if pol.product_id.heat_sensitive_item:
132
 
                    result[pol.id] = 'KC'
133
 
                elif pol.product_id.dangerous_goods:
134
 
                    result[pol.id] = 'DG'
135
 
        
136
 
        return result
137
 
        
138
 
    _columns = {'kc_dg': fields.function(_kc_dg, method=True, string='KC/DG', type='char'),}
139
 
    
140
 
    def product_id_change(self, cr, uid, ids, pricelist, product, qty, uom,
141
 
            partner_id, date_order=False, fiscal_position=False, date_planned=False,
142
 
            name=False, price_unit=False, notes=False):
143
 
        '''
144
 
        if the product is short shelf life we display a warning
145
 
        '''
146
 
        # call to super
147
 
        result = super(purchase_order_line, self).product_id_change(cr, uid, ids, pricelist, product, qty, uom,
148
 
            partner_id, date_order, fiscal_position, date_planned,
149
 
            name, price_unit, notes)
150
 
        
151
 
        # if the product is short shelf life, display a warning
152
 
        if product:
153
 
            prod_obj = self.pool.get('product.product')
154
 
            if prod_obj.browse(cr, uid, product).short_shelf_life:
155
 
                warning = {
156
 
                            'title': 'Short Shelf Life product',
157
 
                            'message': _(SHORT_SHELF_LIFE_MESS)
158
 
                            }
159
 
                result.update(warning=warning)
160
 
            
161
 
        return result
162
 
    
163
 
purchase_order_line()
164
 
 
165
 
 
166
 
class purchase_order(osv.osv):
167
 
    '''
168
 
    add message when po is written, i.e when we add new po lines
169
 
    
170
 
    no need to modify the wkf_confirm_order as the wrtie method is called during the workflow
171
 
    '''
172
 
    _inherit = 'purchase.order'
173
 
    
174
 
    def write(self, cr, uid, ids, vals, context=None):
175
 
        '''
176
 
        display message if contains short shelf life
177
 
        '''
178
 
        if isinstance(ids, (int, long)):
179
 
            ids = [ids]
180
 
            
181
 
        for obj in self.browse(cr, uid, ids, context=context):
182
 
            for line in obj.order_line:
183
 
                # log the message
184
 
                if line.product_id.short_shelf_life:
185
 
                    # log the message
186
 
                    self.log(cr, uid, obj.id, _(SHORT_SHELF_LIFE_MESS))
187
 
        
188
 
        return super(purchase_order, self).write(cr, uid, ids, vals, context=context)
189
 
    
190
 
purchase_order()
191
 
 
192
 
 
193
 
class stock_warehouse_orderpoint(osv.osv):
194
 
    '''
195
 
    add message
196
 
    '''
197
 
    _inherit = 'stock.warehouse.orderpoint'
198
 
 
199
 
    _columns = {
200
 
         'name': fields.char('Reference', size=128, required=True, select=True),
201
 
         'location_id': fields.many2one('stock.location', 'Location', required=True, ondelete="cascade", 
202
 
                                        domain="[('is_replenishment', '=', warehouse_id)]"),
203
 
    }
204
 
    
205
 
    def _check_product_uom(self, cr, uid, ids, context=None):
206
 
        '''
207
 
        Check if the UoM has the same category as the product standard UoM
208
 
        '''
209
 
        if not context:
210
 
            context = {}
211
 
            
212
 
        for rule in self.browse(cr, uid, ids, context=context):
213
 
            if rule.product_id.uom_id.category_id.id != rule.product_uom.category_id.id:
214
 
                return False
215
 
            
216
 
        return True
217
 
    
218
 
    _constraints = [
219
 
        (_check_product_uom, 'You have to select a product UOM in the same category than the purchase UOM of the product', ['product_id', 'product_uom']),
220
 
    ]
221
 
    
222
 
    def default_get(self, cr, uid, fields, context=None):
223
 
        '''
224
 
        Get the default values for the replenishment rule
225
 
        '''
226
 
        res = super(stock_warehouse_orderpoint, self).default_get(cr, uid, fields, context=context)
227
 
        
228
 
        company_id = res.get('company_id')
229
 
        warehouse_id = res.get('warehouse_id')
230
 
        
231
 
        if not 'company_id' in res:
232
 
            company_id = self.pool.get('res.company')._company_default_get(cr, uid, 'stock.warehouse.automatic.supply', context=context)
233
 
            res.update({'company_id': company_id})
234
 
        
235
 
        if not 'warehouse_id' in res:
236
 
            warehouse_id = self.pool.get('stock.warehouse').search(cr, uid, [('company_id', '=', company_id)], context=context)[0]
237
 
            res.update({'warehouse_id': warehouse_id})
238
 
            
239
 
        if not 'location_id' in res:
240
 
            location_id = self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id, context=context).lot_stock_id.id
241
 
            res.update({'location_id': location_id})
242
 
        
243
 
        return res
244
 
    
245
 
    def create(self, cr, uid, vals, context=None):
246
 
        '''
247
 
        add message
248
 
        '''
249
 
        new_id = super(stock_warehouse_orderpoint, self).create(cr, uid, vals, context=context)
250
 
        
251
 
        product_obj = self.pool.get('product.product')
252
 
        product_id = vals.get('product_id', False)
253
 
        if product_id:
254
 
            if product_obj.browse(cr, uid, product_id, context=context).short_shelf_life:
255
 
                self.log(cr, uid, new_id, _(SHORT_SHELF_LIFE_MESS))
256
 
                
257
 
        return new_id
258
 
    
259
 
    def write(self, cr, uid, ids, vals, context=None):
260
 
        '''
261
 
        add message
262
 
        '''
263
 
        result = super(stock_warehouse_orderpoint, self).write(cr, uid, ids, vals, context=context)
264
 
        
265
 
        if isinstance(ids, (int, long)):
266
 
            ids = [ids]
267
 
        
268
 
        product_obj = self.pool.get('product.product')
269
 
        product_id = vals.get('product_id', False)
270
 
        if product_id:
271
 
            if product_obj.browse(cr, uid, product_id, context=context).short_shelf_life:
272
 
                for obj in self.browse(cr, uid, ids, context=context):
273
 
                    self.log(cr, uid, obj.id, _(SHORT_SHELF_LIFE_MESS))
274
 
        
275
 
        return result
276
 
    
277
 
    def onchange_product_id(self, cr, uid, ids, product_id, context=None):
278
 
        '''
279
 
        Add domain on UoM to have only UoM on the same category of the
280
 
        product standard UoM
281
 
        '''
282
 
        product_obj = self.pool.get('product.product')
283
 
        
284
 
        res = super(stock_warehouse_orderpoint, self).onchange_product_id(cr, uid, ids, product_id, context=context)
285
 
        domain = {}
286
 
 
287
 
        # Get the product UoM category
288
 
        if product_id:        
289
 
            product = product_obj.browse(cr, uid, product_id, context=context)
290
 
            domain = {'product_uom': [('category_id', '=', product.uom_id.category_id.id)]}
291
 
        else:
292
 
            domain = {'product_uom': []}
293
 
            if 'value' in res:
294
 
                res['value'].update({'product_uom': False})
295
 
            else:
296
 
                res.update({'value': {'product_uom': False}})
297
 
                
298
 
        # Apply the domain in res
299
 
        if 'domain' in res:
300
 
            res['domain'].update(domain)
301
 
        else:
302
 
            res.update({'domain': domain})
303
 
            
304
 
        return res
305
 
    
306
 
    def onchange_uom(self, cr, uid, ids, product_id, uom_id, context=None):
307
 
        '''
308
 
        Check if the UoM is convertible to product standard UoM
309
 
        '''
310
 
        if uom_id and product_id:
311
 
            product_obj = self.pool.get('product.product')
312
 
            uom_obj = self.pool.get('product.uom')
313
 
        
314
 
            product = product_obj.browse(cr, uid, product_id, context=context)
315
 
            uom = uom_obj.browse(cr, uid, uom_id, context=context)
316
 
        
317
 
            if product.uom_id.category_id.id != uom.category_id.id:
318
 
                raise osv.except_osv(_('Wrong Product UOM !'), _('You have to select a product UOM in the same category than the purchase UOM of the product'))
319
 
        
320
 
        return {}
321
 
        
322
 
        
323
 
stock_warehouse_orderpoint()
324
 
 
325
 
 
326
 
class product_uom(osv.osv):
327
 
    _name = 'product.uom'
328
 
    _inherit = 'product.uom'
329
 
    
330
 
    def _get_uom_by_product(self, cr, uid, ids, field_name, args, context=None):
331
 
        return {}
332
 
    
333
 
    def _search_uom_by_product(self, cr, uid, obj, name, args, context=None):
334
 
        dom = []
335
 
        
336
 
        for arg in args:
337
 
            if arg[0] == 'uom_by_product' and arg[1] != '=':
338
 
                raise osv.except_osv(_('Error'), _('Bad comparison operator in domain'))
339
 
            elif arg[0] == 'uom_by_product':
340
 
                product_id = arg[2]
341
 
                if isinstance(product_id, (int, long)):
342
 
                    product_id = [product_id]
343
 
                product = self.pool.get('product.product').browse(cr, uid, product_id[0], context=context)
344
 
                dom.append(('category_id', '=', product.uom_id.category_id.id))
345
 
                
346
 
        return dom
347
 
    
348
 
    _columns = {
349
 
        'uom_by_product': fields.function(_get_uom_by_product, fnct_search=_search_uom_by_product, string='UoM by Product', 
350
 
                                          help='Field used to filter the UoM for a specific product'),
351
 
    }
352
 
    
353
 
product_uom()
354
 
 
355
 
 
356
 
class stock_warehouse_automatic_supply(osv.osv):
357
 
    '''
358
 
    add message
359
 
    '''
360
 
    _inherit = 'stock.warehouse.automatic.supply'
361
 
    
362
 
    def create(self, cr, uid, vals, context=None):
363
 
        '''
364
 
        add message
365
 
        '''
366
 
        new_id = super(stock_warehouse_automatic_supply, self).create(cr, uid, vals, context=context)
367
 
        
368
 
        product_obj = self.pool.get('product.product')
369
 
        product_id = vals.get('product_id', False)
370
 
        if product_id:
371
 
            if product_obj.browse(cr, uid, product_id, context=context).short_shelf_life:
372
 
                self.log(cr, uid, new_id, _(SHORT_SHELF_LIFE_MESS))
373
 
                
374
 
        return new_id
375
 
    
376
 
    def write(self, cr, uid, ids, vals, context=None):
377
 
        '''
378
 
        add message
379
 
        '''
380
 
        result = super(stock_warehouse_automatic_supply, self).write(cr, uid, ids, vals, context=context)
381
 
        
382
 
        if isinstance(ids, (int, long)):
383
 
            ids = [ids]
384
 
        
385
 
        product_obj = self.pool.get('product.product')
386
 
        product_id = vals.get('product_id', False)
387
 
        if product_id:
388
 
            if product_obj.browse(cr, uid, product_id, context=context).short_shelf_life:
389
 
                for obj in self.browse(cr, uid, ids, context=context):
390
 
                    self.log(cr, uid, obj.id, _(SHORT_SHELF_LIFE_MESS))
391
 
        
392
 
        return result
393
 
    
394
 
stock_warehouse_automatic_supply()
395
 
 
396
 
 
397
 
class stock_warehouse_order_cycle(osv.osv):
398
 
    '''
399
 
    add message
400
 
    '''
401
 
    _inherit = 'stock.warehouse.order.cycle'
402
 
    
403
 
    def create(self, cr, uid, vals, context=None):
404
 
        '''
405
 
        add message
406
 
        '''
407
 
        new_id = super(stock_warehouse_order_cycle, self).create(cr, uid, vals, context=context)
408
 
        
409
 
        product_obj = self.pool.get('product.product')
410
 
        product_id = vals.get('product_id', False)
411
 
        if product_id:
412
 
            if product_obj.browse(cr, uid, product_id, context=context).short_shelf_life:
413
 
                self.log(cr, uid, new_id, _(SHORT_SHELF_LIFE_MESS))
414
 
                
415
 
        return new_id
416
 
    
417
 
    def write(self, cr, uid, ids, vals, context=None):
418
 
        '''
419
 
        add message
420
 
        '''
421
 
        if context is None:
422
 
            context = {}
423
 
            
424
 
        result = super(stock_warehouse_order_cycle, self).write(cr, uid, ids, vals, context=context)
425
 
        
426
 
        if isinstance(ids, (int, long)):
427
 
            ids = [ids]
428
 
        
429
 
        product_obj = self.pool.get('product.product')
430
 
        product_id = vals.get('product_id', False)
431
 
        if product_id:
432
 
            if product_obj.browse(cr, uid, product_id, context=context).short_shelf_life:
433
 
                for obj in self.browse(cr, uid, ids, context=context):
434
 
                    self.log(cr, uid, obj.id, _(SHORT_SHELF_LIFE_MESS))
435
 
        
436
 
        return result
437
 
    
438
 
stock_warehouse_order_cycle()
439
 
 
440
 
 
441
 
class stock_picking(osv.osv):
442
 
    '''
443
 
    modify hook function
444
 
    '''
445
 
    _inherit = 'stock.picking'
446
 
    
447
 
    def _do_partial_hook(self, cr, uid, ids, context, *args, **kwargs):
448
 
        '''
449
 
        hook to update defaults data
450
 
        '''
451
 
        # variable parameters
452
 
        move = kwargs.get('move')
453
 
        assert move, 'missing move'
454
 
        partial_datas = kwargs.get('partial_datas')
455
 
        assert partial_datas, 'missing partial_datas'
456
 
        
457
 
        # calling super method
458
 
        defaults = super(stock_picking, self)._do_partial_hook(cr, uid, ids, context, *args, **kwargs)
459
 
        assetId = partial_datas.get('move%s'%(move.id), {}).get('asset_id')
460
 
        if assetId:
461
 
            defaults.update({'asset_id': assetId})
462
 
        
463
 
        return defaults
464
 
    
465
 
    _columns = {}
466
 
    
467
 
stock_picking()
468
 
 
469
 
 
470
 
class stock_move(osv.osv):
471
 
    '''
472
 
    add kc/dg
473
 
    '''
474
 
    _inherit = 'stock.move'
475
 
    
476
 
    def create(self, cr, uid, vals, context=None):
477
 
        '''
478
 
        complete info normally generated by javascript on_change function
479
 
        '''
480
 
        id_cross = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_cross_docking', 'stock_location_cross_docking')[1]
481
 
        prod_obj = self.pool.get('product.product')
482
 
        if vals.get('product_id', False):
483
 
            # complete hidden flags - needed if not created from GUI
484
 
            product = prod_obj.browse(cr, uid, vals.get('product_id'), context=context)
485
 
 
486
 
            if vals.get('picking_id') and product.type == 'consu' and vals.get('location_dest_id') != id_cross:
487
 
                pick_bro = self.pool.get('stock.picking').browse(cr, uid, vals.get('picking_id'))
488
 
                id_nonstock = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock_override', 'stock_location_non_stockable')
489
 
                if vals.get('sale_line_id'):
490
 
                    id_pack = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_outgoing', 'stock_location_packing')
491
 
                    if pick_bro.type == 'out':
492
 
                        vals.update(location_id=id_cross)
493
 
                    else:
494
 
                        vals.update(location_id=id_nonstock[1])
495
 
                    vals.update(location_dest_id=id_pack[1])
496
 
                else:
497
 
                    if pick_bro.type != 'out':
498
 
                        vals.update(location_dest_id=id_nonstock[1])
499
 
 
500
 
            if product.batch_management:
501
 
                vals.update(hidden_batch_management_mandatory=True)
502
 
            elif product.perishable:
503
 
                vals.update(hidden_perishable_mandatory=True)
504
 
            else:
505
 
                vals.update(hidden_batch_management_mandatory=False,
506
 
                            hidden_perishable_mandatory=False,
507
 
                            )
508
 
        # call super
509
 
        result = super(stock_move, self).create(cr, uid, vals, context=context)
510
 
        return result
511
 
    
512
 
    def write(self, cr, uid, ids, vals, context=None):
513
 
        '''
514
 
        complete info normally generated by javascript on_change function
515
 
        '''
516
 
        id_cross = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_cross_docking', 'stock_location_cross_docking')[1]
517
 
        prod_obj = self.pool.get('product.product')
518
 
 
519
 
        if vals.get('product_id', False):
520
 
            # complete hidden flags - needed if not created from GUI
521
 
            product = prod_obj.browse(cr, uid, vals.get('product_id'), context=context)
522
 
 
523
 
            if vals.get('picking_id') and product.type == 'consu' and vals.get('location_dest_id') != id_cross:
524
 
                pick_bro = self.pool.get('stock.picking').browse(cr, uid, vals.get('picking_id'))
525
 
                id_nonstock = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock_override', 'stock_location_non_stockable')
526
 
                if vals.get('sale_line_id'):
527
 
                    id_pack = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_outgoing', 'stock_location_packing')
528
 
                    if pick_bro.type == 'out':
529
 
                        vals.update(location_id=id_cross)
530
 
                    else:
531
 
                        vals.update(location_id=id_nonstock[1])
532
 
                    vals.update(location_dest_id=id_pack[1])
533
 
                else:
534
 
                    if pick_bro.type != 'out':
535
 
                        vals.update(location_dest_id=id_nonstock[1])
536
 
 
537
 
            if product.batch_management:
538
 
                vals.update(hidden_batch_management_mandatory=True)
539
 
            elif product.perishable:
540
 
                vals.update(hidden_perishable_mandatory=True)
541
 
            else:
542
 
                vals.update(hidden_batch_management_mandatory=False,
543
 
                            hidden_perishable_mandatory=False,
544
 
                            )
545
 
        # call super
546
 
        result = super(stock_move, self).write(cr, uid, ids, vals, context=context)
547
 
        return result
548
 
 
549
 
    def _kc_dg(self, cr, uid, ids, name, arg, context=None):
550
 
        '''
551
 
        return 'KC' if cold chain or 'DG' if dangerous goods
552
 
        '''
553
 
        result = {}
554
 
        for id in ids:
555
 
            result[id] = ''
556
 
            
557
 
        for move in self.browse(cr, uid, ids, context=context):
558
 
            if move.product_id:
559
 
                if move.product_id.heat_sensitive_item:
560
 
                    result[move.id] = 'KC'
561
 
                elif move.product_id.dangerous_goods:
562
 
                    result[move.id] = 'DG'
563
 
        
564
 
        return result
565
 
    
566
 
    def _check_batch_management(self, cr, uid, ids, context=None):
567
 
        """
568
 
        check for batch management
569
 
        @return: True or False
570
 
        """
571
 
        for move in self.browse(cr, uid, ids, context=context):
572
 
            if move.state == 'done' and move.location_id.id != move.location_dest_id.id:
573
 
                if move.product_id.batch_management:
574
 
                    if not move.prodlot_id and move.product_qty:
575
 
                        raise osv.except_osv(_('Error!'),  _('You must assign a Batch Number for this product (Batch Number Mandatory).'))
576
 
        return True
577
 
    
578
 
    def _check_perishable(self, cr, uid, ids, context=None):
579
 
        """
580
 
        check for perishable
581
 
        @return: True or False
582
 
        """
583
 
        for move in self.browse(cr, uid, ids, context=context):
584
 
            if move.state == 'done' and move.location_id.id != move.location_dest_id.id:
585
 
                if move.product_id.perishable:
586
 
                    if not move.prodlot_id and move.product_qty:
587
 
                        raise osv.except_osv(_('Error!'),  _('You must assign an Expiry Date for this product (Expiry Date Mandatory).'))
588
 
        return True
589
 
    
590
 
    def _check_prodlot_need(self, cr, uid, ids, context=None):
591
 
        """
592
 
        If the move has a prodlot but does not need one, return False.
593
 
        """
594
 
        for move in self.browse(cr, uid, ids, context=context):
595
 
            if move.prodlot_id:
596
 
                if not move.product_id.perishable and not move.product_id.batch_management:
597
 
                    raise osv.except_osv(_('Error!'),  _('The selected product is neither Batch Number Mandatory nor Expiry Date Mandatory.'))
598
 
        return True
599
 
    
600
 
    def _check_prodlot_need_batch_management(self, cr, uid, ids, context=None):
601
 
        """
602
 
        If the product is batch management while the selected prodlot is 'internal'.
603
 
        """
604
 
        for move in self.browse(cr, uid, ids, context=context):
605
 
            if move.prodlot_id:
606
 
                if move.prodlot_id.type == 'internal' and move.product_id.batch_management:
607
 
                    raise osv.except_osv(_('Error!'),  _('The selected product is Batch Number Mandatory while the selected Batch number corresponds to Expiry Date Mandatory.'))
608
 
        return True
609
 
    
610
 
    def _check_prodlot_need_perishable(self, cr, uid, ids, context=None):
611
 
        """
612
 
        If the product is perishable ONLY while the selected prodlot is 'standard'.
613
 
        """
614
 
        for move in self.browse(cr, uid, ids, context=context):
615
 
            if move.prodlot_id:
616
 
                if move.prodlot_id.type == 'standard' and not move.product_id.batch_management and move.product_id.perishable:
617
 
                    raise osv.except_osv(_('Error!'),  _('The selected product is Expiry Date Mandatory while the selected Batch number corresponds to Batch Number Mandatory.'))
618
 
        return True
619
 
    
620
 
    def onchange_product_id(self, cr, uid, ids, prod_id=False, loc_id=False, loc_dest_id=False, address_id=False, parent_type=False, purchase_line_id=False, out=False,):
621
 
        '''
622
 
        the product changes, set the hidden flag if necessary
623
 
        '''
624
 
        result = super(stock_move, self).onchange_product_id(cr, uid, ids, prod_id, loc_id,
625
 
                                                             loc_dest_id, address_id)
626
 
 
627
 
        # product changes, prodlot is always cleared
628
 
        result.setdefault('value', {})['prodlot_id'] = False
629
 
        # reset the hidden flag
630
 
        result.setdefault('value', {})['hidden_batch_management_mandatory'] = False
631
 
        result.setdefault('value', {})['hidden_perishable_mandatory'] = False
632
 
        if prod_id:
633
 
            product = self.pool.get('product.product').browse(cr, uid, prod_id)
634
 
 
635
 
            if product.batch_management:
636
 
                result.setdefault('value', {})['hidden_batch_management_mandatory'] = True
637
 
                result['warning'] = {'title': _('Info'),
638
 
                                     'message': _('The selected product is Batch Management.')}
639
 
 
640
 
            elif product.perishable:
641
 
                result.setdefault('value', {})['hidden_perishable_mandatory'] = True
642
 
                result['warning'] = {'title': _('Info'),
643
 
                                     'message': _('The selected product is Perishable.')}
644
 
        # quantities are set to False
645
 
        result.setdefault('value', {}).update({'product_qty': 0.00,
646
 
                                               'product_uos_qty': 0.00,
647
 
                                               })
648
 
            
649
 
        return result
650
 
    
651
 
    def _get_checks_all(self, cr, uid, ids, name, arg, context=None):
652
 
        '''
653
 
        function for KC/SSL/DG/NP products
654
 
        '''
655
 
        # objects
656
 
        kit_obj = self.pool.get('composition.kit')
657
 
        result = {}
658
 
        for id in ids:
659
 
            result[id] = {}
660
 
            for f in name:
661
 
                result[id].update({f: False})
662
 
        
663
 
        for obj in self.browse(cr, uid, ids, context=context):
664
 
            # keep cool
665
 
            if obj.product_id.heat_sensitive_item:
666
 
                result[obj.id]['kc_check'] = True
667
 
            # ssl
668
 
            if obj.product_id.short_shelf_life:
669
 
                result[obj.id]['ssl_check'] = True
670
 
            # dangerous goods
671
 
            if obj.product_id.dangerous_goods:
672
 
                result[obj.id]['dg_check'] = True
673
 
            # narcotic
674
 
            if obj.product_id.narcotic:
675
 
                result[obj.id]['np_check'] = True
676
 
            # lot management
677
 
            if obj.product_id.batch_management:
678
 
                result[obj.id]['lot_check'] = True
679
 
            # expiry date management
680
 
            if obj.product_id.perishable:
681
 
                result[obj.id]['exp_check'] = True
682
 
            # contains a kit and allow the creation of a new composition LIst
683
 
            # will be false if the kit is batch management and a composition list already uses this batch number
684
 
            # only one composition list can  use a given batch number for a given product
685
 
            if obj.product_id.type == 'product' and obj.product_id.subtype == 'kit':
686
 
                if obj.prodlot_id:
687
 
                    # search if composition list already use this batch number
688
 
                    kit_ids = kit_obj.search(cr, uid, [('composition_lot_id', '=', obj.prodlot_id.id)], context=context)
689
 
                    if not kit_ids:
690
 
                        result[obj.id]['kit_check'] = True
691
 
                else:
692
 
                    # not batch management, we can create as many composition list as we want
693
 
                    result[obj.id]['kit_check'] = True
694
 
            
695
 
        return result
696
 
    
697
 
    def _check_tracking(self, cr, uid, ids, context=None):
698
 
        """ Checks if production lot is assigned to stock move or not.
699
 
        @return: True or False
700
 
        """
701
 
        for move in self.browse(cr, uid, ids, context=context):
702
 
            if not move.prodlot_id and move.product_qty and \
703
 
               (move.state == 'done' and \
704
 
               ( \
705
 
                   (move.product_id.track_production and move.location_id.usage == 'production') or \
706
 
                   (move.product_id.track_production and move.location_dest_id.usage == 'production') or \
707
 
                   (move.product_id.track_incoming and move.location_id.usage == 'supplier') or \
708
 
                   (move.product_id.track_outgoing and move.location_dest_id.usage == 'customer') \
709
 
               )):
710
 
                raise osv.except_osv(_('Error!'),  _('You must assign a batch number for this product.'))
711
 
        return True
712
 
            
713
 
    _columns = {
714
 
        'kc_dg': fields.function(_kc_dg, method=True, string='KC/DG', type='char'),
715
 
        # if prodlot needs to be mandatory, add 'required': ['|', ('hidden_batch_management_mandatory','=',True), ('hidden_perishable_mandatory','=',True)] in attrs
716
 
        'hidden_batch_management_mandatory': fields.boolean(string='Hidden Flag for Batch Management product',),
717
 
        'hidden_perishable_mandatory': fields.boolean(string='Hidden Flag for Perishable product',),
718
 
        'kc_check': fields.function(_get_checks_all, method=True, string='KC', type='boolean', readonly=True, multi="m"),
719
 
        'ssl_check': fields.function(_get_checks_all, method=True, string='SSL', type='boolean', readonly=True, multi="m"),
720
 
        'dg_check': fields.function(_get_checks_all, method=True, string='DG', type='boolean', readonly=True, multi="m"),
721
 
        'np_check': fields.function(_get_checks_all, method=True, string='NP', type='boolean', readonly=True, multi="m"),
722
 
        'lot_check': fields.function(_get_checks_all, method=True, string='B.Num', type='boolean', readonly=True, multi="m"),
723
 
        'exp_check': fields.function(_get_checks_all, method=True, string='Exp', type='boolean', readonly=True, multi="m"),
724
 
        'kit_check': fields.function(_get_checks_all, method=True, string='Kit', type='boolean', readonly=True, multi="m"),
725
 
        'prodlot_id': fields.many2one('stock.production.lot', 'Batch', states={'done': [('readonly', True)]}, help="Batch number is used to put a serial number on the production", select=True),
726
 
    }
727
 
    
728
 
    _constraints = [(_check_batch_management,
729
 
                     'You must assign a Batch Number for this product (Batch Number Mandatory).',
730
 
                     ['prodlot_id']),
731
 
                    (_check_perishable,
732
 
                     'You must assign an Expiry Date for this product (Expiry Date Mandatory).',
733
 
                     ['prodlot_id']),
734
 
                    (_check_prodlot_need,
735
 
                     'The selected product is neither Batch Number Mandatory nor Expiry Date Mandatory.',
736
 
                     ['prodlot_id']),
737
 
                    (_check_prodlot_need_batch_management,
738
 
                     'The selected product is Batch Number Mandatory while the selected Batch number corresponds to Expiry Date Mandatory.',
739
 
                     ['prodlot_id']),
740
 
                    (_check_prodlot_need_perishable,
741
 
                     'The selected product is Expiry Date Mandatory while the selected Batch number corresponds to Batch Number Mandatory.',
742
 
                     ['prodlot_id']),
743
 
                     (_check_tracking,
744
 
                      'You must assign a batch number for this product.',
745
 
                      ['prodlot_id']),
746
 
                    ]
747
 
 
748
 
stock_move()
749
 
 
750
 
 
751
 
class stock_production_lot(osv.osv):
752
 
    '''
753
 
    productin lot modifications
754
 
    '''
755
 
    _inherit = 'stock.production.lot'
756
 
    
757
 
    def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
758
 
        """
759
 
        Correct fields in order to have those from account_statement_from_invoice_lines (in case where account_statement_from_invoice is used)
760
 
        """
761
 
        if context is None:
762
 
            context = {}
763
 
        
764
 
        # warehouse wizards or inventory screen
765
 
        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', False)):
766
 
            view = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'specific_rules', 'view_production_lot_expiry_date_tree')
767
 
            if view:
768
 
                view_id = view[1]
769
 
        result = super(stock_production_lot, self).fields_view_get(cr, uid, view_id, view_type, context=context, toolbar=toolbar, submenu=submenu)
770
 
        return result
771
 
    
772
 
    def copy(self, cr, uid, id, default=None, context=None):
773
 
        '''
774
 
        increase the batch number
775
 
        create a new sequence
776
 
        '''
777
 
        if default is None:
778
 
            default = {}
779
 
            
780
 
        # original reference
781
 
        lot_name = self.read(cr, uid, id, ['name'])['name']
782
 
        default.update(name='%s (copy)'%lot_name, date=time.strftime('%Y-%m-%d'))
783
 
        
784
 
        return super(stock_production_lot, self).copy(cr, uid, id, default, context=context)
785
 
    
786
 
    def copy_data(self, cr, uid, id, default=None, context=None):
787
 
        '''
788
 
        clear the revisions
789
 
        '''
790
 
        if default is None:
791
 
            default = {}
792
 
        default.update(revisions=[])
793
 
        return super(stock_production_lot, self).copy_data(cr, uid, id, default, context=context)
794
 
    
795
 
    def create_sequence(self, cr, uid, vals, context=None):
796
 
        """
797
 
        Create new entry sequence for every new order
798
 
        @param cr: cursor to database
799
 
        @param user: id of current user
800
 
        @param ids: list of record ids to be process
801
 
        @param context: context arguments, like lang, time zone
802
 
        @return: return a result
803
 
        """
804
 
        seq_pool = self.pool.get('ir.sequence')
805
 
        seq_typ_pool = self.pool.get('ir.sequence.type')
806
 
 
807
 
        name = 'Batch number'
808
 
        code = 'stock.production.lot'
809
 
 
810
 
        types = {
811
 
            'name': name,
812
 
            'code': code
813
 
        }
814
 
        seq_typ_pool.create(cr, uid, types)
815
 
 
816
 
        seq = {
817
 
            'name': name,
818
 
            'code': code,
819
 
            'prefix': '',
820
 
            'padding': 0,
821
 
        }
822
 
        return seq_pool.create(cr, uid, seq)
823
 
    
824
 
    def create(self, cr, uid, vals, context=None):
825
 
        '''
826
 
        create the sequence for the version management
827
 
        '''
828
 
        if context is None:
829
 
            context = {}
830
 
            
831
 
        sequence = self.create_sequence(cr, uid, vals, context=context)
832
 
        vals.update({'sequence_id': sequence,})
833
 
        
834
 
        if context.get('update_mode') in ['init', 'update']:
835
 
            if not vals.get('life_date'):
836
 
                # default value to today
837
 
                vals.update(life_date=time.strftime('%Y-%m-%d'))
838
 
        
839
 
        return super(stock_production_lot, self).create(cr, uid, vals, context=context)
840
 
    
841
 
    def write(self, cr, uid, ids, vals, context=None):
842
 
        '''
843
 
        update the sequence for the version management
844
 
        '''
845
 
        if isinstance(ids, (int, long)):
846
 
            ids = [ids]
847
 
        
848
 
        revision_obj = self.pool.get('stock.production.lot.revision')
849
 
        
850
 
        for lot in self.browse(cr, uid, ids, context=context):
851
 
           # create revision object for each lot
852
 
           version_number = lot.sequence_id.get_id(test='id', context=context)
853
 
           values = {'name': 'Auto Revision Logging',
854
 
                     'description': 'The batch number has been modified, this revision log has been created automatically.',
855
 
                     'date': time.strftime('%Y-%m-%d'),
856
 
                     'indice': version_number,
857
 
                     'author_id': uid,
858
 
                     'lot_id': lot.id,}
859
 
           revision_obj.create(cr, uid, values, context=context)
860
 
        
861
 
        return super(stock_production_lot, self).write(cr, uid, ids, vals, context=context)
862
 
    
863
 
    def remove_flag(self, flag, _list):
864
 
        '''
865
 
        if we do not remove the flag, we fall into an infinite loop
866
 
        '''
867
 
        args2 = []
868
 
        for arg in _list:
869
 
            if arg[0] != flag:
870
 
                args2.append(arg)
871
 
        return args2
872
 
    
873
 
    def search_check_type(self, cr, uid, obj, name, args, context=None):
874
 
        '''
875
 
        modify the query to take the type of prodlot into account according to product's attributes
876
 
        'Batch Number mandatory' and 'Expiry Date Mandatory'
877
 
        
878
 
        if batch management: display only 'standard' lot
879
 
        if expiry and not batch management: display only 'internal' lot
880
 
        else: display normally
881
 
        '''
882
 
        product_obj = self.pool.get('product.product')
883
 
        product_id = context.get('product_id', False)
884
 
        
885
 
        # remove flag avoid infinite loop
886
 
        args = self.remove_flag('check_type', args)
887
 
            
888
 
        if not product_id:
889
 
            return args
890
 
        
891
 
        # check the product
892
 
        product = product_obj.browse(cr, uid, product_id, context=context)
893
 
 
894
 
        if product.batch_management:
895
 
            # standard lots
896
 
            args.append(('type', '=', 'standard'))
897
 
        elif product.perishable:
898
 
            # internal lots
899
 
            args.append(('type', '=', 'internal'))
900
 
            
901
 
        return args
902
 
    
903
 
    def _get_false(self, cr, uid, ids, field_name, arg, context=None):
904
 
        '''
905
 
        return false for each id
906
 
        '''
907
 
        if isinstance(ids,(long, int)):
908
 
           ids = [ids]
909
 
        
910
 
        result = {}
911
 
        for id in ids:
912
 
          result[id] = False
913
 
        return result
914
 
 
915
 
    def _stock_search_virtual(self, cr, uid, obj, name, args, context=None):
916
 
        """ Searches Ids of products
917
 
        @return: Ids of locations
918
 
        """
919
 
        if context is None:
920
 
            context = {}
921
 
        # when the location_id = False results now in showing stock for all internal locations
922
 
        # *previously*, was showing the location of no location (= 0.0 for all prodlot)
923
 
        if 'location_id' not in context or not context['location_id']:
924
 
            locations = self.pool.get('stock.location').search(cr, uid, [('usage', '=', 'internal')], context=context)
925
 
        else:
926
 
            locations = context['location_id'] and [context['location_id']] or []
927
 
        
928
 
        ids = [('id', 'in', [])]
929
 
        if locations:
930
 
            cr.execute('''select
931
 
                    prodlot_id,
932
 
                    sum(qty)
933
 
                from
934
 
                    stock_report_prodlots_virtual
935
 
                where
936
 
                    location_id IN %s group by prodlot_id
937
 
                having  sum(qty) '''+ str(args[0][1]) + str(args[0][2]),(tuple(locations),))
938
 
            res = cr.fetchall()
939
 
            ids = [('id', 'in', map(lambda x: x[0], res))]
940
 
        return ids
941
 
    
942
 
    def _stock_search(self, cr, uid, obj, name, args, context=None):
943
 
        '''
944
 
        call super method, as fields.function does not work with inheritance
945
 
        '''
946
 
        return super(stock_production_lot, self)._stock_search(cr, uid, obj, name, args, context=context)
947
 
 
948
 
    def _get_stock_virtual(self, cr, uid, ids, field_name, arg, context=None):
949
 
        """ Gets stock of products for locations
950
 
        @return: Dictionary of values
951
 
        """
952
 
        if context is None:
953
 
            context = {}
954
 
        # when the location_id = False results now in showing stock for all internal locations
955
 
        # *previously*, was showing the location of no location (= 0.0 for all prodlot)
956
 
        if 'location_id' not in context or not context['location_id']:
957
 
            locations = self.pool.get('stock.location').search(cr, uid, [('usage', '=', 'internal')], context=context)
958
 
        else:
959
 
            locations = context['location_id'] and [context['location_id']] or []
960
 
 
961
 
        if isinstance(ids, (int, long)):
962
 
            ids = [ids]
963
 
 
964
 
        res = {}.fromkeys(ids, 0.0)
965
 
        if locations:
966
 
            cr.execute('''select
967
 
                    prodlot_id,
968
 
                    sum(qty)
969
 
                from
970
 
                    stock_report_prodlots_virtual
971
 
                where
972
 
                    location_id IN %s and prodlot_id IN %s group by prodlot_id''',(tuple(locations),tuple(ids),))
973
 
            res.update(dict(cr.fetchall()))
974
 
 
975
 
        return res
976
 
    
977
 
    def _get_stock(self, cr, uid, ids, field_name, arg, context=None):
978
 
        '''
979
 
        call super method, as fields.function does not work with inheritance
980
 
        '''
981
 
        return super(stock_production_lot, self)._get_stock(cr, uid, ids, field_name, arg, context=context)
982
 
    
983
 
    def _get_checks_all(self, cr, uid, ids, name, arg, context=None):
984
 
        '''
985
 
        function for KC/SSL/DG/NP products
986
 
        '''
987
 
        result = {}
988
 
        for id in ids:
989
 
            result[id] = {}
990
 
            for f in name:
991
 
                result[id].update({f: False})
992
 
            
993
 
        for obj in self.browse(cr, uid, ids, context=context):
994
 
            # keep cool
995
 
            if obj.product_id.heat_sensitive_item:
996
 
                result[obj.id]['kc_check'] = True
997
 
            # ssl
998
 
            if obj.product_id.short_shelf_life:
999
 
                result[obj.id]['ssl_check'] = True
1000
 
            # dangerous goods
1001
 
            if obj.product_id.dangerous_goods:
1002
 
                result[obj.id]['dg_check'] = True
1003
 
            # narcotic
1004
 
            if obj.product_id.narcotic:
1005
 
                result[obj.id]['np_check'] = True
1006
 
            # lot management
1007
 
            if obj.product_id.batch_management:
1008
 
                result[obj.id]['lot_check'] = True
1009
 
            # expiry date management
1010
 
            if obj.product_id.perishable:
1011
 
                result[obj.id]['exp_check'] = True
1012
 
            
1013
 
        return result
1014
 
 
1015
 
    def _check_batch_type_integrity(self, cr, uid, ids, context=None):
1016
 
        '''
1017
 
        Check if the type of the batch is consistent with the product attributes
1018
 
        '''
1019
 
        for obj in self.browse(cr, uid, ids, context=context):
1020
 
            if obj.type == 'standard' and not obj.product_id.batch_management:
1021
 
                return False
1022
 
 
1023
 
        return True
1024
 
 
1025
 
    def _check_perishable_type_integrity(self, cr, uid, ids, context=None):
1026
 
        '''
1027
 
        Check if the type of the batch is consistent with the product attributes
1028
 
        '''
1029
 
        for obj in self.browse(cr, uid, ids, context=context):
1030
 
            if obj.type == 'internal' and (obj.product_id.batch_management or not obj.product_id.perishable):
1031
 
                return False
1032
 
 
1033
 
        return True
1034
 
 
1035
 
    def _get_delete_ok(self, cr, uid, ids, field_name, args, context=None):
1036
 
        '''
1037
 
        Returns if the batch is deletable
1038
 
        '''
1039
 
        res = {}
1040
 
        for batch_id in ids:
1041
 
            res[batch_id] = True
1042
 
            move_ids = self.pool.get('stock.move').search(cr, uid, [('prodlot_id', '=', batch_id)], context=context)
1043
 
            if move_ids:
1044
 
                res[batch_id] = False
1045
 
 
1046
 
        return res
1047
 
    
1048
 
    _columns = {'check_type': fields.function(_get_false, fnct_search=search_check_type, string='Check Type', type="boolean", readonly=True, method=True),
1049
 
                # readonly is True, the user is only allowed to create standard lots - internal lots are system-created
1050
 
                'type': fields.selection([('standard', 'Standard'),('internal', 'Internal'),], string="Type", readonly=True),
1051
 
                #'expiry_date': fields.date('Expiry Date'),
1052
 
                'name': fields.char('Batch Number', size=1024, required=True, help="Unique batch number, will be displayed as: PREFIX/SERIAL [INT_REF]"),
1053
 
                'date': fields.datetime('Auto Creation Date', required=True),
1054
 
                'sequence_id': fields.many2one('ir.sequence', 'Batch Sequence', required=True,),
1055
 
                'stock_virtual': fields.function(_get_stock_virtual, method=True, type="float", string="Available Stock", select=True,
1056
 
                                                 help="Current available quantity of products with this Batch Numbre Number in company warehouses",
1057
 
                                                 digits_compute=dp.get_precision('Product UoM'), readonly=True,
1058
 
                                                 fnct_search=_stock_search_virtual,),
1059
 
                'stock_available': fields.function(_get_stock, fnct_search=_stock_search, method=True, type="float", string="Real Stock", select=True,
1060
 
                                                   help="Current real quantity of products with this Batch Number in company warehouses",
1061
 
                                                   digits_compute=dp.get_precision('Product UoM')),
1062
 
                'kc_check': fields.function(_get_checks_all, method=True, string='KC', type='boolean', readonly=True, multi="m"),
1063
 
                'ssl_check': fields.function(_get_checks_all, method=True, string='SSL', type='boolean', readonly=True, multi="m"),
1064
 
                'dg_check': fields.function(_get_checks_all, method=True, string='DG', type='boolean', readonly=True, multi="m"),
1065
 
                'np_check': fields.function(_get_checks_all, method=True, string='NP', type='boolean', readonly=True, multi="m"),
1066
 
                'lot_check': fields.function(_get_checks_all, method=True, string='B.Num', type='boolean', readonly=True, multi="m"),
1067
 
                'exp_check': fields.function(_get_checks_all, method=True, string='Exp', type='boolean', readonly=True, multi="m"),
1068
 
                'delete_ok': fields.function(_get_delete_ok, method=True, string='Possible deletion ?', type='boolean', readonly=True),
1069
 
                }
1070
 
    
1071
 
    _defaults = {'type': 'standard',
1072
 
                 'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'stock.production.lot', context=c),
1073
 
                 'name': False,
1074
 
                 'life_date': False,
1075
 
                 }
1076
 
    
1077
 
    _sql_constraints = [('name_uniq', 'unique (product_id,name)', 'For a given product, the batch number must be unique.'),
1078
 
                        ]
1079
 
 
1080
 
    _constraints = [(_check_batch_type_integrity,
1081
 
                    'You can\'t create a standard batch number for a product which is not batch mandatory. If the product is perishable, the system will create automatically an internal batch number on reception/inventory.',
1082
 
                    ['Type', 'Product']),
1083
 
                    (_check_perishable_type_integrity,
1084
 
                    'You can\'t create an internal Batch Number for a product which is batch managed or which is not perishable. If the product is batch managed, please create a standard batch number.',
1085
 
                    ['Type', 'Product']),
1086
 
                ]
1087
 
 
1088
 
    def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
1089
 
        '''
1090
 
        search function of production lot
1091
 
        '''
1092
 
        result = super(stock_production_lot, self).search(cr, uid, args=args, offset=offset, limit=limit, order=order, context=context, count=count)
1093
 
        
1094
 
        return result
1095
 
    
1096
 
    def name_get(self, cr, uid, ids, context=None):
1097
 
        if not ids:
1098
 
            return []
1099
 
        if context is None:
1100
 
            context = {}
1101
 
 
1102
 
        reads = self.read(cr, uid, ids, ['name', 'prefix', 'ref', 'life_date'], context)
1103
 
        res = []
1104
 
# TODO replace by _get_format in uf-651
1105
 
        if context.get('with_expiry'):
1106
 
            user_obj = self.pool.get('res.users')
1107
 
            lang_obj = self.pool.get('res.lang')
1108
 
            user_lang = user_obj.read(cr, uid, uid, ['context_lang'], context=context)['context_lang']
1109
 
            lang_id = lang_obj.search(cr, uid, [('code','=',user_lang)])
1110
 
            date_format = lang_id and lang_obj.read(cr, uid, lang_id[0], ['date_format'], context=context)['date_format'] or '%m/%d/%Y'
1111
 
 
1112
 
        for record in reads:
1113
 
            if context.get('with_expiry') and record['life_date']:
1114
 
                name = '%s - %s'%(record['name'], DateTime.strptime(record['life_date'],'%Y-%m-%d').strftime(date_format).decode('utf-8'))
1115
 
            else:
1116
 
                name = record['name']
1117
 
            res.append((record['id'], name))
1118
 
        return res
1119
 
 
1120
 
    def unlink(self, cr, uid, ids, context=None):
1121
 
        '''
1122
 
        Remove the batch
1123
 
        '''
1124
 
        for batch in self.browse(cr, uid, ids, context=context):
1125
 
            if not batch.delete_ok:
1126
 
                raise osv.except_osv(_('Error'), _('You cannot remove a batch number which has stock !'))
1127
 
 
1128
 
        return super(stock_production_lot, self).unlink(cr, uid, batch.id, context=context)
1129
 
 
1130
 
 
1131
 
stock_production_lot()
1132
 
 
1133
 
 
1134
 
class stock_location(osv.osv):
1135
 
    '''
1136
 
    override stock location to add:
1137
 
    - stock_real
1138
 
    - stock_virtual
1139
 
    '''
1140
 
    _inherit = 'stock.location'
1141
 
    
1142
 
    def replace_field_key(self, fieldsDic, search, replace):
1143
 
        '''
1144
 
        will replace 'stock_real' by 'stock_real_specific'
1145
 
        and 'stock_virtual' by 'stock_virtual_specific'
1146
 
        
1147
 
        and return a new dictionary
1148
 
        '''
1149
 
        return dict((replace if key == search else key, (self.replace_field_key(value, search, replace) if isinstance(value, dict) else value)) for key, value in fieldsDic.items())
1150
 
    
1151
 
    def _product_value_specific_rules(self, cr, uid, ids, field_names, arg, context=None):
1152
 
        '''
1153
 
        add two fields for custom stock computation, if no product selected, both stock are set to 0.0
1154
 
        '''
1155
 
        if context is None:
1156
 
            context = {}
1157
 
        # initialize data
1158
 
        result = {}
1159
 
        for id in ids:
1160
 
            result[id] = {}
1161
 
            for f in field_names:
1162
 
                result[id].update({f: False,})
1163
 
        # if product is set to False, it does not make sense to return a stock value, return False for each location
1164
 
        if 'product_id' in context and not context['product_id']:
1165
 
            return result
1166
 
        
1167
 
        result = super(stock_location, self)._product_value(cr, uid, ids, ['stock_real', 'stock_virtual'], arg, context=context)
1168
 
        # replace stock real
1169
 
        result = self.replace_field_key(result, 'stock_real', 'stock_real_specific')
1170
 
        # replace stock virtual
1171
 
        result = self.replace_field_key(result, 'stock_virtual', 'stock_virtual_specific')
1172
 
        return result
1173
 
    
1174
 
    def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
1175
 
        """
1176
 
        display the modified stock values (stock_real_specific, stock_virtual_specific) if needed
1177
 
        """
1178
 
        if context is None:
1179
 
            context = {}
1180
 
        # warehouse wizards or inventory screen
1181
 
        if view_type == 'tree' and context.get('specific_rules_tree_view', False):
1182
 
            view = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'specific_rules', 'view_location_tree2')
1183
 
            if view:
1184
 
                view_id = view[1]
1185
 
        result = super(osv.osv, self).fields_view_get(cr, uid, view_id, view_type, context=context, toolbar=toolbar, submenu=submenu)
1186
 
        return result
1187
 
    
1188
 
    _columns = {'stock_real_specific': fields.function(_product_value_specific_rules, method=True, type='float', string='Real Stock', multi="get_vals_specific_rules"),
1189
 
                'stock_virtual_specific': fields.function(_product_value_specific_rules, method=True, type='float', string='Virtual Stock', multi="get_vals_specific_rules"),
1190
 
                }
1191
 
    
1192
 
stock_location()
1193
 
 
1194
 
 
1195
 
class stock_production_lot_revision(osv.osv):
1196
 
    _inherit = 'stock.production.lot.revision'
1197
 
    _order = 'indice desc'
1198
 
    
1199
 
stock_production_lot_revision()
1200
 
 
1201
 
 
1202
 
class stock_inventory(osv.osv):
1203
 
    '''
1204
 
    override the action_confirm to create the production lot if needed
1205
 
    '''
1206
 
    _inherit = 'stock.inventory'
1207
 
 
1208
 
    def _check_line_data(self, cr, uid, ids, context=None):
1209
 
        for inv in self.browse(cr, uid, ids, context=context):
1210
 
            if inv.state not in ('draft', 'cancel'):
1211
 
                for line in inv.inventory_line_id:
1212
 
                    if line.product_qty != 0.00 and not line.location_id:
1213
 
                        return False
1214
 
 
1215
 
        return True
1216
 
 
1217
 
    def copy(self, cr, uid, inventory_id, defaults, context=None):
1218
 
        '''
1219
 
        Set the creation date of the document to the current date
1220
 
        '''
1221
 
        if not defaults:
1222
 
            defaults = {}
1223
 
 
1224
 
        defaults.update({'date': time.strftime('%Y-%m-%d %H:%M:%S'), 'move_ids': False})
1225
 
        return super(stock_inventory, self).copy(cr, uid, inventory_id, defaults, context=context)
1226
 
        
1227
 
    _columns = {
1228
 
        'sublist_id': fields.many2one('product.list', string='List/Sublist'),
1229
 
        'nomen_manda_0': fields.many2one('product.nomenclature', 'Main Type'),
1230
 
        'nomen_manda_1': fields.many2one('product.nomenclature', 'Group'),
1231
 
        'nomen_manda_2': fields.many2one('product.nomenclature', 'Family'),
1232
 
        'nomen_manda_3': fields.many2one('product.nomenclature', 'Root'),
1233
 
    }
1234
 
 
1235
 
    _constraints = [
1236
 
        (_check_line_data, "You must define a stock location for each line", ['state']),
1237
 
    ]
1238
 
    
1239
 
    def onChangeSearchNomenclature(self, cr, uid, ids, position, n_type, nomen_manda_0, nomen_manda_1, nomen_manda_2, nomen_manda_3, num=True, context=None):
1240
 
        return self.pool.get('product.product').onChangeSearchNomenclature(cr, uid, 0, position, n_type, nomen_manda_0, nomen_manda_1, nomen_manda_2, nomen_manda_3, False, context={'withnum': 1})
1241
 
 
1242
 
    def fill_lines(self, cr, uid, ids, context=None):
1243
 
        '''
1244
 
        Fill all lines according to defined nomenclature level and sublist
1245
 
        '''
1246
 
        line_obj = self.pool.get('stock.inventory.line')
1247
 
        product_obj = self.pool.get('product.product')
1248
 
        
1249
 
        if context is None:
1250
 
            context = {}
1251
 
 
1252
 
        discrepancy_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_discrepancy')[1]
1253
 
            
1254
 
        for inv in self.browse(cr, uid, ids, context=context):
1255
 
            product_ids = []
1256
 
 
1257
 
            nom = False
1258
 
            field = False
1259
 
            # Get all products for the defined nomenclature
1260
 
            if inv.nomen_manda_3:
1261
 
                nom = inv.nomen_manda_3.id
1262
 
                field = 'nomen_manda_3'
1263
 
            elif inv.nomen_manda_2:
1264
 
                nom = inv.nomen_manda_2.id
1265
 
                field = 'nomen_manda_2'
1266
 
            elif inv.nomen_manda_1:
1267
 
                nom = inv.nomen_manda_1.id
1268
 
                field = 'nomen_manda_1'
1269
 
            elif inv.nomen_manda_0:
1270
 
                nom = inv.nomen_manda_0.id
1271
 
                field = 'nomen_manda_0'
1272
 
            if nom:
1273
 
                product_ids.extend(self.pool.get('product.product').search(cr, uid, [(field, '=', nom)], context=context))
1274
 
 
1275
 
            # Get all products for the defined list
1276
 
            if inv.sublist_id:
1277
 
                for line in inv.sublist_id.product_ids:
1278
 
                    product_ids.append(line.name.id)
1279
 
                    
1280
 
            for product in product_obj.browse(cr, uid, product_ids, context=context):
1281
 
                # Check if the product is not already in the list
1282
 
                if product.type not in ('consu', 'service', 'service_recep') and\
1283
 
                   not line_obj.search(cr, uid, [('inventory_id', '=', inv.id), 
1284
 
                                                 ('product_id', '=', product.id),
1285
 
                                                 ('product_uom', '=', product.uom_id.id)], context=context):
1286
 
                    line_obj.create(cr, uid, {'inventory_id': inv.id,
1287
 
                                              'product_id': product.id,
1288
 
                                              'reason_type_id': discrepancy_id,
1289
 
                                              'product_uom': product.uom_id.id}, context=context)
1290
 
        
1291
 
        return True
1292
 
 
1293
 
    def get_nomen(self, cr, uid, ids, field):
1294
 
        return self.pool.get('product.nomenclature').get_nomen(cr, uid, self, ids, field, context={'withnum': 1})
1295
 
 
1296
 
    def _hook_dont_move(self, cr, uid, *args, **kwargs):
1297
 
        res = super(stock_inventory, self)._hook_dont_move(cr, uid, *args, **kwargs)
1298
 
        if 'line' in kwargs:
1299
 
            return res and not kwargs['line'].dont_move
1300
 
 
1301
 
        return res
1302
 
    
1303
 
    def action_confirm(self, cr, uid, ids, context=None):
1304
 
        '''
1305
 
        if the line is perishable without prodlot, we create the prodlot
1306
 
        '''
1307
 
        prodlot_obj = self.pool.get('stock.production.lot')
1308
 
        product_obj = self.pool.get('product.product')
1309
 
        # treat the needed production lot
1310
 
        for obj in self.browse(cr, uid, ids, context=context):
1311
 
            for line in obj.inventory_line_id:
1312
 
                if self._name == 'initial.stock.inventory' and line.product_qty == 0.00:
1313
 
                    line.write({'dont_move': True})
1314
 
                    continue
1315
 
                if line.hidden_perishable_mandatory and not line.expiry_date:
1316
 
                    raise osv.except_osv(_('Error'), _('The product %s is perishable but the line with this product has no expiry date') % product_obj.name_get(cr, uid, [line.product_id.id])[0][1])
1317
 
                if line.hidden_batch_management_mandatory and not line.prod_lot_id:
1318
 
                    raise osv.except_osv(_('Error'), _('The product %s is batch mandatory but the line with this product has no batch') % product_obj.name_get(cr, uid, [line.product_id.id])[0][1])
1319
 
                # if perishable product
1320
 
                if line.hidden_perishable_mandatory and not line.hidden_batch_management_mandatory:
1321
 
                    # integrity test
1322
 
                    assert line.product_id.perishable, 'product is not perishable but line is'
1323
 
                    assert line.expiry_date, 'expiry date is not set'
1324
 
                    # if no production lot, we create a new one
1325
 
                    if not line.prod_lot_id:
1326
 
                        # double check to find the corresponding prodlot
1327
 
                        prodlot_ids = prodlot_obj.search(cr, uid, [('life_date', '=', line.expiry_date),
1328
 
                                                                   ('type', '=', 'internal'),
1329
 
                                                                   ('product_id', '=', line.product_id.id)], context=context)
1330
 
                        # no prodlot, create a new one
1331
 
                        if not prodlot_ids:
1332
 
                            vals = {'product_id': line.product_id.id,
1333
 
                                    'life_date': line.expiry_date,
1334
 
                                    'name': self.pool.get('ir.sequence').get(cr, uid, 'stock.lot.serial'),
1335
 
                                    'type': 'internal',
1336
 
                                    }
1337
 
                            prodlot_id = prodlot_obj.create(cr, uid, vals, context=context)
1338
 
                        else:
1339
 
                            prodlot_id = prodlot_ids[0]
1340
 
                        # update the line
1341
 
                        line.write({'prod_lot_id': prodlot_id,},)
1342
 
        
1343
 
        # super function after production lot creation - production lot are therefore taken into account at stock move creation
1344
 
        result = super(stock_inventory, self).action_confirm(cr, uid, ids, context=context)      
1345
 
        return result
1346
 
                        
1347
 
stock_inventory()
1348
 
 
1349
 
 
1350
 
class stock_inventory_line(osv.osv):
1351
 
    '''
1352
 
    add mandatory or readonly behavior to prodlot
1353
 
    '''
1354
 
    _inherit = 'stock.inventory.line'
1355
 
    _rec_name = 'product_id'
1356
 
    
1357
 
    def common_on_change(self, cr, uid, ids, location_id, product, prod_lot_id, uom=False, to_date=False, result=None):
1358
 
        '''
1359
 
        commmon qty computation
1360
 
        '''
1361
 
        if result is None:
1362
 
            result = {}
1363
 
        if not product:
1364
 
            return result
1365
 
        product_obj = self.pool.get('product.product').browse(cr, uid, product)
1366
 
        uom = uom or product_obj.uom_id.id
1367
 
        stock_context = {'uom': uom, 'to_date': to_date,
1368
 
                         'prodlot_id':prod_lot_id,}
1369
 
        if location_id:
1370
 
            # if a location is specified, we do not list the children locations, otherwise yes
1371
 
            stock_context.update({'compute_child': False,})
1372
 
        amount = self.pool.get('stock.location')._product_get(cr, uid, location_id, [product], stock_context)[product]
1373
 
        result.setdefault('value', {}).update({'product_qty': amount, 'product_uom': uom})
1374
 
        return result
1375
 
    
1376
 
    def change_lot(self, cr, uid, ids, location_id, product, prod_lot_id, uom=False, to_date=False,):
1377
 
        '''
1378
 
        prod lot changes, update the expiry date
1379
 
        '''
1380
 
        prodlot_obj = self.pool.get('stock.production.lot')
1381
 
        result = {'value':{}}
1382
 
        # reset expiry date or fill it
1383
 
        if prod_lot_id:
1384
 
            result['value'].update(expiry_date=prodlot_obj.browse(cr, uid, prod_lot_id).life_date)
1385
 
        else:
1386
 
            result['value'].update(expiry_date=False)
1387
 
        # compute qty
1388
 
        result = self.common_on_change(cr, uid, ids, location_id, product, prod_lot_id, uom, to_date, result=result)
1389
 
        return result
1390
 
    
1391
 
    def change_expiry(self, cr, uid, id, expiry_date, product_id, type_check, context=None):
1392
 
        '''
1393
 
        expiry date changes, find the corresponding internal prod lot
1394
 
        '''
1395
 
        prodlot_obj = self.pool.get('stock.production.lot')
1396
 
        result = {'value':{}}
1397
 
        
1398
 
        if expiry_date and product_id:
1399
 
            prod_ids = prodlot_obj.search(cr, uid, [('life_date', '=', expiry_date),
1400
 
                                                    ('type', '=', 'internal'),
1401
 
                                                    ('product_id', '=', product_id)], context=context)
1402
 
            if not prod_ids:
1403
 
                if type_check == 'in':
1404
 
                    # the corresponding production lot will be created afterwards
1405
 
                    result['warning'] = {'title': _('Info'),
1406
 
                                     'message': _('The selected Expiry Date does not exist in the system. It will be created during validation process.')}
1407
 
                    # clear prod lot
1408
 
                    result['value'].update(prod_lot_id=False)
1409
 
                else:
1410
 
                    # display warning
1411
 
                    result['warning'] = {'title': _('Error'),
1412
 
                                         'message': _('The selected Expiry Date does not exist in the system.')}
1413
 
                    # clear date
1414
 
                    result['value'].update(expiry_date=False, prod_lot_id=False)
1415
 
            else:
1416
 
                # return first prodlot
1417
 
                result['value'].update(prod_lot_id=prod_ids[0])
1418
 
        else:
1419
 
            # clear expiry date, we clear production lot
1420
 
            result['value'].update(prod_lot_id=False,
1421
 
                                   expiry_date=False,
1422
 
                                   )
1423
 
        return result
1424
 
    
1425
 
    def on_change_location_id(self, cr, uid, ids, location_id, product, prod_lot_id, uom=False, to_date=False,):
1426
 
        """ Changes UoM and name if product_id changes.
1427
 
        @param location_id: Location id
1428
 
        @param product: Changed product_id
1429
 
        @param uom: UoM product
1430
 
        @return:  Dictionary of changed values
1431
 
        """
1432
 
        result = {}
1433
 
        if not product:
1434
 
            # do nothing
1435
 
            result.setdefault('value', {}).update({'product_qty': 0.0,})
1436
 
            return result
1437
 
        # compute qty
1438
 
        result = self.common_on_change(cr, uid, ids, location_id, product, prod_lot_id, uom, to_date, result=result)
1439
 
        return result
1440
 
    
1441
 
    def on_change_product_id_specific_rules(self, cr, uid, ids, location_id, product, prod_lot_id, uom=False, to_date=False,):
1442
 
        '''
1443
 
        the product changes, set the hidden flag if necessary
1444
 
        '''
1445
 
        result = super(stock_inventory_line, self).on_change_product_id(cr, uid, ids, location_id, product, uom, to_date)
1446
 
        # product changes, prodlot is always cleared
1447
 
        result.setdefault('value', {})['prod_lot_id'] = False
1448
 
        result.setdefault('value', {})['expiry_date'] = False
1449
 
        # reset the hidden flags
1450
 
        result.setdefault('value', {})['hidden_batch_management_mandatory'] = False
1451
 
        result.setdefault('value', {})['hidden_perishable_mandatory'] = False
1452
 
        if product:
1453
 
            product_obj = self.pool.get('product.product').browse(cr, uid, product)
1454
 
            if product_obj.batch_management:
1455
 
                result.setdefault('value', {})['hidden_batch_management_mandatory'] = True
1456
 
            elif product_obj.perishable:
1457
 
                result.setdefault('value', {})['hidden_perishable_mandatory'] = True
1458
 
            # if not product, result is 0.0 by super
1459
 
            # compute qty
1460
 
            result = self.common_on_change(cr, uid, ids, location_id, product, prod_lot_id, uom, to_date, result=result)
1461
 
        return result
1462
 
    
1463
 
    def create(self, cr, uid, vals, context=None):
1464
 
        '''
1465
 
        complete info normally generated by javascript on_change function
1466
 
        '''
1467
 
        prod_obj = self.pool.get('product.product')
1468
 
        if vals.get('product_id', False):
1469
 
            # complete hidden flags - needed if not created from GUI
1470
 
            product = prod_obj.browse(cr, uid, vals.get('product_id'), context=context)
1471
 
            if product.batch_management:
1472
 
                vals.update(hidden_batch_management_mandatory=True)
1473
 
            elif product.perishable:
1474
 
                vals.update(hidden_perishable_mandatory=True)
1475
 
            else:
1476
 
                vals.update(hidden_batch_management_mandatory=False,
1477
 
                            hidden_perishable_mandatory=False,
1478
 
                            )
1479
 
        # complete expiry date from production lot - needed if not created from GUI
1480
 
        prodlot_obj = self.pool.get('stock.production.lot')
1481
 
        if vals.get('prod_lot_id', False):
1482
 
            vals.update(expiry_date=prodlot_obj.browse(cr, uid, vals.get('prod_lot_id'), context=context).life_date)
1483
 
        # call super
1484
 
        result = super(stock_inventory_line, self).create(cr, uid, vals, context=context)
1485
 
        return result
1486
 
    
1487
 
    def write(self, cr, uid, ids, vals, context=None):
1488
 
        '''
1489
 
        complete info normally generated by javascript on_change function
1490
 
        '''
1491
 
        prod_obj = self.pool.get('product.product')
1492
 
        if vals.get('product_id', False):
1493
 
            # complete hidden flags - needed if not created from GUI
1494
 
            product = prod_obj.browse(cr, uid, vals.get('product_id'), context=context)
1495
 
            if product.batch_management:
1496
 
                vals.update(hidden_batch_management_mandatory=True)
1497
 
            elif product.perishable:
1498
 
                vals.update(hidden_perishable_mandatory=True)
1499
 
            else:
1500
 
                vals.update(hidden_batch_management_mandatory=False,
1501
 
                            hidden_perishable_mandatory=False,
1502
 
                            )
1503
 
        # complete expiry date from production lot - needed if not created from GUI
1504
 
        prodlot_obj = self.pool.get('stock.production.lot')
1505
 
        if vals.get('prod_lot_id', False):
1506
 
            vals.update(expiry_date=prodlot_obj.browse(cr, uid, vals.get('prod_lot_id'), context=context).life_date)
1507
 
        
1508
 
        # call super
1509
 
        result = super(stock_inventory_line, self).write(cr, uid, ids, vals, context=context)
1510
 
        return result
1511
 
    
1512
 
    def _get_checks_all(self, cr, uid, ids, name, arg, context=None):
1513
 
        '''
1514
 
        function for KC/SSL/DG/NP products
1515
 
        '''
1516
 
        result = {}
1517
 
        for id in ids:
1518
 
            result[id] = {}
1519
 
            for f in name:
1520
 
                result[id].update({f: False,})
1521
 
            
1522
 
        for obj in self.browse(cr, uid, ids, context=context):
1523
 
            # keep cool
1524
 
            if obj.product_id.heat_sensitive_item:
1525
 
                result[obj.id]['kc_check'] = True
1526
 
            # ssl
1527
 
            if obj.product_id.short_shelf_life:
1528
 
                result[obj.id]['ssl_check'] = True
1529
 
            # dangerous goods
1530
 
            if obj.product_id.dangerous_goods:
1531
 
                result[obj.id]['dg_check'] = True
1532
 
            # narcotic
1533
 
            if obj.product_id.narcotic:
1534
 
                result[obj.id]['np_check'] = True
1535
 
            # lot management
1536
 
            if obj.product_id.batch_management:
1537
 
                result[obj.id]['lot_check'] = True
1538
 
            # expiry date management
1539
 
            if obj.product_id.perishable:
1540
 
                result[obj.id]['exp_check'] = True
1541
 
 
1542
 
            # has a problem
1543
 
            # Line will be displayed in red if it's not correct
1544
 
            result[obj.id]['has_problem'] = False
1545
 
            if not obj.location_id \
1546
 
               or not self._check_perishable(cr, uid, [obj.id]) \
1547
 
               or not self._check_batch_management(cr, uid, [obj.id]):
1548
 
                   result[obj.id]['has_problem'] = True
1549
 
            
1550
 
        return result
1551
 
    
1552
 
    def _check_batch_management(self, cr, uid, ids, context=None):
1553
 
        '''
1554
 
        check for batch management
1555
 
        '''
1556
 
        for obj in self.browse(cr, uid, ids, context=context):
1557
 
            if obj.inventory_id.state not in ('draft', 'cancel') and obj.product_id.batch_management:
1558
 
                if not obj.prod_lot_id or obj.prod_lot_id.type != 'standard':
1559
 
                    return False
1560
 
        return True
1561
 
    
1562
 
    def _check_perishable(self, cr, uid, ids, context=None):
1563
 
        """
1564
 
        check for perishable ONLY
1565
 
        """
1566
 
        for obj in self.browse(cr, uid, ids, context=context):
1567
 
            if obj.inventory_id.state not in ('draft', 'cancel') and obj.product_id.perishable and not obj.product_id.batch_management:
1568
 
                if (not obj.prod_lot_id and not obj.expiry_date) or (obj.prod_lot_id and obj.prod_lot_id.type != 'internal'):
1569
 
                    return False
1570
 
        return True
1571
 
    
1572
 
    def _check_prodlot_need(self, cr, uid, ids, context=None):
1573
 
        """
1574
 
        If the inv line has a prodlot but does not need one, return False.
1575
 
        """
1576
 
        for obj in self.browse(cr, uid, ids, context=context):
1577
 
            if obj.prod_lot_id:
1578
 
                if not obj.product_id.perishable and not obj.product_id.batch_management:
1579
 
                    return False
1580
 
        return True
1581
 
    
1582
 
    _columns = {
1583
 
        'hidden_perishable_mandatory': fields.boolean(string='Hidden Flag for Perishable product',),
1584
 
        'hidden_batch_management_mandatory': fields.boolean(string='Hidden Flag for Batch Management product',),
1585
 
        # Remove the 'required' attribute on location_id to allow the possiblity to fill lines with list or nomenclature
1586
 
        # The required attribute is True on the XML view
1587
 
        'location_id': fields.many2one('stock.location', 'Location'),
1588
 
        'prod_lot_id': fields.many2one('stock.production.lot', 'Batch', domain="[('product_id','=',product_id)]"),
1589
 
        'expiry_date': fields.date(string='Expiry Date'),
1590
 
        'type_check': fields.char(string='Type Check', size=1024,),
1591
 
        'kc_check': fields.function(_get_checks_all, method=True, string='KC', type='boolean', readonly=True, multi="m"),
1592
 
        'ssl_check': fields.function(_get_checks_all, method=True, string='SSL', type='boolean', readonly=True, multi="m"),
1593
 
        'dg_check': fields.function(_get_checks_all, method=True, string='DG', type='boolean', readonly=True, multi="m"),
1594
 
        'np_check': fields.function(_get_checks_all, method=True, string='NP', type='boolean', readonly=True, multi="m"),
1595
 
        'lot_check': fields.function(_get_checks_all, method=True, string='B.Num', type='boolean', readonly=True, multi="m"),
1596
 
        'exp_check': fields.function(_get_checks_all, method=True, string='Exp', type='boolean', readonly=True, multi="m"),
1597
 
        'has_problem': fields.function(_get_checks_all, method=True, string='Has problem', type='boolean', readonly=True, multi="m"),
1598
 
        'dont_move': fields.boolean(string='Don\'t create stock.move for this line'),
1599
 
    }
1600
 
    
1601
 
    _defaults = {# in is used, meaning a new prod lot will be created if the specified expiry date does not exist
1602
 
                 'type_check': 'in',
1603
 
                 'dont_move': lambda *a: False,
1604
 
                 }
1605
 
    
1606
 
    _constraints = [(_check_batch_management,
1607
 
                     'You must assign a Batch Number which corresponds to Batch Number Mandatory Products.',
1608
 
                     ['prod_lot_id']),
1609
 
                    (_check_perishable,
1610
 
                     'You must assign a Batch Numbre which corresponds to Expiry Date Mandatory Products.',
1611
 
                     ['prod_lot_id']),
1612
 
                    (_check_prodlot_need,
1613
 
                     'The selected product is neither Batch Number Mandatory nor Expiry Date Mandatory',
1614
 
                     ['prod_lot_id']),
1615
 
                    ]
1616
 
 
1617
 
stock_inventory_line()
1618
 
 
1619
 
class report_stock_inventory(osv.osv):
1620
 
    '''
1621
 
    UF-565: add group by expired_date
1622
 
    '''
1623
 
    _inherit = "report.stock.inventory"
1624
 
    
1625
 
    def init(self, cr):
1626
 
        tools.drop_view_if_exists(cr, 'report_stock_inventory')
1627
 
        cr.execute("""
1628
 
CREATE OR REPLACE view report_stock_inventory AS (
1629
 
    (SELECT
1630
 
        min(m.id) as id, m.date as date,
1631
 
        m.expired_date as expired_date,
1632
 
        m.address_id as partner_id, m.location_id as location_id,
1633
 
        m.product_id as product_id, pt.categ_id as product_categ_id, l.usage as location_type,
1634
 
        m.company_id,
1635
 
        m.state as state, m.prodlot_id as prodlot_id,
1636
 
        coalesce(sum(-pt.standard_price * m.product_qty)::decimal, 0.0) as value,
1637
 
        CASE when pt.uom_id = m.product_uom
1638
 
        THEN
1639
 
        coalesce(sum(-m.product_qty)::decimal, 0.0)
1640
 
        ELSE
1641
 
        coalesce(sum(-m.product_qty * pu.factor)::decimal, 0.0) END as product_qty
1642
 
    FROM
1643
 
        stock_move m
1644
 
            LEFT JOIN stock_picking p ON (m.picking_id=p.id)
1645
 
            LEFT JOIN product_product pp ON (m.product_id=pp.id)
1646
 
                LEFT JOIN product_template pt ON (pp.product_tmpl_id=pt.id)
1647
 
                LEFT JOIN product_uom pu ON (pt.uom_id=pu.id)
1648
 
            LEFT JOIN product_uom u ON (m.product_uom=u.id)
1649
 
            LEFT JOIN stock_location l ON (m.location_id=l.id)
1650
 
    GROUP BY
1651
 
        m.id, m.product_id, m.product_uom, pt.categ_id, m.address_id, m.location_id,  m.location_dest_id,
1652
 
        m.prodlot_id, m.expired_date, m.date, m.state, l.usage, m.company_id,pt.uom_id
1653
 
) UNION ALL (
1654
 
    SELECT
1655
 
        -m.id as id, m.date as date,
1656
 
        m.expired_date as expired_date,
1657
 
        m.address_id as partner_id, m.location_dest_id as location_id,
1658
 
        m.product_id as product_id, pt.categ_id as product_categ_id, l.usage as location_type,
1659
 
        m.company_id,
1660
 
        m.state as state, m.prodlot_id as prodlot_id,
1661
 
        coalesce(sum(pt.standard_price * m.product_qty )::decimal, 0.0) as value,
1662
 
        CASE when pt.uom_id = m.product_uom
1663
 
        THEN
1664
 
        coalesce(sum(m.product_qty)::decimal, 0.0)
1665
 
        ELSE
1666
 
        coalesce(sum(m.product_qty * pu.factor)::decimal, 0.0) END as product_qty
1667
 
    FROM
1668
 
        stock_move m
1669
 
            LEFT JOIN stock_picking p ON (m.picking_id=p.id)
1670
 
            LEFT JOIN product_product pp ON (m.product_id=pp.id)
1671
 
                LEFT JOIN product_template pt ON (pp.product_tmpl_id=pt.id)
1672
 
                LEFT JOIN product_uom pu ON (pt.uom_id=pu.id)
1673
 
            LEFT JOIN product_uom u ON (m.product_uom=u.id)
1674
 
            LEFT JOIN stock_location l ON (m.location_dest_id=l.id)
1675
 
    GROUP BY
1676
 
        m.id, m.product_id, m.product_uom, pt.categ_id, m.address_id, m.location_id, m.location_dest_id,
1677
 
        m.prodlot_id, m.expired_date, m.date, m.state, l.usage, m.company_id,pt.uom_id
1678
 
    )
1679
 
);
1680
 
        """)
1681
 
    
1682
 
    _columns = {
1683
 
        'prodlot_id': fields.many2one('stock.production.lot', 'Batch', readonly=True),
1684
 
        'expired_date': fields.date(string='Expiry Date',),
1685
 
    }
1686
 
   
1687
 
    def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
1688
 
        if context is None:
1689
 
            context = {}
1690
 
        if fields is None:
1691
 
            fields = []
1692
 
        context['with_expiry'] = 1
1693
 
        return super(report_stock_inventory, self).read(cr, uid, ids, fields, context, load)
1694
 
    
1695
 
    def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
1696
 
        '''
1697
 
        UF-1546: This method is to remove the lines that have quantity = 0 from the list view
1698
 
        '''
1699
 
        res = super(report_stock_inventory, self).read_group(cr, uid, domain, fields, groupby, offset, limit, context, orderby)
1700
 
        if self._name == 'report.stock.inventory' and res:
1701
 
             return [data for data in res if data.get('product_qty', 10) != 0.0]
1702
 
        return res
1703
 
    
1704
 
report_stock_inventory()
1705
 
 
1706
 
class product_product(osv.osv):
1707
 
    _inherit = 'product.product'
1708
 
    def open_stock_by_location(self, cr, uid, ids, context=None):
1709
 
        if context is None:
1710
 
            context = {}
1711
 
 
1712
 
        ctx = {'product_id': context.get('active_id') , 'compute_child': False}
1713
 
        if context.get('lang'):
1714
 
            ctx['lang'] = context['lang']
1715
 
 
1716
 
        name = _('Stock by Location')
1717
 
        if ids:
1718
 
            prod = self.pool.get('product.product').read(cr, uid, ids[0], ['name', 'code'], context=ctx)
1719
 
            name = "%s: [%s] %s"%(name, prod['code'], prod['name'])
1720
 
        view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock_override', 'view_location_tree_tree')[1] 
1721
 
        return {
1722
 
            'name': name,
1723
 
            'type': 'ir.actions.act_window',
1724
 
            'res_model': 'stock.location',
1725
 
            'view_type': 'tree',
1726
 
            'view_id': [view_id],
1727
 
            'domain': [('location_id','=',False)],
1728
 
            'view_mode': 'tree',
1729
 
            'context': ctx,
1730
 
            'target': 'current',
1731
 
        }
1732
 
 
1733
 
product_product()