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

« back to all changes in this revision

Viewing changes to msf_outgoing/msf_outgoing.py

  • Committer: Olivier DOSSMANN
  • Date: 2014-03-31 09:31:46 UTC
  • mto: This revision was merged to the branch mainline in revision 2086.
  • Revision ID: od@tempo-consulting.fr-20140331093146-tgvxnly1kc1hbv1s
UF-2171 [ADD] Analytic distribution reset button for recurring models

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
from osv import osv, fields
23
23
from tools.translate import _
24
24
import netsvc
25
 
from datetime import datetime, timedelta, date
 
25
from datetime import datetime, date
26
26
 
27
27
from dateutil.relativedelta import relativedelta
28
28
import decimal_precision as dp
29
 
import netsvc
30
29
import logging
31
30
import tools
 
31
import time
32
32
from os import path
33
33
 
34
34
class stock_warehouse(osv.osv):
35
 
    '''
36
 
    add new packing, dispatch and distribution locations for input
37
 
    '''
38
 
    _inherit = "stock.warehouse"
 
35
    """
 
36
    Add new packing, dispatch and distribution locations for input
 
37
    """
 
38
    _inherit = 'stock.warehouse'
39
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
 
                }
 
40
 
 
41
    _columns = {
 
42
        'lot_packing_id': fields.many2one(
 
43
            'stock.location',
 
44
            string='Location Packing',
 
45
            required=True,
 
46
            domain=[('usage', '<>', 'view')]
 
47
        ),
 
48
        'lot_dispatch_id': fields.many2one(
 
49
            'stock.location',
 
50
            string='Location Dispatch',
 
51
            required=True,
 
52
            domain=[('usage', '<>', 'view')]
 
53
        ),
 
54
        'lot_distribution_id': fields.many2one(
 
55
            'stock.location',
 
56
            string='Location Distribution',
 
57
            required=True,
 
58
            domain=[('usage', '<>', 'view')]
 
59
        ),
 
60
    }
 
61
 
 
62
    def default_get(self, cr, uid, fields_list, context=None):
 
63
        """
 
64
        Get the default locations
 
65
        """
 
66
        # Objects
 
67
        data_get = self.pool.get('ir.model.data').get_object_reference
 
68
 
 
69
        if context is None:
 
70
            context = {}
 
71
 
 
72
        res = super(stock_warehouse, self).default_get(cr, uid, fields_list, context=context)
 
73
 
 
74
        res['lot_packing_id'] = data_get(cr, uid, 'msf_outgoing', 'stock_location_packing')[1]
 
75
        res['lot_dispatch_id'] = data_get(cr, uid, 'msf_outgoing', 'stock_location_dispatch')[1]
 
76
        res['lot_distribution_id'] = data_get(cr, uid, 'msf_outgoing', 'stock_location_distribution')[1]
 
77
 
 
78
        return res
50
79
 
51
80
stock_warehouse()
52
81
 
53
82
 
54
83
class pack_type(osv.osv):
55
 
    '''
56
 
    pack type corresponding to a type of pack (name, length, width, height)
57
 
    '''
 
84
    """
 
85
    Type of pack (name, length, width, height) like bag, box...
 
86
    """
58
87
    _name = 'pack.type'
59
88
    _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
 
                }
 
89
    _columns = {
 
90
        'name': fields.char(string='Name', size=1024),
 
91
        'length': fields.float(digits=(16, 2), string='Length [cm]'),
 
92
        'width': fields.float(digits=(16, 2), string='Width [cm]'),
 
93
        'height': fields.float(digits=(16, 2), string='Height [cm]'),
 
94
    }
65
95
 
66
96
pack_type()
67
97
 
72
102
    '''
73
103
    _name = 'shipment'
74
104
    _description = 'represents a group of pack families'
75
 
    
76
 
    def copy(self, cr, uid, id, default=None, context=None):
 
105
 
 
106
    def copy(self, cr, uid, copy_id, default=None, context=None):
77
107
        '''
78
108
        prevent copy
79
109
        '''
80
110
        raise osv.except_osv(_('Error !'), _('Shipment copy is forbidden.'))
81
 
    
82
 
    def copy_data(self, cr, uid, id, default=None, context=None):
 
111
 
 
112
    def copy_data(self, cr, uid, copy_id, default=None, context=None):
83
113
        '''
84
114
        reset one2many fields
85
115
        '''
89
119
            context = {}
90
120
        # reset one2many fields
91
121
        default.update(pack_family_memory_ids=[])
92
 
        result = super(shipment, self).copy_data(cr, uid, id, default=default, context=context)
93
 
        
 
122
        result = super(shipment, self).copy_data(cr, uid, copy_id, default=default, context=context)
 
123
 
94
124
        return result
95
 
    
 
125
 
96
126
    def _vals_get(self, cr, uid, ids, fields, arg, context=None):
97
127
        '''
98
128
        multi function for global shipment values
99
 
        
100
129
        '''
101
 
        pf_memory_obj = self.pool.get('pack.family.memory')
102
130
        picking_obj = self.pool.get('stock.picking')
103
 
        
 
131
 
104
132
        result = {}
105
133
        for shipment in self.browse(cr, uid, ids, context=context):
106
134
            values = {'total_amount': 0.0,
107
135
                      'currency_id': False,
108
136
                      'num_of_packs': 0,
109
137
                      'total_weight': 0.0,
 
138
                      'total_volume': 0.0,
110
139
                      'state': 'draft',
111
140
                      'backshipment_id': False,
112
141
                      }
113
142
            result[shipment.id] = values
114
143
            # gather the state from packing objects, all packing must have the same state for shipment
115
144
            # 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)
 
145
            packing_ids = picking_obj.search(cr, uid, [('shipment_id', '=', shipment.id), ], context=context)
117
146
            # fields to check and get
118
147
            state = None
119
148
            first_shipment_packing_id = None
120
149
            backshipment_id = None
 
150
            # delivery validated
 
151
            delivery_validated = None
121
152
            # browse the corresponding packings
122
153
            for packing in picking_obj.browse(cr, uid, packing_ids, context=context):
123
154
                # state check
125
156
                # if one packing is draft, even if other packing have been shipped, the shipment must stay draft until all packing are done
126
157
                if state != 'draft':
127
158
                    state = packing.state
128
 
                
 
159
 
 
160
                # all corresponding shipment must be dev validated or not
 
161
                if packing.delivered:
 
162
                    # integrity check
 
163
                    if delivery_validated is not None and delivery_validated != packing.delivered:
 
164
                        # two packing have different delivery validated values -> problem
 
165
                        assert False, 'All packing do not have the same validated value - %s - %s' % (delivery_validated, packing.delivered)
 
166
                    # update the value
 
167
                    delivery_validated = packing.delivered
 
168
 
129
169
                # first_shipment_packing_id check - no check for the same reason
130
170
                first_shipment_packing_id = packing.first_shipment_packing_id.id
131
 
                
 
171
 
132
172
                # backshipment_id check
133
173
                if backshipment_id and backshipment_id != packing.backorder_id.shipment_id.id:
134
 
                    assert False, 'all packing of the shipment have not the same draft shipment correspondance - %s - %s'%(backshipment_id, packing.backorder_id.shipment_id.id)
 
174
                    assert False, 'all packing of the shipment have not the same draft shipment correspondance - %s - %s' % (backshipment_id, packing.backorder_id.shipment_id.id)
135
175
                backshipment_id = packing.backorder_id and packing.backorder_id.shipment_id.id or False
136
 
            
137
176
            # if state is in ('draft', 'done', 'cancel'), the shipment keeps the same state
138
177
            if state not in ('draft', 'done', 'cancel',):
139
178
                if first_shipment_packing_id:
141
180
                    state = 'shipped'
142
181
                else:
143
182
                    state = 'packed'
 
183
            elif state == 'done':
 
184
                if delivery_validated:
 
185
                    # special state corresponding to delivery validated
 
186
                    state = 'delivered'
 
187
 
144
188
            values['state'] = state
145
189
            values['backshipment_id'] = backshipment_id
146
 
            
147
 
            for memory_family in shipment.pack_family_memory_ids:
 
190
 
 
191
            pack_fam_ids = [x.id for x in shipment.pack_family_memory_ids]
 
192
            for memory_family in self.pool.get('pack.family.memory').read(cr, uid, pack_fam_ids, ['state', 'num_of_packs', 'total_weight', 'total_volume', 'total_amount', 'currency_id']):
148
193
                # taken only into account if not done (done means returned packs)
149
 
                if memory_family.state not in ('done',):
 
194
                if shipment.state in ('delivered', 'done') or memory_family['state'] not in ('done',) :
150
195
                    # num of packs
151
 
                    num_of_packs = memory_family.num_of_packs
 
196
                    num_of_packs = memory_family['num_of_packs']
152
197
                    values['num_of_packs'] += int(num_of_packs)
153
198
                    # total weight
154
 
                    total_weight = memory_family.total_weight
 
199
                    total_weight = memory_family['total_weight']
155
200
                    values['total_weight'] += int(total_weight)
 
201
                    # total volume
 
202
                    total_volume = memory_family['total_volume']
 
203
                    values['total_volume'] += float(total_volume)
156
204
                    # total amount
157
 
                    total_amount = memory_family.total_amount
 
205
                    total_amount = memory_family['total_amount']
158
206
                    values['total_amount'] += total_amount
159
207
                    # currency
160
 
                    currency_id = memory_family.currency_id and memory_family.currency_id.id or False
 
208
                    currency_id = memory_family['currency_id'] or False
161
209
                    values['currency_id'] = currency_id
162
 
                
 
210
            for item in shipment.additional_items_ids:
 
211
                values['total_weight'] += item.weight
163
212
        return result
164
 
    
 
213
 
165
214
    def _get_shipment_ids(self, cr, uid, ids, context=None):
166
215
        '''
167
216
        ids represents the ids of stock.picking objects for which state has changed
168
 
        
 
217
 
169
218
        return the list of ids of shipment object which need to get their state field updated
170
219
        '''
171
220
        pack_obj = self.pool.get('stock.picking')
173
222
        for packing in pack_obj.browse(cr, uid, ids, context=context):
174
223
            if packing.shipment_id and packing.shipment_id.id not in result:
175
224
                result.append(packing.shipment_id.id)
176
 
        return result 
177
 
    
 
225
        return result
 
226
 
 
227
    def _packs_search(self, cr, uid, obj, name, args, context=None):
 
228
        """
 
229
        Searches Ids of shipment
 
230
        """
 
231
        if context is None:
 
232
            context = {}
 
233
 
 
234
        cr.execute('''
 
235
        select t.id, sum(case when t.tp != 0 then  t.tp - t.fp + 1 else 0 end) as sumpack from (
 
236
            select p.shipment_id as id, min(to_pack) as tp, min(from_pack) as fp from stock_picking p
 
237
            left join stock_move m on m.picking_id = p.id and m.state != 'cancel' and m.product_qty > 0
 
238
            where p.shipment_id is not null
 
239
            group by p.shipment_id, to_pack, from_pack
 
240
        ) t
 
241
        group by t.id
 
242
        having sum(case when t.tp != 0 then  t.tp - t.fp + 1 else 0 end) %s %s
 
243
''' % (args[0][1], args[0][2]))
 
244
        return [('id', 'in', [x[0] for x in cr.fetchall()])]
 
245
 
178
246
    _columns = {'name': fields.char(string='Reference', size=1024),
179
 
                'date': fields.date(string='Date'),
 
247
                'date': fields.datetime(string='Creation Date'),
 
248
                'shipment_expected_date': fields.datetime(string='Expected Ship Date'),
 
249
                'shipment_actual_date': fields.datetime(string='Actual Ship Date', readonly=True,),
180
250
                'transport_type': fields.selection([('by_road', 'By road')],
181
251
                                                   string="Transport Type", readonly=True),
182
252
                'address_id': fields.many2one('res.partner.address', 'Address', help="Address of customer"),
214
284
                'consignee_signature': fields.char(string='Signature', size=1024),
215
285
                # functions
216
286
                'partner_id': fields.related('address_id', 'partner_id', type='many2one', relation='res.partner', string='Customer', store=True),
 
287
                'partner_id2': fields.many2one('res.partner', string='Customer', required=False),
217
288
                'total_amount': fields.function(_vals_get, method=True, type='float', string='Total Amount', multi='get_vals',),
218
289
                'currency_id': fields.function(_vals_get, method=True, type='many2one', relation='res.currency', string='Currency', multi='get_vals',),
219
 
                'num_of_packs': fields.function(_vals_get, method=True, type='integer', string='Number of Packs', multi='get_vals_X',), # old_multi ship_vals
 
290
                '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
220
291
                'total_weight': fields.function(_vals_get, method=True, type='float', string='Total Weight[kg]', multi='get_vals',),
 
292
                'total_volume': fields.function(_vals_get, method=True, type='float', string=u'Total Volume[dm³]', multi='get_vals',),
221
293
                'state': fields.function(_vals_get, method=True, type='selection', selection=[('draft', 'Draft'),
222
294
                                                                                              ('packed', 'Packed'),
223
295
                                                                                              ('shipped', 'Shipped'),
224
 
                                                                                              ('done', 'Done'),
225
 
                                                                                              ('cancel', 'Canceled')], string='State', multi='get_vals',
226
 
                                         store= {'stock.picking': (_get_shipment_ids, ['state', 'shipment_id',], 10),}),
 
296
                                                                                              ('done', 'Closed'),
 
297
                                                                                              ('delivered', 'Delivered'),
 
298
                                                                                              ('cancel', 'Cancelled')], string='State', multi='get_vals',
 
299
                                         store={
 
300
                                             'stock.picking': (_get_shipment_ids, ['state', 'shipment_id', 'delivered'], 10),
 
301
                                             }),
227
302
                'backshipment_id': fields.function(_vals_get, method=True, type='many2one', relation='shipment', string='Draft Shipment', multi='get_vals',),
 
303
                # added by Quentin https://bazaar.launchpad.net/~unifield-team/unifield-wm/trunk/revision/426.20.14
 
304
                'parent_id': fields.many2one('shipment', string='Parent shipment'),
 
305
                'invoice_id': fields.many2one('account.invoice', string='Related invoice'),
 
306
                'additional_items_ids': fields.one2many('shipment.additionalitems', 'shipment_id', string='Additional Items'),
 
307
                'picking_ids': fields.one2many(
 
308
                    'stock.picking',
 
309
                    'shipment_id',
 
310
                    string='Associated Packing List',
 
311
                )
228
312
                }
 
313
 
 
314
    def _get_sequence(self, cr, uid, context=None):
 
315
        ir_id = self.pool.get('ir.model.data')._get_id(cr, uid, 'msf_outgoing', 'seq_shipment')
 
316
        return self.pool.get('ir.model.data').browse(cr, uid, ir_id).res_id
 
317
 
 
318
    _defaults = {
 
319
        'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
 
320
        'sequence_id': _get_sequence,
 
321
    }
 
322
 
229
323
    _order = 'name desc'
230
 
    
 
324
 
231
325
    def create_shipment(self, cr, uid, ids, context=None):
232
 
        '''
233
 
        open the wizard to create (partial) shipment
234
 
        '''
235
 
        # we need the context for the wizard switch
236
 
        if context is None:
237
 
            context = {}
238
 
            
239
 
        wiz_obj = self.pool.get('wizard')
240
 
        
241
 
        # data
242
 
        name = _("Create Shipment")
243
 
        model = 'shipment.wizard'
244
 
        step = 'create'
245
 
        # open the selected wizard
246
 
        return wiz_obj.open_wizard(cr, uid, ids, name=name, model=model, step=step, context=context)
247
 
    
248
 
    def do_create_shipment(self, cr, uid, ids, context=None):
249
 
        '''
250
 
        for each original draft picking:
251
 
         - creation of the new packing object with empty moves
252
 
         - convert partial data to move related data
253
 
         - create corresponding moves in new packing
254
 
         - update initial packing object
255
 
         - trigger workflow for new packing object
256
 
        '''
257
 
        # integrity check
258
 
        assert context, 'no context, method call is wrong'
259
 
        assert 'partial_datas_shipment' in context, 'partial_datas_shipment no defined in context'
260
 
        
261
 
        pick_obj = self.pool.get('stock.picking')
262
 
        move_obj = self.pool.get('stock.move')
263
 
        shipment_obj = self.pool.get('shipment')
264
 
        
265
 
        # data from wizard
266
 
        partial_datas_shipment = context['partial_datas_shipment']
267
 
        # shipment ids from ids must be equal to shipment ids from partial datas
268
 
        assert set(ids) == set(partial_datas_shipment.keys()), 'shipment ids from ids and partial do not match'
269
 
        
270
 
        for draft_shipment in self.browse(cr, uid, partial_datas_shipment.keys(), context=context):
271
 
            # for each shipment create a new shipment which will be used by the group of new packing objects
272
 
            address_id = shipment_obj.read(cr, uid, [draft_shipment.id], ['address_id'], context=context)[0]['address_id'][0]
273
 
            sequence = draft_shipment.sequence_id
274
 
            shipment_number = sequence.get_id(test='id', context=context)
275
 
            # state is a function - not set
276
 
            shipment_name = draft_shipment.name + '-' + shipment_number
277
 
            values = {'name': shipment_name, 'address_id': address_id}
278
 
            shipment_id = shipment_obj.create(cr, uid, values, context=context)
 
326
        """
 
327
        Open the wizard to process the shipment
 
328
        """
 
329
        # Objects
 
330
        ship_proc_obj = self.pool.get('shipment.processor')
 
331
 
 
332
        if context is None:
 
333
            context = {}
 
334
 
 
335
        if isinstance(ids, (int, long)):
 
336
            ids = [ids]
 
337
 
 
338
        if not ids:
 
339
            raise osv.except_osv(
 
340
                _('Processing Error'),
 
341
                _('No data to process !'),
 
342
            )
 
343
 
 
344
        for shipment in self.browse(cr, uid, ids, context=context):
 
345
            # Create the shipment processor wizard
 
346
            ship_proc_vals = {
 
347
                'shipment_id': shipment.id,
 
348
                'address_id': shipment.address_id.id,
 
349
 
 
350
            }
 
351
            ship_proc_id = ship_proc_obj.create(cr, uid, ship_proc_vals, context=context)
 
352
            ship_proc_obj.create_lines(cr, uid, ship_proc_id, context=context)
 
353
 
 
354
        return {
 
355
            'type': 'ir.actions.act_window',
 
356
            'name': _('Create shipment'),
 
357
            'res_model': ship_proc_obj._name,
 
358
            'view_type': 'form',
 
359
            'view_mode': 'form',
 
360
            'target': 'new',
 
361
            'res_id': ship_proc_id,
 
362
            'context': context,
 
363
        }
 
364
 
 
365
    def do_create_shipment(self, cr, uid, wizard_ids, context=None):
 
366
        """
 
367
        Create the shipment
 
368
 
 
369
        BE CAREFUL: the wizard_ids parameters is the IDs of the shipment.processor objects,
 
370
        not those of shipment objects
 
371
        """
 
372
        # Objects
 
373
        ship_proc_obj = self.pool.get('shipment.processor')
 
374
        picking_obj = self.pool.get('stock.picking')
 
375
        add_line_obj = self.pool.get('shipment.additionalitems')
 
376
        data_obj = self.pool.get('ir.model.data')
 
377
 
 
378
        if context is None:
 
379
            context = {}
 
380
 
 
381
        if isinstance(wizard_ids, (int, long)):
 
382
            wizard_ids = wizard_ids
 
383
 
 
384
        for wizard in ship_proc_obj.browse(cr, uid, wizard_ids, context=context):
 
385
            shipment = wizard.shipment_id
 
386
            sequence = shipment.sequence_id
 
387
 
 
388
            shipment_number = sequence.get_id(code_or_id='id', context=context)
 
389
            shipment_name = '%s-%s' % (shipment.name, shipment_number)
 
390
 
 
391
            ship_val = {
 
392
                'name': shipment_name,
 
393
                'address': shipment.address_id.id,
 
394
                'partner_id': shipment.partner_id.id,
 
395
                'partner_id2': shipment.partner_id.id,
 
396
                'shipment_expected_date': shipment.shipment_expected_date,
 
397
                'shipment_actual_date': shipment.shipment_actual_date,
 
398
                'parent_id': shipment.id,
 
399
            }
 
400
 
 
401
            shipment_id = self.create(cr, uid, ship_val, context=context)
 
402
 
279
403
            context['shipment_id'] = shipment_id
280
 
            for draft_packing in pick_obj.browse(cr, uid, partial_datas_shipment[draft_shipment.id].keys(), context=context):
281
 
                # copy the picking object without moves
282
 
                # creation of moves and update of initial in picking create method
283
 
                context.update(draft_shipment_id=draft_shipment.id, draft_packing_id=draft_packing.id)
284
 
                sequence = draft_packing.sequence_id
285
 
                packing_number = sequence.get_id(test='id', context=context)
286
 
                new_packing_id = pick_obj.copy(cr, uid, draft_packing.id,
287
 
                                               {'name': draft_packing.name + '-' + packing_number,
288
 
                                                'backorder_id': draft_packing.id,
289
 
                                                'shipment_id': False,
290
 
                                                'move_lines': []}, context=dict(context, keep_prodlot=True, allow_copy=True,))
 
404
 
 
405
            for add_line in wizard.additional_line_ids:
 
406
                line_vals = {
 
407
                    'name': add_line.name,
 
408
                    'shipment_id': shipment_id,
 
409
                    'picking_id': False,
 
410
                    'quantity': add_line.quantity,
 
411
                    'uom': add_line.uom_id.id,
 
412
                    'comment': add_line.comment,
 
413
                    'volume': add_line.volume,
 
414
                    'weight': add_line.weight,
 
415
                }
 
416
 
 
417
                add_line_obj.create(cr, uid, line_vals, context=context)
 
418
 
 
419
            for family in wizard.family_ids:
 
420
                picking = family.draft_packing_id
 
421
                # Copy the picking object without moves
 
422
                # Creation of moves and update of initial in picking create method
 
423
                sequence = picking.sequence_id
 
424
                packing_number = sequence.get_id(code_or_id='id', context=context)
 
425
 
 
426
                # New packing data
 
427
                packing_data = {
 
428
                    'name': '%s-%s' % (picking.name, packing_number),
 
429
                    'backorder_id': picking.id,
 
430
                    'shipment_id': False,
 
431
                    'move_lines': [],
 
432
                }
 
433
 
 
434
                # Update context for copy
 
435
                context.update({
 
436
                    'keep_prodlot': True,
 
437
                    'keepLineNumber': True,
 
438
                    'allow_copy': True,
 
439
                    'non_stock_noupdate': True,
 
440
                    'shipment_proc_id': wizard.id,
 
441
                    'draft_packing_id': picking.id,
 
442
                })
 
443
 
 
444
                new_packing_id = picking_obj.copy(cr, uid, picking.id, packing_data, context=context)
 
445
 
 
446
                # Reset context
 
447
                context.update({
 
448
                    'keep_prodlot': False,
 
449
                    'keepLineNumber': False,
 
450
                    'allow_copy': False,
 
451
                    'non_stock_noupdate': False,
 
452
                    'draft_packing_id': False,
 
453
                })
291
454
 
292
455
                # confirm the new packing
293
456
                wf_service = netsvc.LocalService("workflow")
294
457
                wf_service.trg_validate(uid, 'stock.picking', new_packing_id, 'button_confirm', cr)
295
458
                # simulate check assign button, as stock move must be available
296
 
                pick_obj.force_assign(cr, uid, [new_packing_id])
297
 
                
298
 
            # log creation message
299
 
            self.log(cr, uid, shipment_id, _('The new Shipment %s has been created.'%shipment_name))
300
 
                
301
 
        # TODO which behavior
302
 
        return {'type': 'ir.actions.act_window_close'}
303
 
    
 
459
                picking_obj.force_assign(cr, uid, [new_packing_id])
 
460
 
 
461
            # Log creation message
 
462
            self.log(cr, uid, shipment.id, _('The new Shipment %s has been created.') % (shipment_name,))
 
463
            # The shipment is automatically shipped, no more pack states in between.
 
464
            self.ship(cr, uid, [shipment_id], context=context)
 
465
 
 
466
        view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_shipment_form')
 
467
        view_id = view_id and view_id[1] or False
 
468
 
 
469
        # Remove unneeded data in context
 
470
        context.update({
 
471
            'partial_datas_ppl1': {},
 
472
            'partial_datas': {},
 
473
        })
 
474
 
 
475
        return {
 
476
            'name':_("Shipment"),
 
477
            'type': 'ir.actions.act_window',
 
478
            'res_model': 'shipment',
 
479
            'view_mode': 'form,tree',
 
480
            'view_type': 'form',
 
481
            'view_id': [view_id],
 
482
            'res_id': shipment_id,
 
483
            'target': 'crush',
 
484
            'context': context,
 
485
        }
 
486
 
304
487
    def return_packs(self, cr, uid, ids, context=None):
305
 
        '''
306
 
        open the wizard to return packs from draft shipment
307
 
        '''
308
 
        # we need the context for the wizard switch
 
488
        """
 
489
        Open the wizard to return packs from draft shipment
 
490
        """
 
491
        # Objects
 
492
        proc_obj = self.pool.get('return.shipment.processor')
 
493
 
309
494
        if context is None:
310
495
            context = {}
311
 
            
312
 
        wiz_obj = self.pool.get('wizard')
313
 
        
314
 
        # data
315
 
        name = _("Return Packs")
316
 
        model = 'shipment.wizard'
317
 
        step = 'returnpacks'
318
 
        # open the selected wizard
319
 
        return wiz_obj.open_wizard(cr, uid, ids, name=name, model=model, step=step, context=context)
320
 
    
321
 
    def do_return_packs(self, cr, uid, ids, context=None):
322
 
        '''
323
 
        for each original draft picking:
324
 
         - convert partial data to move related data
325
 
         - update the draft_packing's moves, decrease quantity and update from/to info
326
 
         - update initial packing object
327
 
         - create a back move for each move with return quantity to initial location
328
 
         - increase quantity of related draft_picking_ticket's moves
329
 
        '''
330
 
        # integrity check
331
 
        assert context, 'no context, method call is wrong'
332
 
        assert 'partial_datas' in context, 'partial_datas no defined in context'
333
 
        
334
 
        pick_obj = self.pool.get('stock.picking')
 
496
 
 
497
        if isinstance(ids, (int, long)):
 
498
            ids = [ids]
 
499
 
 
500
        if not ids:
 
501
            raise osv.except_osv(
 
502
                _('Processing Error'),
 
503
                _('No data to process !'),
 
504
            )
 
505
 
 
506
        processor_id = proc_obj.create(cr, uid, {'shipment_id': ids[0]}, context=context)
 
507
        proc_obj.create_lines(cr, uid, processor_id, context=context)
 
508
 
 
509
        return {
 
510
            'type': 'ir.actions.act_window',
 
511
            'res_model': proc_obj._name,
 
512
            'name': _('Return Packs'),
 
513
            'view_type': 'form',
 
514
            'view_mode': 'form',
 
515
            'res_id': processor_id,
 
516
            'target': 'new',
 
517
            'context': context,
 
518
        }
 
519
 
 
520
    def do_return_packs(self, cr, uid, wizard_ids, context=None):
 
521
        """
 
522
        Return the selected packs to the draft picking ticket
 
523
 
 
524
        BE CAREFUL: the wizard_ids parameters is the IDs of the return.shipment.processor objects,
 
525
        not those of shipment objects
 
526
        """
 
527
        # Objects
 
528
        proc_obj = self.pool.get('return.shipment.processor')
 
529
        picking_obj = self.pool.get('stock.picking')
335
530
        move_obj = self.pool.get('stock.move')
336
 
        obj_data = self.pool.get('ir.model.data')
337
 
        
338
 
        # data from wizard
339
 
        partial_datas = context['partial_datas']
340
 
        # shipment ids from ids must be equal to shipment ids from partial datas
341
 
        assert set(ids) == set(partial_datas.keys()), 'shipment ids from ids and partial do not match'
342
 
        
343
 
        for draft_shipment_id in partial_datas:
344
 
            # log flag - log for draft shipment is displayed only one time for each draft shipment
 
531
        data_obj = self.pool.get('ir.model.data')
 
532
 
 
533
        if context is None:
 
534
            context = {}
 
535
 
 
536
        if isinstance(wizard_ids, (int, long)):
 
537
            wizard_ids = [wizard_ids]
 
538
 
 
539
        if not wizard_ids:
 
540
            raise osv.except_osv(
 
541
                _('Processing Error'),
 
542
                _('No data to process'),
 
543
            )
 
544
 
 
545
        shipment_ids = []
 
546
 
 
547
        for wizard in proc_obj.browse(cr, uid, wizard_ids, context=context):
 
548
            draft_picking = None
 
549
            shipment = wizard.shipment_id
 
550
            shipment_ids.append(shipment.id)
 
551
            # log flag - res.log for draft shipment is displayed only one time for each draft shipment
345
552
            log_flag = False
346
 
            # for each draft packing
347
 
            for draft_packing in pick_obj.browse(cr, uid, partial_datas[draft_shipment_id].keys(), context=context):
348
 
                # corresponding draft picking ticket -> draft_packing - ppl - picking_ticket - draft_picking_ticket
349
 
                draft_picking = draft_packing.previous_step_id.previous_step_id.backorder_id
350
 
                draft_picking_id = draft_packing.previous_step_id.previous_step_id.backorder_id.id
351
 
                # for each sequence
352
 
                for from_pack in partial_datas[draft_shipment_id][draft_packing.id]:
353
 
                    for to_pack in partial_datas[draft_shipment_id][draft_packing.id][from_pack]:
354
 
                        # partial data for one sequence of one draft packing
355
 
                        data = partial_datas[draft_shipment_id][draft_packing.id][from_pack][to_pack][0]
356
 
                        # total number of packs
357
 
                        total_num = to_pack - from_pack + 1
358
 
                        # number of returned packs
359
 
                        selected_number = data['selected_number']
360
 
                        # we take the packs with the highest numbers
361
 
                        # new moves
362
 
                        selected_from_pack = to_pack - selected_number + 1
363
 
                        selected_to_pack = to_pack
364
 
                        # update initial moves
365
 
                        if selected_number == total_num:
366
 
                            # if all packs have been selected, from/to are set to 0
367
 
                            initial_from_pack = 0
368
 
                            initial_to_pack = 0
369
 
                        else:
370
 
                            initial_from_pack = from_pack
371
 
                            initial_to_pack = to_pack - selected_number
372
 
                        # find the concerned stock moves
373
 
                        move_ids = move_obj.search(cr, uid, [('picking_id', '=', draft_packing.id),
374
 
                                                             ('from_pack', '=', from_pack),
375
 
                                                             ('to_pack', '=', to_pack)])
376
 
                        # update the moves, decrease the quantities
377
 
                        for move in move_obj.browse(cr, uid, move_ids, context=context):
378
 
                            # stock move are not canceled as for ppl return process
379
 
                            # because this represents a draft packing, meaning some shipment could be canceled and
380
 
                            # returned to this stock move
381
 
                            # initial quantity
382
 
                            initial_qty = move.product_qty
383
 
                            # quantity to return
384
 
                            return_qty = selected_number * move.qty_per_pack
385
 
                            # update initial quantity
386
 
                            initial_qty = max(initial_qty - return_qty, 0)
387
 
                            values = {'product_qty': initial_qty,
388
 
                                      'from_pack': initial_from_pack,
389
 
                                      'to_pack': initial_to_pack,}
390
 
                            
391
 
                            move_obj.write(cr, uid, [move.id], values, context=context)
392
 
                            
393
 
                            # create a back move with the quantity to return to the good location
394
 
                            # the good location is stored in the 'initial_location' field
395
 
                            copy_id = move_obj.copy(cr, uid, move.id, {'product_qty': return_qty,
396
 
                                                             'location_dest_id': move.initial_location.id,
397
 
                                                             'from_pack': selected_from_pack,
398
 
                                                             'to_pack': selected_to_pack,
399
 
                                                             'state': 'done'}, context=context)
400
 
                            # find the corresponding move in draft in the draft **picking**
401
 
                            draft_move = move.backmove_id
402
 
                            # increase the draft move with the move quantity
403
 
                            draft_initial_qty = move_obj.read(cr, uid, [draft_move.id], ['product_qty'], context=context)[0]['product_qty']
404
 
                            draft_initial_qty += return_qty
405
 
                            move_obj.write(cr, uid, [draft_move.id], {'product_qty': draft_initial_qty}, context=context)
406
 
            
407
 
                # log the increase action - display the picking ticket view form - log message for each draft packing because each corresponds to a different draft picking
408
 
                if not log_flag:
409
 
                    draft_shipment_name = self.read(cr, uid, draft_shipment_id, ['name'], context=context)['name']
410
 
                    self.log(cr, uid, draft_shipment_id, _("Packs from the draft Shipment (%s) have been returned to stock."%draft_shipment_name),)
411
 
                    log_flag = True
412
 
                res = obj_data.get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_form')[1]
413
 
                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,})
414
 
            
415
 
        # call complete_finished on the shipment object
416
 
        # if everything is alright (all draft packing are finished) the shipment is done also 
417
 
        result = self.complete_finished(cr, uid, partial_datas.keys(), context=context)
418
 
        
419
 
        # TODO which behavior
420
 
        return {'type': 'ir.actions.act_window_close'}
421
 
    
 
553
 
 
554
            for family in wizard.family_ids:
 
555
                picking = family.draft_packing_id
 
556
                draft_picking = family.ppl_id and family.ppl_id.previous_step_id and family.ppl_id.previous_step_id.backorder_id or False
 
557
 
 
558
                # Update initial move
 
559
                if family.selected_number == int(family.num_of_packs):
 
560
                    # If al packs have been selected, from/to are set to 0
 
561
                    initial_from_pack = 0
 
562
                    initial_to_pack = 0
 
563
                else:
 
564
                    initial_from_pack = family.from_pack
 
565
                    initial_to_pack = family.to_pack - family.selected_number
 
566
 
 
567
                # Find the concerned stock moves
 
568
                move_ids = move_obj.search(cr, uid, [
 
569
                    ('picking_id', '=', picking.id),
 
570
                    ('from_pack', '=', family.from_pack),
 
571
                    ('to_pack', '=', family.to_pack),
 
572
                ], context=context)
 
573
 
 
574
                # Update the moves, decrease the quantities
 
575
                for move in move_obj.browse(cr, uid, move_ids, context=context):
 
576
                    """
 
577
                    Stock moves are not canceled as for PPL return process
 
578
                    because this represents a draft packing, meaning some shipment could be canceled and
 
579
                    return to this stock move
 
580
                    """
 
581
                    return_qty = family.selected_number * move.qty_per_pack
 
582
                    move_vals = {
 
583
                        'product_qty': max(move.product_qty - return_qty, 0),
 
584
                        'from_pack': initial_from_pack,
 
585
                        'to_pack': initial_to_pack,
 
586
                    }
 
587
 
 
588
                    move_obj.write(cr, uid, [move.id], move_vals, context=context)
 
589
 
 
590
                    """
 
591
                    Create a back move with the quantity to return to the good location.
 
592
                    The good location is store in the 'initial_location' field
 
593
                    """
 
594
                    cp_vals = {
 
595
                        'product_qty': return_qty,
 
596
                        'line_number': move.line_number,
 
597
                        'location_dest_id': move.initial_location.id,
 
598
                        'from_pack': family.to_pack - family.selected_number + 1,
 
599
                        'to_pack': family.to_pack,
 
600
                        'state': 'done',
 
601
                    }
 
602
                    context['non_stock_noupdate'] = True
 
603
 
 
604
                    move_obj.copy(cr, uid, move.id, cp_vals, context=context)
 
605
 
 
606
                    context['non_stock_noupdate'] = False
 
607
 
 
608
                    # Find the corresponding move in draft in the draft picking ticket
 
609
                    draft_move = move.backmove_id
 
610
                    # Increase the draft move with the move quantity
 
611
                    draft_initial_qty = move_obj.read(cr, uid, [draft_move.id], ['product_qty'], context=context)[0]['product_qty']
 
612
                    draft_initial_qty += return_qty
 
613
                    move_obj.write(cr, uid, [draft_move.id], {'product_qty': draft_initial_qty}, context=context)
 
614
 
 
615
            # log the increase action - display the picking ticket view form - log message for each draft packing because each corresponds to a different draft picking
 
616
            if not log_flag:
 
617
                draft_shipment_name = self.read(cr, uid, shipment.id, ['name'], context=context)['name']
 
618
                self.log(cr, uid, shipment.id, _("Packs from the draft Shipment (%s) have been returned to stock.") % (draft_shipment_name,))
 
619
                log_flag = True
 
620
 
 
621
            res = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_form')[1]
 
622
            context.update({
 
623
                'view_id': res,
 
624
                'picking_type': 'picking.ticket'
 
625
            })
 
626
            if draft_picking:
 
627
                picking_obj.log(cr, uid, draft_picking.id, _("The corresponding Draft Picking Ticket (%s) has been updated.") % (draft_picking.name,), context=context)
 
628
 
 
629
        # Call complete_finished on the shipment object
 
630
        # If everything is allright (all draft packing are finished) the shipment is done also
 
631
        self.complete_finished(cr, uid, shipment_ids, context=context)
 
632
 
 
633
        view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_form')
 
634
        return {
 
635
            'name':_("Picking Ticket"),
 
636
            'view_mode': 'form,tree',
 
637
            'view_id': [view_id and view_id[1] or False],
 
638
            'view_type': 'form',
 
639
            'res_model': 'stock.picking',
 
640
            'res_id': draft_picking.id,
 
641
            'type': 'ir.actions.act_window',
 
642
            'target': 'crush',
 
643
            'context': context
 
644
        }
 
645
 
422
646
    def return_packs_from_shipment(self, cr, uid, ids, context=None):
423
 
        '''
