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

« back to all changes in this revision

Viewing changes to stock_override/stock.py

  • Committer: Quentin THEURET
  • Date: 2016-03-04 11:30:53 UTC
  • Revision ID: qt@tempo-consulting.fr-20160304113053-abbygzrfvf0zpuxw
US-826 [FIX] Parent_id is an integer, not a many2one

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
 
21
21
from datetime import datetime
22
22
from dateutil.relativedelta import relativedelta
 
23
from itertools import groupby
 
24
import logging
 
25
from operator import itemgetter
 
26
from os import path
23
27
import time
24
 
from operator import itemgetter
25
 
from itertools import groupby
26
28
 
 
29
import netsvc
27
30
from osv import fields, osv
 
31
import tools
28
32
from tools.translate import _
29
 
import netsvc
30
 
import tools
 
33
 
31
34
import decimal_precision as dp
32
 
import logging
 
35
from msf_partner import PARTNER_TYPE
 
36
from order_types.stock import check_cp_rw
 
37
from order_types.stock import check_rw_warning
33
38
 
34
39
 
35
40
#----------------------------------------------------------
39
44
    _name = 'procurement.order'
40
45
    _inherit = 'procurement.order'
41
46
 
 
47
    def create(self, cr, uid, vals, context=None):
 
48
        '''
 
49
        create method for filling flag from yml tests
 
50
        '''
 
51
        if context is None:
 
52
            context = {}
 
53
        return super(procurement_order, self).create(cr, uid, vals, context=context)
 
54
 
 
55
    # @@@override: procurement>procurement.order->action_confirm()
42
56
    def action_confirm(self, cr, uid, ids, context=None):
43
57
        """ Confirms procurement and writes exception message if any.
44
58
        @return: True
45
59
        """
46
60
        move_obj = self.pool.get('stock.move')
 
61
        data_obj = self.pool.get('ir.model.data')
 
62
 
47
63
        for procurement in self.browse(cr, uid, ids, context=context):
48
64
            if procurement.product_qty <= 0.00:
49
65
                raise osv.except_osv(_('Data Insufficient !'),
50
66
                    _('Please check the Quantity in Procurement Order(s), it should not be less than 1!'))
51
67
            if procurement.product_id.type in ('product', 'consu'):
52
68
                if not procurement.move_id:
53
 
                    source = procurement.location_id.id
54
 
                    reason_type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_other')[1]
55
69
                    if procurement.procure_method == 'make_to_order':
56
 
                        reason_type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_external_supply')[1]
 
70
                        reason_type_id = data_obj.get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_external_supply')[1]
57
71
                        source = procurement.product_id.product_tmpl_id.property_stock_procurement.id
 
72
                    else:
 
73
                        source = procurement.location_id.id
 
74
                        reason_type_id = data_obj.get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_other')[1]
58
75
                    id = move_obj.create(cr, uid, {
59
76
                        'name': procurement.name,
60
77
                        'location_id': source,
69
86
                        'reason_type_id': reason_type_id,
70
87
                    })
71
88
                    move_obj.action_confirm(cr, uid, [id], context=context)
 
89
                    if procurement.procure_method == 'make_to_order':
 
90
                        move_obj.write(cr, uid, [id], {'state': 'hidden'}, context=context)
72
91
                    self.write(cr, uid, [procurement.id], {'move_id': id, 'close_move': 1})
73
92
        self.write(cr, uid, ids, {'state': 'confirmed', 'message': ''})
74
93
        return True
75
 
    
 
94
    # @@@END override: procurement>procurement.order->action_confirm()
 
95
 
 
96
    def copy_data(self, cr, uid, id, default=None, context=None):
 
97
        '''
 
98
        reset link to purchase order from update of on order purchase order
 
99
        '''
 
100
        if not default:
 
101
            default = {}
 
102
        default.update({'so_back_update_dest_po_id_procurement_order': False,
 
103
                        'so_back_update_dest_pol_id_procurement_order': False})
 
104
        return super(procurement_order, self).copy_data(cr, uid, id, default, context=context)
 
105
 
 
106
    _columns = {'from_yml_test': fields.boolean('Only used to pass addons unit test', readonly=True, help='Never set this field to true !'),
 
107
                # this field is used when the po is modified during on order process, and the so must be modified accordingly
 
108
                # the resulting new purchase order line will be merged in specified po_id
 
109
                'so_back_update_dest_po_id_procurement_order': fields.many2one('purchase.order', string='Destination of new purchase order line', readonly=True),
 
110
                'so_back_update_dest_pol_id_procurement_order': fields.many2one('purchase.order.line', string='Original purchase order line', readonly=True),
 
111
                }
 
112
 
 
113
    _defaults = {'from_yml_test': lambda *a: False,
 
114
                 }
 
115
 
76
116
procurement_order()
77
117
 
78
118
 
82
122
class stock_picking(osv.osv):
83
123
    _inherit = "stock.picking"
84
124
    _description = "Picking List"
85
 
    
86
 
    
87
 
    def _do_partial_hook(self, cr, uid, ids, context, *args, **kwargs):
88
 
        '''
89
 
        hook to update defaults data
 
125
 
 
126
    def _hook_state_list(self, cr, uid, *args, **kwargs):
 
127
        '''
 
128
        Change terms into states list
 
129
        '''
 
130
        state_list = kwargs['state_list']
 
131
 
 
132
        state_list['done'] = _('is closed.')
 
133
        state_list['shipped'] = _('is shipped.')  # UF-1617: New state for the IN of partial shipment
 
134
 
 
135
        return state_list
 
136
 
 
137
    def _get_stock_picking_from_partner_ids(self, cr, uid, ids, context=None):
 
138
        '''
 
139
        ids represents the ids of res.partner objects for which values have changed
 
140
 
 
141
        return the list of ids of stock.picking objects which need to get their fields updated
 
142
 
 
143
        self is res.partner object
 
144
        '''
 
145
        # Some verifications
 
146
        if context is None:
 
147
            context = {}
 
148
        if isinstance(ids, (int, long)):
 
149
            ids = [ids]
 
150
 
 
151
        pick_obj = self.pool.get('stock.picking')
 
152
        result = pick_obj.search(cr, uid, [('partner_id2', 'in', ids)], context=context)
 
153
        return result
 
154
 
 
155
    def _vals_get_stock_ov(self, cr, uid, ids, fields, arg, context=None):
 
156
        '''
 
157
        multi fields function method
 
158
        '''
 
159
        # Some verifications
 
160
        if context is None:
 
161
            context = {}
 
162
        if isinstance(ids, (int, long)):
 
163
            ids = [ids]
 
164
 
 
165
        result = {}
 
166
        for obj in self.browse(cr, uid, ids, context=context):
 
167
            result[obj.id] = {}
 
168
            for f in fields:
 
169
                result[obj.id].update({f:False})
 
170
            if obj.partner_id2:
 
171
                result[obj.id].update({'partner_type_stock_picking': obj.partner_id2.partner_type})
 
172
 
 
173
        return result
 
174
 
 
175
    def _get_inactive_product(self, cr, uid, ids, field_name, args, context=None):
 
176
        res = {}
 
177
        for pick in self.browse(cr, uid, ids, context=context):
 
178
            res[pick.id] = False
 
179
            for line in pick.move_lines:
 
180
                if line.inactive_product:
 
181
                    res[pick.id] = True
 
182
                    break
 
183
 
 
184
        return res
 
185
 
 
186
    def _get_is_esc(self, cr, uid, ids, field_name, args, context=None):
 
187
        '''
 
188
        Return True if the partner is an ESC
 
189
        '''
 
190
        if isinstance(ids, (int, long)):
 
191
            ids = [ids]
 
192
 
 
193
        res = {}
 
194
 
 
195
        for pick in self.browse(cr, uid, ids, context=context):
 
196
            res[pick.id] = pick.partner_id2 and pick.partner_id2.partner_type == 'esc' or False
 
197
 
 
198
        return res
 
199
 
 
200
    def _get_dpo_incoming(self, cr, uid, ids, field_name, args, context=None):
 
201
        '''
 
202
        Return True if the picking is an incoming and if one the stock move are linked to dpo_line
 
203
        '''
 
204
        if isinstance(ids, (int, long)):
 
205
            ids = [ids]
 
206
 
 
207
        res = {}
 
208
        for pick in self.browse(cr, uid, ids, context=context):
 
209
            res[pick.id] = {'dpo_incoming': False,
 
210
                            'dpo_out': False}
 
211
            if pick.type == 'in':
 
212
                for move in pick.move_lines:
 
213
                    if move.sync_dpo or move.dpo_line_id:
 
214
                        res[pick.id]['dpo_incoming'] = True
 
215
                        break
 
216
 
 
217
            if pick.type == 'out' and pick.subtype in ('standard', 'picking'):
 
218
                for move in pick.move_lines:
 
219
                    if move.sync_dpo or move.dpo_line_id:
 
220
                        res[pick.id]['dpo_out'] = True
 
221
                        break
 
222
        return res
 
223
 
 
224
    def _get_dpo_picking_ids(self, cr, uid, ids, context=None):
 
225
        result = []
 
226
        for obj in self.browse(cr, uid, ids, context=context):
 
227
            if obj.picking_id and obj.picking_id.id not in result:
 
228
                result.append(obj.picking_id.id)
 
229
 
 
230
        return result
 
231
 
 
232
    def _get_do_not_sync(self, cr, uid, ids, field_name, args, context=None):
 
233
        res = {}
 
234
 
 
235
        if context is None:
 
236
            context = {}
 
237
 
 
238
        current_company_p_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.partner_id.id
 
239
 
 
240
        for pick in self.browse(cr, uid, ids, context=context):
 
241
            res[pick.id] = False
 
242
            if pick.partner_id.id == current_company_p_id:
 
243
                res[pick.id] = True
 
244
 
 
245
        return res
 
246
 
 
247
    def _src_do_not_sync(self, cr, uid, obj, name, args, context=None):
 
248
        '''
 
249
        Returns picking ticket that do not synched because the partner of the
 
250
        picking is the partner of the current company.
 
251
        '''
 
252
        res = []
 
253
        curr_partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.partner_id.id
 
254
 
 
255
        if context is None:
 
256
            context = {}
 
257
 
 
258
        for arg in args:
 
259
 
 
260
            eq_false = arg[1] == '=' and arg[2] in (False, 'f', 'false', 'False', 0)
 
261
            neq_true = arg[1] in ('!=', '<>') and arg[2] in (True, 't', 'true', 'True', 1)
 
262
            eq_true = arg[1] == '=' and arg[2] in (True, 't', 'true', 'True', 1)
 
263
            neq_false = arg[1] in ('!=', '<>') and arg[2] in (False, 'f', 'false', 'False', 0)
 
264
 
 
265
            if arg[0] == 'do_not_sync' and (eq_false or neq_true):
 
266
                res.append(('partner_id', '!=', curr_partner_id))
 
267
            elif arg[0] == 'do_not_sync' and (eq_true or neq_false):
 
268
                res.append(('partner_id', '=', curr_partner_id))
 
269
 
 
270
        return res
 
271
 
 
272
 
 
273
    _columns = {
 
274
        'state': fields.selection([
 
275
            ('draft', 'Draft'),
 
276
            ('auto', 'Waiting'),
 
277
            ('confirmed', 'Confirmed'),
 
278
            ('assigned', 'Available'),
 
279
            ('shipped', 'Available Shipped'),  # UF-1617: new state of IN for partial shipment
 
280
            ('done', 'Closed'),
 
281
            ('cancel', 'Cancelled'),
 
282
            ('import', 'Import in progress'),
 
283
            ], 'State', readonly=True, select=True,
 
284
            help="* Draft: not confirmed yet and will not be scheduled until confirmed\n"\
 
285
                 "* Confirmed: still waiting for the availability of products\n"\
 
286
                 "* Available: products reserved, simply waiting for confirmation.\n"\
 
287
                 "* Available Shipped: products already shipped at supplier, simply waiting for arrival confirmation.\n"\
 
288
                 "* Waiting: waiting for another move to proceed before it becomes automatically available (e.g. in Make-To-Order flows)\n"\
 
289
                 "* Closed: has been processed, can't be modified or cancelled anymore\n"\
 
290
                 "* Cancelled: has been cancelled, can't be confirmed anymore"),
 
291
        'from_yml_test': fields.boolean('Only used to pass addons unit test', readonly=True, help='Never set this field to true !'),
 
292
        '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)]"),
 
293
        'partner_id2': fields.many2one('res.partner', 'Partner', required=False),
 
294
        'from_wkf': fields.boolean('From wkf'),
 
295
        'from_wkf_sourcing': fields.boolean('From wkf sourcing'),
 
296
        'update_version_from_in_stock_picking': fields.integer(string='Update version following IN processing'),
 
297
        '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,
 
298
                                                      store={'stock.picking': (lambda self, cr, uid, ids, c=None: ids, ['partner_id2'], 10),
 
299
                                                              'res.partner': (_get_stock_picking_from_partner_ids, ['partner_type'], 10), }),
 
300
        'inactive_product': fields.function(_get_inactive_product, method=True, type='boolean', string='Product is inactive', store=False),
 
301
        '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."),
 
302
        'shipment_ref': fields.char(string='Ship Reference', size=256, readonly=True),  # UF-1617: indicating the reference to the SHIP object at supplier
 
303
        'move_lines': fields.one2many('stock.move', 'picking_id', 'Internal Moves', states={'done': [('readonly', True)], 'cancel': [('readonly', True)], 'import': [('readonly', True)]}),
 
304
        'state_before_import': fields.char(size=64, string='State before import', readonly=True),
 
305
        'is_esc': fields.function(_get_is_esc, method=True, string='ESC Partner ?', type='boolean', store=False),
 
306
        'dpo_incoming': fields.function(_get_dpo_incoming, method=True, type='boolean', string='DPO Incoming', multi='dpo',
 
307
                                        store={'stock.move': (_get_dpo_picking_ids, ['sync_dpo', 'dpo_line_id', 'picking_id'], 10,),
 
308
                                               'stock.picking': (lambda self, cr, uid, ids, c={}: ids, ['move_lines'], 10)}),
 
309
        'dpo_out': fields.function(_get_dpo_incoming, method=True, type='boolean', string='DPO Out', multi='dpo',
 
310
                                        store={'stock.move': (_get_dpo_picking_ids, ['sync_dpo', 'dpo_line_id', 'picking_id'], 10,),
 
311
                                               'stock.picking': (lambda self, cr, uid, ids, c={}: ids, ['move_lines'], 10)}),
 
312
        'previous_chained_pick_id': fields.many2one('stock.picking', string='Previous chained picking', ondelete='set null', readonly=True),
 
313
        'do_not_sync': fields.function(
 
314
            _get_do_not_sync,
 
315
            fnct_search=_src_do_not_sync,
 
316
            method=True,
 
317
            type='boolean',
 
318
            string='Do not sync.',
 
319
            store=False,
 
320
        ),
 
321
        'company_id2': fields.many2one('res.partner', string='Company', required=True),
 
322
    }
 
323
 
 
324
    _defaults = {'from_yml_test': lambda *a: False,
 
325
                 'from_wkf': lambda *a: False,
 
326
                 'from_wkf_sourcing': lambda *a: False,
 
327
                 'update_version_from_in_stock_picking': 0,
 
328
                 'fake_type': 'in',
 
329
                 'shipment_ref':False,
 
330
                 'company_id2': lambda s,c,u,ids,ctx=None: s.pool.get('res.users').browse(c,u,u).company_id.partner_id.id,
 
331
                 }
 
332
 
 
333
    def copy_data(self, cr, uid, id, default=None, context=None):
 
334
        if default is None:
 
335
            default = {}
 
336
        if context is None:
 
337
            context = {}
 
338
        default.update(shipment_ref=False)
 
339
 
 
340
        if not 'from_wkf_sourcing' in default:
 
341
            default['from_wkf_sourcing'] = False
 
342
 
 
343
        if not 'previous_chained_pick_id' in default:
 
344
            default['previous_chained_pick_id'] = False
 
345
 
 
346
        return super(stock_picking, self).copy_data(cr, uid, id, default=default, context=context)
 
347
 
 
348
    def _check_active_product(self, cr, uid, ids, context=None):
 
349
        '''
 
350
        Check if the stock picking contains a line with an inactive products
 
