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

« back to all changes in this revision

Viewing changes to msf_outgoing/msf_outgoing.py

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

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: utf-8 -*-
2
 
##############################################################################
3
 
#
4
 
#    OpenERP, Open Source Management Solution
5
 
#    Copyright (C) 2011 MSF, TeMPO Consulting
6
 
#
7
 
#    This program is free software: you can redistribute it and/or modify
8
 
#    it under the terms of the GNU Affero General Public License as
9
 
#    published by the Free Software Foundation, either version 3 of the
10
 
#    License, or (at your option) any later version.
11
 
#
12
 
#    This program is distributed in the hope that it will be useful,
13
 
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 
#    GNU Affero General Public License for more details.
16
 
#
17
 
#    You should have received a copy of the GNU Affero General Public License
18
 
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
 
#
20
 
##############################################################################
21
 
 
22
 
from osv import osv, fields
23
 
from tools.translate import _
24
 
import netsvc
25
 
from datetime import datetime, timedelta, date
26
 
 
27
 
from dateutil.relativedelta import relativedelta
28
 
import decimal_precision as dp
29
 
import logging
30
 
import tools
31
 
import time
32
 
from os import path
33
 
 
34
 
class stock_warehouse(osv.osv):
35
 
    '''
36
 
    add new packing, dispatch and distribution locations for input
37
 
    '''
38
 
    _inherit = "stock.warehouse"
39
 
    _name = "stock.warehouse"
40
 
    
41
 
    _columns = {'lot_packing_id': fields.many2one('stock.location', 'Location Packing', required=True, domain=[('usage','<>','view')]),
42
 
                'lot_dispatch_id': fields.many2one('stock.location', 'Location Dispatch', required=True, domain=[('usage','<>','view')]),
43
 
                'lot_distribution_id': fields.many2one('stock.location', 'Location Distribution', required=True, domain=[('usage','<>','view')]),
44
 
                }
45
 
    
46
 
    _defaults = {'lot_packing_id': lambda obj, cr, uid, c: len(obj.pool.get('stock.location').search(cr, uid, [('name', '=', 'Packing'),], context=c)) and obj.pool.get('stock.location').search(cr, uid, [('name', '=', 'Packing'),], context=c)[0] or False,
47
 
                 'lot_dispatch_id': lambda obj, cr, uid, c: len(obj.pool.get('stock.location').search(cr, uid, [('name', '=', 'Dispatch'),], context=c)) and obj.pool.get('stock.location').search(cr, uid, [('name', '=', 'Dispatch'),], context=c)[0] or False,
48
 
                 'lot_distribution_id': lambda obj, cr, uid, c: len(obj.pool.get('stock.location').search(cr, uid, [('name', '=', 'Distribution'),], context=c)) and obj.pool.get('stock.location').search(cr, uid, [('name', '=', 'Distribution'),], context=c)[0] or False,
49
 
                }
50
 
 
51
 
stock_warehouse()
52
 
 
53
 
 
54
 
class pack_type(osv.osv):
55
 
    '''
56
 
    pack type corresponding to a type of pack (name, length, width, height)
57
 
    '''
58
 
    _name = 'pack.type'
59
 
    _description = 'Pack Type'
60
 
    _columns = {'name': fields.char(string='Name', size=1024),
61
 
                'length': fields.float(digits=(16,2), string='Length [cm]'),
62
 
                'width': fields.float(digits=(16,2), string='Width [cm]'),
63
 
                'height': fields.float(digits=(16,2), string='Height [cm]'),
64
 
                }
65
 
 
66
 
pack_type()
67
 
 
68
 
 
69
 
class shipment(osv.osv):
70
 
    '''
71
 
    a shipment presents the data from grouped stock moves in a 'sequence' way
72
 
    '''
73
 
    _name = 'shipment'
74
 
    _description = 'represents a group of pack families'
75
 
    
76
 
    def copy(self, cr, uid, id, default=None, context=None):
77
 
        '''
78
 
        prevent copy
79
 
        '''
80
 
        raise osv.except_osv(_('Error !'), _('Shipment copy is forbidden.'))
81
 
    
82
 
    def copy_data(self, cr, uid, id, default=None, context=None):
83
 
        '''
84
 
        reset one2many fields
85
 
        '''
86
 
        if default is None:
87
 
            default = {}
88
 
        if context is None:
89
 
            context = {}
90
 
        # reset one2many fields
91
 
        default.update(pack_family_memory_ids=[])
92
 
        result = super(shipment, self).copy_data(cr, uid, id, default=default, context=context)
93
 
        
94
 
        return result
95
 
    
96
 
    def _vals_get(self, cr, uid, ids, fields, arg, context=None):
97
 
        '''
98
 
        multi function for global shipment values
99
 
        '''
100
 
        pf_memory_obj = self.pool.get('pack.family.memory')
101
 
        picking_obj = self.pool.get('stock.picking')
102
 
        
103
 
        result = {}
104
 
        for shipment in self.browse(cr, uid, ids, context=context):
105
 
            values = {'total_amount': 0.0,
106
 
                      'currency_id': False,
107
 
                      'num_of_packs': 0,
108
 
                      'total_weight': 0.0,
109
 
                      'total_volume': 0.0,
110
 
                      'state': 'draft',
111
 
                      'backshipment_id': False,
112
 
                      }
113
 
            result[shipment.id] = values
114
 
            # gather the state from packing objects, all packing must have the same state for shipment
115
 
            # for draft shipment, we can have done packing and draft packing
116
 
            packing_ids = picking_obj.search(cr, uid, [('shipment_id', '=', shipment.id),], context=context)
117
 
            # fields to check and get
118
 
            state = None
119
 
            first_shipment_packing_id = None
120
 
            backshipment_id = None
121
 
            # delivery validated
122
 
            delivery_validated = None
123
 
            # browse the corresponding packings
124
 
            for packing in picking_obj.browse(cr, uid, packing_ids, context=context):
125
 
                # state check
126
 
                # because when the packings are validated one after the other, it triggers the compute of state, and if we have multiple packing for this shipment, it will fail
127
 
                # if one packing is draft, even if other packing have been shipped, the shipment must stay draft until all packing are done
128
 
                if state != 'draft':
129
 
                    state = packing.state
130
 
                    
131
 
                # all corresponding shipment must be dev validated or not
132
 
                if packing.delivered:
133
 
                    # integrity check
134
 
                    if delivery_validated is not None and delivery_validated != packing.delivered:
135
 
                        # two packing have different delivery validated values -> problem
136
 
                        assert False, 'All packing do not have the same validated value - %s - %s'%(delivery_validated, packing.delivered)
137
 
                    # update the value
138
 
                    delivery_validated = packing.delivered
139
 
                
140
 
                # first_shipment_packing_id check - no check for the same reason
141
 
                first_shipment_packing_id = packing.first_shipment_packing_id.id
142
 
                
143
 
                # backshipment_id check
144
 
                if backshipment_id and backshipment_id != packing.backorder_id.shipment_id.id:
145
 
                    assert False, 'all packing of the shipment have not the same draft shipment correspondance - %s - %s'%(backshipment_id, packing.backorder_id.shipment_id.id)
146
 
                backshipment_id = packing.backorder_id and packing.backorder_id.shipment_id.id or False
147
 
            
148
 
            # if state is in ('draft', 'done', 'cancel'), the shipment keeps the same state
149
 
            if state not in ('draft', 'done', 'cancel',):
150
 
                if first_shipment_packing_id:
151
 
                    # second step of shipment : shipped
152
 
                    state = 'shipped'
153
 
                else:
154
 
                    state = 'packed'
155
 
            elif state == 'done':
156
 
                if delivery_validated:
157
 
                    # special state corresponding to delivery validated
158
 
                    state = 'delivered'
159
 
                    
160
 
            values['state'] = state
161
 
            values['backshipment_id'] = backshipment_id
162
 
            
163
 
            for memory_family in shipment.pack_family_memory_ids:
164
 
                # taken only into account if not done (done means returned packs)
165
 
                if shipment.state in ('delivered',) or memory_family.state not in ('done',) :
166
 
                    # num of packs
167
 
                    num_of_packs = memory_family.num_of_packs
168
 
                    values['num_of_packs'] += int(num_of_packs)
169
 
                    # total weight
170
 
                    total_weight = memory_family.total_weight
171
 
                    values['total_weight'] += int(total_weight)
172
 
                    # total volume
173
 
                    total_volume = memory_family.total_volume
174
 
                    values['total_volume'] += float(total_volume)
175
 
                    # total amount
176
 
                    total_amount = memory_family.total_amount
177
 
                    values['total_amount'] += total_amount
178
 
                    # currency
179
 
                    currency_id = memory_family.currency_id and memory_family.currency_id.id or False
180
 
                    values['currency_id'] = currency_id
181
 
                
182
 
        return result
183
 
    
184
 
    def _get_shipment_ids(self, cr, uid, ids, context=None):
185
 
        '''
186
 
        ids represents the ids of stock.picking objects for which state has changed
187
 
        
188
 
        return the list of ids of shipment object which need to get their state field updated
189
 
        '''
190
 
        pack_obj = self.pool.get('stock.picking')
191
 
        result = []
192
 
        for packing in pack_obj.browse(cr, uid, ids, context=context):
193
 
            if packing.shipment_id and packing.shipment_id.id not in result:
194
 
                result.append(packing.shipment_id.id)
195
 
        return result
196
 
    
197
 
    def _packs_search(self, cr, uid, obj, name, args, context=None):
198
 
        """ 
199
 
        Searches Ids of shipment
200
 
        """
201
 
        if context is None:
202
 
            context = {}
203
 
            
204
 
        shipments = self.pool.get('shipment').search(cr, uid, [], context=context)
205
 
        # result dic
206
 
        result = {}
207
 
        for shipment in self.browse(cr, uid, shipments, context=context):
208
 
            result[shipment.id] = shipment.num_of_packs
209
 
        # construct the request
210
 
        # adapt the operator
211
 
        op = args[0][1]
212
 
        if op == '=':
213
 
            op = '=='
214
 
        ids = [('id', 'in', [x for x in result.keys() if eval("%s %s %s"%(result[x], op, args[0][2]))])]
215
 
        return ids
216
 
 
217
 
    _columns = {'name': fields.char(string='Reference', size=1024),
218
 
                'date': fields.datetime(string='Creation Date'),
219
 
                'shipment_expected_date': fields.datetime(string='Expected Ship Date'),
220
 
                'shipment_actual_date': fields.datetime(string='Actual Ship Date', readonly=True,),
221
 
                'transport_type': fields.selection([('by_road', 'By road')],
222
 
                                                   string="Transport Type", readonly=True),
223
 
                'address_id': fields.many2one('res.partner.address', 'Address', help="Address of customer"),
224
 
                'sequence_id': fields.many2one('ir.sequence', 'Shipment Sequence', help="This field contains the information related to the numbering of the shipment.", ondelete='cascade'),
225
 
                # cargo manifest things
226
 
                'cargo_manifest_reference': fields.char(string='Cargo Manifest Reference', size=1024,),
227
 
                'date_of_departure': fields.date(string='Date of Departure'),
228
 
                'planned_date_of_arrival': fields.date(string='Planned Date of Arrival'),
229
 
                'transit_via': fields.char(string='Transit via', size=1024),
230
 
                'registration': fields.char(string='Registration', size=1024),
231
 
                'driver_name': fields.char(string='Driver Name', size=1024),
232
 
                # -- shipper
233
 
                'shipper_name': fields.char(string='Name', size=1024),
234
 
                'shipper_address': fields.char(string='Address', size=1024),
235
 
                'shipper_phone': fields.char(string='Phone', size=1024),
236
 
                'shipper_email': fields.char(string='Email', size=1024),
237
 
                'shipper_other': fields.char(string='Other', size=1024),
238
 
                'shipper_date': fields.date(string='Date'),
239
 
                'shipper_signature': fields.char(string='Signature', size=1024),
240
 
                # -- carrier
241
 
                'carrier_name': fields.char(string='Name', size=1024),
242
 
                'carrier_address': fields.char(string='Address', size=1024),
243
 
                'carrier_phone': fields.char(string='Phone', size=1024),
244
 
                'carrier_email': fields.char(string='Email', size=1024),
245
 
                'carrier_other': fields.char(string='Other', size=1024),
246
 
                'carrier_date': fields.date(string='Date'),
247
 
                'carrier_signature': fields.char(string='Signature', size=1024),
248
 
                # -- consignee
249
 
                'consignee_name': fields.char(string='Name', size=1024),
250
 
                'consignee_address': fields.char(string='Address', size=1024),
251
 
                'consignee_phone': fields.char(string='Phone', size=1024),
252
 
                'consignee_email': fields.char(string='Email', size=1024),
253
 
                'consignee_other': fields.char(string='Other', size=1024),
254
 
                'consignee_date': fields.date(string='Date'),
255
 
                'consignee_signature': fields.char(string='Signature', size=1024),
256
 
                # functions
257
 
                'partner_id': fields.related('address_id', 'partner_id', type='many2one', relation='res.partner', string='Customer', store=True),
258
 
                'partner_id2': fields.many2one('res.partner', string='Customer', required=False),
259
 
                'total_amount': fields.function(_vals_get, method=True, type='float', string='Total Amount', multi='get_vals',),
260
 
                'currency_id': fields.function(_vals_get, method=True, type='many2one', relation='res.currency', string='Currency', multi='get_vals',),
261
 
                'num_of_packs': fields.function(_vals_get, method=True, fnct_search=_packs_search, type='integer', string='Number of Packs', multi='get_vals_X',), # old_multi ship_vals
262
 
                'total_weight': fields.function(_vals_get, method=True, type='float', string='Total Weight[kg]', multi='get_vals',),
263
 
                'total_volume': fields.function(_vals_get, method=True, type='float', string=u'Total Volume[dm³]', multi='get_vals',),
264
 
                'state': fields.function(_vals_get, method=True, type='selection', selection=[('draft', 'Draft'),
265
 
                                                                                              ('packed', 'Packed'),
266
 
                                                                                              ('shipped', 'Shipped'),
267
 
                                                                                              ('done', 'Closed'),
268
 
                                                                                              ('delivered', 'Delivered'),
269
 
                                                                                              ('cancel', 'Cancelled')], string='State', multi='get_vals',
270
 
                                         store= {'stock.picking': (_get_shipment_ids, ['state', 'shipment_id', 'delivered'], 10),}),
271
 
                'backshipment_id': fields.function(_vals_get, method=True, type='many2one', relation='shipment', string='Draft Shipment', multi='get_vals',),
272
 
                # added by Quentin https://bazaar.launchpad.net/~unifield-team/unifield-wm/trunk/revision/426.20.14
273
 
                'parent_id': fields.many2one('shipment', string='Parent shipment'),
274
 
                'invoice_id': fields.many2one('account.invoice', string='Related invoice'),
275
 
                }