424
 
        open the wizard to return packs from draft shipment
425
 
        '''
426
 
        # we need the context for the wizard switch
 
647
        """
 
648
        Open the wizard to return packs from draft shipment
 
649
        """
 
650
        # Objects
 
651
        proc_obj = self.pool.get('return.pack.shipment.processor')
 
652
 
427
653
        if context is None:
428
654
            context = {}
429
 
            
430
 
        wiz_obj = self.pool.get('wizard')
431
 
        
432
 
        # data
433
 
        name = _("Return Packs from Shipment")
434
 
        model = 'shipment.wizard'
435
 
        step = 'returnpacksfromshipment'
436
 
        # open the selected wizard
437
 
        return wiz_obj.open_wizard(cr, uid, ids, name=name, model=model, step=step, context=context)
438
 
    
 
655
 
 
656
        if isinstance(ids, (int, long)):
 
657
            ids = [ids]
 
658
 
 
659
        if not ids:
 
660
            raise osv.except_osv(
 
661
                _('Processing Error'),
 
662
                _('No data to process !'),
 
663
            )
 
664
 
 
665
        processor_id = proc_obj.create(cr, uid, {'shipment_id': ids[0]}, context=context)
 
666
        proc_obj.create_lines(cr, uid, processor_id, context=context)
 
667
 
 
668
        return {
 
669
            'type': 'ir.actions.act_window',
 
670
            'res_model': proc_obj._name,
 
671
            'name': _('Return Packs from Shipment'),
 
672
            'view_type': 'form',
 
673
            'view_mode': 'form',
 
674
            'res_id': processor_id,
 
675
            'target': 'new',
 
676
            'context': context,
 
677
        }
 
678
 
439
679
    def compute_sequences(self, cr, uid, ids, context=None, *args, **kwargs):
440
680
        '''
441
681
        compute corresponding sequences
447
687
        stay = [(from_pack, to_pack)]
448
688
        # the list of tuple representing the draft packing movements from/to
449
689
        back_to_draft = []
450
 
        
 
690
 
451
691
        # loop the partials
452
692
        for partial in datas:
453
693
            return_from = partial['return_from']
469
709
                            break
470
710
                        else:
471
711
                            # to+1-seq[1] in stay
472
 
                            stay.append((return_to+1, seq[1]))
 
712
                            stay.append((return_to + 1, seq[1]))
473
713
                            break
474
 
                    
 
714
 
475
715
                    elif return_to == seq[1]:
476
716
                        # do not start at beginning, but same end
477
 
                        stay.append((seq[0], return_from-1))
 
717
                        stay.append((seq[0], return_from - 1))
478
718
                        break
479
 
                    
 
719
 
480
720
                    else:
481
721
                        # in the middle, two new tuple in stay
482
 
                        stay.append((seq[0], return_from-1))
483
 
                        stay.append((return_to+1, seq[1]))
 
722
                        stay.append((seq[0], return_from - 1))
 
723
                        stay.append((return_to + 1, seq[1]))
484
724
                        break
485
 
            
 
725
 
486
726
            # old one is always removed
487
727
            stay.pop(i)
488
 
            
 
728
 
489
729
        # return both values - return order is important
490
730
        return stay, back_to_draft
491
 
    
492
 
    def do_return_packs_from_shipment(self, cr, uid, ids, context=None):
493
 
        '''
494
 
        return the packs to the corresponding draft packing object
495
 
        
496
 
        for each corresponding draft packing
497
 
        - 
498
 
        '''
499
 
        # integrity check
500
 
        assert context, 'no context, method call is wrong'
501
 
        assert 'partial_datas' in context, 'partial_datas no defined in context'
502
 
        
503
 
        pick_obj = self.pool.get('stock.picking')
 
731
 
 
732
    def do_return_packs_from_shipment(self, cr, uid, wizard_ids, context=None):
 
733
        """
 
734
        Return the selected packs to the PPL
 
735
 
 
736
        BE CAREFUL: the wizard_ids parameters is the IDs of the return.shipment.processor objects,
 
737
        not those of shipment objects
 
738
        """
 
739
        # Objects
 
740
        proc_obj = self.pool.get('return.pack.shipment.processor')
504
741
        move_obj = self.pool.get('stock.move')
505
 
        wf_service = netsvc.LocalService("workflow")
506
 
        
507
 
        # data from wizard
508
 
        partial_datas = context['partial_datas']
509
 
        # shipment ids from ids must be equal to shipment ids from partial datas
510
 
        assert set(ids) == set(partial_datas.keys()), 'shipment ids from ids and partial do not match'
511
 
        
512
 
        # for each shipment
513
 
        for shipment_id in partial_datas:
514
 
            # for each packing
515
 
            for packing in pick_obj.browse(cr, uid, partial_datas[shipment_id].keys(), context=context):
516
 
                # corresponding draft packing -> backorder
517
 
                draft_packing_id = packing.backorder_id.id
518
 
                # corresponding draft shipment (all packing for a shipment belong to the same draft_shipment)
519
 
                draft_shipment_id = packing.backorder_id.shipment_id.id
520
 
                # for each sequence
521
 
                for from_pack in partial_datas[shipment_id][packing.id]:
522
 
                    for to_pack in partial_datas[shipment_id][packing.id][from_pack]:
523
 
                        # partial datas for one sequence of one packing
524
 
                        # could have multiple data multiple products in the same pack family
525
 
                        datas = partial_datas[shipment_id][packing.id][from_pack][to_pack]
526
 
                        # the corresponding moves
527
 
                        move_ids = move_obj.search(cr, uid, [('picking_id', '=', packing.id),
528
 
                                                             ('from_pack', '=', from_pack),
529
 
                                                             ('to_pack', '=', to_pack)], context=context)
530
 
                        
531
 
                        # compute the sequences to stay/to return to draft packing
532
 
                        stay, back_to_draft = self.compute_sequences(cr, uid, ids, context=context,
533
 
                                                                     datas=datas,
534
 
                                                                     from_pack=from_pack,
535
 
                                                                     to_pack=to_pack,)
536
 
                        
537
 
                        # we have the information concerning movements to update the packing and the draft packing
538
 
                        
539
 
                        # update the packing object, we update the existing move
540
 
                        # if needed new moves are created
541
 
                        updated = {}
542
 
                        for move in move_obj.browse(cr, uid, move_ids, context=context):
543
 
                            # update values
544
 
                            updated[move.id] = {'initial': move.product_qty, 'partial_qty': 0}
545
 
                            # loop through stay sequences
546
 
                            for seq in stay:
547
 
                                # corresponding number of packs
548
 
                                selected_number = seq[1] - seq[0] + 1
549
 
                                # quantity to return
550
 
                                new_qty = selected_number * move.qty_per_pack
551
 
                                # for both cases, we update the from/to and compute the corresponding quantity
552
 
                                # if the move has been updated already, we copy/update
553
 
                                values = {'from_pack': seq[0],
554
 
                                          'to_pack': seq[1],
555
 
                                          'product_qty': new_qty,
556
 
                                          'state': 'assigned'}
557
 
                                
558
 
                                # the original move is never modified, but canceled
559
 
                                updated[move.id]['partial_qty'] += new_qty
560
 
                                new_move_id = move_obj.copy(cr, uid, move.id, values, context=context)
561
 
                                
562
 
#                            # nothing stays
563
 
#                            if 'partial_qty' not in updated[move.id]:
564
 
#                                updated[move.id]['partial_qty'] = 0
565
 
                                    
566
 
                            # loop through back_to_draft sequences
567
 
                            for seq in back_to_draft:
568
 
                                # for each sequence we add the corresponding stock move to draft packing
569
 
                                # corresponding number of packs
570
 
                                selected_number = seq[1] - seq[0] + 1
571
 
                                # quantity to return
572
 
                                new_qty = selected_number * move.qty_per_pack
573
 
                                # values
574
 
                                location_dispatch = move.picking_id.warehouse_id.lot_dispatch_id.id
575
 
                                location_distrib = move.picking_id.warehouse_id.lot_distribution_id.id
576
 
                                values = {'from_pack': seq[0],
577
 
                                          'to_pack': seq[1],
578
 
                                          'product_qty': new_qty,
579
 
                                          'location_id': location_distrib,
580
 
                                          'location_dest_id': location_dispatch,
581
 
                                          'state': 'done'}
582
 
                                
583
 
                                # create a back move in the packing object
584
 
                                # distribution -> dispatch
585
 
                                new_back_move_id = move_obj.copy(cr, uid, move.id, values, context=context)
586
 
                                updated[move.id]['partial_qty'] += new_qty
587
 
 
588
 
                                # create the draft move
589
 
                                # dispatch -> distribution
590
 
                                # picking_id = draft_picking
591
 
                                values.update(location_id=location_dispatch,
592
 
                                              location_dest_id=location_distrib,
593
 
                                              picking_id=draft_packing_id,
594
 
                                              state='assigned')
595
 
                                new_draft_move_id = move_obj.copy(cr, uid, move.id, values, context=context)
596
 
                                
597
 
                            # quantities are right - stay + return qty = original qty
598
 
                            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)
599
 
                            # if packs are returned corresponding move is canceled
600
 
                            # cancel move or 0 qty + done ?
601
 
                            #move_obj.action_cancel(cr, uid, [move.id], context=context)
602
 
                            move_obj.write(cr, uid, [move.id], {'product_qty': 0.0, 'state': 'done', 'from_pack': 0, 'to_pack': 0,}, context=context)
603
 
            
 
742
        data_obj = self.pool.get('ir.model.data')
 
743
 
 
744
        if context is None:
 
745
            context = {}
 
746
 
 
747
        if isinstance(wizard_ids, (int, long)):
 
748
            wizard_ids = [wizard_ids]
 
749
 
 
750
        if not wizard_ids:
 
751
            raise osv.except_osv(
 
752
                _('Processing Error'),
 
753
                _('No data to process !'),
 
754
            )
 
755
 
 
756
        shipment_ids = []
 
757
 
 
758
        for wizard in proc_obj.browse(cr, uid, wizard_ids, context=context):
 
759
            shipment = wizard.shipment_id
 
760
            shipment_ids.append(shipment.id)
 
761
 
 
762
            for family in wizard.family_ids:
 
763
                draft_packing = family.draft_packing_id.backorder_id
 
764
                draft_shipment_id = draft_packing.shipment_id.id
 
765
 
 
766
                if family.return_from == 0 and family.return_to == 0:
 
767
                    continue
 
768
 
 
769
                # Search the corresponding moves
 
770
                move_ids = move_obj.search(cr, uid, [
 
771
                    ('picking_id', '=', family.draft_packing_id.id),
 
772
                    ('from_pack', '=', family.from_pack),
 
773
                    ('to_pack', '=', family.to_pack),
 
774
                ], context=context)
 
775
 
 
776
                stay = []
 
777
 
 
778
                if family.to_pack >= family.return_to:
 
779
                    if family.return_from == family.from_pack:
 
780
                        if family.return_to != family.to_pack:
 
781
                            stay.append((family.return_to + 1, family.to_pack))
 
782
                    elif family.return_to == family.to_pack:
 
783
                        # Do not start at beginning, but same end
 
784
                        stay.append((family.from_pack, family.return_from - 1))
 
785
                    else:
 
786
                        # In the middle, two now tuple in stay
 
787
                        stay.append((family.from_pack, family.return_from - 1))
 
788
                        stay.append((family.return_to + 1, family.to_pack))
 
789
 
 
790
                move_data = {}
 
791
                for move in move_obj.browse(cr, uid, move_ids, context=context):
 
792
                    move_data.setdefault(move.id, {
 
793
                        'initial': move.product_qty,
 
794
                        'partial_qty': 0,
 
795
                    })
 
796
 
 
797
                    for seq in stay:
 
798
                        # Corresponding number of packs
 
799
                        selected_number = seq[1] - seq[0] + 1
 
800
                        # Quantity to return
 
801
                        new_qty = selected_number * move.qty_per_pack
 
802
                        # For both cases, we update the from/to and compute the corresponding quantity
 
803
                        # if the move has been updated already, we copy/update
 
804
                        move_values = {
 
805
                            'from_pack': seq[0],
 
806
                            'to_pack': seq[1],
 
807
                            'product_qty': new_qty,
 
808
                            'line_number': move.line_number,
 
809
                            'state': 'assigned',
 
810
                        }
 
811
                        # The original move is never modified, but canceled
 
812
                        move_data[move.id]['partial_qty'] += new_qty
 
813
 
 
814
                        move_obj.copy(cr, uid, move.id, move_values, context=context)
 
815
 
 
816
                    # Get the back_to_draft sequences
 
817
                    selected_number = family.return_to - family.return_from + 1
 
818
                    # Quantity to return
 
819
                    new_qty = selected_number * move.qty_per_pack
 
820
                    # values
 
821
                    move_values = {
 
822
                        'from_pack': family.return_from,
 
823
                        'to_pack': family.return_to,
 
824
                        'line_number': move.line_number,
 
825
                        'product_qty': new_qty,
 
826
                        'location_id': move.picking_id.warehouse_id.lot_distribution_id.id,
 
827
                        'location_dest_id': move.picking_id.warehouse_id.lot_dispatch_id.id,
 
828
                        'state': 'done',
 
829
                    }
 
830
 
 
831
                    # Create a back move in the packing object
 
832
                    # Distribution -> Dispatch
 
833
                    context['non_stock_noupdate'] = True
 
834
                    move_obj.copy(cr, uid, move.id, move_values, context=context)
 
835
                    context['non_stock_noupdate'] = False
 
836
 
 
837
                    move_data[move.id]['partial_qty'] += new_qty
 
838
 
 
839
                    # Create the draft move
 
840
                    # Dispatch -> Distribution
 
841
                    # Picking_id = draft_picking
 
842
                    move_values.update({
 
843
                        'location_id': move.picking_id.warehouse_id.lot_dispatch_id.id,
 
844
                        'location_dest_id': move.picking_id.warehouse_id.lot_distribution_id.id,
 
845
                        'picking_id': draft_packing.id,
 
846
                        'state': 'assigned',
 
847
                    })
 
848
 
 
849
                    context['non_stock_noupdate'] = True
 
850
                    move_obj.copy(cr, uid, move.id, move_values, context=context)
 
851
                    context['non_stock_noupdate'] = False
 
852
 
 
853
                    move_values = {
 
854
                        'product_qty': 0.00,
 
855
                        'state': 'done',
 
856
                        'from_pack': 0,
 
857
                        'to_pack': 0,
 
858
                    }
 
859
                    move_obj.write(cr, uid, [move.id], move_values, context=context)
 
860
 
 
861
                for move_vals in move_data.values():
 
862
                    if round(move_vals['initial'], 14) != round(move_vals['partial_qty'], 14):
 
863
                        raise osv.except_osv(
 
864
                            _('Processing Error'),
 
865
                            _('The sum of the processed quantities is not equal to the sum of the initial quantities'),
 
866
                        )
 
867
 
604
868
            # log corresponding action
605
 
            shipment_name = self.read(cr, uid, shipment_id, ['name'], context=context)['name']
606
 
            self.log(cr, uid, shipment_id, _("Packs from the shipped Shipment (%s) have been returned to dispatch location."%shipment_name),)
607
 
            self.log(cr, uid, draft_shipment_id, _("The corresponding Draft Shipment (%s) has been updated."%packing.backorder_id.shipment_id.name),)
608
 
                            
 
869
            shipment_log_msg = _('Packs from the shipped Shipment (%s) have been returned to %s location.') % (shipment.name, _('Dispatch'))
 
870
            self.log(cr, uid, shipment.id, shipment_log_msg)
 
871
 
 
872
            draft_log_msg = _('The corresponding Draft Shipment (%s) has been updated.') % family.draft_packing_id.backorder_id.shipment_id.name
 
873
            self.log(cr, uid, draft_shipment_id, draft_log_msg)
 
874
 
609
875
        # call complete_finished on the shipment object
610
 
        # if everything is allright (all draft packing are finished) the shipment is done also 
611
 
        self.complete_finished(cr, uid, partial_datas.keys(), context=context)
612
 
        
613
 
        # TODO which behavior
614
 
        return {'type': 'ir.actions.act_window_close'}
615
 
        
 
876
        # if everything is allright (all draft packing are finished) the shipment is done also
 
877
        self.complete_finished(cr, uid, shipment_ids, context=context)
 
878
 
 
879
        view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_shipment_form')
 
880
        return {
 
881
            'name':_("Shipment"),
 
882
            'view_mode': 'form,tree',
 
883
            'view_id': [view_id and view_id[1] or False],
 
884
            'view_type': 'form',
 
885
            'res_model': 'shipment',
 
886
            'res_id': draft_shipment_id,
 
887
            'type': 'ir.actions.act_window',
 
888
            'target': 'crush',
 
889
        }
 
890
 
616
891
    def action_cancel(self, cr, uid, ids, context=None):
617
892
        '''
618
893
        cancel the shipment which is not yet shipped (packed state)
619
 
        
 
894
 
620
895
        - for each related packing object
621
896
         - trigger the cancel workflow signal
622
897
         logic is performed in the action_cancel method of stock.picking
623
898
        '''
624
899
        pick_obj = self.pool.get('stock.picking')
625
900
        wf_service = netsvc.LocalService("workflow")
626
 
        
 
901
 
627
902
        for shipment in self.browse(cr, uid, ids, context=context):
 
903
            # shipment state should be 'packed'
 
904
            assert shipment.state == 'packed', 'cannot ship a shipment which is not in correct state - packed - %s' % shipment.state
628
905
            # for each shipment
629
906
            packing_ids = pick_obj.search(cr, uid, [('shipment_id', '=', shipment.id)], context=context)
630
907
            # call cancel workflow on corresponding packing objects
632
909
                # we cancel each picking object - action_cancel is overriden at stock_picking level for stock_picking of subtype == 'packing'
633
910
                wf_service.trg_validate(uid, 'stock.picking', packing.id, 'button_cancel', cr)
634
911
            # log corresponding action
635
 
            self.log(cr, uid, shipment.id, _("The Shipment (%s) has been canceled."%shipment.name),)
636
 
            self.log(cr, uid, shipment.backshipment_id.id, _("The corresponding Draft Shipment (%s) has been updated."%shipment.backshipment_id.name),)
637
 
                
 
912
            self.log(cr, uid, shipment.id, _("The Shipment (%s) has been canceled.") % (shipment.name,))
 
913
            self.log(cr, uid, shipment.backshipment_id.id, _("The corresponding Draft Shipment (%s) has been updated.") % (shipment.backshipment_id.name,))
 
914
 
638
915
        return True
639
 
    
 
916
 
640
917
    def ship(self, cr, uid, ids, context=None):
641
918
        '''
642
919
        we ship the created shipment, the state of the shipment is changed, we do not use any wizard
646
923
        - trigger the workflow button_confirm for the new packing
647
924
        - trigger the workflow to terminate the initial packing
648
925
        - update the draft_picking_id fields of pack_families
 
926
        - update the shipment_date of the corresponding sale_order if not set yet
649
927
        '''
650
928
        pick_obj = self.pool.get('stock.picking')
651
 
        pf_obj = self.pool.get('pack.family')
652
 
        
 
929
        so_obj = self.pool.get('sale.order')
 
930
        # objects
 
931
        date_tools = self.pool.get('date.tools')
 
932
        db_datetime_format = date_tools.get_db_datetime_format(cr, uid, context=context)
 
933
 
653
934
        for shipment in self.browse(cr, uid, ids, context=context):
 
935
            # shipment state should be 'packed'
 
936
            assert shipment.state == 'packed', 'cannot ship a shipment which is not in correct state - packed - %s' % shipment.state
654
937
            # the state does not need to be updated - function
 
938
            # update actual ship date (shipment_actual_date) to today + time
 
939
            today = time.strftime(db_datetime_format)
 
940
            shipment.write({'shipment_actual_date': today, })
655
941
            # corresponding packing objects
656
942
            packing_ids = pick_obj.search(cr, uid, [('shipment_id', '=', shipment.id)], context=context)
657
 
            
 
943
 
658
944
            for packing in pick_obj.browse(cr, uid, packing_ids, context=context):
659
945
                assert packing.subtype == 'packing'
660
946
                # update the packing object for the same reason
661
947
                # - an integrity check at _get_vals level of shipment states that all packing linked to a shipment must have the same state
662
948
                # we therefore modify it before the copy, otherwise new (assigned) and old (done) are linked to the same shipment
663
949
                # -> integrity check has been removed
664
 
                pick_obj.write(cr, uid, [packing.id], {'shipment_id': False,}, context=context)
 
950
                pick_obj.write(cr, uid, [packing.id], {'shipment_id': False, }, context=context)
665
951
                # copy each packing
666
952
                new_packing_id = pick_obj.copy(cr, uid, packing.id, {'name': packing.name,
667
953
                                                                     'first_shipment_packing_id': packing.id,
668
 
                                                                     'shipment_id': shipment.id,}, context=dict(context, keep_prodlot=True, allow_copy=True,))
 
954
                                                                     # UF-1617: keepLineNumber must be set so that all line numbers are passed correctly when updating the corresponding IN
 
955
                                                                     'shipment_id': shipment.id, }, context=dict(context, keepLineNumber=True, keep_prodlot=True, allow_copy=True,))
669
956
                pick_obj.write(cr, uid, [new_packing_id], {'origin': packing.origin}, context=context)
670
957
                new_packing = pick_obj.browse(cr, uid, new_packing_id, context=context)
 
958
                # update the shipment_date of the corresponding sale order if the date is not set yet - with current date
 
959
                if new_packing.sale_id and not new_packing.sale_id.shipment_date:
 
960
                    # get the date format
 
961
                    date_tools = self.pool.get('date.tools')
 
962
                    date_format = date_tools.get_date_format(cr, uid, context=context)
 
963
                    db_date_format = date_tools.get_db_date_format(cr, uid, context=context)
 
964
                    today = time.strftime(date_format)
 
965
                    today_db = time.strftime(db_date_format)
 
966
                    so_obj.write(cr, uid, [new_packing.sale_id.id], {'shipment_date': today_db, }, context=context)
 
967
                    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))
 
968
 
671
969
                # update locations of stock moves
672
970
                for move in new_packing.move_lines:
673
971
                    move.write({'location_id': new_packing.warehouse_id.lot_distribution_id.id,
674
972
                                'location_dest_id': new_packing.warehouse_id.lot_output_id.id}, context=context)
675
 
                
676
 
                # update the pack families
677
 
#                pf_ids = pf_obj.search(cr, uid, [('draft_packing_id', '=', packing.id)], context=context)
678
 
#                pf_obj.write(cr, uid, pf_ids, {'draft_packing_id': new_packing.id}, context=context)
679
 
                
680
 
                # update old moves - unlink so we don't see old moves when we open the pack families
681
 
#                for move in packing.move_lines:
682
 
#                    move.write({'pack_family_id': False}, context=context)
683
 
                
 
973
 
684
974
                wf_service = netsvc.LocalService("workflow")
685
975
                wf_service.trg_validate(uid, 'stock.picking', new_packing_id, 'button_confirm', cr)
686
976
                # simulate check assign button, as stock move must be available
688
978
                # trigger standard workflow
689
979
                pick_obj.action_move(cr, uid, [packing.id])
690
980
                wf_service.trg_validate(uid, 'stock.picking', packing.id, 'button_done', cr)
691
 
                
 
981
 
692
982
            # log the ship action
693
 
            self.log(cr, uid, shipment.id, _('The Shipment %s has been shipped.'%shipment.name))
694
 
    
 
983
            self.log(cr, uid, shipment.id, _('The Shipment %s has been shipped.') % (shipment.name,))
 
984
 
695
985
        # TODO which behavior
696
986
        return True
697
 
    
 
987
 
698
988
    def complete_finished(self, cr, uid, ids, context=None):
699
989
        '''
700
990
        - check all draft packing corresponding to this shipment
705
995
        '''
706
996
        pick_obj = self.pool.get('stock.picking')
707
997
        wf_service = netsvc.LocalService("workflow")
708
 
        
 
998
 
709
999
        for shipment_base in self.browse(cr, uid, ids, context=context):
710
1000
            # the shipment which will be treated
711
1001
            shipment = shipment_base
712
 
            
 
1002
 
713
1003
            if shipment.state not in ('draft',):
714
1004
                # it's not a draft shipment, check all corresponding packing, trg.write them
715
 
                packing_ids = pick_obj.search(cr, uid, [('shipment_id', '=', shipment.id),], context=context)
 
1005
                packing_ids = pick_obj.search(cr, uid, [('shipment_id', '=', shipment.id), ], context=context)
716
1006
                for packing_id in packing_ids:
717
1007
                    wf_service.trg_write(uid, 'stock.picking', packing_id, cr)
718
 
                
 
1008
 
719
1009
                # this shipment is possibly finished, we now check the corresponding draft shipment
720
1010
                # this will possibly validate the draft shipment, if everything is finished and corresponding draft picking
721
1011
                shipment = shipment.backshipment_id
722
 
                
 
1012
 
723
1013
            # draft packing for this shipment - some draft packing can already be done for this shipment, so we filter according to state
724
 
            draft_packing_ids = pick_obj.search(cr, uid, [('shipment_id', '=', shipment.id), ('state', '=', 'draft'),], context=context)
 
1014
            draft_packing_ids = pick_obj.search(cr, uid, [('shipment_id', '=', shipment.id), ('state', '=', 'draft'), ], context=context)
725
1015
            for draft_packing in pick_obj.browse(cr, uid, draft_packing_ids, context=context):
726
 
                assert draft_packing.subtype == 'packing', 'draft packing which is not packing subtype - %s'%draft_packing.subtype
727
 
                assert draft_packing.state == 'draft', 'draft packing which is not draft state - %s'%draft_packing.state
 
1016
                assert draft_packing.subtype == 'packing', 'draft packing which is not packing subtype - %s' % draft_packing.subtype
 
1017
                assert draft_packing.state == 'draft', 'draft packing which is not draft state - %s' % draft_packing.state
728
1018
                # we check if the corresponding draft packing can be moved to done.
729
1019
                # if all packing with backorder_id equal to draft are done or canceled
730
1020
                # and the quantity for each stock move (state != done) of the draft packing is equal to zero
731
 
                
 
1021
 
732
1022
                # we first check the stock moves quantities of the draft packing
733
1023
                # we can have done moves when some packs are returned
734
1024
                treat_draft = True
739
1029
                        elif move.from_pack or move.to_pack:
740
1030
                            # qty = 0, from/to pack should have been set to zero
741
1031
                            assert False, 'stock moves with 0 quantity but part of pack family sequence'
742
 
                
 
1032
 
743
1033
                # check if ongoing packing are present, if present, we do not validate the draft one, the shipping is not finished
744
1034
                if treat_draft:
745
 
                    linked_packing_ids = pick_obj.search(cr, uid, [('backorder_id', '=', draft_packing.id)], context=context)
746
 
                    for linked_packing in pick_obj.browse(cr, uid, linked_packing_ids, context=context):
747
 
                        if linked_packing.state not in ('done','cancel'):
748
 
                            treat_draft = False
749
 
                
 
1035
                    linked_packing_ids = pick_obj.search(cr, uid, [('backorder_id', '=', draft_packing.id),
 
1036
                                                                   ('state', 'not in', ['done', 'cancel'])], context=context)
 
1037
                    if linked_packing_ids:
 
1038
                        treat_draft = False
 
1039
 
750
1040
                if treat_draft:
751
1041
                    # trigger the workflow for draft_picking
752
1042
                    # confirm the new picking ticket
759
1049
                    # ask for draft picking validation, depending on picking completion
760
1050
                    # if picking ticket is not completed, the validation will not complete
761
1051
                    draft_packing.previous_step_id.previous_step_id.backorder_id.validate(context=context)
762
 
            
 
1052
 
 
1053
                    # UF-1617: set the flag to PPL to indicate that the SHIP has been done, for synchronisation purpose
 
1054
#                    if draft_packing.previous_step_id and draft_packing.previous_step_id.id:
 
1055
#                        cr.execute('update stock_picking set already_shipped=\'t\' where id=%s' %draft_packing.previous_step_id.id)
 
1056
 
763
1057
            # all draft packing are validated (done state) - the state of shipment is automatically updated -> function
764
1058
        return True
765
 
        
 
1059
 
 
1060
    def shipment_create_invoice(self, cr, uid, ids, context=None):
 
1061
        '''
 
1062
        Create invoices for validated shipment
 
1063
        '''
 
1064
        invoice_obj = self.pool.get('account.invoice')
 
1065
        line_obj = self.pool.get('account.invoice.line')
 
1066
        partner_obj = self.pool.get('res.partner')
 
1067
        distrib_obj = self.pool.get('analytic.distribution')
 
1068
        sale_line_obj = self.pool.get('sale.order.line')
 
1069
        sale_obj = self.pool.get('sale.order')
 
1070
        company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
 
1071
 
 
1072
        if not context:
 
1073
            context = {}
 
1074
 
 
1075
        if isinstance(ids, (int, long)):
 
1076
            ids = [ids]
 
1077
 
 
1078
        for shipment in self.browse(cr, uid, ids, context=context):
 
1079
            make_invoice = False
 
1080
            for pack in shipment.pack_family_memory_ids:
 
1081
                for move in pack.move_lines:
 
1082
                    if move.state != 'cancel' and (not move.sale_line_id or move.sale_line_id.order_id.order_policy == 'picking'):
 
1083
                        make_invoice = True
 
1084
 
 
1085
            if not make_invoice:
 
1086
                continue
 
1087
 
 
1088
            payment_term_id = False
 
1089
            partner = shipment.partner_id2
 
1090
            if not partner:
 
1091
                raise osv.except_osv(_('Error, no partner !'),
 
1092
                    _('Please put a partner on the shipment if you want to generate invoice.'))
 
1093
 
 
1094
            inv_type = 'out_invoice'
 
1095
 
 
1096
            if inv_type in ('out_invoice', 'out_refund'):
 
1097
                account_id = partner.property_account_receivable.id
 
1098
                payment_term_id = partner.property_payment_term and partner.property_payment_term.id or False
 
1099
            else:
 
1100
                account_id = partner.property_account_payable.id
 
1101
 
 
1102
            addresses = partner_obj.address_get(cr, uid, [partner.id], ['contact', 'invoice'])
 
1103
            today = time.strftime('%Y-%m-%d', time.localtime())
 
1104
 
 
1105
            invoice_vals = {
 
1106
                    'name': shipment.name,
 
1107
                    'origin': shipment.name or '',
 
1108
                    'type': inv_type,
 
1109
                    'account_id': account_id,
 
1110
                    'partner_id': partner.id,
 
1111
                    'address_invoice_id': addresses['invoice'],
 
1112
                    'address_contact_id': addresses['contact'],
 
1113
                    'payment_term': payment_term_id,
 
1114
                    'fiscal_position': partner.property_account_position.id,
 
1115
                    'date_invoice': context.get('date_inv', False) or today,
 
1116
                    'user_id':uid,
 
1117
                }
 
1118
 
 
1119
            cur_id = shipment.pack_family_memory_ids[0].currency_id.id
 
1120
            if cur_id:
 
1121
                invoice_vals['currency_id'] = cur_id
 
1122
            # Journal type
 
1123
            journal_type = 'sale'
 
1124
            # Disturb journal for invoice only on intermission partner type
 
1125
            if shipment.partner_id2.partner_type == 'intermission':
 
1126
                if not company.intermission_default_counterpart or not company.intermission_default_counterpart.id:
 
1127
                    raise osv.except_osv(_('Error'), _('Please configure a default intermission account in Company configuration.'))
 
1128
                invoice_vals['is_intermission'] = True
 
1129
                invoice_vals['account_id'] = company.intermission_default_counterpart.id
 
1130
                journal_type = 'intermission'
 
1131
            journal_ids = self.pool.get('account.journal').search(cr, uid, [('type', '=', journal_type),
 
1132
                                                                            ('is_current_instance', '=', True)])
 
1133
            if not journal_ids:
 
1134
                raise osv.except_osv(_('Warning'), _('No %s journal found!') % (journal_type,))
 
1135
            invoice_vals['journal_id'] = journal_ids[0]
 
1136
 
 
1137
            invoice_id = invoice_obj.create(cr, uid, invoice_vals,
 
1138
                        context=context)
 
1139
 
 
1140
            # Change currency for the intermission invoice
 
1141
            if shipment.partner_id2.partner_type == 'intermission':
 
1142
                company_currency = company.currency_id and company.currency_id.id or False
 
1143
                if not company_currency:
 
1144
                    raise osv.except_osv(_('Warning'), _('No company currency found!'))
 
1145
                wiz_account_change = self.pool.get('account.change.currency').create(cr, uid, {'currency_id': company_currency}, context=context)
 
1146
                self.pool.get('account.change.currency').change_currency(cr, uid, [wiz_account_change], context={'active_id': invoice_id})
 
1147
 
 
1148
            # Link the invoice to the shipment
 
1149
            self.write(cr, uid, [shipment.id], {'invoice_id': invoice_id}, context=context)
 
1150
 
 
1151
            # For each stock moves, create an invoice line
 
1152
            for pack in shipment.pack_family_memory_ids:
 
1153
                for move in pack.move_lines:
 
1154
                    if move.state == 'cancel':
 
1155
                        continue
 
1156
 
 
1157
                    if move.sale_line_id and move.sale_line_id.order_id.order_policy != 'picking':
 
1158
                        continue
 
1159
 
 
1160
                    origin = move.picking_id.name or ''
 
1161
                    if move.picking_id.origin:
 
1162
                        origin += ':' + move.picking_id.origin
 
1163
 
 
1164
                    if inv_type in ('out_invoice', 'out_refund'):
 
1165
                        account_id = move.product_id.product_tmpl_id.\
 
1166
                                property_account_income.id
 
1167
                        if not account_id:
 
1168
                            account_id = move.product_id.categ_id.\
 
1169
                                    property_account_income_categ.id
 
1170
                    else:
 
1171
                        account_id = move.product_id.product_tmpl_id.\
 
1172
                                property_account_expense.id
 
1173
                        if not account_id:
 
1174
                            account_id = move.product_id.categ_id.\
 
1175
                                    property_account_expense_categ.id
 
1176
 
 
1177
                    # Compute unit price from FO line if the move is linked to
 
1178
                    price_unit = move.product_id.list_price
 
1179
                    if move.sale_line_id and move.sale_line_id.product_id.id == move.product_id.id:
 
1180
                        uos_id = move.product_id.uos_id and move.product_id.uos_id.id or False
 
1181
                        price = move.sale_line_id.price_unit
 
1182
                        price_unit = self.pool.get('product.uom')._compute_price(cr, uid, move.sale_line_id.product_uom.id, price, move.product_uom.id)
 
1183
 
 
1184
                    # Get discount from FO line
 
1185
                    discount = 0.00
 
1186
                    if move.sale_line_id and move.sale_line_id.product_id.id == move.product_id.id:
 
1187
                        discount = move.sale_line_id.discount
 
1188
 
 
1189
                    # Get taxes from FO line
 
1190
                    taxes = move.product_id.taxes_id
 
1191
                    if move.sale_line_id and move.sale_line_id.product_id.id == move.product_id.id:
 
1192
                        taxes = [x.id for x in move.sale_line_id.tax_id]
 
1193
 
 
1194
                    if shipment.partner_id2:
 
1195
                        tax_ids = self.pool.get('account.fiscal.position').map_tax(cr, uid, shipment.partner_id2.property_account_position, taxes)
 
1196
                    else:
 
1197
                        tax_ids = map(lambda x: x.id, taxes)
 
1198
 
 
1199
                    distrib_id = False
 
1200
                    if move.sale_line_id:
 
1201
                        sol_ana_dist_id = move.sale_line_id.analytic_distribution_id or move.sale_line_id.order_id.analytic_distribution_id
 
1202
                        if sol_ana_dist_id:
 
1203
                            distrib_id = distrib_obj.copy(cr, uid, sol_ana_dist_id.id, context=context)
 
1204
 
 
1205
                    # set UoS if it's a sale and the picking doesn't have one
 
1206
                    uos_id = move.product_uos and move.product_uos.id or False
 
1207
                    if not uos_id and inv_type in ('out_invoice', 'out_refund'):
 
1208
                        uos_id = move.product_uom.id
 
1209
                    account_id = self.pool.get('account.fiscal.position').map_account(cr, uid, partner.property_account_position, account_id)
 
1210
 
 
1211
                    line_id = line_obj.create(cr, uid, {'name': move.name,
 
1212
                                                        'origin': origin,
 
1213
                                                        'invoice_id': invoice_id,
 
1214
                                                        'uos_id': uos_id,
 
1215
                                                        'product_id': move.product_id.id,
 
1216
                                                        'account_id': account_id,
 
1217
                                                        'price_unit': price_unit,
 
1218
                                                        'discount': discount,
 
1219
                                                        'quantity': move.product_qty or move.product_uos_qty,
 
1220
                                                        'invoice_line_tax_id': [(6, 0, tax_ids)],
 
1221
                                                        'analytic_distribution_id': distrib_id,
 
1222
                                                       }, context=context)
 
1223
 
 
1224
                    self.pool.get('shipment').write(cr, uid, [shipment.id], {'invoice_id': invoice_id}, context=context)
 
1225
                    if move.sale_line_id:
 
1226
                        sale_obj.write(cr, uid, [move.sale_line_id.order_id.id], {'invoice_ids': [(4, invoice_id)], })
 
1227
                        sale_line_obj.write(cr, uid, [move.sale_line_id.id], {'invoiced': True,
 
1228
                                                                              'invoice_lines': [(4, line_id)], })
 
1229
 
 
1230
        return True
 
1231
 
766
1232
    def validate(self, cr, uid, ids, context=None):
767
1233
        '''
768
1234
        validate the shipment
769
 
        
 
1235
 
770
1236
        change the state to Done for the corresponding packing
771
1237
        - validate the workflow for all the packings
772
1238
        '''
773
1239
        pick_obj = self.pool.get('stock.picking')
774
1240
        wf_service = netsvc.LocalService("workflow")
775
 
        
 
1241
 
776
1242
        for shipment in self.browse(cr, uid, ids, context=context):
777
1243
            # validate should only be called on shipped shipments
778
1244
            assert shipment.state in ('shipped',), 'shipment state is not shipped'
779
1245
            # corresponding packing objects - only the distribution -> customer ones
780
 
            packing_ids = pick_obj.search(cr, uid, [('shipment_id', '=', shipment.id),], context=context)
781
 
            
 
1246
            # we have to discard picking object with state done, because when we return from shipment
 
1247
            # all object of a given picking object, he is set to Done and still belong to the same shipment_id
 
1248
            # another possibility would be to unlink the picking object from the shipment, set shipment_id to False
 
1249
            # but in this case the returned pack families would not be displayed anymore in the shipment
 
1250
            packing_ids = pick_obj.search(cr, uid, [('shipment_id', '=', shipment.id), ('state', '!=', 'done'), ], context=context)
 
1251
 
782
1252
            for packing in pick_obj.browse(cr, uid, packing_ids, context=context):
783
1253
                assert packing.subtype == 'packing' and packing.state == 'assigned'
784
1254
                # trigger standard workflow
785
1255
                pick_obj.action_move(cr, uid, [packing.id])
786
1256
                wf_service.trg_validate(uid, 'stock.picking', packing.id, 'button_done', cr)
787
 
                
 
1257
                pick_obj._hook_create_sync_messages(cr, uid, packing.id, context)  # UF-1617: Create the sync message for batch and asset before shipping
 
1258
 
 
1259
                # UF-1617: set the flag to this packing object to indicate that the SHIP has been done, for synchronisation purpose
 
1260
                cr.execute('update stock_picking set already_shipped=\'t\' where id=%s' % packing.id)
 
1261
 
 
1262
 
 
1263
            # Create automatically the invoice
 
1264
            self.shipment_create_invoice(cr, uid, shipment.id, context=context)
 
1265
 
788
1266
            # log validate action
789
 
            self.log(cr, uid, shipment.id, _('The Shipment %s has been validated.'%shipment.name))
790
 
            
791
 
        result = self.complete_finished(cr, uid, ids, context=context)
792
 
        return True
793
 
        
 
1267
            self.log(cr, uid, shipment.id, _('The Shipment %s has been closed.') % (shipment.name,))
 
1268
 
 
1269
        self.complete_finished(cr, uid, ids, context=context)
 
1270
        return True
 
1271
 
 
1272
    def set_delivered(self, cr, uid, ids, context=None):
 
1273
        '''
 
1274
        set the delivered flag
 
1275
        '''
 
1276
        # objects
 
1277
        pick_obj = self.pool.get('stock.picking')
 
1278
        for shipment in self.browse(cr, uid, ids, context=context):
 
1279
            # validate should only be called on shipped shipments
 
1280
            assert shipment.state in ['done'], 'shipment state is not shipped'
 
1281
            # gather the corresponding packing and trigger the corresponding function
 
1282
            packing_ids = pick_obj.search(cr, uid, [('shipment_id', '=', shipment.id), ('state', '=', 'done')], context=context)
 
1283
            # set delivered all packings
 
1284
            pick_obj.set_delivered(cr, uid, packing_ids, context=context)
 
1285
 
 
1286
        return True
 
1287
 
794
1288
shipment()
795
1289
 
796
1290
 
797
 
class pack_family_memory(osv.osv_memory):
798
 
    '''
799
 
    dynamic memory object for pack families
800
 
    '''
801
 
    _name = 'pack.family.memory'
802
 
    
803
 
    def _vals_get(self, cr, uid, ids, fields, arg, context=None):
804
 
        '''
805
 
        get functional values
806
 
        '''
807
 
        result = {}
808
 
        for pf_memory in self.browse(cr, uid, ids, context=context):
809
 
            values = {'move_lines': [],
810
 
                      'state': 'draft',
811
 
                      'location_id': False,
812
 
                      'location_dest_id': False,
813
 
                      'total_amount': 0.0,
814
 
                      'amount': 0.0,
815
 
                      'currency_id': False,
816
 
                      'num_of_packs': 0,
817
 
                      'total_weight': 0.0,
818
 
                      }
819
 
            result[pf_memory.id] = values
820
 
            # pack family related fields
821
 
            if pf_memory.to_pack == 0:
822
 
                num_of_packs = 0
823
 
            else:
824
 
                num_of_packs = pf_memory.to_pack - pf_memory.from_pack + 1
825
 
            values['num_of_packs'] = num_of_packs
826
 
            values['total_weight'] = pf_memory.weight * num_of_packs
827
 
            
828
 
            # moves related fields
829
 
            for move in pf_memory.draft_packing_id.move_lines:
830
 
                if move.from_pack == pf_memory.from_pack:
831
 
                    if move.to_pack == pf_memory.to_pack:
832
 
                        # this move is in the good packing object and corresponds to this pack family
833
 
                        # we add it to the stock move list
834
 
                        values['move_lines'].append(move.id)
835
 
                        values['state'] = move.state
836
 
                        values['location_id'] = move.location_id.id
837
 
                        values['location_dest_id'] = move.location_dest_id.id
838
 
                        values['total_amount'] += move.total_amount
839
 
                        values['amount'] += move.amount
840
 
                        values['currency_id'] = move.currency_id and move.currency_id.id or False
841
 
                    else:
842
 
                        raise osv.except_osv(_('Error !'), _('Integrity check failed! Pack Family and Stock Moves from/to do not match.'))
843
 
                    
844
 
        return result
845
 
    
846
 
    _columns = {'name': fields.char(string='Reference', size=1024),
847
 
                'shipment_id': fields.many2one('shipment', string='Shipment'),
848
 
                'draft_packing_id': fields.many2one('stock.picking', string="Draft Packing Ref"),
849
 
                'sale_order_id': fields.many2one('sale.order', string="Sale Order Ref"),
850
 
                'ppl_id': fields.many2one('stock.picking', string="PPL Ref"),
851
 
                'from_pack': fields.integer(string='From p.'),
852
 
                'to_pack': fields.integer(string='To p.'),
853
 
                'pack_type': fields.many2one('pack.type', string='Pack Type'),
854
 
                'length' : fields.float(digits=(16,2), string='Length [cm]'),
855
 
                'width' : fields.float(digits=(16,2), string='Width [cm]'),
856
 
                'height' : fields.float(digits=(16,2), string='Height [cm]'),
857
 
                'weight' : fields.float(digits=(16,2), string='Weight p.p [kg]'),
858
 
                # functions
859
 
                'move_lines': fields.function(_vals_get, method=True, type='one2many', relation='stock.move', string='Stock Moves', multi='get_vals',),
860
 
                'state': fields.function(_vals_get, method=True, type='selection', selection=[('draft', 'Draft'),
861
 
                                                                                              ('assigned', 'Available'),
862
 
                                                                                              ('stock_return', 'Returned to Stock'),
863
 
                                                                                              ('ship_return', 'Returned from Shipment'),
864
 
                                                                                              ('cancel', 'Canceled'),
865
 
                                                                                              ('done', 'Done'),], string='State', multi='get_vals',),
866
 
                'location_id': fields.function(_vals_get, method=True, type='many2one', relation='stock.location', string='Src Loc.', multi='get_vals',),
867
 
                'location_dest_id': fields.function(_vals_get, method=True, type='many2one', relation='stock.location', string='Dest. Loc.', multi='get_vals',),
868
 
                'total_amount': fields.function(_vals_get, method=True, type='float', string='Total Amount', multi='get_vals',),
869
 
                'amount': fields.function(_vals_get, method=True, type='float', string='Pack Amount', multi='get_vals',),
870
 
                'currency_id': fields.function(_vals_get, method=True, type='many2one', relation='res.currency', string='Currency', multi='get_vals',),
871
 
                'num_of_packs': fields.function(_vals_get, method=True, type='integer', string='#Packs', multi='get_vals',),
872
 
                'total_weight': fields.function(_vals_get, method=True, type='float', string='Total Weight[kg]', multi='get_vals',),
 
1291
class shipment_additionalitems(osv.osv):
 
1292
    _name = "shipment.additionalitems"
 
1293
    _description = "Additional Items"
 
1294
 
 
1295
    _columns = {'name': fields.char(string='Additional Item', size=1024, required=True),
 
1296
                'shipment_id': fields.many2one('shipment', string='Shipment', readonly=True, on_delete='cascade'),
 
1297
                'picking_id': fields.many2one('stock.picking', string='Picking', readonly=True, on_delete='cascade'),
 
1298
                'quantity': fields.float(digits=(16, 2), string='Quantity', required=True),
 
1299
                'uom': fields.many2one('product.uom', string='UOM', required=True),
 
1300
                'comment': fields.char(string='Comment', size=1024),
 
1301
                'volume': fields.float(digits=(16, 2), string='Volume[dm³]'),
 
1302
                'weight': fields.float(digits=(16, 2), string='Weight[kg]', required=True),
873
1303
                }
874
 
    
875
 
    _defaults = {'shipment_id': False,
876
 
                 'draft_packing_id': False,
877
 
                 }
878
 
    
879
 
pack_family_memory()
880
 
 
881
 
 
882
 
class shipment(osv.osv):
 
1304
 
 
1305
shipment_additionalitems()
 
1306
 
 
1307
 
 
1308
class shipment2(osv.osv):
883
1309
    '''
884
1310
    add pack_family_ids
885
1311
    '''
886
1312
    _inherit = 'shipment'
887
 
    
888
 
    def _vals_get_2(self, cr, uid, ids, fields, arg, context=None):
889
 
        '''
890
 
        get functional values
891
 
        '''
892
 
        picking_obj = self.pool.get('stock.picking')
893
 
        
894
 
        result = {}
895
 
        for shipment in self.browse(cr, uid, ids, context=context):
896
 
            values = {'pack_family_memory_ids':[],
897
 
                      }
898
 
            result[shipment.id] = values
899
 
            # look for all corresponding packing
900
 
            packing_ids = picking_obj.search(cr, uid, [('shipment_id', '=', shipment.id),], context=context)
901
 
            # get the corresponding data
902
 
            data = picking_obj.generate_data_from_picking_for_pack_family(cr, uid, packing_ids, context=context)
903
 
            # create a memory family
904
 
            created_ids = picking_obj.create_pack_families_memory_from_data(cr, uid, data, shipment.id, context=context)
905
 
            values['pack_family_memory_ids'].extend(created_ids)
906
 
            
907
 
        return result
908
 
    
909
 
    _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',),
910
 
                }
911
 
 
912
 
shipment()
 
1313
 
 
1314
    def on_change_partner(self, cr, uid, ids, partner_id, address_id, context=None):
 
1315
        '''
 
1316
        Change the delivery address when the partner change.
 
1317
        '''
 
1318
        v = {}
 
1319
        d = {}
 
1320
 
 
1321
        if not partner_id:
 
1322
            v.update({'address_id': False})
 
1323
        else:
 
1324
            d.update({'address_id': [('partner_id', '=', partner_id)]})
 
1325
 
 
1326
 
 
1327
        if address_id:
 
1328
            addr = self.pool.get('res.partner.address').browse(cr, uid, address_id, context=context)
 
1329
 
 
1330
        if not address_id or addr.partner_id.id != partner_id:
 
1331
            addr = self.pool.get('res.partner').address_get(cr, uid, partner_id, ['delivery', 'default'])
 
1332
            if not addr.get('delivery'):
 
1333
                addr = addr.get('default')
 
1334
            else:
 
1335
                addr = addr.get('delivery')
 
1336
 
 
1337
            v.update({'address_id': addr})
 
1338
 
 
1339
 
 
1340
        return {'value': v,
 
1341
                'domain': d}
 
1342
 
 
1343
    _columns = {
 
1344
        'pack_family_memory_ids': fields.one2many('pack.family.memory', 'shipment_id', string='Memory Families'),
 
1345
    }
 
1346
 
 
1347
shipment2()
913
1348
 
914
1349
 
915
1350
class ppl_customize_label(osv.osv):
917
1352
    label preferences
918
1353
    '''
919
1354
    _name = 'ppl.customize.label'
920
 
    
 
1355
 
921
1356
    def init(self, cr):
922
1357
        """
923
1358
        Load msf_outgoing_data.xml before self
925
1360
        if hasattr(super(ppl_customize_label, self), 'init'):
926
1361
            super(ppl_customize_label, self).init(cr)
927
1362
 
928
 
        mod_obj = self.pool.get('ir.module.module')
929
1363
        logging.getLogger('init').info('HOOK: module msf_outgoing: loading data/msf_outgoing_data.xml')
930
1364
        pathname = path.join('msf_outgoing', 'data/msf_outgoing_data.xml')
931
 
        file = tools.file_open(pathname)
932
 
        tools.convert_xml_import(cr, 'msf_outgoing', file, {}, mode='init', noupdate=False)
 
1365
        file_to_open = tools.file_open(pathname)
 
1366
        tools.convert_xml_import(cr, 'msf_outgoing', file_to_open, {}, mode='init', noupdate=False)
933
1367
 
934
1368
    _columns = {'name': fields.char(string='Name', size=1024,),
935
1369
                'notes': fields.text(string='Notes'),
936
 
                #'packing_list_reference': fields.boolean(string='Packing List Reference'),
 
1370
                # 'packing_list_reference': fields.boolean(string='Packing List Reference'),
937
1371
                'pre_packing_list_reference': fields.boolean(string='Pre-Packing List Reference'),
938
1372
                'destination_partner': fields.boolean(string='Destination Partner'),
939
1373
                'destination_address': fields.boolean(string='Destination Address'),
940
1374
                'requestor_order_reference': fields.boolean(string='Requestor Order Reference'),
941
1375
                'weight': fields.boolean(string='Weight'),
942
 
                #'shipment_reference': fields.boolean(string='Shipment Reference'),
 
1376
                # 'shipment_reference': fields.boolean(string='Shipment Reference'),
943
1377
                'packing_parcel_number': fields.boolean(string='Packing Parcel Number'),
944
 
                #'expedition_parcel_number': fields.boolean(string='Expedition Parcel Number'),
 
1378
                # 'expedition_parcel_number': fields.boolean(string='Expedition Parcel Number'),
945
1379
                'specific_information': fields.boolean(string='Specific Information'),
946
1380
                'logo': fields.boolean(string='Company Logo'),
947
1381
                }
948
 
    
 
1382
 
949
1383
    _defaults = {'name': 'My Customization',
950
1384
                'notes': '',
951
 
                #'packing_list_reference': True,
 
1385
                # 'packing_list_reference': True,
952
1386
                'pre_packing_list_reference': True,
953
1387
                'destination_partner': True,
954
1388
                'destination_address': True,
955
1389
                'requestor_order_reference': True,
956
1390
                'weight': True,
957
 
                #'shipment_reference': True,
 
1391
                # 'shipment_reference': True,
958
1392
                'packing_parcel_number': True,
959
 
                #'expedition_parcel_number': True,
 
1393
                # 'expedition_parcel_number': True,
960
1394
                'specific_information': True,
961
1395
                'logo': True,
962
1396
                }
973
1407
    '''
974
1408
    _inherit = 'stock.picking'
975
1409
    _name = 'stock.picking'
976
 
   
 
1410
 
 
1411
    def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
 
1412
        '''
 
1413
        Set the appropriate search view according to the context
 
1414
        '''
 
1415
        if not context:
 
1416
            context = {}
 
1417
 
 
1418
        if not view_id and context.get('wh_dashboard') and view_type == 'search':
 
1419
            try:
 
1420
                if context.get('pick_type') == 'incoming':
 
1421
                    view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', 'view_picking_in_search')[1]
 
1422
                elif context.get('pick_type') == 'delivery':
 
1423
                    view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', 'view_picking_out_search')[1]
 
1424
                elif context.get('pick_type') == 'picking_ticket':
 
1425
                    view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_search')[1]
 
1426
                elif context.get('pick_type') == 'pack':
 
1427
                    view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_outgoing', 'view_ppl_search')[1]
 
1428
            except ValueError:
 
1429
                pass
 
1430
        if not view_id and context.get('pick_type') == 'incoming' and view_type == 'tree':
 
1431
            try:
 
1432
                view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', 'view_picking_in_tree')[1]
 
1433
            except ValueError:
 
1434
                pass
 
1435
 
 
1436
        return super(stock_picking, self).fields_view_get(cr, uid, view_id, view_type, context=context, toolbar=toolbar, submenu=submenu)
 
1437
 
 
1438
    def unlink(self, cr, uid, ids, context=None):
 
1439
        '''
 
1440
        unlink test for draft
 
1441
        '''
 
1442
        datas = self.read(cr, uid, ids, ['state', 'type', 'subtype'], context=context)
 
1443
        if [data for data in datas if data['state'] != 'draft']:
 
1444
            raise osv.except_osv(_('Warning !'), _('Only draft picking tickets can be deleted.'))
 
1445
        ids_picking_draft = [data['id'] for data in datas if data['subtype'] == 'picking' and data['type'] == 'out' and data['state'] == 'draft']
 
1446
        if ids_picking_draft:
 
1447
            data = self.has_picking_ticket_in_progress(cr, uid, ids, context=context)
 
1448
            if [x for x in data.values() if x]:
 
1449
                raise osv.except_osv(_('Warning !'), _('Some Picking Tickets are in progress. Return products to stock from ppl and shipment and try again.'))
 
1450
 
 
1451
        return super(stock_picking, self).unlink(cr, uid, ids, context=context)
 
1452
 
977
1453
    def _hook_picking_get_view(self, cr, uid, ids, context=None, *args, **kwargs):
978
1454
        pick = kwargs['pick']
979
1455
        obj_data = self.pool.get('ir.model.data')
983
1459
                     'packing': ('msf_outgoing', 'view_packing_form'),
984
1460
                     }
985
1461
        if pick.type == 'out':
986
 
            module, view = view_list.get(pick.subtype,('msf_outgoing', 'view_picking_ticket_form'))
 
1462
            context.update({'picking_type': pick.subtype == 'standard' and 'delivery_order' or 'picking_ticket'})
 
1463
            module, view = view_list.get(pick.subtype, ('msf_outgoing', 'view_picking_ticket_form'))
987
1464
            try:
988
1465
                return obj_data.get_object_reference(cr, uid, module, view)
989
 
            except ValueError, e:
 
1466
            except ValueError:
990
1467
                pass
991
 
        
992
 
        module, view = view_list.get(pick.type,('stock', 'view_picking_form'))
993
 
        return self.pool.get('ir.model.data').get_object_reference(cr, uid, module, view)
 
1468
        elif pick.type == 'in':
 
1469
            context.update({'picking_type': 'incoming_shipment'})
 
1470
        else:
 
1471
            context.update({'picking_type': 'internal_move'})
 
1472
 
 
1473
        return super(stock_picking, self)._hook_picking_get_view(cr, uid, ids, context=context, *args, **kwargs)
 
1474
 
 
1475
    def _hook_custom_log(self, cr, uid, ids, context=None, *args, **kwargs):
 
1476
        '''
 
1477
        hook from stock>stock.py>log_picking
 
1478
        update the domain and other values if necessary in the log creation
 
1479
        '''
 
1480
        result = super(stock_picking, self)._hook_custom_log(cr, uid, ids, context=context, *args, **kwargs)
 
1481
        pick_obj = self.pool.get('stock.picking')
 
1482
        pick = kwargs['pick']
 
1483
        message = kwargs['message']
 
1484
        if pick.type and pick.subtype:
 
1485
            domain = [('type', '=', pick.type), ('subtype', '=', pick.subtype)]
 
1486
            return self.pool.get('res.log').create(cr, uid,
 
1487
                                                   {'name': message,
 
1488
                                                    'res_model': pick_obj._name,
 
1489
                                                    'secondary': False,
 
1490
                                                    'res_id': pick.id,
 
1491
                                                    'domain': domain,
 
1492
                                                    }, context=context)
 
1493
        return result
994
1494
 
995
1495
    def _hook_log_picking_log_cond(self, cr, uid, ids, context=None, *args, **kwargs):
996
1496
        '''
1000
1500
        result = super(stock_picking, self)._hook_log_picking_log_cond(cr, uid, ids, context=context, *args, **kwargs)
1001
1501
        pick = kwargs['pick']
1002
1502
        if pick.subtype == 'packing':
 
1503
            return 'packing'
 
1504
        # if false the log will be defined by the method _hook_custom_log (which include a domain)
 
1505
        if pick.type and pick.subtype:
1003
1506
            return False
 
1507
 
1004
1508
        return result
1005
 
    
1006
 
    def copy(self, cr, uid, id, default=None, context=None):
 
1509
 
 
1510
    def copy(self, cr, uid, copy_id, default=None, context=None):
1007
1511
        '''
1008
1512
        set the name corresponding to object subtype
1009
1513
        '''
1011
1515
            default = {}
1012
1516
        if context is None:
1013
1517
            context = {}
1014
 
        obj = self.browse(cr, uid, id, context=context)
 
1518
        obj = self.browse(cr, uid, copy_id, context=context)
1015
1519
        if not context.get('allow_copy', False):
1016
 
            if obj.subtype == 'picking':
 
1520
            if obj.subtype == 'picking' and default.get('subtype', 'picking') == 'picking':
1017
1521
                if not obj.backorder_id:
1018
1522
                    # draft, new ref
1019
1523
                    default.update(name=self.pool.get('ir.sequence').get(cr, uid, 'picking.ticket'),
1022
1526
                                   sale_id=False,
1023
1527
                                   )
1024
1528
                else:
 
1529
                    # if the corresponding draft picking ticket is done, we do not allow copy
 
1530
                    if obj.backorder_id and obj.backorder_id.state == 'done':
 
1531
                        raise osv.except_osv(_('Error !'), _('Corresponding Draft picking ticket is Closed. This picking ticket cannot be copied.'))
 
1532
                    if obj.backorder_id:
 
1533
                        raise osv.except_osv(_('Error !'), _('You cannot duplicate a Picking Ticket linked to a Draft Picking Ticket.'))
1025
1534
                    # picking ticket, use draft sequence, keep other fields
1026
1535
                    base = obj.name
1027
1536
                    base = base.split('-')[0] + '-'
1028
 
                    default.update(name=base + obj.backorder_id.sequence_id.get_id(test='id', context=context),
 
1537
                    default.update(name=base + obj.backorder_id.sequence_id.get_id(code_or_id='id', context=context),
1029
1538
                                   date=date.today().strftime('%Y-%m-%d'),
1030
1539
                                   )
1031
 
                    
 
1540
 
1032
1541
            elif obj.subtype == 'ppl':
1033
1542
                raise osv.except_osv(_('Error !'), _('Pre-Packing List copy is forbidden.'))
1034
1543
                # ppl, use the draft picking ticket sequence
1035
1544
#                if obj.previous_step_id and obj.previous_step_id.backorder_id:
1036
1545
#                    base = obj.name
1037
1546
#                    base = base.split('-')[0] + '-'
1038
 
#                    default.update(name=base + obj.previous_step_id.backorder_id.sequence_id.get_id(test='id', context=context))
 
1547
#                    default.update(name=base + obj.previous_step_id.backorder_id.sequence_id.get_id(code_or_id='id', context=context))
1039
1548
#                else:
1040
1549
#                    default.update(name=self.pool.get('ir.sequence').get(cr, uid, 'ppl'))
1041
 
                
1042
 
        result = super(stock_picking, self).copy(cr, uid, id, default=default, context=context)
 
1550
 
 
1551
        result = super(stock_picking, self).copy(cr, uid, copy_id, default=default, context=context)
1043
1552
        if not context.get('allow_copy', False):
1044
1553
            if obj.subtype == 'picking' and obj.backorder_id:
1045
1554
                # confirm the new picking ticket - the picking ticket should not stay in draft state !
1048
1557
                # we force availability
1049
1558
                self.force_assign(cr, uid, [result])
1050
1559
        return result
1051
 
    
1052
 
    def copy_data(self, cr, uid, id, default=None, context=None):
 
1560
 
 
1561
    def copy_data(self, cr, uid, copy_id, default=None, context=None):
1053
1562
        '''
1054
1563
        reset one2many fields
1055
1564
        '''
1061
1570
        default.update(backorder_ids=[])
1062
1571
        default.update(previous_step_ids=[])
1063
1572
        default.update(pack_family_memory_ids=[])
1064
 
        result = super(stock_picking, self).copy_data(cr, uid, id, default=default, context=context)
1065
 
        
 
1573
        # the tag 'from_button' was added in the web client (openerp/controllers/form.py in the method duplicate) on purpose
 
1574
        if context.get('from_button'):
 
1575
            default.update(purchase_id=False)
 
1576
        if not context.get('wkf_copy'):
 
1577
            context['not_workflow'] = True
 
1578
        result = super(stock_picking, self).copy_data(cr, uid, copy_id, default=default, context=context)
 
1579
 
1066
1580
        return result
1067
 
    
1068
 
    def _erase_prodlot_hook(self, cr, uid, id, context=None, *args, **kwargs):
 
1581
 
 
1582
    def _erase_prodlot_hook(self, cr, uid, pick_id, context=None, *args, **kwargs):
1069
1583
        '''
1070
1584
        hook to keep the production lot when a stock move is copied
1071
1585
        '''
1072
 
        res = super(stock_picking, self)._erase_prodlot_hook(cr, uid, id, context=context, *args, **kwargs)
1073
 
        
 
1586
        res = super(stock_picking, self)._erase_prodlot_hook(cr, uid, pick_id, context=context, *args, **kwargs)
 
1587
 
1074
1588
        return res and not context.get('keep_prodlot', False)
1075
 
    
 
1589
 
 
1590
    def has_picking_ticket_in_progress(self, cr, uid, ids, context=None):
 
1591
        '''
 
1592
        ids is the list of draft picking object we want to test
 
1593
        completed means, we recursively check that next_step link object is cancel or done
 
1594
 
 
1595
        return true if picking tickets are in progress, meaning picking ticket or ppl or shipment not done exist
 
1596
        '''
 
1597
        if context is None:
 
1598
            context = {}
 
1599
        if isinstance(ids, (int, long)):
 
1600
            ids = []
 
1601
        res = {}
 
1602
        for obj in self.browse(cr, uid, ids, context=context):
 
1603
            # by default, nothing is in progress
 
1604
            res[obj.id] = False
 
1605
            # treat only draft picking
 
1606
            assert obj.subtype in 'picking' and obj.state == 'draft', 'the validate function should only be called on draft picking ticket objects'
 
1607
            for picking in obj.backorder_ids:
 
1608
                # take care, is_completed returns a dictionary
 
1609
                if not picking.is_completed()[picking.id]:
 
1610
                    res[obj.id] = True
 
1611
                    break
 
1612
 
 
1613
        return res
 
1614
 
1076
1615
    def validate(self, cr, uid, ids, context=None):
1077
1616
        '''
1078
1617
        validate or not the draft picking ticket
1079
1618
        '''
 
1619
        # objects
 
1620
        move_obj = self.pool.get('stock.move')
 
1621
 
1080
1622
        for draft_picking in self.browse(cr, uid, ids, context=context):
1081
1623
            # the validate function should only be called on draft picking ticket
1082
 
            assert draft_picking.subtype == 'picking' and draft_picking.state == 'draft', 'the validate function should only be called on draft picking objects'
1083
 
            #check the qty of all stock moves
 
1624
            assert draft_picking.subtype == 'picking' and draft_picking.state == 'draft', 'the validate function should only be called on draft picking ticket objects'
 
1625
            # check the qty of all stock moves
1084
1626
            treat_draft = True
1085
 
            for move in draft_picking.move_lines:
1086
 
                if move.product_qty:
1087
 
                    treat_draft = False
1088
 
            
 
1627
            move_ids = move_obj.search(cr, uid, [('picking_id', '=', draft_picking.id),
 
1628
                                                 ('product_qty', '!=', 0.0),
 
1629
                                                 ('state', 'not in', ['done', 'cancel'])], context=context)
 
1630
            if move_ids:
 
1631
                treat_draft = False
 
1632
 
1089
1633
            if treat_draft:
1090
1634
                # then all child picking must be fully completed, meaning:
1091
1635
                # - all picking must be 'completed'
1092
1636
                # completed means, we recursively check that next_step link object is cancel or done
1093
 
                for picking in draft_picking.backorder_ids:
1094
 
                    # take care, is_completed returns a dictionary
1095
 
                    if not picking.is_completed()[picking.id]:
1096
 
                        treat_draft = False
1097
 
                        break
1098
 
            
 
1637
                if self.has_picking_ticket_in_progress(cr, uid, [draft_picking.id], context=context)[draft_picking.id]:
 
1638
                    treat_draft = False
 
1639
 
1099
1640
            if treat_draft:
1100
1641
                # - all picking are completed (means ppl completed and all shipment validated)
1101
1642
                wf_service = netsvc.LocalService("workflow")
1105
1646
                # finish
1106
1647
                draft_picking.action_move()
1107
1648
                wf_service.trg_validate(uid, 'stock.picking', draft_picking.id, 'button_done', cr)
1108
 
                
 
1649
 
1109
1650
        return True
1110
 
    
1111
 
    def _vals_get_2(self, cr, uid, ids, fields, arg, context=None):
1112
 
        '''
1113
 
        get functional values
1114
 
        '''
 
1651
 
 
1652
    def _get_overall_qty(self, cr, uid, ids, fields, arg, context=None):
1115
1653
        result = {}
1116
 
        for stock_picking in self.browse(cr, uid, ids, context=context):
1117
 
            values = {'pack_family_memory_ids':[],
1118
 
                      }
1119
 
            result[stock_picking.id] = values
1120
 
            
1121
 
            # get the corresponding data for pack family memory
1122
 
            data = self.generate_data_from_picking_for_pack_family(cr, uid, [stock_picking.id], context=context)
1123
 
            # create a memory family - no shipment id
1124
 
            created_ids = self.create_pack_families_memory_from_data(cr, uid, data, shipment_id=False, context=context)
1125
 
            values['pack_family_memory_ids'].extend(created_ids)
1126
 
                    
 
1654
        if not ids:
 
1655
            return result
 
1656
        if isinstance(ids, (int, long)):
 
1657
            ids = [ids]
 
1658
        cr.execute('''select p.id, sum(m.product_qty)
 
1659
            from stock_picking p, stock_move m
 
1660
            where m.picking_id = p.id
 
1661
            group by p.id''')
 
1662
        for i in cr.fetchall():
 
1663
            result[i[0]] = i[1] or 0
1127
1664
        return result
1128
 
    
 
1665
 
1129
1666
    def _vals_get(self, cr, uid, ids, fields, arg, context=None):
1130
1667
        '''
1131
1668
        get functional values
1132
1669
        '''
1133
1670
        result = {}
1134
 
        for stock_picking in self.browse(cr, uid, ids, context=context):
 
1671
 
 
1672
        for stock_picking in self.read(cr, uid, ids, ['pack_family_memory_ids', 'move_lines'], context=context):
1135
1673
            values = {'total_amount': 0.0,
1136
1674
                      'currency_id': False,
1137
1675
                      'is_dangerous_good': False,
1138
1676
                      'is_keep_cool': False,
1139
1677
                      'is_narcotic': False,
1140
1678
                      'num_of_packs': 0,
 
1679
                      'total_volume': 0.0,
1141
1680
                      'total_weight': 0.0,
1142
 
                      #'is_completed': False,
 
1681
                      # 'is_completed': False,
1143
1682
                      }
1144
 
            result[stock_picking.id] = values
1145
 
            
1146
 
            for family in stock_picking.pack_family_memory_ids:
1147
 
                # number of packs from pack_family
1148
 
                num_of_packs = family.num_of_packs
1149
 
                values['num_of_packs'] += int(num_of_packs)
1150
 
                # total_weight
1151
 
                total_weight = family.total_weight
1152
 
                values['total_weight'] += total_weight
1153
 
                
1154
 
            for move in stock_picking.move_lines:
1155
 
                # total amount (float)
1156
 
                total_amount = move.total_amount
1157
 
                values['total_amount'] = total_amount
1158
 
                # currency
1159
 
                values['currency_id'] = move.currency_id and move.currency_id.id or False
1160
 
                # dangerous good
1161
 
                values['is_dangerous_good'] = move.is_dangerous_good
1162
 
                # keep cool - if heat_sensitive_item is True
1163
 
                values['is_keep_cool'] = move.is_keep_cool
1164
 
                # narcotic
1165
 
                values['is_narcotic'] = move.is_narcotic
1166
 
                
 
1683
            result[stock_picking['id']] = values
 
1684
 
 
1685
            if stock_picking['pack_family_memory_ids']:
 
1686
                for family in self.pool.get('pack.family.memory').read(cr, uid, stock_picking['pack_family_memory_ids'], ['num_of_packs', 'total_weight', 'total_volume'], context=context):
 
1687
                    # number of packs from pack_family
 
1688
                    num_of_packs = family['num_of_packs']
 
1689
                    values['num_of_packs'] += int(num_of_packs)
 
1690
                    # total_weight
 
1691
                    total_weight = family['total_weight']
 
1692
                    values['total_weight'] += float(total_weight)
 
1693
                    total_volume = family['total_volume']
 
1694
                    values['total_volume'] += float(total_volume)
 
1695
 
 
1696
            if stock_picking['move_lines']:
 
1697
 
 
1698
                for move in self.pool.get('stock.move').read(cr, uid, stock_picking['move_lines'], ['total_amount', 'currency_id', 'is_dangerous_good', 'is_keep_cool', 'is_narcotic', 'product_qty'], context=context):
 
1699
                    # total amount (float)
 
1700
                    total_amount = move['total_amount']
 
1701
                    values['total_amount'] = total_amount
 
1702
                    # currency
 
1703
                    values['currency_id'] = move['currency_id'] or False
 
1704
                    # dangerous good
 
1705
                    values['is_dangerous_good'] = move['is_dangerous_good']
 
1706
                    # keep cool - if heat_sensitive_item is True
 
1707
                    values['is_keep_cool'] = move['is_keep_cool']
 
1708
                    # narcotic
 
1709
                    values['is_narcotic'] = move['is_narcotic']
 
1710
 
1167
1711
            # completed field - based on the previous_step_ids field, recursive call from picking to draft packing and packing
1168
1712
            # - picking checks that the corresponding ppl is completed
1169
1713
            # - ppl checks that the corresponding draft packing and packings are completed
1174
1718
#                    if not next_step.is_completed:
1175
1719
#                        completed = False
1176
1720
#                        break
1177
 
#                    
 
1721
#
1178
1722
#            values['is_completed'] = completed
1179
 
                    
 
1723
 
1180
1724
        return result
1181
 
    
 
1725
 
1182
1726
    def is_completed(self, cr, uid, ids, context=None):
1183
1727
        '''
1184
1728
        recursive test of completion
 
1729
        - to be applied on picking ticket
 
1730
 
 
1731
        ex:
 
1732
        for picking in draft_picking.backorder_ids:
 
1733
            # take care, is_completed returns a dictionary
 
1734
            if not picking.is_completed()[picking.id]:
 
1735
                ...balbala
 
1736
 
 
1737
        ***BEWARE: RETURNS A DICTIONARY !
1185
1738
        '''
1186
1739
        result = {}
1187
1740
        for stock_picking in self.browse(cr, uid, ids, context=context):
1188
 
            # for debugging
1189
 
            state = stock_picking.state
1190
 
            subtype = stock_picking.subtype
1191
1741
            completed = stock_picking.state in ('done', 'cancel')
1192
1742
            result[stock_picking.id] = completed
1193
1743
            if completed:
1196
1746
                        completed = False
1197
1747
                        result[stock_picking.id] = completed
1198
1748
                        break
1199
 
        
 
1749
 
1200
1750
        return result
1201
 
    
 
1751
 
1202
1752
    def init(self, cr):
1203
1753
        """
1204
1754
        Load msf_outgoing_data.xml before self
1208
1758
 
1209
1759
        mod_obj = self.pool.get('ir.module.module')
1210
1760
        demo = False
1211
 
        mod_id = mod_obj.search(cr, 1, [('name', '=', 'msf_outgoing'),])
 
1761
        mod_id = mod_obj.search(cr, 1, [('name', '=', 'msf_outgoing'), ])
1212
1762
        if mod_id:
1213
1763
            demo = mod_obj.read(cr, 1, mod_id, ['demo'])[0]['demo']
1214
1764
 
1215
1765
        if demo:
1216
1766
            logging.getLogger('init').info('HOOK: module msf_outgoing: loading data/msf_outgoing_data.xml')
1217
1767
            pathname = path.join('msf_outgoing', 'data/msf_outgoing_data.xml')
1218
 
            file = tools.file_open(pathname)
1219
 
            tools.convert_xml_import(cr, 'msf_outgoing', file, {}, mode='init', noupdate=False)
1220
 
    
1221
 
    _columns = {'flow_type': fields.selection([('full', 'Full'),('quick', 'Quick')], readonly=True, states={'draft': [('readonly', False),],}, string='Flow Type'),
1222
 
                'subtype': fields.selection([('standard', 'Standard'), ('picking', 'Picking'),('ppl', 'PPL'),('packing', 'Packing')], string='Subtype'),
 
1768
            file_to_open = tools.file_open(pathname)
 
1769
            tools.convert_xml_import(cr, 'msf_outgoing', file_to_open, {}, mode='init', noupdate=False)
 
1770
 
 
1771
    def _qty_search(self, cr, uid, obj, name, args, context=None):
 
1772
        """ Searches Ids of stock picking
 
1773
            @return: Ids of locations
 
1774
        """
 
1775
        if context is None:
 
1776
            context = {}
 
1777
        stock_pickings = self.pool.get('stock.picking').search(cr, uid, [], context=context)
 
1778
        # result dic
 
1779
        result = {}
 
1780
        for stock_picking in self.browse(cr, uid, stock_pickings, context=context):
 
1781
            result[stock_picking.id] = 0.0
 
1782
            for move in stock_picking.move_lines:
 
1783
                result[stock_picking.id] += move.product_qty
 
1784
        # construct the request
 
1785
        # adapt the operator
 
1786
        op = args[0][1]
 
1787
        if op == '=':
 
1788
            op = '=='
 
1789
        ids = [('id', 'in', [x for x in result.keys() if eval("%s %s %s" % (result[x], op, args[0][2]))])]
 
1790
        return ids
 
1791
 
 
1792
    def _get_picking_ids(self, cr, uid, ids, context=None):
 
1793
        '''
 
1794
        ids represents the ids of stock.move objects for which values have changed
 
1795
        return the list of ids of picking object which need to get their state field updated
 
1796
 
 
1797
        self is stock.move object
 
1798
        '''
 
1799
        result = []
 
1800
        for obj in self.browse(cr, uid, ids, context=context):
 
1801
            if obj.picking_id and obj.picking_id.id not in result:
 
1802
                result.append(obj.picking_id.id)
 
1803
        return result
 
1804
 
 
1805
    def _get_draft_moves(self, cr, uid, ids, field_name, args, context=None):
 
1806
        '''
 
1807
        Returns True if there is draft moves on Picking Ticket
 
1808
        '''
 
1809
        res = {}
 
1810
 
 
1811
        for pick in self.browse(cr, uid, ids, context=context):
 
1812
            res[pick.id] = False
 
1813
            for move in pick.move_lines:
 
1814
                if move.state == 'draft':
 
1815
                    res[pick.id] = True
 
1816
                    continue
 
1817
 
 
1818
        return res
 
1819
 
 
1820
    def _get_lines_state(self, cr, uid, ids, field_name, args, context=None):
 
1821
        '''
 
1822
        Returns the state according to line states and picking state
 
1823
        If the Picking Ticket is not draft, don't compute the line state
 
1824
        Else, for all moves with quantity, check the state of the move
 
1825
        and set the line state with these values :
 
1826
        'mixed': 'Partially Available'
 
1827
        'assigned': 'Available'
 
1828
        'confirmed': 'Not available'
 
1829
        '''
 
1830
        res = {}
 
1831
 
 
1832
        for pick in self.browse(cr, uid, ids, context=context):
 
1833
            if pick.type != 'out' or pick.subtype != 'picking' or pick.state != 'draft':
 
1834
                res[pick.id] = False
 
1835
                continue
 
1836
 
 
1837
            res[pick.id] = 'confirmed'
 
1838
            available = False
 
1839
            confirmed = False
 
1840
            processed = True
 
1841
            empty = len(pick.move_lines)
 
1842
            for move in pick.move_lines:
 
1843
                if move.product_qty == 0.00:
 
1844
                    continue
 
1845
 
 
1846
                processed = False
 
1847
 
 
1848
                if move.state != 'assigned':
 
1849
                    confirmed = True
 
1850
                else:
 
1851
                    available = True
 
1852
 
 
1853
                if confirmed and available:
 
1854
                    break
 
1855
 
 
1856
            if available and confirmed:
 
1857
                res[pick.id] = 'mixed'
 
1858
            elif available:
 
1859
                res[pick.id] = 'assigned'
 
1860
            elif confirmed:
 
1861
                res[pick.id] = 'confirmed'
 
1862
            elif processed and empty:
 
1863
                res[pick.id] = 'processed'
 
1864
            elif empty == 0:
 
1865
                res[pick.id] = 'empty'
 
1866
            else:
 
1867
                res[pick.id] = False
 
1868
 
 
1869
        return res
 
1870
 
 
1871
    _columns = {'flow_type': fields.selection([('full', 'Full'), ('quick', 'Quick')], readonly=True, states={'draft': [('readonly', False), ], }, string='Flow Type'),
 
1872
                'subtype': fields.selection([('standard', 'Standard'), ('picking', 'Picking'), ('ppl', 'PPL'), ('packing', 'Packing')], string='Subtype'),
1223
1873
                'backorder_ids': fields.one2many('stock.picking', 'backorder_id', string='Backorder ids',),
1224
1874
                'previous_step_id': fields.many2one('stock.picking', 'Previous step'),
1225
1875
                'previous_step_ids': fields.one2many('stock.picking', 'previous_step_id', string='Previous Step ids',),
1226
1876
                'shipment_id': fields.many2one('shipment', string='Shipment'),
1227
1877
                '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'),
1228
1878
                'first_shipment_packing_id': fields.many2one('stock.picking', 'Shipment First Step'),
1229
 
                #'pack_family_ids': fields.one2many('pack.family', 'ppl_id', string='Pack Families',),
 
1879
                # 'pack_family_ids': fields.one2many('pack.family', 'ppl_id', string='Pack Families',),
1230
1880
                # attributes for specific packing labels
1231
1881
                'ppl_customize_label': fields.many2one('ppl.customize.label', string='Labels Customization',),
1232
1882
                # warehouse info (locations) are gathered from here - allow shipment process without sale order
1233
1883
                'warehouse_id': fields.many2one('stock.warehouse', string='Warehouse', required=True,),
1234
 
                # functions
1235
 
                'num_of_packs': fields.function(_vals_get, method=True, type='integer', string='#Packs', multi='get_vals_X',), # old_multi get_vals
1236
 
                'total_weight': fields.function(_vals_get, method=True, type='float', string='Total Weight[kg]', multi='get_vals',),
1237
 
                'total_amount': fields.function(_vals_get, method=True, type='float', string='Total Amount', multi='get_vals',),
1238
 
                'currency_id': fields.function(_vals_get, method=True, type='many2one', relation='res.currency', string='Currency', multi='get_vals',),
1239
 
                'is_dangerous_good': fields.function(_vals_get, method=True, type='boolean', string='Dangerous Good', multi='get_vals',),
1240
 
                'is_keep_cool': fields.function(_vals_get, method=True, type='boolean', string='Keep Cool', multi='get_vals',),
1241
 
                'is_narcotic': fields.function(_vals_get, method=True, type='boolean', string='Narcotic', multi='get_vals',),
1242
 
                #'is_completed': fields.function(_vals_get, method=True, type='boolean', string='Completed Process', multi='get_vals',),
1243
 
                'pack_family_memory_ids': fields.function(_vals_get_2, method=True, type='one2many', relation='pack.family.memory', string='Memory Families', multi='get_vals_2',),
1244
1884
                # flag for converted picking
1245
1885
                'converted_to_standard': fields.boolean(string='Converted to Standard'),
 
1886
                # functions
 
1887
                'num_of_packs': fields.function(_vals_get, method=True, type='integer', string='#Packs', multi='get_vals_X'),  # old_multi get_vals
 
1888
                'total_volume': fields.function(_vals_get, method=True, type='float', string=u'Total Volume[dm³]', multi='get_vals'),
 
1889
                'total_weight': fields.function(_vals_get, method=True, type='float', string='Total Weight[kg]', multi='get_vals'),
 
1890
                'total_amount': fields.function(_vals_get, method=True, type='float', string='Total Amount', digits_compute=dp.get_precision('Picking Price'), multi='get_vals'),
 
1891
                'currency_id': fields.function(_vals_get, method=True, type='many2one', relation='res.currency', string='Currency', multi='get_vals'),
 
1892
                'is_dangerous_good': fields.function(_vals_get, method=True, type='boolean', string='Dangerous Good', multi='get_vals'),
 
1893
                'is_keep_cool': fields.function(_vals_get, method=True, type='boolean', string='Keep Cool', multi='get_vals'),
 
1894
                'is_narcotic': fields.function(_vals_get, method=True, type='boolean', string='Narcotic', multi='get_vals'),
 
1895
                'overall_qty': fields.function(_get_overall_qty, method=True, fnct_search=_qty_search, type='float', string='Overall Qty',
 
1896
                                    store={'stock.move': (_get_picking_ids, ['product_qty', 'picking_id'], 10), }),
 
1897
                'line_state': fields.function(_get_lines_state, method=True, type='selection',
 
1898
                                    selection=[('confirmed', 'Not available'),
 
1899
                                               ('assigned', 'Available'),
 
1900
                                               ('empty', 'Empty'),
 
1901
                                               ('processed', 'Processed'),
 
1902
                                               ('mixed', 'Partially available')], string='Lines state',
 
1903
                                    store={'stock.move': (_get_picking_ids, ['picking_id', 'state', 'product_qty'], 10),
 
1904
                                           'stock.picking': (lambda self, cr, uid, ids, c={}: ids, ['move_lines'], 10)}),
 
1905
                # 'is_completed': fields.function(_vals_get, method=True, type='boolean', string='Completed Process', multi='get_vals',),
 
1906
                'pack_family_memory_ids': fields.one2many('pack.family.memory', 'draft_packing_id', string='Memory Families'),
 
1907
                'description_ppl': fields.char('Description', size=256),
 
1908
                'already_shipped': fields.boolean(string='The shipment is done'),  # UF-1617: only for indicating the PPL that the relevant Ship has been closed
 
1909
                'has_draft_moves': fields.function(_get_draft_moves, method=True, type='boolean', string='Has draft moves ?', store=False),
 
1910
                'has_to_be_resourced': fields.boolean(string='Picking has to be resourced'),
1246
1911
                }
 
1912
 
1247
1913
    _defaults = {'flow_type': 'full',
1248
 
                 '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,
 
1914
                 '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,
1249
1915
                 'subtype': 'standard',
1250
1916
                 'first_shipment_packing_id': False,
1251
1917
                 '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,
1252
1918
                 'converted_to_standard': False,
 
1919
                 'already_shipped': False,
 
1920
                 'line_state': 'empty',
1253
1921
                 }
1254
 
    #_order = 'origin desc, name asc'
 
1922
 
1255
1923
    _order = 'name desc'
1256
 
    
 
1924
 
 
1925
    def onchange_move(self, cr, uid, ids, context=None):
 
1926
        '''
 
1927
        Display or not the 'Confirm' button on Picking Ticket
 
1928
        '''
 
1929
        res = super(stock_picking, self).onchange_move(cr, uid, ids, context=context)
 
1930
 
 
1931
        if ids:
 
1932
            has_draft_moves = self._get_draft_moves(cr, uid, ids, 'has_draft_moves', False)[ids[0]]
 
1933
            res.setdefault('value', {}).update({'has_draft_moves': has_draft_moves})
 
1934
 
 
1935
        return res
 
1936
 
1257
1937
    def picking_ticket_data(self, cr, uid, ids, context=None):
1258
1938
        '''
1259
1939
        generate picking ticket data for report creation
1260
 
        
 
1940
 
1261
1941
        - sale order line without product: does not work presently
1262
 
        
 
1942
 
1263
1943
        - many sale order line with same product: stored in different dictionary with line id as key.
1264
1944
            so the same product could be displayed many times in the picking ticket according to sale order
1265
 
        
 
1945
 
1266
1946
        - many stock move with same product: two cases, if from different sale order lines, the above rule applies,
1267
1947
            if from the same order line, they will be stored according to prodlot id
1268
 
            
 
1948
 
1269
1949
        - many stock move with same prodlot (so same product): if same sale order line, the moves will be
1270
1950
            stored in the same structure, with global quantity, i.e. this batch for this product for this
1271
1951
            sale order line will be displayed only once with summed quantity from concerned stock moves
1272
 
        
 
1952
 
1273
1953
        [sale_line.id][product_id][prodlot_id]
1274
 
        
1275
 
        other prod lot, not used are added in order that all prod lot are displayed 
1276
 
        
 
1954
 
 
1955
        other prod lot, not used are added in order that all prod lot are displayed
 
1956
 
1277
1957
        to check, if a move does not come from the sale order line:
1278
1958
        stored with line id False, product is relevant, multiple
1279
1959
        product for the same 0 line id is possible
1285
1965
                                        'lines': values,
1286
1966
                                        }
1287
1967
            for move in stock_picking.move_lines:
1288
 
                if move.product_id: # product is mandatory at stock_move level ;)
 
1968
                if move.product_id:  # product is mandatory at stock_move level ;)
1289
1969
                    sale_line_id = move.sale_line_id and move.sale_line_id.id or False
1290
1970
                    # structure, data is reorganized in order to regroup according to sale order line > product > production lot
1291
1971
                    # and to sum the quantities corresponding to different levels because this is impossible within the rml framework
1296
1976
                        .setdefault('uoms', {}) \
1297
1977
                        .setdefault(move.product_uom.id, {}) \
1298
1978
                        .setdefault('lots', {})
1299
 
                        
 
1979
 
1300
1980
                    # ** sale order line info**
1301
1981
                    values[sale_line_id]['obj'] = move.sale_line_id or False
1302
 
                    
 
1982
 
1303
1983
                    # **uom level info**
1304
1984
                    values[sale_line_id]['products'][move.product_id.id]['uoms'][move.product_uom.id]['obj'] = move.product_uom
1305
 
                    
 
1985
 
1306
1986
                    # **prodlot level info**
1307
1987
                    if move.prodlot_id:
1308
1988
                        values[sale_line_id]['products'][move.product_id.id]['uoms'][move.product_uom.id]['lots'].setdefault(move.prodlot_id.id, {})
1311
1991
                        values[sale_line_id]['products'][move.product_id.id]['uoms'][move.product_uom.id]['lots'][move.prodlot_id.id]['reserved_qty'] += move.product_qty
1312
1992
                        # store the object for info retrieval
1313
1993
                        values[sale_line_id]['products'][move.product_id.id]['uoms'][move.product_uom.id]['lots'][move.prodlot_id.id]['obj'] = move.prodlot_id
1314
 
                    
 
1994
 
1315
1995
                    # **product level info**
1316
1996
                    # total quantity from STOCK_MOVES for one sale order line (directly for one product)
1317
1997
                    # or if not linked to a sale order line, stock move created manually, the line id is False
1320
2000
                    values[sale_line_id]['products'][move.product_id.id]['uoms'][move.product_uom.id]['qty_to_pick_sm'] += move.product_qty
1321
2001
                    # total quantity from SALE_ORDER_LINES, which can be different from the one from stock moves
1322
2002
                    # if stock moves have been created manually in the picking, no present in the so, equal to 0 if not linked to an so
1323
 
                    values[sale_line_id]['products'][move.product_id.id]['uoms'][move.product_uom.id].setdefault('qty_to_pick_so', 0)
1324
 
                    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 
 
2003
 
 
2004
                    # UF-2227: Qty of FO line is only taken once, and use it, do not accumulate for the case the line got split!
 
2005
                    if 'qty_to_pick_so' not in values[sale_line_id]['products'][move.product_id.id]['uoms'][move.product_uom.id]:
 
2006
                        values[sale_line_id]['products'][move.product_id.id]['uoms'][move.product_uom.id].setdefault('qty_to_pick_so', 0)
 
2007
                        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
 
2008
 
1325
2009
                    # store the object for info retrieval
1326
2010
                    values[sale_line_id]['products'][move.product_id.id]['obj'] = move.product_id
1327
 
                    
 
2011
 
1328
2012
            # all moves have been treated
1329
2013
            # complete the lot lists for each product
1330
2014
            for sale_line in values.values():
1338
2022
                                uom['lots'][lot.id]['obj'] = lot
1339
2023
                                # reserved qty is 0 since no stock moves correspond to this lot
1340
2024
                                uom['lots'][lot.id]['reserved_qty'] = 0.0
1341
 
                    
 
2025
 
1342
2026
        return result
1343
 
    
 
2027
 
 
2028
    def action_confirm_moves(self, cr, uid, ids, context=None):
 
2029
        '''
 
2030
        Confirm all stock moves of the picking
 
2031
        '''
 
2032
        moves_to_confirm = []
 
2033
        for pick in self.browse(cr, uid, ids, context=context):
 
2034
            for move in pick.move_lines:
 
2035
                if move.state == 'draft':
 
2036
                    moves_to_confirm.append(move.id)
 
2037
 
 
2038
        self.pool.get('stock.move').action_confirm(cr, uid, moves_to_confirm, context=context)
 
2039
 
 
2040
        return True
 
2041
 
1344
2042
    def create_sequence(self, cr, uid, vals, context=None):
1345
2043
        """
1346
2044
        Create new entry sequence for every new picking
1349
2047
        @param ids: list of record ids to be process
1350
2048
        @param context: context arguments, like lang, time zone
1351
2049
        @return: return a result
1352
 
        
 
2050
 
1353
2051
        example of name: 'PICK/xxxxx'
1354
2052
        example of code: 'picking.xxxxx'
1355
2053
        example of prefix: 'PICK'
1357
2055
        """
1358
2056
        seq_pool = self.pool.get('ir.sequence')
1359
2057
        seq_typ_pool = self.pool.get('ir.sequence.type')
1360
 
        
1361
 
        assert vals, 'no vals dictionary'
1362
 
        assert 'name' in vals, 'Missing Name'
1363
 
        assert 'code' in vals, 'Missing Code'
1364
 
        assert 'prefix' in vals, 'Missing prefix'
1365
 
        assert 'padding' in vals, 'Missing padding'
1366
 
 
1367
 
        name = vals['name']
 
2058
 
 
2059
        default_name = 'Stock Picking'
 
2060
        default_code = 'stock.picking'
 
2061
        default_prefix = ''
 
2062
        default_padding = 0
 
2063
 
 
2064
        if vals is None:
 
2065
            vals = {}
 
2066
 
 
2067
        name = vals.get('name', False)
1368
2068
        if not name:
1369
 
            name = 'default_name'
1370
 
        code = vals['code']
 
2069
            name = default_name
 
2070
        code = vals.get('code', False)
1371
2071
        if not code:
1372
 
            code = 'default_code'
1373
 
        prefix = vals['prefix']
1374
 
        padding = vals['padding']
 
2072
            code = default_code
 
2073
        prefix = vals.get('prefix', False)
 
2074
        if not prefix:
 
2075
            prefix = default_prefix
 
2076
        padding = vals.get('padding', False)
 
2077
        if not padding:
 
2078
            padding = default_padding
1375
2079
 
1376
2080
        types = {
1377
2081
            'name': name,
1386
2090
            'padding': padding,
1387
2091
        }
1388
2092
        return seq_pool.create(cr, uid, seq)
1389
 
    
 
2093
 
1390
2094
    def generate_data_from_picking_for_pack_family(self, cr, uid, pick_ids, object_type='shipment', from_pack=False, to_pack=False, context=None):
1391
2095
        '''
 
2096
        USED BY THE SHIPMENT PROCESS WIZARD
1392
2097
        generate the data structure from the stock.picking object
1393
 
        
 
2098
 
1394
2099
        we can limit the generation to certain from/to sequence
1395
 
        
 
2100
 
1396
2101
        one data for each move_id - here is the difference with data generated from partial
1397
 
        
 
2102
 
1398
2103
        structure:
1399
2104
            {pick_id: {from_pack: {to_pack: {move_id: {data}}}}}
1400
 
            
 
2105
 
1401
2106
        if the move has a quantity equal to 0, it means that no pack are available,
1402
2107
        these moves are therefore not taken into account for the pack families generation
1403
 
        
 
2108
 
1404
2109
        TODO: integrity constraints
1405
 
        
 
2110
 
1406
2111
        Note: why the same dictionary is repeated n times for n moves, because
1407
2112
        it is directly used when we create the pack families. could be refactored
1408
2113
        with one dic per from/to with a 'move_ids' entry
1409
2114
        '''
1410
2115
        assert bool(from_pack) == bool(to_pack), 'from_pack and to_pack must be either both filled or empty'
1411
2116
        result = {}
1412
 
        
1413
 
        
 
2117
 
 
2118
 
1414
2119
        if object_type == 'shipment':
1415
2120
            # all moves are taken into account, therefore the back moves are represented
1416
2121
            # by done pack families
1420
2125
            states = ('done')
1421
2126
        else:
1422
2127
            assert False, 'Should not reach this line'
1423
 
        
 
2128
 
1424
2129
        for pick in self.browse(cr, uid, pick_ids, context=context):
1425
2130
            result[pick.id] = {}
1426
2131
            for move in pick.move_lines:
1433
2138
                                result[pick.id] \
1434
2139
                                    .setdefault(move.from_pack, {}) \
1435
2140
                                    .setdefault(move.to_pack, {})[move.id] = {'sale_order_id': pick.sale_id.id,
1436
 
                                                                              'ppl_id': pick.id, # only change between ppl - packing
 
2141
                                                                              'ppl_id': pick.id,  # only change between ppl - packing
1437
2142
                                                                              'from_pack': move.from_pack,
1438
2143
                                                                              'to_pack': move.to_pack,
1439
2144
                                                                              'pack_type': move.pack_type.id,
1442
2147
                                                                              'height': move.height,
1443
2148
                                                                              'weight': move.weight,
1444
2149
                                                                              'draft_packing_id': pick.id,
 
2150
                                                                              'description_ppl': pick.description_ppl,
1445
2151
                                                                              }
1446
2152
                            # subtype == packing - caled from shipment
1447
2153
                            elif pick.subtype == 'packing':
1458
2164
                                                                              'weight': move.weight,
1459
2165
                                                                              'draft_packing_id': pick.id,
1460
2166
                                                                              }
1461
 
        
 
2167
 
 
2168
                                if object_type != 'memory':
 
2169
                                        result[pick.id][move.from_pack][move.to_pack][move.id]['description_ppl'] = pick.description_ppl
 
2170
 
1462
2171
        return result
1463
 
    
 
2172
 
1464
2173
    def create_pack_families_memory_from_data(self, cr, uid, data, shipment_id, context=None,):
1465
2174
        '''
 
2175
        **DEPECRATED**
1466
2176
        - clear existing pack family memory objects is not necessary thanks to vaccum system
1467
2177
        -> in fact cleaning old memory objects reslults in a bug, because when we click on a
1468
2178
           pf memory to see it's form view, the shipment view is regenerated (delete the pf)
1473
2183
        '''
1474
2184
        pf_memory_obj = self.pool.get('pack.family.memory')
1475
2185
        # find and delete existing objects
1476
 
        #ids = pf_memory_obj.search(cr, uid, [('shipment_id', '=', shipment_id),], context=context)
1477
 
        #pf_memory_obj.unlink(cr, uid, ids, context=context)
 
2186
        # ids = pf_memory_obj.search(cr, uid, [('shipment_id', '=', shipment_id),], context=context)
 
2187
        # pf_memory_obj.unlink(cr, uid, ids, context=context)
1478
2188
        created_ids = []
1479
2189
        # create pack family memory
1480
2190
        for picking in data.values():
1485
2195
                    # create corresponding memory object
1486
2196
                    move_data.update(name='_name',
1487
2197
                                     shipment_id=shipment_id,)
1488
 
                    id = pf_memory_obj.create(cr, uid, move_data, context=context)
1489
 
                    created_ids.append(id)
1490
 
        
 
2198
                    pf_id = pf_memory_obj.create(cr, uid, move_data, context=context)
 
2199
                    created_ids.append(pf_id)
1491
2200
        return created_ids
1492
 
        
 
2201
 
1493
2202
    def create(self, cr, uid, vals, context=None):
1494
2203
        '''
1495
2204
        creation of a stock.picking of subtype 'packing' triggers
1496
2205
        special behavior :
1497
2206
         - creation of corresponding shipment
1498
2207
        '''
 
2208
        # Objects
 
2209
        ship_proc_obj = self.pool.get('shipment.processor')
 
2210
 
 
2211
        # For picking ticket from scratch, invoice it !
 
2212
        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':
 
2213
            vals['invoice_state'] = '2binvoiced'
 
2214
        # objects
 
2215
        date_tools = self.pool.get('date.tools')
 
2216
        fields_tools = self.pool.get('fields.tools')
 
2217
        db_date_format = date_tools.get_db_date_format(cr, uid, context=context)
 
2218
        db_datetime_format = date_tools.get_db_datetime_format(cr, uid, context=context)
 
2219
 
1499
2220
        if context is None:
1500
2221
            context = {}
 
2222
 
 
2223
        if context.get('sync_update_execution', False):
 
2224
            # UF-2066: in case the data comes from sync, some False value has been removed, but needed in some assert.
 
2225
            # The following lines are to re-enter explicitly the values, even if they are already set to False
 
2226
            vals['backorder_id'] = vals.get('backorder_id', False)
 
2227
            vals['shipment_id'] = vals.get('shipment_id', False)
 
2228
 
1501
2229
        # the action adds subtype in the context depending from which screen it is created
1502
2230
        if context.get('picking_screen', False) and not vals.get('name', False):
1503
2231
            pick_name = self.pool.get('ir.sequence').get(cr, uid, 'picking.ticket')
1506
2234
                        name=pick_name,
1507
2235
                        flow_type='full',
1508
2236
                        )
1509
 
        
 
2237
 
1510
2238
        if context.get('ppl_screen', False) and not vals.get('name', False):
1511
2239
            pick_name = self.pool.get('ir.sequence').get(cr, uid, 'ppl')
1512
2240
            vals.update(subtype='ppl',
1518
2246
        shipment_obj = self.pool.get('shipment')
1519
2247
        # move object
1520
2248
        move_obj = self.pool.get('stock.move')
1521
 
        
 
2249
 
1522
2250
        # sequence creation
1523
2251
        # if draft picking
1524
2252
        if 'subtype' in vals and vals['subtype'] == 'picking':
1525
2253
            # creation of a new picking ticket
1526
2254
            assert 'backorder_id' in vals, 'No backorder_id'
1527
 
            
 
2255
 
1528
2256
            if not vals['backorder_id']:
1529
2257
                # creation of *draft* picking ticket
1530
2258
                vals.update(sequence_id=self.create_sequence(cr, uid, {'name':vals['name'],
1531
2259
                                                                       'code':vals['name'],
1532
2260
                                                                       'prefix':'',
1533
2261
                                                                       'padding':2}, context=context))
1534
 
                
 
2262
 
1535
2263
        if 'subtype' in vals and vals['subtype'] == 'packing':
1536
2264
            # creation of a new packing
1537
2265
            assert 'backorder_id' in vals, 'No backorder_id'
1538
2266
            assert 'shipment_id' in vals, 'No shipment_id'
1539
 
            
 
2267
 
1540
2268
            if not vals['backorder_id']:
1541
2269
                # creation of *draft* picking ticket
1542
2270
                vals.update(sequence_id=self.create_sequence(cr, uid, {'name':vals['name'],
1544
2272
                                                                       'prefix':'',
1545
2273
                                                                       'padding':2,
1546
2274
                                                                       }, context=context))
1547
 
        
 
2275
 
1548
2276
        # create packing object
1549
2277
        new_packing_id = super(stock_picking, self).create(cr, uid, vals, context=context)
1550
 
        
 
2278
 
1551
2279
        if 'subtype' in vals and vals['subtype'] == 'packing':
1552
2280
            # creation of a new packing
1553
2281
            assert 'backorder_id' in vals, 'No backorder_id'
1554
2282
            assert 'shipment_id' in vals, 'No shipment_id'
1555
 
            
 
2283
 
1556
2284
            if vals['backorder_id'] and vals['shipment_id']:
1557
2285
                # ship of existing shipment
1558
2286
                # no new shipment
1559
2287
                # TODO
1560
2288
                return new_packing_id
1561
 
            
 
2289
 
1562
2290
            if vals['backorder_id'] and not vals['shipment_id']:
1563
 
                # data from do_create_shipment method
1564
 
                assert 'partial_datas_shipment' in context, 'Missing partial_datas_shipment'
1565
 
                assert 'draft_shipment_id' in context, 'Missing draft_shipment_id'
1566
 
                assert 'draft_packing_id' in context, 'Missing draft_packing_id'
1567
 
                assert 'shipment_id' in context, 'Missing shipment_id'
1568
 
                draft_shipment_id = context['draft_shipment_id']
 
2291
                if not context.get('shipment_proc_id', False):
 
2292
                    raise osv.except_osv(
 
2293
                        _('Processing Error'),
 
2294
                        _('Missing the ID of the shipment processor in the context'),
 
2295
                    )
 
2296
                if not context.get('shipment_id', False):
 
2297
                    raise osv.except_osv(
 
2298
                        _('Processing Error'),
 
2299
                        _('Missing the ID of the shipment in the context'),
 
2300
                    )
 
2301
 
 
2302
                if not context.get('draft_packing_id', False):
 
2303
                    raise osv.except_osv(
 
2304
                        _('Processing Error'),
 
2305
                        _('Missing the ID of the draft packing in the context'),
 
2306
                    )
 
2307
 
 
2308
                ship_proc = ship_proc_obj.browse(cr, uid, context['shipment_proc_id'], context=context)
 
2309
                shipment_id = context['shipment_id']
1569
2310
                draft_packing_id = context['draft_packing_id']
1570
 
                data = context['partial_datas_shipment'][draft_shipment_id][draft_packing_id]
1571
 
                shipment_id = context['shipment_id']
1572
 
                # We have a backorder_id, no shipment_id
1573
 
                # -> we have just created a shipment
1574
 
                # the created packing object has no stock_move
1575
 
                # - we create the sock move from the data in context
1576
 
                # - if no shipment in context, create a new shipment object
1577
 
                # - generate the data from the new picking object
1578
 
                # - create the pack families
1579
 
                for from_pack in data:
1580
 
                    for to_pack in data[from_pack]:
1581
 
                        # total number of packs
1582
 
                        total_num = to_pack - from_pack + 1
1583
 
                        # number of selected packs to ship
1584
 
                        # note: when the data is generated, lines without selected_number are not kept, so we have nothing to check here
1585
 
                        selected_number = data[from_pack][to_pack][0]['selected_number']
1586
 
                        # we take the packs with the highest numbers
1587
 
                        # new moves
1588
 
                        selected_from_pack = to_pack - selected_number + 1
1589
 
                        selected_to_pack = to_pack
1590
 
                        # update initial moves
1591
 
                        if selected_number == total_num:
1592
 
                            # if all packs have been selected, from/to are set to 0
1593
 
                            initial_from_pack = 0
1594
 
                            initial_to_pack = 0
1595
 
                        else:
1596
 
                            initial_from_pack = from_pack
1597
 
                            initial_to_pack = to_pack - selected_number
1598
 
                        
1599
 
                        # find the corresponding moves
1600
 
                        moves_ids = move_obj.search(cr, uid, [('picking_id', '=', draft_packing_id),
1601
 
                                                              ('from_pack', '=', from_pack),
1602
 
                                                              ('to_pack', '=', to_pack),], context=context)
1603
 
                        
1604
 
                        for move in move_obj.browse(cr, uid, moves_ids, context=context):
1605
 
                            # we compute the selected quantity
1606
 
                            selected_qty = move.qty_per_pack * selected_number
1607
 
                            # create the new move - store the back move from draft **packing** object
1608
 
                            new_move = move_obj.copy(cr, uid, move.id, {'picking_id': new_packing_id,
1609
 
                                                                        'product_qty': selected_qty,
1610
 
                                                                        'from_pack': selected_from_pack,
1611
 
                                                                        'to_pack': selected_to_pack,
1612
 
                                                                        'backmove_packing_id': move.id,}, context=context)
1613
 
                            
1614
 
                            # update corresponding initial move
1615
 
                            initial_qty = move.product_qty
1616
 
                            initial_qty = max(initial_qty - selected_qty, 0)
1617
 
                            # if all packs have been selected, from/to have been set to 0
1618
 
                            # update the original move object - the corresponding original shipment (draft)
1619
 
                            # is automatically updated generically in the write method
1620
 
                            move_obj.write(cr, uid, [move.id], {'product_qty': initial_qty,
1621
 
                                                                'from_pack': initial_from_pack,
1622
 
                                                                'to_pack': initial_to_pack}, context=context)
1623
 
            
 
2311
 
 
2312
                for family in ship_proc.family_ids:
 
2313
                    if family.draft_packing_id.id != draft_packing_id or family.selected_number == 0.00:
 
2314
                        continue
 
2315
 
 
2316
                    selected_from_pack = family.to_pack - family.selected_number + 1
 
2317
 
 
2318
                    if family.selected_number == int(family.num_of_packs):
 
2319
                        initial_from_pack = 0
 
2320
                        initial_to_pack = 0
 
2321
                    else:
 
2322
                        initial_from_pack = family.from_pack
 
2323
                        initial_to_pack = family.to_pack - family.selected_number
 
2324
 
 
2325
                    # find the corresponding moves
 
2326
                    moves_ids = move_obj.search(cr, uid, [
 
2327
                        ('picking_id', '=', family.draft_packing_id.id),
 
2328
                        ('from_pack', '=', family.from_pack),
 
2329
                        ('to_pack', '=', family.to_pack),
 
2330
                    ], context=context)
 
2331
 
 
2332
                    # For corresponding moves
 
2333
                    for move in move_obj.browse(cr, uid, moves_ids, context=context):
 
2334
                        # We compute the selected quantity
 
2335
                        selected_qty = move.qty_per_pack * family.selected_number
 
2336
 
 
2337
                        move_vals = {
 
2338
                            'picking_id': new_packing_id,
 
2339
                            'line_number': move.line_number,
 
2340
                            'product_qty': selected_qty,
 
2341
                            'from_pack': selected_from_pack,
 
2342
                            'to_pack': family.to_pack,
 
2343
                            'backmove_packing_id': move.id,
 
2344
                        }
 
2345
 
 
2346
                        move_obj.copy(cr, uid, move.id, move_vals, context=context)
 
2347
 
 
2348
                        # Update corresponding initial move
 
2349
                        initial_qty = max(move.product_qty - selected_qty, 0)
 
2350
 
 
2351
 
 
2352
                        # if all packs have been selected, from/to have been set to 0
 
2353
                        # update the original move object - the corresponding original shipment (draft)
 
2354
                        # is automatically updated generically in the write method
 
2355
                        move_obj.write(cr, uid, [move.id], {'product_qty': initial_qty,
 
2356
                                                            'from_pack': initial_from_pack,
 
2357
                                                            'to_pack': initial_to_pack}, context=context)
 
2358
 
1624
2359
            if not vals['backorder_id']:
1625
2360
                # creation of packing after ppl validation
1626
2361
                # find an existing shipment or create one - depends on new pick state
1627
2362
                shipment_ids = shipment_obj.search(cr, uid, [('state', '=', 'draft'), ('address_id', '=', vals['address_id'])], context=context)
1628
2363
                # only one 'draft' shipment should be available
1629
 
                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)
1630
 
                
 
2364
                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)
 
2365
                # get rts of corresponding sale order
 
2366
                sale_id = self.read(cr, uid, [new_packing_id], ['sale_id'], context=context)
 
2367
                sale_id = sale_id[0]['sale_id']
 
2368
                if sale_id:
 
2369
                    sale_id = sale_id[0]
 
2370
                    # today
 
2371
                    rts = self.pool.get('sale.order').read(cr, uid, [sale_id], ['ready_to_ship_date'], context=context)[0]['ready_to_ship_date']
 
2372
                else:
 
2373
                    rts = date.today().strftime(db_date_format)
 
2374
                # rts + shipment lt
 
2375
                shipment_lt = fields_tools.get_field_from_company(cr, uid, object=self._name, field='shipment_lead_time', context=context)
 
2376
                rts_obj = datetime.strptime(rts, db_date_format)
 
2377
                rts = rts_obj + relativedelta(days=shipment_lt or 0)
 
2378
                rts = rts.strftime(db_date_format)
 
2379
 
1631
2380
                if not len(shipment_ids):
1632
 
                    # no shipment, create one - no need to specify the state, it's a function
1633
 
                    name = self.pool.get('ir.sequence').get(cr, uid, 'shipment')
1634
 
                    values = {'name': name,
1635
 
                              'address_id': vals['address_id'],
1636
 
                              'sequence_id': self.create_sequence(cr, uid, {'name':name,
1637
 
                                                                            'code':name,
1638
 
                                                                            'prefix':'',
1639
 
                                                                            'padding':2}, context=context)}
1640
 
                    
1641
 
                    shipment_id = shipment_obj.create(cr, uid, values, context=context)
1642
 
                    shipment_obj.log(cr, uid, shipment_id, _('The new Draft Shipment %s has been created.'%name))
 
2381
                    # only create new shipment if we don't already have one through sync
 
2382
                    if not vals['shipment_id'] and not context.get('offline_synchronization', False):
 
2383
                        # no shipment, create one - no need to specify the state, it's a function
 
2384
                        name = self.pool.get('ir.sequence').get(cr, uid, 'shipment')
 
2385
                        addr = self.pool.get('res.partner.address').browse(cr, uid, vals['address_id'], context=context)
 
2386
                        partner_id = addr.partner_id and addr.partner_id.id or False
 
2387
                        values = {'name': name,
 
2388
                                  'address_id': vals['address_id'],
 
2389
                                  'partner_id2': partner_id,
 
2390
                                  'shipment_expected_date': rts,
 
2391
                                  'shipment_actual_date': rts,
 
2392
                                  'sequence_id': self.create_sequence(cr, uid, {'name':name,
 
2393
                                                                                'code':name,
 
2394
                                                                                'prefix':'',
 
2395
                                                                                'padding':2}, context=context)}
 
2396
 
 
2397
                        shipment_id = shipment_obj.create(cr, uid, values, context=context)
 
2398
                        shipment_obj.log(cr, uid, shipment_id, _('The new Draft Shipment %s has been created.') % (name,))
 
2399
                    else:
 
2400
                        shipment_id = vals['shipment_id']
1643
2401
                else:
1644
2402
                    shipment_id = shipment_ids[0]
1645
 
                    shipment_name = shipment_obj.browse(cr, uid, shipment_id, context=context).name
1646
 
                    shipment_obj.log(cr, uid, shipment_id, _('The ppl has been added to the existing Draft Shipment %s.'%shipment_name))
1647
 
            
 
2403
                    shipment = shipment_obj.browse(cr, uid, shipment_id, context=context)
 
2404
                    # if expected ship date of shipment is greater than rts, update shipment_expected_date and shipment_actual_date
 
2405
                    shipment_expected = datetime.strptime(shipment.shipment_expected_date, db_datetime_format)
 
2406
                    if rts_obj < shipment_expected:
 
2407
                        shipment.write({'shipment_expected_date': rts, 'shipment_actual_date': rts, }, context=context)
 
2408
                    shipment_name = shipment.name
 
2409
                    shipment_obj.log(cr, uid, shipment_id, _('The ppl has been added to the existing Draft Shipment %s.') % (shipment_name,))
 
2410
 
1648
2411
            # update the new pick with shipment_id
1649
2412
            self.write(cr, uid, [new_packing_id], {'shipment_id': shipment_id}, context=context)
1650
 
            
 
2413
 
1651
2414
        return new_packing_id
1652
2415
 
1653
2416
    def _hook_action_assign_raise_exception(self, cr, uid, ids, context=None, *args, **kwargs):
1654
2417
        '''
1655
2418
        Please copy this to your module's method also.
1656
2419
        This hook belongs to the action_assign method from stock>stock.py>stock_picking class
1657
 
        
 
2420
 
1658
2421
        - allow to choose wether or not an exception should be raised in case of no stock move
1659
2422
        '''
1660
2423
        res = super(stock_picking, self)._hook_action_assign_raise_exception(cr, uid, ids, context=context, *args, **kwargs)
1661
2424
        return res and False
1662
 
    
 
2425
 
1663
2426
    def _hook_log_picking_modify_message(self, cr, uid, ids, context=None, *args, **kwargs):
1664
2427
        '''
1665
2428
        stock>stock.py>log_picking
1670
2433
        # if the picking is converted to standard, and state is confirmed
1671
2434
        if pick.converted_to_standard and pick.state == 'confirmed':
1672
2435
            return 'The Preparation Picking has been converted to simple Out. ' + message
 
2436
        if pick.type == 'out' and pick.subtype == 'picking':
 
2437
            kwargs['message'] = message.replace('Delivery Order', 'Picking Ticket')
 
2438
        elif pick.type == 'out' and pick.subtype == 'packing':
 
2439
            kwargs['message'] = message.replace('Delivery Order', 'Packing List')
 
2440
        elif pick.type == 'out' and pick.subtype == 'ppl':
 
2441
            kwargs['message'] = message.replace('Delivery Order', 'Pre-Packing List')
1673
2442
        return super(stock_picking, self)._hook_log_picking_modify_message(cr, uid, ids, context, *args, **kwargs)
1674
 
    
 
2443
 
 
2444
    def _get_keep_move(self, cr, uid, ids, context=None):
 
2445
        '''
 
2446
        Returns for each stock move of the draft PT, if we should keep it
 
2447
        '''
 
2448
        pick_moves = {}
 
2449
        for pick in self.browse(cr, uid, ids, context=context):
 
2450
            for bo in pick.backorder_ids:
 
2451
                if not bo.is_completed()[bo.id]:
 
2452
                    pick_moves.setdefault(pick.id, {})
 
2453
                    for m in bo.move_lines:
 
2454
                        if m.state not in ('done', 'cancel'):
 
2455
                            pick_moves[pick.id].setdefault(m.backmove_id.id, True)
 
2456
 
 
2457
        return pick_moves
 
2458
 
1675
2459
    def convert_to_standard(self, cr, uid, ids, context=None):
1676
2460
        '''
1677
2461
        check of back orders exists, if not, convert to standard: change subtype to standard, and trigger workflow
1678
 
        
 
2462
 
1679
2463
        only one picking object at a time
1680
2464
        '''
 
2465
        if not context:
 
2466
            context = {}
 
2467
        # objects
 
2468
        move_obj = self.pool.get('stock.move')
 
2469
        date_tools = self.pool.get('date.tools')
 
2470
        fields_tools = self.pool.get('fields.tools')
 
2471
        db_date_format = date_tools.get_db_date_format(cr, uid, context=context)
 
2472
        moves_states = {}
 
2473
        pick_to_check = set()
 
2474
 
1681
2475
        for obj in self.browse(cr, uid, ids, context=context):
1682
 
            if obj.backorder_ids:
1683
 
                raise osv.except_osv(_('Warning !'), _('You cannot convert a picking which has already been started.'))
1684
 
            
 
2476
            # the convert function should only be called on draft picking ticket
 
2477
            assert obj.subtype == 'picking' and obj.state in ('draft', 'assigned'), 'the convert function should only be called on draft picking ticket objects'
 
2478
#            if self.has_picking_ticket_in_progress(cr, uid, [obj.id], context=context)[obj.id]:
 
2479
#                    raise osv.except_osv(_('Warning !'), _('Some Picking Tickets are in progress. Return products to stock from ppl and shipment and try again.'))
 
2480
 
1685
2481
            # log a message concerning the conversion
1686
2482
            new_name = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.out')
1687
 
            self.log(cr, uid, obj.id, _('The Preparation Picking (%s) has been converted to simple Out (%s).'%(obj.name, new_name)))
 
2483
            self.log(cr, uid, obj.id, _('The Preparation Picking (%s) has been converted to simple Out (%s).') % (obj.name, new_name))
 
2484
 
 
2485
            keep_move = self._get_keep_move(cr, uid, [obj.id], context=context).get(obj.id, {})
 
2486
 
1688
2487
            # change subtype and name
1689
 
            obj.write({'name': new_name,
1690
 
                       'subtype': 'standard',
1691
 
                       'converted_to_standard': True,
1692
 
                       }, context=context)
 
2488
            default_vals = {'name': new_name,
 
2489
                            'move_lines': [],
 
2490
                            'subtype': 'standard',
 
2491
                            'converted_to_standard': True,
 
2492
                            'backorder_id': False,
 
2493
                           }
 
2494
            new_pick_id = False
 
2495
            new_lines = []
 
2496
 
 
2497
            if obj.state == 'draft' and keep_move:
 
2498
                context['wkf_copy'] = True
 
2499
                new_pick_id = self.copy(cr, uid, obj.id, default_vals, context=context)
 
2500
                pick_to_check.add(obj.id)
 
2501
            else:
 
2502
                self.write(cr, uid, obj.id, default_vals, context=context)
 
2503
 
 
2504
            if obj.backorder_id:
 
2505
                pick_to_check.add(obj.backorder_id.id)
 
2506
 
1693
2507
            # all destination location of the stock moves must be output location of warehouse - lot_output_id
 
2508
            # if corresponding sale order, date and date_expected are updated to rts + shipment lt
1694
2509
            for move in obj.move_lines:
1695
 
                move.write({'location_dest_id': obj.warehouse_id.lot_output_id.id,}, context=context)
1696
 
            # trigger workflow
1697
 
            self.draft_force_assign(cr, uid, [obj.id])
1698
 
        
 
2510
                # was previously set to confirmed/assigned, otherwise, when we confirm the stock picking,
 
2511
                # using draft_force_assign, the moves are not treated because not in draft
 
2512
                # and the corresponding chain location on location_dest_id was not computed
 
2513
                # we therefore set them back in draft state before treatment
 
2514
                if move.product_qty == 0.0:
 
2515
                    vals = {'state': 'done'}
 
2516
                else:
 
2517
                    # Save the state of this stock move to set it before action_assign()
 
2518
                    moves_states[move.id] = move.state
 
2519
                    if move.state not in ('cancel', 'done'):
 
2520
                        vals = {'state': 'draft'}
 
2521
                    else:
 
2522
                        vals = {'state': move.state}
 
2523
                vals.update({'backmove_id': False})
 
2524
                # If the move comes from a DPO, don't change the destination location
 
2525
                if not move.dpo_id:
 
2526
                    vals.update({'location_dest_id': obj.warehouse_id.lot_output_id.id})
 
2527
 
 
2528
                if obj.sale_id:
 
2529
                    # compute date
 
2530
                    shipment_lt = fields_tools.get_field_from_company(cr, uid, object=self._name, field='shipment_lead_time', context=context)
 
2531
                    rts = datetime.strptime(obj.sale_id.ready_to_ship_date, db_date_format)
 
2532
                    rts = rts + relativedelta(days=shipment_lt or 0)
 
2533
                    rts = rts.strftime(db_date_format)
 
2534
                    vals.update({'date': rts, 'date_expected': rts, 'state': 'draft'})
 
2535
 
 
2536
                if not new_pick_id:
 
2537
                    move.write(vals, context=context)
 
2538
                    keep_backmove = move_obj.search(cr, uid, [('backmove_id', '=', move.backmove_id.id)], context=context)
 
2539
                    if move.backmove_id and move.backmove_id.product_qty == 0.00:
 
2540
                        keep_backmove = move_obj.search(cr, uid, [('backmove_id', '=', move.backmove_id.id), ('state', 'not in', ('done', 'cancel'))], context=context)
 
2541
                        if not keep_backmove:
 
2542
                            move_obj.write(cr, uid, [move.backmove_id.id], {'state': 'done'}, context=context)
 
2543
                            move_obj.update_linked_documents(cr, uid, move.backmove_id.id, move.id, context=context)
 
2544
                    if move.product_qty == 0.00:
 
2545
                        move.write({'state': 'draft'})
 
2546
                        move.unlink()
 
2547
#                        move.action_done(context=context)
 
2548
                elif move.product_qty != 0.00:
 
2549
                    vals.update({'picking_id': new_pick_id,
 
2550
                                 'line_number': move.line_number,
 
2551
                                 'state': moves_states[move.id],
 
2552
                                 'product_qty': move.product_qty, })
 
2553
 
 
2554
                    new_move_id = move_obj.copy(cr, uid, move.id, vals, context=context)
 
2555
 
 
2556
                    # Compute the chained location as an initial confirmation of move
 
2557
                    if move.state == 'assigned':
 
2558
                        new_move = move_obj.browse(cr, uid, new_move_id, context=context)
 
2559
                        tmp_ac = context.get('action_confirm', False)
 
2560
                        context['action_confirm'] = True
 
2561
                        move_obj.create_chained_picking(cr, uid, [new_move], context=context)
 
2562
                        context['action_confirm'] = tmp_ac
 
2563
 
 
2564
                    # Update all linked objects to avoid close of related documents
 
2565
                    if move.id not in keep_move or not keep_move[move.id]:
 
2566
                        move_obj.update_linked_documents(cr, uid, move.id, new_move_id, context=context)
 
2567
 
 
2568
                    # Set the stock move to done with 0.00 qty
 
2569
                    if move.id in keep_move and keep_move[move.id]:
 
2570
                        m_st = move.state
 
2571
                    else:
 
2572
                        m_st = 'done'
 
2573
                        moves_states[move.id] = 'done'
 
2574
                    move_obj.write(cr, uid, [move.id], {'product_qty': 0.00,
 
2575
                                                        'state': m_st}, context=context)
 
2576
 
 
2577
                    new_lines.append(new_move_id)
 
2578
 
 
2579
            if pick_to_check:
 
2580
                for ptc_id in pick_to_check:
 
2581
                    ptc = self.browse(cr, uid, ptc_id, context=context)
 
2582
                    if all(m.product_qty == 0.00 and m.state in ('done', 'cancel') for m in ptc.move_lines):
 
2583
                        ptc.action_done(context=context)
 
2584
 
 
2585
            # trigger workflow (confirm picking)
 
2586
            self.draft_force_assign(cr, uid, [new_pick_id or obj.id])
 
2587
 
 
2588
            for s in moves_states:
 
2589
                move_obj.write(cr, uid, [s], {'state': moves_states[s]}, context=context)
 
2590
 
 
2591
            # check availability
 
2592
            self.action_assign(cr, uid, [new_pick_id or obj.id], context=context)
 
2593
 
 
2594
            if 'assigned' in moves_states.values():
 
2595
                # Add an empty write to display the 'Process' button on OUT
 
2596
                self.write(cr, uid, [new_pick_id or obj.id], {'state': 'assigned'}, context=context)
 
2597
 
1699
2598
            # TODO which behavior
1700
2599
            data_obj = self.pool.get('ir.model.data')
1701
2600
            view_id = data_obj.get_object_reference(cr, uid, 'stock', 'view_picking_out_form')
1702
2601
            view_id = view_id and view_id[1] or False
1703
 
            # display newly created picking ticket
 
2602
            context.update({'picking_type': 'delivery_order', 'view_id': view_id})
1704
2603
            return {'name':_("Delivery Orders"),
1705
2604
                    'view_mode': 'form,tree',
1706
2605
                    'view_id': [view_id],
1707
2606
                    'view_type': 'form',
1708
2607
                    'res_model': 'stock.picking',
1709
 
                    'res_id': obj.id,
 
2608
                    'res_id': new_pick_id or obj.id,
1710
2609
                    'type': 'ir.actions.act_window',
1711
2610
                    'target': 'crush',
1712
 
                    }
1713
 
    
 
2611
                    'context': context,
 
2612
                    }
 
2613
 
 
2614
    def convert_to_pick(self, cr, uid, ids, context=None):
 
2615
        '''
 
2616
        Change simple OUTs to draft Picking Tickets
 