351
        '''
 
352
        product_tbd = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_doc_import', 'product_tbd')[1]
 
353
        inactive_lines = self.pool.get('stock.move').search(cr, uid, [('product_id.active', '=', False),
 
354
                                                                      ('product_id', '!=', product_tbd),
 
355
                                                                      ('picking_id', 'in', ids),
 
356
                                                                      ('picking_id.state', 'not in', ['draft', 'cancel', 'done'])],
 
357
                                                                      count=True, context=context)
 
358
 
 
359
        if inactive_lines:
 
360
            plural = inactive_lines == 1 and _('A product has') or _('Some products have')
 
361
            l_plural = inactive_lines == 1 and _('line') or _('lines')
 
362
            p_plural = inactive_lines == 1 and _('this inactive product') or _('those inactive products')
 
363
            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))
 
364
            return False
 
365
        return True
 
366
 
 
367
    _constraints = [
 
368
            (_check_active_product, "You cannot validate this document because it contains a line with an inactive product", ['order_line', 'state'])
 
369
    ]
 
370
 
 
371
    def _check_restriction_line(self, cr, uid, ids, context=None):
 
372
        '''
 
373
        Check restriction on products
 
374
        '''
 
375
        if isinstance(ids, (int, long)):
 
376
            ids = [ids]
 
377
 
 
378
        line_obj = self.pool.get('stock.move')
 
379
        res = True
 
380
 
 
381
        for picking in self.browse(cr, uid, ids, context=context):
 
382
            if picking.type == 'internal' and picking.state not in ('draft', 'done', 'cancel'):
 
383
                res = res and line_obj._check_restriction_line(cr, uid, [x.id for x in picking.move_lines], context=context)
 
384
        return res
 
385
 
 
386
    # UF-2148: override and use only this method when checking the cancel condition: if a line has 0 qty, then whatever state, it is always allowed to be canceled
 
387
    def allow_cancel(self, cr, uid, ids, context=None):
 
388
        for pick in self.browse(cr, uid, ids, context=context):
 
389
            if not pick.move_lines:
 
390
                return True
 
391
            for move in pick.move_lines:
 
392
                if move.state == 'done' and move.product_qty != 0:
 
393
                    raise osv.except_osv(_('Error'), _('You cannot cancel picking because stock move is in done state !'))
 
394
        return True
 
395
 
 
396
 
 
397
    def create(self, cr, uid, vals, context=None):
 
398
        '''
 
399
        create method for filling flag from yml tests
 
400
        '''
 
401
        if context is None:
 
402
            context = {}
 
403
 
 
404
        if not context.get('active_id', False):
 
405
            vals['from_wkf'] = True
 
406
        # in case me make a copy of a stock.picking coming from a workflow
 
407
        if context.get('not_workflow', False):
 
408
            vals['from_wkf'] = False
 
409
 
 
410
        if vals.get('from_wkf') and vals.get('purchase_id'):
 
411
            po = self.pool.get('purchase.order').browse(cr, uid, vals.get('purchase_id'), context=context)
 
412
            for line in po.order_line:
 
413
                if line.procurement_id and line.procurement_id.sale_id:
 
414
                    vals['from_wkf_sourcing'] = True
 
415
                    break
 
416
 
 
417
        if not vals.get('partner_id2') and vals.get('address_id'):
 
418
            addr = self.pool.get('res.partner.address').browse(cr, uid, vals.get('address_id'), context=context)
 
419
            vals['partner_id2'] = addr.partner_id and addr.partner_id.id or False
 
420
 
 
421
        if not vals.get('address_id') and vals.get('partner_id2'):
 
422
            addr = self.pool.get('res.partner').address_get(cr, uid, vals.get('partner_id2'), ['delivery', 'default'])
 
423
            if not addr.get('delivery'):
 
424
                vals['address_id'] = addr.get('default')
 
425
            else:
 
426
                vals['address_id'] = addr.get('delivery')
 
427
 
 
428
        res = super(stock_picking, self).create(cr, uid, vals, context=context)
 
429
 
 
430
        return res
 
431
 
 
432
 
 
433
    def write(self, cr, uid, ids, vals, context=None):
 
434
        '''
 
435
        Update the partner or the address according to the other
 
436
        '''
 
437
        if isinstance(ids, (int, long)):
 
438
            ids = [ids]
 
439
 
 
440
        if not vals.get('address_id') and vals.get('partner_id2'):
 
441
            for pick in self.browse(cr, uid, ids, context=context):
 
442
                if pick.partner_id.id != vals.get('partner_id2'):
 
443
                    addr = self.pool.get('res.partner').address_get(cr, uid, vals.get('partner_id2'), ['delivery', 'default'])
 
444
                    if not addr.get('delivery'):
 
445
                        vals['address_id'] = addr.get('default')
 
446
                    else:
 
447
                        vals['address_id'] = addr.get('delivery')
 
448
 
 
449
        if not vals.get('partner_id2') and vals.get('address_id'):
 
450
            for pick in self.browse(cr, uid, ids, context=context):
 
451
                if pick.address_id.id != vals.get('address_id'):
 
452
                    addr = self.pool.get('res.partner.address').browse(cr, uid, vals.get('address_id'), context=context)
 
453
                    vals['partner_id2'] = addr.partner_id and addr.partner_id.id or False
 
454
 
 
455
        res = super(stock_picking, self).write(cr, uid, ids, vals, context=context)
 
456
 
 
457
        return res
 
458
 
 
459
    def go_to_simulation_screen(self, cr, uid, ids, context=None):
 
460
        '''
 
461
        Return the simulation screen
 
462
        '''
 
463
        simu_obj = self.pool.get('wizard.import.in.simulation.screen')
 
464
        line_obj = self.pool.get('wizard.import.in.line.simulation.screen')
 
465
 
 
466
        if isinstance(ids, (int, long)):
 
467
            ids = [ids]
 
468
 
 
469
        picking_id = ids[0]
 
470
        if not picking_id:
 
471
            raise osv.except_osv(_('Error'), _('No picking defined'))
 
472
 
 
473
        simu_id = simu_obj.create(cr, uid, {'picking_id': picking_id, }, context=context)
 
474
        for move in self.browse(cr, uid, picking_id, context=context).move_lines:
 
475
            if move.state not in ('draft', 'cancel', 'done'):
 
476
                line_obj.create(cr, uid, {'move_id': move.id,
 
477
                                          'simu_id': simu_id,
 
478
                                          'move_product_id': move.product_id and move.product_id.id or False,
 
479
                                          'move_product_qty': move.product_qty or 0.00,
 
480
                                          'move_uom_id': move.product_uom and move.product_uom.id or False,
 
481
                                          'move_price_unit': move.price_unit or move.product_id.standard_price,
 
482
                                          'move_currency_id': move.price_currency_id and move.price_currency_id.id or False,
 
483
                                          'line_number': move.line_number, }, context=context)
 
484
 
 
485
        return {'type': 'ir.actions.act_window',
 
486
                'res_model': 'wizard.import.in.simulation.screen',
 
487
                'view_mode': 'form',
 
488
                'view_type': 'form',
 
489
                'target': 'same',
 
490
                'res_id': simu_id,
 
491
                'context': context}
 
492
 
 
493
    def on_change_partner(self, cr, uid, ids, partner_id, address_id, context=None):
 
494
        '''
 
495
        Change the delivery address when the partner change.
 
496
        '''
 
497
        if context is None:
 
498
            context = {}
 
499
 
 
500
        v = {}
 
501
        d = {}
 
502
 
 
503
        partner = False
 
504
 
 
505
        if not partner_id:
 
506
            v.update({'address_id': False, 'is_esc': False})
 
507
        else:
 
508
            partner = self.pool.get('res.partner').browse(cr, uid, partner_id)
 
509
            d.update({'address_id': [('partner_id', '=', partner_id)]})
 
510
            v.update({'is_esc': partner.partner_type == 'esc'})
 
511
 
 
512
 
 
513
        if address_id:
 
514
            addr = self.pool.get('res.partner.address').browse(cr, uid, address_id, context=context)
 
515
 
 
516
        if not address_id or addr.partner_id.id != partner_id:
 
517
            addr = self.pool.get('res.partner').address_get(cr, uid, partner_id, ['delivery', 'default'])
 
518
            if not addr.get('delivery'):
 
519
                addr = addr.get('default')
 
520
            else:
 
521
                addr = addr.get('delivery')
 
522
 
 
523
            v.update({'address_id': addr})
 
524
 
 
525
        if partner_id and ids:
 
526
            context['partner_id'] = partner_id
 
527
 
 
528
            out_loc_ids = self.pool.get('stock.location').search(cr, uid, [
 
529
                ('outgoing_dest', '=', context['partner_id']),
 
530
            ], order='NO_ORDER', context=context)
 
531
            move_ids = self.pool.get('stock.move').search(cr, uid, [
 
532
                ('picking_id', 'in', ids),
 
533
                ('location_dest_id', 'not in', out_loc_ids),
 
534
            ], limit=1, order='NO_ORDER', context=context)
 
535
            if move_ids:
 
536
                return {
 
537
                    'value': {'partner_id2': False, 'partner_id': False,},
 
538
                    'warning': {
 
539
                        'title': _('Error'),
 
540
                        'message': _("""
 
541
You cannot choose this supplier because some destination locations are not available for this partner.
 
542
"""),
 
543
                    },
 
544
                }
 
545
 
 
546
        return {'value': v,
 
547
                'domain': d}
 
548
 
 
549
    def return_to_state(self, cr, uid, ids, context=None):
 
550
        '''
 
551
        Return to initial state if the picking is 'Import in progress'
 
552
        '''
 
553
        if not context:
 
554
            context = {}
 
555
 
 
556
        if isinstance(ids, (int, long)):
 
557
            ids = [ids]
 
558
 
 
559
        for pick in self.browse(cr, uid, ids, context=context):
 
560
            self.write(cr, uid, [pick.id], {'state': pick.state_before_import}, context=context)
 
561
 
 
562
        return True
 
563
 
 
564
    def set_manually_done(self, cr, uid, ids, all_doc=True, context=None):
 
565
        '''
 
566
        Set the picking to done
 
567
        '''
 
568
        move_ids = []
 
569
 
 
570
        if isinstance(ids, (int, long)):
 
571
            ids = [ids]
 
572
 
 
573
        for pick in self.browse(cr, uid, ids, context=context):
 
574
            for move in pick.move_lines:
 
575
                if move.state not in ('cancel', 'done'):
 
576
                    move_ids.append(move.id)
 
577
 
 
578
        # Set all stock moves to done
 
579
        self.pool.get('stock.move').set_manually_done(cr, uid, move_ids, all_doc=all_doc, context=context)
 
580
 
 
581
        return True
 
582
 
 
583
    @check_cp_rw
 
584
    def force_assign(self, cr, uid, ids, context=None):
 
585
        res = super(stock_picking, self).force_assign(cr, uid, ids)
 
586
        for pick_id in ids:
 
587
            self.infolog(cr, uid, 'Force availability ran on stock.picking id:%s' % pick_id)
 
588
        return res
 
589
 
 
590
    @check_cp_rw
 
591
    def action_assign(self, cr, uid, ids, context=None):
 
592
        res = super(stock_picking, self).action_assign(cr, uid, ids, context=context)
 
593
        for pick_id in ids:
 
594
            self.infolog(cr, uid, 'Check availability ran on stock.picking id:%s' % pick_id)
 
595
        return res
 
596
 
 
597
    @check_cp_rw
 
598
    def cancel_assign(self, cr, uid, ids, *args, **kwargs):
 
599
        res = super(stock_picking, self).cancel_assign(cr, uid, ids)
 
600
        for pick_id in ids:
 
601
            self.infolog(cr, uid, 'Cancel availability ran on stock.picking id:%s' % pick_id)
 
602
        return res
 
603
 
 
604
 
 
605
    @check_rw_warning
 
606
    def call_cancel_wizard(self, cr, uid, ids, context=None):
 
607
        '''
 
608
        Call the wizard of cancelation (ask user if he wants to resource goods)
 
609
        '''
 
610
        for pick_data in self.read(cr, uid, ids, ['sale_id', 'purchase_id', 'subtype', 'state'], context=context):
 
611
            # if draft and shipment is in progress, we cannot cancel
 
612
            if pick_data['subtype'] == 'picking' and pick_data['state'] in ('draft',):
 
613
                if self.has_picking_ticket_in_progress(cr, uid, [pick_data['id']], context=context)[pick_data['id']]:
 
614
                    raise osv.except_osv(_('Warning !'), _('Some Picking Tickets are in progress. Return products to stock from ppl and shipment and      try to cancel again.'))
 
615
            # if not draft or qty does not match, the shipping is already in progress
 
616
            if pick_data['subtype'] == 'picking' and pick_data['state'] in ('done',):
 
617
                raise osv.except_osv(_('Warning !'), _('The shipment process is completed and cannot be canceled!'))
 
618
 
 
619
            if pick_data['sale_id'] or pick_data['purchase_id']:
 
620
                return {'type': 'ir.actions.act_window',
 
621
                        'res_model': 'stock.picking.cancel.wizard',
 
622
                        'view_type': 'form',
 
623
                        'view_mode': 'form',
 
624
                        'target': 'new',
 
625
                        'context': dict(context, active_id=pick_data['id'])}
 
626
 
 
627
        wf_service = netsvc.LocalService("workflow")
 
628
        for id in ids:
 
629
            wf_service.trg_validate(uid, 'stock.picking', id, 'button_cancel', cr)
 
630
 
 
631
        return True
 
632
 
 
633
    def action_cancel(self, cr, uid, ids, context=None):
 
634
        '''
 
635
        Re-source the FO/IR lines if needed
 
636
        '''
 
637
        # Variables
 
638
        wf_service = netsvc.LocalService("workflow")
 
639
 
 
640
        if isinstance(ids, (int, long)):
 
641
            ids = [ids]
 
642
 
 
643
        if context is None:
 
644
            context = {}
 
645
 
 
646
        context['cancel_type'] = 'update_out'
 
647
        res = super(stock_picking, self).action_cancel(cr, uid, ids, context=context)
 
648
 
 
649
        # Re-source the sale.order.line
 
650
        fo_ids = set()
 
651
        for pick in self.browse(cr, uid, ids, context=context):
 
652
            # Don't delete lines if an Available PT is canceled
 
653
            if pick.type == 'out' and pick.subtype == 'picking' and pick.backorder_id and True:
 
654
                continue
 
655
 
 
656
            for move in pick.move_lines:
 
657
                if move.sale_line_id and move.product_qty > 0.00:
 
658
                    fo_ids.add(move.sale_line_id.order_id.id)
 
659
 
 
660
            # If the IN is linked to a PO and has a backorder not closed, change the subflow
 
661
            # of the PO to the backorder
 
662
            if pick.type == 'in' and pick.purchase_id:
 
663
                po_id = pick.purchase_id.id
 
664
                bo_id = False
 
665
                if pick.backorder_id and pick.backorder_id.state not in ('done', 'cancel'):
 
666
                    bo_id = pick.backorder_id.id
 
667
                else:
 
668
                    picking_ids = self.search(cr, uid, [
 
669
                        ('purchase_id', '=', po_id),
 
670
                        ('id', '!=', pick.id),
 
671
                        ('state', 'not in', ['done', 'cancel']),
 
672
                    ], limit=1, context=context)
 
673
                    if picking_ids:
 
674
                        bo_id = picking_ids[0]
 
675
 
 
676
                if bo_id:
 
677
                    netsvc.LocalService("workflow").trg_change_subflow(uid, 'purchase.order', [po_id], 'stock.picking', [pick.id], bo_id, cr)
 
678
 
 
679
        # Run the signal 'ship_corrected' to the FO
 
680
        for fo in fo_ids:
 
681
            wf_service.trg_validate(uid, 'sale.order', fo, 'ship_corrected', cr)
 
682
 
 
683
        return res
 
684
 
 
685
    def _do_partial_hook(self, cr, uid, ids, context=None, *args, **kwargs):
 
686
        '''
 
687
        Please copy this to your module's method also.
 
688
        This hook belongs to the do_partial method from stock_override>stock.py>stock_picking
 
689
 
 
690
        - allow to modify the defaults data for move creation and copy
90
691
        '''
91
692
        defaults = kwargs.get('defaults')
92
693
        assert defaults is not None, 'missing defaults'
93
 
        
 
694
 
94
695
        return defaults
95
 
        
 
696
 
 
697
    def _picking_done_cond(self, cr, uid, ids, context=None, *args, **kwargs):
 
698
        '''
 
699
        Please copy this to your module's method also.
 
700
        This hook belongs to the do_partial method from stock_override>stock.py>stock_picking
 
701
 
 
702
        - allow to conditionally execute the picking processing to done
 