276
 
    _defaults = {'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),}
277
 
    
278
 
    
279
 
    _order = 'name desc'
280
 
    
281
 
    def create_shipment(self, cr, uid, ids, context=None):
282
 
        '''
283
 
        open the wizard to create (partial) shipment
284
 
        '''
285
 
        # we need the context for the wizard switch
286
 
        if context is None:
287
 
            context = {}
288
 
        context['group_by'] = False
289
 
            
290
 
        wiz_obj = self.pool.get('wizard')
291
 
        
292
 
        # data
293
 
        name = _("Create Shipment")
294
 
        model = 'shipment.wizard'
295
 
        step = 'create'
296
 
        # open the selected wizard
297
 
        return wiz_obj.open_wizard(cr, uid, ids, name=name, model=model, step=step, context=context)
298
 
    
299
 
    def do_create_shipment(self, cr, uid, ids, context=None):
300
 
        '''
301
 
        for each original draft picking:
302
 
         - creation of the new packing object with empty moves
303
 
         - convert partial data to move related data
304
 
         - create corresponding moves in new packing
305
 
         - update initial packing object
306
 
         - trigger workflow for new packing object
307
 
        '''
308
 
        # integrity check
309
 
        assert context, 'no context, method call is wrong'
310
 
        assert 'partial_datas_shipment' in context, 'partial_datas_shipment no defined in context'
311
 
        
312
 
        pick_obj = self.pool.get('stock.picking')
313
 
        move_obj = self.pool.get('stock.move')
314
 
        shipment_obj = self.pool.get('shipment')
315
 
        
316
 
        # data from wizard
317
 
        partial_datas_shipment = context['partial_datas_shipment']
318
 
        # shipment ids from ids must be equal to shipment ids from partial datas
319
 
        assert set(ids) == set(partial_datas_shipment.keys()), 'shipment ids from ids and partial do not match'
320
 
        
321
 
        for draft_shipment in self.browse(cr, uid, partial_datas_shipment.keys(), context=context):
322
 
            # for each shipment create a new shipment which will be used by the group of new packing objects
323
 
            address_id = shipment_obj.read(cr, uid, [draft_shipment.id], ['address_id'], context=context)[0]['address_id'][0]
324
 
            partner_id = shipment_obj.read(cr, uid, [draft_shipment.id], ['partner_id'], context=context)[0]['partner_id'][0]
325
 
            sequence = draft_shipment.sequence_id
326
 
            shipment_number = sequence.get_id(test='id', context=context)
327
 
            # state is a function - not set
328
 
            shipment_name = draft_shipment.name + '-' + shipment_number
329
 
            # 
330
 
            values = {'name': shipment_name, 'address_id': address_id, 'partner_id': partner_id, 'partner_id2': partner_id, 'shipment_expected_date': draft_shipment.shipment_expected_date, 'shipment_actual_date': draft_shipment.shipment_actual_date, 'parent_id': draft_shipment.id}
331
 
            shipment_id = shipment_obj.create(cr, uid, values, context=context)
332
 
            context['shipment_id'] = shipment_id
333
 
            for draft_packing in pick_obj.browse(cr, uid, partial_datas_shipment[draft_shipment.id].keys(), context=context):
334
 
                # copy the picking object without moves
335
 
                # creation of moves and update of initial in picking create method
336
 
                context.update(draft_shipment_id=draft_shipment.id, draft_packing_id=draft_packing.id)
337
 
                sequence = draft_packing.sequence_id
338
 
                packing_number = sequence.get_id(test='id', context=context)
339
 
                new_packing_id = pick_obj.copy(cr, uid, draft_packing.id,
340
 
                                               {'name': draft_packing.name + '-' + packing_number,
341
 
                                                'backorder_id': draft_packing.id,
342
 
                                                'shipment_id': False,
343
 
                                                'move_lines': []}, context=dict(context, keep_prodlot=True, allow_copy=True,))
344
 
 
345
 
                # confirm the new packing
346
 
                wf_service = netsvc.LocalService("workflow")
347
 
                wf_service.trg_validate(uid, 'stock.picking', new_packing_id, 'button_confirm', cr)
348
 
                # simulate check assign button, as stock move must be available
349
 
                pick_obj.force_assign(cr, uid, [new_packing_id])
350
 
                
351
 
            # log creation message
352
 
            self.log(cr, uid, shipment_id, _('The new Shipment %s has been created.')%(shipment_name,))
353
 
            # the shipment is automatically shipped, no more pack states in between.
354
 
            self.ship(cr, uid, [shipment_id], context=context)
355
 
 
356
 
        # TODO which behavior
357
 
        #return {'type': 'ir.actions.act_window_close'}
358
 
        data_obj = self.pool.get('ir.model.data')
359
 
        view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_shipment_form')
360
 
        view_id = view_id and view_id[1] or False
361
 
        return {
362
 
            'name':_("Shipment"),
363
 
            'view_mode': 'form,tree',
364
 
            'view_id': [view_id],
365
 
            'view_type': 'form',
366
 
            'res_model': 'shipment',
367
 
            'res_id': shipment_id,
368
 
            'type': 'ir.actions.act_window',
369
 
            'target': 'crush',
370
 
        }
371
 
    
372
 
    def return_packs(self, cr, uid, ids, context=None):
373
 
        '''
374
 
        open the wizard to return packs from draft shipment
375
 
        '''
376
 
        # we need the context for the wizard switch
377
 
        if context is None:
378
 
            context = {}
379
 
            
380
 
        wiz_obj = self.pool.get('wizard')
381
 
        
382
 
        # data
383
 
        name = _("Return Packs")
384
 
        model = 'shipment.wizard'
385
 
        step = 'returnpacks'
386
 
        # open the selected wizard
387
 
        return wiz_obj.open_wizard(cr, uid, ids, name=name, model=model, step=step, context=context)
388
 
    
389
 
    def do_return_packs(self, cr, uid, ids, context=None):
390
 
        '''
391
 
        for each original draft picking:
392
 
         - convert partial data to move related data
393
 
         - update the draft_packing's moves, decrease quantity and update from/to info
394
 
         - update initial packing object
395
 
         - create a back move for each move with return quantity to initial location
396
 
         - increase quantity of related draft_picking_ticket's moves
397
 
        '''
398
 
        # integrity check
399
 
        assert context, 'no context, method call is wrong'
400
 
        assert 'partial_datas' in context, 'partial_datas no defined in context'
401
 
        
402
 
        pick_obj = self.pool.get('stock.picking')
403
 
        move_obj = self.pool.get('stock.move')
404
 
        obj_data = self.pool.get('ir.model.data')
405
 
        
406
 
        # data from wizard
407
 
        partial_datas = context['partial_datas']
408
 
        # shipment ids from ids must be equal to shipment ids from partial datas
409
 
        assert set(ids) == set(partial_datas.keys()), 'shipment ids from ids and partial do not match'
410
 
       
411
 
        draft_picking_id = False
412
 
        for draft_shipment_id in partial_datas:
413
 
            # log flag - log for draft shipment is displayed only one time for each draft shipment
414
 
            log_flag = False
415
 
            # for each draft packing
416
 
            for draft_packing in pick_obj.browse(cr, uid, partial_datas[draft_shipment_id].keys(), context=context):
417
 
                # corresponding draft picking ticket -> draft_packing - ppl - picking_ticket - draft_picking_ticket
418
 
                draft_picking = draft_packing.previous_step_id.previous_step_id.backorder_id
419
 
                draft_picking_id = draft_packing.previous_step_id.previous_step_id.backorder_id.id
420
 
                # for each sequence
421
 
                for from_pack in partial_datas[draft_shipment_id][draft_packing.id]:
422
 
                    for to_pack in partial_datas[draft_shipment_id][draft_packing.id][from_pack]:
423
 
                        # partial data for one sequence of one draft packing
424
 
                        data = partial_datas[draft_shipment_id][draft_packing.id][from_pack][to_pack][0]
425
 
                        # total number of packs
426
 
                        total_num = to_pack - from_pack + 1
427
 
                        # number of returned packs
428
 
                        selected_number = data['selected_number']
429
 
                        # we take the packs with the highest numbers
430
 
                        # new moves
431
 
                        selected_from_pack = to_pack - selected_number + 1
432
 
                        selected_to_pack = to_pack
433
 
                        # update initial moves
434
 
                        if selected_number == total_num:
435
 
                            # if all packs have been selected, from/to are set to 0
436
 
                            initial_from_pack = 0
437
 
                            initial_to_pack = 0
438
 
                        else:
439
 
                            initial_from_pack = from_pack
440
 
                            initial_to_pack = to_pack - selected_number
441
 
                        # find the concerned stock moves
442
 
                        move_ids = move_obj.search(cr, uid, [('picking_id', '=', draft_packing.id),
443
 
                                                             ('from_pack', '=', from_pack),
444
 
                                                             ('to_pack', '=', to_pack)])
445
 
                        # update the moves, decrease the quantities
446
 
                        for move in move_obj.browse(cr, uid, move_ids, context=context):
447
 
                            # stock move are not canceled as for ppl return process
448
 
                            # because this represents a draft packing, meaning some shipment could be canceled and
449
 
                            # returned to this stock move
450
 
                            # initial quantity
451
 
                            initial_qty = move.product_qty
452
 
                            # quantity to return
453
 
                            return_qty = selected_number * move.qty_per_pack
454
 
                            # update initial quantity
455
 
                            initial_qty = max(initial_qty - return_qty, 0)
456
 
                            values = {'product_qty': initial_qty,
457
 
                                      'from_pack': initial_from_pack,
458
 
                                      'to_pack': initial_to_pack,}
459
 
                            
460
 
                            move_obj.write(cr, uid, [move.id], values, context=context)
461
 
                            
462
 
                            # create a back move with the quantity to return to the good location
463
 
                            # the good location is stored in the 'initial_location' field
464
 
                            copy_id = move_obj.copy(cr, uid, move.id, {'product_qty': return_qty,
465
 
                                                             'location_dest_id': move.initial_location.id,
466
 
                                                             'from_pack': selected_from_pack,
467
 
                                                             'to_pack': selected_to_pack,
468
 
                                                             'state': 'done'}, context=context)
469
 
                            # find the corresponding move in draft in the draft **picking**
470
 
                            draft_move = move.backmove_id
471
 
                            # increase the draft move with the move quantity
472
 
                            draft_initial_qty = move_obj.read(cr, uid, [draft_move.id], ['product_qty'], context=context)[0]['product_qty']
473
 
                            draft_initial_qty += return_qty
474
 
                            move_obj.write(cr, uid, [draft_move.id], {'product_qty': draft_initial_qty}, context=context)
475
 
            
476
 
                # log the increase action - display the picking ticket view form - log message for each draft packing because each corresponds to a different draft picking
477
 
                if not log_flag:
478
 
                    draft_shipment_name = self.read(cr, uid, draft_shipment_id, ['name'], context=context)['name']
479
 
                    self.log(cr, uid, draft_shipment_id, _("Packs from the draft Shipment (%s) have been returned to stock.")%(draft_shipment_name,))
480
 
                    log_flag = True
481
 
                res = obj_data.get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_form')[1]
482
 
                self.pool.get('stock.picking').log(cr, uid, draft_picking_id, _("The corresponding Draft Picking Ticket (%s) has been updated.")%(draft_picking.name,), context={'view_id': res,})
483
 
            
484
 
        # call complete_finished on the shipment object
485
 
        # if everything is alright (all draft packing are finished) the shipment is done also 
486
 
        result = self.complete_finished(cr, uid, partial_datas.keys(), context=context)
487
 
        
488
 
        # TODO which behavior
489
 
        #return {'type': 'ir.actions.act_window_close'}
490
 
        data_obj = self.pool.get('ir.model.data')
491
 
        view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_form')
492
 
        view_id = view_id and view_id[1] or False
493
 
        return {
494
 
            'name':_("Picking Ticket"),
495
 
            'view_mode': 'form,tree',
496
 
            'view_id': [view_id],
497
 
            'view_type': 'form',
498
 
            'res_model': 'stock.picking',
499
 
            'res_id': draft_picking_id ,
500
 
            'type': 'ir.actions.act_window',
501
 
            'target': 'crush',
502
 
        }
503
 
    
504
 
    def return_packs_from_shipment(self, cr, uid, ids, context=None):
505
 
        '''
506
 
        open the wizard to return packs from draft shipment
507
 
        '''
508
 
        # we need the context for the wizard switch
509
 
        if context is None:
510
 
            context = {}
511
 
            
512
 
        wiz_obj = self.pool.get('wizard')
513
 
        
514
 
        # data
515
 
        name = _("Return Packs from Shipment")
516
 
        model = 'shipment.wizard'
517
 
        step = 'returnpacksfromshipment'
518
 
        # open the selected wizard
519
 
        return wiz_obj.open_wizard(cr, uid, ids, name=name, model=model, step=step, context=context)
520
 
    
521
 
    def compute_sequences(self, cr, uid, ids, context=None, *args, **kwargs):
522
 
        '''
523
 
        compute corresponding sequences
524
 
        '''
525
 
        datas = kwargs['datas']
526
 
        from_pack = kwargs['from_pack']
527
 
        to_pack = kwargs['to_pack']
528
 
        # the list of tuple representing the packing movements from/to - default to sequence value
529
 
        stay = [(from_pack, to_pack)]
530
 
        # the list of tuple representing the draft packing movements from/to
531
 
        back_to_draft = []
532
 
        
533
 
        # loop the partials
534
 
        for partial in datas:
535
 
            return_from = partial['return_from']
536
 
            return_to = partial['return_to']
537
 
            # create the corresponding tuple
538
 
            back_to_draft.append((return_from, return_to))
539
 
            # stay list must be ordered
540
 
            sorted(stay)
541
 
            # find the corresponding tuple in the stay list
542
 
            for i in range(len(stay)):
543
 
                # the tuple are ordered
544
 
                seq = stay[i]
545
 
                if seq[1] >= return_to:
546
 
                    # this is the good tuple
547
 
                    # stay tuple creation logic
548
 
                    if return_from == seq[0]:
549
 
                        if return_to == seq[1]:
550
 
                            # all packs for this sequence are sent back - simply remove it
551
 
                            break
552
 
                        else:
553
 
                            # to+1-seq[1] in stay
554
 
                            stay.append((return_to+1, seq[1]))
555
 
                            break
556
 
                    
557
 
                    elif return_to == seq[1]:
558
 
                        # do not start at beginning, but same end
559
 
                        stay.append((seq[0], return_from-1))
560
 
                        break
561
 
                    
562
 
                    else:
563
 
                        # in the middle, two new tuple in stay
564
 
                        stay.append((seq[0], return_from-1))
565
 
                        stay.append((return_to+1, seq[1]))
566
 
                        break
567
 
            
568
 
            # old one is always removed
569
 
            stay.pop(i)
570
 
            
571
 
        # return both values - return order is important
572
 
        return stay, back_to_draft
573
 
    
574
 
    def do_return_packs_from_shipment(self, cr, uid, ids, context=None):
575
 
        '''
576
 
        return the packs to the corresponding draft packing object
577
 
        
578
 
        for each corresponding draft packing
579
 
        - 
580
 
        '''
581
 
        # integrity check
582
 
        assert context, 'no context, method call is wrong'
583
 
        assert 'partial_datas' in context, 'partial_datas no defined in context'
584
 
        
585
 
        pick_obj = self.pool.get('stock.picking')
586
 
        move_obj = self.pool.get('stock.move')
587
 
        wf_service = netsvc.LocalService("workflow")
588
 
        
589
 
        # data from wizard
590
 
        partial_datas = context['partial_datas']
591
 
        # shipment ids from ids must be equal to shipment ids from partial datas
592
 
        assert set(ids) == set(partial_datas.keys()), 'shipment ids from ids and partial do not match'
593
 
        
594
 
        # for each shipment
595
 
        for shipment_id in partial_datas:
596
 
            # for each packing
597
 
            for packing in pick_obj.browse(cr, uid, partial_datas[shipment_id].keys(), context=context):
598
 
                # corresponding draft packing -> backorder
599
 
                draft_packing_id = packing.backorder_id.id
600
 
                # corresponding draft shipment (all packing for a shipment belong to the same draft_shipment)
601
 
                draft_shipment_id = packing.backorder_id.shipment_id.id
602
 
                # for each sequence
603
 
                for from_pack in partial_datas[shipment_id][packing.id]:
604
 
                    for to_pack in partial_datas[shipment_id][packing.id][from_pack]:
605
 
                        # partial datas for one sequence of one packing
606
 
                        # could have multiple data multiple products in the same pack family
607
 
                        datas = partial_datas[shipment_id][packing.id][from_pack][to_pack]
608
 
                        # the corresponding moves
609
 
                        move_ids = move_obj.search(cr, uid, [('picking_id', '=', packing.id),
610
 
                                                             ('from_pack', '=', from_pack),
611
 
                                                             ('to_pack', '=', to_pack)], context=context)
612
 
                        
613
 
                        # compute the sequences to stay/to return to draft packing
614
 
                        stay, back_to_draft = self.compute_sequences(cr, uid, ids, context=context,
615
 
                                                                     datas=datas,
616
 
                                                                     from_pack=from_pack,
617
 
                                                                     to_pack=to_pack,)
618
 
                        
619
 
                        # we have the information concerning movements to update the packing and the draft packing
620
 
                        
621
 
                        # update the packing object, we update the existing move
622
 
                        # if needed new moves are created
623
 
                        updated = {}
624
 
                        for move in move_obj.browse(cr, uid, move_ids, context=context):
625
 
                            # update values
626
 
                            updated[move.id] = {'initial': move.product_qty, 'partial_qty': 0}
627
 
                            # loop through stay sequences
628
 
                            for seq in stay:
629
 
                                # corresponding number of packs
630
 
                                selected_number = seq[1] - seq[0] + 1
631
 
                                # quantity to return
632
 
                                new_qty = selected_number * move.qty_per_pack
633
 
                                # for both cases, we update the from/to and compute the corresponding quantity
634
 
                                # if the move has been updated already, we copy/update
635
 
                                values = {'from_pack': seq[0],
636
 
                                          'to_pack': seq[1],
637
 
                                          'product_qty': new_qty,
638
 
                                          'state': 'assigned'}
639
 
                                
640
 
                                # the original move is never modified, but canceled
641
 
                                updated[move.id]['partial_qty'] += new_qty
642
 
                                new_move_id = move_obj.copy(cr, uid, move.id, values, context=context)
643
 
                                
644
 
#                            # nothing stays
645
 
#                            if 'partial_qty' not in updated[move.id]:
646
 
#                                updated[move.id]['partial_qty'] = 0
647
 
                                    
648
 
                            # loop through back_to_draft sequences
649
 
                            for seq in back_to_draft:
650
 
                                # for each sequence we add the corresponding stock move to draft packing
651
 
                                # corresponding number of packs
652
 
                                selected_number = seq[1] - seq[0] + 1
653
 
                                # quantity to return
654
 
                                new_qty = selected_number * move.qty_per_pack
655
 
                                # values
656
 
                                location_dispatch = move.picking_id.warehouse_id.lot_dispatch_id.id
657
 
                                location_distrib = move.picking_id.warehouse_id.lot_distribution_id.id
658
 
                                values = {'from_pack': seq[0],
659
 
                                          'to_pack': seq[1],
660
 
                                          'product_qty': new_qty,
661
 
                                          'location_id': location_distrib,
662
 
                                          'location_dest_id': location_dispatch,
663
 
                                          'state': 'done'}
664
 
                                
665
 
                                # create a back move in the packing object
666
 
                                # distribution -> dispatch
667
 
                                new_back_move_id = move_obj.copy(cr, uid, move.id, values, context=context)
668
 
                                updated[move.id]['partial_qty'] += new_qty
669
 
 
670
 
                                # create the draft move
671
 
                                # dispatch -> distribution
672
 
                                # picking_id = draft_picking
673
 
                                values.update(location_id=location_dispatch,
674
 
                                              location_dest_id=location_distrib,
675
 
                                              picking_id=draft_packing_id,
676
 
                                              state='assigned')
677
 
                                new_draft_move_id = move_obj.copy(cr, uid, move.id, values, context=context)
678
 
                                
679
 
                            # quantities are right - stay + return qty = original qty
680
 
                            assert all([updated[m]['initial'] == updated[m]['partial_qty'] for m in updated.keys()]), 'initial quantity is not equal to the sum of partial quantities (%s).'%(updated)
681
 
                            # if packs are returned corresponding move is canceled
682
 
                            # cancel move or 0 qty + done ?
683
 
                            #move_obj.action_cancel(cr, uid, [move.id], context=context)
684
 
                            move_obj.write(cr, uid, [move.id], {'product_qty': 0.0, 'state': 'done', 'from_pack': 0, 'to_pack': 0,}, context=context)
685
 
            
686
 
            # log corresponding action
687
 
            shipment_name = self.read(cr, uid, shipment_id, ['name'], context=context)['name']
688
 
            self.log(cr, uid, shipment_id, _("Packs from the shipped Shipment (%s) have been returned to dispatch location.")%(shipment_name,))
689
 
            self.log(cr, uid, draft_shipment_id, _("The corresponding Draft Shipment (%s) has been updated.")%(packing.backorder_id.shipment_id.name,))
690
 
                            
691
 
        # call complete_finished on the shipment object
692
 
        # if everything is allright (all draft packing are finished) the shipment is done also 
693
 
        self.complete_finished(cr, uid, partial_datas.keys(), context=context)
694
 
        
695
 
        # TODO which behavior
696
 
        #return {'type': 'ir.actions.act_window_close'}
697
 
        data_obj = self.pool.get('ir.model.data')
698
 
        view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_shipment_form')
699
 
        view_id = view_id and view_id[1] or False
700
 
        return {
701
 
            'name':_("Shipment"),
702
 
            'view_mode': 'form,tree',
703
 
            'view_id': [view_id],
704
 
            'view_type': 'form',
705
 
            'res_model': 'shipment',
706
 
            'res_id': draft_shipment_id,
707
 
            'type': 'ir.actions.act_window',
708
 
            'target': 'crush',
709
 
        }
710
 
 
711
 
        return {'type': 'ir.actions.act_window_close'}
712
 
        
713
 
    def action_cancel(self, cr, uid, ids, context=None):
714
 
        '''
715
 
        cancel the shipment which is not yet shipped (packed state)
716
 
        
717
 
        - for each related packing object
718
 
         - trigger the cancel workflow signal
719
 
         logic is performed in the action_cancel method of stock.picking
720
 
        '''
721
 
        pick_obj = self.pool.get('stock.picking')
722
 
        wf_service = netsvc.LocalService("workflow")
723
 
        
724
 
        for shipment in self.browse(cr, uid, ids, context=context):
725
 
            # shipment state should be 'packed'
726
 
            assert shipment.state == 'packed', 'cannot ship a shipment which is not in correct state - packed - %s'%shipment.state
727
 
            # for each shipment
728
 
            packing_ids = pick_obj.search(cr, uid, [('shipment_id', '=', shipment.id)], context=context)
729
 
            # call cancel workflow on corresponding packing objects
730
 
            for packing in pick_obj.browse(cr, uid, packing_ids, context=context):
731
 
                # we cancel each picking object - action_cancel is overriden at stock_picking level for stock_picking of subtype == 'packing'
732
 
                wf_service.trg_validate(uid, 'stock.picking', packing.id, 'button_cancel', cr)
733
 
            # log corresponding action
734
 
            self.log(cr, uid, shipment.id, _("The Shipment (%s) has been canceled.")%(shipment.name,))
735
 
            self.log(cr, uid, shipment.backshipment_id.id, _("The corresponding Draft Shipment (%s) has been updated.")%(shipment.backshipment_id.name,))
736
 
                
737
 
        return True
738
 
    
739
 
    def ship(self, cr, uid, ids, context=None):
740
 
        '''
741
 
        we ship the created shipment, the state of the shipment is changed, we do not use any wizard
742
 
        - state of the shipment is updated to 'shipped'
743
 
        - copy the packing
744
 
        - modify locations of moves for the new packing
745
 
        - trigger the workflow button_confirm for the new packing
746
 
        - trigger the workflow to terminate the initial packing
747
 
        - update the draft_picking_id fields of pack_families
748
 
        - update the shipment_date of the corresponding sale_order if not set yet
749
 
        '''
750
 
        pick_obj = self.pool.get('stock.picking')
751
 
        pf_obj = self.pool.get('pack.family')
752
 
        so_obj = self.pool.get('sale.order')
753
 
        # objects
754
 
        date_tools = self.pool.get('date.tools')
755
 
        db_datetime_format = date_tools.get_db_datetime_format(cr, uid, context=context)
756
 
        
757
 
        for shipment in self.browse(cr, uid, ids, context=context):
758
 
            # shipment state should be 'packed'
759
 
            assert shipment.state == 'packed', 'cannot ship a shipment which is not in correct state - packed - %s'%shipment.state
760
 
            # the state does not need to be updated - function
761
 
            # update actual ship date (shipment_actual_date) to today + time
762
 
            today = time.strftime(db_datetime_format)
763
 
            shipment.write({'shipment_actual_date': today,})
764
 
            # corresponding packing objects
765
 
            packing_ids = pick_obj.search(cr, uid, [('shipment_id', '=', shipment.id)], context=context)
766
 
            
767
 
            for packing in pick_obj.browse(cr, uid, packing_ids, context=context):
768
 
                assert packing.subtype == 'packing'
769
 
                # update the packing object for the same reason
770
 
                # - an integrity check at _get_vals level of shipment states that all packing linked to a shipment must have the same state
771
 
                # we therefore modify it before the copy, otherwise new (assigned) and old (done) are linked to the same shipment
772
 
                # -> integrity check has been removed
773
 
                pick_obj.write(cr, uid, [packing.id], {'shipment_id': False,}, context=context)
774
 
                # copy each packing
775
 
                new_packing_id = pick_obj.copy(cr, uid, packing.id, {'name': packing.name,
776
 
                                                                     'first_shipment_packing_id': packing.id,
777
 
                                                                     'shipment_id': shipment.id,}, context=dict(context, keep_prodlot=True, allow_copy=True,))
778
 
                pick_obj.write(cr, uid, [new_packing_id], {'origin': packing.origin}, context=context)
779
 
                new_packing = pick_obj.browse(cr, uid, new_packing_id, context=context)
780
 
                # update the shipment_date of the corresponding sale order if the date is not set yet - with current date
781
 
                if new_packing.sale_id and not new_packing.sale_id.shipment_date:
782
 
                    # get the date format
783
 
                    date_tools = self.pool.get('date.tools')
784
 
                    date_format = date_tools.get_date_format(cr, uid, context=context)
785
 
                    db_date_format = date_tools.get_db_date_format(cr, uid, context=context)
786
 
                    today = time.strftime(date_format)
787
 
                    today_db = time.strftime(db_date_format)
788
 
                    so_obj.write(cr, uid, [new_packing.sale_id.id], {'shipment_date': today_db,}, context=context)
789
 
                    so_obj.log(cr, uid, new_packing.sale_id.id, _("Shipment Date of the Field Order '%s' has been updated to %s.")%(new_packing.sale_id.name, today))
790
 
                
791
 
                # update locations of stock moves
792
 
                for move in new_packing.move_lines:
793
 
                    move.write({'location_id': new_packing.warehouse_id.lot_distribution_id.id,
794
 
                                'location_dest_id': new_packing.warehouse_id.lot_output_id.id}, context=context)
795
 
                
796
 
                wf_service = netsvc.LocalService("workflow")
797
 
                wf_service.trg_validate(uid, 'stock.picking', new_packing_id, 'button_confirm', cr)
798
 
                # simulate check assign button, as stock move must be available
799
 
                pick_obj.force_assign(cr, uid, [new_packing_id])
800
 
                # trigger standard workflow
801
 
                pick_obj.action_move(cr, uid, [packing.id])
802
 
                wf_service.trg_validate(uid, 'stock.picking', packing.id, 'button_done', cr)
803
 
                
804
 
            # log the ship action
805
 
            self.log(cr, uid, shipment.id, _('The Shipment %s has been shipped.')%(shipment.name,))
806
 
    
807
 
        # TODO which behavior
808
 
        return True
809
 
    
810
 
    def complete_finished(self, cr, uid, ids, context=None):
811
 
        '''
812
 
        - check all draft packing corresponding to this shipment
813
 
          - check the stock moves (qty and from/to)
814
 
          - check all corresponding packing are done or canceled (no ongoing shipment)
815
 
          - if all packings are ok, the draft is validated
816
 
        - if all draft packing are ok, the shipment state is done
817
 
        '''
818
 
        pick_obj = self.pool.get('stock.picking')
819
 
        wf_service = netsvc.LocalService("workflow")
820
 
        
821
 
        for shipment_base in self.browse(cr, uid, ids, context=context):
822
 
            # the shipment which will be treated
823
 
            shipment = shipment_base
824
 
            
825
 
            if shipment.state not in ('draft',):
826
 
                # it's not a draft shipment, check all corresponding packing, trg.write them
827
 
                packing_ids = pick_obj.search(cr, uid, [('shipment_id', '=', shipment.id),], context=context)
828
 
                for packing_id in packing_ids:
829
 
                    wf_service.trg_write(uid, 'stock.picking', packing_id, cr)
830
 
                
831
 
                # this shipment is possibly finished, we now check the corresponding draft shipment
832
 
                # this will possibly validate the draft shipment, if everything is finished and corresponding draft picking
833
 
                shipment = shipment.backshipment_id
834
 
                
835
 
            # draft packing for this shipment - some draft packing can already be done for this shipment, so we filter according to state
836
 
            draft_packing_ids = pick_obj.search(cr, uid, [('shipment_id', '=', shipment.id), ('state', '=', 'draft'),], context=context)
837
 
            for draft_packing in pick_obj.browse(cr, uid, draft_packing_ids, context=context):
838
 
                assert draft_packing.subtype == 'packing', 'draft packing which is not packing subtype - %s'%draft_packing.subtype
839
 
                assert draft_packing.state == 'draft', 'draft packing which is not draft state - %s'%draft_packing.state
840
 
                # we check if the corresponding draft packing can be moved to done.
841
 
                # if all packing with backorder_id equal to draft are done or canceled
842
 
                # and the quantity for each stock move (state != done) of the draft packing is equal to zero
843
 
                
844
 
                # we first check the stock moves quantities of the draft packing
845
 
                # we can have done moves when some packs are returned
846
 
                treat_draft = True
847
 
                for move in draft_packing.move_lines:
848
 
                    if move.state not in ('done',):
849
 
                        if move.product_qty:
850
 
                            treat_draft = False
851
 
                        elif move.from_pack or move.to_pack:
852
 
                            # qty = 0, from/to pack should have been set to zero
853
 
                            assert False, 'stock moves with 0 quantity but part of pack family sequence'
854
 
                
855
 
                # check if ongoing packing are present, if present, we do not validate the draft one, the shipping is not finished
856
 
                if treat_draft:
857
 
                    linked_packing_ids = pick_obj.search(cr, uid, [('backorder_id', '=', draft_packing.id),
858
 
                                                                   ('state', 'not in', ['done', 'cancel'])], context=context)
859
 
                    if linked_packing_ids:
860
 
                        treat_draft = False
861
 
                
862
 
                if treat_draft:
863
 
                    # trigger the workflow for draft_picking
864
 
                    # confirm the new picking ticket
865
 
                    wf_service.trg_validate(uid, 'stock.picking', draft_packing.id, 'button_confirm', cr)
866
 
                    # we force availability
867
 
                    pick_obj.force_assign(cr, uid, [draft_packing.id])
868
 
                    # finish
869
 
                    pick_obj.action_move(cr, uid, [draft_packing.id])
870
 
                    wf_service.trg_validate(uid, 'stock.picking', draft_packing.id, 'button_done', cr)
871
 
                    # ask for draft picking validation, depending on picking completion
872
 
                    # if picking ticket is not completed, the validation will not complete
873
 
                    draft_packing.previous_step_id.previous_step_id.backorder_id.validate(context=context)
874
 
            
875
 
            # all draft packing are validated (done state) - the state of shipment is automatically updated -> function
876
 
        return True
877
 
    
878
 
    def shipment_create_invoice(self, cr, uid, ids, context=None):
879
 
        '''
880
 
        Create invoices for validated shipment
881
 
        '''
882
 
        invoice_obj = self.pool.get('account.invoice')
883
 
        line_obj = self.pool.get('account.invoice.line')
884
 
        partner_obj = self.pool.get('res.partner')
885
 
        distrib_obj = self.pool.get('analytic.distribution')
886
 
        sale_line_obj = self.pool.get('sale.order.line')
887
 
        sale_obj = self.pool.get('sale.order')
888
 
        company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
889
 
        
890
 
        if not context:
891
 
            context = {}
892
 
            
893
 
        if isinstance(ids, (int, long)):
894
 
            ids = [ids]
895
 
            
896
 
        for shipment in self.browse(cr, uid, ids, context=context):
897
 
            make_invoice = False
898
 
            for pack in shipment.pack_family_memory_ids:
899
 
                for move in pack.move_lines:
900
 
                    if move.state != 'cancel' and (not move.sale_line_id or move.sale_line_id.order_id.order_policy == 'picking'):
901
 
                        make_invoice = True
902
 
                    
903
 
            if not make_invoice:
904
 
                continue
905
 
                    
906
 
            payment_term_id = False
907
 
            partner =  shipment.partner_id2
908
 
            if not partner:
909
 
                raise osv.except_osv(_('Error, no partner !'),
910
 
                    _('Please put a partner on the shipment if you want to generate invoice.'))
911
 
            
912
 
            inv_type = 'out_invoice'
913
 
            
914
 
            if inv_type in ('out_invoice', 'out_refund'):
915
 
                account_id = partner.property_account_receivable.id
916
 
                payment_term_id = partner.property_payment_term and partner.property_payment_term.id or False
917
 
            else:
918
 
                account_id = partner.property_account_payable.id
919
 
            
920
 
            addresses = partner_obj.address_get(cr, uid, [partner.id], ['contact', 'invoice'])
921
 
            today = time.strftime('%Y-%m-%d',time.localtime())  
922
 
            
923
 
            invoice_vals = {
924
 
                    'name': shipment.name,
925
 
                    'origin': shipment.name or '',
926
 
                    'type': inv_type,
927
 
                    'account_id': account_id,
928
 
                    'partner_id': partner.id,
929
 
                    'address_invoice_id': addresses['invoice'],
930
 
                    'address_contact_id': addresses['contact'],
931
 
                    'payment_term': payment_term_id,
932
 
                    'fiscal_position': partner.property_account_position.id,
933
 
                    'date_invoice': context.get('date_inv',False) or today,
934
 
                    'user_id':uid,
935
 
                }
936
 
 
937
 
            cur_id = shipment.pack_family_memory_ids[0].currency_id.id
938
 
            if cur_id:
939
 
                invoice_vals['currency_id'] = cur_id
940
 
            # Journal type
941
 
            journal_type = 'sale'
942
 
            # Disturb journal for invoice only on intermission partner type
943
 
            if shipment.partner_id2.partner_type == 'intermission':
944
 
                if not company.intermission_default_counterpart or not company.intermission_default_counterpart.id:
945
 
                    raise osv.except_osv(_('Error'), _('Please configure a default intermission account in Company configuration.'))
946
 
                invoice_vals['is_intermission'] = True
947
 
                invoice_vals['account_id'] = company.intermission_default_counterpart.id
948
 
                journal_type = 'intermission'
949
 
            journal_ids = self.pool.get('account.journal').search(cr, uid, [('type', '=', journal_type),
950
 
                                                                            ('is_current_instance', '=', True)])
951
 
            if not journal_ids:
952
 
                raise osv.except_osv(_('Warning'), _('No %s journal found!') % (journal_type,))
953
 
            invoice_vals['journal_id'] = journal_ids[0]
954
 
                
955
 
            invoice_id = invoice_obj.create(cr, uid, invoice_vals,
956
 
                        context=context)
957
 
            
958
 
            # Change currency for the intermission invoice
959
 
            if shipment.partner_id2.partner_type == 'intermission':
960
 
                company_currency = company.currency_id and company.currency_id.id or False
961
 
                if not company_currency:
962
 
                    raise osv.except_osv(_('Warning'), _('No company currency found!'))
963
 
                wiz_account_change = self.pool.get('account.change.currency').create(cr, uid, {'currency_id': company_currency})
964
 
                self.pool.get('account.change.currency').change_currency(cr, uid, [wiz_account_change], context={'active_id': invoice_id})
965
 
            
966
 
            # Link the invoice to the shipment
967
 
            self.write(cr, uid, [shipment.id], {'invoice_id': invoice_id}, context=context)
968
 
            
969
 
            # For each stock moves, create an invoice line
970
 
            for pack in shipment.pack_family_memory_ids:
971
 
                for move in pack.move_lines:
972
 
                    if move.state == 'cancel':
973
 
                        continue
974
 
                    
975
 
                    if move.sale_line_id and move.sale_line_id.order_id.order_policy != 'picking':
976
 
                        continue
977
 
                    
978
 
                    origin = move.picking_id.name or ''
979
 
                    if move.picking_id.origin:
980
 
                        origin += ':' + move.picking_id.origin
981
 
                        
982
 
                    if inv_type in ('out_invoice', 'out_refund'):
983
 
                        account_id = move.product_id.product_tmpl_id.\
984
 
                                property_account_income.id
985
 
                        if not account_id:
986
 
                            account_id = move.product_id.categ_id.\
987
 
                                    property_account_income_categ.id
988
 
                    else:
989
 
                        account_id = move.product_id.product_tmpl_id.\
990
 
                                property_account_expense.id
991
 
                        if not account_id:
992
 
                            account_id = move.product_id.categ_id.\
993
 
                                    property_account_expense_categ.id
994
 
                                    
995
 
                    # Compute unit price from FO line if the move is linked to
996
 
                    price_unit = move.product_id.list_price
997
 
                    if move.sale_line_id and move.sale_line_id.product_id.id == move.product_id.id:
998
 
                        uom_id = move.product_id.uom_id.id
999
 
                        uos_id = move.product_id.uos_id and move.product_id.uos_id.id or False
1000
 
                        price = move.sale_line_id.price_unit
1001
 
                        coeff = move.product_id.uos_coeff
1002
 
                        if uom_id != uos_id and coeff != 0:
1003
 
                            price_unit = price / coeff
1004
 
                        else:
1005
 
                            price_unit = move.sale_line_id.price_unit
1006
 
                            
1007
 
                    # Get discount from FO line
1008
 
                    discount = 0.00
1009
 
                    if move.sale_line_id and move.sale_line_id.product_id.id == move.product_id.id:
1010
 
                        discount = move.sale_line_id.discount
1011
 
                        
1012
 
                    # Get taxes from FO line
1013
 
                    taxes = move.product_id.taxes_id
1014
 
                    if move.sale_line_id and move.sale_line_id.product_id.id == move.product_id.id:
1015
 
                        taxes = [x.id for x in move.sale_line_id.tax_id]
1016
 
                        
1017
 
                    if shipment.partner_id2:
1018
 
                        tax_ids = self.pool.get('account.fiscal.position').map_tax(cr, uid, shipment.partner_id2.property_account_position, taxes)
1019
 
                    else:
1020
 
                        tax_ids = map(lambda x: x.id, taxes)
1021
 
                        
1022
 
                    distrib_id = False
1023
 
                    if move.sale_line_id:
1024
 
                        sol_ana_dist_id = move.sale_line_id.analytic_distribution_id or move.sale_line_id.order_id.analytic_distribution_id
1025
 
                        if sol_ana_dist_id:
1026
 
                            distrib_id = distrib_obj.copy(cr, uid, sol_ana_dist_id.id, context=context)
1027
 
                        
1028
 
                    #set UoS if it's a sale and the picking doesn't have one
1029
 
                    uos_id = move.product_uos and move.product_uos.id or False
1030
 
                    if not uos_id and inv_type in ('out_invoice', 'out_refund'):
1031
 
                        uos_id = move.product_uom.id
1032
 
                    account_id = self.pool.get('account.fiscal.position').map_account(cr, uid, partner.property_account_position, account_id)
1033
 
                        
1034
 
                    line_id = line_obj.create(cr, uid, {'name': move.name,
1035
 
                                                        'origin': origin,
1036
 
                                                        'invoice_id': invoice_id,
1037
 
                                                        'uos_id': uos_id,
1038
 
                                                        'product_id': move.product_id.id,
1039
 
                                                        'account_id': account_id,
1040
 
                                                        'price_unit': price_unit,
1041
 
                                                        'discount': discount,
1042
 
                                                        'quantity': move.product_qty or move.product_uos_qty,
1043
 
                                                        'invoice_line_tax_id': [(6, 0, tax_ids)],
1044
 
                                                        'analytic_distribution_id': distrib_id,
1045
 
                                                       }, context=context)
1046
 
 
1047
 
                    self.pool.get('shipment').write(cr, uid, [shipment.id], {'invoice_id': invoice_id}, context=context)
1048
 
                    if move.sale_line_id:
1049
 
                        sale_obj.write(cr, uid, [move.sale_line_id.order_id.id], {'invoice_ids': [(4, invoice_id)],})
1050
 
                        sale_line_obj.write(cr, uid, [move.sale_line_id.id], {'invoiced': True,
1051
 
                                                                              'invoice_lines': [(4, line_id)],})
1052
 
            
1053
 
        return True
1054
 
        
1055
 
    def validate(self, cr, uid, ids, context=None):
1056
 
        '''
1057
 
        validate the shipment
1058
 
        
1059
 
        change the state to Done for the corresponding packing
1060
 
        - validate the workflow for all the packings
1061
 
        '''
1062
 
        pick_obj = self.pool.get('stock.picking')
1063
 
        wf_service = netsvc.LocalService("workflow")
1064
 
        
1065
 
        for shipment in self.browse(cr, uid, ids, context=context):
1066
 
            # validate should only be called on shipped shipments
1067
 
            assert shipment.state in ('shipped',), 'shipment state is not shipped'
1068
 
            # corresponding packing objects - only the distribution -> customer ones
1069
 
            # we have to discard picking object with state done, because when we return from shipment
1070
 
            # all object of a given picking object, he is set to Done and still belong to the same shipment_id
1071
 
            # another possibility would be to unlink the picking object from the shipment, set shipment_id to False
1072
 
            # but in this case the returned pack families would not be displayed anymore in the shipment
1073
 
            packing_ids = pick_obj.search(cr, uid, [('shipment_id', '=', shipment.id), ('state', '!=', 'done'),], context=context)
1074
 
            
1075
 
            for packing in pick_obj.browse(cr, uid, packing_ids, context=context):
1076
 
                assert packing.subtype == 'packing' and packing.state == 'assigned'
1077
 
                # trigger standard workflow
1078
 
                pick_obj.action_move(cr, uid, [packing.id])
1079
 
                wf_service.trg_validate(uid, 'stock.picking', packing.id, 'button_done', cr)
1080
 
            
1081
 
            # Create automatically the invoice
1082
 
            self.shipment_create_invoice(cr, uid, shipment.id, context=context)
1083
 
                
1084
 
            # log validate action
1085
 
            self.log(cr, uid, shipment.id, _('The Shipment %s has been closed.')%(shipment.name,))
1086
 
            
1087
 
        result = self.complete_finished(cr, uid, ids, context=context)
1088
 
        return True
1089
 
    
1090
 
    def set_delivered(self, cr, uid, ids, context=None):
1091
 
        '''
1092
 
        set the delivered flag
1093
 
        '''
1094
 
        # objects
1095
 
        pick_obj = self.pool.get('stock.picking')
1096
 
        for shipment in self.browse(cr, uid, ids, context=context):
1097
 
            # validate should only be called on shipped shipments
1098
 
            assert shipment.state in ['done'], 'shipment state is not shipped'
1099
 
            # gather the corresponding packing and trigger the corresponding function
1100
 
            packing_ids = pick_obj.search(cr, uid, [('shipment_id', '=', shipment.id), ('state', '=', 'done')], context=context)
1101
 
            # set delivered all packings
1102
 
            pick_obj.set_delivered(cr, uid, packing_ids, context=context)
1103
 
            
1104
 
        return True
1105
 
        
1106
 
shipment()
1107
 
 
1108
 
 
1109
 
class pack_family_memory(osv.osv_memory):
1110
 
    '''
1111
 
    dynamic memory object for pack families
1112
 
    '''
1113
 
    _name = 'pack.family.memory'
1114
 
    
1115
 
    def _vals_get(self, cr, uid, ids, fields, arg, context=None):
1116
 
        '''
1117
 
        get functional values
1118
 
        '''
1119
 
        result = {}
1120
 
        for pf_memory in self.browse(cr, uid, ids, context=context):
1121
 
            values = {'move_lines': [],
1122
 
                      'state': 'draft',
1123
 
                      'location_id': False,
1124
 
                      'location_dest_id': False,
1125
 
                      'total_amount': 0.0,
1126
 
                      'amount': 0.0,
1127
 
                      'currency_id': False,
1128
 
                      'num_of_packs': 0,
1129
 
                      'total_weight': 0.0,
1130
 
                      'total_volume': 0.0,
1131
 
                      }
1132
 
            result[pf_memory.id] = values
1133
 
            # pack family related fields
1134
 
            if pf_memory.to_pack == 0:
1135
 
                num_of_packs = 0
1136
 
            else:
1137
 
                num_of_packs = pf_memory.to_pack - pf_memory.from_pack + 1
1138
 
            values['num_of_packs'] = num_of_packs
1139
 
            values['total_weight'] = pf_memory.weight * num_of_packs
1140
 
            values['total_volume'] = (pf_memory.length * pf_memory.width * pf_memory.height * num_of_packs) / 1000.0
1141
 
            
1142
 
            # moves related fields
1143
 
            for move in pf_memory.draft_packing_id.move_lines:
1144
 
                if move.from_pack == pf_memory.from_pack:
1145
 
                    if move.to_pack == pf_memory.to_pack:
1146
 
                        # this move is in the good packing object and corresponds to this pack family
1147
 
                        # we add it to the stock move list
1148
 
                        values['move_lines'].append(move.id)
1149
 
                        values['state'] = move.state
1150
 
                        values['location_id'] = move.location_id.id
1151
 
                        values['location_dest_id'] = move.location_dest_id.id
1152
 
                        values['total_amount'] += move.total_amount
1153
 
                        values['amount'] += move.amount
1154
 
                        values['currency_id'] = move.currency_id and move.currency_id.id or False
1155
 
                    else:
1156
 
                        # when multiple moves are modified from/to values, the first one would raise an exception as the second one is not written yet
1157
 
                        pass
1158
 
                        #raise osv.except_osv(_('Error !'), _('Integrity check failed! Pack Family and Stock Moves from/to do not match.'))
1159
 
                    
1160
 
        return result
1161
 
 
1162
 
    _columns = {'name': fields.char(string='Reference', size=1024),
1163
 
                'shipment_id': fields.many2one('shipment', string='Shipment'),
1164
 
                'draft_packing_id': fields.many2one('stock.picking', string="Draft Packing Ref"),
1165
 
                'sale_order_id': fields.many2one('sale.order', string="Sale Order Ref"),
1166
 
                'ppl_id': fields.many2one('stock.picking', string="PPL Ref"),
1167
 
                'from_pack': fields.integer(string='From p.'),
1168
 
                'to_pack': fields.integer(string='To p.'),
1169
 
                'pack_type': fields.many2one('pack.type', string='Pack Type'),
1170
 
                'length' : fields.float(digits=(16,2), string='Length [cm]'),
1171
 
                'width' : fields.float(digits=(16,2), string='Width [cm]'),
1172
 
                'height' : fields.float(digits=(16,2), string='Height [cm]'),
1173
 
                'weight' : fields.float(digits=(16,2), string='Weight p.p [kg]'),
1174
 
                # functions
1175
 
                'move_lines': fields.function(_vals_get, method=True, type='one2many', relation='stock.move', string='Stock Moves', multi='get_vals',),
1176
 
                'state': fields.function(_vals_get, method=True, type='selection', selection=[('draft', 'Draft'),
1177
 
                                                                                              ('assigned', 'Available'),
1178
 
                                                                                              ('stock_return', 'Returned to Stock'),
1179
 
                                                                                              ('ship_return', 'Returned from Shipment'),
1180
 
                                                                                              ('cancel', 'Cancelled'),
1181
 
                                                                                              ('done', 'Closed'),], string='State', multi='get_vals',),
1182
 
                'location_id': fields.function(_vals_get, method=True, type='many2one', relation='stock.location', string='Src Loc.', multi='get_vals',),
1183
 
                'location_dest_id': fields.function(_vals_get, method=True, type='many2one', relation='stock.location', string='Dest. Loc.', multi='get_vals',),
1184
 
                'total_amount': fields.function(_vals_get, method=True, type='float', string='Total Amount', multi='get_vals',),
1185
 
                'amount': fields.function(_vals_get, method=True, type='float', string='Pack Amount', multi='get_vals',),
1186
 
                'currency_id': fields.function(_vals_get, method=True, type='many2one', relation='res.currency', string='Currency', multi='get_vals',),
1187
 
                'num_of_packs': fields.function(_vals_get, method=True, type='integer', string='#Packs', multi='get_vals',),
1188
 
                'total_weight': fields.function(_vals_get, method=True, type='float', string='Total Weight[kg]', multi='get_vals',),
1189
 
                'total_volume': fields.function(_vals_get, method=True, type='float', string=u'Total Volume[dm³]', multi='get_vals',),
1190
 
                'description_ppl': fields.char('Description', size=256 ),
1191
 
                }
1192
 
    
1193
 
    _defaults = {'shipment_id': False,
1194
 
                 'draft_packing_id': False,
1195
 
                 }
1196
 
    
1197
 
pack_family_memory()
1198
 
 
1199
 
 
1200
 
class shipment2(osv.osv):
1201
 
    '''
1202
 
    add pack_family_ids
1203
 
    '''
1204
 
    _inherit = 'shipment'
1205
 
    
1206
 
    def on_change_partner(self, cr, uid, ids, partner_id, address_id, context=None):
1207
 
        '''
1208
 
        Change the delivery address when the partner change.
1209
 
        '''
1210
 
        v = {}
1211
 
        d = {}
1212
 
        
1213
 
        if not partner_id:
1214
 
            v.update({'address_id': False})
1215
 
        else:
1216
 
            d.update({'address_id': [('partner_id', '=', partner_id)]})
1217
 
            
1218
 
 
1219
 
        if address_id:
1220
 
            addr = self.pool.get('res.partner.address').browse(cr, uid, address_id, context=context)
1221
 
        
1222
 
        if not address_id or addr.partner_id.id != partner_id:
1223
 
            addr = self.pool.get('res.partner').address_get(cr, uid, partner_id, ['delivery', 'default'])
1224
 
            if not addr.get('delivery'):
1225
 
                addr = addr.get('default')
1226
 
            else:
1227
 
                addr = addr.get('delivery')
1228
 
                
1229
 
            v.update({'address_id': addr})
1230
 
            
1231
 
        
1232
 
        return {'value': v,
1233
 
                'domain': d}
1234
 
    
1235
 
    def _vals_get_2(self, cr, uid, ids, fields, arg, context=None):
1236
 
        '''
1237
 
        get functional values
1238
 
        '''
1239
 
        picking_obj = self.pool.get('stock.picking')
1240
 
        
1241
 
        result = {}
1242
 
        for shipment in self.browse(cr, uid, ids, context=context):
1243
 
            values = {'pack_family_memory_ids':[],
1244
 
                      }
1245
 
            result[shipment.id] = values
1246
 
            # look for all corresponding packing
1247
 
            packing_ids = picking_obj.search(cr, uid, [('shipment_id', '=', shipment.id),], context=context)
1248
 
            # get the corresponding data
1249
 
            data = picking_obj.generate_data_from_picking_for_pack_family(cr, uid, packing_ids, context=context)
1250
 
            # create a memory family
1251
 
            created_ids = picking_obj.create_pack_families_memory_from_data(cr, uid, data, shipment.id, context=context)
1252
 
            values['pack_family_memory_ids'].extend(created_ids)
1253
 
            
1254
 
        return result
1255
 
    
1256
 
    _columns = {'pack_family_memory_ids': fields.function(_vals_get_2, method=True, type='one2many', relation='pack.family.memory', string='Memory Families', multi='get_vals_2',),
1257
 
                }
1258
 
 
1259
 
shipment2()
1260
 
 
1261
 
 
1262
 
class ppl_customize_label(osv.osv):
1263
 
    '''
1264
 
    label preferences
1265
 
    '''
1266
 
    _name = 'ppl.customize.label'
1267
 
    
1268
 
    def init(self, cr):
1269
 
        """
1270
 
        Load msf_outgoing_data.xml before self
1271
 
        """
1272
 
        if hasattr(super(ppl_customize_label, self), 'init'):
1273
 
            super(ppl_customize_label, self).init(cr)
1274
 
 
1275
 
        mod_obj = self.pool.get('ir.module.module')
1276
 
        logging.getLogger('init').info('HOOK: module msf_outgoing: loading data/msf_outgoing_data.xml')
1277
 
        pathname = path.join('msf_outgoing', 'data/msf_outgoing_data.xml')
1278
 
        file = tools.file_open(pathname)
1279
 
        tools.convert_xml_import(cr, 'msf_outgoing', file, {}, mode='init', noupdate=False)
1280
 
 
1281
 
    _columns = {'name': fields.char(string='Name', size=1024,),
1282
 
                'notes': fields.text(string='Notes'),
1283
 
                #'packing_list_reference': fields.boolean(string='Packing List Reference'),
1284
 
                'pre_packing_list_reference': fields.boolean(string='Pre-Packing List Reference'),
1285
 
                'destination_partner': fields.boolean(string='Destination Partner'),
1286
 
                'destination_address': fields.boolean(string='Destination Address'),
1287
 
                'requestor_order_reference': fields.boolean(string='Requestor Order Reference'),
1288
 
                'weight': fields.boolean(string='Weight'),
1289
 
                #'shipment_reference': fields.boolean(string='Shipment Reference'),
1290
 
                'packing_parcel_number': fields.boolean(string='Packing Parcel Number'),
1291
 
                #'expedition_parcel_number': fields.boolean(string='Expedition Parcel Number'),
1292
 
                'specific_information': fields.boolean(string='Specific Information'),
1293
 
                'logo': fields.boolean(string='Company Logo'),
1294
 
                }
1295
 
    
1296
 
    _defaults = {'name': 'My Customization',
1297
 
                'notes': '',
1298
 
                #'packing_list_reference': True,
1299
 
                'pre_packing_list_reference': True,
1300
 
                'destination_partner': True,
1301
 
                'destination_address': True,
1302
 
                'requestor_order_reference': True,
1303
 
                'weight': True,
1304
 
                #'shipment_reference': True,
1305
 
                'packing_parcel_number': True,
1306
 
                #'expedition_parcel_number': True,
1307
 
                'specific_information': True,
1308
 
                'logo': True,
1309
 
                }
1310
 
 
1311
 
ppl_customize_label()
1312
 
 
1313
 
 
1314
 
class stock_picking(osv.osv):
1315
 
    '''
1316
 
    override stock picking to add new attributes
1317
 
    - flow_type: the type of flow (full, quick)
1318
 
    - subtype: the subtype of picking object (picking, ppl, packing)
1319
 
    - previous_step_id: the id of picking object of the previous step, picking for ppl, ppl for packing
1320
 
    '''
1321
 
    _inherit = 'stock.picking'
1322
 
    _name = 'stock.picking'
1323
 
 
1324
 
    def fields_view_get(self, cr, uid, view_id, view_type, context=None, toolbar=False, submenu=False):
1325
 
        '''
1326
 
        Set the appropriate search view according to the context
1327
 
        '''
1328
 
        if not context:
1329
 
            context = {}
1330
 
 
1331
 
        if not view_id and context.get('wh_dashboard') and view_type == 'search':
1332
 
            try:
1333
 
                if context.get('pick_type') == 'incoming':
1334
 
                    view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', 'view_picking_in_search')[1]
1335
 
                elif context.get('pick_type') == 'delivery':
1336
 
                    view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', 'view_picking_out_search')[1]
1337
 
                elif context.get('pick_type') == 'picking_ticket':
1338
 
                    view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_search')[1]
1339
 
                elif context.get('pick_type') == 'pack':
1340
 
                    view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_outgoing', 'view_ppl_search')[1]
1341
 
            except ValueError:
1342
 
                pass
1343
 
 
1344
 
        return super(stock_picking, self).fields_view_get(cr, uid, view_id, view_type, context=context, toolbar=toolbar, submenu=submenu)
1345
 
    
1346
 
    def unlink(self, cr, uid, ids, context=None):
1347
 
        '''
1348
 
        unlink test for draft
1349
 
        '''
1350
 
        datas = self.read(cr, uid, ids, ['state','type','subtype'], context=context)
1351
 
        if [data for data in datas if data['state'] != 'draft']:
1352
 
            raise osv.except_osv(_('Warning !'), _('Only draft picking tickets can be deleted.'))
1353
 
        ids_picking_draft = [data['id'] for data in datas if data['subtype'] == 'picking' and data['type'] == 'out' and data['state'] == 'draft']
1354
 
        if ids_picking_draft:
1355
 
            data = self.has_picking_ticket_in_progress(cr, uid, ids, context=context)
1356
 
            if [x for x in data.values() if x]:
1357
 
                raise osv.except_osv(_('Warning !'), _('Some Picking Tickets are in progress. Return products to stock from ppl and shipment and try again.'))
1358
 
        
1359
 
        return super(stock_picking, self).unlink(cr, uid, ids, context=context)
1360
 
   
1361
 
    def _hook_picking_get_view(self, cr, uid, ids, context=None, *args, **kwargs):
1362
 
        pick = kwargs['pick']
1363
 
        obj_data = self.pool.get('ir.model.data')
1364
 
        view_list = {'standard': ('stock', 'view_picking_out_form'),
1365
 
                     'picking': ('msf_outgoing', 'view_picking_ticket_form'),
1366
 
                     'ppl': ('msf_outgoing', 'view_ppl_form'),
1367
 
                     'packing': ('msf_outgoing', 'view_packing_form'),
1368
 
                     }
1369
 
        if pick.type == 'out':
1370
 
            context.update({'picking_type': pick.subtype == 'standard' and 'delivery_order' or 'picking_ticket'})
1371
 
            module, view = view_list.get(pick.subtype,('msf_outgoing', 'view_picking_ticket_form'))
1372
 
            try:
1373
 
                return obj_data.get_object_reference(cr, uid, module, view)
1374
 
            except ValueError, e:
1375
 
                pass
1376
 
        elif pick.type == 'in':
1377
 
            context.update({'picking_type': 'incoming_shipment'})
1378
 
        else:
1379
 
            context.update({'picking_type': 'internal_move'})
1380
 
        
1381
 
        return super(stock_picking, self)._hook_picking_get_view(cr, uid, ids, context=context, *args, **kwargs)
1382
 
 
1383
 
    def _hook_custom_log(self, cr, uid, ids, context=None, *args, **kwargs):
1384
 
        '''
1385
 
        hook from stock>stock.py>log_picking
1386
 
        update the domain and other values if necessary in the log creation
1387
 
        '''
1388
 
        result = super(stock_picking, self)._hook_custom_log(cr, uid, ids, context=context, *args, **kwargs)
1389
 
        pick_obj = self.pool.get('stock.picking')
1390
 
        pick = kwargs['pick']
1391
 
        message = kwargs['message']
1392
 
        if pick.type and pick.subtype:
1393
 
            domain = [('type', '=', pick.type), ('subtype', '=', pick.subtype)]
1394
 
            return self.pool.get('res.log').create(cr, uid,
1395
 
                                                   {'name': message,
1396
 
                                                    'res_model': pick_obj._name,
1397
 
                                                    'secondary': False,
1398
 
                                                    'res_id': pick.id,
1399
 
                                                    'domain': domain,
1400
 
                                                    }, context=context)
1401
 
        return result
1402
 
 
1403
 
    def _hook_log_picking_log_cond(self, cr, uid, ids, context=None, *args, **kwargs):
1404
 
        '''
1405
 
        hook from stock>stock.py>stock_picking>log_picking
1406
 
        specify if we display a log or not
1407
 
        '''
1408
 
        result = super(stock_picking, self)._hook_log_picking_log_cond(cr, uid, ids, context=context, *args, **kwargs)
1409
 
        pick = kwargs['pick']
1410
 
        if pick.subtype == 'packing':
1411
 
            return False
1412
 
        # if false the log will be defined by the method _hook_custom_log (which include a domain)
1413
 
        if pick.type and pick.subtype:
1414
 
            return False
1415
 
 
1416
 
        return result
1417
 
    
1418
 
    def copy(self, cr, uid, id, default=None, context=None):
1419
 
        '''
1420
 
        set the name corresponding to object subtype
1421
 
        '''
1422
 
        if default is None:
1423
 
            default = {}
1424
 
        if context is None:
1425
 
            context = {}
1426
 
        obj = self.browse(cr, uid, id, context=context)
1427
 
        if not context.get('allow_copy', False):
1428
 
            if obj.subtype == 'picking':
1429
 
                if not obj.backorder_id:
1430
 
                    # draft, new ref
1431
 
                    default.update(name=self.pool.get('ir.sequence').get(cr, uid, 'picking.ticket'),
1432
 
                                   origin=False,
1433
 
                                   date=date.today().strftime('%Y-%m-%d'),
1434
 
                                   sale_id=False,
1435
 
                                   )
1436
 
                else:
1437
 
                    # if the corresponding draft picking ticket is done, we do not allow copy
1438
 
                    if obj.backorder_id and obj.backorder_id.state == 'done':
1439
 
                        raise osv.except_osv(_('Error !'), _('Corresponding Draft picking ticket is Closed. This picking ticket cannot be copied.'))
1440
 
                    # picking ticket, use draft sequence, keep other fields
1441
 
                    base = obj.name
1442
 
                    base = base.split('-')[0] + '-'
1443
 
                    default.update(name=base + obj.backorder_id.sequence_id.get_id(test='id', context=context),
1444
 
                                   date=date.today().strftime('%Y-%m-%d'),
1445
 
                                   )
1446
 
                    
1447
 
            elif obj.subtype == 'ppl':
1448
 
                raise osv.except_osv(_('Error !'), _('Pre-Packing List copy is forbidden.'))
1449
 
                # ppl, use the draft picking ticket sequence
1450
 
#                if obj.previous_step_id and obj.previous_step_id.backorder_id:
1451
 
#                    base = obj.name
1452
 
#                    base = base.split('-')[0] + '-'
1453
 
#                    default.update(name=base + obj.previous_step_id.backorder_id.sequence_id.get_id(test='id', context=context))
1454
 
#                else:
1455
 
#                    default.update(name=self.pool.get('ir.sequence').get(cr, uid, 'ppl'))
1456
 
                
1457
 
        result = super(stock_picking, self).copy(cr, uid, id, default=default, context=context)
1458
 
        if not context.get('allow_copy', False):
1459
 
            if obj.subtype == 'picking' and obj.backorder_id:
1460
 
                # confirm the new picking ticket - the picking ticket should not stay in draft state !
1461
 
                wf_service = netsvc.LocalService("workflow")
1462
 
                wf_service.trg_validate(uid, 'stock.picking', result, 'button_confirm', cr)
1463
 
                # we force availability
1464
 
                self.force_assign(cr, uid, [result])
1465
 
        return result
1466
 
    
1467
 
    def copy_data(self, cr, uid, id, default=None, context=None):
1468
 
        '''
1469
 
        reset one2many fields
1470
 
        '''
1471
 
        if default is None:
1472
 
            default = {}
1473
 
        if context is None:
1474
 
            context = {}
1475
 
        # reset one2many fields
1476
 
        default.update(backorder_ids=[])
1477
 
        default.update(previous_step_ids=[])
1478
 
        default.update(pack_family_memory_ids=[])
1479
 
        
1480
 
        context['not_workflow'] = True
1481
 
        result = super(stock_picking, self).copy_data(cr, uid, id, default=default, context=context)
1482
 
        
1483
 
        return result
1484
 
    
1485
 
    def _erase_prodlot_hook(self, cr, uid, id, context=None, *args, **kwargs):
1486
 
        '''
1487
 
        hook to keep the production lot when a stock move is copied
1488
 
        '''
1489
 
        res = super(stock_picking, self)._erase_prodlot_hook(cr, uid, id, context=context, *args, **kwargs)
1490
 
        
1491
 
        return res and not context.get('keep_prodlot', False)
1492
 
    
1493
 
    def has_picking_ticket_in_progress(self, cr, uid, ids, context=None):
1494
 
        '''
1495
 
        ids is the list of draft picking object we want to test
1496
 
        completed means, we recursively check that next_step link object is cancel or done
1497
 
        
1498
 
        return true if picking tickets are in progress, meaning picking ticket or ppl or shipment not done exist
1499
 
        '''
1500
 
        if context is None:
1501
 
            context = {}
1502
 
        if isinstance(ids, (int, long)):
1503
 
            ids = []
1504
 
        res = {}
1505
 
        for obj in self.browse(cr, uid, ids, context=context):
1506
 
            # by default, nothing is in progress
1507
 
            res[obj.id] = False
1508
 
            # treat only draft picking
1509
 
            assert obj.subtype in 'picking' and obj.state == 'draft', 'the validate function should only be called on draft picking ticket objects'
1510
 
            for picking in obj.backorder_ids:
1511
 
                # take care, is_completed returns a dictionary
1512
 
                if not picking.is_completed()[picking.id]:
1513
 
                    res[obj.id] = True
1514
 
                    break
1515
 
        
1516
 
        return res
1517
 
    
1518
 
    def validate(self, cr, uid, ids, context=None):
1519
 
        '''
1520
 
        validate or not the draft picking ticket
1521
 
        '''
1522
 
        # objects
1523
 
        move_obj = self.pool.get('stock.move')
1524
 
        
1525
 
        for draft_picking in self.browse(cr, uid, ids, context=context):
1526
 
            # the validate function should only be called on draft picking ticket
1527
 
            assert draft_picking.subtype == 'picking' and draft_picking.state == 'draft', 'the validate function should only be called on draft picking ticket objects'
1528
 
            #check the qty of all stock moves
1529
 
            treat_draft = True
1530
 
            move_ids = move_obj.search(cr, uid, [('picking_id', '=', draft_picking.id),
1531
 
                                                 ('product_qty', '!=', 0.0),
1532
 
                                                 ('state', 'not in', ['done', 'cancel'])], context=context)
1533
 
            if move_ids:
1534
 
                treat_draft = False
1535
 
            
1536
 
            if treat_draft:
1537
 
                # then all child picking must be fully completed, meaning:
1538
 
                # - all picking must be 'completed'
1539
 
                # completed means, we recursively check that next_step link object is cancel or done
1540
 
                if self.has_picking_ticket_in_progress(cr, uid, [draft_picking.id], context=context)[draft_picking.id]:
1541
 
                    treat_draft = False
1542
 
            
1543
 
            if treat_draft:
1544
 
                # - all picking are completed (means ppl completed and all shipment validated)
1545
 
                wf_service = netsvc.LocalService("workflow")
1546
 
                wf_service.trg_validate(uid, 'stock.picking', draft_picking.id, 'button_confirm', cr)
1547
 
                # we force availability
1548
 
                draft_picking.force_assign()
1549
 
                # finish
1550
 
                draft_picking.action_move()
1551
 
                wf_service.trg_validate(uid, 'stock.picking', draft_picking.id, 'button_done', cr)
1552
 
                
1553
 
        return True
1554
 
    
1555
 
    def _vals_get_2(self, cr, uid, ids, fields, arg, context=None):
1556
 
        '''
1557
 
        get functional values
1558
 
        '''
1559
 
        result = {}
1560
 
        for stock_picking in self.browse(cr, uid, ids, context=context):
1561
 
            values = {'pack_family_memory_ids':[],
1562
 
                      }
1563
 
            result[stock_picking.id] = values
1564
 
            
1565
 
            # get the corresponding data for pack family memory
1566
 
            data = self.generate_data_from_picking_for_pack_family(cr, uid, [stock_picking.id], context=context)
1567
 
            # create a memory family - no shipment id
1568
 
            created_ids = self.create_pack_families_memory_from_data(cr, uid, data, shipment_id=False, context=context)
1569
 
            values['pack_family_memory_ids'].extend(created_ids)
1570
 
                    
1571
 
        return result
1572
 
    
1573
 
    def _vals_get(self, cr, uid, ids, fields, arg, context=None):
1574
 
        '''
1575
 
        get functional values
1576
 
        '''
1577
 
        result = {}
1578
 
        for stock_picking in self.browse(cr, uid, ids, context=context):
1579
 
            values = {'total_amount': 0.0,
1580
 
                      'currency_id': False,
1581
 
                      'is_dangerous_good': False,
1582
 
                      'is_keep_cool': False,
1583
 
                      'is_narcotic': False,
1584
 
                      'num_of_packs': 0,
1585
 
                      'total_volume': 0.0,
1586
 
                      'total_weight': 0.0,
1587
 
                      #'is_completed': False,
1588
 
                      'overall_qty': 0.0,
1589
 
                      }
1590
 
            result[stock_picking.id] = values
1591
 
            
1592
 
            for family in stock_picking.pack_family_memory_ids:
1593
 
                # number of packs from pack_family
1594
 
                num_of_packs = family.num_of_packs
1595
 
                values['num_of_packs'] += int(num_of_packs)
1596
 
                # total_weight
1597
 
                total_weight = family.total_weight
1598
 
                values['total_weight'] += total_weight
1599
 
                total_volume = family.total_volume
1600
 
                values['total_volume'] += total_volume
1601
 
                
1602
 
            for move in stock_picking.move_lines:
1603
 
                # total amount (float)
1604
 
                total_amount = move.total_amount
1605
 
                values['total_amount'] = total_amount
1606
 
                # currency
1607
 
                values['currency_id'] = move.currency_id and move.currency_id.id or False
1608
 
                # dangerous good
1609
 
                values['is_dangerous_good'] = move.is_dangerous_good
1610
 
                # keep cool - if heat_sensitive_item is True
1611
 
                values['is_keep_cool'] = move.is_keep_cool
1612
 
                # narcotic
1613
 
                values['is_narcotic'] = move.is_narcotic
1614
 
                # overall qty of products in all corresponding stock moves
1615
 
                values['overall_qty'] += move.product_qty
1616
 
                
1617
 
            # completed field - based on the previous_step_ids field, recursive call from picking to draft packing and packing
1618
 
            # - picking checks that the corresponding ppl is completed
1619
 
            # - ppl checks that the corresponding draft packing and packings are completed
1620
 
            # the recursion stops there because packing does not have previous_step_ids values
1621
 
#            completed = stock_picking.state in ('done', 'cancel')
1622
 
#            if completed:
1623
 
#                for next_step in stock_picking.previous_step_ids:
1624
 
#                    if not next_step.is_completed:
1625
 
#                        completed = False
1626
 
#                        break
1627
 
#                    
1628
 
#            values['is_completed'] = completed
1629
 
                    
1630
 
        return result
1631
 
    
1632
 
    def is_completed(self, cr, uid, ids, context=None):
1633
 
        '''
1634
 
        recursive test of completion
1635
 
        - to be applied on picking ticket
1636
 
        
1637
 
        ex:
1638
 
        for picking in draft_picking.backorder_ids:
1639
 
            # take care, is_completed returns a dictionary
1640
 
            if not picking.is_completed()[picking.id]:
1641
 
                ...balbala
1642
 
        
1643
 
        ***BEWARE: RETURNS A DICTIONARY !
1644
 
        '''
1645
 
        result = {}
1646
 
        for stock_picking in self.browse(cr, uid, ids, context=context):
1647
 
            # for debugging
1648
 
            state = stock_picking.state
1649
 
            subtype = stock_picking.subtype
1650
 
            completed = stock_picking.state in ('done', 'cancel')
1651
 
            result[stock_picking.id] = completed
1652
 
            if completed:
1653
 
                for next_step in stock_picking.previous_step_ids:
1654
 
                    if not next_step.is_completed()[next_step.id]:
1655
 
                        completed = False
1656
 
                        result[stock_picking.id] = completed
1657
 
                        break
1658
 
        
1659
 
        return result
1660
 
    
1661
 
    def init(self, cr):
1662
 
        """
1663
 
        Load msf_outgoing_data.xml before self
1664
 
        """
1665
 
        if hasattr(super(stock_picking, self), 'init'):
1666
 
            super(stock_picking, self).init(cr)
1667
 
 
1668
 
        mod_obj = self.pool.get('ir.module.module')
1669
 
        demo = False
1670
 
        mod_id = mod_obj.search(cr, 1, [('name', '=', 'msf_outgoing'),])
1671
 
        if mod_id:
1672
 
            demo = mod_obj.read(cr, 1, mod_id, ['demo'])[0]['demo']
1673
 
 
1674
 
        if demo:
1675
 
            logging.getLogger('init').info('HOOK: module msf_outgoing: loading data/msf_outgoing_data.xml')
1676
 
            pathname = path.join('msf_outgoing', 'data/msf_outgoing_data.xml')
1677
 
            file = tools.file_open(pathname)
1678
 
            tools.convert_xml_import(cr, 'msf_outgoing', file, {}, mode='init', noupdate=False)
1679
 
            
1680
 
    def _qty_search(self, cr, uid, obj, name, args, context=None):
1681
 
        """ Searches Ids of stock picking
1682
 
            @return: Ids of locations
1683
 
        """
1684
 
        if context is None:
1685
 
            context = {}
1686
 
            
1687
 
        stock_pickings = self.pool.get('stock.picking').search(cr, uid, [], context=context)
1688
 
        # result dic
1689
 
        result = {}
1690
 
        for stock_picking in self.browse(cr, uid, stock_pickings, context=context):
1691
 
            result[stock_picking.id] = 0.0
1692
 
            for move in stock_picking.move_lines:
1693
 
                result[stock_picking.id] += move.product_qty
1694
 
        # construct the request
1695
 
        # adapt the operator
1696
 
        op = args[0][1]
1697
 
        if op == '=':
1698
 
            op = '=='
1699
 
        ids = [('id', 'in', [x for x in result.keys() if eval("%s %s %s"%(result[x], op, args[0][2]))])]
1700
 
        return ids
1701
 
    
1702
 
    def _get_picking_ids(self, cr, uid, ids, context=None):
1703
 
        '''
1704
 
        ids represents the ids of stock.move objects for which values have changed
1705
 
        return the list of ids of picking object which need to get their state field updated
1706
 
        
1707
 
        self is stock.move object
1708
 
        '''
1709
 
        result = []
1710
 
        for obj in self.browse(cr, uid, ids, context=context):
1711
 
            if obj.picking_id and obj.picking_id.id not in result:
1712
 
                result.append(obj.picking_id.id)
1713
 
        return result 
1714
 
    
1715
 
    _columns = {'flow_type': fields.selection([('full', 'Full'),('quick', 'Quick')], readonly=True, states={'draft': [('readonly', False),],}, string='Flow Type'),
1716
 
                'subtype': fields.selection([('standard', 'Standard'), ('picking', 'Picking'),('ppl', 'PPL'),('packing', 'Packing')], string='Subtype'),
1717
 
                'backorder_ids': fields.one2many('stock.picking', 'backorder_id', string='Backorder ids',),
1718
 
                'previous_step_id': fields.many2one('stock.picking', 'Previous step'),
1719
 
                'previous_step_ids': fields.one2many('stock.picking', 'previous_step_id', string='Previous Step ids',),
1720
 
                'shipment_id': fields.many2one('shipment', string='Shipment'),
1721
 
                'sequence_id': fields.many2one('ir.sequence', 'Picking Ticket Sequence', help="This field contains the information related to the numbering of the picking tickets.", ondelete='cascade'),
1722
 
                'first_shipment_packing_id': fields.many2one('stock.picking', 'Shipment First Step'),
1723
 
                #'pack_family_ids': fields.one2many('pack.family', 'ppl_id', string='Pack Families',),
1724
 
                # attributes for specific packing labels
1725
 
                'ppl_customize_label': fields.many2one('ppl.customize.label', string='Labels Customization',),
1726
 
                # warehouse info (locations) are gathered from here - allow shipment process without sale order
1727
 
                'warehouse_id': fields.many2one('stock.warehouse', string='Warehouse', required=True,),
1728
 
                # flag for converted picking
1729
 
                'converted_to_standard': fields.boolean(string='Converted to Standard'),
1730
 
                # functions
1731
 
                'num_of_packs': fields.function(_vals_get, method=True, type='integer', string='#Packs', multi='get_vals_X'), # old_multi get_vals
1732
 
                'total_volume': fields.function(_vals_get, method=True, type='float', string=u'Total Volume[dm³]', multi='get_vals'),
1733
 
                'total_weight': fields.function(_vals_get, method=True, type='float', string='Total Weight[kg]', multi='get_vals'),
1734
 
                'total_amount': fields.function(_vals_get, method=True, type='float', string='Total Amount', digits_compute=dp.get_precision('Picking Price'), multi='get_vals'),
1735
 
                'currency_id': fields.function(_vals_get, method=True, type='many2one', relation='res.currency', string='Currency', multi='get_vals'),
1736
 
                'is_dangerous_good': fields.function(_vals_get, method=True, type='boolean', string='Dangerous Good', multi='get_vals'),
1737
 
                'is_keep_cool': fields.function(_vals_get, method=True, type='boolean', string='Keep Cool', multi='get_vals'),
1738
 
                'is_narcotic': fields.function(_vals_get, method=True, type='boolean', string='Narcotic', multi='get_vals'),
1739
 
                'overall_qty': fields.function(_vals_get, method=True, fnct_search=_qty_search, type='float', string='Overall Qty', multi='get_vals',
1740
 
                                               store= {'stock.move': (_get_picking_ids, ['product_qty', 'picking_id'], 10),}),
1741
 
                #'is_completed': fields.function(_vals_get, method=True, type='boolean', string='Completed Process', multi='get_vals',),
1742
 
                'pack_family_memory_ids': fields.function(_vals_get_2, method=True, type='one2many', relation='pack.family.memory', string='Memory Families', multi='get_vals_2',),
1743
 
                'description_ppl': fields.char('Description', size=256 ),
1744
 
                }
1745
 
    _defaults = {'flow_type': 'full',
1746
 
                 'ppl_customize_label': lambda obj, cr, uid, c: len(obj.pool.get('ppl.customize.label').search(cr, uid, [('name', '=', 'Default Label'),], context=c)) and obj.pool.get('ppl.customize.label').search(cr, uid, [('name', '=', 'Default Label'),], context=c)[0] or False,
1747
 
                 'subtype': 'standard',
1748
 
                 'first_shipment_packing_id': False,
1749
 
                 'warehouse_id': lambda obj, cr, uid, c: len(obj.pool.get('stock.warehouse').search(cr, uid, [], context=c)) and obj.pool.get('stock.warehouse').search(cr, uid, [], context=c)[0] or False,
1750
 
                 'converted_to_standard': False,
1751
 
                 }
1752
 
    #_order = 'origin desc, name asc'
1753
 
    _order = 'name desc'
1754
 
    
1755
 
    def picking_ticket_data(self, cr, uid, ids, context=None):
1756
 
        '''
1757
 
        generate picking ticket data for report creation
1758
 
        
1759
 
        - sale order line without product: does not work presently
1760
 
        
1761
 
        - many sale order line with same product: stored in different dictionary with line id as key.
1762
 
            so the same product could be displayed many times in the picking ticket according to sale order
1763
 
        
1764
 
        - many stock move with same product: two cases, if from different sale order lines, the above rule applies,
1765
 
            if from the same order line, they will be stored according to prodlot id
1766
 
            
1767
 
        - many stock move with same prodlot (so same product): if same sale order line, the moves will be
1768
 
            stored in the same structure, with global quantity, i.e. this batch for this product for this
1769
 
            sale order line will be displayed only once with summed quantity from concerned stock moves
1770
 
        
1771
 
        [sale_line.id][product_id][prodlot_id]
1772
 
        
1773
 
        other prod lot, not used are added in order that all prod lot are displayed 
1774
 
        
1775
 
        to check, if a move does not come from the sale order line:
1776
 
        stored with line id False, product is relevant, multiple
1777
 
        product for the same 0 line id is possible
1778
 
        '''
1779
 
        result = {}
1780
 
        for stock_picking in self.browse(cr, uid, ids, context=context):
1781
 
            values = {}
1782
 
            result[stock_picking.id] = {'obj': stock_picking,
1783
 
                                        'lines': values,
1784
 
                                        }
1785
 
            for move in stock_picking.move_lines:
1786
 
                if move.product_id: # product is mandatory at stock_move level ;)
1787
 
                    sale_line_id = move.sale_line_id and move.sale_line_id.id or False
1788
 
                    # structure, data is reorganized in order to regroup according to sale order line > product > production lot
1789
 
                    # and to sum the quantities corresponding to different levels because this is impossible within the rml framework
1790
 
                    values \
1791
 
                        .setdefault(sale_line_id, {}) \
1792
 
                        .setdefault('products', {}) \
1793
 
                        .setdefault(move.product_id.id, {}) \
1794
 
                        .setdefault('uoms', {}) \
1795
 
                        .setdefault(move.product_uom.id, {}) \
1796
 
                        .setdefault('lots', {})
1797
 
                        
1798
 
                    # ** sale order line info**
1799
 
                    values[sale_line_id]['obj'] = move.sale_line_id or False
1800
 
                    
1801
 
                    # **uom level info**
1802
 
                    values[sale_line_id]['products'][move.product_id.id]['uoms'][move.product_uom.id]['obj'] = move.product_uom
1803
 
                    
1804
 
                    # **prodlot level info**
1805
 
                    if move.prodlot_id:
1806
 
                        values[sale_line_id]['products'][move.product_id.id]['uoms'][move.product_uom.id]['lots'].setdefault(move.prodlot_id.id, {})
1807
 
                        # qty corresponding to this production lot
1808
 
                        values[sale_line_id]['products'][move.product_id.id]['uoms'][move.product_uom.id]['lots'][move.prodlot_id.id].setdefault('reserved_qty', 0)
1809
 
                        values[sale_line_id]['products'][move.product_id.id]['uoms'][move.product_uom.id]['lots'][move.prodlot_id.id]['reserved_qty'] += move.product_qty
1810
 
                        # store the object for info retrieval
1811
 
                        values[sale_line_id]['products'][move.product_id.id]['uoms'][move.product_uom.id]['lots'][move.prodlot_id.id]['obj'] = move.prodlot_id
1812
 
                    
1813
 
                    # **product level info**
1814
 
                    # total quantity from STOCK_MOVES for one sale order line (directly for one product)
1815
 
                    # or if not linked to a sale order line, stock move created manually, the line id is False
1816
 
                    # and in this case the product is important
1817
 
                    values[sale_line_id]['products'][move.product_id.id]['uoms'][move.product_uom.id].setdefault('qty_to_pick_sm', 0)
1818
 
                    values[sale_line_id]['products'][move.product_id.id]['uoms'][move.product_uom.id]['qty_to_pick_sm'] += move.product_qty
1819
 
                    # total quantity from SALE_ORDER_LINES, which can be different from the one from stock moves
1820
 
                    # if stock moves have been created manually in the picking, no present in the so, equal to 0 if not linked to an so
1821
 
                    values[sale_line_id]['products'][move.product_id.id]['uoms'][move.product_uom.id].setdefault('qty_to_pick_so', 0)
1822
 
                    values[sale_line_id]['products'][move.product_id.id]['uoms'][move.product_uom.id]['qty_to_pick_so'] += move.sale_line_id and move.sale_line_id.product_uom_qty or 0.0 
1823
 
                    # store the object for info retrieval
1824
 
                    values[sale_line_id]['products'][move.product_id.id]['obj'] = move.product_id
1825
 
                    
1826
 
            # all moves have been treated
1827
 
            # complete the lot lists for each product
1828
 
            for sale_line in values.values():
1829
 
                for product in sale_line['products'].values():
1830
 
                    for uom in product['uoms'].values():
1831
 
                        # loop through all existing production lot for this product - all are taken into account, internal and external
1832
 
                        for lot in product['obj'].prodlot_ids:
1833
 
                            if lot.id not in uom['lots'].keys():
1834
 
                                # the lot is not present, we add it
1835
 
                                uom['lots'][lot.id] = {}
1836
 
                                uom['lots'][lot.id]['obj'] = lot
1837
 
                                # reserved qty is 0 since no stock moves correspond to this lot
1838
 
                                uom['lots'][lot.id]['reserved_qty'] = 0.0
1839
 
                    
1840
 
        return result
1841
 
    
1842
 
    def create_sequence(self, cr, uid, vals, context=None):
1843
 
        """
1844
 
        Create new entry sequence for every new picking
1845
 
        @param cr: cursor to database
1846
 
        @param user: id of current user
1847
 
        @param ids: list of record ids to be process
1848
 
        @param context: context arguments, like lang, time zone
1849
 
        @return: return a result
1850
 
        
1851
 
        example of name: 'PICK/xxxxx'
1852
 
        example of code: 'picking.xxxxx'
1853
 
        example of prefix: 'PICK'
1854
 
        example of padding: 5
1855
 
        """
1856
 
        seq_pool = self.pool.get('ir.sequence')
1857
 
        seq_typ_pool = self.pool.get('ir.sequence.type')
1858
 
        
1859
 
        default_name = 'Stock Picking'
1860
 
        default_code = 'stock.picking'
1861
 
        default_prefix = ''
1862
 
        default_padding = 0
1863
 
        
1864
 
        if vals is None:
1865
 
            vals = {}
1866
 
 
1867
 
        name = vals.get('name', False)
1868
 
        if not name:
1869
 
            name = default_name
1870
 
        code = vals.get('code', False)
1871
 
        if not code:
1872
 
            code = default_code
1873
 
        prefix = vals.get('prefix', False)
1874
 
        if not prefix:
1875
 
            prefix = default_prefix
1876
 
        padding = vals.get('padding', False)
1877
 
        if not padding:
1878
 
            padding = default_padding
1879
 
 
1880
 
        types = {
1881
 
            'name': name,
1882
 
            'code': code
1883
 
        }
1884
 
        seq_typ_pool.create(cr, uid, types)
1885
 
 
1886
 
        seq = {
1887
 
            'name': name,
1888
 
            'code': code,
1889
 
            'prefix': prefix,
1890
 
            'padding': padding,
1891
 
        }
1892
 
        return seq_pool.create(cr, uid, seq)
1893
 
    
1894
 
    def generate_data_from_picking_for_pack_family(self, cr, uid, pick_ids, object_type='shipment', from_pack=False, to_pack=False, context=None):
1895
 
        '''
1896
 
        generate the data structure from the stock.picking object
1897
 
        
1898
 
        we can limit the generation to certain from/to sequence
1899
 
        
1900
 
        one data for each move_id - here is the difference with data generated from partial
1901
 
        
1902
 
        structure:
1903
 
            {pick_id: {from_pack: {to_pack: {move_id: {data}}}}}
1904
 
            
1905
 
        if the move has a quantity equal to 0, it means that no pack are available,
1906
 
        these moves are therefore not taken into account for the pack families generation
1907
 
        
1908
 
        TODO: integrity constraints
1909
 
        
1910
 
        Note: why the same dictionary is repeated n times for n moves, because
1911
 
        it is directly used when we create the pack families. could be refactored
1912
 
        with one dic per from/to with a 'move_ids' entry
1913
 
        '''
1914
 
        assert bool(from_pack) == bool(to_pack), 'from_pack and to_pack must be either both filled or empty'
1915
 
        result = {}
1916
 
        
1917
 
        
1918
 
        if object_type == 'shipment':
1919
 
            # all moves are taken into account, therefore the back moves are represented
1920
 
            # by done pack families
1921
 
            states = ('cancel')
1922
 
        elif object_type == 'memory':
1923
 
            # done moves are not displayed as pf as we cannot select these packs anymore (they are returned)
1924
 
            states = ('done')
1925
 
        else:
1926
 
            assert False, 'Should not reach this line'
1927
 
        
1928
 
        for pick in self.browse(cr, uid, pick_ids, context=context):
1929
 
            result[pick.id] = {}
1930
 
            for move in pick.move_lines:
1931
 
                if not from_pack or move.from_pack == from_pack:
1932
 
                    if not to_pack or move.to_pack == to_pack:
1933
 
                        # the quantity must be positive and the state depends on the window's type
1934
 
                        if move.product_qty and move.state not in states:
1935
 
                            # subtype == ppl - called from stock picking
1936
 
                            if pick.subtype == 'ppl':
1937
 
                                result[pick.id] \
1938
 
                                    .setdefault(move.from_pack, {}) \
1939
 
                                    .setdefault(move.to_pack, {})[move.id] = {'sale_order_id': pick.sale_id.id,
1940
 
                                                                              'ppl_id': pick.id, # only change between ppl - packing
1941
 
                                                                              'from_pack': move.from_pack,
1942
 
                                                                              'to_pack': move.to_pack,
1943
 
                                                                              'pack_type': move.pack_type.id,
1944
 
                                                                              'length': move.length,
1945
 
                                                                              'width': move.width,
1946
 
                                                                              'height': move.height,
1947
 
                                                                              'weight': move.weight,
1948
 
                                                                              'draft_packing_id': pick.id,
1949
 
                                                                              'description_ppl': pick.description_ppl,
1950
 
                                                                              }
1951
 
                            # subtype == packing - caled from shipment
1952
 
                            elif pick.subtype == 'packing':
1953
 
                                result[pick.id] \
1954
 
                                    .setdefault(move.from_pack, {}) \
1955
 
                                    .setdefault(move.to_pack, {})[move.id] = {'sale_order_id': pick.sale_id.id,
1956
 
                                                                              'ppl_id': pick.previous_step_id.id,
1957
 
                                                                              'from_pack': move.from_pack,
1958
 
                                                                              'to_pack': move.to_pack,
1959
 
                                                                              'pack_type': move.pack_type.id,
1960
 
                                                                              'length': move.length,
1961
 
                                                                              'width': move.width,
1962
 
                                                                              'height': move.height,
1963
 
                                                                              'weight': move.weight,
1964
 
                                                                              'draft_packing_id': pick.id,
1965
 
                                                                              }
1966
 
 
1967
 
                                if object_type != 'memory':
1968
 
                                        result[pick.id][move.from_pack][move.to_pack][move.id]['description_ppl'] = pick.description_ppl
1969
 
 
1970
 
        return result
1971
 
    
1972
 
    def create_pack_families_memory_from_data(self, cr, uid, data, shipment_id, context=None,):
1973
 
        '''
1974
 
        - clear existing pack family memory objects is not necessary thanks to vaccum system
1975
 
        -> in fact cleaning old memory objects reslults in a bug, because when we click on a
1976
 
           pf memory to see it's form view, the shipment view is regenerated (delete the pf)
1977
 
           but then the form view uses the old pf memory id for data and it crashes. Cleaning
1978
 
           of previous pf memory has therefore been removed.
1979
 
        - generate new ones based on data
1980
 
        - return created ids
1981
 
        '''
1982
 
        pf_memory_obj = self.pool.get('pack.family.memory')
1983
 
        # find and delete existing objects
1984
 
        #ids = pf_memory_obj.search(cr, uid, [('shipment_id', '=', shipment_id),], context=context)
1985
 
        #pf_memory_obj.unlink(cr, uid, ids, context=context)
1986
 
        created_ids = []
1987
 
        # create pack family memory
1988
 
        for picking in data.values():
1989
 
            for from_pack in picking.values():
1990
 
                for to_pack in from_pack.values():
1991
 
                    for move_id in to_pack.keys():
1992
 
                        move_data = to_pack[move_id]
1993
 
                    # create corresponding memory object
1994
 
                    move_data.update(name='_name',
1995
 
                                     shipment_id=shipment_id,)
1996
 
                    id = pf_memory_obj.create(cr, uid, move_data, context=context)
1997
 
                    created_ids.append(id)
1998
 
        return created_ids
1999
 
        
2000
 
    def create(self, cr, uid, vals, context=None):
2001
 
        '''
2002
 
        creation of a stock.picking of subtype 'packing' triggers
2003
 
        special behavior :
2004
 
         - creation of corresponding shipment
2005
 
        '''
2006
 
        # For picking ticket from scratch, invoice it !
2007
 
        if not vals.get('sale_id') and not vals.get('purchase_id') and not vals.get('invoice_state') and 'type' in vals and vals['type'] == 'out':
2008
 
            vals['invoice_state'] = '2binvoiced'
2009
 
        # objects
2010
 
        date_tools = self.pool.get('date.tools')
2011
 
        fields_tools = self.pool.get('fields.tools')
2012
 
        db_date_format = date_tools.get_db_date_format(cr, uid, context=context)
2013
 
        db_datetime_format = date_tools.get_db_datetime_format(cr, uid, context=context)
2014
 
        
2015
 
        if context is None:
2016
 
            context = {}
2017
 
        # the action adds subtype in the context depending from which screen it is created
2018
 
        if context.get('picking_screen', False) and not vals.get('name', False):
2019
 
            pick_name = self.pool.get('ir.sequence').get(cr, uid, 'picking.ticket')
2020
 
            vals.update(subtype='picking',
2021
 
                        backorder_id=False,
2022
 
                        name=pick_name,
2023
 
                        flow_type='full',
2024
 
                        )
2025
 
        
2026
 
        if context.get('ppl_screen', False) and not vals.get('name', False):
2027
 
            pick_name = self.pool.get('ir.sequence').get(cr, uid, 'ppl')
2028
 
            vals.update(subtype='ppl',
2029
 
                        backorder_id=False,
2030
 
                        name=pick_name,
2031
 
                        flow_type='full',
2032
 
                        )
2033
 
        # shipment object
2034
 
        shipment_obj = self.pool.get('shipment')
2035
 
        # move object
2036
 
        move_obj = self.pool.get('stock.move')
2037
 
        
2038
 
        # sequence creation
2039
 
        # if draft picking
2040
 
        if 'subtype' in vals and vals['subtype'] == 'picking':
2041
 
            # creation of a new picking ticket
2042
 
            assert 'backorder_id' in vals, 'No backorder_id'
2043
 
            
2044
 
            if not vals['backorder_id']:
2045
 
                # creation of *draft* picking ticket
2046
 
                vals.update(sequence_id=self.create_sequence(cr, uid, {'name':vals['name'],
2047
 
                                                                       'code':vals['name'],
2048
 
                                                                       'prefix':'',
2049
 
                                                                       'padding':2}, context=context))
2050
 
                
2051
 
        if 'subtype' in vals and vals['subtype'] == 'packing':
2052
 
            # creation of a new packing
2053
 
            assert 'backorder_id' in vals, 'No backorder_id'
2054
 
            assert 'shipment_id' in vals, 'No shipment_id'
2055
 
            
2056
 
            if not vals['backorder_id']:
2057
 
                # creation of *draft* picking ticket
2058
 
                vals.update(sequence_id=self.create_sequence(cr, uid, {'name':vals['name'],
2059
 
                                                                       'code':vals['name'],
2060
 
                                                                       'prefix':'',
2061
 
                                                                       'padding':2,
2062
 
                                                                       }, context=context))
2063
 
        
2064
 
        # create packing object
2065
 
        new_packing_id = super(stock_picking, self).create(cr, uid, vals, context=context)
2066
 
        
2067
 
        if 'subtype' in vals and vals['subtype'] == 'packing':
2068
 
            # creation of a new packing
2069
 
            assert 'backorder_id' in vals, 'No backorder_id'
2070
 
            assert 'shipment_id' in vals, 'No shipment_id'
2071
 
            
2072
 
            if vals['backorder_id'] and vals['shipment_id']:
2073
 
                # ship of existing shipment
2074
 
                # no new shipment
2075
 
                # TODO
2076
 
                return new_packing_id
2077
 
            
2078
 
            if vals['backorder_id'] and not vals['shipment_id']:
2079
 
                # data from do_create_shipment method
2080
 
                assert 'partial_datas_shipment' in context, 'Missing partial_datas_shipment'
2081
 
                assert 'draft_shipment_id' in context, 'Missing draft_shipment_id'
2082
 
                assert 'draft_packing_id' in context, 'Missing draft_packing_id'
2083
 
                assert 'shipment_id' in context, 'Missing shipment_id'
2084
 
                draft_shipment_id = context['draft_shipment_id']
2085
 
                draft_packing_id = context['draft_packing_id']
2086
 
                data = context['partial_datas_shipment'][draft_shipment_id][draft_packing_id]
2087
 
                shipment_id = context['shipment_id']
2088
 
                # We have a backorder_id, no shipment_id
2089
 
                # -> we have just created a shipment
2090
 
                # the created packing object has no stock_move
2091
 
                # - we create the sock move from the data in context
2092
 
                # - if no shipment in context, create a new shipment object
2093
 
                # - generate the data from the new picking object
2094
 
                # - create the pack families
2095
 
                for from_pack in data:
2096
 
                    for to_pack in data[from_pack]:
2097
 
                        # total number of packs
2098
 
                        total_num = to_pack - from_pack + 1
2099
 
                        # number of selected packs to ship
2100
 
                        # note: when the data is generated, lines without selected_number are not kept, so we have nothing to check here
2101
 
                        selected_number = data[from_pack][to_pack][0]['selected_number']
2102
 
                        # we take the packs with the highest numbers
2103
 
                        # new moves
2104
 
                        selected_from_pack = to_pack - selected_number + 1
2105
 
                        selected_to_pack = to_pack
2106
 
                        # update initial moves
2107
 
                        if selected_number == total_num:
2108
 
                            # if all packs have been selected, from/to are set to 0
2109
 
                            initial_from_pack = 0
2110
 
                            initial_to_pack = 0
2111
 
                        else:
2112
 
                            initial_from_pack = from_pack
2113
 
                            initial_to_pack = to_pack - selected_number
2114
 
                        
2115
 
                        # find the corresponding moves
2116
 
                        moves_ids = move_obj.search(cr, uid, [('picking_id', '=', draft_packing_id),
2117
 
                                                              ('from_pack', '=', from_pack),
2118
 
                                                              ('to_pack', '=', to_pack),], context=context)
2119
 
                        
2120
 
                        for move in move_obj.browse(cr, uid, moves_ids, context=context):
2121
 
                            # we compute the selected quantity
2122
 
                            selected_qty = move.qty_per_pack * selected_number
2123
 
                            # create the new move - store the back move from draft **packing** object
2124
 
                            new_move = move_obj.copy(cr, uid, move.id, {'picking_id': new_packing_id,
2125
 
                                                                        'product_qty': selected_qty,
2126
 
                                                                        'from_pack': selected_from_pack,
2127
 
                                                                        'to_pack': selected_to_pack,
2128
 
                                                                        'backmove_packing_id': move.id,}, context=context)
2129
 
                            
2130
 
                            # update corresponding initial move
2131
 
                            initial_qty = move.product_qty
2132
 
                            initial_qty = max(initial_qty - selected_qty, 0)
2133
 
                            # if all packs have been selected, from/to have been set to 0
2134
 
                            # update the original move object - the corresponding original shipment (draft)
2135
 
                            # is automatically updated generically in the write method
2136
 
                            move_obj.write(cr, uid, [move.id], {'product_qty': initial_qty,
2137
 
                                                                'from_pack': initial_from_pack,
2138
 
                                                                'to_pack': initial_to_pack}, context=context)
2139
 
            
2140
 
            if not vals['backorder_id']:
2141
 
                # creation of packing after ppl validation
2142
 
                # find an existing shipment or create one - depends on new pick state
2143
 
                shipment_ids = shipment_obj.search(cr, uid, [('state', '=', 'draft'), ('address_id', '=', vals['address_id'])], context=context)
2144
 
                # only one 'draft' shipment should be available
2145
 
                assert len(shipment_ids) in (0, 1), 'Only one draft shipment should be available for a given address at a time - %s'%len(shipment_ids)
2146
 
                # get rts of corresponding sale order
2147
 
                sale_id = self.read(cr, uid, [new_packing_id], ['sale_id'], context=context)
2148
 
                sale_id = sale_id[0]['sale_id']
2149
 
                if sale_id:
2150
 
                    sale_id = sale_id[0]
2151
 
                    # today
2152
 
                    today = time.strftime(db_datetime_format)
2153
 
                    rts = self.pool.get('sale.order').read(cr, uid, [sale_id], ['ready_to_ship_date'], context=context)[0]['ready_to_ship_date']
2154
 
                else:
2155
 
                    rts = date.today().strftime(db_date_format)
2156
 
                # rts + shipment lt
2157
 
                shipment_lt = fields_tools.get_field_from_company(cr, uid, object=self._name, field='shipment_lead_time', context=context)
2158
 
                rts_obj = datetime.strptime(rts, db_date_format)
2159
 
                rts = rts_obj + relativedelta(days=shipment_lt or 0)
2160
 
                rts = rts.strftime(db_date_format)
2161
 
                
2162
 
                if not len(shipment_ids):
2163
 
                    # no shipment, create one - no need to specify the state, it's a function
2164
 
                    name = self.pool.get('ir.sequence').get(cr, uid, 'shipment')
2165
 
                    addr = self.pool.get('res.partner.address').browse(cr, uid, vals['address_id'], context=context)
2166
 
                    partner_id = addr.partner_id and addr.partner_id.id or False
2167
 
                    values = {'name': name,
2168
 
                              'address_id': vals['address_id'],
2169
 
                              'partner_id2': partner_id,
2170
 
                              'shipment_expected_date': rts,
2171
 
                              'shipment_actual_date': rts,
2172
 
                              'sequence_id': self.create_sequence(cr, uid, {'name':name,
2173
 
                                                                            'code':name,
2174
 
                                                                            'prefix':'',
2175
 
                                                                            'padding':2}, context=context)}
2176
 
                    
2177
 
                    shipment_id = shipment_obj.create(cr, uid, values, context=context)
2178
 
                    shipment_obj.log(cr, uid, shipment_id, _('The new Draft Shipment %s has been created.')%(name,))
2179
 
                else:
2180
 
                    shipment_id = shipment_ids[0]
2181
 
                    shipment = shipment_obj.browse(cr, uid, shipment_id, context=context)
2182
 
                    # if expected ship date of shipment is greater than rts, update shipment_expected_date and shipment_actual_date
2183
 
                    shipment_expected = datetime.strptime(shipment.shipment_expected_date, db_datetime_format)
2184
 
                    if rts_obj < shipment_expected:
2185
 
                        shipment.write({'shipment_expected_date': rts, 'shipment_actual_date': rts,}, context=context)
2186
 
                    shipment_name = shipment.name
2187
 
                    shipment_obj.log(cr, uid, shipment_id, _('The ppl has been added to the existing Draft Shipment %s.')%(shipment_name,))
2188
 
            
2189
 
            # update the new pick with shipment_id
2190
 
            self.write(cr, uid, [new_packing_id], {'shipment_id': shipment_id}, context=context)
2191
 
            
2192
 
        return new_packing_id
2193
 
 
2194
 
    def _hook_action_assign_raise_exception(self, cr, uid, ids, context=None, *args, **kwargs):
2195
 
        '''
2196
 
        Please copy this to your module's method also.
2197
 
        This hook belongs to the action_assign method from stock>stock.py>stock_picking class
2198
 
        
2199
 
        - allow to choose wether or not an exception should be raised in case of no stock move
2200
 
        '''
2201
 
        res = super(stock_picking, self)._hook_action_assign_raise_exception(cr, uid, ids, context=context, *args, **kwargs)
2202
 
        return res and False
2203
 
    
2204
 
    def _hook_log_picking_modify_message(self, cr, uid, ids, context=None, *args, **kwargs):
2205
 
        '''
2206
 
        stock>stock.py>log_picking
2207
 
        update the message to be displayed by the function
2208
 
        '''
2209
 
        pick = kwargs['pick']
2210
 
        message = kwargs['message']
2211
 
        # if the picking is converted to standard, and state is confirmed
2212
 
        if pick.converted_to_standard and pick.state == 'confirmed':
2213
 
            return 'The Preparation Picking has been converted to simple Out. ' + message
2214
 
        if pick.type == 'out' and pick.subtype == 'picking':
2215
 
            kwargs['message'] = message.replace('Delivery Order', 'Picking Ticket')
2216
 
        elif pick.type == 'out' and pick.subtype == 'packing':
2217
 
            kwargs['message'] = message.replace('Delivery Order', 'Packing List')
2218
 
        elif pick.type == 'out' and pick.subtype == 'ppl':
2219
 
            kwargs['message'] = message.replace('Delivery Order', 'Pre-Packing List')
2220
 
        return super(stock_picking, self)._hook_log_picking_modify_message(cr, uid, ids, context, *args, **kwargs)
2221
 
    
2222
 
    def convert_to_standard(self, cr, uid, ids, context=None):
2223
 
        '''
2224
 
        check of back orders exists, if not, convert to standard: change subtype to standard, and trigger workflow
2225
 
        
2226
 
        only one picking object at a time
2227
 
        '''
2228
 
        if not context:
2229
 
            context = {}
2230
 
        # objects
2231
 
        date_tools = self.pool.get('date.tools')
2232
 
        fields_tools = self.pool.get('fields.tools')
2233
 
        db_date_format = date_tools.get_db_date_format(cr, uid, context=context)
2234
 
        for obj in self.browse(cr, uid, ids, context=context):
2235
 
            # the convert function should only be called on draft picking ticket
2236
 
            assert obj.subtype == 'picking' and obj.state == 'draft', 'the convert function should only be called on draft picking ticket objects'
2237
 
            if self.has_picking_ticket_in_progress(cr, uid, [obj.id], context=context)[obj.id]:
2238
 
                    raise osv.except_osv(_('Warning !'), _('Some Picking Tickets are in progress. Return products to stock from ppl and shipment and try again.'))
2239
 
            
2240
 
            # log a message concerning the conversion
2241
 
            new_name = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.out')
2242
 
            self.log(cr, uid, obj.id, _('The Preparation Picking (%s) has been converted to simple Out (%s).')%(obj.name, new_name))
2243
 
            # change subtype and name
2244
 
            obj.write({'name': new_name,
2245
 
                       'subtype': 'standard',
2246
 
                       'converted_to_standard': True,
2247
 
                       }, context=context)
2248
 
            # all destination location of the stock moves must be output location of warehouse - lot_output_id
2249
 
            # if corresponding sale order, date and date_expected are updated to rts + shipment lt
2250
 
            for move in obj.move_lines:
2251
 
                # was previously set to confirmed/assigned, otherwise, when we confirm the stock picking,
2252
 
                # using draft_force_assign, the moves are not treated because not in draft
2253
 
                # and the corresponding chain location on location_dest_id was not computed
2254
 
                # we therefore set them back in draft state before treatment
2255
 
                if move.product_qty == 0.0:
2256
 
                    vals = {'state': 'done'}
2257
 
                else:
2258
 
                    vals = {'state': 'draft'}
2259
 
                # If the move comes from a DPO, don't change the destination location
2260
 
                if not move.dpo_id:
2261
 
                    vals.update({'location_dest_id': obj.warehouse_id.lot_output_id.id})
2262
 
 
2263
 
                if obj.sale_id:
2264
 
                    # compute date
2265
 
                    shipment_lt = fields_tools.get_field_from_company(cr, uid, object=self._name, field='shipment_lead_time', context=context)
2266
 
                    rts = datetime.strptime(obj.sale_id.ready_to_ship_date, db_date_format)
2267
 
                    rts = rts + relativedelta(days=shipment_lt or 0)
2268
 
                    rts = rts.strftime(db_date_format)
2269
 
                    vals.update({'date': rts, 'date_expected': rts, 'state': 'draft'})
2270
 
                move.write(vals, context=context)
2271
 
                if move.product_qty == 0.00:
2272
 
                    move.action_done(context=context)
2273
 
 
2274
 
 
2275
 
            # trigger workflow (confirm picking)
2276
 
            self.draft_force_assign(cr, uid, [obj.id])
2277
 
            # check availability
2278
 
            self.action_assign(cr, uid, [obj.id], context=context)
2279
 
        
2280
 
            # TODO which behavior
2281
 
            data_obj = self.pool.get('ir.model.data')
2282
 
            view_id = data_obj.get_object_reference(cr, uid, 'stock', 'view_picking_out_form')
2283
 
            view_id = view_id and view_id[1] or False
2284
 
            context.update({'picking_type': 'delivery_order'})
2285
 
            return {'name':_("Delivery Orders"),
2286
 
                    'view_mode': 'form,tree',
2287
 
                    'view_id': [view_id],
2288
 
                    'view_type': 'form',
2289
 
                    'res_model': 'stock.picking',
2290
 
                    'res_id': obj.id,
2291
 
                    'type': 'ir.actions.act_window',
2292
 
                    'target': 'crush',
2293
 
                    'context': context,
2294
 
                    }
2295
 
    
2296
 
    def create_picking(self, cr, uid, ids, context=None):
2297
 
        '''
2298
 
        open the wizard to create (partial) picking tickets
2299
 
        '''
2300
 
        # we need the context for the wizard switch
2301
 
        if context is None:
2302
 
            context = {}
2303
 
        
2304
 
        # data
2305
 
        name = _("Create Picking Ticket")
2306
 
        model = 'create.picking'
2307
 
        step = 'create'
2308
 
        wiz_obj = self.pool.get('wizard')
2309
 
        # open the selected wizard
2310
 
        return wiz_obj.open_wizard(cr, uid, ids, name=name, model=model, step=step, context=context)
2311
 
 
2312
 
    def do_create_picking_first_hook(self, cr, uid, ids, context, *args, **kwargs):
2313
 
        '''
2314
 
        hook to update new_move data. Originally: to complete msf_cross_docking module
2315
 
        '''
2316
 
        values = kwargs.get('values')
2317
 
        assert values is not None, 'missing defaults'
2318
 
        
2319
 
        return values
2320
 
 
2321
 
    def do_create_picking(self, cr, uid, ids, context=None):
2322
 
        '''
2323
 
        create the picking ticket from selected stock moves
2324
 
        '''
2325
 
        if not context:
2326
 
            context = {}
2327
 
        assert context, 'context is not defined'
2328
 
        assert 'partial_datas' in context, 'partial datas not present in context'
2329
 
        partial_datas = context['partial_datas']
2330
 
        
2331
 
        # stock move object
2332
 
        move_obj = self.pool.get('stock.move')
2333
 
        
2334
 
        for pick in self.browse(cr, uid, ids, context=context):
2335
 
            # create the new picking object
2336
 
            # a sequence for each draft picking ticket is used for the picking ticket
2337
 
            sequence = pick.sequence_id
2338
 
            ticket_number = sequence.get_id(test='id', context=context)
2339
 
            new_pick_id = self.copy(cr, uid, pick.id, {'name': (pick.name or 'NoName/000') + '-' + ticket_number,
2340
 
                                                       'backorder_id': pick.id,
2341
 
                                                       'move_lines': []}, context=dict(context, allow_copy=True,))
2342
 
            # create stock moves corresponding to partial datas
2343
 
            # for now, each new line from the wizard corresponds to a new stock.move
2344
 
            # it could be interesting to regroup according to production lot/asset id
2345
 
            move_ids = partial_datas[pick.id].keys()
2346
 
            for move in move_obj.browse(cr, uid, move_ids, context=context):
2347
 
                # qty selected
2348
 
                count = 0
2349
 
                # initial qty
2350
 
                initial_qty = move.product_qty
2351
 
                for partial in partial_datas[pick.id][move.id]:
2352
 
                    # integrity check
2353
 
                    assert partial['product_id'] == move.product_id.id, 'product id is wrong, %s - %s'%(partial['product_id'], move.product_id.id)
2354
 
                    assert partial['product_uom'] == move.product_uom.id, 'product uom is wrong, %s - %s'%(partial['product_uom'], move.product_uom.id)
2355
 
                    # the quantity
2356
 
                    count = count + partial['product_qty']
2357
 
                    # copy the stock move and set the quantity
2358
 
                    values = {'picking_id': new_pick_id,
2359
 
                              'product_qty': partial['product_qty'],
2360
 
                              'product_uos_qty': partial['product_qty'],
2361
 
                              'prodlot_id': partial['prodlot_id'],
2362
 
                              'asset_id': partial['asset_id'],
2363
 
                              'composition_list_id': partial['composition_list_id'],
2364
 
                              'backmove_id': move.id}
2365
 
                    #add hook
2366
 
                    values = self.do_create_picking_first_hook(cr, uid, ids, context=context, partial_datas=partial_datas, values=values, move=move)
2367
 
                    new_move = move_obj.copy(cr, uid, move.id, values, context=dict(context, keepLineNumber=True))
2368
 
                    
2369
 
                # decrement the initial move, cannot be less than zero and mark the stock move as processed - will not be updated by delivery_mech anymore
2370
 
                initial_qty = max(initial_qty - count, 0)
2371
 
                move_obj.write(cr, uid, [move.id], {'product_qty': initial_qty, 'product_uos_qty': initial_qty, 'processed_stock_move': True}, context=context)
2372
 
                
2373
 
            # confirm the new picking ticket
2374
 
            wf_service = netsvc.LocalService("workflow")
2375
 
            wf_service.trg_validate(uid, 'stock.picking', new_pick_id, 'button_confirm', cr)
2376
 
            # we force availability
2377
 
            self.force_assign(cr, uid, [new_pick_id])
2378
 
        
2379
 
        # TODO which behavior
2380
 
        #return {'type': 'ir.actions.act_window_close'}
2381
 
        data_obj = self.pool.get('ir.model.data')
2382
 
        view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_form')
2383
 
        view_id = view_id and view_id[1] or False
2384
 
        context.update({'picking_type': 'picking_ticket', 'picking_screen': True})
2385
 
        return {'name':_("Picking Ticket"),
2386
 
                'view_mode': 'form,tree',
2387
 
                'view_id': [view_id],
2388
 
                'view_type': 'form',
2389
 
                'res_model': 'stock.picking',
2390
 
                'res_id': new_pick_id,
2391
 
                'type': 'ir.actions.act_window',
2392
 
                'target': 'crush',
2393
 
                'context': context,
2394
 
                }
2395
 
        
2396
 
    def validate_picking(self, cr, uid, ids, context=None):
2397
 
        '''
2398
 
        validate the picking ticket
2399
 
        '''
2400
 
        # we need the context for the wizard switch
2401
 
        if context is None:
2402
 
            context = {}
2403
 
            
2404
 
        # data
2405
 
        name = _("Validate Picking Ticket")
2406
 
        model = 'create.picking'
2407
 
        step = 'validate'
2408
 
        wiz_obj = self.pool.get('wizard')
2409
 
        # open the selected wizard
2410
 
        return wiz_obj.open_wizard(cr, uid, ids, name=name, model=model, step=step, context=context)
2411
 
 
2412
 
    def do_validate_picking_first_hook(self, cr, uid, ids, context, *args, **kwargs):
2413
 
        '''
2414
 
        hook to update new_move data. Originally: to complete msf_cross_docking module
2415
 
        '''
2416
 
        values = kwargs.get('values')
2417
 
        assert values is not None, 'missing defaults'
2418
 
        
2419
 
        return values
2420
 
 
2421
 
    def do_validate_picking(self, cr, uid, ids, context=None):
2422
 
        '''
2423
 
        validate the picking ticket from selected stock moves
2424
 
        
2425
 
        move here the logic of validate picking
2426
 
        available for picking loop
2427
 
        '''
2428
 
        if not context:
2429
 
            context = {}
2430
 
        assert context, 'context is not defined'
2431
 
        assert 'partial_datas' in context, 'partial datas not present in context'
2432
 
        partial_datas = context['partial_datas']
2433
 
        
2434
 
        # objects
2435
 
        date_tools = self.pool.get('date.tools')
2436
 
        db_date_format = date_tools.get_db_date_format(cr, uid, context=context)
2437
 
        today = time.strftime(db_date_format)
2438
 
        
2439
 
        # stock move object
2440
 
        move_obj = self.pool.get('stock.move')
2441
 
        # create picking object
2442
 
        create_picking_obj = self.pool.get('create.picking')
2443
 
        
2444
 
        new_ppl = False
2445
 
        for pick in self.browse(cr, uid, ids, context=context):
2446
 
            # create stock moves corresponding to partial datas
2447
 
            move_ids = partial_datas[pick.id].keys()
2448
 
            for move in move_obj.browse(cr, uid, move_ids, context=context):
2449
 
                # qty selected
2450
 
                count = 0
2451
 
                # flag to update the first move - if split was performed during the validation, new stock moves are created
2452
 
                first = True
2453
 
                # initial qty
2454
 
                initial_qty = move.product_qty
2455
 
                for partial in partial_datas[pick.id][move.id]:
2456
 
                    # integrity check
2457
 
                    assert partial['product_id'] == move.product_id.id, 'product id is wrong, %s - %s'%(partial['product_id'], move.product_id.id)
2458
 
                    assert partial['product_uom'] == move.product_uom.id, 'product uom is wrong, %s - %s'%(partial['product_uom'], move.product_uom.id)
2459
 
                    # the quantity
2460
 
                    count = count + partial['product_qty']
2461
 
                    if first:
2462
 
                        first = False
2463
 
                        # update existing move
2464
 
                        values = {'product_qty': partial['product_qty'],
2465
 
                                  'product_uos_qty': partial['product_qty'],
2466
 
                                  'prodlot_id': partial['prodlot_id'],
2467
 
                                  'composition_list_id': partial['composition_list_id'],
2468
 
                                  'asset_id': partial['asset_id']}
2469
 
                        values = self.do_validate_picking_first_hook(cr, uid, ids, context=context, partial_datas=partial_datas, values=values, move=move)
2470
 
                        move_obj.write(cr, uid, [move.id], values, context=context)
2471
 
                    else:
2472
 
                        # split happend during the validation
2473
 
                        # copy the stock move and set the quantity
2474
 
                        values = {'state': 'assigned',
2475
 
                                  'product_qty': partial['product_qty'],
2476
 
                                  'product_uos_qty': partial['product_qty'],
2477
 
                                  'prodlot_id': partial['prodlot_id'],
2478
 
                                  'composition_list_id': partial['composition_list_id'],
2479
 
                                  'asset_id': partial['asset_id']}
2480
 
                        values = self.do_validate_picking_first_hook(cr, uid, ids, context=context, partial_datas=partial_datas, values=values, move=move)
2481
 
                        new_move = move_obj.copy(cr, uid, move.id, values, context=dict(context, keepLineNumber=True))
2482
 
                # decrement the initial move, cannot be less than zero
2483
 
                diff_qty = initial_qty - count
2484
 
                # the quantity after the validation does not correspond to the picking ticket quantity
2485
 
                # the difference is written back to draft picking ticket
2486
 
                # is positive if some qty was removed during the validation -> draft qty is increased
2487
 
                # is negative if some qty was added during the validation -> draft qty is decreased
2488
 
                if diff_qty != 0:
2489
 
                    # original move from the draft picking ticket which will be updated
2490
 
                    original_move = move.backmove_id
2491
 
                    backorder_qty = move_obj.read(cr, uid, [original_move.id], ['product_qty'], context=context)[0]['product_qty']
2492
 
                    backorder_qty = max(backorder_qty + diff_qty, 0)
2493
 
                    move_obj.write(cr, uid, [original_move.id], {'product_qty': backorder_qty}, context=context)
2494
 
 
2495
 
            # create the new ppl object
2496
 
            ppl_number = pick.name.split("/")[1]
2497
 
            # we want the copy to keep the production lot reference from picking ticket to pre-packing list
2498
 
            new_ppl_id = self.copy(cr, uid, pick.id, {'name': 'PPL/' + ppl_number,
2499
 
                                                      'subtype': 'ppl',
2500
 
                                                      'previous_step_id': pick.id,
2501
 
                                                      'backorder_id': False}, context=dict(context, keep_prodlot=True, allow_copy=True, keepLineNumber=True))
2502
 
            new_ppl = self.browse(cr, uid, new_ppl_id, context=context)
2503
 
            # update locations of stock moves - if the move quantity is equal to zero, the stock move is removed
2504
 
            for move in new_ppl.move_lines:
2505
 
                if move.product_qty:
2506
 
                    move_obj.write(cr, uid, [move.id], {'initial_location': move.location_id.id,
2507
 
                                                        'location_id': move.location_dest_id.id,
2508
 
                                                        'location_dest_id': new_ppl.warehouse_id.lot_dispatch_id.id,
2509
 
                                                        'date': today,
2510
 
                                                        'date_expected': today,}, context=context)
2511
 
                else:
2512
 
                    move_obj.unlink(cr, uid, [move.id], context=dict(context, skipResequencing=True))
2513
 
            
2514
 
            wf_service = netsvc.LocalService("workflow")
2515
 
            wf_service.trg_validate(uid, 'stock.picking', new_ppl_id, 'button_confirm', cr)
2516
 
            # simulate check assign button, as stock move must be available
2517
 
            self.force_assign(cr, uid, [new_ppl_id])
2518
 
            # trigger standard workflow for validated picking ticket
2519
 
            self.action_move(cr, uid, [pick.id])
2520
 
            wf_service.trg_validate(uid, 'stock.picking', pick.id, 'button_done', cr)
2521
 
            
2522
 
            # if the flow type is in quick mode, we perform the ppl steps automatically
2523
 
            if pick.flow_type == 'quick':
2524
 
                create_picking_obj.quick_mode(cr, uid, new_ppl, context=context)
2525
 
 
2526
 
        # TODO which behavior
2527
 
        #return {'type': 'ir.actions.act_window_close'}
2528
 
        data_obj = self.pool.get('ir.model.data')
2529
 
        view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_ppl_form')
2530
 
        view_id = view_id and view_id[1] or False
2531
 
        context.update({'picking_type': 'picking_ticket', 'ppl_screen': True})
2532
 
        return {'name':_("Pre-Packing List"),
2533
 
                'view_mode': 'form,tree',
2534
 
                'view_id': [view_id],
2535
 
                'view_type': 'form',
2536
 
                'res_model': 'stock.picking',
2537
 
                'res_id': new_ppl and new_ppl.id or False,
2538
 
                'type': 'ir.actions.act_window',
2539
 
                'target': 'crush',
2540
 
                'context': context,
2541
 
                }
2542
 
 
2543
 
    def ppl(self, cr, uid, ids, context=None):
2544
 
        '''
2545
 
        pack the ppl - open the ppl step1 wizard
2546
 
        '''
2547
 
        # we need the context for the wizard switch
2548
 
        if context is None:
2549
 
            context = {}
2550
 
            
2551
 
        # data
2552
 
        name = _("PPL Information - step1")
2553
 
        model = 'create.picking'
2554
 
        step = 'ppl1'
2555
 
        wiz_obj = self.pool.get('wizard')
2556
 
        # open the selected wizard
2557
 
        return wiz_obj.open_wizard(cr, uid, ids, name=name, model=model, step=step, context=context)
2558
 
        
2559
 
    def do_ppl1(self, cr, uid, ids, context=None):
2560
 
        '''
2561
 
        - receives generated data from ppl in context
2562
 
        - call action to ppl2 step with partial_datas_ppl1 in context
2563
 
        - ids are the picking ids
2564
 
        '''
2565
 
        # we need the context for the wizard switch
2566
 
        assert context, 'No context defined'
2567
 
            
2568
 
        # data
2569
 
        name = _("PPL Information - step2")
2570
 
        model = 'create.picking'
2571
 
        step = 'ppl2'
2572
 
        wiz_obj = self.pool.get('wizard')
2573
 
        # open the selected wizard
2574
 
        return wiz_obj.open_wizard(cr, uid, ids, name=name, model=model, step=step, context=context)
2575
 
        
2576
 
    def do_ppl2(self, cr, uid, ids, context=None):
2577
 
        '''
2578
 
        finalize the ppl logic
2579
 
        '''
2580
 
        # integrity check
2581
 
        assert context, 'context not defined'
2582
 
        assert 'partial_datas_ppl1' in context, 'partial_datas_ppl1 no defined in context'
2583
 
        
2584
 
        move_obj = self.pool.get('stock.move')
2585
 
        wf_service = netsvc.LocalService("workflow")
2586
 
        # data from wizard
2587
 
        partial_datas_ppl = context['partial_datas_ppl1']
2588
 
        # picking ids from ids must be equal to picking ids from partial datas
2589
 
        assert set(ids) == set(partial_datas_ppl.keys()), 'picking ids from ids and partial do not match'
2590
 
        
2591
 
        # update existing stock moves - create new one if split occurred
2592
 
        # for each pick
2593
 
        for pick in self.browse(cr, uid, ids, context=context):
2594
 
            # integrity check on move_ids - moves ids from picking and partial must be the same
2595
 
            # dont take into account done moves, which represents returned products
2596
 
            from_pick = [move.id for move in pick.move_lines if move.state in ('confirmed', 'assigned')]
2597
 
            from_partial = []
2598
 
            # the list of updated stock.moves
2599
 
            # if a stock.move is updated, the next time a new move is created
2600
 
            updated = {}
2601
 
            # load moves data
2602
 
            # browse returns a list of browse object in the same order as from_partial
2603
 
            browse_moves = move_obj.browse(cr, uid, from_pick, context=context)
2604
 
            moves = dict(zip(from_pick, browse_moves))
2605
 
            # loop through data
2606
 
            for from_pack in partial_datas_ppl[pick.id]:
2607
 
                for to_pack in partial_datas_ppl[pick.id][from_pack]:
2608
 
                    for move in partial_datas_ppl[pick.id][from_pack][to_pack]:
2609
 
                        # integrity check
2610
 
                        from_partial.append(move)
2611
 
                        for partial in partial_datas_ppl[pick.id][from_pack][to_pack][move]:
2612
 
                            # {'asset_id': False, 'weight': False, 'product_id': 77, 'product_uom': 1, 'pack_type': False, 'length': False, 'to_pack': 1, 'height': False, 'from_pack': 1, 'prodlot_id': False, 'qty_per_pack': 18.0, 'product_qty': 18.0, 'width': False, 'move_id': 179}
2613
 
                            # integrity check
2614
 
                            assert partial['product_id'] == moves[move].product_id.id
2615
 
                            assert partial['asset_id'] == moves[move].asset_id.id
2616
 
                            assert partial['composition_list_id'] == moves[move].composition_list_id.id
2617
 
                            assert partial['product_uom'] == moves[move].product_uom.id
2618
 
                            assert partial['prodlot_id'] == moves[move].prodlot_id.id
2619
 
                            # dictionary of new values, used for creation or update
2620
 
                            # - qty_per_pack is a function at stock move level
2621
 
                            fields = ['product_qty', 'from_pack', 'to_pack', 'pack_type', 'length', 'width', 'height', 'weight']
2622
 
                            values = dict(zip(fields, [partial["%s"%x] for x in fields]))
2623
 
                            
2624
 
                            if move in updated:
2625
 
                                # if already updated, we create a new stock.move
2626
 
                                updated[move]['partial_qty'] += partial['product_qty']
2627
 
                                # force state to 'assigned'
2628
 
                                values.update(state='assigned')
2629
 
                                # copy stock.move with new product_qty, qty_per_pack. from_pack, to_pack, pack_type, length, width, height, weight
2630
 
                                move_obj.copy(cr, uid, move, values, context=context)
2631
 
                            else:
2632
 
                                # update the existing stock move
2633
 
                                updated[move] = {'initial': moves[move].product_qty, 'partial_qty': partial['product_qty']}
2634
 
                                move_obj.write(cr, uid, [move], values, context=context)
2635
 
        
2636
 
            # integrity check - all moves are treated and no more
2637
 
            assert set(from_pick) == set(from_partial), 'move_ids are not equal pick:%s - partial:%s'%(set(from_pick), set(from_partial))
2638
 
            # quantities are right
2639
 
            assert all([updated[m]['initial'] == updated[m]['partial_qty'] for m in updated.keys()]), 'initial quantity is not equal to the sum of partial quantities (%s).'%(updated)
2640
 
            # copy to 'packing' stock.picking
2641
 
            # draft shipment is automatically created or updated if a shipment already
2642
 
            pack_number = pick.name.split("/")[1]
2643
 
            new_packing_id = self.copy(cr, uid, pick.id, {'name': 'PACK/' + pack_number,
2644
 
                                                          'subtype': 'packing',
2645
 
                                                          'previous_step_id': pick.id,
2646
 
                                                          'backorder_id': False,
2647
 
                                                          'shipment_id': False}, context=dict(context, keep_prodlot=True, allow_copy=True,))
2648
 
 
2649
 
            self.write(cr, uid, [new_packing_id], {'origin': pick.origin}, context=context)
2650
 
            # update locations of stock moves and state as the picking stay at 'draft' state.
2651
 
            # if return move have been done in previous ppl step, we remove the corresponding copied move (criteria: qty_per_pack == 0)
2652
 
            new_packing = self.browse(cr, uid, new_packing_id, context=context)
2653
 
            for move in new_packing.move_lines:
2654
 
                if move.qty_per_pack == 0:
2655
 
                    move_obj.unlink(cr, uid, [move.id], context=context)
2656
 
                else:
2657
 
                    move.write({'state': 'assigned',
2658
 
                                'location_id': new_packing.warehouse_id.lot_dispatch_id.id,
2659
 
                                'location_dest_id': new_packing.warehouse_id.lot_distribution_id.id}, context=context)
2660
 
            
2661
 
            # trigger standard workflow
2662
 
            self.action_move(cr, uid, [pick.id])
2663
 
            wf_service.trg_validate(uid, 'stock.picking', pick.id, 'button_done', cr)
2664
 
        
2665
 
        # TODO which behavior
2666
 
        #return {'type': 'ir.actions.act_window_close'}
2667
 
        data_obj = self.pool.get('ir.model.data')
2668
 
        view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_shipment_form')
2669
 
        view_id = view_id and view_id[1] or False
2670
 
        return {'name':_("Shipment"),
2671
 
                'view_mode': 'form,tree',
2672
 
                'view_id': [view_id],
2673
 
                'view_type': 'form',
2674
 
                'res_model': 'shipment',
2675
 
                'res_id': new_packing.shipment_id.id,
2676
 
                'type': 'ir.actions.act_window',
2677
 
                'target': 'crush',
2678
 
                'context': context,
2679
 
                }
2680
 
    
2681
 
    def return_products(self, cr, uid, ids, context=None):
2682
 
        '''
2683
 
        open the return products wizard
2684
 
        '''
2685
 
        # we need the context
2686
 
        if context is None:
2687
 
            context = {}
2688
 
            
2689
 
        # data
2690
 
        name = _("Return Products")
2691
 
        model = 'create.picking'
2692
 
        step = 'returnproducts'
2693
 
        wiz_obj = self.pool.get('wizard')
2694
 
        # open the selected wizard
2695
 
        return wiz_obj.open_wizard(cr, uid, ids, name=name, model=model, step=step, context=context)
2696
 
    
2697
 
    def do_return_products(self, cr, uid, ids, context=None):
2698
 
        '''
2699
 
        - update the ppl
2700
 
        - update the draft picking ticket
2701
 
        - create the back move
2702
 
        '''
2703
 
        if not context:
2704
 
            context = {}
2705
 
        # integrity check
2706
 
        assert context, 'context not defined'
2707
 
        assert 'partial_datas' in context, 'partial_datas no defined in context'
2708
 
        partial_datas = context['partial_datas']
2709
 
        
2710
 
        move_obj = self.pool.get('stock.move')
2711
 
        wf_service = netsvc.LocalService("workflow")
2712
 
       
2713
 
        draft_picking_id = False
2714
 
        for picking in self.browse(cr, uid, ids, context=context):
2715
 
            # for each picking
2716
 
            # corresponding draft picking ticket
2717
 
            draft_picking_id = picking.previous_step_id.backorder_id.id
2718
 
            
2719
 
            for move in move_obj.browse(cr, uid, partial_datas[picking.id].keys(), context=context):
2720
 
                # we browse the updated moves (return qty > 0 is checked during data generation)
2721
 
                # data from wizard
2722
 
                data = partial_datas[picking.id][move.id]
2723
 
 
2724
 
                # qty to return
2725
 
                return_qty = data['qty_to_return']
2726
 
                # initial qty is decremented
2727
 
                initial_qty = move.product_qty
2728
 
                initial_qty = max(initial_qty - return_qty, 0)
2729
 
                values = {'product_qty': initial_qty}
2730
 
                
2731
 
                if not initial_qty:
2732
 
                    # if all products are sent back to stock, the move state is cancel - done for now, ideologic question, wahouuu!
2733
 
                    #values.update({'state': 'cancel'})
2734
 
                    values.update({'state': 'done'})
2735
 
                move_obj.write(cr, uid, [move.id], values, context=context)
2736
 
                
2737
 
                # create a back move with the quantity to return to the good location
2738
 
                # the good location is stored in the 'initial_location' field
2739
 
                move_obj.copy(cr, uid, move.id, {'product_qty': return_qty,
2740
 
                                                 'location_dest_id': move.initial_location.id,
2741
 
                                                 'state': 'done'}, context=dict(context, keepLineNumber=True))
2742
 
                
2743
 
                # increase the draft move with the move quantity
2744
 
                draft_move_id = move.backmove_id.id
2745
 
                draft_initial_qty = move_obj.read(cr, uid, [draft_move_id], ['product_qty'], context=context)[0]['product_qty']
2746
 
                draft_initial_qty += return_qty
2747
 
                move_obj.write(cr, uid, [draft_move_id], {'product_qty': draft_initial_qty}, context=context)
2748
 
                
2749
 
            # log the increase action - display the picking ticket view form
2750
 
            # TODO refactoring needed
2751
 
            obj_data = self.pool.get('ir.model.data')
2752
 
            res = obj_data.get_object_reference(cr, uid, 'msf_outgoing', 'view_ppl_form')[1]
2753
 
            self.log(cr, uid, picking.id, _("Products from Pre-Packing List (%s) have been returned to stock.")%(picking.name,), context={'view_id': res,})
2754
 
            res = obj_data.get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_form')[1]
2755
 
            self.log(cr, uid, draft_picking_id, _("The corresponding Draft Picking Ticket (%s) has been updated.")%(picking.previous_step_id.backorder_id.name,), context={'view_id': res,})
2756
 
            # if all moves are done or canceled, the ppl is canceled
2757
 
            cancel_ppl = True
2758
 
            for move in picking.move_lines:
2759
 
                if move.state in ('assigned'):
2760
 
                    cancel_ppl = False
2761
 
            
2762
 
            if cancel_ppl:
2763
 
                # we dont want the back move (done) to be canceled - so we dont use the original cancel workflow state because
2764
 
                # action_cancel() from stock_picking would be called, this would cancel the done stock_moves
2765
 
                # instead we move to the new return_cancel workflow state which simply set the stock_picking state to 'cancel'
2766
 
                # TODO THIS DOESNT WORK - still done state - replace with trigger for now
2767
 
                #wf_service.trg_validate(uid, 'stock.picking', picking.id, 'return_cancel', cr)
2768
 
                wf_service.trg_write(uid, 'stock.picking', picking.id, cr)
2769
 
 
2770
 
        # TODO which behavior
2771
 
        #return {'type': 'ir.actions.act_window_close'}
2772
 
        data_obj = self.pool.get('ir.model.data')
2773
 
        view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_form')
2774
 
        view_id = view_id and view_id[1] or False
2775
 
        context.update({'picking_type': 'picking_ticket'})
2776
 
        return {
2777
 
            'name':_("Picking Ticket"),
2778
 
            'view_mode': 'form,tree',
2779
 
            'view_id': [view_id],
2780
 
            'view_type': 'form',
2781
 
            'res_model': 'stock.picking',
2782
 
            'res_id': draft_picking_id ,
2783
 
            'type': 'ir.actions.act_window',
2784
 
            'target': 'crush',
2785
 
            'context': context,
2786
 
        }
2787
 
    
2788
 
    def action_cancel(self, cr, uid, ids, context=None):
2789
 
        '''
2790
 
        override cancel state action from the workflow
2791
 
        
2792
 
        - depending on the subtype and state of the stock.picking object
2793
 
          the behavior will be different
2794
 
        
2795
 
        Cancel button is active for the picking object:
2796
 
        - subtype: 'picking'
2797
 
        Cancel button is active for the shipment object:
2798
 
        - subtype: 'packing'
2799
 
        
2800
 
        state is not taken into account as picking is canceled before
2801
 
        '''
2802
 
        if context is None:
2803
 
            context = {}
2804
 
        move_obj = self.pool.get('stock.move')
2805
 
        obj_data = self.pool.get('ir.model.data')
2806
 
        
2807
 
        # check the state of the picking
2808
 
        for picking in self.browse(cr, uid, ids, context=context):
2809
 
            # if draft and shipment is in progress, we cannot cancel
2810
 
            if picking.subtype == 'picking' and picking.state in ('draft',):
2811
 
                if self.has_picking_ticket_in_progress(cr, uid, [picking.id], context=context)[picking.id]:
2812
 
                    raise osv.except_osv(_('Warning !'), _('Some Picking Tickets are in progress. Return products to stock from ppl and shipment and try to cancel again.'))
2813
 
                return super(stock_picking, self).action_cancel(cr, uid, ids, context=context)
2814
 
            # if not draft or qty does not match, the shipping is already in progress
2815
 
            if picking.subtype == 'picking' and picking.state in ('done',):
2816
 
                raise osv.except_osv(_('Warning !'), _('The shipment process is completed and cannot be canceled!'))
2817
 
        
2818
 
        # first call to super method, so if some checks fail won't perform other actions anyway
2819
 
        # call super - picking is canceled
2820
 
        super(stock_picking, self).action_cancel(cr, uid, ids, context=context)
2821
 
        
2822
 
        for picking in self.browse(cr, uid, ids, context=context):
2823
 
                
2824
 
            if picking.subtype == 'picking':
2825
 
                # for each picking
2826
 
                # get the draft picking
2827
 
                draft_picking_id = picking.backorder_id.id
2828
 
                
2829
 
                # for each move from picking ticket - could be split moves
2830
 
                for move in picking.move_lines:
2831
 
                    # find the corresponding move in draft
2832
 
                    draft_move = move.backmove_id
2833
 
                    # increase the draft move with the move quantity
2834
 
                    initial_qty = move_obj.read(cr, uid, [draft_move.id], ['product_qty'], context=context)[0]['product_qty']
2835
 
                    initial_qty += move.product_qty
2836
 
                    move_obj.write(cr, uid, [draft_move.id], {'product_qty': initial_qty}, context=context)
2837
 
                    # log the increase action
2838
 
                    # TODO refactoring needed
2839
 
                    obj_data = self.pool.get('ir.model.data')
2840
 
                    res = obj_data.get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_form')[1]
2841
 
                    self.log(cr, uid, draft_picking_id, _("The corresponding Draft Picking Ticket (%s) has been updated.")%(picking.backorder_id.name,), context={'view_id': res,})
2842
 
                    
2843
 
            if picking.subtype == 'packing':
2844
 
                # for each packing we get the draft packing
2845
 
                draft_packing_id = picking.backorder_id.id
2846
 
                
2847
 
                # for each move from the packing
2848
 
                for move in picking.move_lines:
2849
 
                    # corresponding draft move from draft **packing** object
2850
 
                    draft_move_id = move.backmove_packing_id.id
2851
 
                    # check the to_pack of draft move
2852
 
                    # if equal to draft to_pack = move from_pack - 1 (as we always take the pack with the highest number available)
2853
 
                    # we can increase the qty and update draft to_pack
2854
 
                    # otherwise we copy the draft packing move with updated quantity and from/to
2855
 
                    # we always create a new move
2856
 
                    draft_read = move_obj.read(cr, uid, [draft_move_id], ['product_qty', 'to_pack'], context=context)[0]
2857
 
                    draft_to_pack = draft_read['to_pack']
2858
 
                    if draft_to_pack + 1 == move.from_pack and False: # DEACTIVATED
2859
 
                        # updated quantity
2860
 
                        draft_qty = draft_read['product_qty'] + move.product_qty
2861
 
                        # update the draft move
2862
 
                        move_obj.write(cr, uid, [draft_move_id], {'product_qty': draft_qty, 'to_pack': move.to_pack}, context=context)
2863
 
                    else:
2864
 
                        # copy draft move (to be sure not to miss original info) with move qty and from/to
2865
 
                        move_obj.copy(cr, uid, draft_move_id, {'product_qty': move.product_qty,
2866
 
                                                               'from_pack': move.from_pack,
2867
 
                                                               'to_pack': move.to_pack,
2868
 
                                                               'state': 'assigned'}, context=context)
2869
 
        
2870
 
        return True
2871
 
            
2872
 
stock_picking()
2873
 
 
2874
 
 
2875
 
class wizard(osv.osv):
2876
 
    '''
2877
 
    class offering open_wizard method for wizard control
2878
 
    '''
2879
 
    _name = 'wizard'
2880
 
    
2881
 
    def open_wizard(self, cr, uid, ids, name=False, model=False, step='default', type='create', context=None):
2882
 
        '''
2883
 
        WARNING : IDS CORRESPOND TO ***MAIN OBJECT IDS*** (picking for example) take care when calling the method from wizards
2884
 
        return the newly created wizard's id
2885
 
        name, model, step are mandatory only for type 'create'
2886
 
        '''
2887
 
        if context is None:
2888
 
            context = {}
2889
 
        
2890
 
        if type == 'create':
2891
 
            assert name, 'type "create" and no name defined'
2892
 
            assert model, 'type "create" and no model defined'
2893
 
            assert step, 'type "create" and no step defined'
2894
 
            # create the memory object - passing the picking id to it through context
2895
 
            wizard_id = self.pool.get(model).create(
2896
 
                cr, uid, {}, context=dict(context,
2897
 
                                          active_ids=ids,
2898
 
                                          model=model,
2899
 
                                          step=step,
2900
 
                                          back_model=context.get('model', False),
2901
 
                                          back_wizard_ids=context.get('wizard_ids', False),
2902
 
                                          back_wizard_name=context.get('wizard_name', False),
2903
 
                                          back_step=context.get('step', False),
2904
 
                                          wizard_name=name))
2905
 
        
2906
 
        elif type == 'back':
2907
 
            # open the previous wizard
2908
 
            assert context['back_wizard_ids'], 'no back_wizard_ids defined'
2909
 
            wizard_id = context['back_wizard_ids'][0]
2910
 
            assert context['back_wizard_name'], 'no back_wizard_name defined'
2911
 
            name = context['back_wizard_name']
2912
 
            assert context['back_model'], 'no back_model defined'
2913
 
            model = context['back_model']
2914
 
            assert context['back_step'], 'no back_step defined'
2915
 
            step = context['back_step']
2916
 
            
2917
 
        elif type == 'update':
2918
 
            # refresh the same wizard
2919
 
            assert context['wizard_ids'], 'no wizard_ids defined'
2920
 
            wizard_id = context['wizard_ids'][0]
2921
 
            assert context['wizard_name'], 'no wizard_name defined'
2922
 
            name = context['wizard_name']
2923
 
            assert context['model'], 'no model defined'
2924
 
            model = context['model']
2925
 
            assert context['step'], 'no step defined'
2926
 
            step = context['step']
2927
 
            
2928
 
        # call action to wizard view
2929
 
        return {
2930
 
            'name': name,
2931
 
            'view_mode': 'form',
2932
 
            'view_id': False,
2933
 
            'view_type': 'form',
2934
 
            'res_model': model,
2935
 
            'res_id': wizard_id,
2936
 
            'type': 'ir.actions.act_window',
2937
 
            'nodestroy': True,
2938
 
            'target': 'new',
2939
 
            'domain': '[]',
2940
 
            'context': dict(context,
2941
 
                            active_ids=ids,
2942
 
                            wizard_ids=[wizard_id],
2943
 
                            model=model,
2944
 
                            step=step,
2945
 
                            back_model=context.get('model', False),
2946
 
                            back_wizard_ids=context.get('wizard_ids', False),
2947
 
                            back_wizard_name=context.get('wizard_name', False),
2948
 
                            back_step=context.get('step', False),
2949
 
                            wizard_name=name)
2950
 
        }
2951
 
    
2952
 
wizard()
2953
 
 
2954
 
 
2955
 
class product_product(osv.osv):
2956
 
    '''
2957
 
    add a getter for keep cool notion
2958
 
    '''
2959
 
    _inherit = 'product.product'
2960
 
    
2961
 
    def _vals_get(self, cr, uid, ids, fields, arg, context=None):
2962
 
        '''
2963
 
        get functional values
2964
 
        '''
2965
 
        result = {}
2966
 
        for product in self.browse(cr, uid, ids, context=context):
2967
 
            values = {'is_keep_cool': False,
2968
 
                      }
2969
 
            result[product.id] = values
2970
 
            # keep cool
2971
 
            is_keep_cool = bool(product.heat_sensitive_item)# in ('*', '**', '***',)
2972
 
            values['is_keep_cool'] = is_keep_cool
2973
 
                    
2974
 
        return result
2975
 
    
2976
 
    _columns = {'is_keep_cool': fields.function(_vals_get, method=True, type='boolean', string='Keep Cool', multi='get_vals',),
2977
 
                'prodlot_ids': fields.one2many('stock.production.lot', 'product_id', string='Batch Numbers',),
2978
 
                }
2979
 
    
2980
 
product_product()
2981
 
 
2982
 
 
2983
 
class stock_move(osv.osv):
2984
 
    '''
2985
 
    stock move
2986
 
    '''
2987
 
    _inherit = 'stock.move'
2988
 
    
2989
 
    def _product_available(self, cr, uid, ids, field_names=None, arg=False, context=None):
2990
 
        '''
2991
 
        facade for product_available function from product (stock)
2992
 
        '''
2993
 
        # get the corresponding product ids
2994
 
        result = {}
2995
 
        for d in self.read(cr, uid, ids, ['product_id'], context):
2996
 
            result[d['id']] = d['product_id'][0]
2997
 
        
2998
 
        # get the virtual stock identified by product ids
2999
 
        virtual = self.pool.get('product.product')._product_available(cr, uid, result.values(), field_names, arg, context)
3000
 
        
3001
 
        # replace product ids by corresponding stock move id
3002
 
        result = dict([id, virtual[result[id]]] for id in result.keys())
3003
 
        return result
3004
 
    
3005
 
    def _vals_get(self, cr, uid, ids, fields, arg, context=None):
3006
 
        '''
3007
 
        get functional values
3008
 
        '''
3009
 
        result = {}
3010
 
        for move in self.browse(cr, uid, ids, context=context):
3011
 
            values = {'qty_per_pack': 0.0,
3012
 
                      'total_amount': 0.0,
3013
 
                      'amount': 0.0,
3014
 
                      'currency_id': False,
3015
 
                      'num_of_packs': 0,
3016
 
                      'is_dangerous_good': False,
3017
 
                      'is_keep_cool': False,
3018
 
                      'is_narcotic': False,
3019
 
                      'sale_order_line_number': 0,
3020
 
                      }
3021
 
            result[move.id] = values
3022
 
            # number of packs with from/to values (integer)
3023
 
            if move.to_pack == 0:
3024
 
                num_of_packs = 0
3025
 
            else:
3026
 
                num_of_packs = move.to_pack - move.from_pack + 1
3027
 
            values['num_of_packs'] = num_of_packs
3028
 
            # quantity per pack
3029
 
            if num_of_packs:
3030
 
                values['qty_per_pack'] = move.product_qty / num_of_packs
3031
 
            else:
3032
 
                values['qty_per_pack'] = 0
3033
 
            # total amount (float)
3034
 
            total_amount = move.sale_line_id and move.sale_line_id.price_unit * move.product_qty or 0.0
3035
 
            values['total_amount'] = total_amount
3036
 
            # amount for one pack
3037
 
            if num_of_packs:
3038
 
                amount = total_amount / num_of_packs
3039
 
            else:
3040
 
                amount = 0
3041
 
            values['amount'] = amount
3042
 
            # currency
3043
 
            values['currency_id'] = move.sale_line_id and move.sale_line_id.currency_id and move.sale_line_id.currency_id.id or False
3044
 
            # dangerous good
3045
 
            values['is_dangerous_good'] = move.product_id and move.product_id.dangerous_goods or False
3046
 
            # keep cool - if heat_sensitive_item is True
3047
 
            values['is_keep_cool'] = bool(move.product_id and move.product_id.heat_sensitive_item or False)
3048
 
            # narcotic
3049
 
            values['is_narcotic'] = move.product_id and move.product_id.narcotic or False
3050
 
            # sale_order_line_number
3051
 
            values['sale_order_line_number'] = move.sale_line_id and move.sale_line_id.line_number or 0
3052
 
                    
3053
 
        return result
3054
 
    
3055
 
    def default_get(self, cr, uid, fields, context=None):
3056
 
        '''
3057
 
        Set default values according to type and subtype
3058
 
        '''
3059
 
        if not context:
3060
 
            context = {}
3061
 
            
3062
 
        res = super(stock_move, self).default_get(cr, uid, fields, context=context)
3063
 
        
3064
 
        if 'warehouse_id' in context and context.get('warehouse_id'):
3065
 
            warehouse_id = context.get('warehouse_id')
3066
 
        else:
3067
 
            warehouse_id = self.pool.get('stock.warehouse').search(cr, uid, [], context=context)[0]
3068
 
        res.update({'location_output_id': self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id, context=context).lot_output_id.id})
3069
 
        
3070
 
        loc_virtual_ids = self.pool.get('stock.location').search(cr, uid, [('name', '=', 'Virtual Locations')])
3071
 
        loc_virtual_id = len(loc_virtual_ids) > 0 and loc_virtual_ids[0] or False
3072
 
        res.update({'location_virtual_id': loc_virtual_id})
3073
 
        
3074
 
        if 'type' in context and context.get('type', False) == 'out':
3075
 
            loc_stock_id = self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id, context=context).lot_stock_id.id
3076
 
            res.update({'location_id': loc_stock_id})
3077
 
        
3078
 
        if 'subtype' in context and context.get('subtype', False) == 'picking':
3079
 
            loc_packing_id = self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id, context=context).lot_packing_id.id
3080
 
            res.update({'location_dest_id': loc_packing_id})
3081
 
        elif 'subtype' in context and context.get('subtype', False) == 'standard':
3082
 
            loc_output_id = self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id, context=context).lot_output_id.id
3083
 
            res.update({'location_dest_id': loc_output_id})
3084
 
        
3085
 
        return res
3086
 
    
3087
 
    _columns = {'from_pack': fields.integer(string='From p.'),
3088
 
                'to_pack': fields.integer(string='To p.'),
3089
 
                'pack_type': fields.many2one('pack.type', string='Pack Type'),
3090
 
                'length' : fields.float(digits=(16,2), string='Length [cm]'),
3091
 
                'width' : fields.float(digits=(16,2), string='Width [cm]'),
3092
 
                'height' : fields.float(digits=(16,2), string='Height [cm]'),
3093
 
                'weight' : fields.float(digits=(16,2), string='Weight p.p [kg]'),
3094
 
                #'pack_family_id': fields.many2one('pack.family', string='Pack Family'),
3095
 
                'initial_location': fields.many2one('stock.location', string='Initial Picking Location'),
3096
 
                # relation to the corresponding move from draft **picking** ticket object
3097
 
                'backmove_id': fields.many2one('stock.move', string='Corresponding move of previous step'),
3098
 
                # relation to the corresponding move from draft **packing** ticket object
3099
 
                'backmove_packing_id': fields.many2one('stock.move', string='Corresponding move of previous step in draft packing'),
3100
 
                # functions
3101
 
                'virtual_available': fields.function(_product_available, method=True, type='float', string='Virtual Stock', help="Future stock for this product according to the selected locations or all internal if none have been selected. Computed as: Real Stock - Outgoing + Incoming.", multi='qty_available', digits_compute=dp.get_precision('Product UoM')),
3102
 
                'qty_per_pack': fields.function(_vals_get, method=True, type='float', string='Qty p.p', multi='get_vals',),
3103
 
                'total_amount': fields.function(_vals_get, method=True, type='float', string='Total Amount', digits_compute=dp.get_precision('Picking Price'), multi='get_vals',),
3104
 
                'amount': fields.function(_vals_get, method=True, type='float', string='Pack Amount', digits_compute=dp.get_precision('Picking Price'), multi='get_vals',),
3105
 
                'num_of_packs': fields.function(_vals_get, method=True, type='integer', string='#Packs', multi='get_vals_X',), # old_multi get_vals
3106
 
                'currency_id': fields.function(_vals_get, method=True, type='many2one', relation='res.currency', string='Currency', multi='get_vals',),
3107
 
                'is_dangerous_good': fields.function(_vals_get, method=True, type='boolean', string='Dangerous Good', multi='get_vals',),
3108
 
                'is_keep_cool': fields.function(_vals_get, method=True, type='boolean', string='Keep Cool', multi='get_vals',),
3109
 
                'is_narcotic': fields.function(_vals_get, method=True, type='boolean', string='Narcotic', multi='get_vals',),
3110
 
                'sale_order_line_number': fields.function(_vals_get, method=True, type='integer', string='Sale Order Line Number', multi='get_vals_X',), # old_multi get_vals
3111
 
                # Fields used for domain
3112
 
                'location_virtual_id': fields.many2one('stock.location', string='Virtual location'),
3113
 
                'location_output_id': fields.many2one('stock.location', string='Output location'),
3114
 
                'invoice_line_id': fields.many2one('account.invoice.line', string='Invoice line'),
3115
 
                }
