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

« back to all changes in this revision

Viewing changes to delivery_mechanism/delivery_mechanism.py

  • Committer: jf
  • Date: 2012-06-13 12:43:21 UTC
  • mfrom: (827.5.11 uf-635)
  • Revision ID: jf@tempo4-20120613124321-2b8cwgl86gyy2tb7
UF-635 [DEV] Documents workflow: Graphic representation
lp:~unifield-team/unifield-wm/uf-635 revno 838

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
##############################################################################
 
3
#
 
4
#    OpenERP, Open Source Management Solution
 
5
#    Copyright (C) 2011 TeMPO Consulting, MSF 
 
6
#
 
7
#    This program is free software: you can redistribute it and/or modify
 
8
#    it under the terms of the GNU Affero General Public License as
 
9
#    published by the Free Software Foundation, either version 3 of the
 
10
#    License, or (at your option) any later version.
 
11
#
 
12
#    This program is distributed in the hope that it will be useful,
 
13
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
14
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
15
#    GNU Affero General Public License for more details.
 
16
#
 
17
#    You should have received a copy of the GNU Affero General Public License
 
18
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
19
#
 
20
##############################################################################
 
21
 
 
22
from osv import osv, fields
 
23
 
 
24
import time
 
25
 
 
26
from tools.translate import _
 
27
from dateutil.relativedelta import relativedelta
 
28
from datetime import datetime
 
29
 
 
30
import netsvc
 
31
 
 
32
 
 
33
class stock_move(osv.osv):
 
34
    '''
 
35
    new function to get mirror move
 
36
    '''
 
37
    _inherit = 'stock.move'
 
38
    _columns = {'line_number': fields.integer(string='Line', required=True),
 
39
                'change_reason': fields.char(string='Change Reason', size=1024, readonly=True),
 
40
                }
 
41
    _defaults = {'line_number': 0,}
 
42
    _order = 'line_number'
 
43
    
 
44
    def create(self, cr, uid, vals, context=None):
 
45
        '''
 
46
        add the corresponding line number
 
47
        
 
48
        if a corresponding purchase order line or sale order line exist
 
49
        we take the line number from there
 
50
        '''
 
51
        # object
 
52
        picking_obj = self.pool.get('stock.picking')
 
53
        pol_obj = self.pool.get('purchase.order.line')
 
54
        sol_obj = self.pool.get('sale.order.line')
 
55
        seq_pool = self.pool.get('ir.sequence')
 
56
 
 
57
        # line number correspondance to be checked with Magali
 
58
        if vals.get('picking_id'):
 
59
            if vals.get('purchase_line_id') and False:
 
60
                # from purchase order line
 
61
                line = pol_obj.read(cr, uid, [vals.get('purchase_line_id')], ['line_number'], context=context)[0]['line_number']
 
62
            elif vals.get('sale_line_id') and False:
 
63
                # from sale order line
 
64
                line = sol_obj.read(cr, uid, [vals.get('sale_line_id')], ['line_number'], context=context)[0]['line_number']
 
65
            else:
 
66
                # new numbers - gather the line number from the sequence
 
67
                sequence_id = picking_obj.read(cr, uid, [vals['picking_id']], ['move_sequence_id'], context=context)[0]['move_sequence_id'][0]
 
68
                line = seq_pool.get_id(cr, uid, sequence_id, test='id', context=context)
 
69
            # update values with line value
 
70
            vals.update({'line_number': line})
 
71
        
 
72
        # create the new object
 
73
        result = super(stock_move, self).create(cr, uid, vals, context=context)
 
74
        return result
 
75
    
 
76
    def get_mirror_move(self, cr, uid, ids, data_back, context=None):
 
77
        '''
 
78
        return a dictionary with IN for OUT and OUT for IN, if exists, False otherwise
 
79
        
 
80
        only one mirror object should exist for each object (to check)
 
81
        return objects which are not done
 
82
        
 
83
        same sale_line_id/purchase_line_id - same product - same quantity
 
84
        
 
85
        IN: move -> po line -> procurement -> so line -> move
 
86
        OUT: move -> so line -> procurement -> po line -> move
 
87
        
 
88
        I dont use move.move_dest_id because of back orders both on OUT and IN sides
 
89
        '''
 
90
        if context is None:
 
91
            context = {}
 
92
        if isinstance(ids, (int, long)):
 
