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

« back to all changes in this revision

Viewing changes to purchase_override/purchase.py

  • Committer: jf
  • Date: 2011-05-16 09:55:17 UTC
  • mfrom: (129.1.1 unifield-wm)
  • Revision ID: jf@tempo4-20110516095517-giuzv2mouka39jb8
UF-270 Advance return in a currency that is not the functional currency

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
 
from order_types import ORDER_PRIORITY, ORDER_CATEGORY
24
 
from tools.translate import _
25
 
import netsvc
26
 
from mx.DateTime import *
27
 
import time
28
 
 
29
 
from workflow.wkf_expr import _eval_expr
30
 
import logging
31
 
 
32
 
from dateutil.relativedelta import relativedelta
33
 
from datetime import datetime
34
 
 
35
 
from purchase_override import PURCHASE_ORDER_STATE_SELECTION
36
 
 
37
 
class purchase_order_confirm_wizard(osv.osv):
38
 
    _name = 'purchase.order.confirm.wizard'
39
 
    
40
 
    _columns = {
41
 
            'order_id': fields.many2one('purchase.order', string='Purchase Order', readonly=True),
42
 
            'errors': fields.text(string='Error message', readonly=True),
43
 
        }
44
 
    
45
 
    def validate_order(self, cr, uid, ids, context=None):
46
 
        wf_service = netsvc.LocalService("workflow")
47
 
        for wiz in self.browse(cr, uid, ids, context=context):
48
 
            wf_service.trg_validate(uid, 'purchase.order', wiz.order_id.id, 'purchase_confirmed_wait', cr)
49
 
        return {'type': 'ir.actions.act_window_close'}
50
 
    
51
 
purchase_order_confirm_wizard()
52
 
 
53
 
class purchase_order(osv.osv):
54
 
    _name = 'purchase.order'
55
 
    _inherit = 'purchase.order'
56
 
 
57
 
    def copy(self, cr, uid, id, default=None, context=None):
58
 
        '''
59
 
        Remove loan_id field on new purchase.order
60
 
        '''
61
 
        if not default:
62
 
            default = {}
63
 
        default.update({'loan_id': False, 'merged_line_ids': False, 'origin': False})
64
 
        return super(purchase_order, self).copy(cr, uid, id, default, context=context)
65
 
    
66
 
    # @@@purchase.purchase_order._invoiced
67
 
    def _invoiced(self, cursor, user, ids, name, arg, context=None):
68
 
        res = {}
69
 
        for purchase in self.browse(cursor, user, ids, context=context):
70
 
            invoiced = False
71
 
            if purchase.invoiced_rate == 100.00:
72
 
                invoiced = True
73
 
            res[purchase.id] = invoiced
74
 
        return res
75
 
    # @@@end
76
 
    
77
 
    # @@@purchase.purchase_order._shipped_rate
78
 
    def _invoiced_rate(self, cursor, user, ids, name, arg, context=None):
79
 
        res = {}
80
 
        for purchase in self.browse(cursor, user, ids, context=context):
81
 
            if ((purchase.order_type == 'regular' and purchase.partner_id.partner_type == 'internal') or \
82
 
                purchase.order_type in ['donation_exp', 'donation_st', 'loan', 'in_kind']):
83
 
                res[purchase.id] = purchase.shipped_rate
84
 
            else:
85
 
                tot = 0.0
86
 
                for invoice in purchase.invoice_ids:
87
 
                    if invoice.state not in ('draft','cancel'):
88
 
                        tot += invoice.amount_untaxed
89
 
                if purchase.amount_untaxed:
90
 
                    res[purchase.id] = min(100.0, tot * 100.0 / (purchase.amount_untaxed))
91
 
                else:
92
 
                    res[purchase.id] = 0.0
93
 
        return res
94
 
    # @@@end
95
 
    
96
 
    def _get_allocation_setup(self, cr, uid, ids, field_name, args, context=None):
97
 
        '''
98
 
        Returns the Unifield configuration value
99
 
        '''
100
 
        res = {}
101
 
        setup = self.pool.get('unifield.setup.configuration').get_config(cr, uid)
102
 
        
103
 
        for order in ids:
104
 
            res[order] = setup.allocation_setup
105
 
        
106
 
        return res
107
 
    
108
 
    def _get_no_line(self, cr, uid, ids, field_name, args, context=None):
109
 
        res = {}
110
 
        
111
 
        for order in self.browse(cr, uid, ids, context=context):
112
 
            res[order.id] = True
113
 
            for line in order.order_line:
114
 
                res[order.id] = False
115
 
                break
116
 
            # better: if order.order_line: res[order.id] = False
117
 
                
118
 
        return res