3116
 
 
3117
 
    def action_cancel(self, cr, uid, ids, context=None):
3118
 
        '''
3119
 
            Confirm or check the procurement order associated to the stock move
3120
 
        '''
3121
 
        res = super(stock_move, self).action_cancel(cr, uid, ids, context=context)
3122
 
        
3123
 
        wf_service = netsvc.LocalService("workflow")
3124
 
 
3125
 
        proc_obj = self.pool.get('procurement.order')
3126
 
        proc_ids = proc_obj.search(cr, uid, [('move_id', 'in', ids)], context=context)
3127
 
        for proc in proc_obj.browse(cr, uid, proc_ids, context=context):
3128
 
            if proc.state == 'draft':
3129
 
                wf_service.trg_validate(uid, 'procurement.order', proc.id, 'button_confirm', cr)
3130
 
            else:
3131
 
                wf_service.trg_validate(uid, 'procurement.order', proc.id, 'button_check', cr)
3132
 
        
3133
 
        return res
3134
 
 
3135
 
stock_move()
3136
 
 
3137
 
 
3138
 
class sale_order(osv.osv):
3139
 
    '''
3140
 
    re-override to modify behavior for outgoing workflow
3141
 
    '''
3142
 
    _inherit = 'sale.order'
3143
 
    _name = 'sale.order'