93
            ids = [ids]
 
94
            
 
95
        # objects
 
96
        so_line_obj = self.pool.get('sale.order.line')
 
97
            
 
98
        res = {}
 
99
        for obj in self.browse(cr, uid, ids, context=context):
 
100
            res[obj.id] = False
 
101
            if obj.picking_id and obj.picking_id.type == 'in':
 
102
                # we are looking for corresponding OUT move from sale order line
 
103
                if obj.purchase_line_id:
 
104
                    # linekd to a po
 
105
                    if obj.purchase_line_id.procurement_id:
 
106
                        # on order
 
107
                        procurement_id = obj.purchase_line_id.procurement_id.id
 
108
                        # find the corresponding sale order line
 
109
                        so_line_ids = so_line_obj.search(cr, uid, [('procurement_id', '=', procurement_id)], context=context)
 
110
                        # if the procurement comes from replenishment rules, there will be a procurement, but no associated sale order line
 
111
                        # we therefore do not raise an exception, but handle the case only if sale order lines are found
 
112
                        if so_line_ids:
 
113
                            # find the corresponding OUT move
 
114
                            # move_ids = self.search(cr, uid, [('product_id', '=', obj.product_id.id), ('product_qty', '=', obj.product_qty), ('state', 'in', ('assigned', 'confirmed')), ('sale_line_id', '=', so_line_ids[0])], context=context)
 
115
                            move_ids = self.search(cr, uid, [('product_id', '=', data_back['product_id']), ('state', 'in', ('assigned', 'confirmed')), ('sale_line_id', '=', so_line_ids[0])], context=context)
 
116
                            # list of matching out moves
 
117
                            integrity_check = []
 
118
                            for move in self.browse(cr, uid, move_ids, context=context):
 
119
                                # move from draft picking or standard picking
 
120
                                if (move.picking_id.subtype == 'picking' and not move.picking_id.backorder_id and move.picking_id.state == 'draft') or (move.picking_id.subtype == 'standard') and move.picking_id.type == 'out':
 
121
                                    integrity_check.append(move.id)
 
122
                            # return the first one matching
 
123
                            if integrity_check:
 
124
                                res[obj.id] = integrity_check[0]
 
125
            else:
 
126
                # we are looking for corresponding IN from on_order purchase order
 
127
                assert False, 'This method is not implemented for OUT or Internal moves'
 
128
                
 
129
        return res
 
130
    
 
131
stock_move()
 
132
 
 
133
 
 
134
class stock_picking(osv.osv):
 
135
    '''
 
136
    do_partial modification
 
137
    '''
 
138
    _inherit = 'stock.picking'
 
139
    _columns = {'move_sequence_id': fields.many2one('ir.sequence', string='Moves Sequence', help="This field contains the information related to the numbering of the moves of this picking.", required=True, ondelete='cascade'),
 
140
                'change_reason': fields.char(string='Change Reason', size=1024, readonly=True),
 
141
                }
 
142
    
 
143
    def _stock_picking_action_process_hook(self, cr, uid, ids, context=None, *args, **kwargs):
 
144
        '''
 
145
        Please copy this to your module's method also.
 
146
        This hook belongs to the action_process method from stock>stock.py>stock_picking
 
147
        
 
148
        - allow to modify the data for wizard display
 
149
        '''
 
150
        if context is None:
 
151
            context = {}
 
152
        if isinstance(ids, (int, long)):
 
153
            ids = [ids]
 
154
        res = super(stock_picking, self)._stock_picking_action_process_hook(cr, uid, ids, context=context, *args, **kwargs)
 
155
        wizard_obj = self.pool.get('wizard')
 
156
        res = wizard_obj.open_wizard(cr, uid, ids, type='update', context=dict(context,
 
157
                                                                               wizard_ids=[res['res_id']],
 
158
                                                                               wizard_name=res['name'],
 
159
                                                                               model=res['res_model'],
 
160
                                                                               step='default'))
 
161
        return res
 
162
    
 
163
    def create(self, cr, uid, vals, context=None):
 
164
        '''
 
165
        create the sequence for the numbering of the lines
 
166
        '''
 
167
        # object
 
168
        seq_pool = self.pool.get('ir.sequence')
 
169
        po_obj = self.pool.get('purchase.order')
 
170
        so_obj = self.pool.get('sale.order')
 