703
        '''
 
704
        return True
 
705
 
 
706
    def _custom_code(self, cr, uid, ids, context=None, *args, **kwargs):
 
707
        '''
 
708
        Please copy this to your module's method also.
 
709
        This hook belongs to the do_partial method from stock_override>stock.py>stock_picking
 
710
 
 
711
        - allow to execute specific custom code before processing picking to done
 
712
        - no supposed to modify partial_datas
 
713
        '''
 
714
        return True
96
715
 
97
716
    # @@@override stock>stock.py>stock_picking>do_partial
98
 
    def do_partial(self, cr, uid, ids, partial_datas, context=None):
 
717
    def do_partial_deprecated(self, cr, uid, ids, partial_datas, context=None):
99
718
        """ Makes partial picking and moves done.
100
719
        @param partial_datas : Dictionary containing details of partial picking
101
720
                          like partner_id, address_id, delivery_date,
102
721
                          delivery moves with product_id, product_qty, uom
103
722
        @return: Dictionary of values
104
723
        """
 
724
        if isinstance(ids, (int, long)):
 
725
            ids = [ids]
 
726
 
105
727
        if context is None:
106
728
            context = {}
107
729
        else:
113
735
        uom_obj = self.pool.get('product.uom')
114
736
        sequence_obj = self.pool.get('ir.sequence')
115
737
        wf_service = netsvc.LocalService("workflow")
 
738
 
 
739
        internal_loc_ids = self.pool.get('stock.location').search(cr, uid, [('usage', '=', 'internal'), ('cross_docking_location_ok', '=', False)])
 
740
        ctx_avg = context.copy()
 
741
        ctx_avg['location'] = internal_loc_ids
116
742
        for pick in self.browse(cr, uid, ids, context=context):
117
743
            new_picking = None
118
 
            complete, too_many, too_few = [], [], []
 
744
            complete, too_many, too_few , not_aval = [], [], [], []
119
745
            move_product_qty = {}
120
746
            prodlot_ids = {}
121
747
            product_avail = {}
122
748
            for move in pick.move_lines:
123
749
                if move.state in ('done', 'cancel'):
124
750
                    continue
125
 
                partial_data = partial_datas.get('move%s'%(move.id), {})
126
 
                #Commented in order to process the less number of stock moves from partial picking wizard
127
 
                #assert partial_data, _('Missing partial picking data for move #%s') % (move.id)
 
751
                elif move.state in ('confirmed'):
 
752
                    not_aval.append(move)
 
753
                    continue
 
754
                partial_data = partial_datas.get('move%s' % (move.id), {})
 
755
                # Commented in order to process the less number of stock moves from partial picking wizard
 
756
                # assert partial_data, _('Missing partial picking data for move #%s') % (move.id)
128
757
                product_qty = partial_data.get('product_qty') or 0.0
129
758
                move_product_qty[move.id] = product_qty
130
759
                product_uom = partial_data.get('product_uom') or False
140
769
                    too_many.append(move)
141
770
 
142
771
                # Average price computation
143
 
                if (pick.type == 'in') and (move.product_id.cost_method == 'average'):
144
 
                    product = product_obj.browse(cr, uid, move.product_id.id)
 
772
                if (pick.type == 'in') and (move.product_id.cost_method == 'average') and not move.location_dest_id.cross_docking_location_ok:
 
773
                    product = product_obj.browse(cr, uid, move.product_id.id, context=ctx_avg)
145
774
                    move_currency_id = move.company_id.currency_id.id
146
775
                    context['currency_id'] = move_currency_id
147
776
                    qty = uom_obj._compute_qty(cr, uid, product_uom, product_qty, product.uom_id.id)
153
782
 
154
783
                    if qty > 0:
155
784
                        new_price = currency_obj.compute(cr, uid, product_currency,
156
 
                                move_currency_id, product_price)
 
785
                                move_currency_id, product_price, round=False, context=context)
157
786
                        new_price = uom_obj._compute_price(cr, uid, product_uom, new_price,
158
787
                                product.uom_id.id)
159
788
                        if product.qty_available <= 0:
162
791
                            # Get the standard price
163
792
                            amount_unit = product.price_get('standard_price', context)[product.id]
164
793
                            new_std_price = ((amount_unit * product_avail[product.id])\
165
 
                                + (new_price * qty))/(product_avail[product.id] + qty)
 
794
                                + (new_price * qty)) / (product_avail[product.id] + qty)
166
795
                        # Write the field according to price type field
167
796
                        product_obj.write(cr, uid, [product.id], {'standard_price': new_std_price})
168
797
 
171
800
                        move_obj.write(cr, uid, [move.id],
172
801
                                {'price_unit': product_price,
173
802
                                 'price_currency_id': product_currency})
174
 
 
 
803
            for move in not_aval:
 
804
                if not new_picking:
 
805
                    new_picking = self.copy(cr, uid, pick.id,
 
806
                            {
 
807
                                'name': sequence_obj.get(cr, uid, 'stock.picking.%s' % (pick.type)),
 
808
                                'move_lines' : [],
 
809
                                'state':'draft',
 
810
                            })
175
811
 
176
812
            for move in too_few:
177
813
                product_qty = move_product_qty[move.id]
178
 
 
179
814
                if not new_picking:
180
815
                    new_picking = self.copy(cr, uid, pick.id,
181
816
                            {
182
 
                                'name': sequence_obj.get(cr, uid, 'stock.picking.%s'%(pick.type)),
 
817
                                'name': sequence_obj.get(cr, uid, 'stock.picking.%s' % (pick.type)),
183
818
                                'move_lines' : [],
184
819
                                'state':'draft',
185
820
                            })
186
821
                if product_qty != 0:
187
822
                    defaults = {
188
823
                            'product_qty' : product_qty,
189
 
                            'product_uos_qty': product_qty, #TODO: put correct uos_qty
 
824
                            'product_uos_qty': product_qty,  # TODO: put correct uos_qty
190
825
                            'picking_id' : new_picking,
191
826
                            'state': 'assigned',
192
827
                            'move_dest_id': False,
193
828
                            'price_unit': move.price_unit,
 
829
                            'processed_stock_move': True,
194
830
                    }
195
831
                    prodlot_id = prodlot_ids[move.id]
196
832
                    if prodlot_id:
202
838
                move_obj.write(cr, uid, [move.id],
203
839
                        {
204
840
                            'product_qty' : move.product_qty - product_qty,
205
 
                            'product_uos_qty':move.product_qty - product_qty, #TODO: put correct uos_qty
 
841
                            'product_uos_qty':move.product_qty - product_qty,  # TODO: put correct uos_qty
 
842
                            'processed_stock_move': True,
206
843
                        })
207
844
 
208
845
            if new_picking:
216
853
                defaults = self._do_partial_hook(cr, uid, ids, context, move=move, partial_datas=partial_datas, defaults=defaults)
217
854
                move_obj.write(cr, uid, [move.id], defaults)
218
855
                # override : end
 
856
 
219
857
            for move in too_many:
220
858
                product_qty = move_product_qty[move.id]
221
859
                defaults = {
222
860
                    'product_qty' : product_qty,
223
 
                    'product_uos_qty': product_qty, #TODO: put correct uos_qty
 
861
                    'product_uos_qty': product_qty,  # TODO: put correct uos_qty
224
862
                }
225
863
                prodlot_id = prodlot_ids.get(move.id)
226
864
                if prodlot_ids.get(move.id):
231
869
                defaults = self._do_partial_hook(cr, uid, ids, context, move=move, partial_datas=partial_datas, defaults=defaults)
232
870
                move_obj.write(cr, uid, [move.id], defaults)
233
871
 
234
 
 
235
872
            # At first we confirm the new picking (if necessary)
236
873
            if new_picking:
 
874
                self.write(cr, uid, [pick.id], {'backorder_id': new_picking})
 
875
                # custom code execution
 
876
                self._custom_code(cr, uid, ids, context=context, partial_datas=partial_datas, concerned_picking=self.browse(cr, uid, new_picking, context=context))
 
877
                # we confirm the new picking after its name was possibly modified by custom code - so the link message (top message) is correct
237
878
                wf_service.trg_validate(uid, 'stock.picking', new_picking, 'button_confirm', cr)
238
879
                # Then we finish the good picking
239
 
                self.write(cr, uid, [pick.id], {'backorder_id': new_picking})
240
 
                self.action_move(cr, uid, [new_picking])
241
 
                wf_service.trg_validate(uid, 'stock.picking', new_picking, 'button_done', cr)
 
880
                if self._picking_done_cond(cr, uid, ids, context=context, partial_datas=partial_datas):
 
881
                    self.action_move(cr, uid, [new_picking])
 
882
                    wf_service.trg_validate(uid, 'stock.picking', new_picking, 'button_done', cr)
 
883
                    # UF-1617: Hook a method to create the sync messages for some extra objects: batch number, asset once the OUT/partial is done
 
884
                    self._hook_create_sync_messages(cr, uid, new_picking, context)
 
885
 
242
886
                wf_service.trg_write(uid, 'stock.picking', pick.id, cr)
243
887
                delivered_pack_id = new_picking
244
888
            else:
245
 
                self.action_move(cr, uid, [pick.id])
246
 
                wf_service.trg_validate(uid, 'stock.picking', pick.id, 'button_done', cr)
 
889
                # custom code execution
 
890
                self._custom_code(cr, uid, ids, context=context, partial_datas=partial_datas, concerned_picking=pick)
 
891
                if self._picking_done_cond(cr, uid, ids, context=context, partial_datas=partial_datas):
 
892
                    self.action_move(cr, uid, [pick.id])
 
893
                    wf_service.trg_validate(uid, 'stock.picking', pick.id, 'button_done', cr)
 
894
                    # UF-1617: Hook a method to create the sync messages for some extra objects: batch number, asset once the OUT/partial is done
 
895
                    self._hook_create_sync_messages(cr, uid, ids, context)
 
896
 
247
897
                delivered_pack_id = pick.id
248
898
 
 
899
            # UF-1617: set the delivered_pack_id (new or original) to become already_shipped
 
900
            self.write(cr, uid, [delivered_pack_id], {'already_shipped': True})
 
901
 
249
902
            delivered_pack = self.browse(cr, uid, delivered_pack_id, context=context)
250
903
            res[pick.id] = {'delivered_picking': delivered_pack.id or False}
251
904
 
252
905
        return res
253
906
    # @@@override end
254
907
 
 
908
    # UF-1617: Empty hook here, to be implemented in sync modules
 
909
    def _hook_create_sync_messages(self, cr, uid, ids, context=None):
 
910
        return True
 
911
 
 
912
    # @@@override stock>stock.py>stock_picking>_get_invoice_type
 
913
    def _get_invoice_type(self, pick):
 
914
        src_usage = dest_usage = None
 
915
        inv_type = None
 
916
        if pick.invoice_state == '2binvoiced':
 
917
            if pick.move_lines:
 
918
                src_usage = pick.move_lines[0].location_id.usage
 
919
                dest_usage = pick.move_lines[0].location_dest_id.usage
 
920
            if pick.type == 'out' and dest_usage == 'supplier':
 
921
                inv_type = 'in_refund'
 
922
            elif pick.type == 'out' and dest_usage == 'customer':
 
923
                inv_type = 'out_invoice'
 
924
            elif (pick.type == 'in' and src_usage == 'supplier') or (pick.type == 'internal'):
 
925
                inv_type = 'in_invoice'
 
926
            elif pick.type == 'in' and src_usage == 'customer':
 
927
                inv_type = 'out_refund'
 
928
            else:
 
929
                inv_type = 'out_invoice'
 
930
        return inv_type
 
931
 
 
932
    def _hook_get_move_ids(self, cr, uid, *args, **kwargs):
 
933
        move_obj = self.pool.get('stock.move')
 
934
        pick = kwargs['pick']
 
935
        move_ids = move_obj.search(cr, uid, [('picking_id', '=', pick.id),
 
936
                                             ('state', 'in', ('waiting', 'confirmed'))], order='prodlot_id, product_qty desc')
 
937
 
 
938
        return move_ids
 
939
 
 
940
    def draft_force_assign(self, cr, uid, ids, context=None):
 
941
        '''
 
942
        Confirm all stock moves
 
943
        '''
 
944
        res = super(stock_picking, self).draft_force_assign(cr, uid, ids)
 
945
 
 
946
        move_obj = self.pool.get('stock.move')
 
947
        move_ids = move_obj.search(cr, uid, [('state', '=', 'draft'), ('picking_id', 'in', ids)], context=context)
 
948
        move_obj.action_confirm(cr, uid, move_ids, context=context)
 
949
 
 
950
        return res
 
951
 
 
952
    def is_invoice_needed(self, cr, uid, sp=None):
 
953
        """
 
954
        Check if invoice is needed. Cases where we do not need invoice:
 
955
        - OUT from scratch (without purchase_id and sale_id) AND stock picking type in internal, external or esc
 
956
        - OUT from FO AND stock picking type in internal, external or esc
 
957
        So all OUT that have internel, external or esc should return FALSE from this method.
 
958
        This means to only accept intermission and intersection invoicing on OUT with reason type "Deliver partner".
 
959
        """
 
960
        res = True
 
961
        if not sp:
 
962
            return res
 
963
        # Fetch some values
 
964
        try:
 
965
            rt_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_deliver_partner')[1]
 
966
        except ValueError:
 
967
            rt_id = False
 
968
        # type out and partner_type in internal, external or esc
 
969
        if sp.type == 'out' and not sp.purchase_id and not sp.sale_id and sp.partner_id.partner_type in ['external', 'internal', 'esc']:
 
970
            res = False
 
971
        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']:
 
972
            # Search all stock moves attached to this one. If one of them is deliver partner, then is_invoice_needed is ok
 
973
            res = False
 
974
            sm_ids = self.pool.get('stock.move').search(cr, uid, [('picking_id', '=', sp.id)])
 
975
            if sm_ids:
 
976
                for sm in self.pool.get('stock.move').browse(cr, uid, sm_ids):
 
977
                    if sm.reason_type_id.id == rt_id:
 
978
                        res = True
 
979
        # partner is itself (those that own the company)
 
980
        company_partner_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.partner_id
 
981
        if sp.partner_id.id == company_partner_id.id:
 
982
            res = False
 
983
        return res
 
984
 
 
985
    def _create_invoice(self, cr, uid, stock_picking):
 
986
        """
 
987
        Creates an invoice for the specified stock picking
 
988
        @param stock_picking browse_record: The stock picking for which to create an invoice
 
989
        """
 
990
        picking_type = False
 
991
        invoice_type = self._get_invoice_type(stock_picking)
 
992
 
 
993
        # Check if no invoice needed
 
994
        if not self.is_invoice_needed(cr, uid, stock_picking):
 
995
            return
 
996
 
 
997
        # we do not create invoice for procurement_request (Internal Request)
 
998
        if not stock_picking.sale_id.procurement_request and stock_picking.subtype == 'standard':
 
999
            if stock_picking.type == 'in' or stock_picking.type == 'internal':
 
1000
                if invoice_type == 'out_refund':
 
1001
                    picking_type = 'sale_refund'
 
1002
                else:
 
1003
                    picking_type = 'purchase'
 
1004
            elif stock_picking.type == 'out':
 
1005
                if invoice_type == 'in_refund':
 
1006
                    picking_type = 'purchase_refund'
 
1007
                else:
 
1008
                    picking_type = 'sale'
 
1009
 
 
1010
            # Set journal type based on picking type
 
1011
            journal_type = picking_type
 
1012
 
 
1013
            # Disturb journal for invoice only on intermission partner type
 
1014
            if stock_picking.partner_id.partner_type == 'intermission':
 
1015
                journal_type = 'intermission'
 
1016
 
 
1017
            # Find appropriate journal
 
1018
            journal_ids = self.pool.get('account.journal').search(cr, uid, [('type', '=', journal_type),
 
1019
                                                                            ('is_current_instance', '=', True)])
 
1020
            if not journal_ids:
 
1021
                raise osv.except_osv(_('Warning'), _('No journal of type %s found when trying to create invoice for picking %s!') % (journal_type, stock_picking.name))
 
1022
 
 
1023
            # Create invoice
 
1024
            self.action_invoice_create(cr, uid, [stock_picking.id], journal_ids[0], False, invoice_type, {})
 
1025
 
 
1026
    def action_done(self, cr, uid, ids, context=None):
 
1027
        """
 
1028
        Create automatically invoice or NOT (regarding some criteria in is_invoice_needed)
 
1029
        """
 
1030
        res = super(stock_picking, self).action_done(cr, uid, ids, context=context)
 
1031
 
 
1032
        if res:
 
1033
            if isinstance(ids, (int, long)):
 
1034
                ids = [ids]
 
1035
            for sp in self.browse(cr, uid, ids):
 
1036
                prog_id = self.update_processing_info(cr, uid, sp.id, False, {
 
1037
                   'close_in': _('Invoice creation in progress'),
 
1038
                }, context=context)
 
1039
                # If the IN is linked to a PO and has a backorder not closed, change the subflow
 
1040
                # of the PO to the backorder
 
1041
                if sp.type == 'in' and sp.purchase_id:
 
1042
                    po_id = sp.purchase_id.id
 
1043
                    bo_id = False
 
1044
                    if sp.backorder_id and sp.backorder_id.state not in ('done', 'cancel'):
 
1045
                        bo_id = sp.backorder_id.id
 
1046
                    else:
 
1047
                        picking_ids = self.search(cr, uid, [
 
1048
                            ('purchase_id', '=', po_id),
 
1049
                            ('id', '!=', sp.id),
 
1050
                            ('state', 'not in', ['done', 'cancel']),
 
1051
                        ], limit=1, context=context)
 
1052
                        if picking_ids:
 
1053
                            bo_id = picking_ids[0]
 
1054
 
 
1055
                    if bo_id:
 
1056
                        netsvc.LocalService("workflow").trg_change_subflow(uid, 'purchase.order', [po_id], 'stock.picking', [sp.id], bo_id, cr)
 
1057
 
 
1058
                self._create_invoice(cr, uid, sp)
 
1059
 
 
1060
        return res
 
1061
 
 
1062
    def _get_price_unit_invoice(self, cr, uid, move_line, type):
 
1063
        '''
 