119
 
    
120
 
    _columns = {
121
 
        'order_type': fields.selection([('regular', 'Regular'), ('donation_exp', 'Donation before expiry'), 
122
 
                                        ('donation_st', 'Standard donation'), ('loan', 'Loan'), 
123
 
                                        ('in_kind', 'In Kind Donation'), ('purchase_list', 'Purchase List'),
124
 
                                        ('direct', 'Direct Purchase Order')], string='Order Type', required=True, states={'approved':[('readonly',True)],'done':[('readonly',True)]}),
125
 
        'loan_id': fields.many2one('sale.order', string='Linked loan', readonly=True),
126
 
        'priority': fields.selection(ORDER_PRIORITY, string='Priority', states={'approved':[('readonly',True)],'done':[('readonly',True)]}),
127
 
        'categ': fields.selection(ORDER_CATEGORY, string='Order category', required=True, states={'approved':[('readonly',True)],'done':[('readonly',True)]}),
128
 
        'details': fields.char(size=30, string='Details', states={'cancel':[('readonly',True)], 'confirmed_wait':[('readonly',True)], 'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}),
129
 
        'invoiced': fields.function(_invoiced, method=True, string='Invoiced', type='boolean', help="It indicates that an invoice has been generated"),
130
 
        'invoiced_rate': fields.function(_invoiced_rate, method=True, string='Invoiced', type='float'),
131
 
        'loan_duration': fields.integer(string='Loan duration', help='Loan duration in months', states={'confirmed':[('readonly',True)],'approved':[('readonly',True)],'done':[('readonly',True)]}),
132
 
        'from_yml_test': fields.boolean('Only used to pass addons unit test', readonly=True, help='Never set this field to true !'),
133
 
        'date_order':fields.date(string='Creation Date', readonly=True, required=True,
134
 
                            states={'draft':[('readonly',False)],}, select=True, help="Date on which this document has been created."),
135
 
        'name': fields.char('Order Reference', size=64, required=True, select=True, states={'cancel':[('readonly',True)], 'confirmed_wait':[('readonly',True)], 'confirmed':[('readonly',True)], 'approved':[('readonly',True)], 'done':[('readonly',True)]},
136
 
                            help="unique number of the purchase order,computed automatically when the purchase order is created"),
137
 
        'invoice_ids': fields.many2many('account.invoice', 'purchase_invoice_rel', 'purchase_id', 'invoice_id', 'Invoices', help="Invoices generated for a purchase order", readonly=True),
138
 
        'order_line': fields.one2many('purchase.order.line', 'order_id', 'Order Lines', readonly=True, states={'draft':[('readonly',False)], 'rfq_sent':[('readonly',False)], 'confirmed': [('readonly',False)]}),
139
 
        'partner_id':fields.many2one('res.partner', 'Supplier', required=True, states={'rfq_sent':[('readonly',True)], 'rfq_done':[('readonly',True)], 'rfq_updated':[('readonly',True)], 'confirmed':[('readonly',True)], 'confirmed_wait':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)],'cancel':[('readonly',True)]}, change_default=True, domain="[('id', '!=', company_id)]"),
140
 
        'partner_address_id':fields.many2one('res.partner.address', 'Address', required=True,
141
 
            states={'rfq_sent':[('readonly',True)], 'rfq_done':[('readonly',True)], 'rfq_updated':[('readonly',True)], 'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]},domain="[('partner_id', '=', partner_id)]"),
142
 
        'dest_partner_id': fields.many2one('res.partner', string='Destination partner', domain=[('partner_type', '=', 'internal')]),
143
 
        'invoice_address_id': fields.many2one('res.partner.address', string='Invoicing address', required=True, 
144
 
                                              help="The address where the invoice will be sent."),
145
 
        'invoice_method': fields.selection([('manual','Manual'),('order','From Order'),('picking','From Picking')], 'Invoicing Control', required=True, readonly=True,
146
 
            help="From Order: a draft invoice will be pre-generated based on the purchase order. The accountant " \
147
 
                "will just have to validate this invoice for control.\n" \
148
 
                "From Picking: a draft invoice will be pre-generated based on validated receptions.\n" \
149
 
                "Manual: allows you to generate suppliers invoices by chosing in the uninvoiced lines of all manual purchase orders."
150
 
        ),
151
 
        'merged_line_ids': fields.one2many('purchase.order.merged.line', 'order_id', string='Merged line'),
152
 
        'date_confirm': fields.date(string='Confirmation date'),
153
 
        'allocation_setup': fields.function(_get_allocation_setup, type='selection',
154
 
                                            selection=[('allocated', 'Allocated'),
155
 
                                                       ('unallocated', 'Unallocated'),
156
 
                                                       ('mixed', 'Mixed')], string='Allocated setup', method=True, store=False),
157
 
        'unallocation_ok': fields.boolean(string='Unallocated PO'),
158
 
        'partner_ref': fields.char('Supplier Reference', size=64),
159
 
        'product_id': fields.related('order_line', 'product_id', type='many2one', relation='product.product', string='Product'),
160
 
        'no_line': fields.function(_get_no_line, method=True, type='boolean', string='No line'),
161
 
    }
162
 
    
163
 
    _defaults = {
164
 
        'order_type': lambda *a: 'regular',
165
 
        'priority': lambda *a: 'normal',
166
 
        'categ': lambda *a: 'other',
167
 
        'loan_duration': 2,
168
 
        'from_yml_test': lambda *a: False,
169
 
        'invoice_address_id': lambda obj, cr, uid, ctx: obj.pool.get('res.partner').address_get(cr, uid, obj.pool.get('res.users').browse(cr, uid, uid, ctx).company_id.id, ['invoice'])['invoice'],
170
 
        'invoice_method': lambda *a: 'picking',
171
 
        'dest_address_id': lambda obj, cr, uid, ctx: obj.pool.get('res.partner').address_get(cr, uid, obj.pool.get('res.users').browse(cr, uid, uid, ctx).company_id.id, ['delivery'])['delivery'],
172
 
        'no_line': lambda *a: True,
173
 
    }
174
 
 
175
 
    def default_get(self, cr, uid, fields, context=None):
176
 
        '''
177
 
        Fill the unallocated_ok field according to Unifield setup
178
 
        '''
179
 
        res = super(purchase_order, self).default_get(cr, uid, fields, context=context)
180
 
        setup = self.pool.get('unifield.setup.configuration').get_config(cr, uid)
181
 
        
182
 
        res.update({'unallocation_ok': False, 'allocation_setup': setup.allocation_setup})
183
 
        if setup.allocation_setup == 'unallocated':
184
 
            res.update({'unallocation_ok': True})
185
 
 
186
 
        return res
187
 
 
188
 
    def _check_user_company(self, cr, uid, company_id, context=None):
189
 
        '''
190
 
        Remove the possibility to make a PO to user's company
191
 
        '''
192
 
        user_company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
193
 
        if company_id == user_company_id:
194
 
            raise osv.except_osv(_('Error'), _('You cannot made a purchase order to your own company !'))
195
 
 
196
 
        return True
197
 
 
198
 
    def write(self, cr, uid, ids, vals, context=None):
199
 
        '''
200
 
        Check if the partner is correct
201
 
        '''
202
 
        if 'partner_id' in vals:
203
 
            self._check_user_company(cr, uid, vals['partner_id'], context=context)
204
 
            
205
 
        if vals.get('order_type'):
206
 
            if vals.get('order_type') in ['donation_exp', 'donation_st', 'loan']:
207
 
                vals.update({'invoice_method': 'manual'})
208
 
            elif vals.get('order_type') in ['direct', 'purchase_list']:
209
 
                vals.update({'invoice_method': 'order'})
210
 
            else:
211
 
                vals.update({'invoice_method': 'picking'})
212
 
 
213
 
        return super(purchase_order, self).write(cr, uid, ids, vals, context=context)
214
 
    
215
 
    def onchange_internal_type(self, cr, uid, ids, order_type, partner_id, dest_partner_id=False, warehouse_id=False):
216
 
        '''
217
 
        Changes the invoice method of the purchase order according to
218
 
        the choosen order type
219
 
        Changes the partner to local market if the type is Purchase List
220
 
        '''
221
 
        partner_obj = self.pool.get('res.partner')
222
 
        v = {}
223
 
        d = {'partner_id': []}
224
 
        w = {}
225
 
        local_market = None
226
 
        
227
 
        # Search the local market partner id
228
 
        data_obj = self.pool.get('ir.model.data')
229
 
        data_id = data_obj.search(cr, uid, [('module', '=', 'order_types'), ('model', '=', 'res.partner'), ('name', '=', 'res_partner_local_market')] )
230
 
        if data_id:
231
 
            local_market = data_obj.read(cr, uid, data_id, ['res_id'])[0]['res_id']
232
 
            
233
 
        if order_type == 'loan':
234
 
            setup = self.pool.get('unifield.setup.configuration').get_config(cr, uid)
235
 
                
236
 
            if not setup.field_orders_ok:
237
 
                return {'value': {'order_type': 'regular'},
238
 
                        'warning': {'title': 'Error',
239
 
                                    'message': 'The Field orders feature is not activated on your system, so, you cannot create a Loan Purchase Order !'}}
240
 
        
241
 
        if order_type in ['donation_exp', 'donation_st', 'loan']:
242
 
            v['invoice_method'] = 'manual'
243
 
        elif order_type in ['direct', 'purchase_list']:
244
 
            v['invoice_method'] = 'order'
245
 
            d['partner_id'] = [('partner_type', 'in', ['esc', 'external'])]
246
 
        elif order_type in ['in_kind']:
247
 
            v['invoice_method'] = 'picking'
248
 
            d['partner_id'] = [('partner_type', 'in', ['esc', 'external'])]
249
 
        else:
250
 
            v['invoice_method'] = 'picking'
251
 
        
252
 
        if order_type == 'direct' and dest_partner_id:
253
 
            cp_address_id = self.pool.get('res.partner').address_get(cr, uid, dest_partner_id, ['delivery'])['delivery']
254
 
            v.update({'dest_address_id': cp_address_id})
255
 
            d.update({'dest_address_id': [('partner_id', '=', dest_partner_id)]})
256
 
        elif order_type == 'direct':
257
 
            v.update({'dest_address_id': False})
258
 
            d.update({'dest_address_id': [('partner_id', '=', self.pool.get('res.users').browse(cr, uid, uid).company_id.id)]})
259
 
        else:
260
 
            cp_address_id = self.pool.get('res.partner').address_get(cr, uid, self.pool.get('res.users').browse(cr, uid, uid).company_id.id, ['delivery'])['delivery']
261
 
            v.update({'dest_address_id': cp_address_id})
262
 
            d.update({'dest_address_id': [('partner_id', '=', self.pool.get('res.users').browse(cr, uid, uid).company_id.id)]})
263
 
 
264
 
        if partner_id and partner_id != local_market:
265
 
            partner = partner_obj.browse(cr, uid, partner_id)
266
 
            if partner.partner_type == 'internal' and order_type == 'regular':
267
 
                v['invoice_method'] = 'manual'
268
 
            elif partner.partner_type not in ('external', 'esc') and order_type == 'direct':
269
 
                v.update({'partner_address_id': False, 'partner_id': False, 'pricelist_id': False,})
270
 
                d['partner_id'] = [('partner_type', 'in', ['esc', 'external'])]
271
 
                w.update({'message': 'You cannot have a Direct Purchase Order with a partner which is not external or an ESC',
272
 
                          'title': 'An error has occured !'})
273
 
        elif partner_id and partner_id == local_market and order_type != 'purchase_list':
274
 
            v['partner_id'] = None
275
 
            v['partner_address_id'] = None
276
 
            v['pricelist_id'] = None
277
 
            
278
 
        if order_type == 'purchase_list':
279
 
            if local_market:
280
 
                partner = self.pool.get('res.partner').browse(cr, uid, local_market)
281
 
                v['partner_id'] = partner.id
282
 
                if partner.address:
283
 
                    v['partner_address_id'] = partner.address[0].id
284
 
                if partner.property_product_pricelist_purchase:
285
 
                    v['pricelist_id'] = partner.property_product_pricelist_purchase.id
286
 
        elif order_type == 'direct':
287
 
            v['cross_docking_ok'] = False
288
 
        
289
 
        return {'value': v, 'domain': d, 'warning': w}
290
 
    
291
 
    def onchange_partner_id(self, cr, uid, ids, part, *a, **b):
292
 
        '''
293
 
        Fills the Requested and Confirmed delivery dates
294
 
        '''
295
 
        if isinstance(ids, (int, long)):
296
 
            ids = [ids]
297
 
        
298
 
        res = super(purchase_order, self).onchange_partner_id(cr, uid, ids, part, *a, **b)
299
 
        
300
 
        if part:
301
 
            partner_obj = self.pool.get('res.partner')
302
 
            partner = partner_obj.browse(cr, uid, part)
303
 
            if partner.partner_type == 'internal':
304
 
                res['value']['invoice_method'] = 'manual'
305
 
        
306
 
        return res
307
 
    
308
 
    # Be careful during integration, the onchange_warehouse_id method is also defined on UF-965
309
 
    def onchange_warehouse_id(self, cr, uid, ids,  warehouse_id, order_type, dest_address_id):
310
 
        '''
311
 
        Change the destination address to the destination address of the company if False
312
 
        '''
313
 
        res = super(purchase_order, self).onchange_warehouse_id(cr, uid, ids, warehouse_id)
314
 
        
315
 
        if not res.get('value', {}).get('dest_address_id') and order_type!='direct':
316
 
            cp_address_id = self.pool.get('res.partner').address_get(cr, uid, self.pool.get('res.users').browse(cr, uid, uid).company_id.id, ['delivery'])['delivery']
317
 
            if 'value' in res:
318
 
                res['value'].update({'dest_address_id': cp_address_id})
319
 
            else:
320
 
                res.update({'value': {'dest_address_id': cp_address_id}})
321
 
        if order_type == 'direct' or dest_address_id:
322
 
            if 'dest_address_id' in res.get('value', {}):
323
 
                res['value'].pop('dest_address_id')
324
 
        
325
 
        return res
326
 
    
327
 
    def on_change_dest_partner_id(self, cr, uid, ids, dest_partner_id, context=None):
328
 
        '''
329
 
        Fill automatically the destination address according to the destination partner
330
 
        '''
331
 
        v = {}
332
 
        d = {}
333
 
        
334
 
        if not context:
335
 
            context = {}
336
 
        
337
 
        company_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.id
338
 
        
339
 
        if not dest_partner_id:
340
 
            v.update({'dest_address_id': False})
341
 
            d.update({'dest_address_id': [('partner_id', '=', company_id)]})
342
 
        else:
343
 
            d.update({'dest_address_id': [('partner_id', '=', dest_partner_id)]})
344
 
        
345
 
            delivery_addr = self.pool.get('res.partner').address_get(cr, uid, dest_partner_id, ['delivery'])
346
 
            v.update({'dest_address_id': delivery_addr['delivery']})
347
 
        
348
 
        return {'value': v, 'domain': d}
349
 
    
350
 
    def change_currency(self, cr, uid, ids, context=None):
351
 
        '''
352
 
        Launches the wizard to change the currency and update lines
353
 
        '''
354
 
        if not context:
355
 
            context = {}
356
 
            
357
 
        if isinstance(ids, (int, long)):
358
 
            ids = [ids]
359
 
            
360
 
        for order in self.browse(cr, uid, ids, context=context):
361
 
            data = {'order_id': order.id,
362
 
                    'partner_id': order.partner_id.id,
363
 
                    'partner_type': order.partner_id.partner_type,
364
 
                    'new_pricelist_id': order.pricelist_id.id,
365
 
                    'currency_rate': 1.00,
366
 
                    'old_pricelist_id': order.pricelist_id.id}
367
 
            wiz = self.pool.get('purchase.order.change.currency').create(cr, uid, data, context=context)
368
 
            return {'type': 'ir.actions.act_window',
369
 
                    'res_model': 'purchase.order.change.currency',
370
 
                    'view_type': 'form',
371
 
                    'view_mode': 'form',
372
 
                    'res_id': wiz,
373
 
                    'target': 'new'}
374
 
            
375
 
        return True
376
 
    
377
 
    def order_line_change(self, cr, uid, ids, order_line):
378
 
        res = {'no_line': True}
379
 
        
380
 
        if order_line:
381
 
            res = {'no_line': False}
382
 
        
383
 
        return {'value': res}
384
 
 
385
 
    def _hook_confirm_order_message(self, cr, uid, context=None, *args, **kwargs):
386
 
        '''
387
 
        Change the logged message
388
 
        '''
389
 
        if context is None:
390
 
            context = {}
391
 
        if 'po' in kwargs:
392
 
            po = kwargs['po']
393
 
            return _("Purchase order '%s' is validated.") % (po.name,)
394
 
        else:
395
 
            return super(purchase_order, self)._hook_confirm_order_message(cr, uid, context, args, kwargs)
396
 
 
397
 
    def check_analytic_distribution(self, cr, uid, ids, context=None):
398
 
        """
399
 
        Check analytic distribution validity for given PO.
400
 
        Also check that partner have a donation account (is PO is in_kind)
401
 
        """
402
 
        if isinstance(ids, (int, long)):
403
 
            ids = [ids]
404
 
        # Analytic distribution verification
405
 
        for po in self.browse(cr, uid, ids, context=context):
406
 
            if po.order_type and po.order_type == 'in_kind':
407
 
                if not po.partner_id.donation_payable_account:
408
 
                    raise osv.except_osv(_('Error'), _('No donation account on this partner: %s') % (po.partner_id.name or '',))
409
 
            for pol in po.order_line:
410
 
                # Forget check if we come from YAML tests
411
 
                if po.from_yml_test:
412
 
                    continue
413
 
                distrib_id = (pol.analytic_distribution_id and pol.analytic_distribution_id.id) or (po.analytic_distribution_id and po.analytic_distribution_id.id) or False
414
 
                # Raise an error if no analytic distribution found
415
 
                if not distrib_id:
416
 
                    raise osv.except_osv(_('Warning'), _('Analytic allocation is mandatory for this line: %s!') % (pol.name or '',))
417
 
                if pol.analytic_distribution_state != 'valid':
418
 
                    raise osv.except_osv(_('Warning'), _("Analytic distribution is not valid for '%s'!") % (pol.name or '',))
419
 
        return True
420
 
 
421
 
    def wkf_confirm_order(self, cr, uid, ids, context=None):
422
 
        '''
423
 
        Update the confirmation date of the PO at confirmation.
424
 
        Check analytic distribution.
425
 
        '''
426
 
        for order in self.browse(cr, uid, ids, context=context):
427
 
            pricelist_ids = self.pool.get('product.pricelist').search(cr, uid, [('in_search', '=', order.partner_id.partner_type)], context=context)
428
 
            if order.pricelist_id.id not in pricelist_ids:
429
 
                raise osv.except_osv(_('Error'), _('The currency used on the order is not compatible with the supplier. Please change the currency to choose a compatible currency.'))
430
 
        res = super(purchase_order, self).wkf_confirm_order(cr, uid, ids, context=context)
431
 
        self.write(cr, uid, ids, {'date_confirm': time.strftime('%Y-%m-%d')}, context=context)
432
 
        # CODE MOVED TO self.check_analytic_distribution()
433
 
        self.check_analytic_distribution(cr, uid, ids, context=context)
434
 
        return res
435
 
 
436
 
    def wkf_picking_done(self, cr, uid, ids, context=None):
437
 
        '''
438
 
        Change the shipped boolean and the state of the PO
439
 
        '''
440
 
        for order in self.browse(cr, uid, ids, context=context):
441
 
            if order.order_type == 'direct':
442
 
                self.write(cr, uid, order.id, {'state': 'approved'}, context=context)
443
 
            else:
444
 
                self.write(cr, uid, order.id, {'shipped':1,'state':'approved'}, context=context)
445
 
 
446
 
        return True
447
 
    
448
 
    def purchase_approve(self, cr, uid, ids, context=None):
449
 
        '''
450
 
        If the PO is a DPO, check the state of the stock moves
451
 
        '''
452
 
        if isinstance(ids, (int, long)):
453
 
            ids = [ids]
454
 
            
455
 
        wf_service = netsvc.LocalService("workflow")
456
 
        move_obj = self.pool.get('stock.move')
457
 
            
458
 
        for order in self.browse(cr, uid, ids, context=context):
459
 
            if not order.delivery_confirmed_date:
460
 
                raise osv.except_osv(_('Error'), _('Delivery Confirmed Date is a mandatory field.'))
461
 
            todo = []
462
 
            todo2 = []
463
 
            todo3 = []
464
 
            
465
 
            if order.order_type == 'direct':
466
 
                for line in order.order_line:
467
 
                    if line.procurement_id: todo.append(line.procurement_id.id)
468
 
                    
469
 
            if todo:
470
 
                todo2 = self.pool.get('sale.order.line').search(cr, uid, [('procurement_id', 'in', todo)], context=context)
471
 
            
472
 
            if todo2:
473
 
                sm_ids = move_obj.search(cr, uid, [('sale_line_id', 'in', todo2)], context=context)
474
 
                error_moves = []
475
 
                for move in move_obj.browse(cr, uid, sm_ids, context=context):
476
 
                    backmove_ids = self.pool.get('stock.move').search(cr, uid, [('backmove_id', '=', move.id)])
477
 
                    if move.state == 'done':
478
 
                        error_moves.append(move)
479
 
                    if backmove_ids:
480
 
                        for bmove in move_obj.browse(cr, uid, backmove_ids):
481
 
                            error_moves.append(bmove)
482
 
                        
483
 
                if error_moves:
484
 
                    errors = '''You are trying to confirm a Direct Purchase Order.
485
 
At Direct Purchase Order confirmation, the system tries to change the state of concerning OUT moves but for this DPO, the system has detected 
486
 
stock moves which are already processed : '''
487
 
                    for m in error_moves:
488
 
                        errors = '%s \n %s' % (errors, '''
489
 
        * Picking : %s - Product : [%s] %s - Product Qty. : %s %s \n''' % (m.picking_id.name, m.product_id.default_code, m.product_id.name, m.product_qty, m.product_uom.name))
490
 
                        
491
 
                    errors = '%s \n %s' % (errors, 'This warning is only for informational purpose. The stock moves already processed will not be modified by this confirmation.')
492
 
                        
493
 
                    wiz_id = self.pool.get('purchase.order.confirm.wizard').create(cr, uid, {'order_id': order.id,
494
 
                                                                                             'errors': errors})
495
 
                    return {'type': 'ir.actions.act_window',
496
 
                            'res_model': 'purchase.order.confirm.wizard',
497
 
                            'res_id': wiz_id,
498
 
                            'view_type': 'form',
499
 
                            'view_mode': 'form',
500
 
                            'target': 'new'}
501
 
            
502
 
            # If no errors, validate the DPO
503
 
            wf_service.trg_validate(uid, 'purchase.order', order.id, 'purchase_confirmed_wait', cr)
504
 
            
505
 
        return True
506
 
    
507
 
    def get_so_ids_from_po_ids(self, cr, uid, ids, context=None):
508
 
        '''
509
 
        receive the list of purchase order ids
510
 
        
511
 
        return the list of sale order ids corresponding (through procurement process)
512
 
        '''
513
 
        # Some verifications
514
 
        if not context:
515
 
            context = {}
516
 
        if isinstance(ids, (int, long)):
517
 
            ids = [ids]
518
 
        
519
 
        # objects
520
 
        sol_obj = self.pool.get('sale.order.line')
521
 
        # sale order list
522
 
        so_ids = []
523
 
        
524
 
        # get the sale order lines
525
 
        sol_ids = self.get_sol_ids_from_po_ids(cr, uid, ids, context=context)
526
 
        if sol_ids:
527
 
            # list of dictionaries for each sale order line
528
 
            datas = sol_obj.read(cr, uid, sol_ids, ['order_id'], context=context)
529
 
            # we retrieve the list of sale order ids
530
 
            for data in datas:
531
 
                if data['order_id'] and data['order_id'][0] not in so_ids:
532
 
                    so_ids.append(data['order_id'][0])
533
 
        return so_ids
534
 
    
535
 
    def get_sol_ids_from_po_ids(self, cr, uid, ids, context=None):
536
 
        '''
537
 
        receive the list of purchase order ids
538
 
        
539
 
        return the list of sale order line ids corresponding (through procurement process)
540
 
        '''
541
 
        # Some verifications
542
 
        if not context:
543
 
            context = {}
544
 
        if isinstance(ids, (int, long)):
545
 
            ids = [ids]
546
 
            
547
 
        # objects
548
 
        sol_obj = self.pool.get('sale.order.line')
549
 
        # procurement ids list
550
 
        proc_ids = []
551
 
        # sale order lines list
552
 
        sol_ids = []
553
 
        
554
 
        for po in self.browse(cr, uid, ids, context=context):
555
 
            for line in po.order_line:
556
 
                if line.procurement_id:
557
 
                    proc_ids.append(line.procurement_id.id)
558
 
        # get the corresponding sale order line list
559
 
        if proc_ids:
560
 
            sol_ids = sol_obj.search(cr, uid, [('procurement_id', 'in', proc_ids)], context=context)
561
 
        return sol_ids
562
 
    
563
 
    def common_code_from_wkf_approve_order(self, cr, uid, ids, context=None):
564
 
        '''
565
 
        delivery confirmed date at po level is mandatory
566
 
        update corresponding date at line level if needed.
567
 
        Check analytic distribution
568
 
        '''
569
 
        # objects
570
 
        ana_obj = self.pool.get('analytic.distribution')
571
 
        
572
 
        # Check analytic distribution
573
 
        self.check_analytic_distribution(cr, uid, ids, context=context)
574
 
        for po in self.browse(cr, uid, ids, context=context):
575
 
            # CODE MOVED TO self.check_analytic_distribution()
576
 
            # msf_order_date checks
577
 
            if not po.delivery_confirmed_date:
578
 
                raise osv.except_osv(_('Error'), _('Delivery Confirmed Date is a mandatory field.'))
579
 
            # for all lines, if the confirmed date is not filled, we copy the header value
580
 
            for line in po.order_line:
581
 
                if not line.confirmed_delivery_date:
582
 
                    line.write({'confirmed_delivery_date': po.delivery_confirmed_date,}, context=context)
583
 
        # MOVE code for COMMITMENT into wkf_approve_order
584
 
        return True
585
 
    
586
 
    def wkf_confirm_wait_order(self, cr, uid, ids, context=None):
587
 
        """
588
 
        Checks:
589
 
        1/ if all purchase line could take an analytic distribution
590
 
        2/ if a commitment voucher should be created after PO approbation
591
 
        
592
 
        _> originally in purchase.py from analytic_distribution_supply
593
 
        
594
 
        Checks if the Delivery Confirmed Date has been filled
595
 
        
596
 
        _> originally in order_dates.py from msf_order_date
597
 
        """
598
 
        # Some verifications
599
 
        if not context:
600
 
            context = {}
601
 
        if isinstance(ids, (int, long)):
602
 
            ids = [ids]
603
 
        
604
 
        # objects
605
 
        sol_obj = self.pool.get('sale.order.line')
606
 
        
607
 
        # code from wkf_approve_order
608
 
        self.common_code_from_wkf_approve_order(cr, uid, ids, context=context)
609
 
        # set the state of purchase order to confirmed_wait
610
 
        self.write(cr, uid, ids, {'state': 'confirmed_wait'}, context=context)
611
 
        # sale order lines with modified state
612
 
        sol_ids = self.get_sol_ids_from_po_ids(cr, uid, ids, context=context)
613
 
        if sol_ids:
614
 
            sol_obj.write(cr, uid, sol_ids, {'state': 'confirmed'}, context=context)
615
 
        
616
 
        # !!BEWARE!! we must update the So lines before any writing to So objects
617
 
        for po in self.browse(cr, uid, ids, context=context): 
618
 
            # hook for corresponding Fo update
619
 
            self._hook_confirm_order_update_corresponding_so(cr, uid, ids, context=context, po=po)
620
 
        
621
 
        return True
622
 
    
623
 
    def compute_confirmed_delivery_date(self, cr, uid, ids, confirmed, prep_lt, ship_lt, est_transport_lead_time, db_date_format, context=None):
624
 
        '''
625
 
        compute the confirmed date
626
 
        
627
 
        confirmed must be string
628
 
        return string corresponding to database format
629
 
        '''
630
 
        assert type(confirmed) == str
631
 
        confirmed = datetime.strptime(confirmed, db_date_format)
632
 
        confirmed = confirmed + relativedelta(days=prep_lt or 0)
633
 
        confirmed = confirmed + relativedelta(days=ship_lt or 0)
634
 
        confirmed = confirmed + relativedelta(days=est_transport_lead_time or 0)
635
 
        confirmed = confirmed.strftime(db_date_format)
636
 
        
637
 
        return confirmed
638
 
    
639
 
    def _hook_confirm_order_update_corresponding_so(self, cr, uid, ids, context=None, *args, **kwargs):
640
 
        '''
641
 
        Add a hook to update correspondingn so
642
 
        '''
643
 
        # Some verifications
644
 
        if context is None:
645
 
            context = {}
646
 
        if isinstance(ids, (int, long)):
647
 
            ids = [ids]
648
 
            
649
 
        # objects
650
 
        po = kwargs['po']
651
 
        pol_obj = self.pool.get('purchase.order.line')
652
 
        so_obj = self.pool.get('sale.order')
653
 
        sol_obj = self.pool.get('sale.order.line')
654
 
        date_tools = self.pool.get('date.tools')
655
 
        fields_tools = self.pool.get('fields.tools')
656
 
        db_date_format = date_tools.get_db_date_format(cr, uid, context=context)
657
 
        
658
 
        # update corresponding fo if exist
659
 
        so_ids = self.get_so_ids_from_po_ids(cr, uid, ids, context=context)
660
 
        if so_ids:
661
 
            # date values
662
 
            ship_lt = fields_tools.get_field_from_company(cr, uid, object=self._name, field='shipment_lead_time', context=context)
663
 
            prep_lt = fields_tools.get_field_from_company(cr, uid, object=self._name, field='preparation_lead_time', context=context)
664
 
            
665
 
            for line in po.order_line:
666
 
                # get the corresponding so line
667
 
                sol_ids = pol_obj.get_sol_ids_from_pol_ids(cr, uid, [line.id], context=context)
668
 
                if sol_ids:
669
 
                    # get so_id
670
 
                    data = sol_obj.read(cr, uid, sol_ids, ['order_id'], context=context)
671
 
                    order_id = data[0]['order_id'][0]
672
 
                    # get est_transport_lead_time of corresponding so
673
 
                    data = so_obj.read(cr, uid, order_id, ['est_transport_lead_time'], context=context)
674
 
                    est_transport_lead_time = data['est_transport_lead_time']
675
 
                    
676
 
                    line_confirmed = False
677
 
                    # compute confirmed date for line
678
 
                    if line.confirmed_delivery_date:
679
 
                        line_confirmed = self.compute_confirmed_delivery_date(cr, uid, ids, line.confirmed_delivery_date,
680
 
                                                                              prep_lt, ship_lt, est_transport_lead_time,
681
 
                                                                              db_date_format, context=context)
682
 
                    # we update the corresponding sale order line
683
 
                    sol = sol_obj.browse(cr, uid, sol_ids[0], context=context)
684
 
                    # do not update Internal Requests
685
 
                    if sol.order_id.procurement_request:
686
 
                        continue
687
 
                    # {sol: pol}
688
 
                    # compute the price_unit value - we need to specify the date
689
 
                    date_context = {'date': po.date_order}
690
 
                    # convert from currency of pol to currency of sol
691
 
                    price_unit_converted = self.pool.get('res.currency').compute(cr, uid, line.currency_id.id,
692
 
                                                                                 sol.currency_id.id, line.price_unit or 0.0,
693
 
                                                                                 round=True, context=date_context)
694
 
                    fields_dic = {'product_id': line.product_id and line.product_id.id or False,
695
 
                                  'name': line.name,
696
 
                                  'default_name': line.default_name,
697
 
                                  'default_code': line.default_code,
698
 
                                  'product_uom_qty': line.product_qty,
699
 
                                  'product_uom': line.product_uom and line.product_uom.id or False,
700
 
                                  'product_uos_qty': line.product_qty,
701
 
                                  'product_uos': line.product_uom and line.product_uom.id or False,
702
 
                                  'price_unit': price_unit_converted,
703
 
                                  'nomenclature_description': line.nomenclature_description,
704
 
                                  'nomenclature_code': line.nomenclature_code,
705
 
                                  'comment': line.comment,
706
 
                                  'nomen_manda_0': line.nomen_manda_0 and line.nomen_manda_0.id or False,
707
 
                                  'nomen_manda_1': line.nomen_manda_1 and line.nomen_manda_1.id or False,
708
 
                                  'nomen_manda_2': line.nomen_manda_2 and line.nomen_manda_2.id or False,
709
 
                                  'nomen_manda_3': line.nomen_manda_3 and line.nomen_manda_3.id or False,
710
 
                                  'nomen_sub_0': line.nomen_sub_0 and line.nomen_sub_0.id or False,
711
 
                                  'nomen_sub_1': line.nomen_sub_1 and line.nomen_sub_1.id or False,
712
 
                                  'nomen_sub_2': line.nomen_sub_2 and line.nomen_sub_2.id or False,
713
 
                                  'nomen_sub_3': line.nomen_sub_3 and line.nomen_sub_3.id or False,
714
 
                                  'nomen_sub_4': line.nomen_sub_4 and line.nomen_sub_4.id or False,
715
 
                                  'nomen_sub_5': line.nomen_sub_5 and line.nomen_sub_5.id or False,
716
 
                                  'confirmed_delivery_date': line_confirmed,
717
 
                                  }
718
 
                    # write the line
719
 
                    sol_obj.write(cr, uid, sol_ids, fields_dic, context=context)
720
 
            
721
 
            # compute so dates -- only if we get a confirmed value, because rts is mandatory on So side
722
 
            # update after lines update, as so write triggers So workflow, and we dont want the Out document
723
 
            # to be created with old So datas
724
 
            if po.delivery_confirmed_date:
725
 
                for so in so_obj.browse(cr, uid, so_ids, context=context):
726
 
                    # Fo rts = Po confirmed date + prep_lt
727
 
                    delivery_confirmed_date = datetime.strptime(po.delivery_confirmed_date, db_date_format)
728
 
                    so_rts = delivery_confirmed_date + relativedelta(days=prep_lt or 0)
729
 
                    so_rts = so_rts.strftime(db_date_format)
730
 
                
731
 
                    # Fo confirmed date = confirmed date + prep_lt + ship_lt + transport_lt
732
 
                    so_confirmed = self.compute_confirmed_delivery_date(cr, uid, ids, po.delivery_confirmed_date,
733
 
                                                                        prep_lt, ship_lt, so.est_transport_lead_time,
734
 
                                                                        db_date_format, context=context)
735
 
                    # write data to so
736
 
                    so_obj.write(cr, uid, [so.id], {'delivery_confirmed_date': so_confirmed,
737
 
                                                   'ready_to_ship_date': so_rts}, context=context)
738
 
            
739
 
        return True
740
 
    
741
 
    def all_po_confirmed(self, cr, uid, ids, context=None):
742
 
        '''
743
 
        condition for the po to leave the act_confirmed_wait state
744
 
        
745
 
        if the po is from scratch (no procurement), or from replenishment mechanism (procurement but no sale order line)
746
 
        the method will return True and therefore the po workflow is not blocked
747
 
        
748
 
        only 'make_to_order' sale order lines are checked, we dont care on state of 'make_to_stock' sale order line
749
 
        _> anyway, thanks to Fo split, make_to_stock and make_to_order so lines are separated in different sale orders
750
 
        '''
751
 
        # Some verifications
752
 
        if not context:
753
 
            context = {}
754
 
        if isinstance(ids, (int, long)):
755
 
            ids = [ids]
756
 
        
757
 
        # objects
758
 
        sol_obj = self.pool.get('sale.order.line')
759
 
        so_obj = self.pool.get('sale.order')
760
 
        
761
 
        # corresponding sale order
762
 
        so_ids = self.get_so_ids_from_po_ids(cr, uid, ids, context=context)
763
 
        # from so, list corresponding po
764
 
        all_po_ids = so_obj.get_po_ids_from_so_ids(cr, uid, so_ids, context=context)
765
 
        # from listed po, list corresponding so
766
 
        all_so_ids = self.get_so_ids_from_po_ids(cr, uid, all_po_ids, context=context)
767
 
        # if we have sol_ids, we are treating a po which is make_to_order from sale order
768
 
        if all_so_ids:
769
 
            # we retrieve the list of ids of all sale order line if type 'make_to_order' with state != 'confirmed'
770
 
            # with product_id (if no product id, no procurement, no po, so should not be taken into account)
771
 
            # in case of grouped po, multiple Fo depend on this po, all Po of these Fo need to be completed
772
 
            # and all Fo will be confirmed together. Because IN of grouped Po need corresponding OUT document of all Fo
773
 
            # internal request are automatically 'confirmed'
774
 
            # not take done into account, because IR could be done as they are confirmed before the Po are all done
775
 
            # see video in uf-1050 for detail
776
 
            all_sol_not_confirmed_ids = sol_obj.search(cr, uid, [('order_id', 'in', all_so_ids),
777
 
                                                                 ('type', '=', 'make_to_order'),
778
 
                                                                 ('product_id', '!=', False),
779
 
                                                                 ('state', 'not in', ['confirmed', 'done'])], context=context)
780
 
            # if any lines exist, we return False
781
 
            if all_sol_not_confirmed_ids:
782
 
                return False
783
 
            
784
 
        return True
785
 
    
786
 
    def wkf_confirm_trigger(self, cr, uid, ids, context=None):
787
 
        '''
788
 
        trigger corresponding so then po
789
 
        '''
790
 
        # Some verifications
791
 
        if not context:
792
 
            context = {}
793
 
        if isinstance(ids, (int, long)):
794
 
            ids = [ids]
795
 
            
796
 
        # objects
797
 
        sol_obj = self.pool.get('sale.order.line')
798
 
        so_obj = self.pool.get('sale.order')
799
 
        proc_obj = self.pool.get('procurement.order')
800
 
        wf_service = netsvc.LocalService("workflow")
801
 
        
802
 
        # corresponding sale order
803
 
        so_ids = self.get_so_ids_from_po_ids(cr, uid, ids, context=context)
804
 
        # from so, list corresponding po first level
805
 
        all_po_ids = so_obj.get_po_ids_from_so_ids(cr, uid, so_ids, context=context)
806
 
        # from listed po, list corresponding so
807
 
        all_so_ids = self.get_so_ids_from_po_ids(cr, uid, all_po_ids, context=context)
808
 
        # from all so, list all corresponding po second level
809
 
        all_po_for_all_so_ids = so_obj.get_po_ids_from_so_ids(cr, uid, all_so_ids, context=context)
810
 
        
811
 
        # we trigger all the corresponding sale order -> test_lines is called on these so
812
 
        for so_id in all_so_ids:
813
 
            wf_service.trg_write(uid, 'sale.order', so_id, cr)
814
 
            
815
 
        # we trigger pos of all sale orders -> all_po_confirm is called on these po
816
 
        for po_id in all_po_for_all_so_ids:
817
 
            wf_service.trg_write(uid, 'purchase.order', po_id, cr)
818
 
        
819
 
        return True
820
 
    
821
 
    def wkf_approve_order(self, cr, uid, ids, context=None):
822
 
        '''
823
 
        Checks if the invoice should be create from the purchase order
824
 
        or not
825
 
        If the PO is a DPO, set all related OUT stock move to 'done' state
826
 
        '''
827
 
        line_obj = self.pool.get('purchase.order.line')
828
 
        move_obj = self.pool.get('stock.move')
829
 
        wf_service = netsvc.LocalService("workflow")
830
 
        
831
 
        if isinstance(ids, (int, long)):
832
 
            ids = [ids]
833
 
        
834
 
        # duplicated code with wkf_confirm_wait_order because of backward compatibility issue with yml tests for dates,
835
 
        # which doesnt execute wkf_confirm_wait_order (null value in column "date_expected" violates not-null constraint for stock.move otherwise)
836
 
        # msf_order_date checks
837
 
        self.common_code_from_wkf_approve_order(cr, uid, ids, context=context)
838
 
 
839
 
        for order in self.browse(cr, uid, ids):
840
 
            # Create commitments for each PO only if po is "from picking"
841
 
            if order.invoice_method in ['picking', 'order'] and not order.from_yml_test and order.order_type != 'in_kind' and order.partner_id.partner_type != 'intermission':
842
 
                self.action_create_commitment(cr, uid, [order.id], order.partner_id and order.partner_id.partner_type, context=context)
843
 
            # Don't accept the confirmation of regular PO with 0.00 unit price lines
844
 
            if order.order_type == 'regular':
845
 
                line_error = []
846
 
                for line in order.order_line:
847
 
                    if line.price_unit == 0.00:
848
 
                        line_error.append(line.line_number)
849
 
 
850
 
                if len(line_error) > 0:
851
 
                    errors = ' / '.join(str(x) for x in line_error)
852
 
                    raise osv.except_osv(_('Error !'), _('You cannot have a purchase order line with a 0.00 Unit Price. Lines in exception : %s') % errors)
853
 
 
854
 
            todo = []
855
 
            todo2 = []
856
 
            todo3 = []
857
 
            if order.partner_id.partner_type == 'internal' and order.order_type == 'regular' or \
858
 
                         order.order_type in ['donation_exp', 'donation_st', 'loan']:
859
 
                self.write(cr, uid, [order.id], {'invoice_method': 'manual'})
860
 
                line_obj.write(cr, uid, [x.id for x in order.order_line], {'invoiced': 1})
861
 
 
862
 
            message = _("Purchase order '%s' is confirmed.") % (order.name,)
863
 
            self.log(cr, uid, order.id, message)
864
 
            
865
 
            if order.order_type == 'direct':
866
 
                self.write(cr, uid, [order.id], {'invoice_method': 'order'}, context=context)
867
 
                for line in order.order_line:
868
 
                    if line.procurement_id: todo.append(line.procurement_id.id)
869
 
                    
870
 
            if todo:
871
 
                todo2 = self.pool.get('sale.order.line').search(cr, uid, [('procurement_id', 'in', todo)], context=context)
872
 
            
873
 
            if todo2:
874
 
                sm_ids = move_obj.search(cr, uid, [('sale_line_id', 'in', todo2)], context=context)
875
 
                self.pool.get('stock.move').action_confirm(cr, uid, sm_ids, context=context)
876
 
                stock_location_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', 'stock_location_stock')[1]
877
 
                for move in move_obj.browse(cr, uid, sm_ids, context=context):
878
 
                    # Search if this move has been processed
879
 
                    backmove_ids = self.pool.get('stock.move').search(cr, uid, [('backmove_id', '=', move.id)])
880
 
                    if move.state != 'done' and not backmove_ids and not move.backmove_id:
881
 
                        move_obj.write(cr, uid, sm_ids, {'dpo_id': order.id, 'state': 'done',
882
 
                                                         'location_id': stock_location_id,
883
 
                                                         'location_dest_id': stock_location_id, 
884
 
                                                         'date': time.strftime('%Y-%m-%d %H:%M:%S')}, context=context)
885
 
                        wf_service.trg_trigger(uid, 'stock.move', move.id, cr)
886
 
                        if move.picking_id: 
887
 
                            all_move_closed = True
888
 
                            # Check if the picking should be updated
889
 
                            if move.picking_id.subtype == 'picking':
890
 
                                for m in move.picking_id.move_lines:
891
 
                                    if m.id not in sm_ids and m.state != 'done':
892
 
                                        all_move_closed = False
893
 
                            # If all stock moves of the picking is done, trigger the workflow
894
 
                            if all_move_closed:
895
 
                                todo3.append(move.picking_id.id)
896
 
                
897
 
            if todo3:
898
 
                for pick_id in todo3:
899
 
                    wf_service.trg_validate(uid, 'stock.picking', pick_id, 'button_confirm', cr)
900
 
                    wf_service.trg_write(uid, 'stock.picking', pick_id, cr)
901
 
            
902
 
        return super(purchase_order, self).wkf_approve_order(cr, uid, ids, context=context)
903
 
    
904
 
    def action_sale_order_create(self, cr, uid, ids, context=None):
905
 
        '''
906
 
        Create a sale order as counterpart for the loan.
907
 
        '''
908
 
        if isinstance(ids, (int, long)):
909
 
            ids = [ids]
910
 
        if context is None:
911
 
            context = {}
912
 
            
913
 
        sale_obj = self.pool.get('sale.order')
914
 
        sale_line_obj = self.pool.get('sale.order.line')
915
 
        sale_shop = self.pool.get('sale.shop')
916
 
        partner_obj = self.pool.get('res.partner')
917
 
            
918
 
        for order in self.browse(cr, uid, ids):
919
 
            loan_duration = Parser.DateFromString(order.minimum_planned_date) + RelativeDateTime(months=+order.loan_duration)
920
 
            # from yml test is updated according to order value
921
 
            values = {'shop_id': sale_shop.search(cr, uid, [])[0],
922
 
                      'partner_id': order.partner_id.id,
923
 
                      'partner_order_id': partner_obj.address_get(cr, uid, [order.partner_id.id], ['contact'])['contact'],
924
 
                      'partner_invoice_id': partner_obj.address_get(cr, uid, [order.partner_id.id], ['invoice'])['invoice'],
925
 
                      'partner_shipping_id': partner_obj.address_get(cr, uid, [order.partner_id.id], ['delivery'])['delivery'],
926
 
                      'pricelist_id': order.partner_id.property_product_pricelist.id,
927
 
                      'loan_id': order.id,
928
 
                      'loan_duration': order.loan_duration,
929
 
                      'origin': order.name,
930
 
                      'order_type': 'loan',
931
 
                      'delivery_requested_date': loan_duration.strftime('%Y-%m-%d'),
932
 
                      'categ': order.categ,
933
 
                      'priority': order.priority,
934
 
                      'from_yml_test': order.from_yml_test,
935
 
                      }
936
 
            order_id = sale_obj.create(cr, uid, values, context=context)
937
 
            for line in order.order_line:
938
 
                sale_line_obj.create(cr, uid, {'product_id': line.product_id and line.product_id.id or False,
939
 
                                               'product_uom': line.product_uom.id,
940
 
                                               'order_id': order_id,
941
 
                                               'price_unit': line.price_unit,
942
 
                                               'product_uom_qty': line.product_qty,
943
 
                                               'date_planned': loan_duration.strftime('%Y-%m-%d'),
944
 
                                               'delay': 60.0,
945
 
                                               'name': line.name,
946
 
                                               'type': line.product_id.procure_method})
947
 
            self.write(cr, uid, [order.id], {'loan_id': order_id})
948
 
            
949
 
            sale = sale_obj.browse(cr, uid, order_id)
950
 
            
951
 
            message = _("Loan counterpart '%s' has been created.") % (sale.name,)
952
 
            
953
 
            sale_obj.log(cr, uid, order_id, message)
954
 
        
955
 
        return order_id
956
 
    
957
 
    def has_stockable_product(self,cr, uid, ids, *args):
958
 
        '''
959
 
        Override the has_stockable_product to return False
960
 
        when the order_type of the order is 'direct'
961
 
        '''
962
 
        # TODO: See with Synchro team which object the system will should create
963
 
        # to have an Incoming Movement in the destination instance
964
 
        for order in self.browse(cr, uid, ids):
965
 
            if order.order_type != 'direct':
966
 
                return super(purchase_order, self).has_stockable_product(cr, uid, ids, args)
967
 
        
968
 
        return False
969
 
    
970
 
    def action_invoice_create(self, cr, uid, ids, *args):
971
 
        '''
972
 
        Override this method to check the purchase_list box on invoice
973
 
        when the invoice comes from a purchase list.
974
 
        Change journal to an inkind journal if we comes from a In-kind Donation PO
975
 
        '''
976
 
        invoice_id = super(purchase_order, self).action_invoice_create(cr, uid, ids, args)
977
 
        invoice_obj = self.pool.get('account.invoice')
978
 
        inkind_journal_ids = self.pool.get('account.journal').search(cr, uid, [("type", "=", "inkind")])
979
 
 
980
 
        for order in self.browse(cr, uid, ids):
981
 
            if order.order_type == 'purchase_list':
982
 
                invoice_obj.write(cr, uid, [invoice_id], {'purchase_list': 1})
983
 
            elif order.order_type == 'in_kind':
984
 
                if not inkind_journal_ids:
985
 
                    raise osv.except_osv(_('Error'), _('No In-kind Donation journal found!'))
986
 
                invoice_obj.write(cr, uid, [invoice_id], {'journal_id': inkind_journal_ids[0], 'is_inkind_donation': True})
987
 
 
988
 
        return invoice_id
989
 
    
990
 
    def _hook_action_picking_create_modify_out_source_loc_check(self, cr, uid, ids, context=None, *args, **kwargs):
991
 
        '''
992
 
        Please copy this to your module's method also.
993
 
        This hook belongs to the action_picking_create method from purchase>purchase.py>purchase_order class
994
 
        
995
 
        - allow to choose whether or not the source location of the corresponding outgoing stock move should
996
 
        match the destination location of incoming stock move
997
 
        '''
998
 
        order_line = kwargs['order_line']
999
 
        # by default, we change the destination stock move if the destination stock move exists
1000
 
        return order_line.move_dest_id
1001
 
    
1002
 
    def _hook_action_picking_create_stock_picking(self, cr, uid, ids, context=None, *args, **kwargs):
1003
 
        '''
1004
 
        modify data for stock move creation
1005
 
        '''
1006
 
        move_values = kwargs['move_values']
1007
 
        return move_values
1008
 
    
1009
 
    # @@@override@purchase.purchase.order.action_picking_create
1010
 
    def action_picking_create(self,cr, uid, ids, context=None, *args):
1011
 
        picking_id = False
1012
 
        for order in self.browse(cr, uid, ids):
1013
 
            loc_id = order.partner_id.property_stock_supplier.id
1014
 
            istate = 'none'
1015
 
            reason_type_id = False
1016
 
            if order.invoice_method=='picking':
1017
 
                istate = '2binvoiced'
1018
 
                
1019
 
            pick_name = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.in')
1020
 
            picking_values = {
1021
 
                'name': pick_name,
1022
 
                'origin': order.name+((order.origin and (':'+order.origin)) or ''),
1023
 
                'type': 'in',
1024
 
                'partner_id2': order.partner_id.id,
1025
 
                'address_id': order.partner_address_id.id or False,
1026
 
                'invoice_state': istate,
1027
 
                'purchase_id': order.id,
1028
 
                'company_id': order.company_id.id,
1029
 
                'move_lines' : [],
1030
 
            }
1031
 
            
1032
 
            if order.order_type in ('regular', 'purchase_list', 'direct'):
1033
 
                reason_type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_external_supply')[1]
1034
 
            if order.order_type == 'loan':
1035
 
                reason_type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_loan')[1]
1036
 
            if order.order_type == 'donation_st':
1037
 
                reason_type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_donation')[1]
1038
 
            if order.order_type == 'donation_exp':
1039
 
                reason_type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_donation_expiry')[1]
1040
 
            if order.order_type == 'in_kind':
1041
 
                reason_type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_in_kind_donation')[1]
1042
 
                
1043
 
            if reason_type_id:
1044
 
                picking_values.update({'reason_type_id': reason_type_id})
1045
 
            
1046
 
            picking_id = self.pool.get('stock.picking').create(cr, uid, picking_values, context=context)
1047
 
            todo_moves = []
1048
 
            for order_line in order.order_line:
1049
 
                if not order_line.product_id:
1050
 
                    continue
1051
 
                if order_line.product_id.product_tmpl_id.type in ('product', 'consu', 'service_recep',):
1052
 
                    dest = order.location_id.id
1053
 
                    # service with reception are directed to Service Location
1054
 
                    if order_line.product_id.product_tmpl_id.type == 'service_recep':
1055
 
                        service_loc = self.pool.get('stock.location').search(cr, uid, [('service_location', '=', True)], context=context)
1056
 
                        if service_loc:
1057
 
                            dest = service_loc[0]
1058
 
                            
1059
 
                    move_values = {
1060
 
                        'name': order.name + ': ' +(order_line.name or ''),
1061
 
                        'product_id': order_line.product_id.id,
1062
 
                        'product_qty': order_line.product_qty,
1063
 
                        'product_uos_qty': order_line.product_qty,
1064
 
                        'product_uom': order_line.product_uom.id,
1065
 
                        'product_uos': order_line.product_uom.id,
1066
 
                        'date': order_line.date_planned,
1067
 
                        'date_expected': order_line.date_planned,
1068
 
                        'location_id': loc_id,
1069
 
                        'location_dest_id': dest,
1070
 
                        'picking_id': picking_id,
1071
 
                        'move_dest_id': order_line.move_dest_id.id,
1072
 
                        'state': 'draft',
1073
 
                        'purchase_line_id': order_line.id,
1074
 
                        'company_id': order.company_id.id,
1075
 
                        'price_currency_id': order.pricelist_id.currency_id.id,
1076
 
                        'price_unit': order_line.price_unit
1077
 
                    }
1078
 
                    # hook for stock move values modification
1079
 
                    move_values = self._hook_action_picking_create_stock_picking(cr, uid, ids, context=context, move_values=move_values, order_line=order_line,)
1080
 
                    
1081
 
                    if reason_type_id:
1082
 
                        move_values.update({'reason_type_id': reason_type_id})
1083
 
                    
1084
 
                    move = self.pool.get('stock.move').create(cr, uid, move_values, context=context)
1085
 
                    if self._hook_action_picking_create_modify_out_source_loc_check(cr, uid, ids, context=context, order_line=order_line, move_id=move):
1086
 
                        self.pool.get('stock.move').write(cr, uid, [order_line.move_dest_id.id], {'location_id':order.location_id.id})
1087
 
                    todo_moves.append(move)
1088
 
            self.pool.get('stock.move').action_confirm(cr, uid, todo_moves)
1089
 
            self.pool.get('stock.move').force_assign(cr, uid, todo_moves)
1090
 
            wf_service = netsvc.LocalService("workflow")
1091
 
            wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
1092
 
        return picking_id
1093
 
        # @@@end
1094
 
 
1095
 
    def create(self, cr, uid, vals, context=None):
1096
 
        """
1097
 
        Filled in 'from_yml_test' to True if we come from tests
1098
 
        """
1099
 
        if not context:
1100
 
            context = {}
1101
 
        if context.get('update_mode') in ['init', 'update'] and 'from_yml_test' not in vals:
1102
 
            logging.getLogger('init').info('PO: set from yml test to True')
1103
 
            vals['from_yml_test'] = True
1104
 
            
1105
 
        if vals.get('order_type'):
1106
 
            if vals.get('order_type') in ['donation_exp', 'donation_st', 'loan']:
1107
 
                vals.update({'invoice_method': 'manual'})
1108
 
            elif vals.get('order_type') in ['direct', 'purchase_list']:
1109
 
                vals.update({'invoice_method': 'order'})
1110
 
            else:
1111
 
                vals.update({'invoice_method': 'picking'})
1112
 
            
1113
 
        if 'partner_id' in vals:
1114
 
            self._check_user_company(cr, uid, vals['partner_id'], context=context)
1115
 
    
1116
 
        return super(purchase_order, self).create(cr, uid, vals, context=context)
1117
 
 
1118
 
    def wkf_action_cancel_po(self, cr, uid, ids, context=None):
1119
 
        """
1120
 
        Cancel activity in workflow.
1121
 
        """
1122
 
        # Some verifications
1123
 
        if not context:
1124
 
            context = {}
1125
 
        if isinstance(ids, (int, long)):
1126
 
            ids = [ids]
1127
 
        return self.write(cr, uid, ids, {'state':'cancel'}, context=context)
1128
 
 
1129
 
    def action_done(self, cr, uid, ids, context=None):
1130
 
        """
1131
 
        Done activity in workflow.
1132
 
        """
1133
 
        # Some verifications
1134
 
        if not context:
1135
 
            context = {}
1136
 
        if isinstance(ids, (int, long)):
1137
 
            ids = [ids]
1138
 
        for order in self.browse(cr, uid, ids, context=context):
1139
 
            vals = {'state': 'done'}
1140
 
            if order.order_type == 'direct':
1141
 
                vals.update({'shipped': 1})
1142
 
            self.write(cr, uid, order.id, vals, context=context)
1143
 
        return True
1144
 
 
1145
 
    def set_manually_done(self, cr, uid, ids, all_doc=True, context=None):
1146
 
        '''
1147
 
        Set the PO to done state
1148
 
        '''
1149
 
        wf_service = netsvc.LocalService("workflow")
1150
 
 
1151
 
        if context is None:
1152
 
            context = {}
1153
 
        if isinstance(ids, (int, long)):
1154
 
            ids = [ids]
1155
 
 
1156
 
        order_lines = []
1157
 
        for order in self.browse(cr, uid, ids, context=context):
1158
 
            for line in order.order_line:
1159
 
                order_lines.append(line.id)
1160
 
 
1161
 
            # Done picking
1162
 
            for pick in order.picking_ids:
1163
 
                if pick.state not in ('cancel', 'done'):
1164
 
                    wf_service.trg_validate(uid, 'stock.picking', pick.id, 'manually_done', cr)
1165
 
 
1166
 
            # Done loan counterpart
1167
 
            if order.loan_id and order.loan_id.state not in ('cancel', 'done') and not context.get('loan_id', False) == order.id:
1168
 
                loan_context = context.copy()
1169
 
                loan_context.update({'loan_id': order.id})
1170
 
                self.pool.get('sale.order').set_manually_done(cr, uid, order.loan_id.id, all_doc=all_doc, context=loan_context)
1171
 
 
1172
 
            # Done invoices
1173
 
            invoice_error_ids = []
1174
 
            for invoice in order.invoice_ids:
1175
 
                if invoice.state == 'draft':
1176
 
                    wf_service.trg_validate(uid, 'account.invoice', invoice.id, 'invoice_cancel', cr)
1177
 
                elif invoice.state not in ('cancel', 'done'):
1178
 
                    invoice_error_ids.append(invoice.id)
1179
 
 
1180
 
            if invoice_error_ids:
1181
 
                invoices_ref = ' / '.join(x.number for x in self.pool.get('account.invoice').browse(cr, uid, invoice_error_ids, context=context))
1182
 
                raise osv.except_osv(_('Error'), _('The state of the following invoices cannot be updated automatically. Please cancel them manually or discuss with the accounting team to solve the problem.' \
1183
 
                                'Invoices references : %s') % invoices_ref)
1184
 
 
1185
 
        # Done stock moves
1186
 
        move_ids = self.pool.get('stock.move').search(cr, uid, [('purchase_line_id', 'in', order_lines), ('state', 'not in', ('cancel', 'done'))], context=context)
1187
 
        self.pool.get('stock.move').set_manually_done(cr, uid, move_ids, all_doc=all_doc, context=context)
1188
 
 
1189
 
        # Cancel all procurement ordes which have generated one of these PO
1190
 
        proc_ids = self.pool.get('procurement.order').search(cr, uid, [('purchase_id', 'in', ids)], context=context)
1191
 
        for proc in self.pool.get('procurement.order').browse(cr, uid, proc_ids, context=context):
1192
 
            self.pool.get('stock.move').write(cr, uid, [proc.move_id.id], {'state': 'cancel'}, context=context)
1193
 
            wf_service.trg_validate(uid, 'procurement.order', proc.id, 'subflow.cancel', cr)
1194
 
 
1195
 
        if all_doc:
1196
 
            # Detach the PO from his workflow and set the state to done
1197
 
            for order_id in self.browse(cr, uid, ids, context=context):
1198
 
                if order_id.rfq_ok and order_id.state == 'draft':
1199
 
                    wf_service.trg_validate(uid, 'purchase.order', order_id.id, 'purchase_cancel', cr)
1200
 
                elif order_id.tender_id:
1201
 
                    raise osv.except_osv(_('Error'), _('You cannot \'Close\' a Request for Quotation attached to a tender. Please make the tender %s to \'Closed\' before !') % order_id.tender_id.name)
1202
 
                else:
1203
 
                    wf_service.trg_delete(uid, 'purchase.order', order_id.id, cr)
1204
 
                    # Search the method called when the workflow enter in last activity
1205
 
                    wkf_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'purchase', 'act_done')[1]
1206
 
                    activity = self.pool.get('workflow.activity').browse(cr, uid, wkf_id, context=context)
1207
 
                    res = _eval_expr(cr, [uid, 'purchase.order', order_id.id], False, activity.action)
1208
 
 
1209
 
        return True
1210
 
    
1211
 
purchase_order()
1212
 
 
1213
 
 
1214
 
class purchase_order_merged_line(osv.osv):
1215
 
    '''
1216
 
    A purchase order merged line is a special PO line.
1217
 
    These lines give the total quantity of all normal PO lines
1218
 
    which have the same product and the same quantity.
1219
 
    When a new normal PO line is created, the system will check
1220
 
    if this new line can be attached to other PO lines. If yes,
1221
 
    the unit price of all normal PO lines with the same product and
1222
 
    the same UoM will be computed from supplier catalogue and updated on lines.
1223
 
    '''
1224
 
    _name = 'purchase.order.merged.line'
1225
 
    _inherit = 'purchase.order.line'
1226
 
    _description = 'Purchase Order Merged Lines'
1227
 
    _table = 'purchase_order_merged_line'
1228
 
 
1229
 
    def _get_name(self, cr, uid, ids, field_name, args, context=None):
1230
 
        res = {}
1231
 
        for line in self.browse(cr, uid, ids, context=context):
1232
 
            if line.order_line_ids:
1233
 
                res[line.id] = line.product_id and line.product_id.name or line.order_line_ids[0].comment
1234
 
        return res
1235
 
 
1236
 
    _columns = {
1237
 
        'order_line_ids': fields.one2many('purchase.order.line', 'merged_id', string='Purchase Lines'),
1238
 
        'date_planned': fields.date(string='Delivery Requested Date', required=False, select=True,
1239
 
                                            help='Header level dates has to be populated by default with the possibility of manual updates'),
1240
 
        'name': fields.function(_get_name, method=True, type='char', string='Name', store=False),
1241
 
    }
1242
 
 
1243
 
    def create(self, cr, uid, vals, context=None):
1244
 
        '''
1245
 
        Set the line number to 0
1246
 
        '''
1247
 
        if self._name == 'purchase.order.merged.line':
1248
 
            vals.update({'line_number': 0})
1249
 
        return super(purchase_order_merged_line, self).create(cr, uid, vals, context=context)
1250
 
 
1251
 
    def write(self, cr, uid, ids, vals, context=None):
1252
 
        '''
1253
 
        Update unit price of PO lines attached to the merged line
1254
 
        '''
1255
 
        if context is None:
1256
 
            context = {}
1257
 
        new_context = context.copy()
1258
 
        new_context.update({'update_merge': True})
1259
 
        # If the unit price is changing, update the price unit of all normal PO lines
1260
 
        # associated to this merged PO line
1261
 
        if 'price_unit' in vals:
1262
 
            for merged_line in self.browse(cr, uid, ids, context=context):
1263
 
                for po_line in merged_line.order_line_ids:
1264
 
                    self.pool.get('purchase.order.line').write(cr, uid, [po_line.id], {'price_unit': vals['price_unit'],
1265
 
                                                                                       'old_price_unit': vals['price_unit']}, context=new_context)
1266
 
 
1267
 
        return super(purchase_order_merged_line, self).write(cr, uid, ids, vals, context=context)
1268
 
 
1269
 
    def _update(self, cr, uid, id, po_line_id, product_qty, price=0.00, context=None, no_update=False):
1270
 
        '''
1271
 
        Update the quantity and the unit price according to the new qty
1272
 
        '''
1273
 
        line = self.browse(cr, uid, id, context=context)
1274
 
        change_price_ok = True
1275
 
        if not po_line_id:
1276
 
            change_price_ok = context.get('change_price_ok', True)
1277
 
        else:
1278
 
            po_line = self.pool.get('purchase.order.line').browse(cr, uid, po_line_id, context=context)
1279
 
            change_price_ok = po_line.change_price_ok
1280
 
            if 'change_price_ok' in context:
1281
 
                change_price_ok = context.get('change_price_ok')
1282
 
 
1283
 
        # If no PO line attached to this merged line, remove the merged line
1284
 
        if not line.order_line_ids:
1285
 
            self.unlink(cr, uid, [id], context=context)
1286
 
            return False, False
1287
 
 
1288
 
        new_price = False
1289
 
        new_qty = line.product_qty + product_qty
1290
 
        
1291
 
        if (po_line_id and not change_price_ok and not po_line.order_id.rfq_ok) or (not po_line_id and not change_price_ok):    
1292
 
            # Get the catalogue unit price according to the total qty
1293
 
            new_price = self.pool.get('product.pricelist').price_get(cr, uid, 
1294
 
                                                              [line.order_id.pricelist_id.id],
1295
 
                                                              line.product_id.id,
1296
 
                                                              new_qty,
1297
 
                                                              line.order_id.partner_id.id,
1298
 
                                                              {'uom': line.product_uom.id,
1299
 
                                                               'date': line.order_id.date_order})[line.order_id.pricelist_id.id]                                      
1300
 
        
1301
 
        # Update the quantity of the merged line                  
1302
 
        values = {'product_qty': new_qty}
1303
 
        # If a catalogue unit price exist and the unit price is not manually changed
1304
 
        if new_price:
1305
 
            values.update({'price_unit': new_price})
1306
 
        else:
1307
 
            # Keep the unit price given by the user
1308
 
            values.update({'price_unit': price})
1309
 
            new_price = price
1310
 
 
1311
 
        # Update the unit price and the quantity of the merged line
1312
 
        if not no_update:
1313
 
            self.write(cr, uid, [id], values, context=context)
1314
 
 
1315
 
        return id, new_price or False
1316
 
 
1317
 
 
1318
 
purchase_order_merged_line()
1319
 
 
1320
 
 
1321
 
class purchase_order_line(osv.osv):
1322
 
    _name = 'purchase.order.line'
1323
 
    _inherit = 'purchase.order.line'
1324
 
 
1325
 
    def link_merged_line(self, cr, uid, vals, product_id, order_id, product_qty, uom_id, price_unit=0.00, context=None):
1326
 
        '''
1327
 
        Check if a merged line exist. If not, create a new one and attach them to the Po line
1328
 
        '''
1329
 
        line_obj = self.pool.get('purchase.order.merged.line')
1330
 
        if product_id:
1331
 
            domain = [('product_id', '=', product_id), ('order_id', '=', order_id), ('product_uom', '=', uom_id)]
1332
 
            # Search if a merged line already exist for the same product, the same order and the same UoM
1333
 
            merged_ids = line_obj.search(cr, uid, domain, context=context)
1334
 
        else:
1335
 
            merged_ids = []
1336
 
        
1337
 
        new_vals = vals.copy()
1338
 
 
1339
 
        if not merged_ids:
1340
 
            new_vals['order_id'] = order_id
1341
 
            if not new_vals.get('price_unit', False):
1342
 
                new_vals['price_unit'] = price_unit
1343
 
            # Create a new merged line which is the same than the normal PO line except for price unit
1344
 
            vals['merged_id'] = line_obj.create(cr, uid, new_vals, context=context)
1345
 
        else:
1346
 
            c = context.copy()
1347
 
            order = self.pool.get('purchase.order').browse(cr, uid, order_id, context=context)
1348
 
            stages = self._get_stages_price(cr, uid, product_id, uom_id, order, context=context)
1349
 
            if order.state != 'confirmed' and stages and not order.rfq_ok:
1350
 
                c.update({'change_price_ok': False})
1351
 
            # Update the associated merged line
1352
 
            res_merged = line_obj._update(cr, uid, merged_ids[0], False, product_qty, price_unit, context=c, no_update=False)
1353
 
            vals['merged_id'] = res_merged[0]
1354
 
            # Update unit price
1355
 
            vals['price_unit'] = res_merged[1]
1356
 
 
1357
 
        return vals
1358
 
 
1359
 
    def _update_merged_line(self, cr, uid, line_id, vals=None, context=None):
1360
 
        '''
1361
 
        Update the merged line
1362
 
        '''
1363
 
        merged_line_obj = self.pool.get('purchase.order.merged.line')
1364
 
        
1365
 
        if not vals:
1366
 
            vals = {}
1367
 
        tmp_vals = vals.copy()
1368
 
 
1369
 
        # If it's an update of a line
1370
 
        if vals and line_id:
1371
 
            line = self.browse(cr, uid, line_id, context=context)
1372
 
            
1373
 
            # Set default values if not pass in values
1374
 
            if not 'product_uom' in vals: 
1375
 
                tmp_vals.update({'product_uom': line.product_uom.id})
1376
 
            if not 'product_qty' in vals: 
1377
 
                tmp_vals.update({'product_qty': line.product_qty})
1378
 
            
1379
 
            # If the user changed the product or the UoM or both on the PO line
1380
 
            if ('product_id' in vals and line.product_id.id != vals['product_id']) or ('product_uom' in vals and line.product_uom.id != vals['product_uom']):
1381
 
                # Need removing the merged_id link before update the merged line because the merged line
1382
 
                # will be removed if it hasn't attached PO line
1383
 
                merged_id = line.merged_id.id
1384
 
                change_price_ok = line.change_price_ok
1385
 
                c = context.copy()
1386
 
                c.update({'change_price_ok': change_price_ok})
1387
 
                self.write(cr, uid, line_id, {'merged_id': False}, context=context)
1388
 
                res_merged = merged_line_obj._update(cr, uid, merged_id, line.id, -line.product_qty, line.price_unit, context=c)
1389
 
                
1390
 
                # Create or update an existing merged line with the new product
1391
 
                vals = self.link_merged_line(cr, uid, tmp_vals, tmp_vals.get('product_id', line.product_id.id), line.order_id.id, tmp_vals.get('product_qty', line.product_qty), tmp_vals.get('product_uom', line.product_uom.id), tmp_vals.get('price_unit', line.price_unit), context=context)
1392
 
            
1393
 
            # If the quantity is changed
1394
 
            elif 'product_qty' in vals and line.product_qty != vals['product_qty']:
1395
 
                res_merged = merged_line_obj._update(cr, uid, line.merged_id.id, line.id, vals['product_qty']-line.product_qty, line.price_unit, context=context)
1396
 
                # Update the unit price
1397
 
                if res_merged and res_merged[1]:
1398
 
                    vals.update({'price_unit': res_merged[1]})
1399
 
                    
1400
 
            # If the price unit is changed and the product and the UoM is not modified
1401
 
            if 'price_unit' in tmp_vals and (line.price_unit != tmp_vals['price_unit'] or vals['price_unit'] != tmp_vals['price_unit']) and not (line.product_id.id != vals.get('product_id', False) or line.product_uom.id != vals.get('product_uom', False)):
1402
 
                # Give 0.00 to quantity because the _update should recompute the price unit with the same quantity
1403
 
                res_merged = merged_line_obj._update(cr, uid, line.merged_id.id, line.id, 0.00, tmp_vals['price_unit'], context=context)
1404
 
                # Update the unit price
1405
 
                if res_merged and res_merged[1]:
1406
 
                    vals.update({'price_unit': res_merged[1]})
1407
 
        # If it's a new line
1408
 
        elif not line_id:
1409
 
            c = context.copy()
1410
 
            vals = self.link_merged_line(cr, uid, vals, vals.get('product_id'), vals['order_id'], vals['product_qty'], vals['product_uom'], vals['price_unit'], context=c)
1411
 
        # If the line is removed
1412
 
        elif not vals:
1413
 
            line = self.browse(cr, uid, line_id, context=context)
1414
 
            # Remove the qty from the merged line
1415
 
            if line.merged_id:
1416
 
                merged_id = line.merged_id.id
1417
 
                change_price_ok = line.change_price_ok
1418
 
                c = context.copy()
1419
 
                c.update({'change_price_ok': change_price_ok})
1420
 
                # Need removing the merged_id link before update the merged line because the merged line
1421
 
                # will be removed if it hasn't attached PO line
1422
 
                self.write(cr, uid, [line.id], {'merged_id': False}, context=context)
1423
 
                res_merged = merged_line_obj._update(cr, uid, merged_id, line.id, -line.product_qty, line.price_unit, context=c)
1424
 
 
1425
 
        return vals
1426
 
 
1427
 
    def create(self, cr, uid, vals, context=None):
1428
 
        '''
1429
 
        Create or update a merged line
1430
 
        '''
1431
 
        if not context:
1432
 
            context = {}
1433
 
            
1434
 
        order_id = self.pool.get('purchase.order').browse(cr, uid, vals['order_id'], context=context)
1435
 
        if order_id.from_yml_test:
1436
 
            vals.update({'change_price_manually': True})
1437
 
            if not vals.get('product_qty', False):
1438
 
                vals['product_qty'] = 1.00
1439
 
                
1440
 
        # If we are on a RfQ, use the last entered unit price and update other lines with this price
1441
 
        if order_id.rfq_ok:
1442
 
            vals.update({'change_price_manually': True})
1443
 
        else:
1444
 
            if vals.get('product_qty', 0.00) == 0.00:
1445
 
                raise osv.except_osv(_('Error'), _('You cannot save a line with no quantity !'))
1446
 
        
1447
 
        order_id = vals.get('order_id')
1448
 
        product_id = vals.get('product_id')
1449
 
        product_uom = vals.get('product_uom')
1450
 
        order = self.pool.get('purchase.order').browse(cr, uid, order_id, context=context)
1451
 
        other_lines = self.search(cr, uid, [('order_id', '=', order_id), ('product_id', '=', product_id), ('product_uom', '=', product_uom)], context=context)
1452
 
        stages = self._get_stages_price(cr, uid, product_id, product_uom, order, context=context)
1453
 
 
1454
 
        if (other_lines and stages and order.state != 'confirmed'):
1455
 
            context.update({'change_price_ok': False})
1456
 
 
1457
 
        vals = self._update_merged_line(cr, uid, False, vals, context=context)
1458
 
 
1459
 
        vals.update({'old_price_unit': vals.get('price_unit', False)})
1460
 
 
1461
 
        # add the database Id to the sync_pol_db_id
1462
 
        po_line_id = super(purchase_order_line, self).create(cr, uid, vals, context=context)
1463
 
        if 'sync_pol_db_id' not in vals:
1464
 
            super(purchase_order_line, self).write(cr, uid, po_line_id, {'sync_pol_db_id': po_line_id}, context=context)
1465
 
 
1466
 
        return po_line_id
1467
 
 
1468
 
    def copy(self, cr, uid, line_id, defaults={}, context=None):
1469
 
        '''
1470
 
        Remove link to merged line
1471
 
        '''
1472
 
        defaults.update({'merged_id': False})
1473
 
 
1474
 
        return super(purchase_order_line, self).copy(cr, uid, line_id, defaults, context=context)
1475
 
 
1476
 
    def write(self, cr, uid, ids, vals, context=None):
1477
 
        '''
1478
 
        Update merged line
1479
 
        '''
1480
 
        if not context:
1481
 
            context = {}
1482
 
 
1483
 
        if isinstance(ids, (int, long)):
1484
 
            ids = [ids]
1485
 
        
1486
 
#        if ids and not isinstance(ids[0], (int, long)):
1487
 
#            ids = [x.id for x in ids]
1488
 
            
1489
 
        for line in self.browse(cr, uid, ids, context=context):
1490
 
            if vals.get('product_qty', line.product_qty) == 0.00 and not line.order_id.rfq_ok:
1491
 
                raise osv.except_osv(_('Error'), _('You cannot save a line with no quantity !'))
1492
 
        
1493
 
        if not context.get('update_merge'):
1494
 
            for line in ids:
1495
 
                vals = self._update_merged_line(cr, uid, line, vals, context=context)
1496
 
                
1497
 
        if 'price_unit' in vals:
1498
 
            vals.update({'old_price_unit': vals.get('price_unit')})
1499
 
 
1500
 
        return super(purchase_order_line, self).write(cr, uid, ids, vals, context=context)
1501
 
 
1502
 
    def unlink(self, cr, uid, ids, context=None):
1503
 
        '''
1504
 
        Update the merged line
1505
 
        '''
1506
 
        if context is None:
1507
 
            context = {}
1508
 
        if isinstance(ids, (int, long)):
1509
 
            ids = [ids]
1510
 
            
1511
 
        # if the line is linked to a sale order line through procurement process,
1512
 
        # the deletion is impossible
1513
 
        if self.get_sol_ids_from_pol_ids(cr, uid, ids, context=context):
1514
 
            raise osv.except_osv(_('Error'), _('You cannot delete a line which is linked to a Fo line.'))
1515
 
 
1516
 
        for line_id in ids:
1517
 
            self._update_merged_line(cr, uid, line_id, False, context=context)
1518
 
 
1519
 
        return super(purchase_order_line, self).unlink(cr, uid, ids, context=context)
1520
 
 
1521
 
    def _get_fake_state(self, cr, uid, ids, field_name, args, context=None):
1522
 
        if isinstance(ids, (int, long)):
1523
 
            ids = [ids]
1524
 
        ret = {}
1525
 
        for pol in self.read(cr, uid, ids, ['state']):
1526
 
            ret[pol['id']] = pol['state']
1527
 
        return ret
1528
 
    
1529
 
    def _get_fake_id(self, cr, uid, ids, field_name, args, context=None):
1530
 
        if isinstance(ids, (int, long)):
1531
 
            ids = [ids]
1532
 
        ret = {}
1533
 
        for pol in self.read(cr, uid, ids, ['id']):
1534
 
            ret[pol['id']] = pol['id']
1535
 
        return ret
1536
 
    
1537
 
    def _get_stages_price(self, cr, uid, product_id, uom_id, order, context=None):
1538
 
        '''
1539
 
        Returns True if the product/supplier couple has more than 1 line
1540
 
        '''
1541
 
        suppinfo_ids = self.pool.get('product.supplierinfo').search(cr, uid, [('name', '=', order.partner_id.id), 
1542
 
                                                                              ('product_id', '=', product_id)], context=context)
1543
 
        if suppinfo_ids:
1544
 
            pricelist_ids = self.pool.get('pricelist.partnerinfo').search(cr, uid, [('currency_id', '=', order.pricelist_id.currency_id.id),
1545
 
                                                                                    ('suppinfo_id', 'in', suppinfo_ids),
1546
 
                                                                                    ('uom_id', '=', uom_id),
1547
 
                                                                                    '|', ('valid_till', '=', False),
1548
 
                                                                                    ('valid_till', '>=', order.date_order)], context=context)
1549
 
            if len(pricelist_ids) > 1:
1550
 
                return True
1551
 
        
1552
 
        return False
1553
 
        
1554
 
    def _get_price_change_ok(self, cr, uid, ids, field_name, args, context=None):
1555
 
        '''
1556
 
        Returns True if the price can be changed by the user
1557
 
        '''
1558
 
        res = {}
1559
 
        
1560
 
        for line in self.browse(cr, uid, ids, context=context):
1561
 
            res[line.id] = True
1562
 
            stages = self._get_stages_price(cr, uid, line.product_id.id, line.product_uom.id, line.order_id, context=context)
1563
 
            if line.merged_id and len(line.merged_id.order_line_ids) > 1 and line.order_id.state != 'confirmed' and stages and not line.order_id.rfq_ok:
1564
 
                res[line.id] = False
1565
 
                        
1566
 
        return res
1567
 
    
1568
 
    def _vals_get(self, cr, uid, ids, fields, arg, context=None):
1569
 
        '''
1570
 
        multi fields function method
1571
 
        '''
1572
 
        # Some verifications
1573
 
        if context is None:
1574
 
            context = {}
1575
 
        if isinstance(ids, (int, long)):
1576
 
            ids = [ids]
1577
 
            
1578
 
        # objects
1579
 
        sol_obj = self.pool.get('sale.order.line')
1580
 
        
1581
 
        result = {}
1582
 
        for obj in self.browse(cr, uid, ids, context=context):
1583
 
            # default values
1584
 
            result[obj.id] = {'order_state_purchase_order_line': False}
1585
 
            # order_state_purchase_order_line
1586
 
            if obj.order_id:
1587
 
                result[obj.id].update({'order_state_purchase_order_line': obj.order_id.state})
1588
 
            
1589
 
        return result
1590
 
 
1591
 
    _columns = {
1592
 
        'parent_line_id': fields.many2one('purchase.order.line', string='Parent line'),
1593
 
        'merged_id': fields.many2one('purchase.order.merged.line', string='Merged line'),
1594
 
        'origin': fields.char(size=64, string='Origin'),
1595
 
        'change_price_ok': fields.function(_get_price_change_ok, type='boolean', method=True, string='Price changing'),
1596
 
        'change_price_manually': fields.boolean(string='Update price manually'),
1597
 
        # openerp bug: eval invisible in p.o use the po line state and not the po state !
1598
 
        'fake_state': fields.function(_get_fake_state, type='char', method=True, string='State', help='for internal use only'),
1599
 
        # openerp bug: id is not given to onchanqge call if we are into one2many view
1600
 
        'fake_id':fields.function(_get_fake_id, type='integer', method=True, string='Id', help='for internal use only'),
1601
 
        'old_price_unit': fields.float(digits=(16,2), string='Old price'),
1602
 
        'order_state_purchase_order_line': fields.function(_vals_get, method=True, type='selection', selection=PURCHASE_ORDER_STATE_SELECTION, string='State of Po', multi='get_vals_purchase_override', store=False, readonly=True),
1603
 
 
1604
 
        'sync_pol_db_id': fields.integer(string='PO line DB Id', required=False, readonly=True),
1605
 
        'sync_sol_db_id': fields.integer(string='SO line DB Id', required=False, readonly=True),
1606
 
    }
1607
 
 
1608
 
    _defaults = {
1609
 
        'change_price_manually': lambda *a: False,
1610
 
        'product_qty': lambda *a: 0.00,
1611
 
        'price_unit': lambda *a: 0.00,
1612
 
        'change_price_ok': lambda *a: True,
1613
 
    }
1614
 
    
1615
 
    def product_uom_change(self, cr, uid, ids, pricelist, product, qty, uom,
1616
 
            partner_id, date_order=False, fiscal_position=False, date_planned=False,
1617
 
            name=False, price_unit=False, notes=False):
1618
 
        qty = 0.00
1619
 
        res = super(purchase_order_line, self).product_uom_change(cr, uid, ids, pricelist, product, qty, uom,
1620
 
                                                                  partner_id, date_order, fiscal_position, date_planned,
1621
 
                                                                  name, price_unit, notes)
1622
 
        if not product:
1623
 
            return res
1624
 
        res['value'].update({'product_qty': 0.00})
1625
 
        res.update({'warning': {}})
1626
 
        
1627
 
        return res
1628
 
    
1629
 
    def product_id_on_change(self, cr, uid, ids, pricelist, product, qty, uom,
1630
 
            partner_id, date_order=False, fiscal_position=False, date_planned=False,
1631
 
            name=False, price_unit=False, notes=False, state=False, old_price_unit=False,
1632
 
            nomen_manda_0=False, comment=False, context=None):
1633
 
        all_qty = qty
1634
 
        suppinfo_obj = self.pool.get('product.supplierinfo')
1635
 
        partner_price = self.pool.get('pricelist.partnerinfo')
1636
 
        
1637
 
        if product and not uom:
1638
 
            uom = self.pool.get('product.product').browse(cr, uid, product).uom_po_id.id
1639
 
        
1640
 
        if context and context.get('purchase_id') and state == 'draft' and product:    
1641
 
            domain = [('product_id', '=', product), 
1642
 
                      ('product_uom', '=', uom), 
1643
 
                      ('order_id', '=', context.get('purchase_id'))]
1644
 
            other_lines = self.search(cr, uid, domain)
1645
 
            for l in self.browse(cr, uid, other_lines):
1646
 
                all_qty += l.product_qty 
1647
 
        
1648
 
        res = super(purchase_order_line, self).product_id_change(cr, uid, ids, pricelist, product, all_qty, uom,
1649
 
                                                                 partner_id, date_order, fiscal_position, 
1650
 
                                                                 date_planned, name, price_unit, notes)
1651
 
        
1652
 
        # Remove the warning message if the product has no staged pricelist
1653
 
#        if res.get('warning'):
1654
 
#            supplier_info = self.pool.get('product.supplierinfo').search(cr, uid, [('product_id', '=', product)])
1655
 
#            product_pricelist = self.pool.get('pricelist.partnerinfo').search(cr, uid, [('suppinfo_id', 'in', supplier_info)])
1656
 
#            if not product_pricelist:
1657
 
#                res['warning'] = {}
1658
 
        if res.get('warning', {}).get('title', '') == 'No valid pricelist line found !' or qty == 0.00:
1659
 
            res.update({'warning': {}})
1660
 
        
1661
 
        func_curr_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.currency_id.id
1662
 
        if pricelist:
1663
 
            currency_id = self.pool.get('product.pricelist').browse(cr, uid, pricelist).currency_id.id
1664
 
        else:
1665
 
            currency_id = func_curr_id
1666
 
        
1667
 
        # Update the old price value        
1668
 
        res['value'].update({'product_qty': qty})
1669
 
        if product and not res.get('value', {}).get('price_unit', False) and all_qty != 0.00 and qty != 0.00:
1670
 
            # Display a warning message if the quantity is under the minimal qty of the supplier
1671
 
            currency_id = self.pool.get('product.pricelist').browse(cr, uid, pricelist).currency_id.id
1672
 
            tmpl_id = self.pool.get('product.product').read(cr, uid, product, ['product_tmpl_id'])['product_tmpl_id'][0]
1673
 
            info_prices = []
1674
 
            domain = [('uom_id', '=', uom),
1675
 
                      ('partner_id', '=', partner_id),
1676
 
                      ('product_id', '=', tmpl_id),
1677
 
                      '|', ('valid_from', '<=', date_order),
1678
 
                      ('valid_from', '=', False),
1679
 
                      '|', ('valid_till', '>=', date_order),
1680
 
                      ('valid_till', '=', False)]
1681
 
            
1682
 
            domain_cur = [('currency_id', '=', currency_id)]
1683
 
            domain_cur.extend(domain)
1684
 
            
1685
 
            info_prices = partner_price.search(cr, uid, domain_cur, order='sequence asc, min_quantity asc, id desc', limit=1, context=context)
1686
 
            if not info_prices:
1687
 
                info_prices = partner_price.search(cr, uid, domain, order='sequence asc, min_quantity asc, id desc', limit=1, context=context)
1688
 
                
1689
 
            if info_prices:
1690
 
                info_price = partner_price.browse(cr, uid, info_prices[0], context=context)
1691
 
                info_u_price = self.pool.get('res.currency').compute(cr, uid, info_price.currency_id.id, currency_id, info_price.price)
1692
 
                res['value'].update({'old_price_unit': info_u_price, 'price_unit': info_u_price})
1693
 
                res.update({'warning': {'title': _('Warning'), 'message': _('The product unit price has been set ' \
1694
 
                                                                                'for a minimal quantity of %s (the min quantity of the price list), '\
1695
 
                                                                                'it might change at the supplier confirmation.') % info_price.min_quantity}})
1696
 
            else:
1697
 
                old_price = self.pool.get('res.currency').compute(cr, uid, func_curr_id, currency_id, res['value']['price_unit'])
1698
 
                res['value'].update({'old_price_unit': old_price})
1699
 
        else:
1700
 
            old_price = self.pool.get('res.currency').compute(cr, uid, func_curr_id, currency_id, res.get('value').get('price_unit'))
1701
 
            res['value'].update({'old_price_unit': old_price})
1702
 
                
1703
 
        # Set the unit price with cost price if the product has no staged pricelist
1704
 
        if product and qty != 0.00: 
1705
 
            res['value'].update({'comment': False, 'nomen_manda_0': False, 'nomen_manda_1': False,
1706
 
                                 'nomen_manda_2': False, 'nomen_manda_3': False, 'nomen_sub_0': False, 
1707
 
                                 'nomen_sub_1': False, 'nomen_sub_2': False, 'nomen_sub_3': False, 
1708
 
                                 'nomen_sub_4': False, 'nomen_sub_5': False})
1709
 
            st_price = self.pool.get('product.product').browse(cr, uid, product).standard_price
1710
 
            st_price = self.pool.get('res.currency').compute(cr, uid, func_curr_id, currency_id, st_price)
1711
 
        
1712
 
            if res.get('value', {}).get('price_unit', False) == False and (state and state == 'draft') or not state :
1713
 
                res['value'].update({'price_unit': st_price, 'old_price_unit': st_price})
1714
 
            elif state and state != 'draft' and old_price_unit:
1715
 
                res['value'].update({'price_unit': old_price_unit, 'old_price_unit': old_price_unit})
1716
 
                
1717
 
            if res['value']['price_unit'] == 0.00:
1718
 
                res['value'].update({'price_unit': st_price, 'old_price_unit': st_price})
1719
 
                
1720
 
        elif qty == 0.00:
1721
 
            res['value'].update({'price_unit': 0.00, 'old_price_unit': 0.00})
1722
 
        elif not product and not comment and not nomen_manda_0:
1723
 
            res['value'].update({'price_unit': 0.00, 'product_qty': 0.00, 'product_uom': False, 'old_price_unit': 0.00})
1724
 
        
1725
 
        return res
1726
 
 
1727
 
    def price_unit_change(self, cr, uid, ids, fake_id, price_unit, product_id, 
1728
 
                          product_uom, product_qty, pricelist, partner_id, date_order, 
1729
 
                          change_price_ok, state, old_price_unit, 
1730
 
                          nomen_manda_0=False, comment=False, context=None):
1731
 
        '''
1732
 
        Display a warning message on change price unit if there are other lines with the same product and the same uom
1733
 
        '''
1734
 
        res = {'value': {}}
1735
 
 
1736
 
        if context is None:
1737
 
            context = {}
1738
 
            
1739
 
        if not product_id or not product_uom or not product_qty:
1740
 
            return res
1741
 
        
1742
 
        order_id = context.get('purchase_id', False)
1743
 
        if not order_id:
1744
 
            return res
1745
 
 
1746
 
        order = self.pool.get('purchase.order').browse(cr, uid, order_id, context=context)
1747
 
        other_lines = self.search(cr, uid, [('id', '!=', fake_id), ('order_id', '=', order_id), ('product_id', '=', product_id), ('product_uom', '=', product_uom)], context=context)
1748
 
        stages = self._get_stages_price(cr, uid, product_id, product_uom, order, context=context)
1749
 
        
1750
 
        if not change_price_ok or (other_lines and stages and order.state != 'confirmed' and not context.get('rfq_ok')):
1751
 
            res.update({'warning': {'title': 'Error',
1752
 
                                    'message': 'This product get stages prices for this supplier, you cannot change the price manually in draft state '\
1753
 
                                               'as you have multiple order lines (it is possible in "validated" state.'}})
1754
 
            res['value'].update({'price_unit': old_price_unit})
1755
 
        else:
1756
 
            res['value'].update({'old_price_unit': price_unit})
1757
 
 
1758
 
        return res
1759
 
    
1760
 
    def get_sol_ids_from_pol_ids(self, cr, uid, ids, context=None):
1761
 
        '''
1762
 
        input: purchase order line ids
1763
 
        return: sale order line ids
1764
 
        '''
1765
 
        # Some verifications
1766
 
        if not context:
1767
 
            context = {}
1768
 
        if isinstance(ids, (int, long)):
1769
 
            ids = [ids]
1770
 
            
1771
 
        # objects
1772
 
        sol_obj = self.pool.get('sale.order.line')
1773
 
        # procurement ids list
1774
 
        proc_ids = []
1775
 
        # sale order lines list
1776
 
        sol_ids = []
1777
 
        
1778
 
        for line in self.browse(cr, uid, ids, context=context):
1779
 
            if line.procurement_id:
1780
 
                proc_ids.append(line.procurement_id.id)
1781
 
        # get the corresponding sale order line list
1782
 
        if proc_ids:
1783
 
            sol_ids = sol_obj.search(cr, uid, [('procurement_id', 'in', proc_ids)], context=context)
1784
 
        return sol_ids
1785
 
 
1786
 
    def open_split_wizard(self, cr, uid, ids, context=None):
1787
 
        '''
1788
 
        Open the wizard to split the line
1789
 
        '''
1790
 
        if not context:
1791
 
            context = {}
1792
 
 
1793
 
        if isinstance(ids, (int, long)):
1794
 
            ids = [ids]
1795
 
 
1796
 
        for line in self.browse(cr, uid, ids, context=context):
1797
 
            data = {'purchase_line_id': line.id, 'original_qty': line.product_qty, 'old_line_qty': line.product_qty}
1798
 
            wiz_id = self.pool.get('split.purchase.order.line.wizard').create(cr, uid, data, context=context)
1799
 
            return {'type': 'ir.actions.act_window',
1800
 
                    'res_model': 'split.purchase.order.line.wizard',
1801
 
                    'view_type': 'form',
1802
 
                    'view_mode': 'form',
1803
 
                    'target': 'new',
1804
 
                    'res_id': wiz_id,
1805
 
                    'context': context}
1806
 
 
1807
 
 
1808
 
purchase_order_line()
1809
 
 
1810
 
class account_invoice(osv.osv):
1811
 
    _name = 'account.invoice'
1812
 
    _inherit = 'account.invoice'
1813
 
    
1814
 
    _columns = {
1815
 
        'purchase_list': fields.boolean(string='Purchase List ?', help='Check this box if the invoice comes from a purchase list', readonly=True, states={'draft':[('readonly',False)]}),
1816
 
    }
1817
 
    
1818
 
account_invoice()
1819
 
 
1820
 
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: