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

« back to all changes in this revision

Viewing changes to stock_override/stock.py

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

Show diffs side-by-side

added added

removed removed

Lines of Context:
30
30
import tools
31
31
import decimal_precision as dp
32
32
import logging
33
 
 
 
33
from os import path
 
34
 
 
35
from msf_partner import PARTNER_TYPE
 
36
 
 
37
#----------------------------------------------------------
 
38
# Procurement Order
 
39
#----------------------------------------------------------
 
40
class procurement_order(osv.osv):
 
41
    _name = 'procurement.order'
 
42
    _inherit = 'procurement.order'
 
43
    
 
44
    def create(self, cr, uid, vals, context=None):
 
45
        '''
 
46
        create method for filling flag from yml tests
 
47
        '''
 
48
        if context is None:
 
49
            context = {}
 
50
        if context.get('update_mode') in ['init', 'update'] and 'from_yml_test' not in vals:
 
51
            logging.getLogger('init').info('PRO: set from yml test to True')
 
52
            vals['from_yml_test'] = True
 
53
        return super(procurement_order, self).create(cr, uid, vals, context=context)
 
54
 
 
55
    def action_confirm(self, cr, uid, ids, context=None):
 
56
        """ Confirms procurement and writes exception message if any.
 
57
        @return: True
 
58
        """
 
59
        move_obj = self.pool.get('stock.move')
 
60
        for procurement in self.browse(cr, uid, ids, context=context):
 
61
            if procurement.product_qty <= 0.00:
 
62
                raise osv.except_osv(_('Data Insufficient !'),
 
63
                    _('Please check the Quantity in Procurement Order(s), it should not be less than 1!'))
 
64
            if procurement.product_id.type in ('product', 'consu'):
 
65
                if not procurement.move_id:
 
66
                    source = procurement.location_id.id
 
67
                    reason_type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_other')[1]
 
68
                    if procurement.procure_method == 'make_to_order':
 
69
                        reason_type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_external_supply')[1]
 
70
                        source = procurement.product_id.product_tmpl_id.property_stock_procurement.id
 
71
                    id = move_obj.create(cr, uid, {
 
72
                        'name': procurement.name,
 
73
                        'location_id': source,
 
74
                        'location_dest_id': procurement.location_id.id,
 
75
                        'product_id': procurement.product_id.id,
 
76
                        'product_qty': procurement.product_qty,
 
77
                        'product_uom': procurement.product_uom.id,
 
78
                        'date_expected': procurement.date_planned,
 
79
                        'state': 'draft',
 
80
                        'company_id': procurement.company_id.id,
 
81
                        'auto_validate': True,
 
82
                        'reason_type_id': reason_type_id,
 
83
                    })
 
84
                    move_obj.action_confirm(cr, uid, [id], context=context)
 
85
                    self.write(cr, uid, [procurement.id], {'move_id': id, 'close_move': 1})
 
86
        self.write(cr, uid, ids, {'state': 'confirmed', 'message': ''})
 
87
        return True
 
88
    
 
89
    def copy_data(self, cr, uid, id, default=None, context=None):
 
90
        '''
 
91
        reset link to purchase order from update of on order purchase order
 
92
        '''
 
93
        if not default:
 
94
            default = {}
 
95
        default.update({'so_back_update_dest_po_id_procurement_order': False,
 
96
                        'so_back_update_dest_pol_id_procurement_order': False})
 
97
        return super(procurement_order, self).copy_data(cr, uid, id, default, context=context)
 
98
    
 
99
    _columns = {'from_yml_test': fields.boolean('Only used to pass addons unit test', readonly=True, help='Never set this field to true !'),
 
100
                # this field is used when the po is modified during on order process, and the so must be modified accordingly
 
101
                # the resulting new purchase order line will be merged in specified po_id 
 
102
                'so_back_update_dest_po_id_procurement_order': fields.many2one('purchase.order', string='Destination of new purchase order line', readonly=True),
 
103
                'so_back_update_dest_pol_id_procurement_order': fields.many2one('purchase.order.line', string='Original purchase order line', readonly=True),
 
104
                }
 
105
    
 
106
    _defaults = {'from_yml_test': lambda *a: False,
 
107
                 }
 
108
    
 
109
procurement_order()
34
110
 
35
111
 
36
112
#----------------------------------------------------------
40
116
    _inherit = "stock.picking"
41
117
    _description = "Picking List"
42
118
    
43
 
    
44
 
    def _do_partial_hook(self, cr, uid, ids, context, *args, **kwargs):
45
 
        '''
46
 
        hook to update defaults data
 
119
    def _hook_state_list(self, cr, uid, *args, **kwargs):
 
120
        '''
 
121
        Change terms into states list
 
122
        '''
 
123
        state_list = kwargs['state_list']
 
124
        
 
125
        state_list['done'] = _('is closed.')
 
126
        
 
127
        return state_list
 
128
    
 
129
    def _get_stock_picking_from_partner_ids(self, cr, uid, ids, context=None):
 
130
        '''
 
131
        ids represents the ids of res.partner objects for which values have changed
 
132
        
 
133
        return the list of ids of stock.picking objects which need to get their fields updated
 
134
        
 
135
        self is res.partner object
 
136
        '''
 
137
        # Some verifications
 
138
        if context is None:
 
139
            context = {}
 
140
        if isinstance(ids, (int, long)):
 
141
            ids = [ids]
 
142
            
 
143
        pick_obj = self.pool.get('stock.picking')
 
144
        result = pick_obj.search(cr, uid, [('partner_id2', 'in', ids)], context=context)
 
145
        return result
 
146
    
 
147
    def _vals_get_stock_ov(self, cr, uid, ids, fields, arg, context=None):
 
148
        '''
 
149
        multi fields function method
 
150
        '''
 
151
        # Some verifications
 
152
        if context is None:
 
153
            context = {}
 
154
        if isinstance(ids, (int, long)):
 
155
            ids = [ids]
 
156
            
 
157
        result = {}
 
158
        for obj in self.browse(cr, uid, ids, context=context):
 
159
            result[obj.id] = {}
 
160
            for f in fields:
 
161
                result[obj.id].update({f:False})
 
162
            if obj.partner_id2:
 
163
                result[obj.id].update({'partner_type_stock_picking': obj.partner_id2.partner_type})
 
164
            
 
165
        return result
 
166
    
 
167
    def _get_inactive_product(self, cr, uid, ids, field_name, args, context=None):
 
168
        res = {}
 
169
        for pick in self.browse(cr, uid, ids, context=context):
 
170
            res[pick.id] = False
 
171
            for line in pick.move_lines:
 
172
                if line.inactive_product:
 
173
                    res[pick.id] = True
 
174
                    break
 
175
        
 
176
        return res
 
177
 
 
178
    _columns = {
 
179
        'state': fields.selection([
 
180
            ('draft', 'Draft'),
 
181
            ('auto', 'Waiting'),
 
182
            ('confirmed', 'Confirmed'),
 
183
            ('assigned', 'Available'),
 
184
            ('done', 'Closed'),
 
185
            ('cancel', 'Cancelled'),
 
186
            ], 'State', readonly=True, select=True,
 
187
            help="* Draft: not confirmed yet and will not be scheduled until confirmed\n"\
 
188
                 "* Confirmed: still waiting for the availability of products\n"\
 
189
                 "* Available: products reserved, simply waiting for confirmation.\n"\
 
190
                 "* Waiting: waiting for another move to proceed before it becomes automatically available (e.g. in Make-To-Order flows)\n"\
 
191
                 "* Closed: has been processed, can't be modified or cancelled anymore\n"\
 
192
                 "* Cancelled: has been cancelled, can't be confirmed anymore"),
 
193
        'from_yml_test': fields.boolean('Only used to pass addons unit test', readonly=True, help='Never set this field to true !'),
 
194
        'address_id': fields.many2one('res.partner.address', 'Delivery address', help="Address of partner", readonly=False, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, domain="[('partner_id', '=', partner_id)]"),
 
195
        'partner_id2': fields.many2one('res.partner', 'Partner', required=False),
 
196
        'from_wkf': fields.boolean('From wkf'),
 
197
        'update_version_from_in_stock_picking': fields.integer(string='Update version following IN processing'),
 
198
        'partner_type_stock_picking': fields.function(_vals_get_stock_ov, method=True, type='selection', selection=PARTNER_TYPE, string='Partner Type', multi='get_vals_stock_ov', readonly=True, select=True,
 
199
                                                      store= {'stock.picking': (lambda self, cr, uid, ids, c=None: ids, ['partner_id2'], 10),
 
200
                                                              'res.partner': (_get_stock_picking_from_partner_ids, ['partner_type'], 10),}),
 
201
        'inactive_product': fields.function(_get_inactive_product, method=True, type='boolean', string='Product is inactive', store=False),
 
202
        'fake_type': fields.selection([('out', 'Sending Goods'), ('in', 'Getting Goods'), ('internal', 'Internal')], 'Shipping Type', required=True, select=True, help="Shipping type specify, goods coming in or going out."),
 
203
    }
 
204
    
 
205
    _defaults = {'from_yml_test': lambda *a: False,
 
206
                 'from_wkf': lambda *a: False,
 
207
                 'update_version_from_in_stock_picking': 0,
 
208
                 'fake_type': 'in',
 
209
                 }
 
210
 
 
211
    def _check_active_product(self, cr, uid, ids, context=None):
 
212
        '''
 
213
        Check if the stock picking contains a line with an inactive products
 
214
        '''
 
215
        inactive_lines = self.pool.get('stock.move').search(cr, uid, [('product_id.active', '=', False),
 
216
                                                                      ('picking_id', 'in', ids),
 
217
                                                                      ('picking_id.state', 'not in', ['draft', 'cancel', 'done'])], context=context)
 
218
        
 
219
        if inactive_lines:
 
220
            plural = len(inactive_lines) == 1 and _('A product has') or _('Some products have')
 
221
            l_plural = len(inactive_lines) == 1 and _('line') or _('lines')
 
222
            p_plural = len(inactive_lines) == 1 and _('this inactive product') or _('those inactive products')
 
223
            raise osv.except_osv(_('Error'), _('%s been inactivated. If you want to validate this document you have to remove/correct the %s containing %s (see red %s of the document)') % (plural, l_plural, p_plural, l_plural))
 
224
            return False
 
225
        return True
 
226
    
 
227
    _constraints = [
 
228
            (_check_active_product, "You cannot validate this document because it contains a line with an inactive product", ['order_line', 'state'])
 
229
    ]
 
230
    
 
231
    def create(self, cr, uid, vals, context=None):
 
232
        '''
 
233
        create method for filling flag from yml tests
 
234
        '''
 
235
        if context is None:
 
236
            context = {}
 
237
 
 
238
        if not context.get('active_id',False):
 
239
            vals['from_wkf'] = True
 
240
        # in case me make a copy of a stock.picking coming from a workflow
 
241
        if context.get('not_workflow', False):
 
242
            vals['from_wkf'] = False
 
243
    
 
244
        if context.get('update_mode') in ['init', 'update'] and 'from_yml_test' not in vals:
 
245
            logging.getLogger('init').info('PICKING: set from yml test to True')
 
246
            vals['from_yml_test'] = True
 
247
            
 
248
        if not vals.get('partner_id2') and vals.get('address_id'):
 
249
            addr = self.pool.get('res.partner.address').browse(cr, uid, vals.get('address_id'), context=context)
 
250
            vals['partner_id2'] = addr.partner_id and addr.partner_id.id or False
 
251
            
 
252
        if not vals.get('address_id') and vals.get('partner_id2'):
 
253
            addr = self.pool.get('res.partner').address_get(cr, uid, vals.get('partner_id2'), ['delivery', 'default'])
 
254
            if not addr.get('delivery'):
 
255
                vals['address_id'] = addr.get('default')
 
256
            else:
 
257
                vals['address_id'] = addr.get('delivery')
 
258
                 
 
259
        return super(stock_picking, self).create(cr, uid, vals, context=context)
 
260
    
 
261
    def write(self, cr, uid, ids, vals, context=None):
 
262
        '''
 
263
        Update the partner or the address according to the other
 
264
        '''
 
265
        if isinstance(ids,(int, long)):
 
266
            ids = [ids]
 
267
        
 
268
        if not vals.get('address_id') and vals.get('partner_id2'):
 
269
            for pick in self.browse(cr, uid, ids, context=context):
 
270
                if pick.partner_id.id != vals.get('partner_id2'):
 
271
                    addr = self.pool.get('res.partner').address_get(cr, uid, vals.get('partner_id2'), ['delivery', 'default'])
 
272
                    if not addr.get('delivery'):
 
273
                        vals['address_id'] = addr.get('default')
 
274
                    else:
 
275
                        vals['address_id'] = addr.get('delivery')
 
276
                        
 
277
        if not vals.get('partner_id2') and vals.get('address_id'):
 
278
            for pick in self.browse(cr, uid, ids, context=context):
 
279
                if pick.address_id.id != vals.get('address_id'):
 
280
                    addr = self.pool.get('res.partner.address').browse(cr, uid, vals.get('address_id'), context=context)
 
281
                    vals['partner_id2'] = addr.partner_id and addr.partner_id.id or False
 
282
        
 
283
        return super(stock_picking, self).write(cr, uid, ids, vals, context=context)
 
284
    
 
285
    def on_change_partner(self, cr, uid, ids, partner_id, address_id, context=None):
 
286
        '''
 
287
        Change the delivery address when the partner change.
 
288
        '''
 
289
        v = {}
 
290
        d = {}
 
291
        
 
292
        if not partner_id:
 
293
            v.update({'address_id': False})
 
294
        else:
 
295
            d.update({'address_id': [('partner_id', '=', partner_id)]})
 
296
            
 
297
 
 
298
        if address_id:
 
299
            addr = self.pool.get('res.partner.address').browse(cr, uid, address_id, context=context)
 
300
        
 
301
        if not address_id or addr.partner_id.id != partner_id:
 
302
            addr = self.pool.get('res.partner').address_get(cr, uid, partner_id, ['delivery', 'default'])
 
303
            if not addr.get('delivery'):
 
304
                addr = addr.get('default')
 
305
            else:
 
306
                addr = addr.get('delivery')
 
307
                
 
308
            v.update({'address_id': addr})
 
309
            
 
310
        
 
311
        return {'value': v,
 
312
                'domain': d}
 
313
    
 
314
    def set_manually_done(self, cr, uid, ids, all_doc=True, context=None):
 
315
        '''
 
316
        Set the picking to done
 
317
        '''
 
318
        move_ids = []
 
319
 
 
320
        if isinstance(ids, (int, long)):
 
321
            ids = [ids]
 
322
 
 
323
        for pick in self.browse(cr, uid, ids, context=context):
 
324
            for move in pick.move_lines:
 
325
                if move.state not in ('cancel', 'done'):
 
326
                    move_ids.append(move.id)
 
327
 
 
328
        #Set all stock moves to done
 
329
        self.pool.get('stock.move').set_manually_done(cr, uid, move_ids, all_doc=all_doc, context=context)
 
330
 
 
331
        return True
 
332
    
 
333
    def _do_partial_hook(self, cr, uid, ids, context=None, *args, **kwargs):
 
334
        '''
 
335
        Please copy this to your module's method also.
 
336
        This hook belongs to the do_partial method from stock_override>stock.py>stock_picking
 
337
        
 
338
        - allow to modify the defaults data for move creation and copy
47
339
        '''
48
340
        defaults = kwargs.get('defaults')
49
341
        assert defaults is not None, 'missing defaults'
50
342
        
51
343
        return defaults
52
 
        
 
344
    
 
345
    def _picking_done_cond(self, cr, uid, ids, context=None, *args, **kwargs):
 
346
        '''
 
347
        Please copy this to your module's method also.
 
348
        This hook belongs to the do_partial method from stock_override>stock.py>stock_picking
 
349
        
 
350
        - allow to conditionally execute the picking processing to done
 
351
        '''
 
352
        return True
 
353
    
 
354
    def _custom_code(self, cr, uid, ids, context=None, *args, **kwargs):
 
355
        '''
 
356
        Please copy this to your module's method also.
 
357
        This hook belongs to the do_partial method from stock_override>stock.py>stock_picking
 
358
        
 
359
        - allow to execute specific custom code before processing picking to done
 
360
        - no supposed to modify partial_datas
 
361
        '''
 
362
        return True
53
363
 
54
364
    # @@@override stock>stock.py>stock_picking>do_partial
55
365
    def do_partial(self, cr, uid, ids, partial_datas, context=None):
59
369
                          delivery moves with product_id, product_qty, uom
60
370
        @return: Dictionary of values
61
371
        """
 
372
        if isinstance(ids,(int, long)):
 
373
            ids = [ids]
 
374
        
62
375
        if context is None:
63
376
            context = {}
64
377
        else:
70
383
        uom_obj = self.pool.get('product.uom')
71
384
        sequence_obj = self.pool.get('ir.sequence')
72
385
        wf_service = netsvc.LocalService("workflow")
 
386
 
 
387
        internal_loc_ids = self.pool.get('stock.location').search(cr, uid, [('usage','=','internal'), ('cross_docking_location_ok', '=', False)])
 
388
        ctx_avg = context.copy()
 
389
        ctx_avg['location'] = internal_loc_ids
73
390
        for pick in self.browse(cr, uid, ids, context=context):
74
391
            new_picking = None
75
 
            complete, too_many, too_few = [], [], []
 
392
            complete, too_many, too_few , not_aval = [], [], [], []
76
393
            move_product_qty = {}
77
394
            prodlot_ids = {}
78
395
            product_avail = {}
79
396
            for move in pick.move_lines:
80
397
                if move.state in ('done', 'cancel'):
81
398
                    continue
 
399
                elif move.state in ('confirmed'):
 
400
                    not_aval.append(move)
 
401
                    continue
82
402
                partial_data = partial_datas.get('move%s'%(move.id), {})
83
403
                #Commented in order to process the less number of stock moves from partial picking wizard
84
404
                #assert partial_data, _('Missing partial picking data for move #%s') % (move.id)
97
417
                    too_many.append(move)
98
418
 
99
419
                # Average price computation
100
 
                if (pick.type == 'in') and (move.product_id.cost_method == 'average'):
101
 
                    product = product_obj.browse(cr, uid, move.product_id.id)
 
420
                if (pick.type == 'in') and (move.product_id.cost_method == 'average') and not move.location_dest_id.cross_docking_location_ok:
 
421
                    product = product_obj.browse(cr, uid, move.product_id.id, context=ctx_avg)
102
422
                    move_currency_id = move.company_id.currency_id.id
103
423
                    context['currency_id'] = move_currency_id
104
424
                    qty = uom_obj._compute_qty(cr, uid, product_uom, product_qty, product.uom_id.id)
110
430
 
111
431
                    if qty > 0:
112
432
                        new_price = currency_obj.compute(cr, uid, product_currency,
113
 
                                move_currency_id, product_price)
 
433
                                move_currency_id, product_price, round=False, context=context)
114
434
                        new_price = uom_obj._compute_price(cr, uid, product_uom, new_price,
115
435
                                product.uom_id.id)
116
436
                        if product.qty_available <= 0:
128
448
                        move_obj.write(cr, uid, [move.id],
129
449
                                {'price_unit': product_price,
130
450
                                 'price_currency_id': product_currency})
131
 
 
 
451
            for move in not_aval:
 
452
                if not new_picking:
 
453
                    new_picking = self.copy(cr, uid, pick.id,
 
454
                            {
 
455
                                'name': sequence_obj.get(cr, uid, 'stock.picking.%s'%(pick.type)),
 
456
                                'move_lines' : [],
 
457
                                'state':'draft',
 
458
                            })
132
459
 
133
460
            for move in too_few:
134
461
                product_qty = move_product_qty[move.id]
135
 
 
136
462
                if not new_picking:
137
463
                    new_picking = self.copy(cr, uid, pick.id,
138
464
                            {
148
474
                            'state': 'assigned',
149
475
                            'move_dest_id': False,
150
476
                            'price_unit': move.price_unit,
 
477
                            'processed_stock_move': True,
151
478
                    }
152
479
                    prodlot_id = prodlot_ids[move.id]
153
480
                    if prodlot_id:
160
487
                        {
161
488
                            'product_qty' : move.product_qty - product_qty,
162
489
                            'product_uos_qty':move.product_qty - product_qty, #TODO: put correct uos_qty
 
490
                            'processed_stock_move': True,
163
491
                        })
164
492
 
165
493
            if new_picking:
173
501
                defaults = self._do_partial_hook(cr, uid, ids, context, move=move, partial_datas=partial_datas, defaults=defaults)
174
502
                move_obj.write(cr, uid, [move.id], defaults)
175
503
                # override : end
 
504
 
176
505
            for move in too_many:
177
506
                product_qty = move_product_qty[move.id]
178
507
                defaults = {
188
517
                defaults = self._do_partial_hook(cr, uid, ids, context, move=move, partial_datas=partial_datas, defaults=defaults)
189
518
                move_obj.write(cr, uid, [move.id], defaults)
190
519
 
191
 
 
192
520
            # At first we confirm the new picking (if necessary)
193
521
            if new_picking:
 
522
                self.write(cr, uid, [pick.id], {'backorder_id': new_picking})
 
523
                # custom code execution
 
524
                self._custom_code(cr, uid, ids, context=context, partial_datas=partial_datas, concerned_picking=self.browse(cr, uid, new_picking, context=context))
 
525
                # we confirm the new picking after its name was possibly modified by custom code - so the link message (top message) is correct
194
526
                wf_service.trg_validate(uid, 'stock.picking', new_picking, 'button_confirm', cr)
195
527
                # Then we finish the good picking
196
 
                self.write(cr, uid, [pick.id], {'backorder_id': new_picking})
197
 
                self.action_move(cr, uid, [new_picking])
198
 
                wf_service.trg_validate(uid, 'stock.picking', new_picking, 'button_done', cr)
 
528
                if self._picking_done_cond(cr, uid, ids, context=context, partial_datas=partial_datas):
 
529
                    self.action_move(cr, uid, [new_picking])
 
530
                    wf_service.trg_validate(uid, 'stock.picking', new_picking, 'button_done', cr)
199
531
                wf_service.trg_write(uid, 'stock.picking', pick.id, cr)
200
532
                delivered_pack_id = new_picking
201
533
            else:
202
 
                self.action_move(cr, uid, [pick.id])
203
 
                wf_service.trg_validate(uid, 'stock.picking', pick.id, 'button_done', cr)
 
534
                # custom code execution
 
535
                self._custom_code(cr, uid, ids, context=context, partial_datas=partial_datas, concerned_picking=pick)
 
536
                if self._picking_done_cond(cr, uid, ids, context=context, partial_datas=partial_datas):
 
537
                    self.action_move(cr, uid, [pick.id])
 
538
                    wf_service.trg_validate(uid, 'stock.picking', pick.id, 'button_done', cr)
204
539
                delivered_pack_id = pick.id
205
540
 
206
541
            delivered_pack = self.browse(cr, uid, delivered_pack_id, context=context)
209
544
        return res
210
545
    # @@@override end
211
546
 
 
547
    # @@@override stock>stock.py>stock_picking>_get_invoice_type
 
548
    def _get_invoice_type(self, pick):
 
549
        src_usage = dest_usage = None
 
550
        inv_type = None
 
551
        if pick.invoice_state == '2binvoiced':
 
552
            if pick.move_lines:
 
553
                src_usage = pick.move_lines[0].location_id.usage
 
554
                dest_usage = pick.move_lines[0].location_dest_id.usage
 
555
            if pick.type == 'out' and dest_usage == 'supplier':
 
556
                inv_type = 'in_refund'
 
557
            elif pick.type == 'out' and dest_usage == 'customer':
 
558
                inv_type = 'out_invoice'
 
559
            elif (pick.type == 'in' and src_usage == 'supplier') or (pick.type == 'internal'):
 
560
                inv_type = 'in_invoice'
 
561
            elif pick.type == 'in' and src_usage == 'customer':
 
562
                inv_type = 'out_refund'
 
563
            else:
 
564
                inv_type = 'out_invoice'
 
565
        return inv_type
 
566
    
 
567
    def _hook_get_move_ids(self, cr, uid, *args, **kwargs):
 
568
        move_obj = self.pool.get('stock.move')
 
569
        pick = kwargs['pick']
 
570
        move_ids = move_obj.search(cr, uid, [('picking_id', '=', pick.id), 
 
571
                                             ('state', 'in', ('waiting', 'confirmed'))], order='product_qty desc')
 
572
        
 
573
        return move_ids
 
574
 
 
575
    def is_invoice_needed(self, cr, uid, sp=None):
 
576
        """
 
577
        Check if invoice is needed. Cases where we do not need invoice:
 
578
        - OUT from scratch (without purchase_id and sale_id) AND stock picking type in internal, external or esc
 
579
        - OUT from FO AND stock picking type in internal, external or esc
 
580
        So all OUT that have internel, external or esc should return FALSE from this method.
 
581
        This means to only accept intermission and intersection invoicing on OUT with reason type "Deliver partner".
 
582
        """
 
583
        res = True
 
584
        if not sp:
 
585
            return res
 
586
        # Fetch some values
 
587
        try:
 
588
            rt_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_deliver_partner')[1]
 
589
        except ValueError:
 
590
            rt_id = False
 
591
        # type out and partner_type in internal, external or esc
 
592
        if sp.type == 'out' and not sp.purchase_id and not sp.sale_id and sp.partner_id.partner_type in ['external', 'internal', 'esc']:
 
593
            res = False
 
594
        if sp.type == 'out' and not sp.purchase_id and not sp.sale_id and rt_id and sp.partner_id.partner_type in ['intermission', 'section']:
 
595
            # Search all stock moves attached to this one. If one of them is deliver partner, then is_invoice_needed is ok
 
596
            res = False
 
597
            sm_ids = self.pool.get('stock.move').search(cr, uid, [('picking_id', '=', sp.id)])
 
598
            if sm_ids:
 
599
                for sm in self.pool.get('stock.move').browse(cr, uid, sm_ids):
 
600
                    if sm.reason_type_id.id == rt_id:
 
601
                        res = True
 
602
        # partner is itself (those that own the company)
 
603
        company_partner_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.partner_id
 
604
        if sp.partner_id.id == company_partner_id.id:
 
605
            res = False
 
606
        return res
 
607
 
 
608
    def action_done(self, cr, uid, ids, context=None):
 
609
        """
 
610
        Create automatically invoice or NOT (regarding some criteria in is_invoice_needed)
 
611
        """
 
612
        res = super(stock_picking, self).action_done(cr, uid, ids, context=context)
 
613
        if res:
 
614
            if isinstance(ids, (int, long)):
 
615
                ids = [ids]
 
616
            for sp in self.browse(cr, uid, ids):
 
617
                sp_type = False
 
618
                inv_type = self._get_invoice_type(sp)
 
619
                # Check if no invoice needed
 
620
                is_invoice_needed = self.is_invoice_needed(cr, uid, sp)
 
621
                if not is_invoice_needed:
 
622
                    continue
 
623
                # we do not create invoice for procurement_request (Internal Request)
 
624
                if not sp.sale_id.procurement_request and sp.subtype == 'standard':
 
625
                    if sp.type == 'in' or sp.type == 'internal':
 
626
                        if inv_type == 'out_refund':
 
627
                            sp_type = 'sale_refund'
 
628
                        else:
 
629
                            sp_type = 'purchase'
 
630
                    elif sp.type == 'out':
 
631
                        if inv_type == 'in_refund':
 
632
                            sp_type = 'purchase_refund'
 
633
                        else:
 
634
                            sp_type = 'sale'
 
635
                    # Journal type
 
636
                    journal_type = sp_type
 
637
                    # Disturb journal for invoice only on intermission partner type
 
638
                    if sp.partner_id.partner_type == 'intermission':
 
639
                        journal_type = 'intermission'
 
640
                    journal_ids = self.pool.get('account.journal').search(cr, uid, [('type', '=', journal_type),
 
641
                                                                                    ('is_current_instance', '=', True)])
 
642
                    if not journal_ids:
 
643
                        raise osv.except_osv(_('Warning'), _('No %s journal found!') % (journal_type,))
 
644
                    # Create invoice
 
645
                    self.action_invoice_create(cr, uid, [sp.id], journal_ids[0], False, inv_type, {})
 
646
        return res
 
647
 
 
648
    def action_invoice_create(self, cr, uid, ids, journal_id=False, group=False, type='out_invoice', context=None):
 
649
        """
 
650
        Attach an intermission journal to the Intermission Voucher IN/OUT if partner type is intermission from the picking.
 
651
        Prepare intermission voucher IN/OUT
 
652
        Change invoice purchase_list field to TRUE if this picking come from a PO which is 'purchase_list'
 
653
        """
 
654
        if isinstance(ids,(int, long)):
 
655
            ids = [ids]
 
656
 
 
657
        if not context:
 
658
            context = {}
 
659
        res = super(stock_picking, self).action_invoice_create(cr, uid, ids, journal_id, group, type, context)
 
660
        intermission_journal_ids = self.pool.get('account.journal').search(cr, uid, [('type', '=', 'intermission'),
 
661
                                                                                     ('is_current_instance', '=', True)])
 
662
        company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
 
663
        intermission_default_account = company.intermission_default_counterpart
 
664
        for pick in self.browse(cr, uid, [x for x in res]):
 
665
            # Check if PO and PO is purchase_list
 
666
            if pick.purchase_id and pick.purchase_id.order_type and pick.purchase_id.order_type == 'purchase_list':
 
667
                inv_id = res[pick.id]
 
668
                self.pool.get('account.invoice').write(cr, uid, [inv_id], {'purchase_list': True})
 
669
            # Check intermission
 
670
            if pick.partner_id.partner_type == 'intermission':
 
671
                inv_id = res[pick.id]
 
672
                if not intermission_journal_ids:
 
673
                    raise osv.except_osv(_('Error'), _('No Intermission journal found!'))
 
674
                if not intermission_default_account or not intermission_default_account.id:
 
675
                    raise osv.except_osv(_('Error'), _('Please configure a default intermission account in Company configuration.'))
 
676
                self.pool.get('account.invoice').write(cr, uid, [inv_id], {'journal_id': intermission_journal_ids[0], 
 
677
                    'is_intermission': True, 'account_id': intermission_default_account.id,})
 
678
                # Change currency for this invoice
 
679
                company_currency = company.currency_id and company.currency_id.id or False
 
680
                if not company_currency:
 
681
                    raise osv.except_osv(_('Warning'), _('No company currency found!'))
 
682
                wiz_account_change = self.pool.get('account.change.currency').create(cr, uid, {'currency_id': company_currency}, context=context)
 
683
                self.pool.get('account.change.currency').change_currency(cr, uid, [wiz_account_change], context={'active_id': inv_id})
 
684
        return res
 
685
 
 
686
    def action_confirm(self, cr, uid, ids, context=None):
 
687
        """
 
688
            stock.picking: action confirm
 
689
            if INCOMING picking: confirm and check availability
 
690
        """
 
691
        super(stock_picking, self).action_confirm(cr, uid, ids, context=context)
 
692
        move_obj = self.pool.get('stock.move')
 
693
 
 
694
        if isinstance(ids, (int, long)):
 
695
            ids = [ids]
 
696
        for pick in self.browse(cr, uid, ids):
 
697
            if pick.move_lines and pick.type == 'in':
 
698
                not_assigned_move = [x.id for x in pick.move_lines if x.state == 'confirmed']
 
699
                if not_assigned_move:
 
700
                    move_obj.action_assign(cr, uid, not_assigned_move)
 
701
        return True
 
702
 
 
703
    def _hook_action_assign_batch(self, cr, uid, ids, context=None):
 
704
        '''
 