1064
        Update the Unit price according to the UoM received and the UoM ordered
 
1065
        '''
 
1066
        res = super(stock_picking, self)._get_price_unit_invoice(cr, uid, move_line, type)
 
1067
        if type == 'in_refund':
 
1068
            if move_line.picking_id and move_line.picking_id.purchase_id:
 
1069
                po_line_obj = self.pool.get('purchase.order.line')
 
1070
                po_line_id = po_line_obj.search(cr, uid, [('order_id', '=', move_line.picking_id.purchase_id.id),
 
1071
                    ('product_id', '=', move_line.product_id.id),
 
1072
                    ('state', '!=', 'cancel')
 
1073
                    ], limit=1)
 
1074
                if po_line_id:
 
1075
                    return po_line_obj.read(cr, uid, po_line_id[0], ['price_unit'])['price_unit']
 
1076
 
 
1077
        if move_line.purchase_line_id:
 
1078
            po_uom_id = move_line.purchase_line_id.product_uom.id
 
1079
            move_uom_id = move_line.product_uom.id
 
1080
            uom_ratio = self.pool.get('product.uom')._compute_price(cr, uid, move_uom_id, 1, po_uom_id)
 
1081
            return res / uom_ratio
 
1082
 
 
1083
        return res
 
1084
 
 
1085
    def action_invoice_create(self, cr, uid, ids, journal_id=False, group=False, type='out_invoice', context=None):
 
1086
        """
 
1087
        Attach an intermission journal to the Intermission Voucher IN/OUT if partner type is intermission from the picking.
 
1088
        Prepare intermission voucher IN/OUT
 
1089
        Change invoice purchase_list field to TRUE if this picking come from a PO which is 'purchase_list'
 
1090
        """
 
1091
        if isinstance(ids, (int, long)):
 
1092
            ids = [ids]
 
1093
 
 
1094
        if not context:
 
1095
            context = {}
 
1096
        res = super(stock_picking, self).action_invoice_create(cr, uid, ids, journal_id, group, type, context)
 
1097
        intermission_journal_ids = self.pool.get('account.journal').search(cr, uid, [('type', '=', 'intermission'),
 
1098
                                                                                     ('is_current_instance', '=', True)])
 
1099
        company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
 
1100
        intermission_default_account = company.intermission_default_counterpart
 
1101
        for pick in self.browse(cr, uid, [x for x in res]):
 
1102
            # Check if PO and PO is purchase_list
 
1103
            if pick.purchase_id and pick.purchase_id.order_type and pick.purchase_id.order_type == 'purchase_list':
 
1104
                inv_id = res[pick.id]
 
1105
                self.pool.get('account.invoice').write(cr, uid, [inv_id], {'purchase_list': True})
 
1106
            # Check intermission
 
1107
            if pick.partner_id.partner_type == 'intermission':
 
1108
                inv_id = res[pick.id]
 
1109
                if not intermission_journal_ids:
 
1110
                    raise osv.except_osv(_('Error'), _('No Intermission journal found!'))
 
1111
                if not intermission_default_account or not intermission_default_account.id:
 
1112
                    raise osv.except_osv(_('Error'), _('Please configure a default intermission account in Company configuration.'))
 
1113
                self.pool.get('account.invoice').write(cr, uid, [inv_id], {'journal_id': intermission_journal_ids[0],
 
1114
                    'is_intermission': True, 'account_id': intermission_default_account.id, })
 
1115
                # Change currency for this invoice
 
1116
                company_currency = company.currency_id and company.currency_id.id or False
 
1117
                if not company_currency:
 
1118
                    raise osv.except_osv(_('Warning'), _('No company currency found!'))
 
1119
                wiz_account_change = self.pool.get('account.change.currency').create(cr, uid, {'currency_id': company_currency}, context=context)
 
1120
                self.pool.get('account.change.currency').change_currency(cr, uid, [wiz_account_change], context={'active_id': inv_id})
 
1121
        return res
 
1122
 
 
1123
    def action_confirm(self, cr, uid, ids, context=None):
 
1124
        """
 
1125
            stock.picking: action confirm
 
1126
            if INCOMING picking: confirm and check availability
 
1127
        """
 
1128
        super(stock_picking, self).action_confirm(cr, uid, ids, context=context)
 
1129
        move_obj = self.pool.get('stock.move')
 
1130
 
 
1131
        if isinstance(ids, (int, long)):
 
1132
            ids = [ids]
 
1133
        for pick in self.browse(cr, uid, ids):
 
1134
            if pick.move_lines and pick.type == 'in':
 
1135
                not_assigned_move = [x.id for x in pick.move_lines if x.state == 'confirmed']
 
1136
                if not_assigned_move:
 
1137
                    move_obj.action_assign(cr, uid, not_assigned_move)
 
1138
        return True
 
1139
 
 
1140
    def _hook_action_assign_batch(self, cr, uid, ids, context=None):
 
1141
        '''
 
1142
        Please copy this to your module's method also.
 
1143
        This hook belongs to the action_assign method from stock>stock.py>stock_picking class
 
1144
 
 
1145
        -  when product is Expiry date mandatory, we "pre-assign" batch numbers regarding the available quantity
 
1146
        and location logic in addition to FEFO logic (First expired first out).
 
1147
        '''
 
1148
        if isinstance(ids, (int, long)):
 
1149
            ids = [ids]
 
1150
        if context is None:
 
1151
            context = {}
 
1152
        move_obj = self.pool.get('stock.move')
 
1153
        if not context.get('already_checked'):
 
1154
            for pick in self.browse(cr, uid, ids, context=context):
 
1155
                # perishable for perishable or batch management
 
1156
                move_obj.fefo_update(cr, uid, [move.id for move in pick.move_lines if move.product_id.perishable], context)  # FEFO
 
1157
        context['already_checked'] = True
 
1158
        return super(stock_picking, self)._hook_action_assign_batch(cr, uid, ids, context=context)
 
1159
 
 
1160
    # UF-1617: Handle the new state Shipped of IN
 
1161
    def action_shipped_wkf(self, cr, uid, ids, context=None):
 
1162
        """ Changes picking state to assigned.
 
1163
        @return: True
 
1164
        """
 
1165
        self.write(cr, uid, ids, {'state': 'shipped'})
 
1166
        self.log_picking(cr, uid, ids, context=context)
 
1167
        move_obj = self.pool.get('stock.move')
 
1168
 
 
1169
        for pick in self.browse(cr, uid, ids):
 
1170
            if pick.move_lines and pick.type == 'in':
 
1171
                not_assigned_move = [x.id for x in pick.move_lines]
 
1172
                move_obj.write(cr, uid, not_assigned_move, {'state': 'confirmed'})
 
1173
                if not_assigned_move:
 
1174
                    move_obj.action_assign(cr, uid, not_assigned_move)
 
1175
 
 
1176
        return True
 
1177
 
 
1178
    def change_all_location(self, cr, uid, ids, context=None):
 
1179
        '''
 
1180
        Launch the wizard to change all destination location of stock moves
 
1181
        '''
 
1182
        if not context:
 
1183
            context = {}
 
1184
 
 
1185
        if isinstance(ids, (int, long)):
 
1186
            ids = [ids]
 
1187
 
 
1188
        return {'type': 'ir.actions.act_window',
 
1189
                'res_model': 'change.dest.location',
 
1190
                'view_type': 'form',
 
1191
                'view_mode': 'form',
 
1192
                'res_id': self.pool.get('change.dest.location').create(cr, uid, {'picking_id': ids[0]}, context=context),
 
1193
                'context': context,
 
1194
                'target': 'new'}
255
1195
 
256
1196
stock_picking()
257
1197
 
268
1208
    _inherit = "stock.move"
269
1209
    _description = "Stock Move with hook"
270
1210
 
 
1211
    def set_manually_done(self, cr, uid, ids, all_doc=True, context=None):
 
1212
        '''
 
1213
        Set the stock move to manually done
 
1214
        '''
 
1215
        return self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
 
1216
 
 
1217
    def _get_from_dpo(self, cr, uid, ids, field_name, args, context=None):
 
1218
        '''
 
1219
        Return True if the move has a dpo_id
 
1220
        '''
 
1221
        res = {}
 
1222
        if isinstance(ids, (int, long)):
 
1223
            ids = [ids]
 
1224
 
 
1225
        for move in self.browse(cr, uid, ids, context=context):
 
1226
            res[move.id] = False
 
1227
            if move.dpo_id:
 
1228
                res[move.id] = True
 
1229
 
 
1230
        return res
 
1231
 
 
1232
    def _search_from_dpo(self, cr, uid, obj, name, args, context=None):
 
1233
        '''
 
1234
        Returns the list of moves from or not from DPO
 
1235
        '''
 
1236
        for arg in args:
 
1237
            if arg[0] == 'from_dpo' and arg[1] == '=':
 
1238
                return [('dpo_id', '!=', False)]
 
1239
            elif arg[0] == 'from_dpo' and arg[1] in ('!=', '<>'):
 
1240
                return [('dpo_id', '=', False)]
 
1241
 
 
1242
        return []
 
1243
 
 
1244
    def _default_location_destination(self, cr, uid, context=None):
 
1245
        if not context:
 
1246
            context = {}
 
1247
        partner_id = context.get('partner_id')
 
1248
        company_part_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.partner_id.id
 
1249
        if context.get('picking_type') == 'out':
 
1250
            if partner_id != company_part_id:
 
1251
                wh_ids = self.pool.get('stock.warehouse').search(cr, uid, [])
 
1252
                if wh_ids:
 
1253
                    return self.pool.get('stock.warehouse').browse(cr, uid, wh_ids[0]).lot_output_id.id
 
1254
 
 
1255
        return False
 
1256
 
 
1257
    def _get_inactive_product(self, cr, uid, ids, field_name, args, context=None):
 
1258
        '''
 
1259
        Fill the error message if the product of the line is inactive
 
1260
        '''
 
1261
        res = {}
 
1262
        product_tbd = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_doc_import', 'product_tbd')[1]
 
1263
        if isinstance(ids, (int, long)):
 
1264
            ids = [ids]
 
1265
 
 
1266
        for line in self.browse(cr, uid, ids, context=context):
 
1267
            res[line.id] = {'inactive_product': False,
 
1268
                            'inactive_error': ''}
 
1269
            if line.picking_id and line.picking_id.state not in ('cancel', 'done') and line.product_id and line.product_id.id != product_tbd and not line.product_id.active:
 
1270
                res[line.id] = {'inactive_product': True,
 
1271
                                'inactive_error': _('The product in line is inactive !')}
 
1272
 
 
1273
        return res
 
1274
 
 
1275
    def _is_expired_lot(self, cr, uid, ids, field_name, args, context=None):
 
1276
        '''
 
1277
        Return True if the lot of stock move is expired
 
1278
        '''
 
1279
        res = {}
 
1280
 
 
1281
        if isinstance(ids, (int, long)):
 
1282
            ids = [ids]
 
1283
 
 
1284
        product_tbd = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_doc_import', 'product_tbd')[1]
 
1285
        for move in self.browse(cr, uid, ids, context=context):
 
1286
            res[move.id] = {'expired_lot': False, 'product_tbd': False}
 
1287
            if move.prodlot_id and move.prodlot_id.is_expired:
 
1288
                res[move.id]['expired_lot'] = True
 
1289
 
 
1290
            if move.product_id.id == product_tbd:
 
1291
                res[move.id]['product_tbd'] = True
 
1292
 
 
1293
        return res
 
1294
 
 
1295
    def _is_price_changed(self, cr, uid, ids, field_name, args, context=None):
 
1296
        if isinstance(ids, (int, long)):
 
1297
            ids = [ids]
 
1298
 
 
1299
        res = {}
 
1300
        for m in self.browse(cr, uid, ids, context=context):
 
1301
            res[m.id] = False
 
1302
            if m.purchase_line_id and abs(m.purchase_line_id.price_unit - m.price_unit) > 10**-3:
 
1303
                res[m.id] = True
 
1304
 
 
1305
        return res
 
1306
 
 
1307
    _columns = {
 
1308
        '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)"),
 
1309
        'state': fields.selection([('draft', 'Draft'), ('waiting', 'Waiting'), ('confirmed', 'Not Available'), ('assigned', 'Available'), ('done', 'Closed'), ('cancel', 'Cancelled'), ('hidden', 'Hidden')], 'State', readonly=True, select=True,
 
1310
              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\'.\
 
1311
              \nThe state is \'Waiting\' if the move is waiting for another one.'),
 
1312
        '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)]"),
 
1313
        'partner_id2': fields.many2one('res.partner', 'Partner', required=False),
 
1314
        'already_confirmed': fields.boolean(string='Already confirmed'),
 
1315
        'dpo_id': fields.many2one('purchase.order', string='Direct PO', help='PO from where this stock move is sourced.'),
 
1316
        'dpo_line_id': fields.integer(string='Direct PO line', help='PO line from where this stock move is sourced (for sync. engine).'),
 
1317
        'from_dpo': fields.function(_get_from_dpo, fnct_search=_search_from_dpo, type='boolean', method=True, store=False, string='From DPO ?'),
 
1318
        'sync_dpo': fields.boolean(string='Sync. DPO'),
 
1319
        'from_wkf_line': fields.related('picking_id', 'from_wkf', type='boolean', string='Internal use: from wkf'),
 
1320
        'fake_state': fields.related('state', type='char', store=False, string="Internal use"),
 
1321
        'processed_stock_move': fields.boolean(string='Processed Stock Move'),
 
1322
        'inactive_product': fields.function(_get_inactive_product, method=True, type='boolean', string='Product is inactive', store=False, multi='inactive'),
 
1323
        'inactive_error': fields.function(_get_inactive_product, method=True, type='char', string='Error', store=False, multi='inactive'),
 
1324
        'to_correct_ok': fields.boolean(string='Line to correct'),
 
1325
        'text_error': fields.text(string='Error', readonly=True),
 
1326
        'inventory_ids': fields.many2many('stock.inventory', 'stock_inventory_move_rel', 'move_id', 'inventory_id', 'Created Moves'),
 
1327
        'expired_lot': fields.function(_is_expired_lot, method=True, type='boolean', string='Lot expired', store=False, multi='attribute'),
 
1328
        'product_tbd': fields.function(_is_expired_lot, method=True, type='boolean', string='TbD', store=False, multi='attribute'),
 
1329
        'has_to_be_resourced': fields.boolean(string='Has to be resourced'),
 
1330
        'from_wkf': fields.related('picking_id', 'from_wkf', type='boolean', string='From wkf'),
 
1331
        'price_changed': fields.function(_is_price_changed, method=True, type='boolean', string='Price changed',
 
1332
            store={
 
1333
                'stock.move': (lambda self, cr, uid, ids, c=None: ids, ['price_unit', 'purchase_order_line'], 10),
 
1334
            },
 
1335
        ),
 
1336
    }
 
1337
 
 
1338
    _defaults = {
 
1339
        'location_dest_id': _default_location_destination,
 
1340
        'processed_stock_move': False,  # to know if the stock move has already been partially or completely processed
 
1341
        'inactive_product': False,
 
1342
        'inactive_error': lambda *a: '',
 
1343
        'has_to_be_resourced': False,
 
1344
    }
 
1345
 
 
1346
    @check_rw_warning
 
1347
    def call_cancel_wizard(self, cr, uid, ids, context=None):
 
1348
        '''
 