171
        
 
172
        new_seq_id = self.create_sequence(cr, uid, vals, context=context)
 
173
        vals.update({'move_sequence_id': new_seq_id,})
 
174
        # if from order, we udpate the sequence to match the order's one
 
175
        # line number correspondance to be checked with Magali
 
176
        seq_value = False
 
177
        if vals.get('purchase_id') and False:
 
178
            seq_id = po_obj.read(cr, uid, [vals.get('purchase_id')], ['sequence_id'], context=context)[0]['sequence_id'][0]
 
179
            seq_value = seq_pool.read(cr, uid, [seq_id], ['number_next'], context=context)[0]['number_next']
 
180
        elif vals.get('sale_id') and False:
 
181
            seq_id = po_obj.read(cr, uid, [vals.get('sale_id')], ['sequence_id'], context=context)[0]['sequence_id'][0]
 
182
            seq_value = seq_pool.read(cr, uid, [seq_id], ['number_next'], context=context)[0]['number_next']
 
183
        
 
184
        if seq_value:
 
185
            # update sequence value of stock picking to match order's one
 
186
            seq_pool.write(cr, uid, [new_seq_id], {'number_next': seq_value,})
 
187
            
 
188
        return super(stock_picking, self).create(cr, uid, vals, context=context)
 
189
    
 
190
    def create_data_back(self, cr, uid, move, context=None):
 
191
        '''
 
192
        build data_back dictionary
 
193
        '''
 
194
        res = {'id': move.id,
 
195
               'name': move.product_id.partner_ref,
 
196
               'product_id': move.product_id.id,
 
197
               'product_uom': move.product_uom.id,
 
198
               'product_qty': move.product_qty,
 
199
               }
 
200
        return res
 
201
    
 
202
    def _update_mirror_move(self, cr, uid, ids, data_back, diff_qty, out_move=False, context=None):
 
203
        '''
 
204
        update the mirror move with difference quantity diff_qty
 
205
        
 
206
        if out_move is provided, it is used for copy if another cannot be found (meaning the one provided does
 
207
        not fit anyhow)
 
208
        
 
209
        # NOTE: the price is not update in OUT move according to average price computation. this is an open point.
 
210
        
 
211
        if diff_qty < 0, the qty is decreased
 
212
        if diff_qty > 0, the qty is increased
 
213
        '''
 
214
        # stock move object
 
215
        move_obj = self.pool.get('stock.move')
 
216
        product_obj = self.pool.get('product.product')
 
217
        # first look for a move - we search even if we get out_move because out_move
 
218
        # may not be valid anymore (product changed) - get_mirror_move will validate it or return nothing
 
219
        out_move_id = move_obj.get_mirror_move(cr, uid, [data_back['id']], data_back, context=context)[data_back['id']]
 
220
        if not out_move_id and out_move:
 
221
            # copy existing out_move with move properties: - update the name of the stock move
 
222
            # the state is confirmed, we dont know if available yet - should be in input location before stock
 
223
            values = {'name': data_back['name'],
 
224
                      'product_id': data_back['product_id'],
 
225
                      'product_qty': 0,
 
226
                      'product_uos_qty': 0,
 
227
                      'product_uom': data_back['product_uom'],
 
228
                      'state': 'confirmed',
 
229
                      }
 
230
            out_move_id = move_obj.copy(cr, uid, out_move, values, context=context)
 
231
        # update quantity
 
232
        if out_move_id:
 
233
            # decrease/increase depending on diff_qty sign the qty by diff_qty
 
234
            data = move_obj.read(cr, uid, [out_move_id], ['product_qty', 'picking_id', 'name', 'product_uom'], context=context)[0]
 
235
            picking_out_name = data['picking_id'][1]
 
236
            stock_move_name = data['name']
 
237
            uom_name = data['product_uom'][1]
 
238
            present_qty = data['product_qty']
 
239
            new_qty = max(present_qty + diff_qty, 0)
 
240
            move_obj.write(cr, uid, [out_move_id], {'product_qty' : new_qty,
 
241
                                                    'product_uos_qty': new_qty,}, context=context)
 
242
            # log the modification
 
243
            # log creation message
 
244
            move_obj.log(cr, uid, out_move_id, _('The Stock Move %s from %s has been updated to %s %s.')%(stock_move_name, picking_out_name, new_qty, uom_name))
 
245
        # return updated move or False
 
246
        return out_move_id
 
247
    
 
248
    def _do_incoming_shipment_first_hook(self, cr, uid, ids, context=None, *args, **kwargs):
 
249
        '''
 
250
        hook to update values for stock move if first encountered
 
251
        '''
 
252
        values = kwargs.get('values')
 
253
        assert values is not None, 'missing values'
 
254
        return values
 
255
 
 
256
    
 
257
    def do_incoming_shipment(self, cr, uid, ids, context=None):
 
258
        '''
 
259
        validate the picking ticket from selected stock moves
 
260
        
 
261
        move here the logic of validate picking
 
262
        available for picking loop
 
263
        '''
 
264
        assert context, 'context is not defined'
 
265
        assert 'partial_datas' in context, 'partial datas not present in context'
 
266
        partial_datas = context['partial_datas']
 
267
        if isinstance(ids, (int, long)):
 
268
            ids = [ids]
 
269
        
 
270
        # sequence object
 
271
        sequence_obj = self.pool.get('ir.sequence')
 
272
        # stock move object
 
273
        move_obj = self.pool.get('stock.move')
 
274
        product_obj = self.pool.get('product.product')
 
275
        currency_obj = self.pool.get('res.currency')
 
276
        uom_obj = self.pool.get('product.uom')
 
277
        # create picking object
 
278
        create_picking_obj = self.pool.get('create.picking')
 
279
        # workflow
 
280
        wf_service = netsvc.LocalService("workflow")
 
281
       
 
282
        internal_loc_ids = self.pool.get('stock.location').search(cr, uid, [('usage','=','internal')])
 
283
        ctx_avg = context.copy()
 
284
        ctx_avg['location'] = internal_loc_ids
 
285
        for pick in self.browse(cr, uid, ids, context=context):
 
286
            # corresponding backorder object - not necessarily created
 
287
            backorder_id = None
 
288
            # treat moves
 
289
            move_ids = partial_datas[pick.id].keys()
 
290
            # all moves
 
291
            all_move_ids = [move.id for move in pick.move_lines]
 
292
            # related moves - swap if a backorder is created - openERP logic
 
293
            done_moves = []
 
294
            # average price computation
 
295
            product_avail = {}
 
296
            for move in move_obj.browse(cr, uid, move_ids, context=context):
 
297
                # keep data for back order creation
 
298
                data_back = self.create_data_back(cr, uid, move, context=context)
 
299
                # qty selected
 
300
                count = 0
 
301
                # flag to update the first move - if split was performed during the validation, new stock moves are created
 
302
                first = True
 
303
                # force complete flag = validate all partial for the same move have the same force complete value
 
304
                force_complete = False
 
305
                # initial qty
 
306
                initial_qty = move.product_qty
 
307
                # corresponding out move
 
308
                out_move_id = move_obj.get_mirror_move(cr, uid, [move.id], data_back, context=context)[move.id]
 
309
                # update out flag
 
310
                update_out = (len(partial_datas[pick.id][move.id]) > 1)
 
311
                # average price computation, new values - should be the same for every partial
 
312
                average_values = {}
 
313
                # partial list
 
314
                for partial in partial_datas[pick.id][move.id]:
 
315
                    # original openERP logic - average price computation - To be validated by Matthias
 
316
                    # Average price computation
 
317
                    # selected product from wizard must be tested
 
318
                    product = product_obj.browse(cr, uid, partial['product_id'], context=ctx_avg)
 
319
                    if (pick.type == 'in') and (product.cost_method == 'average'):
 
320
                        move_currency_id = move.company_id.currency_id.id
 
321
                        context['currency_id'] = move_currency_id
 
322
                        # datas from partial
 
323
                        product_uom = partial['product_uom']
 
324
                        product_qty = partial['product_qty']
 
325
                        product_currency = partial['product_currency']
 
326
                        product_price = partial['product_price']
 
327
                        qty = uom_obj._compute_qty(cr, uid, product_uom, product_qty, product.uom_id.id)
 
328
    
 
329
                        if product.id in product_avail:
 
330
                            product_avail[product.id] += qty
 
331
                        else:
 
332
                            product_avail[product.id] = product.qty_available
 
333
    
 
334
                        if qty > 0:
 
335
                            new_price = currency_obj.compute(cr, uid, product_currency,
 
336
                                    move_currency_id, product_price)
 
337
                            new_price = uom_obj._compute_price(cr, uid, product_uom, new_price,
 
338
                                    product.uom_id.id)
 
339
                            if product.qty_available <= 0:
 
340
                                new_std_price = new_price
 
341
                            else:
 
342
                                # Get the standard price
 
343
                                amount_unit = product.price_get('standard_price', context)[product.id]
 
344
                                # check no division by zero
 
345
                                if product_avail[product.id] + qty:
 
346
                                    new_std_price = ((amount_unit * product_avail[product.id])\
 
347
                                        + (new_price * qty))/(product_avail[product.id] + qty)
 
348
                                else:
 
349
                                    new_std_price = 0.0
 
350
                                            
 
351
                            # Write the field according to price type field
 
352
                            product_obj.write(cr, uid, [product.id], {'standard_price': new_std_price})
 
353
    
 
354
                            # Record the values that were chosen in the wizard, so they can be
 
355
                            # used for inventory valuation if real-time valuation is enabled.
 
356
                            average_values = {'price_unit': product_price,
 
357
                                              'price_currency_id': product_currency}
 
358
                    # the quantity
 
359
                    count = count + partial['product_qty']
 
360
                    if first:
 
361
                        first = False
 
362
                        # update existing move
 
363
                        values = {'name': partial['name'],
 
364
                                  'product_id': partial['product_id'],
 
365
                                  'product_qty': partial['product_qty'],
 
366
                                  'product_uos_qty': partial['product_qty'],
 
367
                                  'prodlot_id': partial['prodlot_id'],
 
368
                                  'product_uom': partial['product_uom'],
 
369
                                  'asset_id': partial['asset_id'],
 
370
                                  'change_reason': partial['change_reason'],
 
371
                                  }
 
372
                        # average computation - empty if not average
 
373
                        values.update(average_values)
 
374
                        values = self._do_incoming_shipment_first_hook(cr, uid, ids, context, values=values)
 
375
                        move_obj.write(cr, uid, [move.id], values, context=context)
 
376
                        done_moves.append(move.id)
 
377
                        # if split happened, we update the corresponding OUT move
 
378
                        if out_move_id:
 
379
                            if update_out:
 
380
                                move_obj.write(cr, uid, [out_move_id], values, context=context)
 
381
                            elif move.product_id.id != partial['product_id']:
 
382
                                # no split but product changed, we have to update the corresponding out move
 
383
                                move_obj.write(cr, uid, [out_move_id], values, context=context)
 
384
                                # we force update flag - out will be updated if qty is missing - possibly with the creation of a new move
 
385
                                update_out = True
 
386
                    else:
 
387
                        # split happened during the validation
 
388
                        # copy the stock move and set the quantity
 
389
                        values = {'name': partial['name'],
 
390
                                  'product_id': partial['product_id'],
 
391
                                  'product_qty': partial['product_qty'],
 
392
                                  'product_uos_qty': partial['product_qty'],
 
393
                                  'prodlot_id': partial['prodlot_id'],
 
394
                                  'product_uom': partial['product_uom'],
 
395
                                  'asset_id': partial['asset_id'],
 
396
                                  'change_reason': partial['change_reason'],
 
397
                                  'state': 'assigned',
 
398
                                  }
 
399
                        # average computation - empty if not average
 
400
                        values.update(average_values)
 
401
                        new_move = move_obj.copy(cr, uid, move.id, values, context=context)
 
402
                        done_moves.append(new_move)
 
403
                        if out_move_id:
 
404
                            new_out_move = move_obj.copy(cr, uid, out_move_id, values, context=context)
 
405
                # decrement the initial move, cannot be less than zero
 
406
                diff_qty = initial_qty - count
 
407
                # the quantity after the process does not correspond to the incoming shipment quantity
 
408
                # the difference is written back to incoming shipment - and possibilty to OUT if split happened
 
409
                # is positive if some qty was removed during the process -> current incoming qty is modified
 
410
                #    create a backorder if does not exist, copy original move with difference qty in it # DOUBLE CHECK ORIGINAL FUNCTION BEHAVIOR !!!!!
 
411
                #    if split happened, update the corresponding out move with diff_qty
 
412
                if diff_qty > 0:
 
413
                    if not backorder_id:
 
414
                        # create the backorder - with no lines
 
415
                        backorder_id = self.copy(cr, uid, pick.id, {'name': sequence_obj.get(cr, uid, 'stock.picking.%s'%(pick.type)),
 
416
                                                                    'move_lines' : [],
 
417
                                                                    'state':'draft',
 
418
                                                                    })
 
419
                    # create the corresponding move in the backorder - reset productionlot
 
420
                    defaults = {'name': data_back['name'],
 
421
                                'product_id': data_back['product_id'],
 
422
                                'product_uom': data_back['product_uom'],
 
423
                                'product_qty': diff_qty,
 
424
                                'product_uos_qty': diff_qty,
 
425
                                'picking_id': pick.id, # put in the current picking which will be the actual backorder (OpenERP logic)
 
426
                                'prodlot_id': False,
 
427
                                'state': 'assigned',
 
428
                                'move_dest_id': False,
 
429
                                'price_unit': move.price_unit,
 
430
                                'change_reason': False,
 
431
                                }
 
432
                    # average computation - empty if not average
 
433
                    defaults.update(average_values)
 
434
                    new_back_move = move_obj.copy(cr, uid, move.id, defaults, context=context)
 
435
                    # if split happened
 
436
                    if update_out:
 
437
                        # update out move - quantity is increased, to match the original qty
 
438
                        self._update_mirror_move(cr, uid, ids, data_back, diff_qty, out_move=out_move_id, context=context)
 
439
                # is negative if some qty was added during the validation -> draft qty is increased
 
440
                if diff_qty < 0:
 
441
                    # we update the corresponding OUT object if exists - we want to increase the qty if no split happened
 
442
                    # if split happened and quantity is bigger, the quantities are already updated with stock moves creation
 
443
                    if not update_out:
 
444
                        update_qty = -diff_qty
 
445
                        self._update_mirror_move(cr, uid, ids, data_back, update_qty, out_move=out_move_id, context=context)
 
446
            # clean the picking object - removing lines with 0 qty - force unlink
 
447
            # this should not be a problem as IN moves are not referenced by other objects, only OUT moves are referenced
 
448
            for move in pick.move_lines:
 
449
                if not move.product_qty and move.state not in ('done', 'cancel'):
 
450
                    done_moves.remove(move.id)
 
451
                    move.unlink(context=dict(context, call_unlink=True))
 
452
            # At first we confirm the new picking (if necessary) - **corrected** inverse openERP logic !
 
453
            if backorder_id:
 
454
                # done moves go to new picking object
 
455
                move_obj.write(cr, uid, done_moves, {'picking_id': backorder_id}, context=context)
 
456
                wf_service.trg_validate(uid, 'stock.picking', backorder_id, 'button_confirm', cr)
 
457
                # Then we finish the good picking
 
458
                self.write(cr, uid, [pick.id], {'backorder_id': backorder_id}, context=context)
 
459
                self.action_move(cr, uid, [backorder_id])
 
460
                wf_service.trg_validate(uid, 'stock.picking', backorder_id, 'button_done', cr)
 
461
                wf_service.trg_write(uid, 'stock.picking', pick.id, cr)
 
462
            else:
 
463
                self.action_move(cr, uid, [pick.id])
 
464
                wf_service.trg_validate(uid, 'stock.picking', pick.id, 'button_done', cr)
 
465
 
 
466
        return {'type': 'ir.actions.act_window_close'}
 
467
    
 
468
    def enter_reason(self, cr, uid, ids, context=None):
 
469
        '''
 
470
        open reason wizard
 
471
        '''
 
472
        # we need the context for the wizard switch
 
473
        if context is None:
 
474
            context = {}
 
475
        # data
 
476
        name = _("Enter a Reason for Incoming cancellation")
 
477
        model = 'enter.reason'
 
478
        step = 'default'
 
479
        wiz_obj = self.pool.get('wizard')
 
480
        # open the selected wizard
 
481
        return wiz_obj.open_wizard(cr, uid, ids, name=name, model=model, step=step, context=dict(context, picking_id=ids[0]))
 
482
    
 
483
    def cancel_and_update_out(self, cr, uid, ids, context=None):
 
484
        '''
 
485
        update corresponding out picking if exists and cancel the picking
 
486
        '''
 
487
        if context is None:
 
488
            context = {}
 
489
        if isinstance(ids, (int, long)):
 
490
            ids = [ids]
 
491
        
 
492
        # objects
 