3144
 
    
3145
 
    def _hook_ship_create_execute_specific_code_01(self, cr, uid, ids, context=None, *args, **kwargs):
3146
 
        '''
3147
 
        Please copy this to your module's method also.
3148
 
        This hook belongs to the action_ship_create method from sale>sale.py
3149
 
        
3150
 
        - allow to execute specific code at position 01
3151
 
        '''
3152
 
        super(sale_order, self)._hook_ship_create_execute_specific_code_01(cr, uid, ids, context=context, *args, **kwargs)
3153
 
        # objects
3154
 
        pick_obj = self.pool.get('stock.picking')
3155
 
        
3156
 
        wf_service = netsvc.LocalService("workflow")
3157
 
        proc_id = kwargs['proc_id']
3158
 
        order = kwargs['order']
3159
 
        if order.procurement_request :
3160
 
            proc = self.pool.get('procurement.order').browse(cr, uid, [proc_id], context=context)
3161
 
            pick_id = proc and proc[0] and proc[0].move_id and proc[0].move_id.picking_id and proc[0].move_id.picking_id.id or False
3162
 
            if pick_id:
3163
 
                wf_service.trg_validate(uid, 'stock.picking', [pick_id], 'button_confirm', cr)
3164
 
 
3165
 
                # We also do a first 'check availability': cancel then check