1349
        Call the wizard to ask user if he wants to re-source the need
 
1350
        '''
 
1351
        mem_obj = self.pool.get('stock.picking.processing.info')
 
1352
 
 
1353
        if context is None:
 
1354
            context = {}
 
1355
 
 
1356
        if isinstance(ids, (int, long)):
 
1357
            ids = [ids]
 
1358
 
 
1359
        backmove_ids = self.search(cr, uid, [('backmove_id', 'in', ids), ('state', 'not in', ('done', 'cancel'))], context=context)
 
1360
 
 
1361
        for move in self.browse(cr, uid, ids, context=context):
 
1362
            mem_ids = mem_obj.search(cr, uid, [
 
1363
                ('picking_id', '=', move.picking_id.id),
 
1364
                ('end_date', '=', False),
 
1365
            ], context=context)
 
1366
            if mem_ids:
 
1367
                raise osv.except_osv(
 
1368
                    _('Error'),
 
1369
                    _('The processing of the picking is in progress - You can\'t cancel this move.'),
 
1370
                )
 
1371
            if backmove_ids or move.product_qty == 0.00:
 
1372
                raise osv.except_osv(_('Error'), _('Some Picking Tickets are in progress. Return products to stock from ppl and shipment and try to cancel again.'))
 
1373
            if (move.sale_line_id and move.sale_line_id.order_id) or (move.purchase_line_id and move.purchase_line_id.order_id and (move.purchase_line_id.order_id.po_from_ir or move.purchase_line_id.order_id.po_from_fo)):
 
1374
                vals = {'move_id': ids[0]}
 
1375
                if 'from_int' in context:
 
1376
                    """UFTP-29: we are in a INT stock move - line by line cancel
 
1377
                    do not allow Cancel and Resource if move linked to a PO line
 
1378
                    => the INT is sourced from a PO-IN flow
 
1379
                    'It should only be possible to resource an INT created from the sourcing of an IR / FO from stock,
 
1380
                     but not an INT created by an incoming shipment (Origin field having a "PO" ref.)'
 
1381
                    """
 
1382
                    if move.purchase_line_id:
 
1383
                        vals['cancel_only'] = True
 
1384
 
 
1385
                if move.sale_line_id and move.sale_line_id.type == 'make_to_order':
 
1386
                    vals['cancel_only'] = True
 
1387
 
 
1388
                wiz_id = self.pool.get('stock.move.cancel.wizard').create(cr, uid, vals, context=context)
 
1389
 
 
1390
                return {'type': 'ir.actions.act_window',
 
1391
                        'res_model': 'stock.move.cancel.wizard',
 
1392
                        'view_type': 'form',
 
1393
                        'view_mode': 'form',
 
1394
                        'target': 'new',
 
1395
                        'res_id': wiz_id,
 
1396
                        'context': context}
 
1397
 
 
1398
        return self.unlink(cr, uid, ids, context=context)
 
1399
 
 
1400
    def get_price_changed(self, cr, uid, ids, context=None):
 
1401
        if isinstance(ids, (int, long)):
 
1402
            ids = [ids]
 
1403
 
 
1404
        move = self.browse(cr, uid, ids[0], context=context)
 
1405
        if move.price_changed:
 
1406
            func_curr_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.currency_id.id
 
1407
            price_unit = move.price_unit
 
1408
#            price_unit = self.pool.get('res.currency').compute(cr, uid,
 
1409
#                func_curr_id, move.price_currency_id.id, move.price_unit, round=True)
 
1410
            raise osv.except_osv(
 
1411
                _('Information'),
 
1412
                _('The initial unit price (coming from Purchase order line) is %s %s - The new unit price is %s %s') % (
 
1413
                    move.purchase_line_id.price_unit,
 
1414
                    move.purchase_line_id.currency_id.name,
 
1415
                    price_unit,
 
1416
                    move.price_currency_id.name)
 
1417
            )
 
1418
 
 
1419
        return True
 
1420
 
 
1421
    @check_cp_rw
 
1422
    def force_assign(self, cr, uid, ids, context=None):
 
1423
        product_tbd = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_doc_import', 'product_tbd')[1]
 
1424
 
 
1425
        for move in self.browse(cr, uid, ids, context=context):
 
1426
            if move.product_id.id == product_tbd and move.from_wkf_line:
 
1427
                ids.pop(ids.index(move.id))
 
1428
            else:
 
1429
                self.infolog(cr, uid, 'Force availability run on stock move #%s (id:%s) of picking id:%s' % (move.line_number, move.id, move.picking_id.id))
 
1430
 
 
1431
        return super(stock_move, self).force_assign(cr, uid, ids, context=context)
 
1432
 
 
1433
    def _uom_constraint(self, cr, uid, ids, context=None):
 
1434
        for obj in self.browse(cr, uid, ids, context=context):
 
1435
            if not self.pool.get('uom.tools').check_uom(cr, uid, obj.product_id.id, obj.product_uom.id, context):
 
1436
                raise osv.except_osv(_('Error'), _('You have to select a product UOM in the same category than the purchase UOM of the product !'))
 
1437
 
 
1438
        return True
 
1439
 
 
1440
    def _check_restriction_line(self, cr, uid, ids, context=None):
 
1441
        '''
 
1442
        Check if there is restriction on lines
 
1443
        '''
 
1444
        if isinstance(ids, (int, long)):
 
1445
            ids = [ids]
 
1446
 
 
1447
        if not context:
 
1448
            context = {}
 
1449
 
 
1450
        for move in self.browse(cr, uid, ids, context=context):
 
1451
            if move.picking_id and move.picking_id.type == 'internal' and move.product_id:
 
1452
                if not self.pool.get('product.product')._get_restriction_error(cr, uid, move.product_id.id, vals={'constraints': {'location_id': move.location_dest_id}}, context=context):
 
1453
                    return False
 
1454
 
 
1455
        return True
 
1456
 
 
1457
    _constraints = [(_uom_constraint, 'Constraint error on Uom', [])]
 
1458
 
 
1459
    def create(self, cr, uid, vals, context=None):
 
1460
        '''
 
1461
        1/ Add the corresponding line number: (delivery_mechanism)
 
1462
             - if a corresponding purchase order line or sale order line
 
1463
               exist, we take the line number from there
 
1464
        2/ Add subtype on creation if product is specified (product_asset)
 
1465
        3/ Complete info normally generated by javascript on_change function (specific_rules)
 
1466
        4/ Update the partner or the address according to the other (stock_override)
 
1467
        5/ Set default values for data.xml and tests.yml (reason_types)
 
1468
        '''
 
1469
        # Objects
 
1470
        pick_obj = self.pool.get('stock.picking')
 
1471
        seq_obj = self.pool.get('ir.sequence')
 
1472
        prod_obj = self.pool.get('product.product')
 
1473
        data_obj = self.pool.get('ir.model.data')
 
1474
        addr_obj = self.pool.get('res.partner.address')
 
1475
        user_obj = self.pool.get('res.users')
 
1476
        location_obj = self.pool.get('stock.location')
 
1477
        partner_obj = self.pool.get('res.partner')
 
1478
 
 
1479
        if context is None:
 
1480
            context = {}
 
1481
 
 
1482
        id_cross = data_obj.get_object_reference(cr, uid, 'msf_cross_docking', 'stock_location_cross_docking')[1]
 
1483
        id_nonstock = data_obj.get_object_reference(cr, uid, 'stock_override', 'stock_location_non_stockable')[1]
 
1484
        id_pack = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'stock_location_packing')[1]
 
1485
 
 
1486
        # line number correspondance to be checked with Magali
 
1487
        val_type = vals.get('type', False)
 
1488
        picking = False
 
1489
        if vals.get('picking_id', False):
 
1490
            picking = pick_obj.browse(cr, uid, vals['picking_id'], context=context)
 
1491
            if not vals.get('line_number', False):
 
1492
                # new number need - gather the line number form the sequence
 
1493
                sequence_id = picking.move_sequence_id.id
 
1494
                line = seq_obj.get_id(cr, uid, sequence_id, code_or_id='id', context=context)
 
1495
                # update values with line value
 
1496
                vals['line_number'] = line
 
1497
 
 
1498
            if not val_type:
 
1499
                val_type = picking.type
 
1500
 
 
1501
        if vals.get('product_id', False):
 
1502
            product = prod_obj.browse(cr, uid, vals['product_id'], context=context)
 
1503
            vals['subtype'] = product.subtype
 
1504
 
 
1505
            if not context.get('non_stock_noupdate') and vals.get('picking_id') \
 
1506
                                                     and product.type == 'consu' \
 
1507
                                                     and vals.get('location_dest_id') != id_cross:
 
1508
                if vals.get('sale_line_id'):
 
1509
                    if picking.type == 'out':
 
1510
                        vals['location_id'] = id_cross
 
1511
                    else:
 
1512
                        vals['location_id'] = id_nonstock
 
1513
                    vals['location_dest_id'] = id_pack
 
1514
                else:
 
1515
                    if picking.type != 'out':
 
1516
                        vals['location_dest_id'] = id_nonstock
 
1517
 
 
1518
            if product.batch_management:
 
1519
                vals['hidden_batch_management_mandatory'] = True
 
1520
            elif product.perishable:
 
1521
                vals['hidden_perishable_mandatory'] = True
 
1522
            else:
 
1523
                vals.update({'hidden_batch_management_mandatory': False,
 
1524
                             'hidden_perishable_mandatory': False})
 
1525
 
 
1526
        if not vals.get('partner_id2', False):
 
1527
            if vals.get('address_id', False):
 
1528
                addr = addr_obj.read(cr, uid, vals['address_id'], ['partner_id'], context=context)
 
1529
                vals['partner_id2'] = addr['partner_id'] and addr['partner_id'][0] or False
 
1530
            else:
 
1531
                vals['partner_id2'] = user_obj.browse(cr, uid, uid, context=context).company_id.partner_id.id
 
1532
 
 
1533
        if not vals.get('address_id', False) and vals.get('partner_id2', False):
 
1534
            addr = partner_obj.address_get(cr, uid, vals['partner_id2'], ['delivery', 'default'])
 
1535
            vals['address_id'] = addr.get('delivery', addr.get('default', False))
 
1536
 
 
1537
        if val_type == 'in' and not vals.get('date_expected'):
 
1538
            vals['date_expected'] = time.strftime('%Y-%m-%d %H:%M:%S')
 
1539
 
 
1540
        if vals.get('date_expected'):
 
1541
            vals['date'] = vals.get('date_expected')
 
1542
 
 
1543
        if vals.get('location_dest_id', False):
 
1544
            loc_dest_id = location_obj.browse(cr, uid, vals['location_dest_id'], context=context)
 
1545
            if not loc_dest_id.virtual_location:
 
1546
                if loc_dest_id.scrap_location:
 
1547
                    vals['reason_type_id'] = data_obj.get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_scrap')[1]
 
1548
                elif loc_dest_id.usage == 'inventory':
 
1549
                    vals['reason_type_id'] = data_obj.get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_loss')[1]
 
1550
 
 
1551
            # If the source location and teh destination location are the same, the state should be 'Closed'
 
1552
            if vals.get('location_id', False) == vals.get('location_dest_id', False):
 
1553
                vals['state'] = 'done'
 
1554
 
 
1555
        # Change the reason type of the picking if it is not the same
 
1556
        if picking and not context.get('from_claim') and not context.get('from_chaining') \
 
1557
                                                      and vals.get('reason_type_id', False) != picking.reason_type_id.id:
 
1558
            other_type_id = data_obj.get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_other')[1]
 
1559
            pick_obj.write(cr, uid, [picking.id], {'reason_type_id': other_type_id}, context=context)
 
1560
 
 
1561
        return super(stock_move, self).create(cr, uid, vals, context=context)
 
1562
 
 
1563
    def write(self, cr, uid, ids, vals, context=None):
 
1564
        '''
 
1565
        Update the partner or the address according to the other
 
1566
        '''
 
1567
        # Objects
 
1568
        prod_obj = self.pool.get('product.product')
 
1569
        data_obj = self.pool.get('ir.model.data')
 
1570
        loc_obj = self.pool.get('stock.location')
 
1571
        pick_obj = self.pool.get('stock.picking')
 
1572
        addr_obj = self.pool.get('res.partner.address')
 
1573
        partner_obj = self.pool.get('res.partner')
 
1574
 
 
1575
        if context is None:
 
1576
            context = {}
 
1577
 
 
1578
        if isinstance(ids, (int, long)):
 
1579
            ids = [ids]
 
1580
 
 
1581
        product = None
 
1582
        pick_bro = None
 
1583
        id_cross = data_obj.get_object_reference(cr, uid, 'msf_cross_docking', 'stock_location_cross_docking')[1]
 
1584
 
 
1585
        if vals.get('product_id', False):
 
1586
            # complete hidden flags - needed if not created from GUI
 
1587
            product = prod_obj.browse(cr, uid, vals['product_id'], context=context)
 
1588
            vals.update({
 
1589
                'hidden_batch_management_mandatory': product.batch_management,
 
1590
                'hidden_perishable_mandatory': product.perishable,
 
1591
            })
 
1592
 
 
1593
            if vals.get('picking_id'):
 
1594
                pick_bro = pick_obj.browse(cr, uid, vals['picking_id'], context=context)
 
1595
 
 
1596
        if pick_bro and product and product.type == 'consu' and vals.get('location_dest_id') != id_cross:
 
1597
            id_nonstock = data_obj.get_object_reference(cr, uid, 'stock_override', 'stock_location_non_stockable')
 
1598
            if vals.get('sale_line_id'):
 
1599
                id_pack = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'stock_location_packing')
 
1600
                vals.update({
 
1601
                    'location_id': pick_bro.type == 'out' and id_cross or id_nonstock[1],
 
1602
                    'location_dest_id': id_pack[1],
 
1603
                })
 
1604
            elif pick_bro.type != 'out':
 
1605
                vals['location_dest_id'] = id_nonstock[1]
 
1606
 
 
1607
        if vals.get('location_dest_id'):
 
1608
            dest_id = loc_obj.browse(cr, uid, vals['location_dest_id'], context=context)
 
1609
            if dest_id.usage == 'inventory' and not dest_id.virtual_location:
 
1610
                vals['reason_type_id'] = data_obj.get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_loss')[1]
 
1611
            if dest_id.scrap_location and not dest_id.virtual_location:
 
1612
                vals['reason_type_id'] = data_obj.get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_scrap')[1]
 
1613
            # if the source location and the destination location are the same, the state is done
 
1614
            if 'location_id' in vals and vals['location_dest_id'] == vals['location_id']:
 
1615
                vals['state'] = 'done'
 
1616
 
 
1617
        addr = vals.get('address_id')
 
1618
        partner = vals.get('partner_id2')
 
1619
 
 
1620
        cond1 = not addr and partner
 
1621
        cond2 = not partner and addr
 
1622
 
 
1623
        if vals.get('date_expected') or vals.get('reason_type_id') or cond1 or cond2:
 
1624
            for move in self.browse(cr, uid, ids, context=context):
 
1625
                if cond1 and move.partner_id.id != partner:
 
1626
                    addr = partner_obj.address_get(cr, uid, vals.get('partner_id2'), ['delivery', 'default'])
 
1627
                    vals['address_id'] = addr.get('delivery', False) or addr.get('default')
 
1628
 
 
1629
                if cond2 and move.address_id.id != vals.get('address_id'):
 
1630
                    addr = addr_obj.browse(cr, uid, vals.get('address_id'), context=context)
 
1631
                    vals['partner_id2'] = addr.partner_id and addr.partner_id.id or False
 
1632
 
 
1633
                if vals.get('date_expected') and vals.get('state', move.state) not in ('done', 'cancel'):
 
1634
                    vals['date'] = vals.get('date_expected')
 
1635
 
 
1636
                # Change the reason type of the picking if it is not the same
 
1637
                if 'reason_type_id' in vals:
 
1638
                    if move.picking_id and move.picking_id.reason_type_id.id != vals['reason_type_id']:
 
1639
                        other_type_id = data_obj.get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_other')[1]
 
1640
                        pick_obj.write(cr, uid, move.picking_id.id, {'reason_type_id': other_type_id}, context=context)
 
1641
 
 
1642
        return super(stock_move, self).write(cr, uid, ids, vals, context=context)
 
1643
 
 
1644
    def on_change_partner(self, cr, uid, ids, partner_id, address_id, context=None):
 
1645
        '''
 