705
        Please copy this to your module's method also.
 
706
        This hook belongs to the action_assign method from stock>stock.py>stock_picking class
 
707
        
 
708
        -  when product is Expiry date mandatory, we "pre-assign" batch numbers regarding the available quantity
 
709
        and location logic in addition to FEFO logic (First expired first out).
 
710
        '''
 
711
        if isinstance(ids,(int, long)):
 
712
            ids = [ids]
 
713
        if context is None:
 
714
            context = {}
 
715
        move_obj = self.pool.get('stock.move')
 
716
        if not context.get('already_checked'):
 
717
            for pick in self.browse(cr, uid, ids, context=context):
 
718
                # perishable for perishable or batch management
 
719
                move_obj.fefo_update(cr, uid, [move.id for move in pick.move_lines if move.product_id.perishable], context) # FEFO
 
720
        context['already_checked'] = True
 
721
        return super(stock_picking, self)._hook_action_assign_batch(cr, uid, ids, context=context)
212
722
 
213
723
stock_picking()
214
724
 
225
735
    _inherit = "stock.move"
226
736
    _description = "Stock Move with hook"
227
737
 
 
738
    def set_manually_done(self, cr, uid, ids, all_doc=True, context=None):
 
739
        '''
 
740
        Set the stock move to manually done
 
741
        '''
 
742
        return self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
 
743
 
 
744
    def _get_from_dpo(self, cr, uid, ids, field_name, args, context=None):
 
745
        '''
 
746
        Return True if the move has a dpo_id
 
747
        '''
 
748
        res = {}
 
749
        if isinstance(ids,(int, long)):
 
750
            ids = [ids]
 
751
 
 
752
        for move in self.browse(cr, uid, ids, context=context):
 
753
            res[move.id] = False
 
754
            if move.dpo_id:
 
755
                res[move.id] = True
 
756
 
 
757
        return res
 
758
 
 
759
    def _search_from_dpo(self, cr, uid, obj, name, args, context=None):
 
760
        '''
 
761
        Returns the list of moves from or not from DPO
 
762
        '''
 
763
        for arg in args:
 
764
            if arg[0] == 'from_dpo' and arg[1] == '=':
 
765
                return [('dpo_id', '!=', False)]
 
766
            elif arg[0] == 'from_dpo' and arg[1] in ('!=', '<>'):
 
767
                return [('dpo_id', '=', False)]
 
768
        
 
769
        return []
 
770
    
 
771
    def _default_location_destination(self, cr, uid, context=None):
 
772
        if not context:
 
773
            context = {}
 
774
        if context.get('picking_type') == 'out':
 
775
            wh_ids = self.pool.get('stock.warehouse').search(cr, uid, [])
 
776
            if wh_ids:
 
777
                return self.pool.get('stock.warehouse').browse(cr, uid, wh_ids[0]).lot_output_id.id
 
778
    
 
779
        return False
 
780
    
 
781
    def _get_inactive_product(self, cr, uid, ids, field_name, args, context=None):
 
782
        '''
 
783
        Fill the error message if the product of the line is inactive
 
784
        '''
 
785
        res = {}
 
786
        if isinstance(ids,(int, long)):
 
787
            ids = [ids]
 
788
        
 
789
        for line in self.browse(cr, uid, ids, context=context):
 
790
            res[line.id] = {'inactive_product': False,
 
791
                            'inactive_error': ''}
 
792
            if line.picking_id and line.picking_id.state not in ('cancel', 'done') and line.product_id and not line.product_id.active:
 
793
                res[line.id] = {'inactive_product': True,
 
794
                                'inactive_error': _('The product in line is inactive !')}
 
795
                
 
796
        return res  
 
797
 
 
798
    _columns = {
 
799
        'price_unit': fields.float('Unit Price', digits_compute=dp.get_precision('Picking Price Computation'), help="Technical field used to record the product cost set by the user during a picking confirmation (when average price costing method is used)"),
 
800
        'state': fields.selection([('draft', 'Draft'), ('waiting', 'Waiting'), ('confirmed', 'Not Available'), ('assigned', 'Available'), ('done', 'Closed'), ('cancel', 'Cancelled')], 'State', readonly=True, select=True,
 
801
              help='When the stock move is created it is in the \'Draft\' state.\n After that, it is set to \'Not Available\' state if the scheduler did not find the products.\n When products are reserved it is set to \'Available\'.\n When the picking is done the state is \'Closed\'.\
 
802
              \nThe state is \'Waiting\' if the move is waiting for another one.'),
 
803
        'address_id': fields.many2one('res.partner.address', 'Delivery address', help="Address of partner", readonly=False, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, domain="[('partner_id', '=', partner_id)]"),
 
804
        'partner_id2': fields.many2one('res.partner', 'Partner', required=False),
 
805
        'already_confirmed': fields.boolean(string='Already confirmed'),
 
806
        'dpo_id': fields.many2one('purchase.order', string='Direct PO', help='PO from where this stock move is sourced.'),
 
807
        'from_dpo': fields.function(_get_from_dpo, fnct_search=_search_from_dpo, type='boolean', method=True, store=False, string='From DPO ?'),
 
808
        'from_wkf_line': fields.related('picking_id', 'from_wkf', type='boolean', string='Internal use: from wkf'),
 
809
        'fake_state': fields.related('state', type='char', store=False, string="Internal use"),
 
810
        'processed_stock_move': fields.boolean(string='Processed Stock Move'),
 
811
        'inactive_product': fields.function(_get_inactive_product, method=True, type='boolean', string='Product is inactive', store=False, multi='inactive'),
 
812
        'inactive_error': fields.function(_get_inactive_product, method=True, type='char', string='Error', store=False, multi='inactive'),
 
813
    }
 
814
    
 
815
    _defaults = {
 
816
        'location_dest_id': _default_location_destination,
 
817
        'processed_stock_move': False, # to know if the stock move has already been partially or completely processed
 
818
        'inactive_product': False,
 
819
        'inactive_error': lambda *a: '',
 
820
    }
 
821
 
 
822
    def create(self, cr, uid, vals, context=None):
 
823
        '''
 
824
        Update the partner or the address according to the other
 
825
        '''
 
826
        if not vals.get('partner_id2') and vals.get('address_id'):
 
827
            addr = self.pool.get('res.partner.address').browse(cr, uid, vals.get('address_id'), context=context)
 
828
            vals['partner_id2'] = addr.partner_id and addr.partner_id.id or False
 
829
        elif not vals.get('partner_id2'):
 
830
            vals['partner_id2'] = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.partner_id.id
 
831
            
 
832
        if not vals.get('address_id') and vals.get('partner_id2'):
 
833
            addr = self.pool.get('res.partner').address_get(cr, uid, vals.get('partner_id2'), ['delivery', 'default'])
 
834
            if not addr.get('delivery'):
 
835
                vals['address_id'] = addr.get('default')
 
836
            else:
 
837
                vals['address_id'] = addr.get('delivery')
 
838
 
 
839
        type = vals.get('type')
 
840
        if not type and vals.get('picking_id'):
 
841
            type = self.pool.get('stock.picking').browse(cr, uid, vals.get('picking_id'), context=context).type
 
842
            
 
843
        if type == 'in' and not vals.get('date_expected'):
 
844
            vals['date_expected'] = time.strftime('%Y-%m-%d %H:%M:%S')
 
845
 
 
846
        if vals.get('date_expected'):
 
847
            vals['date'] = vals.get('date_expected')
 
848
        
 
849
        return super(stock_move, self).create(cr, uid, vals, context=context)
 
850
    
 
851
    def write(self, cr, uid, ids, vals, context=None):
 
852
        '''
 
853
        Update the partner or the address according to the other
 
854
        '''
 
855
        
 
856
        if isinstance(ids, (int, long)):
 
857
            ids = [ids]        
 
858
        
 
859
        if not vals.get('address_id') and vals.get('partner_id2'):
 
860
            for move in self.browse(cr, uid, ids, context=context):
 
861
                if move.partner_id.id != vals.get('partner_id'):
 
862
                    addr = self.pool.get('res.partner').address_get(cr, uid, vals.get('partner_id2'), ['delivery', 'default'])
 
863
                    if not addr.get('delivery'):
 
864
                        vals['address_id'] = addr.get('default')
 
865
                    else:
 
866
                        vals['address_id'] = addr.get('delivery')
 
867
                        
 
868
        if not vals.get('partner_id2') and vals.get('address_id'):
 
869
            for move in self.browse(cr, uid, ids, context=context):
 
870
                if move.address_id.id != vals.get('address_id'):
 
871
                    addr = self.pool.get('res.partner.address').browse(cr, uid, vals.get('address_id'), context=context)
 
872
                    vals['partner_id2'] = addr.partner_id and addr.partner_id.id or False
 
873
 
 
874
        if vals.get('date_expected'):
 
875
            for move in self.browse(cr, uid, ids, context=context):
 
876
                if vals.get('state', move.state) not in ('done', 'cancel'):
 
877
                    vals['date'] = vals.get('date_expected')
 
878
        
 
879
        return super(stock_move, self).write(cr, uid, ids, vals, context=context)
 
880
    
 
881
    def on_change_partner(self, cr, uid, ids, partner_id, address_id, context=None):
 
882
        '''
 
883
        Change the delivery address when the partner change.
 
884
        '''
 
885
        v = {}
 
886
        d = {}
 
887
        
 
888
        if not partner_id:
 
889
            v.update({'address_id': False})
 
890
        else:
 
891
            d.update({'address_id': [('partner_id', '=', partner_id)]})
 
892
            
 
893
 
 
894
        if address_id:
 
895
            addr = self.pool.get('res.partner.address').browse(cr, uid, address_id, context=context)
 
896
        
 
897
        if not address_id or addr.partner_id.id != partner_id:
 
898
            addr = self.pool.get('res.partner').address_get(cr, uid, partner_id, ['delivery', 'default'])
 
899
            if not addr.get('delivery'):
 
900
                addr = addr.get('default')
 
901
            else:
 
902
                addr = addr.get('delivery')
 
903
                
 
904
            v.update({'address_id': addr})
 
905
            
 
906
        
 
907
        return {'value': v,
 
908
                'domain': d}
 
909
    
 
910
    def copy(self, cr, uid, id, default=None, context=None):
 
911
        '''
 
912
        Remove the already confirmed flag
 
913
        '''
 
914
        if default is None:
 
915
            default = {}
 
916
        default.update({'already_confirmed':False})
 
917
        
 
918
        return super(stock_move, self).copy(cr, uid, id, default, context=context)
 
919
    
 
920
    def fefo_update(self, cr, uid, ids, context=None):
 
921
        """
 
922
        Update batch, Expiry Date, Location according to FEFO logic
 
923
        """
 
924
        if isinstance(ids, (int, long)):
 
925
            ids = [ids]
 
926
        if context is None:
 
927
            context = {}
 
928
 
 
929
        loc_obj = self.pool.get('stock.location')
 
930
        prodlot_obj = self.pool.get('stock.production.lot')
 
931
        for move in self.browse(cr, uid, ids, context):
 
932
            # FEFO logic
 
933
            if move.state == 'assigned': # a check_availability has already been done in action_assign, so we take only the 'assigned' lines
 
934
                needed_qty = move.product_qty
 
935
                res = loc_obj.compute_availability(cr, uid, [move.location_id.id], True, move.product_id.id, move.product_uom.id, context=context)
 
936
                if 'fefo' in res:
 
937
                    # We need to have the value like below because we need to have the id of the m2o (which is not possible if we do self.read(cr, uid, move.id))
 
938
                    values = {'name': move.name,
 
939
                              'picking_id': move.picking_id.id,
 
940
                              'product_uom': move.product_uom.id,
 
941
                              'product_id': move.product_id.id,
 
942
                              'date_expected': move.date_expected,
 
943
                              'date': move.date,
 
944
                              'state': 'assigned',
 
945
                              'location_dest_id': move.location_dest_id.id,
 
946
                              'reason_type_id': move.reason_type_id.id,
 
947
                              }
 
948
                    for loc in res['fefo']:
 
949
                        # if source == destination, the state becomes 'done', so we don't do fefo logic in that case
 
950
                        if not move.location_dest_id.id == loc['location_id']:
 
951
                            # we ignore the batch that are outdated
 
952
                            expired_date = prodlot_obj.read(cr, uid, loc['prodlot_id'], ['life_date'], context)['life_date']
 
953
                            if datetime.strptime(expired_date, "%Y-%m-%d") >= datetime.today():
 
954
                                # as long all needed are not fulfilled
 
955
                                if needed_qty:
 
956
                                    # if the batch already exists and qty is enough, it is available (assigned)
 
957
                                    if needed_qty <= loc['qty']:
 
958
                                        if move.prodlot_id.id == loc['prodlot_id']:
 
959
                                            self.write(cr, uid, move.id, {'state': 'assigned'}, context)
 
960
                                        else:
 
961
                                            self.write(cr, uid, move.id, {'product_qty': needed_qty, 'product_uom': loc['uom_id'], 
 
962
                                                                        'location_id': loc['location_id'], 'prodlot_id': loc['prodlot_id']}, context)
 
963
                                        needed_qty = 0.0
 
964
                                    elif needed_qty:
 
965
                                        # we take all available
 
966
                                        selected_qty = loc['qty']
 
967
                                        needed_qty -= selected_qty
 
968
                                        dict_for_create = {}
 
969
                                        dict_for_create = values.copy()
 
970
                                        dict_for_create.update({'product_uom': loc['uom_id'], 'product_qty': selected_qty, 'location_id': loc['location_id'], 'prodlot_id': loc['prodlot_id']})
 
971
                                        self.create(cr, uid, dict_for_create, context)
 
972
                                        self.write(cr, uid, move.id, {'product_qty': needed_qty})
 
973
                    # if the batch is outdated, we remove it
 
974
                    if not context.get('yml_test', False):
 
975
                        if move.expired_date and not datetime.strptime(move.expired_date, "%Y-%m-%d") >= datetime.today():
 
976
                            self.write(cr, uid, move.id, {'prodlot_id': False}, context)
 
977
            elif move.state == 'confirmed':
 
978
                # we remove the prodlot_id in case that the move is not available
 
979
                self.write(cr, uid, move.id, {'prodlot_id': False}, context)
 
980
        return True
 
981
    
 
982
    def action_confirm(self, cr, uid, ids, context=None):
 
983
        '''
 
984
        Set the bool already confirmed to True
 
985
        '''
 
986
        res = super(stock_move, self).action_confirm(cr, uid, ids, context=context)
 
987
        
 
988
        self.write(cr, uid, ids, {'already_confirmed': True}, context=context)
 
989
        
 
990
        return res
 
991
    
 
992
    def _hook_confirmed_move(self, cr, uid, *args, **kwargs):
 
993
        '''
 
994
        Always return True
 
995
        '''
 
996
        move = kwargs['move']
 
997
        if not move.already_confirmed:
 
998
            self.action_confirm(cr, uid, [move.id])
 
999
        return True
 
1000
    
 
1001
    def _hook_move_cancel_state(self, cr, uid, *args, **kwargs):
 
1002
        '''
 
1003
        Change the state of the chained move
 
1004
        '''
 
1005
        if kwargs.get('context'):
 
1006
            kwargs['context'].update({'call_unlink': True})
 
1007
        return {'state': 'cancel'}, kwargs.get('context', {})
 
1008
 
 
1009
    def _hook_write_state_stock_move(self, cr, uid, done, notdone, count):
 
1010
        if done:
 
1011
            count += len(done)
 
1012
 
 
1013
            #If source location == dest location THEN stock move is done.
 
1014
            for line in self.read(cr,uid,done,['location_id','location_dest_id']):
 
1015
                if line.get('location_id') and line.get('location_dest_id') and line.get('location_id') == line.get('location_dest_id'):
 
1016
                    self.write(cr, uid, [line['id']], {'state': 'done'})
 
1017
                else:
 
1018
                    self.write(cr, uid, [line['id']], {'state': 'assigned'})
 
1019
 
 
1020
        if notdone:
 
1021
            self.write(cr, uid, notdone, {'state': 'confirmed'})
 
1022
        return count
 
1023
    
 
1024
    def _hook_check_assign(self, cr, uid, *args, **kwargs):
 
1025
        '''
 
1026
        kwargs['move'] is the current move
 
1027
        '''
 
1028
        move = kwargs['move']
 
1029
        return move.location_id.usage == 'supplier'
 
1030
 
 
1031
    def _hook_cancel_assign_batch(self, cr, uid, ids, context=None):
 
1032
        '''
 
1033
        Please copy this to your module's method also.
 
1034
        This hook belongs to the cancel_assign method from stock>stock.py>stock_move class
 
1035
        
 
1036
        -  it erases the batch number associated if any and reset the source location to the original one.
 
1037
        '''
 
1038
        if isinstance(ids,(int, long)):
 
1039
            ids = [ids]
 
1040
 
 
1041
        for line in self.browse(cr, uid, ids, context):
 
1042
            if line.prodlot_id:
 
1043
                self.write(cr, uid, ids, {'prodlot_id': False, 'expired_date': False})
 
1044
            if line.location_id.location_id and line.location_id.location_id.usage != 'view':
 
1045
                self.write(cr, uid, ids, {'location_id': line.location_id.location_id.id})
 
1046
        return True
 
1047
 
 
1048
    def _hook_copy_stock_move(self, cr, uid, res, move, done, notdone):
 
1049
        while res:
 
1050
            r = res.pop(0)
 
1051
            move_id = self.copy(cr, uid, move.id, {'line_number': move.line_number, 'product_qty': r[0],'product_uos_qty': r[0] * move.product_id.uos_coeff,'location_id': r[1]})
 
1052
            if r[2]:
 
1053
                done.append(move_id)
 
1054
            else:
 
1055
                notdone.append(move_id)
 
1056
        return done, notdone 
228
1057
 
229
1058
    def _do_partial_hook(self, cr, uid, ids, context, *args, **kwargs):
230
1059
        '''
243
1072
                          like partner_id, address_id, delivery_date, delivery
244
1073
                          moves with product_id, product_qty, uom
245
1074
        """
 
1075
        
 
1076
        if isinstance(ids,(int, long)):
 
1077
            ids = [ids]
 
1078
        
246
1079
        res = {}
247
1080
        picking_obj = self.pool.get('stock.picking')
248
1081
        product_obj = self.pool.get('product.product')
256
1089
        complete, too_many, too_few = [], [], []
257
1090
        move_product_qty = {}
258
1091
        prodlot_ids = {}
 
1092
        internal_loc_ids = self.pool.get('stock.location').search(cr, uid, [('usage','=','internal'), ('cross_docking_location_ok', '=', False)])
 
1093
        ctx_avg = context.copy()
 
1094
        ctx_avg['location'] = internal_loc_ids
259
1095
        for move in self.browse(cr, uid, ids, context=context):
260
1096
            if move.state in ('done', 'cancel'):
261
1097
                continue
275
1111
                too_many.append(move)
276
1112
 
277
1113
            # Average price computation
278
 
            if (move.picking_id.type == 'in') and (move.product_id.cost_method == 'average'):
279
 
                product = product_obj.browse(cr, uid, move.product_id.id)
 
1114
            if (move.picking_id.type == 'in') and (move.product_id.cost_method == 'average') and not move.location_dest_id.cross_docking_location_ok:
 
1115
                product = product_obj.browse(cr, uid, move.product_id.id, context=ctx_avg)
280
1116
                move_currency_id = move.company_id.currency_id.id
281
1117
                context['currency_id'] = move_currency_id
282
1118
                qty = uom_obj._compute_qty(cr, uid, product_uom, product_qty, product.uom_id.id)
283
1119
                if qty > 0:
284
1120
                    new_price = currency_obj.compute(cr, uid, product_currency,
285
 
                            move_currency_id, product_price)
 
1121
                            move_currency_id, product_price, round=False, context=context)
286
1122
                    new_price = uom_obj._compute_price(cr, uid, product_uom, new_price,
287
1123
                            product.uom_id.id)
288
1124
                    if product.qty_available <= 0:
360
1196
        return [move.id for move in complete]
361
1197
    # @@@override end
362
1198
 
 
1199
    def _get_destruction_products(self, cr, uid, ids, product_ids=False, context=None, recursive=False):
 
1200
        """ Finds the product quantity and price for particular location.
 