2617
        '''
 
2618
        context = context or {}
 
2619
 
 
2620
        # Objects
 
2621
        move_obj = self.pool.get('stock.move')
 
2622
        data_obj = self.pool.get('ir.model.data')
 
2623
 
 
2624
        move_to_update = []
 
2625
 
 
2626
        view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_form')
 
2627
        view_id = view_id and view_id[1] or False
 
2628
 
 
2629
        for out in self.browse(cr, uid, ids, context=context):
 
2630
            if out.state in ('cancel', 'done'):
 
2631
                raise osv.except_osv(_('Error'), _('You cannot convert %s delivery orders') % (out.state == 'cancel' and _('Canceled') or _('Done')))
 
2632
 
 
2633
            # log a message concerning the conversion
 
2634
            new_name = self.pool.get('ir.sequence').get(cr, uid, 'picking.ticket')
 
2635
 
 
2636
            # change subtype and name
 
2637
            default_vals = {'name': new_name,
 
2638
                            'subtype': 'picking',
 
2639
                            'converted_to_standard': False,
 
2640
                            'state': 'draft',
 
2641
                            'sequence_id': self.create_sequence(cr, uid, {'name':new_name,
 
2642
                                                                          'code':new_name,
 
2643
                                                                          'prefix':'',
 
2644
                                                                          'padding':2}, context=context)
 
2645
                           }
 
2646
 
 
2647
            self.write(cr, uid, [out.id], default_vals, context=context)
 
2648
            wf_service = netsvc.LocalService("workflow")
 
2649
            wf_service.trg_validate(uid, 'stock.picking', out.id, 'convert_to_picking_ticket', cr)
 
2650
            # we force availability
 
2651
 
 
2652
            self.log(cr, uid, out.id, _('The Delivery order (%s) has been converted to draft Picking Ticket (%s).') % (out.name, new_name), context={'view_id': view_id, 'picking_type': 'picking'})
 
2653
 
 
2654
            for move in out.move_lines:
 
2655
                move_to_update.append(move.id)
 
2656
 
 
2657
        pack_loc_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'stock_location_packing')[1]
 
2658
        move_obj.write(cr, uid, move_to_update, {'location_dest_id': pack_loc_id}, context=context)
 
2659
 
 
2660
        context.update({'picking_type': 'picking'})
 
2661
        return {'name': _('Picking Tickets'),
 
2662
                'view_mode': 'form,tree',
 
2663
                'view_id': [view_id],
 
2664
                'view_type': 'form',
 
2665
                'res_model': 'stock.picking',
 
2666
                'res_id': out.id,
 
2667
                'type': 'ir.actions.act_window',
 
2668
                'target': 'crush',
 
2669
                'context': context}
 
2670
 
 
2671
    def do_partial_out(self, cr, uid, wizard_ids, context=None):
 
2672
        """
 
2673
        Process the stock picking from selected stock moves
 
2674
 
 
2675
        BE CAREFUL: the wizard_ids parameters is the IDs of the outgoing.delivery.processor objects,
 
2676
        not those of stock.picking objects
 
2677
        """
 
2678
        return self.do_partial(cr, uid, wizard_ids, 'outgoing.delivery.processor', context)
 
2679
 
 
2680
    def do_partial(self, cr, uid, wizard_ids, proc_model=False, context=None):
 
2681
        """
 
2682
        Process the stock picking from selected stock moves
 
2683
 
 
2684
        BE CAREFUL: the wizard_ids parameters is the IDs of the internal.picking.processor objects,
 
2685
        not those of stock.picking objects
 
2686
        """
 
2687
        # Objects
 
2688
        proc_obj = self.pool.get('internal.picking.processor')
 
2689
        picking_obj = self.pool.get('stock.picking')
 
2690
        sequence_obj = self.pool.get('ir.sequence')
 
2691
        uom_obj = self.pool.get('product.uom')
 
2692
        move_obj = self.pool.get('stock.move')
 
2693
        wf_service = netsvc.LocalService("workflow")
 
2694
 
 
2695
        res = {}
 
2696
 
 
2697
        if proc_model:
 
2698
            proc_obj = self.pool.get(proc_model)
 
2699
 
 
2700
        if context is None:
 
2701
            context = {}
 
2702
 
 
2703
        if isinstance(wizard_ids, (int, long)):
 
2704
            wizard_ids = [wizard_ids]
 
2705
 
 
2706
        for wizard in proc_obj.browse(cr, uid, wizard_ids, context=context):
 
2707
            picking = wizard.picking_id
 
2708
            new_picking_id = False
 
2709
            processed_moves = []
 
2710
            move_data = {}
 
2711
 
 
2712
            for line in wizard.move_ids:
 
2713
                move = line.move_id
 
2714
                first = False
 
2715
 
 
2716
                if move.id not in move_data:
 
2717
                    move_data.setdefault(move.id, {
 
2718
                        'original_qty': move.product_qty,
 
2719
                        'processed_qty': 0.00,
 
2720
                        })
 
2721
                    first = True
 
2722
 
 
2723
                if line.quantity <= 0.00:
 
2724
                    continue
 
2725
 
 
2726
                orig_qty = move.product_qty
 
2727
                if move.original_qty_partial and move.original_qty_partial != -1:
 
2728
                    orig_qty = move.original_qty_partial
 
2729
 
 
2730
                if line.uom_id.id != move.product_uom.id:
 
2731
                    quantity = uom_obj._compute_qty(cr, uid, line.uom_id.id, line.quantity, move.product_uom.id)
 
2732
                else:
 
2733
                    quantity = line.quantity
 
2734
 
 
2735
                move_data[move.id]['processed_qty'] += quantity
 
2736
 
 
2737
                values = {
 
2738
                    'line_number': move.line_number,
 
2739
                    'product_qty': line.quantity,
 
2740
                    'product_uos_qty': line.quantity,
 
2741
                    'state': 'assigned',
 
2742
                    'move_dest_id': False,
 
2743
                    'price_unit': move.price_unit,
 
2744
                    'processed_stock_move': True,
 
2745
                    'prodlot_id': line.prodlot_id and line.prodlot_id.id or False,
 
2746
                    'asset_id': line.asset_id and line.asset_id.id or False,
 
2747
                    'composition_list_id': line.composition_list_id and line.composition_list_id.id or False,
 
2748
                    'original_qty_partial': orig_qty,
 
2749
                }
 
2750
 
 
2751
                if quantity < move.product_qty and move_data[move.id]['original_qty'] > move_data[move.id]['processed_qty']:
 
2752
                    # Create a new move
 
2753
                    new_move_id = move_obj.copy(cr, uid, move.id, values, context=context)
 
2754
                    processed_moves.append(new_move_id)
 
2755
                    # Update the original move
 
2756
                    wr_vals = {
 
2757
                        'product_qty': move.product_qty - quantity,
 
2758
                        'product_uos_qty': move.product_qty - quantity,
 
2759
                    }
 
2760
                    move_obj.write(cr, uid, [move.id], wr_vals, context=context)
 
2761
                else:
 
2762
                    # Update the original move
 
2763
                    move_obj.write(cr, uid, [move.id], values, context=context)
 
2764
                    processed_moves.append(move.id)
 
2765
 
 
2766
            need_new_picking = False
 
2767
            for move_vals in move_data.values():
 
2768
                if move_vals['original_qty'] != move_vals['processed_qty']:
 
2769
                    need_new_picking = True
 
2770
                    break
 
2771
 
 
2772
            if need_new_picking:
 
2773
                cp_vals = {
 
2774
                    'name': sequence_obj.get(cr, uid, 'stock.picking.%s' % (picking.type)),
 
2775
                    'move_lines' : [],
 
2776
                    'state':'draft',
 
2777
                }
 
2778
                new_picking_id = picking_obj.copy(cr, uid, picking.id, cp_vals, context=context)
 
2779
                move_obj.write(cr, uid, processed_moves, {'picking_id': new_picking_id}, context=context)
 
2780
 
 
2781
            # At first we confirm the new picking (if necessary)
 
2782
            if new_picking_id:
 
2783
                self.write(cr, uid, [picking.id], {'backorder_id': new_picking_id}, context=context)
 
2784
                # Claim specific code
 
2785
                self._claim_registration(cr, uid, wizard, new_picking_id, context=context)
 
2786
                # We confirm the new picking after its name was possibly modified by custom code - so the link message (top message) is correct
 
2787
                wf_service.trg_validate(uid, 'stock.picking', new_picking_id, 'button_confirm', cr)
 
2788
                # Then we finish the good picking
 
2789
                if not wizard.register_a_claim or (wizard.register_a_claim and wizard.claim_type != 'return'):
 
2790
                    self.action_move(cr, uid, [new_picking_id])
 
2791
                    wf_service.trg_validate(uid, 'stock.picking', new_picking_id, 'button_done', cr)
 
2792
                    # UF-1617: Hook a method to create the sync messages for some extra objects: batch number, asset once the OUT/partial is done
 
2793
                    self._hook_create_sync_messages(cr, uid, new_picking_id, context)
 
2794
 
 
2795
                wf_service.trg_write(uid, 'stock.picking', picking.id, cr)
 
2796
                delivered_pack_id = new_picking_id
 
2797
            else:
 
2798
                # Claim specific code
 
2799
                self._claim_registration(cr, uid, wizard, picking.id, context=context)
 
2800
                if not wizard.register_a_claim or (wizard.register_a_claim and wizard.claim_type != 'return'):
 
2801
                    self.action_move(cr, uid, [picking.id])
 
2802
                    wf_service.trg_validate(uid, 'stock.picking', picking.id, 'button_done', cr)
 
2803
                    # UF-1617: Hook a method to create the sync messages for some extra objects: batch number, asset once the OUT/partial is done
 
2804
                    self._hook_create_sync_messages(cr, uid, [picking.id], context)
 
2805
 
 
2806
                delivered_pack_id = picking.id
 
2807
 
 
2808
            # UF-1617: set the delivered_pack_id (new or original) to become already_shipped
 
2809
            self.write(cr, uid, [delivered_pack_id], {'already_shipped': True})
 
2810
 
 
2811
            delivered_pack = self.browse(cr, uid, delivered_pack_id, context=context)
 
2812
            res[picking.id] = {'delivered_picking': delivered_pack.id or False}
 
2813
 
 
2814
        return res
 
2815
 
1714
2816
    def create_picking(self, cr, uid, ids, context=None):
1715
2817
        '''
1716
 
        open the wizard to create (partial) picking tickets
1717
 
        '''
1718
 
        # we need the context for the wizard switch
 
2818
        Open the wizard to create (partial) picking tickets
 
2819
        '''
 
2820
        # Objects
 
2821
        proc_obj = self.pool.get('create.picking.processor')
 
2822
 
 
2823
        if isinstance(ids, (int, long)):
 
2824
            ids = [ids]
 
2825
 
 
2826
        processor_id = proc_obj.create(cr, uid, {'picking_id': ids[0]}, context=context)
 
2827
        proc_obj.create_lines(cr, uid, processor_id, context=context)
 
2828
 
 
2829
        return {
 
2830
            'type': 'ir.actions.act_window',
 
2831
            'res_model': proc_obj._name,
 
2832
            'res_id': processor_id,
 
2833
            'view_type': 'form',
 
2834
            'view_mode': 'form',
 
2835
            'target': 'new',
 
2836
        }
 
2837
 
 
2838
    def do_create_picking(self, cr, uid, wizard_ids, context=None):
 
2839
        '''
 
2840
        Create the picking ticket from selected stock moves
 
2841
 
 
2842
        BE CAREFUL: the wizard_ids parameters is the IDs of the ppl.processor objects,
 
2843
        not those of stock.picking objects
 
2844
        '''
 
2845
        # Objects
 
2846
        move_obj = self.pool.get('stock.move')
 
2847
        uom_obj = self.pool.get('product.uom')
 
2848
        cp_proc_obj = self.pool.get('create.picking.processor')
 
2849
 
1719
2850
        if context is None:
1720
2851
            context = {}
1721
 
        
1722
 
        # data
1723
 
        name = _("Create Picking Ticket")
1724
 
        model = 'create.picking'
1725
 
        step = 'create'
1726
 
        wiz_obj = self.pool.get('wizard')
1727
 
        # open the selected wizard
1728
 
        return wiz_obj.open_wizard(cr, uid, ids, name=name, model=model, step=step, context=context)
1729
 
        
1730
 
    def do_create_picking(self, cr, uid, ids, context=None):
1731
 
        '''
1732
 
        create the picking ticket from selected stock moves
1733
 
        '''
1734
 
        assert context, 'context is not defined'
1735
 
        assert 'partial_datas' in context, 'partial datas not present in context'
1736
 
        partial_datas = context['partial_datas']
1737
 
        
1738
 
        # stock move object
1739
 
        move_obj = self.pool.get('stock.move')
1740
 
        
1741
 
        for pick in self.browse(cr, uid, ids, context=context):
1742
 
            # create the new picking object
1743
 
            # a sequence for each draft picking ticket is used for the picking ticket
1744
 
            sequence = pick.sequence_id
1745
 
            ticket_number = sequence.get_id(test='id', context=context)
1746
 
            new_pick_id = self.copy(cr, uid, pick.id, {'name': pick.name + '-' + ticket_number,
1747
 
                                                       'backorder_id': pick.id,
1748
 
                                                       'move_lines': []}, context=dict(context, allow_copy=True,))
1749
 
            # create stock moves corresponding to partial datas
 
2852
 
 
2853
        if isinstance(wizard_ids, (int, long)):
 
2854
            wizard_ids = [wizard_ids]
 
2855
 
 
2856
        if not wizard_ids:
 
2857
            raise osv.except_osv(
 
2858
                _('Processing Error'),
 
2859
                _('No data to process !')
 
2860
            )
 
2861
 
 
2862
        for wizard in cp_proc_obj.browse(cr, uid, wizard_ids, context=context):
 
2863
            picking = wizard.picking_id
 
2864
 
 
2865
            move_data = {}
 
2866
 
 
2867
            # Create the new picking object
 
2868
            # A sequence for each draft picking ticket is used for the picking ticket
 
2869
            sequence = picking.sequence_id
 
2870
            ticket_number = sequence.get_id(code_or_id='id', context=context)
 
2871
 
 
2872
            copy_data = {
 
2873
                'name': '%s-%s' % (picking.name or 'NoName/000', ticket_number),
 
2874
                'backorder_id': picking.id,
 
2875
                'move_lines': [],
 
2876
            }
 
2877
 
 
2878
            tmp_allow_copy = context.get('allow_copy')
 
2879
            context.update({
 
2880
                'wkf_copy': True,
 
2881
                'allow_copy': True,
 
2882
            })
 
2883
 
 
2884
            new_picking_id = self.copy(cr, uid, picking.id, copy_data, context=context)
 
2885
            context['allow_copy'] = tmp_allow_copy
 
2886
 
 
2887
            # Create stock moves corresponding to processing lines
1750
2888
            # for now, each new line from the wizard corresponds to a new stock.move
1751
2889
            # it could be interesting to regroup according to production lot/asset id
1752
 
            move_ids = partial_datas[pick.id].keys()
1753
 
            for move in move_obj.browse(cr, uid, move_ids, context=context):
1754
 
                # qty selected
1755
 
                count = 0
1756
 
                # initial qty
1757
 
                initial_qty = move.product_qty
1758
 
                for partial in partial_datas[pick.id][move.id]:
1759
 
                    # integrity check
1760
 
                    partial['product_id'] == move.product_id.id
1761
 
                    partial['product_uom'] == move.product_uom.id
1762
 
                    # the quantity
1763
 
                    count = count + partial['product_qty']
1764
 
                    # copy the stock move and set the quantity
1765
 
                    new_move = move_obj.copy(cr, uid, move.id, {'picking_id': new_pick_id,
1766
 
                                                                'product_qty': partial['product_qty'],
1767
 
                                                                'prodlot_id': partial['prodlot_id'],
1768
 
                                                                'asset_id': partial['asset_id'],
1769
 
                                                                'backmove_id': move.id}, context=context)
1770
 
                # decrement the initial move, cannot be less than zero
1771
 
                initial_qty = max(initial_qty - count, 0)
1772
 
                move_obj.write(cr, uid, [move.id], {'product_qty': initial_qty}, context=context)
1773
 
                
1774
 
            # confirm the new picking ticket
 
2890
            for line in wizard.move_ids:
 
2891
                move_data.setdefault(line.move_id.id, {
 
2892
                    'initial_qty': line.move_id.product_qty,
 
2893
                    'processed_qty': 0.00,
 
2894
                })
 
2895
 
 
2896
                if line.uom_id.id != line.move_id.product_uom.id:
 
2897
                    processed_qty = uom_obj._compute_qty(cr, uid, line.uom_id.id, line.quantity, line.move_id.product_uom.id)
 
2898
                else:
 
2899
                    processed_qty = line.quantity
 
2900
 
 
2901
                move_data[line.move_id.id]['processed_qty'] += processed_qty
 
2902
 
 
2903
                # Copy the stock move and set the quantity
 
2904
                cp_values = {
 
2905
                    'picking_id': new_picking_id,
 
2906
                    'product_qty': line.quantity,
 
2907
                    'product_uom': line.uom_id.id,
 
2908
                    'product_uos_qty': line.quantity,
 
2909
                    'product_uos': line.uom_id.id,
 
2910
                    'prodlot_id': line.prodlot_id and line.prodlot_id.id,
 
2911
                    'asset_id': line.asset_id and line.asset_id.id,
 
2912
                    'composition_list_id': line.composition_list_id and line.composition_list_id.id,
 
2913
                    'pt_created': True,
 
2914
                    'backmove_id': line.move_id.id,
 
2915
                }
 
2916
                context['keepLineNumber'] = True
 
2917
                move_obj.copy(cr, uid, line.move_id.id, cp_values, context=context)
 
2918
                context['keepLineNumber'] = False
 
2919
 
 
2920
 
 
2921
            # Update initial stock moves
 
2922
            for move_id, move_vals in move_data.iteritems():
 
2923
                initial_qty = max(move_vals['initial_qty'] - move_vals['processed_qty'], 0.00)
 
2924
                wr_vals = {
 
2925
                    'product_qty': initial_qty,
 
2926
                    'proudct_uos_qty': initial_qty,
 
2927
                    'processed_stock_move': True,
 
2928
                }
 
2929
                context['keepLineNumber'] = True
 
2930
                move_obj.write(cr, uid, [move_id], wr_vals, context=context)
 
2931
                context['keepLineNumber'] = False
 
2932
 
 
2933
 
 
2934
            # Confirm the new picking ticket
1775
2935
            wf_service = netsvc.LocalService("workflow")
1776
 
            wf_service.trg_validate(uid, 'stock.picking', new_pick_id, 'button_confirm', cr)
1777
 
            # we force availability
1778
 
            self.force_assign(cr, uid, [new_pick_id])
1779
 
        
1780
 
        # TODO which behavior
1781
 
        #return {'type': 'ir.actions.act_window_close'}
1782
 
        data_obj = self.pool.get('ir.model.data')
1783
 
        view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_form')
1784
 
        view_id = view_id and view_id[1] or False
1785
 
        # display newly created picking ticket
 
2936
            wf_service.trg_validate(uid, 'stock.picking', new_picking_id, 'button_confirm', cr)
 
2937
            # We force availability
 
2938
            self.force_assign(cr, uid, [new_picking_id])
 
2939
 
 
2940
        # Just to avoid an error on kit test because view_picking_ticket_form is not still loaded when test is ran
 
2941
        msf_outgoing = self.pool.get('ir.module.module').search(cr, uid, [('name', '=', 'msf_outgoing'), ('state', '=', 'installed')], context=context)
 
2942
        if not msf_outgoing:
 
2943
            view_id = False
 
2944
        else:
 
2945
            data_obj = self.pool.get('ir.model.data')
 
2946
            view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_form')
 
2947
            view_id = view_id and view_id[1] or False
 
2948
 
 
2949
        context.update({'picking_type': 'picking_ticket', 'picking_screen': True})
 
2950
 
1786
2951
        return {'name':_("Picking Ticket"),
1787
2952
                'view_mode': 'form,tree',
1788
2953
                'view_id': [view_id],
1789
2954
                'view_type': 'form',
1790
2955
                'res_model': 'stock.picking',
1791
 
                'res_id': pick.id,
 
2956
                'res_id': new_picking_id,
1792
2957
                'type': 'ir.actions.act_window',
1793
2958
                'target': 'crush',
 
2959
                'context': context,
1794
2960
                }
1795
 
        
 
2961
 
1796
2962
    def validate_picking(self, cr, uid, ids, context=None):
1797
2963
        '''
1798
 
        validate the picking ticket
1799
 
        '''
1800
 
        # we need the context for the wizard switch
 
2964
        Open the wizard to validate the picking ticket
 
2965
        '''
 
2966
        # Objects
 
2967
        proc_obj = self.pool.get('validate.picking.processor')
 
2968
 
 
2969
        if isinstance(ids, (int, long)):
 
2970
            ids = [ids]
 
2971
 
 
2972
        processor_id = proc_obj.create(cr, uid, {'picking_id': ids[0]}, context=context)
 
2973
        proc_obj.create_lines(cr, uid, processor_id, context=context)
 
2974
 
 
2975
        return {
 
2976
            'type': 'ir.actions.act_window',
 
2977
            'res_model': proc_obj._name,
 
2978
            'res_id': processor_id,
 
2979
            'view_type': 'form',
 
2980
            'view_mode': 'form',
 
2981
            'target': 'new',
 
2982
        }
 
2983
 
 
2984
    def do_validate_picking(self, cr, uid, wizard_ids, context=None):
 
2985
        '''
 
2986
        Validate the picking ticket from selected stock moves
 
2987
 
 
2988
        Move here the logic of validate picking
 
2989
 
 
2990
        BE CAREFUL: the wizard_ids parameters is the IDs of the validate.picking.processor objects,
 
2991
        not those of stock.picking objects
 
2992
        '''
 
2993
        # Objects
 
2994
        date_tools = self.pool.get('date.tools')
 
2995
        move_obj = self.pool.get('stock.move')
 
2996
        uom_obj = self.pool.get('product.uom')
 
2997
        proc_obj = self.pool.get('validate.picking.processor')
 
2998
 
1801
2999
        if context is None:
1802
3000
            context = {}
1803
 
            
1804
 
        # data
1805
 
        name = _("Validate Picking Ticket")
1806
 
        model = 'create.picking'
1807
 
        step = 'validate'
1808
 
        wiz_obj = self.pool.get('wizard')
1809
 
        # open the selected wizard
1810
 
        return wiz_obj.open_wizard(cr, uid, ids, name=name, model=model, step=step, context=context)
1811
 
        
1812
 
    def do_validate_picking(self, cr, uid, ids, context=None):
1813
 
        '''
1814
 
        validate the picking ticket from selected stock moves
1815
 
        
1816
 
        move here the logic of validate picking
1817
 
        available for picking loop
1818
 
        '''
1819
 
        assert context, 'context is not defined'
1820
 
        assert 'partial_datas' in context, 'partial datas not present in context'
1821
 
        partial_datas = context['partial_datas']
1822
 
        
1823
 
        # stock move object
1824
 
        move_obj = self.pool.get('stock.move')
1825
 
        # create picking object
1826
 
        create_picking_obj = self.pool.get('create.picking')
1827
 
        
1828
 
        for pick in self.browse(cr, uid, ids, context=context):
1829
 
            # create stock moves corresponding to partial datas
1830
 
            move_ids = partial_datas[pick.id].keys()
1831
 
            for move in move_obj.browse(cr, uid, move_ids, context=context):
1832
 
                # qty selected
1833
 
                count = 0
1834
 
                # flag to update the first move - if split was performed during the validation, new stock moves are created
1835
 
                first = True
1836
 
                # initial qty
1837
 
                initial_qty = move.product_qty
1838
 
                for partial in partial_datas[pick.id][move.id]:
1839
 
                    # integrity check
1840
 
                    partial['product_id'] == move.product_id.id
1841
 
                    partial['product_uom'] == move.product_uom.id
1842
 
                    # the quantity
1843
 
                    count = count + partial['product_qty']
1844
 
                    if first:
1845
 
                        first = False
1846
 
                        # update existing move
1847
 
                        move_obj.write(cr, uid, [move.id], {'product_qty': partial['product_qty'],
1848
 
                                                            'prodlot_id': partial['prodlot_id'],
1849
 
                                                            'asset_id': partial['asset_id']}, context=context)
1850
 
                    else:
1851
 
                        # split happend during the validation
1852
 
                        # copy the stock move and set the quantity
1853
 
                        new_move = move_obj.copy(cr, uid, move.id, {'state': 'assigned',
1854
 
                                                                    'product_qty': partial['product_qty'],
1855
 
                                                                    'prodlot_id': partial['prodlot_id'],
1856
 
                                                                    'asset_id': partial['asset_id']}, context=context)
1857
 
                # decrement the initial move, cannot be less than zero
1858
 
                diff_qty = initial_qty - count
1859
 
                # the quantity after the validation does not correspond to the picking ticket quantity
1860
 
                # the difference is written back to draft picking ticket
1861
 
                # is positive if some qty was removed during the validation -> draft qty is increased
1862
 
                # is negative if some qty was added during the validation -> draft qty is decreased
1863
 
                if diff_qty != 0:
1864
 
                    backorder_id = pick.backorder_id.id
1865
 
                    assert backorder_id, 'No backorder defined.'
1866
 
                    original_moves = move_obj.search(cr, uid, [('picking_id', '=', backorder_id),
1867
 
                                                               ('product_id', '=', move.product_id.id),
1868
 
                                                               ('product_uom', '=', move.product_uom.id)])
1869
 
                    # original move from the draft picking ticket which will be updated
1870
 
                    original_move = move.backmove_id
1871
 
                    assert len(original_moves) == 1, 'No corresponding stock_move have been found in draft picking ticket for product %s and UOM %s'%(move.product_id.name, move.product_uom.name)
1872
 
                    backorder_qty = move_obj.read(cr, uid, [original_move.id], ['product_qty'], context=context)[0]['product_qty']
1873
 
                    backorder_qty = max(backorder_qty + diff_qty, 0)
1874
 
                    move_obj.write(cr, uid, [original_move.id], {'product_qty': backorder_qty}, context=context)
1875
 
 
1876
 
            # create the new ppl object
1877
 
            ppl_number = pick.name.split("/")[1]
1878
 
            # we want the copy to keep the production lot reference from picking ticket to pre-packing list
1879
 
            new_ppl_id = self.copy(cr, uid, pick.id, {'name': 'PPL/' + ppl_number,
1880
 
                                                      'subtype': 'ppl',
1881
 
                                                      'previous_step_id': pick.id,
1882
 
                                                      'backorder_id': False}, context=dict(context, keep_prodlot=True, allow_copy=True,))
 
3001
 
 
3002
        if isinstance(wizard_ids, (int, long)):
 
3003
            wizard_ids = [wizard_ids]
 
3004
 
 
3005
        if not wizard_ids:
 
3006
            raise osv.except_osv(
 
3007
                _('Processing Error'),
 
3008
                _('No data to process !'),
 
3009
            )
 
3010
 
 
3011
        db_date_format = date_tools.get_db_date_format(cr, uid, context=context)
 
3012
        today = time.strftime(db_date_format)
 
3013
 
 
3014
        for wizard in proc_obj.browse(cr, uid, wizard_ids, context=context):
 
3015
            picking = wizard.picking_id
 
3016
 
 
3017
            move_data = {}
 
3018
 
 
3019
            # Create the new ppl object
 
3020
            ppl_number = picking.name.split("/")[1]
 
3021
            # We want the copy to keep the batch number reference from picking ticket to pre-packing list
 
3022
            cp_vals = {
 
3023
                'name': 'PPL/%s' % ppl_number,
 
3024
                'subtype': 'ppl',
 
3025
                'previous_step_id': picking.id,
 
3026
                'backorder_id': False,
 
3027
                'move_lines': [],
 
3028
            }
 
3029
            context.update({
 
3030
                'keep_prodlot': True,
 
3031
                'allow_copy': True,
 
3032
                'keepLineNumber': True,
 
3033
            })
 
3034
            new_ppl_id = self.copy(cr, uid, picking.id, cp_vals, context=context)
1883
3035
            new_ppl = self.browse(cr, uid, new_ppl_id, context=context)
1884
 
            # update locations of stock moves - if the move quantity is equal to zero, the stock move is removed
1885
 
            for move in new_ppl.move_lines:
1886
 
                if move.product_qty:
1887
 
                    move_obj.write(cr, uid, [move.id], {'initial_location': move.location_id.id,
1888
 
                                                        'location_id': move.location_dest_id.id,
1889
 
                                                        'location_dest_id': new_ppl.warehouse_id.lot_dispatch_id.id}, context=context)
1890
 
                else:
1891
 
                    move_obj.unlink(cr, uid, [move.id], context=context)
1892
 
            
 
3036
            context.update({
 
3037
                'keep_prodlot': False,
 
3038
                'allow_copy': False,
 
3039
                'keepLineNumber': False,
 
3040
            })
 
3041
 
 
3042
            # For each processed lines, save the processed quantity to update the draft picking ticket
 
3043
            # and create a new line on PPL
 
3044
            for line in wizard.move_ids:
 
3045
                first = False
 
3046
 
 
3047
                orig_qty = line.move_id.product_qty
 
3048
                if line.move_id.original_qty_partial and line.move_id.original_qty_partial != -1:
 
3049
                    orig_qty = line.move_id.original_qty_partial
 
3050
 
 
3051
                if line.move_id.id not in move_data:
 
3052
                    move_data.setdefault(line.move_id.id, {
 
3053
                        'initial_qty': line.move_id.product_qty,
 
3054
                        'processed_qty': 0.00,
 
3055
                        'move': line.move_id,
 
3056
                    })
 
3057
                    first = True
 
3058
 
 
3059
                if line.uom_id.id != line.move_id.product_uom.id:
 
3060
                    processed_qty = uom_obj._compute_qty(cr, uid, line.uom_id.id, line.quantity, line.move_id.product_uom.id)
 
3061
                else:
 
3062
                    processed_qty = line.quantity
 
3063
 
 
3064
                move_data[line.move_id.id]['processed_qty'] += processed_qty
 
3065
 
 
3066
                values = {
 
3067
                    'product_qty': line.quantity,
 
3068
                    'product_uos_qty': line.quantity,
 
3069
                    'product_uom': line.uom_id.id,
 
3070
                    'product_uos': line.uom_id.id,
 
3071
                    'prodlot_id': line.prodlot_id and line.prodlot_id.id,
 
3072
                    'line_number': line.line_number,
 
3073
                    'asset_id': line.asset_id and line.asset_id.id,
 
3074
                    'composition_list_id': line.composition_list_id and line.composition_list_id.id,
 
3075
                    'original_qty_partial': orig_qty,
 
3076
                }
 
3077
 
 
3078
                # Update or create the validate picking ticket line
 
3079
                if first:
 
3080
                    move_obj.write(cr, uid, [line.move_id.id], values, context=context)
 
3081
                else:
 
3082
                    # Split happened during the validation
 
3083
                    # Copy the stock move and set the quantity
 
3084
                    values['state'] = 'assigned'
 
3085
                    context.update({
 
3086
                        'keepLineNumber': True,
 
3087
                        'non_stock_noupdate': True,
 
3088
                    })
 
3089
                    move_obj.copy(cr, uid, line.move_id.id, values, context=context)
 
3090
                    context.update({
 
3091
                        'keepLineNumber': False,
 
3092
                        'non_stock_noupdate': False,
 
3093
                    })
 
3094
 
 
3095
                values.update({
 
3096
                    'picking_id': new_ppl_id,
 
3097
                    'initial_location': line.move_id.location_id.id,
 
3098
                    'location_id': line.move_id.location_dest_id.id,
 
3099
                    'location_dest_id': new_ppl.warehouse_id.lot_dispatch_id.id,
 
3100
                    'date': today,
 
3101
                    'date_expected': today,
 
3102
                })
 
3103
                context.update({
 
3104
                    'keepLineNumber': True,
 
3105
                    'non_stock_noupdate': True,
 
3106
                })
 
3107
                move_obj.copy(cr, uid, line.move_id.id, values, context=context)
 
3108
                context.update({
 
3109
                    'keepLineNumber': False,
 
3110
                    'non_stock_noupdate': False,
 
3111
                })
 
3112
 
 
3113
 
 
3114
            # For each move, check if there is remaining quantity
 
3115
            for move_vals in move_data.itervalues():
 
3116
                # The quantity after the validation does not correspond to the picking ticket quantity
 
3117
                # The difference is written back to draft picking ticket
 
3118
                # Is positive if some quantity was removed during the validation -> draft quantity is increased
 
3119
                # Is negative if some quantity was added during the validation -> draft quantity is decreased
 
3120
                diff_qty = move_vals['initial_qty'] - move_vals['processed_qty']
 
3121
                if diff_qty != 0.00:
 
3122
                    # Original move from the draft picking ticket which will be updated
 
3123
                    original_move_id = move_vals['move'].backmove_id.id
 
3124
                    original_vals = move_obj.browse(cr, uid, original_move_id, context=context)
 
3125
                    if original_vals.product_uom.id != move_vals['move'].product_uom.id:
 
3126
                        diff_qty = uom_obj._compute_qty(cr, uid, move_vals['move'].product_uom.id, diff_qty, original_vals.product_uom.id)
 
3127
                    backorder_qty = max(original_vals.product_qty + diff_qty, 0)
 
3128
                    if backorder_qty != 0.00:
 
3129
                        move_obj.write(cr, uid, [original_move_id], {'product_qty': backorder_qty}, context=context)
 
3130
 
1893
3131
            wf_service = netsvc.LocalService("workflow")
1894
3132
            wf_service.trg_validate(uid, 'stock.picking', new_ppl_id, 'button_confirm', cr)
1895
3133
            # simulate check assign button, as stock move must be available
1896
3134
            self.force_assign(cr, uid, [new_ppl_id])
1897
3135
            # trigger standard workflow for validated picking ticket
1898
 
            self.action_move(cr, uid, [pick.id])
1899
 
            wf_service.trg_validate(uid, 'stock.picking', pick.id, 'button_done', cr)
1900
 
            
 
3136
            self.action_move(cr, uid, [picking.id])
 
3137
            wf_service.trg_validate(uid, 'stock.picking', picking.id, 'button_done', cr)
 
3138
 
1901
3139
            # if the flow type is in quick mode, we perform the ppl steps automatically
1902
 
            if pick.flow_type == 'quick':
1903
 
                create_picking_obj.quick_mode(cr, uid, new_ppl, context=context)
1904
 
        
1905
 
        # TODO which behavior
1906
 
        #return {'type': 'ir.actions.act_window_close'}
 
3140
            if picking.flow_type == 'quick' and new_ppl:
 
3141
                return self.quick_mode(cr, uid, new_ppl.id, context=context)
 
3142
 
1907
3143
        data_obj = self.pool.get('ir.model.data')
1908
 
        view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_form')
 
3144
        view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_ppl_form')
1909
3145
        view_id = view_id and view_id[1] or False
1910
 
        # display newly created picking ticket
1911
 
        return {'name':_("Picking Ticket"),
 
3146
        context.update({'picking_type': 'picking_ticket', 'ppl_screen': True})
 
3147
 
 
3148
        return {'name':_("Pre-Packing List"),
1912
3149
                'view_mode': 'form,tree',
1913
3150
                'view_id': [view_id],
1914
3151
                'view_type': 'form',
1915
3152
                'res_model': 'stock.picking',
1916
 
                'res_id': pick.id,
 
3153
                'res_id': new_ppl and new_ppl.id or False,
1917
3154
                'type': 'ir.actions.act_window',
1918
3155
                'target': 'crush',
 
3156
                'context': context,
1919
3157
                }
1920
3158
 
 
3159
    def quick_mode(self, cr, uid, ids, context=None):
 
3160
        """
 
3161
        Perform the PPL steps automatically
 
3162
 
 
3163
        """
 
3164
        # Objects
 
3165
        proc_obj = self.pool.get('ppl.processor')
 
3166
 
 
3167
        if context is None:
 
3168
            context = {}
 
3169
 
 
3170
        if not isinstance(ids, (int, long)):
 
3171
            ids = [ids]
 
3172
 
 
3173
        if not ids:
 
3174
            raise osv.except_osv(
 
3175
                _('Processing Error'),
 
3176
                _('No data to process !'),
 
3177
            )
 
3178
 
 
3179
        wizard_id = self.ppl(cr, uid, ids, context=context)['res_id']
 
3180
        proc_obj.do_ppl_step1(cr, uid, wizard_id, context=context)
 
3181
        return proc_obj.do_ppl_step2(cr, uid, wizard_id, context=context)
 
3182
 
1921
3183
    def ppl(self, cr, uid, ids, context=None):
1922
 
        '''