1646
        Change the delivery address when the partner change.
 
1647
        '''
 
1648
        v = {}
 
1649
        d = {}
 
1650
 
 
1651
        if not partner_id:
 
1652
            v.update({'address_id': False})
 
1653
        else:
 
1654
            d.update({'address_id': [('partner_id', '=', partner_id)]})
 
1655
 
 
1656
 
 
1657
        if address_id:
 
1658
            addr = self.pool.get('res.partner.address').browse(cr, uid, address_id, context=context)
 
1659
 
 
1660
        if not address_id or addr.partner_id.id != partner_id:
 
1661
            addr = self.pool.get('res.partner').address_get(cr, uid, partner_id, ['delivery', 'default'])
 
1662
            if not addr.get('delivery'):
 
1663
                addr = addr.get('default')
 
1664
            else:
 
1665
                addr = addr.get('delivery')
 
1666
 
 
1667
            v.update({'address_id': addr})
 
1668
 
 
1669
 
 
1670
        return {'value': v,
 
1671
                'domain': d}
 
1672
 
 
1673
    def copy(self, cr, uid, id, default=None, context=None):
 
1674
        '''
 
1675
        Remove the already confirmed flag
 
1676
        '''
 
1677
        if default is None:
 
1678
            default = {}
 
1679
        default.update({'already_confirmed':False})
 
1680
 
 
1681
        return super(stock_move, self).copy(cr, uid, id, default, context=context)
 
1682
 
 
1683
    def copy_data(self, cr, uid, id, default=None, context=None):
 
1684
        '''
 
1685
        Remove the dpo_line_id link
 
1686
        '''
 
1687
        if default is None:
 
1688
            default = {}
 
1689
 
 
1690
        if not 'dpo_line_id' in default:
 
1691
            default['dpo_line_id'] = 0
 
1692
 
 
1693
        if not 'sync_dpo' in default:
 
1694
            default['sync_dpo'] = False
 
1695
 
 
1696
        return super(stock_move, self).copy_data(cr, uid, id, default, context=context)
 
1697
 
 
1698
    def fefo_update(self, cr, uid, ids, context=None):
 
1699
        """
 
1700
        Update batch, Expiry Date, Location according to FEFO logic
 
1701
        """
 
1702
        if isinstance(ids, (int, long)):
 
1703
            ids = [ids]
 
1704
        if context is None:
 
1705
            context = {}
 
1706
 
 
1707
        loc_obj = self.pool.get('stock.location')
 
1708
        prodlot_obj = self.pool.get('stock.production.lot')
 
1709
        for move in self.browse(cr, uid, ids, context):
 
1710
            compare_date = context.get('rw_date', False)
 
1711
            move_unlinked = False
 
1712
            if compare_date:
 
1713
                compare_date = datetime.strptime(compare_date[0:10], '%Y-%m-%d')
 
1714
            else:
 
1715
                today = datetime.today()
 
1716
                compare_date = datetime(today.year, today.month, today.day)
 
1717
            # FEFO logic
 
1718
            if move.state == 'assigned' and not move.prodlot_id:  # a check_availability has already been done in action_assign, so we take only the 'assigned' lines
 
1719
                needed_qty = move.product_qty
 
1720
                res = loc_obj.compute_availability(cr, uid, [move.location_id.id], True, move.product_id.id, move.product_uom.id, context=context)
 
1721
                if 'fefo' in res:
 
1722
                    # 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))
 
1723
                    values = {'name': move.name,
 
1724
                              'sale_line_id': move.sale_line_id and move.sale_line_id.id or False,
 
1725
                              'picking_id': move.picking_id.id,
 
1726
                              'product_uom': move.product_uom.id,
 
1727
                              'product_id': move.product_id.id,
 
1728
                              'date_expected': move.date_expected,
 
1729
                              'date': move.date,
 
1730
                              'state': 'assigned',
 
1731
                              'location_dest_id': move.location_dest_id.id,
 
1732
                              'reason_type_id': move.reason_type_id.id,
 
1733
                              }
 
1734
                    for loc in res['fefo']:
 
1735
                        # if source == destination, the state becomes 'done', so we don't do fefo logic in that case
 
1736
                        if not move.location_dest_id.id == loc['location_id']:
 
1737
                            # we ignore the batch that are outdated
 
1738
                            expired_date = prodlot_obj.read(cr, uid, loc['prodlot_id'], ['life_date'], context)['life_date']
 
1739
                            if datetime.strptime(expired_date, "%Y-%m-%d") >= compare_date:
 
1740
                                existed_moves = []
 
1741
                                if not move.move_dest_id:
 
1742
                                    # Search if a stock move with the same location_id and same product_id and same prodlot_id exist
 
1743
                                    existed_moves = self.search(cr, uid, [('picking_id', '!=', False), ('picking_id', '=', move.picking_id.id),
 
1744
                                                                          ('product_id', '=', move.product_id.id), ('product_uom', '=', loc['uom_id']),
 
1745
                                                                          ('line_number', '=', move.line_number), ('location_id', '=', loc['location_id']),
 
1746
                                                                          ('location_dest_id', '=', move.location_dest_id.id), ('prodlot_id', '=', loc['prodlot_id'])], context=context)
 
1747
                                # as long all needed are not fulfilled
 
1748
                                if needed_qty:
 
1749
                                    # if the batch already exists and qty is enough, it is available (assigned)
 
1750
                                    if needed_qty <= loc['qty']:
 
1751
                                        # TODO: Why this condition because move.prodlot_id is always False (e.g. line 1261 of this file)
 
1752
                                        if move.prodlot_id.id == loc['prodlot_id']:
 
1753
                                            self.write(cr, uid, move.id, {'state': 'assigned'}, context)
 
1754
                                        elif existed_moves:
 
1755
                                            exist_move = self.browse(cr, uid, existed_moves[0], context)
 
1756
                                            self.write(cr, uid, [exist_move.id], {'product_qty': needed_qty + exist_move.product_qty}, context)
 
1757
                                            self.write(cr, uid, [move.id], {'state': 'draft'}, context=context)
 
1758
                                            # We update the linked documents
 
1759
                                            self.update_linked_documents(cr, uid, [move.id], exist_move.id, context=context)
 
1760
                                            self.unlink(cr, uid, [move.id], context)
 
1761
                                            move_unlinked = True
 
1762
                                        else:
 
1763
                                            self.write(cr, uid, move.id, {'product_qty': needed_qty, 'product_uom': loc['uom_id'],
 
1764
                                                                          'location_id': loc['location_id'], 'prodlot_id': loc['prodlot_id']}, context)
 
1765
                                        needed_qty = 0.0
 
1766
                                        break
 
1767
                                    elif needed_qty:
 
1768
                                        # we take all available
 
1769
                                        selected_qty = loc['qty']
 
1770
                                        needed_qty -= selected_qty
 
1771
                                        dict_for_create = {}
 
1772
                                        dict_for_create = values.copy()
 
1773
                                        dict_for_create.update({'product_uom': loc['uom_id'], 'product_qty': selected_qty, 'location_id': loc['location_id'], 'prodlot_id': loc['prodlot_id'], 'line_number': move.line_number, 'move_cross_docking_ok': move.move_cross_docking_ok})
 
1774
                                        if existed_moves:
 
1775
                                            exist_move = self.browse(cr, uid, existed_moves[0], context)
 
1776
                                            self.write(cr, uid, [exist_move.id], {'product_qty': selected_qty + exist_move.product_qty}, context)
 
1777
                                        else:
 
1778
                                            self.create(cr, uid, dict_for_create, context)
 
1779
                                        self.write(cr, uid, move.id, {'product_qty': needed_qty})
 
1780
                    # if the batch is outdated, we remove it
 
1781
                    if not context.get('yml_test', False):
 
1782
                        if not move_unlinked and move.expired_date and not datetime.strptime(move.expired_date, "%Y-%m-%d") >= compare_date:
 
1783
                            # Don't remove the batch if the move is a chained move
 
1784
                            if not self.search(cr, uid, [('move_dest_id', '=',
 
1785
                                move.id)], limit=1, order='NO_ORDER', context=context):
 
1786
                                self.write(cr, uid, move.id, {'prodlot_id': False}, context)
 
1787
            elif move.state == 'confirmed':
 
1788
                # we remove the prodlot_id in case that the move is not available
 
1789
                self.write(cr, uid, move.id, {'prodlot_id': False}, context)
 
1790
        return True
 
1791
 
 
1792
    def action_confirm(self, cr, uid, ids, context=None):
 
1793
        '''
 
1794
        Set the bool already confirmed to True
 
1795
        '''
 
1796
        ids = isinstance(ids, (int, long)) and [ids] or ids
 
1797
 
 
1798
        no_product = self.search(cr, uid, [
 
1799
            ('id', 'in', ids),
 
1800
            ('product_qty', '<=', 0.00),
 
1801
        ], limit=1, order='NO_ORDER', context=context)
 
1802
 
 
1803
        if no_product:
 
1804
            raise osv.except_osv(_('Error'), _('You cannot confirm a stock move without quantity.'))
 
1805
 
 
1806
        res = super(stock_move, self).action_confirm(cr, uid, ids, context=context)
 
1807
 
 
1808
        self.write(cr, uid, ids, {'already_confirmed': True}, context=context)
 
1809
 
 
1810
        return res
 
1811
 
 
1812
    def _hook_confirmed_move(self, cr, uid, *args, **kwargs):
 
1813
        '''
 
1814
        Always return True
 
1815
        '''
 
1816
        move = kwargs['move']
 
1817
        if not move.already_confirmed:
 
1818
            self.action_confirm(cr, uid, [move.id])
 
1819
        return True
 
1820
 
 
1821
    def _hook_move_cancel_state(self, cr, uid, *args, **kwargs):
 
1822
        '''
 
1823
        Change the state of the chained move
 
1824
        '''
 
1825
        if kwargs.get('context'):
 
1826
            kwargs['context'].update({'call_unlink': True})
 
1827
        return {'state': 'cancel'}, kwargs.get('context', {})
 
1828
 
 
1829
    def _hook_write_state_stock_move(self, cr, uid, done, notdone, count):
 
1830
        if done:
 
1831
            count += len(done)
 
1832
 
 
1833
            done_ids = []
 
1834
            assigned_ids = []
 
1835
            # If source location == dest location THEN stock move is done.
 
1836
            for line in self.read(cr, uid, done, ['location_id', 'location_dest_id']):
 
1837
                if line.get('location_id') and line.get('location_dest_id') and line.get('location_id') == line.get('location_dest_id'):
 
1838
                    done_ids.append(line['id'])
 
1839
                else:
 
1840
                    assigned_ids.append(line['id'])
 
1841
 
 
1842
            if done_ids:
 
1843
                self.write(cr, uid, done_ids, {'state': 'done'})
 
1844
            if assigned_ids:
 
1845
                self.write(cr, uid, assigned_ids, {'state': 'assigned'})
 
1846
 
 
1847
        if notdone:
 
1848
            self.write(cr, uid, notdone, {'state': 'confirmed'})
 
1849
            self.action_assign(cr, uid, notdone)
 
1850
        return count
 
1851
 
 
1852
    def _hook_check_assign(self, cr, uid, *args, **kwargs):
 
1853
        '''
 
1854
        kwargs['move'] is the current move
 
1855
        '''
 
1856
        move = kwargs['move']
 
1857
        return move.location_id.usage == 'supplier'
 
1858
 
 
1859
    def _hook_cancel_assign_batch(self, cr, uid, ids, context=None):
 
1860
        '''
 
1861
        Please copy this to your module's method also.
 
1862
        This hook belongs to the cancel_assign method from stock>stock.py>stock_move class
 
1863
 
 
1864
        -  it erases the batch number associated if any and reset the source location to the original one.
 
1865
        '''
 
1866
        if isinstance(ids, (int, long)):
 
1867
            ids = [ids]
 
1868
        if context is None:
 
1869
            context = {}
 
1870
 
 
1871
        for line in self.browse(cr, uid, ids, context):
 
1872
            if line.prodlot_id:
 
1873
                self.write(cr, uid, ids, {'prodlot_id': False, 'expired_date': False})
 
1874
            # UF-2426: If the cancel is called from sync, do not change the source location!
 
1875
            if not context.get('sync_message_execution', False) and line.location_id.location_id and line.location_id.location_id.usage != 'view':
 
1876
                self.write(cr, uid, ids, {'location_id': line.location_id.location_id.id})
 
1877
        return True
 
1878
 
 
1879
    def check_assign(self, cr, uid, ids, context=None):
 
1880
        res = super(stock_move, self).check_assign(cr, uid, ids, context=context)
 
1881
        for move_id in ids:
 
1882
            self.infolog(cr, uid, 'Check availability ran on stock.move id:%s' % move_id)
 
1883
        return res
 
1884
 
 
1885
    @check_cp_rw
 
1886
    def cancel_assign(self, cr, uid, ids, context=None):
 
1887
        res = super(stock_move, self).cancel_assign(cr, uid, ids, context=context)
 
1888
        res = []
 
1889
 
 
1890
        fields_to_read = ['picking_id', 'product_id', 'product_uom', 'location_id',
 
1891
                          'product_qty', 'product_uos_qty', 'location_dest_id',
 
1892
                          'prodlot_id', 'asset_id', 'composition_list_id', 'line_number']
 
1893
 
 
1894
        for move_data in self.read(cr, uid, ids, fields_to_read, context=context):
 
1895
            search_domain = [('state', '=', 'confirmed'), ('id', '!=', move_data['id'])]
 
1896
 
 
1897
            self.infolog(cr, uid, 'Cancel availability run on stock move #%s (id:%s) of picking id:%s' % (
 
1898
                move_data['line_number'],
 
1899
                move_data['id'],
 
1900
                move_data['picking_id'][0]))
 
1901
 
 
1902
            for f in fields_to_read:
 
1903
                if f in ('product_qty', 'product_uos_qty'):
 
1904
                    continue
 
1905
                d = move_data[f]
 
1906
                if isinstance(move_data[f], tuple):
 
1907
                    d = move_data[f][0]
 
1908
                search_domain.append((f, '=', d))
 
1909
 
 
1910
            move_ids = self.search(cr, uid, search_domain, context=context)
 
1911
            if move_ids:
 
1912
                move = self.browse(cr, uid, move_ids[0], context=context)
 
1913
                res.append(move.id)
 
1914
                self.write(cr, uid, [move.id], {'product_qty': move.product_qty + move_data['product_qty'],
 
1915
                                                'product_uos_qty': move.product_uos_qty + move_data['product_uos_qty']}, context=context)
 
1916
 
 
1917
                # Update all link objects
 
1918
                proc_ids = self.pool.get('procurement.order').search(cr, uid,
 
1919
                        [('move_id', '=', move_data['id'])], order='NO_ORDER',context=context)
 
1920
                if proc_ids:
 
1921
                    self.pool.get('procurement.order').write(cr, uid, proc_ids, {'move_id': move.id}, context=context)
 
1922
 
 
1923
                pol_ids = self.pool.get('purchase.order.line').search(cr, uid,
 
1924
                        [('move_dest_id', '=', move_data['id'])],
 
1925
                        order='NO_ORDER', context=context)
 
1926
                if pol_ids:
 
1927
                    self.pool.get('purchase.order.line').write(cr, uid, pol_ids, {'move_dest_id': move.id}, context=context)
 
1928
 
 
1929
                move_dest_ids = self.search(cr, uid, [('move_dest_id', '=',
 
1930
                    move_data['id'])], order='NO_ORDER', context=context)
 
1931
                if move_dest_ids:
 
1932
                    self.write(cr, uid, move_dest_ids, {'move_dest_id': move.id}, context=context)
 
1933
 
 
1934
                backmove_ids = self.search(cr, uid, [('backmove_id', '=',
 
1935
                    move_data['id'])], order='NO_ORDER', context=context)
 
1936
                if backmove_ids:
 
1937
                    self.write(cr, uid, backmove_ids, {'backmove_id': move.id}, context=context)
 
1938
 
 
1939
                pack_backmove_ids = self.search(cr, uid,
 
1940
                        [('backmove_packing_id', '=', move_data['id'])],
 
1941
                        order='NO_ORDER', context=context)
 
1942
                if pack_backmove_ids:
 
1943
                    self.write(cr, uid, pack_backmove_ids, {'backmove_packing_id': move.id}, context=context)
 
1944
 
 
1945
                self.write(cr, uid, [move_data['id']], {'state': 'draft'}, context=context)
 
1946
                self.unlink(cr, uid, move_data['id'], context=context)
 
1947
 
 
1948
        return res
 
1949
 
 
1950
    def _hook_copy_stock_move(self, cr, uid, res, move, done, notdone):
 
1951
        while res:
 
1952
            r = res.pop(0)
 
1953
            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]})
 
1954
            if r[2]:
 
1955
                done.append(move_id)
 
1956
            else:
 
1957
                notdone.append(move_id)
 
1958
        return done, notdone
271
1959
 
272
1960
    def _do_partial_hook(self, cr, uid, ids, context, *args, **kwargs):
273
1961
        '''