1201
        """
 
1202
        if context is None:
 
1203
            context = {}
 
1204
        if isinstance(ids,(int, long)):
 
1205
            ids = [ids]
 
1206
 
 
1207
        result = []
 
1208
        for move in self.browse(cr, uid, ids, context=context):
 
1209
            # add this move into the list of result
 
1210
            dg_check_flag = ''
 
1211
            if move.dg_check:
 
1212
                dg_check_flag = 'x'
 
1213
                
 
1214
            np_check_flag = ''
 
1215
            if move.np_check:
 
1216
                np_check_flag = 'x'
 
1217
            sub_total = move.product_qty * move.product_id.standard_price
 
1218
            
 
1219
            currency = ''
 
1220
            if move.purchase_line_id and move.purchase_line_id.currency_id:
 
1221
                currency = move.purchase_line_id.currency_id.name
 
1222
            elif move.sale_line_id and move.sale_line_id.currency_id:
 
1223
                currency = move.sale_line_id.currency_id.name
 
1224
            
 
1225
            result.append({
 
1226
                'prod_name': move.product_id.name,
 
1227
                'prod_code': move.product_id.code,
 
1228
                'prod_price': move.product_id.standard_price,
 
1229
                'sub_total': sub_total,
 
1230
                'currency': currency,
 
1231
                'origin': move.origin,
 
1232
                'expired_date': move.expired_date,
 
1233
                'prodlot_id': move.prodlot_id.name,
 
1234
                'dg_check': dg_check_flag,
 
1235
                'np_check': np_check_flag,
 
1236
                'uom': move.product_uom.name,
 
1237
                'prod_qty': move.product_qty,
 
1238
            })
 
1239
        return result
 
1240
 
 
1241
    def in_action_confirm(self, cr, uid, ids, context=None):
 
1242
        """
 
1243
            Incoming: draft or confirmed: validate and assign
 
1244
        """
 
1245
        if isinstance(ids, (int, long)):
 
1246
            ids = [ids]
 
1247
        self.action_confirm(cr, uid, ids, context)
 
1248
        self.action_assign(cr, uid, ids, context)
 
1249
        return True
 
1250
 
363
1251
stock_move()
 
1252
 
 
1253
#-----------------------------------------
 
1254
#   Stock location
 
1255
#-----------------------------------------
 
1256
class stock_location(osv.osv):
 
1257
    _name = 'stock.location'
 
1258
    _inherit = 'stock.location'
 
1259
    
 
1260
    def init(self, cr):
 
1261
        """
 
1262
        Load data.xml asap
 
1263
        """
 
1264
        if hasattr(super(stock_location, self), 'init'):
 
1265
            super(stock_location, self).init(cr)
 
1266
 
 
1267
        mod_obj = self.pool.get('ir.module.module')
 
1268
        logging.getLogger('init').info('HOOK: module stock_override: loading stock_data.xml')
 
1269
        pathname = path.join('stock_override', 'stock_data.xml')
 
1270
        file = tools.file_open(pathname)
 
1271
        tools.convert_xml_import(cr, 'stock_override', file, {}, mode='init', noupdate=False)
 
1272
    
 
1273
    def _product_value(self, cr, uid, ids, field_names, arg, context=None):
 
1274
        """Computes stock value (real and virtual) for a product, as well as stock qty (real and virtual).
 
1275
        @param field_names: Name of field
 
1276
        @return: Dictionary of values
 