493
        move_obj = self.pool.get('stock.move')
 
494
        purchase_obj = self.pool.get('purchase.order')
 
495
        # workflow
 
496
        wf_service = netsvc.LocalService("workflow")
 
497
        
 
498
        for obj in self.browse(cr, uid, ids, context=context):
 
499
            # corresponding sale ids to be manually corrected after purchase workflow trigger
 
500
            sale_ids = []
 
501
            for move in obj.move_lines:
 
502
                data_back = self.create_data_back(cr, uid, move, context=context)
 
503
                diff_qty = -data_back['product_qty']
 
504
                # update corresponding out move
 
505
                out_move_id = self._update_mirror_move(cr, uid, ids, data_back, diff_qty, out_move=False, context=context)
 
506
                # for out cancellation, two points:
 
507
                # - if pick/pack/ship: check that nothing is in progress
 
508
                # - if nothing in progress, and the out picking is canceled, trigger the so to correct the corresponding so manually
 
509
                if out_move_id:
 
510
                    out_move = move_obj.browse(cr, uid, out_move_id, context=context)
 
511
                    cond1 = out_move.picking_id.subtype == 'standard'
 
512
                    cond2 = out_move.picking_id.subtype == 'picking' and not out_move.picking_id.has_picking_ticket_in_progress(context=context)[out_move.picking_id.id]
 
513
                    if (cond1 or cond2) and out_move.picking_id.type == 'out' and not out_move.product_qty:
 
514
                        # the corresponding move can be canceled - the OUT picking workflow is triggered automatically if needed
 
515
                        move_obj.action_cancel(cr, uid, [out_move_id], context=context)
 
516
                        # open points:
 
517
                        # - when searching for open picking tickets - we should take into account the specific move (only product id ?)
 
518
                        # - and also the state of the move not in (cancel done)
 
519
                        # correct the corresponding so manually if exists - could be in shipping exception
 
520
                        if out_move.picking_id and out_move.picking_id.sale_id:
 
521
                            if out_move.picking_id.sale_id.id not in sale_ids:
 
522
                                sale_ids.append(out_move.picking_id.sale_id.id)
 
523
                            
 
524
            # correct the corresponding po manually if exists - should be in shipping exception
 
525
            if obj.purchase_id:
 
526
                wf_service.trg_validate(uid, 'purchase.order', obj.purchase_id.id, 'picking_ok', cr)
 
527
                purchase_obj.log(cr, uid, obj.purchase_id.id, _('The Purchase Order %s is %s%% received.')%(obj.purchase_id.name, round(obj.purchase_id.shipped_rate,2)))
 
528
            # correct the corresponding so
 
529
            for sale_id in sale_ids:
 
530
                wf_service.trg_validate(uid, 'sale.order', sale_id, 'ship_corrected', cr)
 
531
        
 
532
        return True
 
533
        
 
534
stock_picking()
 
535
 
 
536
 
 
537
class purchase_order_line(osv.osv):
 
538
    '''
 
539
    add the link to procurement order
 
540
    '''
 
541
    _inherit = 'purchase.order.line'
 
542
    _columns= {'procurement_id': fields.many2one('procurement.order', string='Procurement Reference', readonly=True,),
 
543
               }
 
544
    _defaults = {'procurement_id': False,}
 
545
    
 
546
    
 
547
purchase_order_line()
 
548
 
 
549
 
 
550
class procurement_order(osv.osv):
 
551
    '''
 
552
    inherit po_values_hook
 
553
    '''
 
554
    _inherit = 'procurement.order'
 
555
 
 
556
    def po_line_values_hook(self, cr, uid, ids, context=None, *args, **kwargs):
 
557
        '''
 
558
        Please copy this to your module's method also.
 
559
        This hook belongs to the make_po method from purchase>purchase.py>procurement_order
 
560
        
 
561
        - allow to modify the data for purchase order line creation
 
562
        '''
 
563
        if isinstance(ids, (int, long)):
 
564
            ids = [ids]
 
565
        line = super(procurement_order, self).po_line_values_hook(cr, uid, ids, context=context, *args, **kwargs)
 
566
        # give the purchase order line a link to corresponding procurement
 
567
        procurement = kwargs['procurement']
 
568
        line.update({'procurement_id': procurement.id,})
 
569
        return line
 
570
    
 
571
procurement_order()
 
572