275
1963
        '''
276
1964
        defaults = kwargs.get('defaults')
277
1965
        assert defaults is not None, 'missing defaults'
278
 
        
 
1966
 
279
1967
        return defaults
280
1968
 
281
1969
 
286
1974
                          like partner_id, address_id, delivery_date, delivery
287
1975
                          moves with product_id, product_qty, uom
288
1976
        """
 
1977
 
 
1978
        if isinstance(ids, (int, long)):
 
1979
            ids = [ids]
 
1980
 
289
1981
        res = {}
290
1982
        picking_obj = self.pool.get('stock.picking')
291
1983
        product_obj = self.pool.get('product.product')
299
1991
        complete, too_many, too_few = [], [], []
300
1992
        move_product_qty = {}
301
1993
        prodlot_ids = {}
 
1994
        internal_loc_ids = self.pool.get('stock.location').search(cr, uid, [('usage', '=', 'internal'), ('cross_docking_location_ok', '=', False)])
 
1995
        ctx_avg = context.copy()
 
1996
        ctx_avg['location'] = internal_loc_ids
302
1997
        for move in self.browse(cr, uid, ids, context=context):
303
1998
            if move.state in ('done', 'cancel'):
304
1999
                continue
305
 
            partial_data = partial_datas.get('move%s'%(move.id), False)
 
2000
            partial_data = partial_datas.get('move%s' % (move.id), False)
306
2001
            assert partial_data, _('Missing partial picking data for move #%s') % (move.id)
307
 
            product_qty = partial_data.get('product_qty',0.0)
 
2002
            product_qty = partial_data.get('product_qty', 0.0)
308
2003
            move_product_qty[move.id] = product_qty
309
 
            product_uom = partial_data.get('product_uom',False)
310
 
            product_price = partial_data.get('product_price',0.0)
311
 
            product_currency = partial_data.get('product_currency',False)
 
2004
            product_uom = partial_data.get('product_uom', False)
 
2005
            product_price = partial_data.get('product_price', 0.0)
 
2006
            product_currency = partial_data.get('product_currency', False)
312
2007
            prodlot_ids[move.id] = partial_data.get('prodlot_id')
313
2008
            if move.product_qty == product_qty:
314
2009
                complete.append(move)
318
2013
                too_many.append(move)
319
2014
 
320
2015
            # Average price computation
321
 
            if (move.picking_id.type == 'in') and (move.product_id.cost_method == 'average'):
322
 
                product = product_obj.browse(cr, uid, move.product_id.id)
 
2016
            if (move.picking_id.type == 'in') and (move.product_id.cost_method == 'average') and not move.location_dest_id.cross_docking_location_ok:
 
2017
                product = product_obj.browse(cr, uid, move.product_id.id, context=ctx_avg)
323
2018
                move_currency_id = move.company_id.currency_id.id
324
2019
                context['currency_id'] = move_currency_id
325
2020
                qty = uom_obj._compute_qty(cr, uid, product_uom, product_qty, product.uom_id.id)
326
2021
                if qty > 0:
327
2022
                    new_price = currency_obj.compute(cr, uid, product_currency,
328
 
                            move_currency_id, product_price)
 
2023
                            move_currency_id, product_price, round=False, context=context)
329
2024
                    new_price = uom_obj._compute_price(cr, uid, product_uom, new_price,
330
2025
                            product.uom_id.id)
331
2026
                    if product.qty_available <= 0:
334
2029
                        # Get the standard price
335
2030
                        amount_unit = product.price_get('standard_price', context)[product.id]
336
2031
                        new_std_price = ((amount_unit * product.qty_available)\
337
 
                            + (new_price * qty))/(product.qty_available + qty)
 
2032
                            + (new_price * qty)) / (product.qty_available + qty)
338
2033
 
339
 
                    product_obj.write(cr, uid, [product.id],{'standard_price': new_std_price})
 
2034
                    product_obj.write(cr, uid, [product.id], {'standard_price': new_std_price})
340
2035
 
341
2036
                    # Record the values that were chosen in the wizard, so they can be
342
2037
                    # used for inventory valuation if real-time valuation is enabled.
403
2098
        return [move.id for move in complete]
404
2099
    # @@@override end
405
2100
 
 
2101
    def _get_destruction_products(self, cr, uid, ids, product_ids=False, context=None, recursive=False):
 
2102
        """ Finds the product quantity and price for particular location.
 
2103
        """
 
2104
        if context is None:
 
2105
            context = {}
 
2106
        if isinstance(ids, (int, long)):
 
2107
            ids = [ids]
 
2108
 
 
2109
        result = []
 
2110
        for move in self.browse(cr, uid, ids, context=context):
 
2111
            # add this move into the list of result
 
2112
            dg_check_flag = ''
 
2113
            if move.dg_check:
 
2114
                dg_check_flag = 'x'
 
2115
 
 
2116
            np_check_flag = ''
 
2117
            if move.np_check:
 
2118
                np_check_flag = 'x'
 
2119
            sub_total = move.product_qty * move.product_id.standard_price
 
2120
 
 
2121
            currency = ''
 
2122
            if move.purchase_line_id and move.purchase_line_id.currency_id:
 
2123
                currency = move.purchase_line_id.currency_id.name
 
2124
            elif move.sale_line_id and move.sale_line_id.currency_id:
 
2125
                currency = move.sale_line_id.currency_id.name
 
2126
 
 
2127
            result.append({
 
2128
                'prod_name': move.product_id.name,
 
2129
                'prod_code': move.product_id.code,
 
2130
                'prod_price': move.product_id.standard_price,
 
2131
                'sub_total': sub_total,
 
2132
                'currency': currency,
 
2133
                'origin': move.origin,
 
2134
                'expired_date': move.expired_date,
 
2135
                'prodlot_id': move.prodlot_id.name,
 
2136
                'dg_check': dg_check_flag,
 
2137
                'np_check': np_check_flag,
 
2138
                'uom': move.product_uom.name,
 
2139
                'prod_qty': move.product_qty,
 
2140
            })
 
2141
        return result
 
2142
 
 
2143
    def in_action_confirm(self, cr, uid, ids, context=None):
 
2144
        """
 
2145
            Incoming: draft or confirmed: validate and assign
 
2146
        """
 
2147
        if isinstance(ids, (int, long)):
 
2148
            ids = [ids]
 
2149
        self.action_confirm(cr, uid, ids, context)
 
2150
        self.action_assign(cr, uid, ids, context)
 
2151
        return True
 
2152
 
 
2153
    # @@@override stock>stock.py>stock_move>_chain_compute
 
2154
    def _chain_compute(self, cr, uid, moves, context=None):
 
2155
        """ Finds whether the location has chained location type or not.
 
2156
        @param moves: Stock moves
 
2157
        @return: Dictionary containing destination location with chained location type.
 
2158
        """
 
2159
        result = {}
 
2160
        if context is None:
 
2161
            context = {}
 
2162
 
 
2163
        moves_by_location = {}
 
2164
        pick_by_journal = {}
 
2165
 
 
2166
        for m in moves:
 
2167
            partner_id = m.picking_id and m.picking_id.address_id and m.picking_id.address_id.partner_id or False
 
2168
            dest = self.pool.get('stock.location').chained_location_get(
 
2169
                cr,
 
2170
                uid,
 
2171
                m.location_dest_id,
 
2172
                partner_id,
 
2173
                m.product_id,
 
2174
                m.product_id.nomen_manda_0,
 
2175
                context
 
2176
            )
 
2177
            if dest and not m.not_chained:
 
2178
                if dest[1] == 'transparent' and context.get('action_confirm', False):
 
2179
                    newdate = (datetime.strptime(m.date, '%Y-%m-%d %H:%M:%S') + relativedelta(days=dest[2] or 0)).strftime('%Y-%m-%d')
 
2180
                    moves_by_location.setdefault(dest[0].id, {}).setdefault(newdate, [])
 
2181
                    moves_by_location[dest[0].id][newdate].append(m.id)
 
2182
                    journal_id = dest[3] or (m.picking_id and m.picking_id.stock_journal_id and m.picking_id.stock_journal_id.id) or False
 
2183
                    pick_by_journal.setdefault(journal_id, set())
 
2184
                    pick_by_journal[journal_id].add(m.picking_id.id)
 
2185
                elif not context.get('action_confirm', False):
 
2186
                    result.setdefault(m.picking_id, [])
 
2187
                    result[m.picking_id].append((m, dest))
 
2188
 
 
2189
        for journal_id, pick_ids in pick_by_journal.iteritems():
 
2190
            if journal_id:
 
2191
                self.pool.get('stock.picking').write(cr, uid, list(pick_ids), {'journal_id': journal_id}, context=context)
 
2192
 
 
2193
        new_moves = []
 
2194
        for location_id in moves_by_location.keys():
 
2195
            for newdate, move_ids in moves_by_location[location_id].iteritems():
 
2196
                self.write(cr, uid, move_ids, {'location_dest_id': location_id,
 
2197
                                               'date': newdate}, context=context)
 
2198
                new_moves.extend(move_ids)
 
2199
 
 
2200
        if new_moves:
 
2201
            new_moves = self.browse(cr, uid, new_moves, context=context)
 
2202
            res2 = self._chain_compute(cr, uid, new_moves, context=context)
 
2203
            for pick_id in res2.keys():
 
2204
                result.setdefault(pick_id, [])
 
2205
                result[pick_id] += res2[pick_id]
 
2206
 
 
2207
        return result
 
2208
    # @@@override end
 
2209
 
 
2210
    # @@@override stock>stock.py>stock_move>_create_chained_picking
 
2211
    def _create_chained_picking(self, cr, uid, pick_name, picking, ptype, move, context=None):
 
2212
        if context is None:
 
2213
            context = {}
 
2214
 
 
2215
        res_obj = self.pool.get('res.company')
 
2216
        picking_obj = self.pool.get('stock.picking')
 
2217
        data_obj = self.pool.get('ir.model.data')
 
2218
 
 
2219
        context['from_chaining'] = True
 
2220
 
 
2221
        reason_type_id = data_obj.get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_internal_move')[1]
 
2222
 
 
2223
        pick_values = {
 
2224
            'name': pick_name,
 
2225
            'origin': tools.ustr(picking.origin or ''),
 
2226
            'type': ptype,
 
2227
            'note': picking.note,
 
2228
            'move_type': picking.move_type,
 
2229
            'auto_picking': move[0][1][1] == 'auto',
 
2230
            'stock_journal_id': move[0][1][3],
 
2231
            'company_id': move[0][1][4] or res_obj._company_default_get(cr, uid, 'stock.company', context=context),
 
2232
            'address_id': picking.address_id.id,
 
2233
            'invoice_state': 'none',
 
2234
            'date': picking.date,
 
2235
            'sale_id': picking.sale_id and picking.sale_id.id or False,
 
2236
            'auto_picking': picking.type == 'in' and any(m.direct_incoming for m in picking.move_lines),
 
2237
            'reason_type_id': reason_type_id,
 
2238
            'previous_chained_pick_id': picking.id,
 
2239
        }
 
2240
 
 
2241
        return picking_obj.create(cr, uid, pick_values, context=context)
 
2242
    # @@@override end
 
2243
 
406
2244
stock_move()
 
2245
 
 
2246
#-----------------------------------------
 
2247
#   Stock location
 
2248
#-----------------------------------------
 
2249
class stock_location(osv.osv):
 
2250
    _name = 'stock.location'
 
2251
    _inherit = 'stock.location'
 
2252
 
 
2253
    def init(self, cr):
 
2254
        """
 
2255
        Load data.xml asap
 
2256
        """
 
2257
        if hasattr(super(stock_location, self), 'init'):
 
2258
            super(stock_location, self).init(cr)
 
2259
 
 
2260
        mod_obj = self.pool.get('ir.module.module')
 
2261
        logging.getLogger('init').info('HOOK: module stock_override: loading stock_data.xml')
 
2262
        pathname = path.join('stock_override', 'stock_data.xml')
 
2263
        file = tools.file_open(pathname)
 
2264
        tools.convert_xml_import(cr, 'stock_override', file, {}, mode='init', noupdate=False)
 
2265
 
 
2266
    def _product_value(self, cr, uid, ids, field_names, arg, context=None):
 
2267
        """Computes stock value (real and virtual) for a product, as well as stock qty (real and virtual).
 
2268
        @param field_names: Name of field
 
2269
        @return: Dictionary of values
 
2270
        """
 
2271
        result = super(stock_location, self)._product_value(cr, uid, ids, field_names, arg, context=context)
 
2272
 
 
2273
        product_product_obj = self.pool.get('product.product')
 
2274
        currency_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.currency_id.id
 
2275
        currency_obj = self.pool.get('res.currency')
 
2276
        currency = currency_obj.browse(cr, uid, currency_id, context=context)
 
2277
        if context.get('product_id'):
 
2278
            view_ids = self.search(cr, uid, [('usage', '=', 'view')], context=context)
 
2279
            result.update(dict([(i, {}.fromkeys(field_names, 0.0)) for i in list(set([aaa for aaa in view_ids]))]))
 
2280
            for loc_id in view_ids:
 
2281
                c = (context or {}).copy()
 
2282
                c['location'] = loc_id
 
2283
                c['compute_child'] = True
 
2284
                for prod in product_product_obj.browse(cr, uid, [context.get('product_id')], context=c):
 
2285
                    for f in field_names:
 
2286
                        if f == 'stock_real':
 
2287
                            if loc_id not in result:
 
2288
                                result[loc_id] = {}
 
2289
                            result[loc_id][f] += prod.qty_available
 
2290
                        elif f == 'stock_virtual':
 
2291
                            result[loc_id][f] += prod.virtual_available
 
2292
                        elif f == 'stock_real_value':
 
2293
                            amount = prod.qty_available * prod.standard_price
 
2294
                            amount = currency_obj.round(cr, uid, currency.rounding, amount)
 
2295
                            result[loc_id][f] += amount
 
2296
                        elif f == 'stock_virtual_value':
 
2297
                            amount = prod.virtual_available * prod.standard_price
 
2298
                            amount = currency_obj.round(cr, uid, currency.rounding, amount)
 
2299
                            result[loc_id][f] += amount
 
2300
 
 
2301
        return result
 
2302
 
 
2303
    def _fake_get(self, cr, uid, ids, fields, arg, context=None):
 
2304
        result = {}
 
2305
        if isinstance(ids, (int, long)):
 
2306
            ids = [ids]
 
2307
        for id in ids:
 
2308
            result[id] = False
 
2309
        return result
 
2310
 
 
2311
    def _prod_loc_search(self, cr, uid, ids, fields, arg, context=None):
 
2312
        if not arg or not arg[0] or not arg[0][2] or not arg[0][2][0]:
 
2313
            return []
 
2314
        if context is None:
 
2315
            context = {}
 
2316
        id_nonstock = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock_override', 'stock_location_non_stockable')[1]
 
2317
        id_cross = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_cross_docking', 'stock_location_cross_docking')[1]
 
2318
        prod_obj = self.pool.get('product.product').browse(cr, uid, arg[0][2][0])
 
2319
        if prod_obj and prod_obj.type == 'consu':
 
2320
            if arg[0][2][1] == 'in':
 
2321
                id_virt = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', 'stock_location_locations_virtual')[1]
 
2322
                ids_child = self.pool.get('stock.location').search(cr, uid,
 
2323
                        [('location_id', 'child_of', id_virt)],
 
2324
                        order='NO_ORDER')
 
2325
                return [('id', 'in', [id_nonstock, id_cross] + ids_child)]
 
2326
            else:
 
2327
                return [('id', 'in', [id_cross])]
 
2328
 
 
2329
        elif prod_obj and  prod_obj.type != 'consu':
 
2330
                if arg[0][2][1] == 'in':
 
2331
                    return [('id', 'in', ids_child)]
 
2332
                else:
 
2333
                    return [('id', 'not in', [id_nonstock]), ('usage', '=', 'internal')]
 
2334
 
 
2335
        return [('id', 'in', [])]
 
2336
 
 
2337
    def _cd_search(self, cr, uid, ids, fields, arg, context=None):
 
2338
        id_cross = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_cross_docking', 'stock_location_cross_docking')[1]
 
2339
        if context is None:
 
2340
            context = {}
 
2341
        if arg[0][2]:
 
2342
            obj_pol = arg[0][2][0] and self.pool.get('purchase.order.line').browse(cr, uid, arg[0][2][0]) or False
 
2343
            if  (obj_pol and obj_pol.order_id.cross_docking_ok) or arg[0][2][1]:
 
2344
                return [('id', 'in', [id_cross])]
 
2345
        return []
 
2346
 
 
2347
    def _check_usage(self, cr, uid, ids, fields, arg, context=None):
 
2348
        if not arg or not arg[0][2]:
 
2349
            return []
 
2350
        if context is None:
 
2351
            context = {}
 
2352
        prod_obj = self.pool.get('product.product').browse(cr, uid, arg[0][2])
 
2353
        if prod_obj.type == 'service_recep':
 
2354
            ids = self.pool.get('stock.location').search(cr, uid, [('usage',
 
2355
                '=', 'inventory')], order='NO_ORDER')
 
2356
            return [('id', 'in', ids)]
 
2357
        elif prod_obj.type == 'consu':
 
2358
            return []
 
2359
        else:
 
2360
            ids = self.pool.get('stock.location').search(cr, uid, [('usage',
 
2361
                '=', 'internal')], order='NO_ORDER')
 
2362
            return [('id', 'in', ids)]
 
2363
        return []
 
2364
 
 
2365
 
 
2366
    _columns = {
 
2367
        'chained_location_type': fields.selection([('none', 'None'), ('customer', 'Customer'), ('fixed', 'Fixed Location'), ('nomenclature', 'Nomenclature')],
 
2368
                                'Chained Location Type', required=True,
 
2369
                                help="Determines whether this location is chained to another location, i.e. any incoming product in this location \n" \
 
2370
                                     "should next go to the chained location. The chained location is determined according to the type :"\
 
2371
                                     "\n* None: No chaining at all"\
 
2372
                                     "\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." \
 
2373
                                     "\n* Fixed Location: The chained location is taken from the next field: Chained Location if Fixed." \
 
2374
                                     "\n* Nomenclature: The chained location is taken from the options field: Chained Location is according to the nomenclature level of product."\
 
2375
                                    ),
 
2376
        'chained_options_ids': fields.one2many('stock.location.chained.options', 'location_id', string='Chained options'),
 
2377
        'optional_loc': fields.boolean(string='Is an optional location ?'),
 
2378
        'stock_real': fields.function(_product_value, method=True, type='float', string='Real Stock', multi="stock"),
 
2379
        'stock_virtual': fields.function(_product_value, method=True, type='float', string='Virtual Stock', multi="stock"),
 
2380
        'stock_real_value': fields.function(_product_value, method=True, type='float', string='Real Stock Value', multi="stock", digits_compute=dp.get_precision('Account')),
 
2381
        'stock_virtual_value': fields.function(_product_value, method=True, type='float', string='Virtual Stock Value', multi="stock", digits_compute=dp.get_precision('Account')),
 
2382
        'check_prod_loc': fields.function(_fake_get, method=True, type='many2one', relation='stock.location', string='zz', fnct_search=_prod_loc_search),
 
2383
        'check_cd': fields.function(_fake_get, method=True, type='many2one', relation='stock.location', string='zz', fnct_search=_cd_search),
 
2384
        'check_usage': fields.function(_fake_get, method=True, type='many2one', relation='stock.location', string='zz', fnct_search=_check_usage),
 
2385
        'virtual_location': fields.boolean(string='Virtual location'),
 
2386
 
 
2387
    }
 
2388
 
 
2389
    # @@@override stock>stock.py>stock_move>chained_location_get
 
2390
    def chained_location_get(self, cr, uid, location, partner=None, product=None, nomenclature=None, context=None):
 
2391
        """ Finds chained location
 
2392
        @param location: Location id
 
2393
        @param partner: Partner id
 
2394
        @param product: Product id
 
2395
        @param nomen: Nomenclature of the product
 
2396
        @return: List of values
 
2397
        """
 
2398
        result = None
 
2399
        if location.chained_location_type == 'customer':
 
2400
            if partner:
 
2401
                result = partner.property_stock_customer
 
2402
        elif location.chained_location_type == 'fixed':
 
2403
            result = location.chained_location_id
 
2404
        elif location.chained_location_type == 'nomenclature':
 
2405
            nomen_id = nomenclature and nomenclature.id or (product and product.nomen_manda_0.id)
 
2406
            for opt in location.chained_options_ids:
 
2407
                if opt.nomen_id.id == nomen_id:
 
2408
                    result = opt.dest_location_id
 
2409
        if result:
 
2410
            return result, location.chained_auto_packing, location.chained_delay, location.chained_journal_id and location.chained_journal_id.id or False, location.chained_company_id and location.chained_company_id.id or False, location.chained_picking_type
 
2411
        return result
 
2412
    # @@@override end
 
2413
 
 
2414
    def _hook_proct_reserve(self, cr, uid, product_qty, result, amount, id, ids):
 
2415
        result.append((amount, id, True))
 
2416
        product_qty -= amount
 
2417
        if product_qty <= 0.0:
 
2418
            return result
 
2419
        else:
 
2420
            result = []
 
2421
            result.append((amount, id, True))
 
2422
            if len(ids) >= 1:
 
2423
                result.append((product_qty, ids[0], False))
 
2424
            else:
 
2425
                result.append((product_qty, id, False))
 
2426
            return result
 
2427
        return []
 
2428
 
 
2429
    def on_change_location_type(self, cr, uid, ids, chained_location_type, context=None):
 
2430
        '''
 