1277
        """
 
1278
        result = super(stock_location, self)._product_value(cr, uid, ids, field_names, arg, context=context)
 
1279
        
 
1280
        product_product_obj = self.pool.get('product.product')
 
1281
        currency_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.currency_id.id
 
1282
        currency_obj = self.pool.get('res.currency')
 
1283
        currency = currency_obj.browse(cr, uid, currency_id, context=context)
 
1284
        if context.get('product_id'):
 
1285
            view_ids = self.search(cr, uid, [('usage', '=', 'view')], context=context)
 
1286
            result.update(dict([(i, {}.fromkeys(field_names, 0.0)) for i in list(set([aaa for aaa in view_ids]))]))
 
1287
            for loc_id in view_ids:
 
1288
                c = (context or {}).copy()
 
1289
                c['location'] = loc_id
 
1290
                c['compute_child'] = True
 
1291
                for prod in product_product_obj.browse(cr, uid, [context.get('product_id')], context=c):
 
1292
                    for f in field_names:
 
1293
                        if f == 'stock_real':
 
1294
                            if loc_id not in result:
 
1295
                                result[loc_id] = {}
 
1296
                            result[loc_id][f] += prod.qty_available
 
1297
                        elif f == 'stock_virtual':
 
1298
                            result[loc_id][f] += prod.virtual_available
 
1299
                        elif f == 'stock_real_value':
 
1300
                            amount = prod.qty_available * prod.standard_price
 
1301
                            amount = currency_obj.round(cr, uid, currency, amount)
 
1302
                            result[loc_id][f] += amount
 
1303
                        elif f == 'stock_virtual_value':
 
1304
                            amount = prod.virtual_available * prod.standard_price
 
1305
                            amount = currency_obj.round(cr, uid, currency, amount)
 
1306
                            result[loc_id][f] += amount
 
1307
                            
 
1308
        return result
 
1309
 
 
1310
    def _fake_get(self, cr, uid, ids, fields, arg, context=None):
 
1311
        result = {}
 
1312
        if isinstance(ids, (int, long)):
 
1313
            ids = [ids]
 
1314
        for id in ids:
 
1315
            result[id] = False
 
1316
        return result
 
1317
 
 
1318
    def _prod_loc_search(self, cr, uid, ids, fields, arg, context=None):
 
1319
        if not arg or not arg[0] or not arg[0][2] or not arg[0][2][0]:
 
1320
            return []
 
1321
        if context is None:
 
1322
            context = {}
 
1323
        id_nonstock = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock_override', 'stock_location_non_stockable')[1]
 
1324
        id_cross = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_cross_docking', 'stock_location_cross_docking')[1]
 
1325
        prod_obj = self.pool.get('product.product').browse(cr, uid, arg[0][2][0])
 
1326
        if prod_obj and prod_obj.type == 'consu':
 
1327
            if arg[0][2][1] == 'in':
 
1328
                id_virt = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock','stock_location_locations_virtual')[1]
 
1329
                ids_child = self.pool.get('stock.location').search(cr,uid,[('location_id', 'child_of', id_virt)])
 
1330
                return [('id', 'in', [id_nonstock, id_cross]+ids_child)]
 
1331
            else:
 
1332
                return [('id', 'in', [id_cross])]
 
1333
 
 
1334
        elif prod_obj and  prod_obj.type != 'consu':
 
1335
                if arg[0][2][1] == 'in':
 
1336
                    return [('id', 'in', ids_child)]
 
1337
                else:
 
1338
                    return [('id', 'not in', [id_nonstock]), ('usage', '=', 'internal')]
 
1339
 
 
1340
        return [('id', 'in', [])]
 
1341
 
 
1342
    def _cd_search(self, cr, uid, ids, fields, arg, context=None):
 
1343
        id_cross = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_cross_docking', 'stock_location_cross_docking')[1]
 
1344
        if context is None:
 
1345
            context = {}
 
1346
        if arg[0][2]:
 
1347
            obj_pol = arg[0][2][0] and self.pool.get('purchase.order.line').browse(cr, uid, arg[0][2][0]) or False
 
1348
            if  (obj_pol and obj_pol.order_id.cross_docking_ok) or arg[0][2][1]:
 
1349
                return [('id', 'in', [id_cross])]
 
1350
        return []
 
1351
 
 
1352
    def _check_usage(self, cr, uid, ids, fields, arg, context=None):
 
1353
        if not arg or not arg[0][2]:
 
1354
            return []
 
1355
        if context is None:
 
1356
            context = {}
 
1357
        prod_obj = self.pool.get('product.product').browse(cr, uid, arg[0][2])
 
1358
        if prod_obj.type == 'service_recep':
 
1359
            ids = self.pool.get('stock.location').search(cr, uid, [('usage','=', 'inventory')])
 
1360
            return [('id', 'in', ids)]
 
1361
        elif prod_obj.type == 'consu':
 
1362
            return []
 
1363
        else:
 
1364
            ids = self.pool.get('stock.location').search(cr, uid, [('usage','=', 'internal')])
 
1365
            return [('id', 'in', ids)]
 
1366
        return []
 
1367
 
 
1368
 
 
1369
    _columns = {
 
1370
        'chained_location_type': fields.selection([('none', 'None'), ('customer', 'Customer'), ('fixed', 'Fixed Location'), ('nomenclature', 'Nomenclature')],
 
1371
                                'Chained Location Type', required=True,
 
1372
                                help="Determines whether this location is chained to another location, i.e. any incoming product in this location \n" \
 
1373
                                     "should next go to the chained location. The chained location is determined according to the type :"\
 
1374
                                     "\n* None: No chaining at all"\
 
1375
                                     "\n* Customer: The chained location will be taken from the Customer Location field on the Partner form of the Partner that is specified in the Picking list of the incoming products." \
 
1376
                                     "\n* Fixed Location: The chained location is taken from the next field: Chained Location if Fixed." \
 
1377
                                     "\n* Nomenclature: The chained location is taken from the options field: Chained Location is according to the nomenclature level of product."\
 
1378
                                    ),
 
1379
        'chained_options_ids': fields.one2many('stock.location.chained.options', 'location_id', string='Chained options'),
 
1380
        'optional_loc': fields.boolean(string='Is an optional location ?'),
 
1381
        'stock_real': fields.function(_product_value, method=True, type='float', string='Real Stock', multi="stock"),
 
1382
        'stock_virtual': fields.function(_product_value, method=True, type='float', string='Virtual Stock', multi="stock"),
 
1383
        'stock_real_value': fields.function(_product_value, method=True, type='float', string='Real Stock Value', multi="stock", digits_compute=dp.get_precision('Account')),
 
1384
        'stock_virtual_value': fields.function(_product_value, method=True, type='float', string='Virtual Stock Value', multi="stock", digits_compute=dp.get_precision('Account')),
 
1385
        'check_prod_loc': fields.function(_fake_get, method=True, type='many2one', string='zz', fnct_search=_prod_loc_search),
 
1386
        'check_cd': fields.function(_fake_get, method=True, type='many2one', string='zz', fnct_search=_cd_search),
 
1387
        'check_usage': fields.function(_fake_get, method=True, type='many2one', string='zz', fnct_search=_check_usage),
 
1388
        'virtual_location': fields.boolean(string='Virtual location'),
 
1389
 
 
1390
    }
 
1391
 
 
1392
    #####
 
1393
    # Chained location on nomenclature level
 
1394
    #####
 
1395
    def _hook_chained_location_get(self, cr, uid, context=None, *args, **kwargs):
 
1396
        '''
 
1397
        Return the location according to nomenclature level
 
1398
        '''
 
1399
        location = kwargs['location']
 
1400
        product = kwargs['product']
 
1401
        result = kwargs['result']
 
1402
 
 
1403
        if location.chained_location_type == 'nomenclature':
 
1404
            for opt in location.chained_options_ids:
 
1405
                if opt.nomen_id.id == product.nomen_manda_0.id:
 
1406
                    return opt.dest_location_id
 
1407
 
 
1408
        return result
 
1409
 
 
1410
    def _hook_proct_reserve(self, cr, uid, product_qty, result, amount, id, ids ):
 
1411
        result.append((amount, id, True))
 
1412
        product_qty -= amount
 
1413
        if product_qty <= 0.0:
 
1414
            return result
 
1415
        else:
 
1416
            result = []
 
1417
            result.append((amount, id, True))
 
1418
            if len(ids) >= 1:
 
1419
                result.append((product_qty, ids[0], False))
 
1420
            else:
 
1421
                result.append((product_qty, id, False))
 
1422
            return result
 
1423
        return []
 
1424
 
 
1425
    def on_change_location_type(self, cr, uid, ids, chained_location_type, context=None):
 
1426
        '''
 
1427
        If the location type is changed to 'Nomenclature', set some other fields values
 
1428
        '''
 
1429
        if chained_location_type and chained_location_type == 'nomenclature':
 
1430
            return {'value': {'chained_auto_packing': 'transparent',
 
1431
                              'chained_picking_type': 'internal',
 
1432
                              'chained_delay': 0}}
 
1433
 
 
1434
        return {}
 
1435
 
 
1436
 
 
1437
stock_location()
 
1438
 
 
1439
class stock_location_chained_options(osv.osv):
 
1440
    _name = 'stock.location.chained.options'
 
1441
    _rec_name = 'location_id'
 
1442
    
 
1443
    _columns = {
 
1444
        'dest_location_id': fields.many2one('stock.location', string='Destination Location', required=True),
 
1445
        'nomen_id': fields.many2one('product.nomenclature', string='Nomenclature Level', required=True),
 
1446
        'location_id': fields.many2one('stock.location', string='Location', required=True),
 
1447
    }
 
1448
 
 
1449
stock_location_chained_options()
 
1450
 
 
1451
 
 
1452
class ir_values(osv.osv):
 
1453
    _name = 'ir.values'
 
1454
    _inherit = 'ir.values'
 
1455
 
 
1456
    def get(self, cr, uid, key, key2, models, meta=False, context=None, res_id_req=False, without_user=True, key2_req=True):
 
1457
        if context is None:
 
1458
            context = {}
 
1459
        values = super(ir_values, self).get(cr, uid, key, key2, models, meta, context, res_id_req, without_user, key2_req)
 
1460
        trans_obj = self.pool.get('ir.translation')
 
1461
        new_values = values
 
1462
        move_accepted_values = {'client_action_multi': [],
 
1463
                                    'client_print_multi': [],
 
1464
                                    'client_action_relate': ['act_relate_picking'],
 
1465
                                    'tree_but_action': [],
 
1466
                                    'tree_but_open': []}
 
1467
        
 
1468
        incoming_accepted_values = {'client_action_multi': ['act_stock_return_picking', 'action_stock_invoice_onshipping'],
 
1469
                                    'client_print_multi': ['Reception'],
 
1470
                                    'client_action_relate': ['View_log_stock.picking'],
 
1471
                                    'tree_but_action': [],
 
1472
                                    'tree_but_open': []}
 
1473
        
 
1474
        internal_accepted_values = {'client_action_multi': [],
 
1475
                                    'client_print_multi': ['Labels'],
 
1476
                                    'client_action_relate': [],
 
1477
                                    'tree_but_action': [],
 
1478
                                    'tree_but_open': []}
 
1479
        
 
1480
        delivery_accepted_values = {'client_action_multi': [],
 
1481
                                    'client_print_multi': ['Labels'],
 
1482
                                    'client_action_relate': [''],
 
1483
                                    'tree_but_action': [],
 
1484
                                    'tree_but_open': []}
 
1485
        
 
1486
        picking_accepted_values = {'client_action_multi': [],
 
1487
                                    'client_print_multi': ['Picking Ticket', 'Pre-Packing List', 'Labels'],
 
1488
                                    'client_action_relate': [''],
 
1489
                                    'tree_but_action': [],
 
1490
                                    'tree_but_open': []}
 
1491
        
 
1492
        if 'stock.move' in [x[0] for x in models]:
 
1493
            new_values = []
 
1494
            Destruction_Report = trans_obj.tr_view(cr, 'Destruction Report', context)
 
1495
            for v in values:
 
1496
                if key == 'action' and v[1] in move_accepted_values[key2]:
 
1497
                    new_values.append(v)
 
1498
                elif context.get('_terp_view_name', False) == Destruction_Report:
 
1499
                    new_values.append(v)
 
1500
        elif context.get('picking_type', False) == 'incoming_shipment' and 'stock.picking' in [x[0] for x in models]:
 
1501
            new_values = []
 
1502
            for v in values:
 
1503
                if key == 'action' and v[1] in incoming_accepted_values[key2]:
 
1504
                    new_values.append(v)
 
1505
        elif context.get('picking_type', False) == 'internal_move' and 'stock.picking' in [x[0] for x in models]:
 
1506
            new_values = []
 
1507
            for v in values:
 
1508
                if key == 'action' and v[1] in internal_accepted_values[key2]:
 
1509
                    new_values.append(v)
 
1510
        elif context.get('picking_type', False) == 'delivery_order' and 'stock.picking' in [x[0] for x in models]:
 
1511
            new_values = []
 
1512
            for v in values:
 
1513
                if key == 'action' and v[1] in delivery_accepted_values[key2]:
 
1514
                    new_values.append(v)
 
1515
        elif context.get('picking_type', False) == 'picking_ticket' and 'stock.picking' in [x[0] for x in models]:
 
1516
            new_values = []
 
1517
            for v in values:
 
1518
                if key == 'action' and v[1] in picking_accepted_values[key2]:
 
1519
                    new_values.append(v)
 
1520
        return new_values
 
1521
 
 
1522
ir_values()