3166
 
                pick_obj.cancel_assign(cr, uid, [pick_id], context)
3167
 
                pick_obj.action_assign(cr, uid, [pick_id], context)
3168
 
        
3169
 
        return True
3170
 
    
3171
 
    def _hook_ship_create_execute_specific_code_02(self, cr, uid, ids, context=None, *args, **kwargs):
3172
 
        '''
3173
 
        Please copy this to your module's method also.
3174
 
        This hook belongs to the action_ship_create method from sale>sale.py
3175
 
        
3176
 
        - allow to execute specific code at position 02
3177
 
        '''
3178
 
        # Some verifications
3179
 
        if context is None:
3180
 
            context = {}
3181
 
        if isinstance(ids, (int, long)):
3182
 
            ids = [ids]
3183
 
        
3184
 
        # objects
3185
 
        pick_obj = self.pool.get('stock.picking')
3186
 
        move_obj = self.pool.get('stock.move')
3187
 
        order = kwargs['order']
3188
 
        move_id = kwargs['move_id']
3189
 
        pick_id = move_obj.browse(cr, uid, [move_id], context=context)[0].picking_id.id
3190
 
        
3191
 
        if order.procurement_request:
3192
 
            move_obj.action_confirm(cr, uid, [move_id], context=context)
3193
 
            # we Validate the picking "Confirms picking directly from draft state."