2431
        If the location type is changed to 'Nomenclature', set some other fields values
 
2432
        '''
 
2433
        if chained_location_type and chained_location_type == 'nomenclature':
 
2434
            return {'value': {'chained_auto_packing': 'transparent',
 
2435
                              'chained_picking_type': 'internal',
 
2436
                              'chained_delay': 0}}
 
2437
 
 
2438
        return {}
 
2439
 
 
2440
 
 
2441
stock_location()
 
2442
 
 
2443
class stock_location_chained_options(osv.osv):
 
2444
    _name = 'stock.location.chained.options'
 
2445
    _rec_name = 'location_id'
 
2446
 
 
2447
    _columns = {
 
2448
        'dest_location_id': fields.many2one('stock.location', string='Destination Location', required=True),
 
2449
        'nomen_id': fields.many2one('product.nomenclature', string='Nomenclature Level', required=True),
 
2450
        'location_id': fields.many2one('stock.location', string='Location', required=True),
 
2451
    }
 
2452
 
 
2453
stock_location_chained_options()
 
2454
 
 
2455
 
 
2456
class stock_move_cancel_wizard(osv.osv_memory):
 
2457
    _name = 'stock.move.cancel.wizard'
 
2458
 
 
2459
    _columns = {
 
2460
        'move_id': fields.many2one('stock.move', string='Move', required=True),
 
2461
        'cancel_only': fields.boolean('Just allow cancel only', invisible=True),
 
2462
    }
 
2463
 
 
2464
    _defaults = {
 
2465
        'move_id': lambda self, cr, uid, c: c.get('active_id'),
 
2466
        'cancel_only': False,
 
2467
    }
 
2468
 
 
2469
    def just_cancel(self, cr, uid, ids, context=None):
 
2470
        '''
 
2471
        Just call the cancel of stock.move (re-sourcing flag not set)
 
2472
        '''
 
2473
        # Objects
 
2474
        move_obj = self.pool.get('stock.move')
 
2475
        pick_obj = self.pool.get('stock.picking')
 
2476
 
 
2477
        wf_service = netsvc.LocalService("workflow")
 
2478
 
 
2479
        for wiz in self.browse(cr, uid, ids, context=context):
 
2480
            move_id = wiz.move_id.id
 
2481
            picking_id = wiz.move_id.picking_id.id
 
2482
            move_obj.action_cancel(cr, uid, [wiz.move_id.id], context=context)
 
2483
            move_ids = move_obj.search(cr, uid, [('id', '=', wiz.move_id.id)],
 
2484
                    limit=1, order='NO_ORDER', context=context)
 
2485
            if move_ids and  wiz.move_id.has_to_be_resourced:
 
2486
                self.infolog(cr, uid, "The stock.move id:%s of the picking id:%s has been canceled and resourced" % (move_id, picking_id))
 
2487
            else:
 
2488
                self.infolog(cr, uid, "The stock.move id:%s of the picking id:%s has been canceled" % (move_id, picking_id))
 
2489
 
 
2490
            if move_ids and wiz.move_id.picking_id:
 
2491
                lines = wiz.move_id.picking_id.move_lines
 
2492
                if all(l.state == 'cancel' for l in lines):
 
2493
                    wf_service.trg_validate(uid, 'stock.picking', wiz.move_id.picking_id.id, 'button_cancel', cr)
 
2494
 
 
2495
        return {'type': 'ir.actions.act_window_close'}
 
2496
 
 
2497
 
 
2498
    def cancel_and_resource(self, cr, uid, ids, context=None):
 
2499
        '''
 
2500
        Call the cancel and resource method of the stock move
 
2501
        '''
 
2502
        # Objects
 
2503
        move_obj = self.pool.get('stock.move')
 
2504
 
 
2505
        move_ids = [x.move_id.id for x in self.browse(cr, uid, ids, context=context)]
 
2506
        move_obj.write(cr, uid, move_ids, {'has_to_be_resourced': True}, context=context)
 
2507
 
 
2508
        return self.just_cancel(cr, uid, ids, context=context)
 
2509
 
 
2510
stock_move_cancel_wizard()
 
2511
 
 
2512
 
 
2513
class stock_picking_cancel_wizard(osv.osv_memory):
 
2514
    _name = 'stock.picking.cancel.wizard'
 
2515
 
 
2516
    def _get_allow_cr(self, cr, uid, context=None):
 
2517
        """
 
2518
        Define if the C&R are allowed on the wizard
 
2519
        """
 
2520
        if context is None:
 
2521
            context = {}
 
2522
 
 
2523
        picking_id = context.get('active_id')
 
2524
        for move in self.pool.get('stock.picking').browse(cr, uid, picking_id, context=context).move_lines:
 
2525
            if move.sale_line_id and move.sale_line_id.type == 'make_to_order':
 
2526
                return False
 
2527
 
 
2528
        return True
 
2529
 
 
2530
    _columns = {
 
2531
        'picking_id': fields.many2one('stock.picking', string='Picking', required=True),
 
2532
        'allow_cr': fields.boolean(string='Allow Cancel and resource'),
 
2533
    }
 
2534
 
 
2535
    _defaults = {
 
2536
        'picking_id': lambda self, cr, uid, c: c.get('active_id'),
 
2537
        'allow_cr': _get_allow_cr,
 
2538
    }
 
2539
 
 
2540
    def just_cancel(self, cr, uid, ids, context=None):
 
2541
        '''
 
2542
        Just call the cancel of the stock.picking
 
2543
        '''
 
2544
        wf_service = netsvc.LocalService("workflow")
 
2545
        for wiz in self.browse(cr, uid, ids, context=context):
 
2546
            wf_service.trg_validate(uid, 'stock.picking', wiz.picking_id.id, 'button_cancel', cr)
 
2547
 
 
2548
        return {'type': 'ir.actions.act_window_close'}
 
2549
 
 
2550
    def cancel_and_resource(self, cr, uid, ids, context=None):
 
2551
        '''
 
2552
        Call the cancel and resource method of the picking
 
2553
        '''
 
2554
        # objects declarations
 
2555
        pick_obj = self.pool.get('stock.picking')
 
2556
 
 
2557
        # variables declarations
 
2558
        pick_ids = []
 
2559
 
 
2560
        for wiz in self.browse(cr, uid, ids, context=context):
 
2561
            pick_ids.append(wiz.picking_id.id)
 
2562
 
 
2563
        # Set the boolean 'has_to_be_resourced' to True for each picking
 
2564
        vals = {'has_to_be_resourced': True}
 
2565
        pick_obj.write(cr, uid, pick_ids, vals, context=context)
 
2566
 
 
2567
        return self.just_cancel(cr, uid, ids, context=context)
 
2568
 
 
2569
 
 
2570
stock_picking_cancel_wizard()
 
2571
 
 
2572
 
 
2573
class ir_values(osv.osv):
 
2574
    _name = 'ir.values'
 
2575
    _inherit = 'ir.values'
 
2576
 
 
2577
    def get(self, cr, uid, key, key2, models, meta=False, context=None, res_id_req=False, without_user=True, key2_req=True):
 
2578
        if context is None:
 
2579
            context = {}
 
2580
        values = super(ir_values, self).get(cr, uid, key, key2, models, meta, context, res_id_req, without_user, key2_req)
 
2581
        trans_obj = self.pool.get('ir.translation')
 
2582
        new_values = values
 
2583
        move_accepted_values = {'client_action_multi': [],
 
2584
                                    'client_print_multi': [],
 
2585
                                    'client_action_relate': ['act_relate_picking'],
 
2586
                                    'tree_but_action': [],
 
2587
                                    'tree_but_open': []}
 
2588
 
 
2589
        incoming_accepted_values = {'client_action_multi': ['act_stock_return_picking', 'action_stock_invoice_onshipping'],
 
2590
                                    'client_print_multi': ['Reception', 'XML Export'],
 
2591
                                    'client_action_relate': ['View_log_stock.picking'],
 
2592
                                    'tree_but_action': [],
 
2593
                                    'tree_but_open': []}
 
2594
 
 
2595
        internal_accepted_values = {'client_action_multi': [],
 
2596
                                    'client_print_multi': ['Internal Move Excel Export', 'Internal Move'],
 
2597
                                    'client_action_relate': [],
 
2598
                                    'tree_but_action': [],
 
2599
                                    'tree_but_open': []}
 
2600
 
 
2601
        delivery_accepted_values = {'client_action_multi': [],
 
2602
                                    'client_print_multi': ['Labels', 'Delivery Order'],
 
2603
                                    'client_action_relate': [''],
 
2604
                                    'tree_but_action': [],
 
2605
                                    'tree_but_open': []}
 
2606
 
 
2607
        picking_accepted_values = {'client_action_multi': [],
 
2608
                                    'client_print_multi': ['Picking Ticket', 'Pre-Packing List', 'Labels'],
 
2609
                                    'client_action_relate': [''],
 
2610
                                    'tree_but_action': [],
 
2611
                                    'tree_but_open': []}
 
2612
 
 
2613
        if 'stock.move' in [x[0] for x in models]:
 
2614
            new_values = []
 
2615
            Destruction_Report = trans_obj.tr_view(cr, 'Destruction Report', context)
 
2616
            for v in values:
 
2617
                if key == 'action' and v[1] in move_accepted_values[key2]:
 
2618
                    new_values.append(v)
 
2619
                elif context.get('_terp_view_name', False) == Destruction_Report:
 
2620
                    new_values.append(v)
 
2621
        elif context.get('picking_type', False) == 'incoming_shipment' and 'stock.picking' in [x[0] for x in models]:
 
2622
            new_values = []
 
2623
            for v in values:
 
2624
                if key == 'action' and v[1] in incoming_accepted_values[key2]:
 
2625
                    new_values.append(v)
 
2626
        elif context.get('picking_type', False) == 'internal_move' and 'stock.picking' in [x[0] for x in models]:
 
2627
            new_values = []
 
2628
            for v in values:
 
2629
                if key == 'action' and v[1] in internal_accepted_values[key2]:
 
2630
                    new_values.append(v)
 
2631
        elif context.get('picking_type', False) == 'delivery_order' and 'stock.picking' in [x[0] for x in models]:
 
2632
            new_values = []
 
2633
            for v in values:
 
2634
                if key == 'action' and v[1] in delivery_accepted_values[key2]:
 
2635
                    new_values.append(v)
 
2636
        elif context.get('picking_type', False) == 'picking_ticket' and 'stock.picking' in [x[0] for x in models]:
 
2637
            new_values = []
 
2638
            for v in values:
 
2639
                if key == 'action' and v[1] in picking_accepted_values[key2]:
 
2640
                    new_values.append(v)
 
2641
 
 
2642
        return new_values
 
2643
 
 
2644
ir_values()