1923
 
        pack the ppl - open the ppl step1 wizard
1924
 
        '''
1925
 
        # we need the context for the wizard switch
 
3184
        """
 
3185
        Open the wizard to process the step 1 of the PPL
 
3186
        """
 
3187
        # Objects
 
3188
        proc_obj = self.pool.get('ppl.processor')
 
3189
        data_obj = self.pool.get('ir.model.data')
 
3190
 
 
3191
        if isinstance(ids, (int, long)):
 
3192
            ids = [ids]
 
3193
 
 
3194
        processor_id = proc_obj.create(cr, uid, {'picking_id': ids[0]}, context=context)
 
3195
        proc_obj.create_lines(cr, uid, processor_id, context=context)
 
3196
 
 
3197
        view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'ppl_processor_step1_form_view')[1]
 
3198
 
 
3199
        return {
 
3200
            'name': _('PPL Information - step 1'),
 
3201
            'type': 'ir.actions.act_window',
 
3202
            'res_model': proc_obj._name,
 
3203
            'res_id': processor_id,
 
3204
            'view_id': [view_id],
 
3205
            'view_type': 'form',
 
3206
            'view_mode': 'form',
 
3207
            'target': 'new',
 
3208
        }
 
3209
 
 
3210
    def do_ppl_step1(self, cr, uid, wizard_ids, context=None):
 
3211
        """
 
3212
        Open the wizard to process the step 2 of the PPL
 
3213
 
 
3214
        BE CAREFUL: the wizard_ids parameters is the IDs of the ppl.processor objects,
 
3215
        not those of stock.picking objects
 
3216
        """
 
3217
        # Objects
 
3218
        proc_obj = self.pool.get('ppl.processor')
 
3219
        proc_line_obj = self.pool.get('ppl.move.processor')
 
3220
        family_obj = self.pool.get('ppl.family.processor')
 
3221
 
 
3222
        if isinstance(wizard_ids, (int, long)):
 
3223
            wizard_ids = [wizard_ids]
 
3224
 
 
3225
        if not wizard_ids:
 
3226
            raise osv.except_osv(
 
3227
                _('Processing Error'),
 
3228
                _('No data to process !'),
 
3229
            )
 
3230
 
 
3231
        # Create the different pack families according to values in stock moves
 
3232
        for wizard in proc_obj.browse(cr, uid, wizard_ids, context=context):
 
3233
            families_data = {}
 
3234
 
 
3235
            for line in wizard.move_ids:
 
3236
                key = 'f%st%s' % (line.from_pack, line.to_pack)
 
3237
                families_data.setdefault(key, {
 
3238
                    'wizard_id': wizard.id,
 
3239
                    'move_ids': [],
 
3240
                    'from_pack': line.from_pack,
 
3241
                    'to_pack': line.to_pack,
 
3242
                })
 
3243
 
 
3244
                families_data[key]['move_ids'].append(line.id)
 
3245
 
 
3246
            for family_data in families_data.values():
 
3247
                move_ids = family_data.get('move_ids', [])
 
3248
                if 'move_ids' in family_data:
 
3249
                    del family_data['move_ids']
 
3250
 
 
3251
                fam_id = family_obj.create(cr, uid, family_data)
 
3252
 
 
3253
                if move_ids:
 
3254
                    proc_line_obj.write(cr, uid, move_ids, {'pack_id': fam_id}, context=context)
 
3255
 
 
3256
        return self.ppl_step2(cr, uid, wizard_ids, context)
 
3257
 
 
3258
    def ppl_step2(self, cr, uid, wizard_ids, context=None):
 
3259
        """
 
3260
        Open the wizard of the second step of PPL processing
 
3261
 
 
3262
        BE CAREFUL: the wizard_ids parameters is the IDs of the ppl.processor objects,
 
3263
        not those of stock.picking objects
 
3264
        """
 
3265
        # Objects
 
3266
        proc_obj = self.pool.get('ppl.processor')
 
3267
        data_obj = self.pool.get('ir.model.data')
 
3268
 
 
3269
        view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'ppl_processor_step2_form_view')[1]
 
3270
 
 
3271
        return {
 
3272
            'name': _('PPL Information - step 2'),
 
3273
            'type': 'ir.actions.act_window',
 
3274
            'res_model': proc_obj._name,
 
3275
            'res_id': wizard_ids[0],
 
3276
            'view_id': [view_id],
 
3277
            'view_type': 'form',
 
3278
            'view_mode': 'form',
 
3279
            'target': 'new',
 
3280
            'context': context,
 
3281
        }
 
3282
 
 
3283
    def do_ppl_step2(self, cr, uid, wizard_ids, context=None):
 
3284
        '''
 
3285
        Create the Pack families and the shipment
 
3286
 
 
3287
        BE CAREFUL: the wizard_ids parameters is the IDs of the ppl.processor objects,
 
3288
        not those of stock.picking objects
 
3289
        '''
 
3290
        # Objects
 
3291
        proc_obj = self.pool.get('ppl.processor')
 
3292
        move_obj = self.pool.get('stock.move')
 
3293
        uom_obj = self.pool.get('product.uom')
 
3294
        data_obj = self.pool.get('ir.model.data')
 
3295
        wf_service = netsvc.LocalService("workflow")
 
3296
 
1926
3297
        if context is None:
1927
3298
            context = {}
1928
 
            
1929
 
        # data
1930
 
        name = _("PPL Information - step1")
1931
 
        model = 'create.picking'
1932
 
        step = 'ppl1'
1933
 
        wiz_obj = self.pool.get('wizard')
1934
 
        # open the selected wizard
1935
 
        return wiz_obj.open_wizard(cr, uid, ids, name=name, model=model, step=step, context=context)
1936
 
        
1937
 
    def do_ppl1(self, cr, uid, ids, context=None):
1938
 
        '''
1939
 
        - receives generated data from ppl in context
1940
 
        - call action to ppl2 step with partial_datas_ppl1 in context
1941
 
        - ids are the picking ids
1942
 
        '''
1943
 
        # we need the context for the wizard switch
1944
 
        assert context, 'No context defined'
1945
 
            
1946
 
        # data
1947
 
        name = _("PPL Information - step2")
1948
 
        model = 'create.picking'
1949
 
        step = 'ppl2'
1950
 
        wiz_obj = self.pool.get('wizard')
1951
 
        # open the selected wizard
1952
 
        return wiz_obj.open_wizard(cr, uid, ids, name=name, model=model, step=step, context=context)
1953
 
        
1954
 
    def do_ppl2(self, cr, uid, ids, context=None):
1955
 
        '''
1956
 
        finalize the ppl logic
1957
 
        '''
1958
 
        # integrity check
1959
 
        assert context, 'context not defined'
1960
 
        assert 'partial_datas_ppl1' in context, 'partial_datas_ppl1 no defined in context'
1961
 
        
1962
 
        move_obj = self.pool.get('stock.move')
1963
 
        wf_service = netsvc.LocalService("workflow")
1964
 
        # data from wizard
1965
 
        partial_datas_ppl = context['partial_datas_ppl1']
1966
 
        # picking ids from ids must be equal to picking ids from partial datas
1967
 
        assert set(ids) == set(partial_datas_ppl.keys()), 'picking ids from ids and partial do not match'
1968
 
        
1969
 
        # update existing stock moves - create new one if split occurred
1970
 
        # for each pick
1971
 
        for pick in self.browse(cr, uid, ids, context=context):
1972
 
            # integrity check on move_ids - moves ids from picking and partial must be the same
1973
 
            # dont take into account done moves, which represents returned products
1974
 
            from_pick = [move.id for move in pick.move_lines if move.state in ('confirmed', 'assigned')]
1975
 
            from_partial = []
1976
 
            # the list of updated stock.moves
1977
 
            # if a stock.move is updated, the next time a new move is created
1978
 
            updated = {}
1979
 
            # load moves data
1980
 
            # browse returns a list of browse object in the same order as from_partial
1981
 
            browse_moves = move_obj.browse(cr, uid, from_pick, context=context)
1982
 
            moves = dict(zip(from_pick, browse_moves))
1983
 
            # loop through data
1984
 
            for from_pack in partial_datas_ppl[pick.id]:
1985
 
                for to_pack in partial_datas_ppl[pick.id][from_pack]:
1986
 
                    for move in partial_datas_ppl[pick.id][from_pack][to_pack]:
1987
 
                        # integrity check
1988
 
                        from_partial.append(move)
1989
 
                        for partial in partial_datas_ppl[pick.id][from_pack][to_pack][move]:
1990
 
                            # {'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}
1991
 
                            # integrity check
1992
 
                            partial['product_id'] == moves[move].product_id.id
1993
 
                            partial['asset_id'] == moves[move].asset_id.id
1994
 
                            partial['product_uom'] == moves[move].product_uom.id
1995
 
                            partial['prodlot_id'] == moves[move].prodlot_id.id
1996
 
                            # dictionary of new values, used for creation or update
1997
 
                            # - qty_per_pack is a function at stock move level
1998
 
                            fields = ['product_qty', 'from_pack', 'to_pack', 'pack_type', 'length', 'width', 'height', 'weight']
1999
 
                            values = dict(zip(fields, [partial["%s"%x] for x in fields]))
2000
 
                            
2001
 
                            if move in updated:
2002
 
                                # if already updated, we create a new stock.move
2003
 
                                updated[move]['partial_qty'] += partial['product_qty']
2004
 
                                # force state to 'assigned'
2005
 
                                values.update(state='assigned')
2006
 
                                # copy stock.move with new product_qty, qty_per_pack. from_pack, to_pack, pack_type, length, width, height, weight
2007
 
                                move_obj.copy(cr, uid, move, values, context=context)
2008
 
                            else:
2009
 
                                # update the existing stock move
2010
 
                                updated[move] = {'initial': moves[move].product_qty, 'partial_qty': partial['product_qty']}
2011
 
                                move_obj.write(cr, uid, [move], values, context=context)
2012
 
        
2013
 
            # integrity check - all moves are treated and no more
2014
 
            assert set(from_pick) == set(from_partial), 'move_ids are not equal pick:%s - partial:%s'%(set(from_pick), set(from_partial))
2015
 
            # quantities are right
2016
 
            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)
2017
 
            # copy to 'packing' stock.picking
2018
 
            # draft shipment is automatically created or updated if a shipment already
2019
 
            pack_number = pick.name.split("/")[1]
2020
 
            new_packing_id = self.copy(cr, uid, pick.id, {'name': 'PACK/' + pack_number,
2021
 
                                                          'subtype': 'packing',
2022
 
                                                          'previous_step_id': pick.id,
2023
 
                                                          'backorder_id': False,
2024
 
                                                          'shipment_id': False}, context=dict(context, keep_prodlot=True, allow_copy=True,))
2025
 
 
2026
 
            self.write(cr, uid, [new_packing_id], {'origin': pick.origin}, context=context)
2027
 
            # update locations of stock moves and state as the picking stay at 'draft' state.
2028
 
            # if return move have been done in previous ppl step, we remove the corresponding copied move (criteria: qty_per_pack == 0)
2029
 
            new_packing = self.browse(cr, uid, new_packing_id, context=context)
2030
 
            for move in new_packing.move_lines:
2031
 
                if move.qty_per_pack == 0:
2032
 
                    move_obj.unlink(cr, uid, [move.id], context=context)
2033
 
                else:
2034
 
                    move.write({'state': 'assigned',
2035
 
                                'location_id': new_packing.warehouse_id.lot_dispatch_id.id,
2036
 
                                'location_dest_id': new_packing.warehouse_id.lot_distribution_id.id}, context=context)
2037
 
            
2038
 
            # trigger standard workflow
2039
 
            self.action_move(cr, uid, [pick.id])
2040
 
            wf_service.trg_validate(uid, 'stock.picking', pick.id, 'button_done', cr)
2041
 
        
2042
 
        # TODO which behavior
2043
 
        #return {'type': 'ir.actions.act_window_close'}
2044
 
        data_obj = self.pool.get('ir.model.data')
2045
 
        view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_ppl_form')
 
3299
 
 
3300
        if isinstance(wizard_ids, (int, long)):
 
3301
            wizard_ids = [wizard_ids]
 
3302
 
 
3303
        if not wizard_ids:
 
3304
            raise osv.except_osv(
 
3305
                _('Processing Error'),
 
3306
                _('No data to process '),
 
3307
            )
 
3308
 
 
3309
        for wizard in proc_obj.browse(cr, uid, wizard_ids, context=context):
 
3310
            picking = wizard.picking_id
 
3311
 
 
3312
            moves_data = {}
 
3313
 
 
3314
            # Create the new packing
 
3315
            # Copy to 'packing' stock.picking
 
3316
            # Draft shipment is automatically created or updated if a shipment already exists
 
3317
            pack_number = picking.name.split("/")[1]
 
3318
 
 
3319
            pack_values = {
 
3320
                'name': 'PACK/' + pack_number,
 
3321
                'subtype': 'packing',
 
3322
                'previous_step_id': picking.id,
 
3323
                'backorder_id': False,
 
3324
                'shipment_id': False,
 
3325
                'origin': picking.origin,
 
3326
                'move_lines': [],
 
3327
            }
 
3328
 
 
3329
            # Change the context for copy
 
3330
            context.update({
 
3331
                'keep_prodlot': True,
 
3332
                'keepLineNumber': True,
 
3333
                'allow_copy': True,
 
3334
            })
 
3335
 
 
3336
            # Create the packing with pack_values and the updated context
 
3337
            new_packing_id = self.copy(cr, uid, picking.id, pack_values, context=context)
 
3338
 
 
3339
            # Reset context values
 
3340
            context.update({
 
3341
                'keep_prodlot': False,
 
3342
                'keepLineNumber': False,
 
3343
                'allow_copy': False,
 
3344
            })
 
3345
 
 
3346
            # Set default values for packing move creation
 
3347
            pack_move_data = {
 
3348
                'picking_id': new_packing_id,
 
3349
                'state': 'assigned',
 
3350
                'location_id': picking.warehouse_id.lot_dispatch_id.id,
 
3351
                'location_dest_id': picking.warehouse_id.lot_distribution_id.id,
 
3352
            }
 
3353
 
 
3354
            # Create the stock moves in the packing
 
3355
            for family in wizard.family_ids:
 
3356
                for line in family.move_ids:
 
3357
                    move_to_copy = line.move_id.id
 
3358
 
 
3359
                    if line.uom_id.id != line.move_id.product_uom.id:
 
3360
                        processed_qty = uom_obj._compute_qty(cr, uid, line.uom_id.id, line.quantity, line.move_id.product_uom.id)
 
3361
                    else:
 
3362
                        processed_qty = line.quantity
 
3363
 
 
3364
                    values = {
 
3365
                        'product_qty': line.quantity,
 
3366
                        'from_pack': family.from_pack,
 
3367
                        'to_pack': family.to_pack,
 
3368
                        'pack_type': family.pack_type and family.pack_type.id or False,
 
3369
                        'length': family.length,
 
3370
                        'width': family.width,
 
3371
                        'height': family.height,
 
3372
                        'weight': family.weight,
 
3373
                    }
 
3374
 
 
3375
                    if line.move_id.id not in moves_data:
 
3376
                        moves_data.setdefault(line.move_id.id, {
 
3377
                            'line_number': line.move_id.line_number,
 
3378
                            'initial_qty': line.move_id.product_qty,
 
3379
                            'processed_qty': processed_qty,
 
3380
                        })
 
3381
                        move_obj.write(cr, uid, [line.move_id.id], values, context=context)
 
3382
 
 
3383
                    else:
 
3384
                        # If already updated, we create a new stock move
 
3385
                        moves_data[line.move_id.id]['processed_qty'] += processed_qty
 
3386
                        # Force state to 'assigned'
 
3387
                        values['state'] = 'assigned'
 
3388
                        # Copy stock move
 
3389
                        context.update({
 
3390
                            'keepLineNumber': True,
 
3391
                            'non_stock_noupdate': True,
 
3392
                        })
 
3393
                        move_to_copy = move_obj.copy(cr, uid, line.move_id.id, values, context=context)
 
3394
                        context.update({
 
3395
                            'keepLineNumber': False,
 
3396
                            'non_stock_noupdate': False,
 
3397
                        })
 
3398
 
 
3399
                    # Create a move line in the Packing
 
3400
                    context.update({
 
3401
                        'keepLineNumber': True,
 
3402
                        'non_stock_noupdate': True,
 
3403
                    })
 
3404
                    move_obj.copy(cr, uid, move_to_copy, pack_move_data, context=context)
 
3405
                    context.update({
 
3406
                        'keepLineNumber': False,
 
3407
                        'non_stock_noupdate': False,
 
3408
                    })
 
3409
 
 
3410
            # Check quantities integrity status
 
3411
            for m_data in moves_data.values():
 
3412
                if m_data['initial_qty'] != m_data['processed_qty']:
 
3413
                    raise osv.except_osv(
 
3414
                        _('Processing Error'),
 
3415
                        _('Line %(line_number)s: The sum of processed quantities %(processed_qty)s '\
 
3416
'is not equal to the initial quantity of the stock move %(initial_qty)s.') % m_data
 
3417
                    )
 
3418
 
 
3419
            # Trigger standard workflow on PPL
 
3420
            self.action_move(cr, uid, [picking.id])
 
3421
            wf_service.trg_validate(uid, 'stock.picking', picking.id, 'button_done', cr)
 
3422
 
 
3423
        shipment_id = new_packing_id and self.read(cr, uid, new_packing_id, ['shipment_id'])['shipment_id'][0] or False
 
3424
 
 
3425
        view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_shipment_form')
2046
3426
        view_id = view_id and view_id[1] or False
2047
 
        # display newly created picking ticket
2048
 
        return {'name':_("Pre-Packing List"),
 
3427
        return {'name':_("Shipment"),
2049
3428
                'view_mode': 'form,tree',
2050
3429
                'view_id': [view_id],
2051
3430
                'view_type': 'form',
2052
 
                'res_model': 'stock.picking',
2053
 
                'res_id': pick.id,
 
3431
                'res_model': 'shipment',
 
3432
                'res_id': shipment_id,
2054
3433
                'type': 'ir.actions.act_window',
2055
3434
                'target': 'crush',
 
3435
                'context': context,
2056
3436
                }
2057
 
    
2058
 
    def return_products(self, cr, uid, ids, context=None):
2059
 
        '''
2060
 
        open the return products wizard
2061
 
        '''
2062
 
        # we need the context
 
3437
 
 
3438
    def ppl_return(self, cr, uid, ids, context=None):
 
3439
        """
 
3440
        Open wizard to return products from a PPL
 
3441
        """
 
3442
        # Objects
 
3443
        proc_obj = self.pool.get('return.ppl.processor')
 
3444
 
 
3445
        if isinstance(ids, (int, long)):
 
3446
            ids = [ids]
 
3447
 
 
3448
        processor_id = proc_obj.create(cr, uid, {'picking_id': ids[0]}, context=context)
 
3449
        proc_obj.create_lines(cr, uid, processor_id, context=context)
 
3450
 
 
3451
        return {
 
3452
            'name': _('Return products'),
 
3453
            'type': 'ir.actions.act_window',
 
3454
            'res_model': proc_obj._name,
 
3455
            'res_id': processor_id,
 
3456
            'view_type': 'form',
 
3457
            'view_mode': 'form',
 
3458
            'target': 'new',
 
3459
        }
 
3460
 
 
3461
 
 
3462
    def do_return_ppl(self, cr, uid, wizard_ids, context=None):
 
3463
        """
 
3464
        Returns products from PPL to the draft picking ticket
 
3465
 
 
3466
        BE CAREFUL: the wizard_ids parameters is the IDs of the ppl.processor objects,
 
3467
        not those of stock.picking objects
 
3468
        """
 
3469
        # Objects
 
3470
        proc_obj = self.pool.get('return.ppl.processor')
 
3471
        move_obj = self.pool.get('stock.move')
 
3472
        data_obj = self.pool.get('ir.model.data')
 
3473
        wf_service = netsvc.LocalService("workflow")
 
3474
 
2063
3475
        if context is None:
2064
3476
            context = {}
2065
 
            
2066
 
        # data
2067
 
        name = _("Return Products")
2068
 
        model = 'create.picking'
2069
 
        step = 'returnproducts'
2070
 
        wiz_obj = self.pool.get('wizard')
2071
 
        # open the selected wizard
2072
 
        return wiz_obj.open_wizard(cr, uid, ids, name=name, model=model, step=step, context=context)
2073
 
    
2074
 
    def do_return_products(self, cr, uid, ids, context=None):
2075
 
        '''
2076
 
        - update the ppl
2077
 
        - update the draft picking ticket
2078
 
        - create the back move
2079
 
        '''
2080
 
        # integrity check
2081
 
        assert context, 'context not defined'
2082
 
        assert 'partial_datas' in context, 'partial_datas no defined in context'
2083
 
        partial_datas = context['partial_datas']
2084
 
        
2085
 
        move_obj = self.pool.get('stock.move')
2086
 
        wf_service = netsvc.LocalService("workflow")
2087
 
        
2088
 
        for picking in self.browse(cr, uid, ids, context=context):
2089
 
            # for each picking
2090
 
            # corresponding draft picking ticket
 
3477
 
 
3478
        if isinstance(wizard_ids, (int, long)):
 
3479
            wizard_ids = [wizard_ids]
 
3480
 
 
3481
        for wizard in proc_obj.browse(cr, uid, wizard_ids, context=context):
 
3482
            picking = wizard.picking_id
2091
3483
            draft_picking_id = picking.previous_step_id.backorder_id.id
2092
 
            
2093
 
            for move in move_obj.browse(cr, uid, partial_datas[picking.id].keys(), context=context):
2094
 
                # we browse the updated moves (return qty > 0 is checked during data generation)
2095
 
                # data from wizard
2096
 
                data = partial_datas[picking.id][move.id]
2097
 
 
2098
 
                # qty to return
2099
 
                return_qty = data['qty_to_return']
2100
 
                # initial qty is decremented
2101
 
                initial_qty = move.product_qty
2102
 
                initial_qty = max(initial_qty - return_qty, 0)
2103
 
                values = {'product_qty': initial_qty}
2104
 
                
 
3484
 
 
3485
            for line in wizard.move_ids:
 
3486
                return_qty = line.quantity
 
3487
                initial_qty = max(line.move_id.product_qty - return_qty, 0)
 
3488
 
 
3489
                move_values = {
 
3490
                    'product_qty': initial_qty,
 
3491
                }
 
3492
 
2105
3493
                if not initial_qty:
2106
 
                    # if all products are sent back to stock, the move state is cancel - done for now, ideologic question, wahouuu!
2107
 
                    #values.update({'state': 'cancel'})
2108
 
                    values.update({'state': 'done'})
2109
 
                move_obj.write(cr, uid, [move.id], values, context=context)
2110
 
                
2111
 
                # create a back move with the quantity to return to the good location
2112
 
                # the good location is stored in the 'initial_location' field
2113
 
                move_obj.copy(cr, uid, move.id, {'product_qty': return_qty,
2114
 
                                                 'location_dest_id': move.initial_location.id,
2115
 
                                                 'state': 'done'})
2116
 
                
2117
 
                # increase the draft move with the move quantity
2118
 
                draft_move_id = move.backmove_id.id
 
3494
                    """
 
3495
                    If all products of the move are sent back to draft picking ticket,
 
3496
                    the move state is done
 
3497
                    """
 
3498
                    move_values['state'] = 'done'
 
3499
 
 
3500
                move_obj.write(cr, uid, [line.move_id.id], move_values, context=context)
 
3501
 
 
3502
                """
 
3503
                Create a back move with the quantity to return to the good location.
 
3504
                Good location is stored in the 'initial_location' field
 
3505
                """
 
3506
                return_values = {
 
3507
                    'product_qty': return_qty,
 
3508
                    'location_dest_id': line.move_id.initial_location.id,
 
3509
                    'state': 'done',
 
3510
                }
 
3511
                context['keepLineNumber'] = True
 
3512
                move_obj.copy(cr, uid, line.move_id.id, return_values, context=context)
 
3513
                context['keepLineNumber'] = False
 
3514
 
 
3515
                # Increase the draft move with the returned quantity
 
3516
                draft_move_id = line.move_id.backmove_id.id
2119
3517
                draft_initial_qty = move_obj.read(cr, uid, [draft_move_id], ['product_qty'], context=context)[0]['product_qty']
2120
3518
                draft_initial_qty += return_qty
2121
3519
                move_obj.write(cr, uid, [draft_move_id], {'product_qty': draft_initial_qty}, context=context)
2122
 
                
2123
 
            # log the increase action - display the picking ticket view form
2124
 
            # TODO refactoring needed
2125
 
            obj_data = self.pool.get('ir.model.data')
2126
 
            res = obj_data.get_object_reference(cr, uid, 'msf_outgoing', 'view_ppl_form')[1]
2127
 
            self.log(cr, uid, picking.id, _("Products from Pre-Packing List (%s) have been returned to stock."%picking.name), context={'view_id': res,})
2128
 
            res = obj_data.get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_form')[1]
2129
 
            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,})
2130
 
            # if all moves are done or canceled, the ppl is canceled
2131
 
            cancel_ppl = True
2132
 
            for move in picking.move_lines:
2133
 
                if move.state in ('assigned'):
2134
 
                    cancel_ppl = False
2135
 
            
 
3520
 
 
3521
 
 
3522
 
 
3523
            # Log message for PPL
 
3524
            ppl_view = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_ppl_form')[1]
 
3525
            context['view_id'] = ppl_view
 
3526
            log_message = _('Products from Pre-Packing List (%s) have been returned to stock.') % (picking.name,)
 
3527
            self.log(cr, uid, picking.id, log_message, context=context)
 
3528
 
 
3529
            # Log message for draft picking ticket
 
3530
            pick_view = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_form')[1]
 
3531
            context.update({
 
3532
                'view_id': pick_view,
 
3533
                'picking_type': 'picking_ticket',
 
3534
            })
 
3535
            log_message = _('The corresponding Draft Picking Ticket (%s) has been updated.') % (picking.previous_step_id.backorder_id.name,)
 
3536
            self.log(cr, uid, draft_picking_id, log_message, context=context)
 
3537
 
 
3538
            # If all moves are done or canceled, the PPL is canceled
 
3539
            cancel_ppl = move_obj.search(cr, uid, [('picking_id', '=', picking.id), ('state', '!=', 'assigned')], count=True, context=context)
 
3540
 
2136
3541
            if cancel_ppl:
2137
 
                # we dont want the back move (done) to be canceled - so we dont use the original cancel workflow state because
2138
 
                # action_cancel() from stock_picking would be called, this would cancel the done stock_moves
2139
 
                # instead we move to the new return_cancel workflow state which simply set the stock_picking state to 'cancel'
2140
 
                # TODO THIS DOESNT WORK - still done state - replace with trigger for now
2141
 
                #wf_service.trg_validate(uid, 'stock.picking', picking.id, 'return_cancel', cr)
 
3542
                """
 
3543
                we dont want the back move (done) to be canceled - so we dont use the original cancel workflow state because
 
3544
                action_cancel() from stock_picking would be called, this would cancel the done stock_moves
 
3545
                instead we move to the new return_cancel workflow state which simply set the stock_picking state to 'cancel'
 
3546
                TODO THIS DOESNT WORK - still done state - replace with trigger for now
 
3547
                wf_service.trg_validate(uid, 'stock.picking', picking.id, 'return_cancel', cr)
 
3548
                """
2142
3549
                wf_service.trg_write(uid, 'stock.picking', picking.id, cr)
2143
 
                
2144
 
        # TODO which behavior
2145
 
        #return {'type': 'ir.actions.act_window_close'}
2146
 
        data_obj = self.pool.get('ir.model.data')
2147
 
        view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_ppl_form')
 
3550
 
 
3551
        view_id = data_obj.get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_form')
2148
3552
        view_id = view_id and view_id[1] or False
2149
 
        # display newly created picking ticket
 
3553
        context['picking_type'] = 'picking_ticket'
 
3554
 
 
3555
        context.update({'picking_type': 'picking_ticket'})
2150
3556
        return {
2151
 
            'name':_("Pre-Packing List"),
 
3557
            'name':_("Picking Ticket"),
2152
3558
            'view_mode': 'form,tree',
2153
3559
            'view_id': [view_id],
2154
3560
            'view_type': 'form',
2155
3561
            'res_model': 'stock.picking',
2156
 
            'res_id': picking.id,
 
3562
            'res_id': draft_picking_id ,
2157
3563
            'type': 'ir.actions.act_window',
2158
3564
            'target': 'crush',
 
3565
            'context': context,
2159
3566
        }
2160
 
    
 
3567
 
2161
3568
    def action_cancel(self, cr, uid, ids, context=None):
2162
3569
        '''
2163
3570
        override cancel state action from the workflow
2164
 
        
 
3571
 
2165
3572
        - depending on the subtype and state of the stock.picking object
2166
3573
          the behavior will be different
2167
 
        
 
3574
 
2168
3575
        Cancel button is active for the picking object:
2169
3576
        - subtype: 'picking'
2170
3577
        Cancel button is active for the shipment object:
2171
3578
        - subtype: 'packing'
2172
 
        
 
3579
 
2173
3580
        state is not taken into account as picking is canceled before
2174
3581
        '''
2175
3582
        if context is None:
2176
3583
            context = {}
2177
3584
        move_obj = self.pool.get('stock.move')
2178
3585
        obj_data = self.pool.get('ir.model.data')
2179
 
        
 
3586
 
2180
3587
        # check the state of the picking
2181
3588
        for picking in self.browse(cr, uid, ids, context=context):
2182
 
            # if draft and all qty are still there, we can cancel it without further checks
 
3589
            # if draft and shipment is in progress, we cannot cancel
2183
3590
            if picking.subtype == 'picking' and picking.state in ('draft',):
2184
 
                for move in picking.move_lines:
2185
 
                    if move.product_qty != move.sale_line_id.product_uom_qty:
2186
 
                        raise osv.except_osv(_('Warning !'), _('The shipment process has already started! Return products to stock from ppl and shipment and try to cancel again.'))
 
3591
                if self.has_picking_ticket_in_progress(cr, uid, [picking.id], context=context)[picking.id]:
 
3592
                    raise osv.except_osv(_('Warning !'), _('Some Picking Tickets are in progress. Return products to stock from ppl and shipment and try to cancel again.'))
2187
3593
                return super(stock_picking, self).action_cancel(cr, uid, ids, context=context)
2188
3594
            # if not draft or qty does not match, the shipping is already in progress
2189
3595
            if picking.subtype == 'picking' and picking.state in ('done',):
2190
3596
                raise osv.except_osv(_('Warning !'), _('The shipment process is completed and cannot be canceled!'))
2191
 
        
 
3597
 
2192
3598
        # first call to super method, so if some checks fail won't perform other actions anyway
2193
3599
        # call super - picking is canceled
2194
3600
        super(stock_picking, self).action_cancel(cr, uid, ids, context=context)
2195
 
        
 
3601
 
2196
3602
        for picking in self.browse(cr, uid, ids, context=context):
2197
 
                
 
3603
 
2198
3604
            if picking.subtype == 'picking':
2199
3605
                # for each picking
2200
3606
                # get the draft picking
2201
3607
                draft_picking_id = picking.backorder_id.id
2202
 
                
 
3608
 
2203
3609
                # for each move from picking ticket - could be split moves
2204
3610
                for move in picking.move_lines:
2205
3611
                    # find the corresponding move in draft
2206
3612
                    draft_move = move.backmove_id
2207
 
                    # increase the draft move with the move quantity
2208
 
                    initial_qty = move_obj.read(cr, uid, [draft_move.id], ['product_qty'], context=context)[0]['product_qty']
2209
 
                    initial_qty += move.product_qty
2210
 
                    move_obj.write(cr, uid, [draft_move.id], {'product_qty': initial_qty}, context=context)
2211
 
                    # log the increase action
2212
 
                    # TODO refactoring needed
2213
 
                    obj_data = self.pool.get('ir.model.data')
2214
 
                    res = obj_data.get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_form')[1]
2215
 
                    self.log(cr, uid, draft_picking_id, _("The corresponding Draft Picking Ticket (%s) has been updated."%picking.backorder_id.name), context={'view_id': res,})
2216
 
                    
 
3613
                    if draft_move:
 
3614
                        # increase the draft move with the move quantity
 
3615
                        initial_qty = move_obj.read(cr, uid, [draft_move.id], ['product_qty'], context=context)[0]['product_qty']
 
3616
                        initial_qty += move.product_qty
 
3617
                        move_obj.write(cr, uid, [draft_move.id], {'product_qty': initial_qty}, context=context)
 
3618
                        # log the increase action
 
3619
                        # TODO refactoring needed
 
3620
                        obj_data = self.pool.get('ir.model.data')
 
3621
                        res = obj_data.get_object_reference(cr, uid, 'msf_outgoing', 'view_picking_ticket_form')[1]
 
3622
                        context.update({'view_id': res, 'picking_type': 'picking_ticket'})
 
3623
                        self.log(cr, uid, draft_picking_id, _("The corresponding Draft Picking Ticket (%s) has been updated.") % (picking.backorder_id.name,), context=context)
 
3624
 
2217
3625
            if picking.subtype == 'packing':
2218
 
                # for each packing we get the draft packing
2219
 
                draft_packing_id = picking.backorder_id.id
2220
 
                
 
3626
 
2221
3627
                # for each move from the packing
2222
3628
                for move in picking.move_lines:
2223
3629
                    # corresponding draft move from draft **packing** object
2229
3635
                    # we always create a new move
2230
3636
                    draft_read = move_obj.read(cr, uid, [draft_move_id], ['product_qty', 'to_pack'], context=context)[0]
2231
3637
                    draft_to_pack = draft_read['to_pack']
2232
 
                    if draft_to_pack + 1 == move.from_pack and False: # DEACTIVATED
 
3638
                    if draft_to_pack + 1 == move.from_pack and False:  # DEACTIVATED
2233
3639
                        # updated quantity
2234
3640
                        draft_qty = draft_read['product_qty'] + move.product_qty
2235
3641
                        # update the draft move
2240
3646
                                                               'from_pack': move.from_pack,
2241
3647
                                                               'to_pack': move.to_pack,
2242
3648
                                                               'state': 'assigned'}, context=context)
2243
 
        
 
3649
 
2244
3650
        return True
2245
 
            
 
3651
 
2246
3652
stock_picking()
2247
3653
 
2248
3654
 
2251
3657
    class offering open_wizard method for wizard control
2252
3658
    '''
2253
3659
    _name = 'wizard'
2254
 
    
2255
 
    def open_wizard(self, cr, uid, ids, name=False, model=False, step='default', type='create', context=None):
 
3660
 
 
3661
    def open_wizard(self, cr, uid, ids, name=False, model=False, step='default', w_type='create', context=None):
2256
3662
        '''
2257
3663
        WARNING : IDS CORRESPOND TO ***MAIN OBJECT IDS*** (picking for example) take care when calling the method from wizards
2258
3664
        return the newly created wizard's id
2260
3666
        '''
2261
3667
        if context is None:
2262
3668
            context = {}
2263
 
        
2264
 
        if type == 'create':
 
3669
 
 
3670
        if w_type == 'create':
2265
3671
            assert name, 'type "create" and no name defined'
2266
3672
            assert model, 'type "create" and no model defined'
2267
3673
            assert step, 'type "create" and no step defined'
 
3674
            vals = {}
 
3675
            # if there are already additional items on the shipment we show them in the wizard
 
3676
            if (name, model, step) == ('Create Shipment', 'shipment.wizard', 'create'):
 
3677
                vals['product_moves_shipment_additionalitems'] = []
 
3678
                for s in self.pool.get('shipment').read(cr, uid, ids, ['additional_items_ids']):
 
3679
                    additionalitems_ids = s['additional_items_ids']
 
3680
                    for additionalitem in self.pool.get('shipment.additionalitems').read(cr, uid, additionalitems_ids):
 
3681
                        additionalitem['additional_item_id'] = additionalitem['id']
 
3682
                        additionalitem.pop('id')
 
3683
                        additionalitem.pop('shipment_id')
 
3684
                        vals['product_moves_shipment_additionalitems'].append((0, 0, additionalitem))
 
3685
 
2268
3686
            # create the memory object - passing the picking id to it through context
2269
3687
            wizard_id = self.pool.get(model).create(
2270
 
                cr, uid, {}, context=dict(context,
 
3688
                cr, uid, vals, context=dict(context,
2271
3689
                                          active_ids=ids,
2272
3690
                                          model=model,
2273
3691
                                          step=step,
2276
3694
                                          back_wizard_name=context.get('wizard_name', False),
2277
3695
                                          back_step=context.get('step', False),
2278
3696
                                          wizard_name=name))
2279
 
        
2280
 
        elif type == 'back':
 
3697
 
 
3698
        elif w_type == 'back':
2281
3699
            # open the previous wizard
2282
3700
            assert context['back_wizard_ids'], 'no back_wizard_ids defined'
2283
3701
            wizard_id = context['back_wizard_ids'][0]
2287
3705
            model = context['back_model']
2288
3706
            assert context['back_step'], 'no back_step defined'
2289
3707
            step = context['back_step']
2290
 
            
2291
 
        elif type == 'update':
 
3708
 
 
3709
        elif w_type == 'update':
2292
3710
            # refresh the same wizard
2293
3711
            assert context['wizard_ids'], 'no wizard_ids defined'
2294
3712
            wizard_id = context['wizard_ids'][0]
2298
3716
            model = context['model']
2299
3717
            assert context['step'], 'no step defined'
2300
3718
            step = context['step']
2301
 
            
 
3719
 
2302
3720
        # call action to wizard view
2303
3721
        return {
2304
3722
            'name': name,
2322
3740
                            back_step=context.get('step', False),
2323
3741
                            wizard_name=name)
2324
3742
        }
2325
 
    
 
3743
 
2326
3744
wizard()
2327
3745
 
2328
3746
 
2331
3749
    add a getter for keep cool notion
2332
3750
    '''
2333
3751
    _inherit = 'product.product'
2334
 
    
 
3752
 
2335
3753
    def _vals_get(self, cr, uid, ids, fields, arg, context=None):
2336
3754
        '''
2337
3755
        get functional values
2342
3760
                      }
2343
3761
            result[product.id] = values
2344
3762
            # keep cool
2345
 
            is_keep_cool = bool(product.heat_sensitive_item)# in ('*', '**', '***',)
 
3763
            is_keep_cool = bool(product.heat_sensitive_item)  # in ('*', '**', '***',)
2346
3764
            values['is_keep_cool'] = is_keep_cool
2347
 
                    
 
3765
 
2348
3766
        return result
2349
 
    
 
3767
 
2350
3768
    _columns = {'is_keep_cool': fields.function(_vals_get, method=True, type='boolean', string='Keep Cool', multi='get_vals',),
2351
 
                'prodlot_ids': fields.one2many('stock.production.lot', 'product_id', string='Production Lots',),
 
3769
                'prodlot_ids': fields.one2many('stock.production.lot', 'product_id', string='Batch Numbers',),
2352
3770
                }
2353
 
    
 
3771
 
2354
3772
product_product()
2355
3773
 
2356
3774
 
2359
3777
    stock move
2360
3778
    '''
2361
3779
    _inherit = 'stock.move'
2362
 
    
 
3780
 
2363
3781
    def _product_available(self, cr, uid, ids, field_names=None, arg=False, context=None):
2364
3782
        '''
2365
3783
        facade for product_available function from product (stock)
2368
3786
        result = {}
2369
3787
        for d in self.read(cr, uid, ids, ['product_id'], context):
2370
3788
            result[d['id']] = d['product_id'][0]
2371
 
        
 
3789
 
2372
3790
        # get the virtual stock identified by product ids
2373
3791
        virtual = self.pool.get('product.product')._product_available(cr, uid, result.values(), field_names, arg, context)
2374
 
        
 
3792
 
2375
3793
        # replace product ids by corresponding stock move id
2376
 
        result = dict([id, virtual[result[id]]] for id in result.keys())
 
3794
        result = dict([move_id, virtual[result[move_id]]] for move_id in result.keys())
2377
3795
        return result
2378
 
    
 
3796
 
2379
3797
    def _vals_get(self, cr, uid, ids, fields, arg, context=None):
2380
3798
        '''
2381
3799
        get functional values
2382
3800
        '''
2383
3801
        result = {}
 
3802
        uom_obj = self.pool.get('product.uom')
2384
3803
        for move in self.browse(cr, uid, ids, context=context):
2385
3804
            values = {'qty_per_pack': 0.0,
2386
3805
                      'total_amount': 0.0,
2406
3825
                values['qty_per_pack'] = 0
2407
3826
            # total amount (float)
2408
3827
            total_amount = move.sale_line_id and move.sale_line_id.price_unit * move.product_qty or 0.0
 
3828
            if move.sale_line_id:
 
3829
                total_amount = uom_obj._compute_price(cr, uid, move.sale_line_id.product_uom.id, total_amount, move.product_uom.id)
2409
3830
            values['total_amount'] = total_amount
2410
3831
            # amount for one pack
2411
3832
            if num_of_packs:
2423
3844
            values['is_narcotic'] = move.product_id and move.product_id.narcotic or False
2424
3845
            # sale_order_line_number
2425
3846
            values['sale_order_line_number'] = move.sale_line_id and move.sale_line_id.line_number or 0
2426
 
                    
 
3847
 
2427
3848
        return result
2428
 
    
 
3849
 
 
3850
    def default_get(self, cr, uid, fields, context=None):
 
3851
        '''
 
3852
        Set default values according to type and subtype
 
3853
        '''
 
3854
        if not context:
 
3855
            context = {}
 
3856
 
 
3857
        res = super(stock_move, self).default_get(cr, uid, fields, context=context)
 
3858
 
 
3859
        if 'warehouse_id' in context and context.get('warehouse_id'):
 
3860
            warehouse_id = context.get('warehouse_id')
 
3861
        else:
 
3862
            warehouse_id = self.pool.get('stock.warehouse').search(cr, uid, [], context=context)[0]
 
3863
        res.update({'location_output_id': self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id, context=context).lot_output_id.id})
 
3864
 
 
3865
        loc_virtual_ids = self.pool.get('stock.location').search(cr, uid, [('name', '=', 'Virtual Locations')])
 
3866
        loc_virtual_id = len(loc_virtual_ids) > 0 and loc_virtual_ids[0] or False
 
3867
        res.update({'location_virtual_id': loc_virtual_id})
 
3868
 
 
3869
        if 'type' in context and context.get('type', False) == 'out':
 
3870
            loc_stock_id = self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id, context=context).lot_stock_id.id
 
3871
            res.update({'location_id': loc_stock_id})
 
3872
 
 
3873
        if 'subtype' in context and context.get('subtype', False) == 'picking':
 
3874
            loc_packing_id = self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id, context=context).lot_packing_id.id
 
3875
            res.update({'location_dest_id': loc_packing_id})
 
3876
        elif 'subtype' in context and context.get('subtype', False) == 'standard':
 
3877
            loc_output_id = self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id, context=context).lot_output_id.id
 
3878
            res.update({'location_dest_id': loc_output_id})
 
3879
 
 
3880
        return res
 
3881
 
2429
3882
    _columns = {'from_pack': fields.integer(string='From p.'),
2430
3883
                'to_pack': fields.integer(string='To p.'),
2431
3884
                'pack_type': fields.many2one('pack.type', string='Pack Type'),
2432
 
                'length' : fields.float(digits=(16,2), string='Length [cm]'),
2433
 
                'width' : fields.float(digits=(16,2), string='Width [cm]'),
2434
 
                'height' : fields.float(digits=(16,2), string='Height [cm]'),
2435
 
                'weight' : fields.float(digits=(16,2), string='Weight p.p [kg]'),
2436
 
                #'pack_family_id': fields.many2one('pack.family', string='Pack Family'),
 
3885
                'length' : fields.float(digits=(16, 2), string='Length [cm]'),
 
3886
                'width' : fields.float(digits=(16, 2), string='Width [cm]'),
 
3887
                'height' : fields.float(digits=(16, 2), string='Height [cm]'),
 
3888
                'weight' : fields.float(digits=(16, 2), string='Weight p.p [kg]'),
 
3889
                # 'pack_family_id': fields.many2one('pack.family', string='Pack Family'),
2437
3890
                'initial_location': fields.many2one('stock.location', string='Initial Picking Location'),
2438
3891
                # relation to the corresponding move from draft **picking** ticket object
2439
3892
                'backmove_id': fields.many2one('stock.move', string='Corresponding move of previous step'),
2442
3895
                # functions
2443
3896
                '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')),
2444
3897
                'qty_per_pack': fields.function(_vals_get, method=True, type='float', string='Qty p.p', multi='get_vals',),
2445
 
                'total_amount': fields.function(_vals_get, method=True, type='float', string='Total Amount', multi='get_vals',),
2446
 
                'amount': fields.function(_vals_get, method=True, type='float', string='Pack Amount', multi='get_vals',),
2447
 
                'num_of_packs': fields.function(_vals_get, method=True, type='integer', string='#Packs', multi='get_vals_X',), # old_multi get_vals
 
3898
                'total_amount': fields.function(_vals_get, method=True, type='float', string='Total Amount', digits_compute=dp.get_precision('Picking Price'), multi='get_vals',),
 
3899
                'amount': fields.function(_vals_get, method=True, type='float', string='Pack Amount', digits_compute=dp.get_precision('Picking Price'), multi='get_vals',),
 
3900
                'num_of_packs': fields.function(_vals_get, method=True, type='integer', string='#Packs', multi='get_vals_X',),  # old_multi get_vals
2448
3901
                'currency_id': fields.function(_vals_get, method=True, type='many2one', relation='res.currency', string='Currency', multi='get_vals',),
2449
3902
                'is_dangerous_good': fields.function(_vals_get, method=True, type='boolean', string='Dangerous Good', multi='get_vals',),
2450
3903
                'is_keep_cool': fields.function(_vals_get, method=True, type='boolean', string='Keep Cool', multi='get_vals',),
2451
3904
                'is_narcotic': fields.function(_vals_get, method=True, type='boolean', string='Narcotic', multi='get_vals',),
2452
 
                '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
 
3905
                '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
 
3906
                # Fields used for domain
 
3907
                'location_virtual_id': fields.many2one('stock.location', string='Virtual location'),
 
3908
                'location_output_id': fields.many2one('stock.location', string='Output location'),
 
3909
                'invoice_line_id': fields.many2one('account.invoice.line', string='Invoice line'),
 
3910
                'pt_created': fields.boolean(string='PT created'),
2453
3911
                }
2454
3912
 
 
3913
    def copy(self, cr, uid, copy_id, values=None, context=None):
 
3914
        if context is None:
 
3915
            context = {}
 
3916
 
 
3917
        if values is None:
 
3918
            values = {}
 
3919
 
 
3920
        if not 'pt_created' in values:
 
3921
            values['pt_created'] = False
 
3922
 
 
3923
        return super(stock_move, self).copy(cr, uid, copy_id, values, context=context)
 
3924
 
 
3925
    def action_cancel(self, cr, uid, ids, context=None):
 
3926
        '''
 
3927
            Confirm or check the procurement order associated to the stock move
 
3928
        '''
 
3929
        pol_obj = self.pool.get('purchase.order.line')
 
3930
        sol_obj = self.pool.get('sale.order.line')
 
3931
        uom_obj = self.pool.get('product.uom')
 
3932
 
 
3933
        for move in self.browse(cr, uid, ids, context=context):
 
3934
            """
 
3935
            A stock move can be re-sourced but there are some conditions
 
3936
 
 
3937
            Re-sourcing checking :
 
3938
              1) The move should be attached to a picking
 
3939
              2) The move should have the flag 'has_to_be_resourced' set
 
3940
              3) The move shouldn't be already canceled
 
3941
              4) The move should be linked to a purchase order line or a field
 
3942
                 order line
 
3943
            """
 
3944
            if not move.picking_id:
 
3945
                continue
 
3946
 
 
3947
            if not move.has_to_be_resourced and not move.picking_id.has_to_be_resourced:
 
3948
                continue
 
3949
 
 
3950
            if move.state == 'cancel':
 
3951
                continue
 
3952
 
 
3953
            pick_type = move.picking_id.type
 
3954
            pick_subtype = move.picking_id.subtype
 
3955
            pick_state = move.picking_id.state
 
3956
            subtype_ok = pick_type == 'out' and (pick_subtype == 'standard' or (pick_subtype == 'picking' and pick_state == 'draft'))
 
3957
 
 
3958
            if pick_type == 'in' and move.purchase_line_id:
 
3959
                sol_ids = pol_obj.get_sol_ids_from_pol_ids(cr, uid, [move.purchase_line_id.id], context=context)
 
3960
                for sol in sol_obj.browse(cr, uid, sol_ids, context=context):
 
3961
                    diff_qty = uom_obj._compute_qty(cr, uid, move.product_uom.id, move.product_qty, sol.product_uom.id)
 
3962
                    if move.has_to_be_resourced or move.picking_id.has_to_be_resourced:
 
3963
                        sol_obj.add_resource_line(cr, uid, sol.id, False, diff_qty, context=context)
 
3964
                    sol_obj.update_or_cancel_line(cr, uid, sol.id, diff_qty, context=context)
 
3965
            elif move.sale_line_id and (pick_type == 'internal' or (pick_type == 'out' and subtype_ok)):
 
3966
                diff_qty = uom_obj._compute_qty(cr, uid, move.product_uom.id, move.product_qty, move.sale_line_id.product_uom.id)
 
3967
                if move.has_to_be_resourced or move.picking_id.has_to_be_resourced:
 
3968
                    sol_obj.add_resource_line(cr, uid, move.sale_line_id.id, False, diff_qty, context=context)
 
3969
                sol_obj.update_or_cancel_line(cr, uid, move.sale_line_id.id, diff_qty, context=context)
 
3970
 
 
3971
        res = super(stock_move, self).action_cancel(cr, uid, ids, context=context)
 
3972
 
 
3973
        wf_service = netsvc.LocalService("workflow")
 
3974
 
 
3975
        proc_obj = self.pool.get('procurement.order')
 
3976
        proc_ids = proc_obj.search(cr, uid, [('move_id', 'in', ids)], context=context)
 
3977
        for proc in proc_obj.browse(cr, uid, proc_ids, context=context):
 
3978
            if proc.state == 'draft':
 
3979
                wf_service.trg_validate(uid, 'procurement.order', proc.id, 'button_confirm', cr)
 
3980
#            else:
 
3981
#                wf_service.trg_validate(uid, 'procurement.order', proc.id, 'button_check', cr)
 
3982
 
 
3983
        return res
 
3984
 
 
3985
    def update_linked_documents(self, cr, uid, ids, new_id, context=None):
 
3986
        '''
 
3987
        Update the linked documents of a stock move to another one
 
3988
        '''
 
3989
        context = context or {}
 
3990
        ids = isinstance(ids, (int, long)) and [ids] or ids
 
3991
 
 
3992
        for move_id in ids:
 
3993
            proc_ids = self.pool.get('procurement.order').search(cr, uid, [('move_id', '=', move_id)], context=context)
 
3994
            if proc_ids:
 
3995
                self.pool.get('procurement.order').write(cr, uid, proc_ids, {'move_id': new_id}, context=context)
 
3996
 
 
3997
            pol_ids = self.pool.get('purchase.order.line').search(cr, uid, [('move_dest_id', '=', move_id)], context=context)
 
3998
            if pol_ids:
 
3999
                self.pool.get('purchase.order.line').write(cr, uid, pol_ids, {'move_dest_id': new_id}, context=context)
 
4000
 
 
4001
            move_dest_ids = self.search(cr, uid, [('move_dest_id', '=', move_id)], context=context)
 
4002
            if move_dest_ids:
 
4003
                self.write(cr, uid, move_dest_ids, {'move_dest_id': new_id}, context=context)
 
4004
 
 
4005
            backmove_ids = self.search(cr, uid, [('backmove_id', '=', move_id)], context=context)
 
4006
            if backmove_ids:
 
4007
                self.write(cr, uid, backmove_ids, {'backmove_id': new_id}, context=context)
 
4008
 
 
4009
            pack_backmove_ids = self.search(cr, uid, [('backmove_packing_id', '=', move_id)], context=context)
 
4010
            if pack_backmove_ids:
 
4011
                self.write(cr, uid, pack_backmove_ids, {'backmove_packing_id': new_id}, context=context)
 
4012
 
 
4013
        return True
 
4014
 
 
4015
 
2455
4016
stock_move()
2456
4017
 
2457
4018
 
2458
 
class sale_order(osv.osv):
2459
 
    '''
2460
 
    re-override to modify behavior for outgoing workflow
2461
 
    '''
2462
 
    _inherit = 'sale.order'
2463
 
    _name = 'sale.order'
2464
 
    
2465
 
    def _hook_ship_create_stock_move(self, cr, uid, ids, context=None, *args, **kwargs):
2466
 
        '''
2467
 
        Please copy this to your module's method also.
2468
 
        This hook belongs to the action_ship_create method from sale>sale.py
2469
 
        
2470
 
        - allow to modify the data for stock move creation
2471
 
        '''
2472
 
        move_data = super(sale_order, self)._hook_ship_create_stock_move(cr, uid, ids, context=context, *args, **kwargs)
2473
 
        order = kwargs['order']
2474
 
        # first go to packing location
2475
 
        packing_id = order.shop_id.warehouse_id.lot_packing_id.id
2476
 
        move_data['location_dest_id'] = packing_id
2477
 
        move_data['state'] = 'confirmed'
2478
 
        return move_data
2479
 
    
2480
 
    def _hook_ship_create_stock_picking(self, cr, uid, ids, context=None, *args, **kwargs):
2481
 
        '''
2482
 
        Please copy this to your module's method also.
2483
 
        This hook belongs to the action_ship_create method from sale>sale.py
2484
 
        
2485
 
        - allow to modify the data for stock picking creation
2486
 
        '''
2487
 
        picking_data = super(sale_order, self)._hook_ship_create_stock_picking(cr, uid, ids, context=context, *args, **kwargs)
2488
 
        order = kwargs['order']
2489
 
        # use the name according to picking ticket sequence
2490
 
        pick_name = self.pool.get('ir.sequence').get(cr, uid, 'picking.ticket')
2491
 
        picking_data['name'] = pick_name
2492
 
        picking_data['state'] = 'draft'
2493
 
        picking_data['subtype'] = 'picking'
2494
 
        picking_data['flow_type'] = 'full'
2495
 
        picking_data['backorder_id'] = False
2496
 
        picking_data['warehouse_id'] = order.shop_id.warehouse_id.id
2497
 
        
2498
 
        return picking_data
2499
 
    
2500
 
    def _hook_ship_create_execute_picking_workflow(self, cr, uid, ids, context=None, *args, **kwargs):
2501
 
        '''
2502
 
        Please copy this to your module's method also.
2503
 
        This hook belongs to the action_ship_create method from sale>sale.py
2504
 
        
2505
 
        - allow to avoid the stock picking workflow execution
2506
 
        - trigger the logging message for the created picking, as it stays in draft state and no call to action_confirm is performed
2507
 
          for the moment within the msf_outgoing logic
2508
 
        '''
2509
 
        cond = super(sale_order, self)._hook_ship_create_execute_picking_workflow(cr, uid, ids, context=context, *args, **kwargs)
2510
 
        cond = cond and False
2511
 
        
2512
 
        # diplay creation message for draft picking ticket
2513
 
        picking_id = kwargs['picking_id']
2514
 
        picking_obj = self.pool.get('stock.picking')
2515
 
        if picking_id:
2516
 
            picking_obj.log_picking(cr, uid, [picking_id], context=context)
2517
 
        
2518
 
        return cond
2519
 
 
2520
 
sale_order()
 
4019
class pack_family_memory(osv.osv):
 
4020
    '''
 
4021
    dynamic memory object for pack families
 
4022
    '''
 
4023
    _name = 'pack.family.memory'
 
4024
    _auto = False
 
4025
    def init(self, cr):
 
4026
        cr.execute('''create or replace view pack_family_memory as (
 
4027
            select
 
4028
                min(m.id) as id,
 
4029
                p.shipment_id as shipment_id,
 
4030
                to_pack as to_pack,
 
4031
                array_agg(m.id) as move_lines,
 
4032
                min(from_pack) as from_pack,
 
4033
                case when to_pack=0 then 0 else to_pack-min(from_pack)+1 end as num_of_packs,
 
4034
                p.sale_id as sale_order_id,
 
4035
                case when p.subtype = 'ppl' then p.id else p.previous_step_id end as ppl_id,
 
4036
                min(m.length) as length,
 
4037
                min(m.width) as width,
 
4038
                min(m.height) as height,
 
4039
                min(m.weight) as weight,
 
4040
                min(m.state) as state,
 
4041
                min(m.location_id) as location_id,
 
4042
                min(m.location_dest_id) as location_dest_id,
 
4043
                min(m.pack_type) as pack_type,
 
4044
                p.id as draft_packing_id,
 
4045
                p.description_ppl as description_ppl,
 
4046
                '_name'::varchar(5) as name,
 
4047
                min(pl.currency_id) as currency_id,
 
4048
                sum(sol.price_unit * m.product_qty) as total_amount
 
4049
            from stock_picking p
 
4050
            inner join stock_move m on m.picking_id = p.id and m.state != 'cancel' and m.product_qty > 0
 
4051
            left join sale_order so on so.id = p.sale_id
 
4052
            left join sale_order_line sol on sol.id = m.sale_line_id
 
4053
            left join product_pricelist pl on pl.id = so.pricelist_id
 
4054
            where p.shipment_id is not null
 
4055
            group by p.shipment_id, p.description_ppl, to_pack, sale_id, p.subtype, p.id, p.previous_step_id
 
4056
    )
 
4057
    ''')
 
4058
 
 
4059
    def _vals_get(self, cr, uid, ids, fields, arg, context=None):
 
4060
        '''
 
4061
        get functional values
 
4062
        '''
 
4063
        result = {}
 
4064
        compute_moves = not fields or 'move_lines' in fields
 
4065
        for pf_memory in self.browse(cr, uid, ids, context=context):
 
4066
            values = {
 
4067
                'amount': 0.0,
 
4068
                'total_weight': 0.0,
 
4069
                'total_volume': 0.0,
 
4070
            }
 
4071
            if compute_moves:
 
4072
                values['move_lines'] = []
 
4073
            num_of_packs = pf_memory.num_of_packs
 
4074
            if num_of_packs:
 
4075
                values['amount'] = pf_memory.total_amount / num_of_packs
 
4076
            values['total_weight'] = pf_memory.weight * num_of_packs
 
4077
            values['total_volume'] = (pf_memory.length * pf_memory.width * pf_memory.height * num_of_packs) / 1000.0
 
4078
 
 
4079
            result[pf_memory.id] = values
 
4080
 
 
4081
        if compute_moves and ids:
 
4082
            if isinstance(ids, (int, long)):
 
4083
                ids = [ids]
 
4084
 
 
4085
            cr.execute('select id, move_lines from ' + self._table + ' where id in %s', (tuple(ids),))
 
4086
            for q_result in cr.fetchall():
 
4087
                result[q_result[0]]['move_lines'] = q_result[1] or []
 
4088
        return result
 
4089
 
 
4090
    _columns = {
 
4091
        'name': fields.char(string='Reference', size=1024),
 
4092
        'shipment_id': fields.many2one('shipment', string='Shipment'),
 
4093
        'draft_packing_id': fields.many2one('stock.picking', string="Draft Packing Ref"),
 
4094
        'sale_order_id': fields.many2one('sale.order', string="Sale Order Ref"),
 
4095
        'ppl_id': fields.many2one('stock.picking', string="PPL Ref"),
 
4096
        'from_pack': fields.integer(string='From p.'),
 
4097
        'to_pack': fields.integer(string='To p.'),
 
4098
        'pack_type': fields.many2one('pack.type', string='Pack Type'),
 
4099
        'length' : fields.float(digits=(16, 2), string='Length [cm]'),
 
4100
        'width' : fields.float(digits=(16, 2), string='Width [cm]'),
 
4101
        'height' : fields.float(digits=(16, 2), string='Height [cm]'),
 
4102
        'weight' : fields.float(digits=(16, 2), string='Weight p.p [kg]'),
 
4103
        # functions
 
4104
        'move_lines': fields.function(_vals_get, method=True, type='one2many', relation='stock.move', string='Stock Moves', multi='get_vals',),
 
4105
        'state': fields.selection(selection=[
 
4106
            ('draft', 'Draft'),
 
4107
            ('assigned', 'Available'),
 
4108
            ('stock_return', 'Returned to Stock'),
 
4109
            ('ship_return', 'Returned from Shipment'),
 
4110
            ('cancel', 'Cancelled'),
 
4111
            ('done', 'Closed'), ], string='State'),
 
4112
        'location_id': fields.many2one('stock.location', string='Src Loc.'),
 
4113
        'location_dest_id': fields.many2one('stock.location', string='Dest. Loc.'),
 
4114
        'total_amount': fields.float('Total Amount'),
 
4115
        'amount': fields.function(_vals_get, method=True, type='float', string='Pack Amount', multi='get_vals',),
 
4116
        'currency_id': fields.many2one('res.currency', string='Currency'),
 
4117
        'num_of_packs': fields.integer('#Packs'),
 
4118
        'total_weight': fields.function(_vals_get, method=True, type='float', string='Total Weight[kg]', multi='get_vals',),
 
4119
        'total_volume': fields.function(_vals_get, method=True, type='float', string=u'Total Volume[dm³]', multi='get_vals',),
 
4120
        'description_ppl': fields.char('Description', size=256),
 
4121
    }
 
4122
 
 
4123
    _defaults = {
 
4124
        'shipment_id': False,
 
4125
        'draft_packing_id': False,
 
4126
    }
 
4127
 
 
4128
pack_family_memory()
 
4129
 
 
4130
 
 
4131
class procurement_order(osv.osv):
 
4132
    '''
 
4133
    procurement order workflow
 
4134
    '''
 
4135
    _inherit = 'procurement.order'
 
4136
 
 
4137
    def _hook_check_mts_on_message(self, cr, uid, context=None, *args, **kwargs):
 
4138
        '''
 
4139
        Please copy this to your module's method also.
 
4140
        This hook belongs to the _check_make_to_stock_product method from procurement>procurement.py>procurement.order
 
4141
 
 
4142
        - allow to modify the message written back to procurement order
 
4143
        '''
 
4144
        message = super(procurement_order, self)._hook_check_mts_on_message(cr, uid, context=context, *args, **kwargs)
 
4145
        procurement = kwargs['procurement']
 
4146
        if procurement.move_id.picking_id.state == 'draft' and procurement.move_id.picking_id.subtype == 'picking':
 
4147
            message = _("Shipment Process in Progress.")
 
4148
        return message
 
4149
 
 
4150
procurement_order()
 
4151