3194
 
            pick_obj.draft_force_assign(cr, uid , [pick_id], context)
3195
 
            # We also do a first 'check availability': cancel then check
3196
 
            pick_obj.cancel_assign(cr, uid, [pick_id], context)
3197
 
            pick_obj.action_assign(cr, uid, [pick_id], context)
3198
 
            
3199
 
        return super(sale_order, self)._hook_ship_create_execute_specific_code_02(cr, uid, ids, context, *args, **kwargs)
3200
 
    
3201
 
    def _hook_ship_create_stock_move(self, cr, uid, ids, context=None, *args, **kwargs):
3202
 
        '''
3203
 
        Please copy this to your module's method also.
3204
 
        This hook belongs to the action_ship_create method from sale>sale.py
3205
 
        
3206
 
        - allow to modify the data for stock move creation
3207
 
        '''
3208
 
        move_data = super(sale_order, self)._hook_ship_create_stock_move(cr, uid, ids, context=context, *args, **kwargs)
3209
 
        order = kwargs['order']
3210
 
        # For IR
3211
 
        if self.read(cr, uid, ids, ['procurement_request'], context=context)[0]['procurement_request']\
3212
 
        and self.read(cr, uid, ids, ['location_requestor_id'], context=context)[0]['location_requestor_id']:
3213
 
            move_data['type'] = 'internal'
3214
 
            move_data['reason_type_id'] = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_internal_supply')[1]
3215
 
            move_data['location_dest_id'] = self.read(cr, uid, ids, ['location_requestor_id'], context=context)[0]['location_requestor_id'][0]
3216
 
        else:
3217
 
            # first go to packing location (PICK/PACK/SHIP) or output location (Simple OUT)
3218
 
            # according to the configuration
3219
 
            # first go to packing location
3220
 
            setup = self.pool.get('unifield.setup.configuration').get_config(cr, uid)
3221
 
            if setup.delivery_process == 'simple':
3222
 
                move_data['location_dest_id'] = order.shop_id.warehouse_id.lot_output_id.id
3223
 
            else:
3224
 
                move_data['location_dest_id'] = order.shop_id.warehouse_id.lot_packing_id.id
3225
 
                
3226
 
            if self.pool.get('product.product').browse(cr, uid, move_data['product_id']).type == 'service_recep':
3227
 
                move_data['location_id'] = self.pool.get('stock.location').get_cross_docking_location(cr, uid)
3228
 
            
3229
 
            if 'sale_line_id' in move_data and move_data['sale_line_id']:
3230
 
                sale_line = self.pool.get('sale.order.line').browse(cr, uid, move_data['sale_line_id'], context=context)
3231
 
                if sale_line.type == 'make_to_order':
3232
 
                    move_data['location_id'] = self.pool.get('stock.location').get_cross_docking_location(cr, uid)
3233
 
                    move_data['move_cross_docking_ok'] = True
3234
 
                    # Update the stock.picking
3235
 
                    self.pool.get('stock.picking').write(cr, uid, move_data['picking_id'], {'cross_docking_ok': True}, context=context)
3236
 
 
3237
 
        move_data['state'] = 'confirmed'
3238
 
        return move_data
3239
 
    
3240
 
    def _hook_ship_create_stock_picking(self, cr, uid, ids, context=None, *args, **kwargs):
3241
 
        '''
3242
 
        Please copy this to your module's method also.
3243
 
        This hook belongs to the action_ship_create method from sale>sale.py
3244
 
        
3245
 
        - allow to modify the data for stock picking creation
3246
 
        '''
3247
 
        setup = self.pool.get('unifield.setup.configuration').get_config(cr, uid)
3248
 
        
3249
 
        picking_data = super(sale_order, self)._hook_ship_create_stock_picking(cr, uid, ids, context=context, *args, **kwargs)
3250
 
        order = kwargs['order']
3251
 
        
3252
 
        picking_data['state'] = 'draft'
3253
 
        if setup.delivery_process == 'simple':
3254
 
            picking_data['subtype'] = 'standard'
3255
 
            # use the name according to picking ticket sequence
3256
 
            pick_name = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.out')
3257
 
        else:
3258
 
            picking_data['subtype'] = 'picking'
3259
 
            # use the name according to picking ticket sequence
3260
 
            pick_name = self.pool.get('ir.sequence').get(cr, uid, 'picking.ticket')
3261
 
            
3262
 
        # For IR
3263
 
        if self.read(cr, uid, ids, ['procurement_request'], context=context):
3264
 
            procurement_request = self.read(cr, uid, ids, ['procurement_request'], context=context)[0]['procurement_request']
3265
 
            if procurement_request:
3266
 
                picking_data['reason_type_id'] = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_internal_supply')[1]
3267
 
                picking_data['type'] = 'internal'
3268
 
                picking_data['subtype'] = 'standard'
3269
 
                picking_data['reason_type_id'] = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_internal_supply')[1]
3270
 
                pick_name = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.internal')
3271
 
            
3272
 
        picking_data['name'] = pick_name        
3273
 
        picking_data['flow_type'] = 'full'
3274
 
        picking_data['backorder_id'] = False
3275
 
        picking_data['warehouse_id'] = order.shop_id.warehouse_id.id
3276
 
        
3277
 
        return picking_data
3278
 
    
3279
 
    def _hook_ship_create_execute_picking_workflow(self, cr, uid, ids, context=None, *args, **kwargs):
3280
 
        '''
3281
 
        Please copy this to your module's method also.
3282
 
        This hook belongs to the action_ship_create method from sale>sale.py
3283
 
        
3284
 
        - allow to avoid the stock picking workflow execution
3285
 
        - trigger the logging message for the created picking, as it stays in draft state and no call to action_confirm is performed
3286
 
          for the moment within the msf_outgoing logic
3287
 
        '''
3288
 
        setup = self.pool.get('unifield.setup.configuration').get_config(cr, uid)
3289
 
        cond = super(sale_order, self)._hook_ship_create_execute_picking_workflow(cr, uid, ids, context=context, *args, **kwargs)
3290
 
 
3291
 
        # On Simple OUT configuration, the system should confirm the OUT and launch a first check availability
3292
 
        if setup.delivery_process != 'simple':
3293
 
            cond = cond and False
3294
 
        
3295
 
        # diplay creation message for draft picking ticket
3296
 
        picking_id = kwargs['picking_id']
3297
 
        picking_obj = self.pool.get('stock.picking')
3298
 
        if picking_id:
3299
 
            picking_obj.log_picking(cr, uid, [picking_id], context=context)
3300
 
            # Launch a first check availability
3301
 
            self.pool.get('stock.picking').action_assign(cr, uid, [picking_id], context=context)
3302
 
        
3303
 
        return cond
3304
 
 
3305
 
sale_order()
3306
 
 
3307
 
 
3308
 
class procurement_order(osv.osv):
3309
 
    '''
3310
 
    procurement order workflow
3311
 
    '''
3312
 
    _inherit = 'procurement.order'
3313
 
    
3314
 
    def _hook_check_mts_on_message(self, cr, uid, context=None, *args, **kwargs):
3315
 
        '''
3316
 
        Please copy this to your module's method also.
3317
 
        This hook belongs to the _check_make_to_stock_product method from procurement>procurement.py>procurement.order
3318
 
        
3319
 
        - allow to modify the message written back to procurement order
3320
 
        '''
3321
 
        message = super(procurement_order, self)._hook_check_mts_on_message(cr, uid, context=context, *args, **kwargs)
3322
 
        procurement = kwargs['procurement']
3323
 
        if procurement.move_id.picking_id.state == 'draft' and procurement.move_id.picking_id.subtype == 'picking':
3324
 
            message = _("Shipment Process in Progress.")
3325
 
        return message
3326
 
    
3327
 
procurement_order()
3328