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

« back to all changes in this revision

Viewing changes to purchase_override/purchase.py

  • Committer: jf
  • Date: 2012-08-03 14:15:44 UTC
  • mfrom: (1071.2.4 uf-1303)
  • Revision ID: jf@tempo4-20120803141544-xaplb4ot6do2b7iz
UF-1311 [FIX] Column of local expense should not be all in bold
UF-1310 [FIX] A menu is missing in supply configuration
lp:~unifield-team/unifield-wm/uf-1310

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
##############################################################################
3
3
#
4
4
#    OpenERP, Open Source Management Solution
5
 
#    Copyright (C) 2011 TeMPO Consulting, MSF
 
5
#    Copyright (C) 2011 TeMPO Consulting, MSF 
6
6
#
7
7
#    This program is free software: you can redistribute it and/or modify
8
8
#    it under the terms of the GNU Affero General Public License as
23
23
from order_types import ORDER_PRIORITY, ORDER_CATEGORY
24
24
from tools.translate import _
25
25
import netsvc
26
 
from mx.DateTime import Parser
27
 
from mx.DateTime import RelativeDateTime
28
 
from time import strftime
29
 
from osv.orm import browse_record, browse_null
 
26
from mx.DateTime import *
 
27
import time
 
28
 
30
29
from workflow.wkf_expr import _eval_expr
 
30
import logging
31
31
 
32
32
from dateutil.relativedelta import relativedelta
33
33
from datetime import datetime
34
34
 
35
 
import decimal_precision as dp
36
 
 
37
35
from purchase_override import PURCHASE_ORDER_STATE_SELECTION
38
36
 
39
37
class purchase_order_confirm_wizard(osv.osv):
40
38
    _name = 'purchase.order.confirm.wizard'
41
 
 
 
39
    
42
40
    _columns = {
43
41
            'order_id': fields.many2one('purchase.order', string='Purchase Order', readonly=True),
44
42
            'errors': fields.text(string='Error message', readonly=True),
45
43
        }
46
 
 
 
44
    
47
45
    def validate_order(self, cr, uid, ids, context=None):
48
46
        wf_service = netsvc.LocalService("workflow")
49
47
        for wiz in self.browse(cr, uid, ids, context=context):
50
48
            wf_service.trg_validate(uid, 'purchase.order', wiz.order_id.id, 'purchase_confirmed_wait', cr)
51
49
        return {'type': 'ir.actions.act_window_close'}
52
 
 
 
50
    
53
51
purchase_order_confirm_wizard()
54
52
 
55
53
class purchase_order(osv.osv):
56
54
    _name = 'purchase.order'
57
55
    _inherit = 'purchase.order'
58
56
 
59
 
    def update_supplier_info(self, cr, uid, ids, context=None, *args, **kwargs):
60
 
        '''
61
 
        update the supplier info of corresponding products
62
 
        '''
63
 
        info_obj = self.pool.get('product.supplierinfo')
64
 
        pricelist_info_obj = self.pool.get('pricelist.partnerinfo')
65
 
        for rfq in self.browse(cr, uid, ids, context=context):
66
 
            for line in rfq.order_line:
67
 
                # if the price is updated and a product selected
68
 
                if line.price_unit and line.product_id:
69
 
                    # get the product
70
 
                    product = line.product_id
71
 
                    # find the corresponding suppinfo with sequence -99
72
 
                    info_99_list = info_obj.search(cr, uid, [('product_id', '=', product.product_tmpl_id.id),
73
 
                                                             ('sequence', '=', -99),], context=context)
74
 
 
75
 
                    if info_99_list:
76
 
                        # we drop it
77
 
                        info_obj.unlink(cr, uid, info_99_list, context=context)
78
 
 
79
 
                    # create the new one
80
 
                    values = {'name': rfq.partner_id.id,
81
 
                              'product_name': False,
82
 
                              'product_code': False,
83
 
                              'sequence' : -99,
84
 
                              #'product_uom': line.product_uom.id,
85
 
                              #'min_qty': 0.0,
86
 
                              #'qty': function
87
 
                              'product_id' : product.product_tmpl_id.id,
88
 
                              'delay' : int(rfq.partner_id.default_delay),
89
 
                              #'pricelist_ids': created just after
90
 
                              #'company_id': default value
91
 
                              }
92
 
 
93
 
                    new_info_id = info_obj.create(cr, uid, values, context=context)
94
 
                    # price lists creation - 'pricelist.partnerinfo
95
 
                    values = {'suppinfo_id': new_info_id,
96
 
                              'min_quantity': 1.00,
97
 
                              'price': line.price_unit,
98
 
                              'uom_id': line.product_uom.id,
99
 
                              'currency_id': line.currency_id.id,
100
 
                              'valid_till': rfq.valid_till,
101
 
                              'purchase_order_line_id': line.id,
102
 
                              'comment': 'RfQ original quantity for price : %s' % line.product_qty,
103
 
                              }
104
 
                    pricelist_info_obj.create(cr, uid, values, context=context)
105
 
 
106
 
        return True
107
 
 
108
 
    def generate_po_from_rfq(self, cr, uid, ids, context=None):
109
 
        '''
110
 
        generate a po from the selected request for quotation
111
 
        '''
112
 
        # Objects
113
 
        line_obj = self.pool.get('purchase.order.line')
114
 
 
115
 
        # Some verifications
116
 
        if context is None:
117
 
            context = {}
118
 
        if isinstance(ids, (int, long)):
119
 
            ids = [ids]
120
 
 
121
 
        # update price lists
122
 
        self.update_supplier_info(cr, uid, ids, context=context)
123
 
        # copy the po with rfq_ok set to False
124
 
        data = self.read(cr, uid, ids[0], ['name'], context=context)
125
 
        new_po_id = self.copy(cr, uid, ids[0], {'name': False, 'rfq_ok': False, 'origin': data['name']}, context=dict(context,keepOrigin=True))
126
 
        # Remove lines with 0.00 as unit price
127
 
        no_price_line_ids = line_obj.search(cr, uid, [
128
 
            ('order_id', '=', new_po_id),
129
 
            ('price_unit', '=', 0.00),
130
 
        ], context=context)
131
 
        line_obj.unlink(cr, uid, no_price_line_ids, context=context)
132
 
 
133
 
        data = self.read(cr, uid, new_po_id, ['name'], context=context)
134
 
        # log message describing the previous action
135
 
        self.log(cr, uid, new_po_id, _('The Purchase Order %s has been generated from Request for Quotation.')%data['name'])
136
 
        # close the current po
137
 
        wf_service = netsvc.LocalService("workflow")
138
 
        wf_service.trg_validate(uid, 'purchase.order', ids[0], 'rfq_done', cr)
139
 
 
140
 
        return True
141
 
 
142
 
    def copy(self, cr, uid, p_id, default=None, context=None):
 
57
    def copy(self, cr, uid, id, default=None, context=None):
143
58
        '''
144
59
        Remove loan_id field on new purchase.order
145
60
        '''
146
61
        if not default:
147
62
            default = {}
148
 
        if context is None:
149
 
            context = {}
150
 
 
151
 
        # if the copy comes from the button duplicate
152
 
        if context.get('from_button'):
153
 
            default.update({'is_a_counterpart': False})
154
 
        default.update({'loan_id': False, 'merged_line_ids': False, 'partner_ref': False})
155
 
        if not context.get('keepOrigin', False):
156
 
            default.update({'origin': False})
157
 
 
158
 
        return super(purchase_order, self).copy(cr, uid, p_id, default, context=context)
159
 
 
 
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
    
160
66
    # @@@purchase.purchase_order._invoiced
161
67
    def _invoiced(self, cursor, user, ids, name, arg, context=None):
162
68
        res = {}
167
73
            res[purchase.id] = invoiced
168
74
        return res
169
75
    # @@@end
170
 
 
 
76
    
171
77
    # @@@purchase.purchase_order._shipped_rate
172
78
    def _invoiced_rate(self, cursor, user, ids, name, arg, context=None):
173
79
        res = {}
174
80
        for purchase in self.browse(cursor, user, ids, context=context):
175
 
            if ((purchase.order_type == 'regular' and purchase.partner_id.partner_type in ('internal', 'esc')) or \
 
81
            if ((purchase.order_type == 'regular' and purchase.partner_id.partner_type == 'internal') or \
176
82
                purchase.order_type in ['donation_exp', 'donation_st', 'loan', 'in_kind']):
177
83
                res[purchase.id] = purchase.shipped_rate
178
84
            else:
186
92
                    res[purchase.id] = 0.0
187
93
        return res
188
94
    # @@@end
189
 
 
 
95
    
190
96
    def _get_allocation_setup(self, cr, uid, ids, field_name, args, context=None):
191
97
        '''
192
98
        Returns the Unifield configuration value
193
99
        '''
194
100
        res = {}
195
101
        setup = self.pool.get('unifield.setup.configuration').get_config(cr, uid)
196
 
 
 
102
        
197
103
        for order in ids:
198
104
            res[order] = setup.allocation_setup
199
 
 
200
 
        return res
201
 
 
202
 
    def _get_no_line(self, cr, uid, ids, field_name, args, context=None):
203
 
        res = {}
204
 
        for order in self.browse(cr, uid, ids, context=context):
205
 
            res[order.id] = True
206
 
            if order.order_line:
207
 
                res[order.id] = False
208
 
        return res
209
 
 
210
 
    def _po_from_x(self, cr, uid, ids, field_names, args, context=None):
211
 
        """fields.function multi for 'po_from_ir' and 'po_from_fo' fields."""
212
 
        res = {}
213
 
        pol_obj = self.pool.get('purchase.order.line')
214
 
        sol_obj = self.pool.get('sale.order.line')
215
 
        for po_data in self.read(cr, uid, ids, ['order_line'], context=context):
216
 
            res[po_data['id']] = {'po_from_ir': False, 'po_from_fo': False}
217
 
            pol_ids = po_data.get('order_line')
218
 
            if pol_ids:
219
 
                pol_datas = pol_obj.read(
220
 
                    cr, uid, pol_ids, ['procurement_id'], context=context)
221
 
                proc_ids = [pol['procurement_id'][0]
222
 
                            for pol in pol_datas if pol.get('procurement_id')]
223
 
                if proc_ids:
224
 
                    # po_from_ir
225
 
                    sol_ids = sol_obj.search(
226
 
                        cr, uid,
227
 
                        [('procurement_id', 'in', proc_ids)],
228
 
                        context=context)
229
 
                    res[po_data['id']]['po_from_ir'] = bool(sol_ids)
230
 
                    # po_from_fo
231
 
                    sol_ids = sol_obj.search(
232
 
                        cr, uid,
233
 
                        [('procurement_id', 'in', proc_ids),
234
 
                         ('order_id.procurement_request', '=', False)],
235
 
                        context=context)
236
 
                    res[po_data['id']]['po_from_fo'] = bool(sol_ids)
237
 
        return res
238
 
 
239
 
    def _get_dest_partner_names(self, cr, uid, ids, field_name, args, context=None):
240
 
        res = {}
241
 
        for po_r in self.read(cr, uid, ids, ['dest_partner_ids'], context=context):
242
 
            names = ''
243
 
            if po_r['dest_partner_ids']:
244
 
                name_tuples = self.pool.get('res.partner').name_get(cr, uid, po_r['dest_partner_ids'], context=context)
245
 
                if name_tuples:
246
 
                    names_list = [nt[1] for nt in name_tuples]
247
 
                    names = "; ".join(names_list)
248
 
            res[po_r['id']] = names
249
 
        return res
250
 
 
251
 
    def _get_project_ref(self, cr, uid, ids, field_name, args, context=None):
252
 
        '''
253
 
        Get the name of the POs at project side
254
 
        '''
255
 
        if isinstance(ids, (int, long)):
256
 
            ids = [ids]
257
 
 
258
 
        res = {}
259
 
        for po in ids:
260
 
            res[po] = ''
261
 
            so_ids = self.get_so_ids_from_po_ids(cr, uid, po, context=context)
262
 
            for so in self.pool.get('sale.order').browse(cr, uid, so_ids, context=context):
263
 
                if so.client_order_ref:
264
 
                    if res[po]:
265
 
                        res[po] += ' - '
266
 
                    res[po] += so.client_order_ref
267
 
 
268
 
        return res
269
 
 
 
105
        
 
106
        return res
 
107
    
270
108
    _columns = {
271
 
        'order_type': fields.selection([('regular', 'Regular'), ('donation_exp', 'Donation before expiry'),
272
 
                                        ('donation_st', 'Standard donation'), ('loan', 'Loan'),
 
109
        'order_type': fields.selection([('regular', 'Regular'), ('donation_exp', 'Donation before expiry'), 
 
110
                                        ('donation_st', 'Standard donation'), ('loan', 'Loan'), 
273
111
                                        ('in_kind', 'In Kind Donation'), ('purchase_list', 'Purchase List'),
274
 
                                        ('direct', 'Direct Purchase Order')], string='Order Type', required=True, states={'sourced':[('readonly',True)], 'split':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}),
 
112
                                        ('direct', 'Direct Purchase Order')], string='Order Type', required=True, states={'approved':[('readonly',True)],'done':[('readonly',True)]}),
275
113
        'loan_id': fields.many2one('sale.order', string='Linked loan', readonly=True),
276
114
        'priority': fields.selection(ORDER_PRIORITY, string='Priority', states={'approved':[('readonly',True)],'done':[('readonly',True)]}),
277
115
        'categ': fields.selection(ORDER_CATEGORY, string='Order category', required=True, states={'approved':[('readonly',True)],'done':[('readonly',True)]}),
278
 
        # we increase the size of the 'details' field from 30 to 86
279
 
        'details': fields.char(size=86, string='Details', states={'sourced':[('readonly',True)], 'split':[('readonly',True)], 'cancel':[('readonly',True)], 'confirmed_wait':[('readonly',True)], 'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}),
 
116
        'details': fields.char(size=30, string='Details', states={'cancel':[('readonly',True)], 'confirmed_wait':[('readonly',True)], 'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}),
280
117
        'invoiced': fields.function(_invoiced, method=True, string='Invoiced', type='boolean', help="It indicates that an invoice has been generated"),
281
118
        'invoiced_rate': fields.function(_invoiced_rate, method=True, string='Invoiced', type='float'),
282
119
        'loan_duration': fields.integer(string='Loan duration', help='Loan duration in months', states={'confirmed':[('readonly',True)],'approved':[('readonly',True)],'done':[('readonly',True)]}),
283
120
        'from_yml_test': fields.boolean('Only used to pass addons unit test', readonly=True, help='Never set this field to true !'),
284
121
        'date_order':fields.date(string='Creation Date', readonly=True, required=True,
285
122
                            states={'draft':[('readonly',False)],}, select=True, help="Date on which this document has been created."),
286
 
        'name': fields.char('Order Reference', size=64, required=True, select=True, readonly=True,
 
123
        '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)]},
287
124
                            help="unique number of the purchase order,computed automatically when the purchase order is created"),
288
125
        'invoice_ids': fields.many2many('account.invoice', 'purchase_invoice_rel', 'purchase_id', 'invoice_id', 'Invoices', help="Invoices generated for a purchase order", readonly=True),
289
126
        'order_line': fields.one2many('purchase.order.line', 'order_id', 'Order Lines', readonly=True, states={'draft':[('readonly',False)], 'rfq_sent':[('readonly',False)], 'confirmed': [('readonly',False)]}),
290
 
        'partner_id':fields.many2one('res.partner', 'Supplier', required=True, states={'sourced':[('readonly',True)], 'split':[('readonly',True)], '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)]"),
 
127
        '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)]"),
291
128
        'partner_address_id':fields.many2one('res.partner.address', 'Address', required=True,
292
 
            states={'sourced':[('readonly',True)], 'split':[('readonly',True)], '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)]"),
 
129
            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)]"),
293
130
        'dest_partner_id': fields.many2one('res.partner', string='Destination partner', domain=[('partner_type', '=', 'internal')]),
294
 
        'invoice_address_id': fields.many2one('res.partner.address', string='Invoicing address', required=True,
 
131
        'invoice_address_id': fields.many2one('res.partner.address', string='Invoicing address', required=True, 
295
132
                                              help="The address where the invoice will be sent."),
296
133
        'invoice_method': fields.selection([('manual','Manual'),('order','From Order'),('picking','From Picking')], 'Invoicing Control', required=True, readonly=True,
297
134
            help="From Order: a draft invoice will be pre-generated based on the purchase order. The accountant " \
306
143
                                                       ('unallocated', 'Unallocated'),
307
144
                                                       ('mixed', 'Mixed')], string='Allocated setup', method=True, store=False),
308
145
        'unallocation_ok': fields.boolean(string='Unallocated PO'),
309
 
        # we increase the size of the partner_ref field from 64 to 128
310
 
        'partner_ref': fields.char('Supplier Reference', size=128),
 
146
        'partner_ref': fields.char('Supplier Reference', size=64),
311
147
        'product_id': fields.related('order_line', 'product_id', type='many2one', relation='product.product', string='Product'),
312
 
        'no_line': fields.function(_get_no_line, method=True, type='boolean', string='No line'),
313
 
        'active': fields.boolean('Active', readonly=True),
314
 
        'po_from_ir': fields.function(_po_from_x, method=True, type='boolean', string='Is PO from IR ?', multi='po_from_x'),
315
 
        'po_from_fo': fields.function(_po_from_x, method=True, type='boolean', string='Is PO from FO ?', multi='po_from_x'),
316
 
        'canceled_end': fields.boolean(string='Canceled End', readonly=True),
317
 
        'is_a_counterpart': fields.boolean('Counterpart?', help="This field is only for indicating that the order is a counterpart"),
318
 
        'po_updated_by_sync': fields.boolean('PO updated by sync', readonly=False),
319
 
        'origin': fields.text('Source Document',
320
 
                        help="Reference of the document that generated this purchase order request."),
321
 
        # UF-2267: Store also the parent PO as reference in the sourced PO
322
 
        'parent_order_name': fields.many2one('purchase.order', string='Parent PO name', help='If the PO is created from a re-source FO, this field contains the relevant original PO name'),
323
 
        'project_ref': fields.char(size=256, string='Project Ref.'),
324
 
        'message_esc': fields.text(string='ESC Message'),
325
 
        'fnct_project_ref': fields.function(_get_project_ref, method=True, string='Project Ref.',
326
 
                                            type='char', size=256, store=False,),
327
 
        'dest_partner_ids': fields.many2many('res.partner', 'res_partner_purchase_order_rel', 'purchase_order_id', 'partner_id', 'Customers'),  # uf-2223
328
 
        'dest_partner_names': fields.function(_get_dest_partner_names, type='string', string='Customers', method=True),  # uf-2223
329
 
        'split_po': fields.boolean('Created by split PO', readonly=True),
330
148
    }
331
 
 
 
149
    
332
150
    _defaults = {
333
151
        'order_type': lambda *a: 'regular',
334
152
        'priority': lambda *a: 'normal',
335
153
        'categ': lambda *a: 'other',
336
154
        'loan_duration': 2,
337
155
        'from_yml_test': lambda *a: False,
338
 
        '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.partner_id.id, ['invoice'])['invoice'],
 
156
        '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'],
339
157
        'invoice_method': lambda *a: 'picking',
340
 
        '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.partner_id.id, ['delivery'])['delivery'],
341
 
        'no_line': lambda *a: True,
342
 
        'active': True,
343
 
        'name': lambda *a: False,
344
 
        'is_a_counterpart': False,
345
 
        'parent_order_name': False,
346
 
        'canceled_end': False,
347
 
        'split_po': False,
 
158
        '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']
348
159
    }
349
160
 
350
 
    def _check_po_from_fo(self, cr, uid, ids, context=None):
351
 
        if not context:
352
 
            context = {}
353
 
        retour = True
354
 
        for po in self.browse(cr, uid, ids, context=context):
355
 
            if po.partner_id.partner_type == 'internal' and po.po_from_fo:
356
 
                retour = False
357
 
        return retour
358
 
 
359
 
    _constraints = [
360
 
        (_check_po_from_fo, 'You cannot choose an internal supplier for this purchase order', []),
361
 
    ]
362
 
 
363
 
    def _check_service(self, cr, uid, ids, vals, context=None):
364
 
        '''
365
 
        Avoid the saving of a PO with non service products on Service PO
366
 
        '''
367
 
        # UTP-871 : Remove check of service
368
 
        return True
369
 
 
370
 
        if isinstance(ids, (int, long)):
371
 
            ids = [ids]
372
 
        if context is None:
373
 
            context = {}
374
 
        if context.get('import_in_progress'):
375
 
            return True
376
 
 
377
 
        for order in self.browse(cr, uid, ids, context=context):
378
 
            for line in order.order_line:
379
 
                if vals.get('categ', order.categ) == 'transport' and line.product_id and (line.product_id.type not in ('service', 'service_recep') or not line.product_id.transport_ok):
380
 
                    raise osv.except_osv(_('Error'), _('The product [%s]%s is not a \'Transport\' product. You can purchase only \'Transport\' products on a \'Transport\' purchase order. Please remove this line.') % (line.product_id.default_code, line.product_id.name))
381
 
                    return False
382
 
                elif vals.get('categ', order.categ) == 'service' and line.product_id and line.product_id.type not in ('service', 'service_recep'):
383
 
                    raise osv.except_osv(_('Error'), _('The product [%s] %s is not a \'Service\' product. You can purchase only \'Service\' products on a \'Service\' purchase order. Please remove this line.') % (line.product_id.default_code, line.product_id.name))
384
 
                    return False
385
 
 
386
 
        return True
387
 
 
388
 
    def purchase_cancel(self, cr, uid, ids, context=None):
389
 
        '''
390
 
        Call the wizard to ask if you want to re-source the line
391
 
        '''
392
 
        line_obj = self.pool.get('purchase.order.line')
393
 
        wiz_obj = self.pool.get('purchase.order.cancel.wizard')
394
 
        wf_service = netsvc.LocalService("workflow")
395
 
 
396
 
        if context is None:
397
 
            context = {}
398
 
 
399
 
        if isinstance(ids, (int, long)):
400
 
            ids = [ids]
401
 
 
402
 
        for po in self.browse(cr, uid, ids, context=context):
403
 
            for l in po.order_line:
404
 
                if line_obj.get_sol_ids_from_pol_ids(cr, uid, [l.id], context=context):
405
 
                    wiz_id = wiz_obj.create(cr, uid, {'order_id': po.id}, context=context)
406
 
                    return {'type': 'ir.actions.act_window',
407
 
                            'res_model': 'purchase.order.cancel.wizard',
408
 
                            'res_id': wiz_id,
409
 
                            'view_type': 'form',
410
 
                            'view_mode': 'form',
411
 
                            'target': 'new',
412
 
                            'context': context}
413
 
 
414
 
            wf_service.trg_validate(uid, 'purchase.order', po.id, 'purchase_cancel', cr)
415
 
 
416
 
        return True
417
 
 
418
 
    def unlink(self, cr, uid, ids, context=None):
419
 
        '''
420
 
        No unlink for PO linked to a FO
421
 
        '''
422
 
        if self.get_so_ids_from_po_ids(cr, uid, ids, context=context):
423
 
            raise osv.except_osv(_('Error'), _('You cannot remove a Purchase order that is linked to a Field Order or an Internal Request. Please cancel it instead.'))
424
 
 
425
 
        return super(purchase_order, self).unlink(cr, uid, ids, context=context)
426
 
 
427
 
    def _check_restriction_line(self, cr, uid, ids, context=None):
428
 
        '''
429
 
        Check restriction on products
430
 
        '''
431
 
        if isinstance(ids, (int, long)):
432
 
            ids = [ids]
433
 
 
434
 
        line_obj = self.pool.get('purchase.order.line')
435
 
        res = True
436
 
 
437
 
        for order in self.browse(cr, uid, ids, context=context):
438
 
            res = res and line_obj._check_restriction_line(cr, uid, [x.id for x in order.order_line], context=context)
439
 
 
440
 
        return res
441
 
 
442
 
 
443
161
    def default_get(self, cr, uid, fields, context=None):
444
162
        '''
445
163
        Fill the unallocated_ok field according to Unifield setup
446
164
        '''
447
165
        res = super(purchase_order, self).default_get(cr, uid, fields, context=context)
448
 
 
449
166
        setup = self.pool.get('unifield.setup.configuration').get_config(cr, uid)
 
167
        
450
168
        res.update({'unallocation_ok': False, 'allocation_setup': setup.allocation_setup})
451
169
        if setup.allocation_setup == 'unallocated':
452
170
            res.update({'unallocation_ok': True})
453
171
 
454
 
        res.update({'name': False})
455
 
 
456
172
        return res
457
173
 
458
 
 
459
174
    def _check_user_company(self, cr, uid, company_id, context=None):
460
175
        '''
461
176
        Remove the possibility to make a PO to user's company
468
183
 
469
184
    def write(self, cr, uid, ids, vals, context=None):
470
185
        '''
471
 
        Check if the partner is correct.
472
 
        # UTP-114 demand purchase_list PO to be "from picking" as invoice_method
 
186
        Check if the partner is correct
473
187
        '''
474
188
        if 'partner_id' in vals:
475
189
            self._check_user_company(cr, uid, vals['partner_id'], context=context)
476
 
 
477
 
        self._check_service(cr, uid, ids, vals, context=context)
478
 
 
479
 
        for order in self.browse(cr, uid, ids, context=context):
480
 
            partner_type = self.pool.get('res.partner').browse(cr, uid, vals.get('partner_id', order.partner_id.id), context=context).partner_type
481
 
            if vals.get('order_type'):
482
 
                if vals.get('order_type') in ['donation_exp', 'donation_st', 'loan']:
483
 
                    vals.update({'invoice_method': 'manual'})
484
 
                elif vals.get('order_type') in ['direct',] and partner_type != 'esc':
485
 
                    vals.update({'invoice_method': 'order'})
486
 
                elif vals.get('order_type') in ['direct',] and partner_type == 'esc':
487
 
                    vals.update({'invoice_method': 'manual'})
488
 
                else:
489
 
                    vals.update({'invoice_method': 'picking'})
490
 
            # we need to update the location_id because it is readonly and so does not pass in the vals of create and write
491
 
            vals = self._get_location_id(cr, uid, vals,  warehouse_id=vals.get('warehouse_id', order.warehouse_id.id), context=context)
492
 
 
493
 
        res = super(purchase_order, self).write(cr, uid, ids, vals, context=context)
494
 
 
495
 
        return res
496
 
 
497
 
    def onchange_internal_type(self, cr, uid, ids, order_type, partner_id, categ, dest_partner_id=False, warehouse_id=False, delivery_requested_date=False):
 
190
            
 
191
        if vals.get('order_type'):
 
192
            if vals.get('order_type') in ['donation_exp', 'donation_st', 'loan']:
 
193
                vals.update({'invoice_method': 'manual'})
 
194
            elif vals.get('order_type') in ['direct', 'purchase_list']:
 
195
                vals.update({'invoice_method': 'order'})
 
196
            else:
 
197
                vals.update({'invoice_method': 'picking'})
 
198
 
 
199
        return super(purchase_order, self).write(cr, uid, ids, vals, context=context)
 
200
    
 
201
    def onchange_internal_type(self, cr, uid, ids, order_type, partner_id, dest_partner_id=False, warehouse_id=False):
498
202
        '''
499
203
        Changes the invoice method of the purchase order according to
500
204
        the choosen order type
502
206
        '''
503
207
        partner_obj = self.pool.get('res.partner')
504
208
        v = {}
505
 
        # the domain on the onchange was replace by a several fields.function that you can retrieve in the
506
 
        # file msf_custom_settings/view/purchase_view.xml: domain="[('supplier', '=', True), ('id', '!=', company_id), ('check_partner_po', '=', order_type),  ('check_partner_rfq', '=', tender_id)]"
507
 
#        d = {'partner_id': []}
 
209
        d = {'partner_id': []}
508
210
        w = {}
509
211
        local_market = None
510
 
 
 
212
        
511
213
        # Search the local market partner id
512
214
        data_obj = self.pool.get('ir.model.data')
513
215
        data_id = data_obj.search(cr, uid, [('module', '=', 'order_types'), ('model', '=', 'res.partner'), ('name', '=', 'res_partner_local_market')] )
514
216
        if data_id:
515
217
            local_market = data_obj.read(cr, uid, data_id, ['res_id'])[0]['res_id']
516
 
 
 
218
            
517
219
        if order_type == 'loan':
518
220
            setup = self.pool.get('unifield.setup.configuration').get_config(cr, uid)
519
 
 
 
221
                
520
222
            if not setup.field_orders_ok:
521
223
                return {'value': {'order_type': 'regular'},
522
224
                        'warning': {'title': 'Error',
523
225
                                    'message': 'The Field orders feature is not activated on your system, so, you cannot create a Loan Purchase Order !'}}
524
 
 
 
226
        
525
227
        if order_type in ['donation_exp', 'donation_st', 'loan']:
526
228
            v['invoice_method'] = 'manual'
527
 
        elif order_type in ['direct']:
 
229
        elif order_type in ['direct', 'purchase_list']:
528
230
            v['invoice_method'] = 'order'
529
 
        elif order_type in ['in_kind', 'purchase_list']:
 
231
            d['partner_id'] = [('partner_type', 'in', ['esc', 'external'])]
 
232
        elif order_type in ['in_kind']:
530
233
            v['invoice_method'] = 'picking'
 
234
            d['partner_id'] = [('partner_type', 'in', ['esc', 'external'])]
531
235
        else:
532
236
            v['invoice_method'] = 'picking'
533
 
 
534
 
        company_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.partner_id.id
535
 
 
536
 
        if order_type == 'direct' and dest_partner_id and dest_partner_id != company_id:
 
237
        
 
238
        if order_type == 'direct' and dest_partner_id:
537
239
            cp_address_id = self.pool.get('res.partner').address_get(cr, uid, dest_partner_id, ['delivery'])['delivery']
538
240
            v.update({'dest_address_id': cp_address_id})
 
241
            d.update({'dest_address_id': [('partner_id', '=', dest_partner_id)]})
539
242
        elif order_type == 'direct':
540
 
            v.update({'dest_address_id': False, 'dest_partner_id': False})
 
243
            v.update({'dest_address_id': False})
 
244
            d.update({'dest_address_id': [('partner_id', '=', self.pool.get('res.users').browse(cr, uid, uid).company_id.id)]})
541
245
        else:
542
 
            cp_address_id = self.pool.get('res.partner').address_get(cr, uid, company_id, ['delivery'])['delivery']
543
 
            v.update({'dest_address_id': cp_address_id, 'dest_partner_id': company_id})
 
246
            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']
 
247
            v.update({'dest_address_id': cp_address_id})
 
248
            d.update({'dest_address_id': [('partner_id', '=', self.pool.get('res.users').browse(cr, uid, uid).company_id.id)]})
544
249
 
545
250
        if partner_id and partner_id != local_market:
546
251
            partner = partner_obj.browse(cr, uid, partner_id)
547
 
            if partner.partner_type in ('internal', 'esc') and order_type in ('regular', 'direct'):
 
252
            if partner.partner_type == 'internal' and order_type == 'regular':
548
253
                v['invoice_method'] = 'manual'
549
254
            elif partner.partner_type not in ('external', 'esc') and order_type == 'direct':
550
255
                v.update({'partner_address_id': False, 'partner_id': False, 'pricelist_id': False,})
 
256
                d['partner_id'] = [('partner_type', 'in', ['esc', 'external'])]
551
257
                w.update({'message': 'You cannot have a Direct Purchase Order with a partner which is not external or an ESC',
552
258
                          'title': 'An error has occured !'})
553
259
        elif partner_id and partner_id == local_market and order_type != 'purchase_list':
554
260
            v['partner_id'] = None
555
261
            v['partner_address_id'] = None
556
262
            v['pricelist_id'] = None
557
 
 
 
263
            
558
264
        if order_type == 'purchase_list':
559
265
            if local_market:
560
266
                partner = self.pool.get('res.partner').browse(cr, uid, local_market)
565
271
                    v['pricelist_id'] = partner.property_product_pricelist_purchase.id
566
272
        elif order_type == 'direct':
567
273
            v['cross_docking_ok'] = False
568
 
 
569
 
        return {'value': v, 'warning': w}
570
 
 
 
274
        
 
275
        return {'value': v, 'domain': d, 'warning': w}
 
276
    
571
277
    def onchange_partner_id(self, cr, uid, ids, part, *a, **b):
572
278
        '''
573
279
        Fills the Requested and Confirmed delivery dates
574
280
        '''
575
281
        if isinstance(ids, (int, long)):
576
282
            ids = [ids]
577
 
 
 
283
        
578
284
        res = super(purchase_order, self).onchange_partner_id(cr, uid, ids, part, *a, **b)
579
 
 
 
285
        
580
286
        if part:
581
287
            partner_obj = self.pool.get('res.partner')
582
 
            product_obj = self.pool.get('product.product')
583
288
            partner = partner_obj.browse(cr, uid, part)
584
 
            if ids:
585
 
                # Check the restrction of product in lines
586
 
                if ids:
587
 
                    product_obj = self.pool.get('product.product')
588
 
                    for order in self.browse(cr, uid, ids):
589
 
                        for line in order.order_line:
590
 
                            if line.product_id:
591
 
                                res, test = product_obj._on_change_restriction_error(cr, uid, line.product_id.id, field_name='partner_id', values=res, vals={'partner_id': part})
592
 
                                if test:
593
 
                                    res.setdefault('value', {}).update({'partner_address_id': False})
594
 
                                    return res
595
 
            if partner.partner_type in ('internal', 'esc'):
 
289
            if partner.partner_type == 'internal':
596
290
                res['value']['invoice_method'] = 'manual'
597
 
            elif ids and partner.partner_type == 'intermission':
598
 
                try:
599
 
                    intermission = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
600
 
                        'analytic_account_project_intermission')[1]
601
 
                except ValueError:
602
 
                    intermission = 0
603
 
                cr.execute('''select po.id from purchase_order po
604
 
                    left join purchase_order_line pol on pol.order_id = po.id
605
 
                    left join cost_center_distribution_line cl1 on cl1.distribution_id = po.analytic_distribution_id
606
 
                    left join cost_center_distribution_line cl2 on cl2.distribution_id = pol.analytic_distribution_id
607
 
                    where po.id in %s and (cl1.analytic_id!=%s or cl2.analytic_id!=%s)''', (tuple(ids), intermission, intermission))
608
 
                if cr.rowcount > 0:
609
 
                    res.setdefault('warning', {})
610
 
                    msg = _('You set an intermission partner, at validation Cost Centers will be changed to intermission.')
611
 
                    if res.get('warning', {}).get('message'):
612
 
                        res['warning']['message'] += msg
613
 
                    else:
614
 
                        res['warning'] = {'title': _('Warning'), 'message': msg}
 
291
        
615
292
        return res
616
 
 
 
293
    
617
294
    # Be careful during integration, the onchange_warehouse_id method is also defined on UF-965
618
295
    def onchange_warehouse_id(self, cr, uid, ids,  warehouse_id, order_type, dest_address_id):
619
296
        '''
620
297
        Change the destination address to the destination address of the company if False
621
298
        '''
622
299
        res = super(purchase_order, self).onchange_warehouse_id(cr, uid, ids, warehouse_id)
623
 
 
 
300
        
624
301
        if not res.get('value', {}).get('dest_address_id') and order_type!='direct':
625
 
            cp_address_id = self.pool.get('res.partner').address_get(cr, uid, self.pool.get('res.users').browse(cr, uid, uid).company_id.partner_id.id, ['delivery'])['delivery']
 
302
            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']
626
303
            if 'value' in res:
627
304
                res['value'].update({'dest_address_id': cp_address_id})
628
305
            else:
630
307
        if order_type == 'direct' or dest_address_id:
631
308
            if 'dest_address_id' in res.get('value', {}):
632
309
                res['value'].pop('dest_address_id')
633
 
 
 
310
        
634
311
        return res
635
 
 
 
312
    
636
313
    def on_change_dest_partner_id(self, cr, uid, ids, dest_partner_id, context=None):
637
314
        '''
638
315
        Fill automatically the destination address according to the destination partner
639
316
        '''
640
317
        v = {}
641
 
 
 
318
        d = {}
 
319
        
642
320
        if not context:
643
321
            context = {}
644
 
 
 
322
        
 
323
        company_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.id
 
324
        
645
325
        if not dest_partner_id:
646
326
            v.update({'dest_address_id': False})
 
327
            d.update({'dest_address_id': [('partner_id', '=', company_id)]})
647
328
        else:
 
329
            d.update({'dest_address_id': [('partner_id', '=', dest_partner_id)]})
 
330
        
648
331
            delivery_addr = self.pool.get('res.partner').address_get(cr, uid, dest_partner_id, ['delivery'])
649
332
            v.update({'dest_address_id': delivery_addr['delivery']})
650
 
        return {'value': v}
 
333
        
 
334
        return {'value': v, 'domain': d}
651
335
 
652
 
    def change_currency(self, cr, uid, ids, context=None):
653
 
        '''
654
 
        Launches the wizard to change the currency and update lines
655
 
        '''
656
 
        if not context:
 
336
    def _hook_confirm_order_message(self, cr, uid, context=None, *args, **kwargs):
 
337
        '''
 
338
        Change the logged message
 
339
        '''
 
340
        if context is None:
657
341
            context = {}
658
 
 
659
 
        if isinstance(ids, (int, long)):
660
 
            ids = [ids]
661
 
 
662
 
        for order in self.browse(cr, uid, ids, context=context):
663
 
            data = {'order_id': order.id,
664
 
                    'partner_id': order.partner_id.id,
665
 
                    'partner_type': order.partner_id.partner_type,
666
 
                    'new_pricelist_id': order.pricelist_id.id,
667
 
                    'currency_rate': 1.00,
668
 
                    'old_pricelist_id': order.pricelist_id.id}
669
 
            wiz = self.pool.get('purchase.order.change.currency').create(cr, uid, data, context=context)
670
 
            return {'type': 'ir.actions.act_window',
671
 
                    'res_model': 'purchase.order.change.currency',
672
 
                    'view_type': 'form',
673
 
                    'view_mode': 'form',
674
 
                    'res_id': wiz,
675
 
                    'target': 'new'}
676
 
 
677
 
        return True
678
 
 
679
 
    def order_line_change(self, cr, uid, ids, order_line):
680
 
        res = {'no_line': True}
681
 
 
682
 
        if order_line:
683
 
            res = {'no_line': False}
684
 
 
685
 
        return {'value': res}
686
 
 
687
 
    def _get_destination_ok(self, cr, uid, lines, context):
688
 
        dest_ok = False
689
 
        for line in lines:
690
 
            is_inkind = line.order_id and line.order_id.order_type == 'in_kind' or False
691
 
            dest_ok = line.account_4_distribution and line.account_4_distribution.destination_ids or False
692
 
            if not dest_ok:
693
 
                if is_inkind:
694
 
                    raise osv.except_osv(_('Error'), _('No destination found. An In-kind Donation account is probably missing for this line: %s.') % (line.name or ''))
695
 
                raise osv.except_osv(_('Error'), _('No destination found for this line: %s.') % (line.name or '',))
696
 
        return dest_ok
 
342
        if 'po' in kwargs:
 
343
            po = kwargs['po']
 
344
            return _("Purchase order '%s' is validated.") % (po.name,)
 
345
        else:
 
346
            return super(purchase_order, self)._hook_confirm_order_message(cr, uid, context, args, kwargs)
697
347
 
698
348
    def check_analytic_distribution(self, cr, uid, ids, context=None):
699
349
        """
700
350
        Check analytic distribution validity for given PO.
701
351
        Also check that partner have a donation account (is PO is in_kind)
702
352
        """
703
 
        # Objects
704
 
        ad_obj = self.pool.get('analytic.distribution')
705
 
        ccdl_obj = self.pool.get('cost.center.distribution.line')
706
 
        pol_obj = self.pool.get('purchase.order.line')
707
 
 
708
 
        if context is None:
709
 
            context = {}
710
 
 
711
353
        if isinstance(ids, (int, long)):
712
354
            ids = [ids]
713
 
 
714
355
        # Analytic distribution verification
715
356
        for po in self.browse(cr, uid, ids, context=context):
716
 
            try:
717
 
                intermission_cc = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
718
 
                                                'analytic_account_project_intermission')[1]
719
 
            except ValueError:
720
 
                intermission_cc = 0
721
 
 
722
 
            if po.order_type == 'in_kind' and not po.partner_id.donation_payable_account:
 
357
            if po.order_type and po.order_type == 'in_kind':
723
358
                if not po.partner_id.donation_payable_account:
724
359
                    raise osv.except_osv(_('Error'), _('No donation account on this partner: %s') % (po.partner_id.name or '',))
725
 
 
726
 
            if po.partner_id and po.partner_id.partner_type == 'intermission':
727
 
                if not intermission_cc:
728
 
                    raise osv.except_osv(_('Error'), _('No Intermission Cost Center found!'))
729
 
 
730
360
            for pol in po.order_line:
731
 
                distrib = pol.analytic_distribution_id  or po.analytic_distribution_id  or False
 
361
                # Forget check if we come from YAML tests
 
362
                if po.from_yml_test:
 
363
                    continue
 
364
                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
732
365
                # Raise an error if no analytic distribution found
733
 
                if not distrib:
734
 
                    if not po.order_type in ('loan', 'donation_st', 'donation_exp'):
735
 
                        raise osv.except_osv(_('Warning'), _('Analytic allocation is mandatory for this line: %s!') % (pol.name or '',))
736
 
 
737
 
                    # UF-2031: If no distrib accepted (for loan, donation), then do not process the distrib
738
 
                    return True
739
 
                elif pol.analytic_distribution_state != 'valid':
740
 
                    id_ad = ad_obj.create(cr, uid, {})
741
 
                    ad_lines = pol.analytic_distribution_id and pol.analytic_distribution_id.cost_centre_lines or po.analytic_distribution_id.cost_center_lines
742
 
                    bro_dests = self._get_destination_ok(cr, uid, [pol], context=context)
743
 
                    for line in ad_lines:
744
 
                        # fetch compatible destinations then use on of them:
745
 
                        # - destination if compatible
746
 
                        # - else default destination of given account
747
 
                        if line.destination_id in bro_dests:
748
 
                            bro_dest_ok = line.destination_id
749
 
                        else:
750
 
                            bro_dest_ok = pol.account_4_distribution.default_destination_id
751
 
                        # Copy cost center line to the new distribution
752
 
                        ccdl_obj.copy(cr, uid, line.id, {'distribution_id': id_ad, 'destination_id': bro_dest_ok.id})
753
 
                        # Write result
754
 
                        pol_obj.write(cr, uid, [pol.id], {'analytic_distribution_id': id_ad})
 
366
                if not distrib_id:
 
367
                    raise osv.except_osv(_('Warning'), _('Analytic allocation is mandatory for this line: %s!') % (pol.name or '',))
 
368
                if pol.analytic_distribution_state != 'valid':
 
369
                    raise osv.except_osv(_('Warning'), _("Analytic distribution is not valid for '%s'!") % (pol.name or '',))
755
370
        return True
756
371
 
 
372
    def wkf_confirm_order(self, cr, uid, ids, context=None):
 
373
        '''
 
374
        Update the confirmation date of the PO at confirmation.
 
375
        Check analytic distribution.
 
376
        '''
 
377
        res = super(purchase_order, self).wkf_confirm_order(cr, uid, ids, context=context)
 
378
        self.write(cr, uid, ids, {'date_confirm': time.strftime('%Y-%m-%d')}, context=context)
 
379
        # CODE MOVED TO self.check_analytic_distribution()
 
380
        self.check_analytic_distribution(cr, uid, ids, context=context)
 
381
        return res
 
382
 
757
383
    def wkf_picking_done(self, cr, uid, ids, context=None):
758
384
        '''
759
385
        Change the shipped boolean and the state of the PO
765
391
                self.write(cr, uid, order.id, {'shipped':1,'state':'approved'}, context=context)
766
392
 
767
393
        return True
768
 
 
769
 
    def confirm_button(self, cr, uid, ids, context=None):
770
 
        '''
771
 
        check the supplier partner type (partner_type)
772
 
 
773
 
        confirmation is needed for internal, inter-mission and inter-section
774
 
 
775
 
        ('internal', 'Internal'), ('section', 'Inter-section'), ('intermission', 'Intermission')
776
 
        '''
777
 
        # data
778
 
        name = _("You're about to confirm a PO that is synchronized and should be consequently confirmed by the supplier (automatically at his equivalent FO confirmation). Are you sure you want to force the confirmation at your level (you won't get the supplier's update)?")
779
 
        model = 'confirm'
780
 
        step = 'default'
781
 
        question = "You're about to confirm a PO that is synchronized and should be consequently confirmed by the supplier (automatically at his equivalent FO confirmation). Are you sure you want to force the confirmation at your level (you won't get the supplier's update)?"
782
 
        clazz = 'purchase.order'
783
 
        func = '_purchase_approve'
784
 
        args = [ids]
785
 
        kwargs = {}
786
 
 
787
 
        for obj in self.browse(cr, uid, ids, context=context):
788
 
            if obj.partner_id.partner_type in ('internal', 'section', 'intermission'):
789
 
                # open the wizard
790
 
                wiz_obj = self.pool.get('wizard')
791
 
                # open the selected wizard
792
 
                res = wiz_obj.open_wizard(cr, uid, ids, name=name, model=model, step=step, context=dict(context, question=question,
793
 
                                                                                                        callback={'clazz': clazz,
794
 
                                                                                                                  'func': func,
795
 
                                                                                                                  'args': args,
796
 
                                                                                                                  'kwargs': kwargs}))
797
 
                return res
798
 
 
799
 
        # otherwise call function directly
800
 
        return self.purchase_approve(cr, uid, ids, context=context)
801
 
 
802
 
    def _purchase_approve(self, cr, uid, ids, context=None):
803
 
        '''
804
 
        interface for call from wizard
805
 
 
806
 
        if called from wizard without opening a new dic -> return close
807
 
        if called from wizard with new dic -> open new wizard
808
 
 
809
 
        if called from button directly, this interface is not called
810
 
        '''
811
 
        res = self.purchase_approve(cr, uid, ids, context=context)
812
 
        if not isinstance(res, dict):
813
 
            return {'type': 'ir.actions.act_window_close'}
814
 
        return res
815
 
 
 
394
    
816
395
    def purchase_approve(self, cr, uid, ids, context=None):
817
396
        '''
818
397
        If the PO is a DPO, check the state of the stock moves
819
398
        '''
820
 
        # Objects
821
 
        sale_line_obj = self.pool.get('sale.order.line')
822
 
        stock_move_obj = self.pool.get('stock.move')
823
 
        wiz_obj = self.pool.get('purchase.order.confirm.wizard')
824
 
 
825
399
        if isinstance(ids, (int, long)):
826
400
            ids = [ids]
827
 
 
 
401
            
828
402
        wf_service = netsvc.LocalService("workflow")
829
403
        move_obj = self.pool.get('stock.move')
830
 
 
 
404
            
831
405
        for order in self.browse(cr, uid, ids, context=context):
832
406
            if not order.delivery_confirmed_date:
833
407
                raise osv.except_osv(_('Error'), _('Delivery Confirmed Date is a mandatory field.'))
834
 
 
 
408
            todo = []
 
409
            todo2 = []
 
410
            todo3 = []
 
411
            
835
412
            if order.order_type == 'direct':
836
 
                todo = []
837
 
                todo2 = []
838
 
 
839
413
                for line in order.order_line:
840
414
                    if line.procurement_id: todo.append(line.procurement_id.id)
841
 
 
842
 
                if todo:
843
 
                    todo2 = sale_line_obj.search(cr, uid, [('procurement_id', 'in', todo)], context=context)
844
 
 
845
 
                if todo2:
846
 
                    sm_ids = move_obj.search(cr, uid, [('sale_line_id', 'in', todo2)], context=context)
847
 
                    error_moves = []
848
 
                    for move in move_obj.browse(cr, uid, sm_ids, context=context):
849
 
                        backmove_ids = stock_move_obj.search(cr, uid, [('backmove_id', '=', move.id)])
850
 
                        if move.state == 'done':
851
 
                            error_moves.append(move)
852
 
                        if backmove_ids:
853
 
                            for bmove in move_obj.browse(cr, uid, backmove_ids):
854
 
                                error_moves.append(bmove)
855
 
 
856
 
                    if error_moves:
857
 
                        errors = '''You are trying to confirm a Direct Purchase Order.
858
 
At Direct Purchase Order confirmation, the system tries to change the state of concerning OUT moves but for this DPO, the system has detected
 
415
                    
 
416
            if todo:
 
417
                todo2 = self.pool.get('sale.order.line').search(cr, uid, [('procurement_id', 'in', todo)], context=context)
 
418
            
 
419
            if todo2:
 
420
                sm_ids = move_obj.search(cr, uid, [('sale_line_id', 'in', todo2)], context=context)
 
421
                error_moves = []
 
422
                for move in move_obj.browse(cr, uid, sm_ids, context=context):
 
423
                    backmove_ids = self.pool.get('stock.move').search(cr, uid, [('backmove_id', '=', move.id)])
 
424
                    if move.state == 'done':
 
425
                        error_moves.append(move)
 
426
                    if backmove_ids:
 
427
                        for bmove in move_obj.browse(cr, uid, backmove_ids):
 
428
                            error_moves.append(bmove)
 
429
                        
 
430
                if error_moves:
 
431
                    errors = '''You are trying to confirm a Direct Purchase Order.
 
432
At Direct Purchase Order confirmation, the system tries to change the state of concerning OUT moves but for this DPO, the system has detected 
859
433
stock moves which are already processed : '''
860
 
                        for m in error_moves:
861
 
                            errors = '%s \n %s' % (errors, '''
 
434
                    for m in error_moves:
 
435
                        errors = '%s \n %s' % (errors, '''
862
436
        * 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))
863
 
 
864
 
                        errors = '%s \n %s' % (errors, 'This warning is only for informational purpose. The stock moves already processed will not be modified by this confirmation.')
865
 
 
866
 
                        wiz_id = wiz_obj.create(cr, uid, {'order_id': order.id,
867
 
                                                          'errors': errors})
868
 
                        return {'type': 'ir.actions.act_window',
869
 
                                'res_model': 'purchase.order.confirm.wizard',
870
 
                                'res_id': wiz_id,
871
 
                                'view_type': 'form',
872
 
                                'view_mode': 'form',
873
 
                                'target': 'new'}
874
 
 
 
437
                        
 
438
                    errors = '%s \n %s' % (errors, 'This warning is only for informational purpose. The stock moves already processed will not be modified by this confirmation.')
 
439
                        
 
440
                    wiz_id = self.pool.get('purchase.order.confirm.wizard').create(cr, uid, {'order_id': order.id,
 
441
                                                                                             'errors': errors})
 
442
                    return {'type': 'ir.actions.act_window',
 
443
                            'res_model': 'purchase.order.confirm.wizard',
 
444
                            'res_id': wiz_id,
 
445
                            'view_type': 'form',
 
446
                            'view_mode': 'form',
 
447
                            'target': 'new'}
 
448
            
875
449
            # If no errors, validate the DPO
876
450
            wf_service.trg_validate(uid, 'purchase.order', order.id, 'purchase_confirmed_wait', cr)
877
 
 
 
451
            
878
452
        return True
879
 
 
880
 
    def get_so_ids_from_po_ids(self, cr, uid, ids, context=None, sol_ids=[]):
 
453
    
 
454
    def get_so_ids_from_po_ids(self, cr, uid, ids, context=None):
881
455
        '''
882
456
        receive the list of purchase order ids
883
 
 
 
457
        
884
458
        return the list of sale order ids corresponding (through procurement process)
885
459
        '''
886
460
        # Some verifications
888
462
            context = {}
889
463
        if isinstance(ids, (int, long)):
890
464
            ids = [ids]
891
 
 
 
465
        
892
466
        # objects
893
467
        sol_obj = self.pool.get('sale.order.line')
894
468
        # sale order list
895
469
        so_ids = []
896
 
 
 
470
        
897
471
        # get the sale order lines
898
 
        if not sol_ids:
899
 
            sol_ids = self.get_sol_ids_from_po_ids(cr, uid, ids, context=context)
 
472
        sol_ids = self.get_sol_ids_from_po_ids(cr, uid, ids, context=context)
900
473
        if sol_ids:
901
474
            # list of dictionaries for each sale order line
902
475
            datas = sol_obj.read(cr, uid, sol_ids, ['order_id'], context=context)
904
477
            for data in datas:
905
478
                if data['order_id'] and data['order_id'][0] not in so_ids:
906
479
                    so_ids.append(data['order_id'][0])
907
 
 
908
 
        for po in self.browse(cr, uid, ids, context=context):
909
 
            for line in po.order_line:
910
 
                if line.procurement_id and line.procurement_id.sale_id and line.procurement_id.sale_id.id not in so_ids:
911
 
                    so_ids.append(line.procurement_id.sale_id.id)
912
 
 
913
480
        return so_ids
914
 
 
 
481
    
915
482
    def get_sol_ids_from_po_ids(self, cr, uid, ids, context=None):
916
483
        '''
917
484
        receive the list of purchase order ids
918
 
 
 
485
        
919
486
        return the list of sale order line ids corresponding (through procurement process)
920
487
        '''
921
488
        # Some verifications
923
490
            context = {}
924
491
        if isinstance(ids, (int, long)):
925
492
            ids = [ids]
926
 
 
 
493
            
927
494
        # objects
928
495
        sol_obj = self.pool.get('sale.order.line')
929
496
        # procurement ids list
930
497
        proc_ids = []
931
498
        # sale order lines list
932
499
        sol_ids = []
933
 
 
 
500
        
934
501
        for po in self.browse(cr, uid, ids, context=context):
935
502
            for line in po.order_line:
936
503
                if line.procurement_id:
939
506
        if proc_ids:
940
507
            sol_ids = sol_obj.search(cr, uid, [('procurement_id', 'in', proc_ids)], context=context)
941
508
        return sol_ids
942
 
 
943
 
    # @@@override purchase->purchase.py>purchase_order>wkf_confirm_order
944
 
    def wkf_confirm_order(self, cr, uid, ids, context=None):
945
 
        '''
946
 
        Update the confirmation date of the PO at confirmation.
947
 
        Check analytic distribution.
948
 
        '''
949
 
        # Objects
950
 
        po_line_obj = self.pool.get('purchase.order.line')
951
 
 
952
 
        if context is None:
953
 
            context = {}
954
 
 
955
 
        if isinstance(ids, (int, long)):
956
 
            ids = [ids]
957
 
 
958
 
        todo = []
959
 
 
960
 
        for po in self.browse(cr, uid, ids, context=context):
961
 
            # Check if the pricelist of the order is good according to currency of the partner
962
 
            pricelist_ids = self.pool.get('product.pricelist').search(cr, uid, [('in_search', '=', po.partner_id.partner_type)], context=context)
963
 
            if po.pricelist_id.id not in pricelist_ids:
964
 
                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.'))
965
 
 
966
 
            if not po.split_po and not po.order_line:
967
 
                raise osv.except_osv(_('Error !'), _('You can not validate a purchase order without Purchase Order Lines.'))
968
 
 
969
 
            if po.amount_total == 0:  # UFTP-69
970
 
                raise osv.except_osv(_('Error'), _('You can not validate a purchase order with a total amount of 0.'))
971
 
 
972
 
            for line in po.order_line:
973
 
                if line.state=='draft':
974
 
                    todo.append(line.id)
975
 
 
976
 
            message = _("Purchase order '%s' is validated.") % (po.name,)
977
 
            self.log(cr, uid, po.id, message)
978
 
            # hook for corresponding Fo update
979
 
            self._hook_confirm_order_update_corresponding_so(cr, uid, ids, context=context, po=po)
980
 
 
981
 
        po_line_obj.action_confirm(cr, uid, todo, context)
982
 
 
983
 
        self.write(cr, uid, ids, {'state' : 'confirmed',
984
 
                                  'validator' : uid,
985
 
                                  'date_confirm': strftime('%Y-%m-%d')}, context=context)
986
 
 
987
 
        self.check_analytic_distribution(cr, uid, ids, context=context)
988
 
 
989
 
        return True
990
 
 
 
509
    
991
510
    def common_code_from_wkf_approve_order(self, cr, uid, ids, context=None):
992
511
        '''
993
512
        delivery confirmed date at po level is mandatory
994
513
        update corresponding date at line level if needed.
995
514
        Check analytic distribution
996
 
        Check that no line have a 0 price unit.
997
515
        '''
998
 
        # Objects
999
 
        po_line_obj = self.pool.get('purchase.order.line')
1000
 
 
1001
 
        if context is None:
1002
 
            context = {}
1003
 
 
1004
 
        if isinstance(ids, (int, long)):
1005
 
            ids = [ids]
1006
 
 
 
516
        # objects
 
517
        ana_obj = self.pool.get('analytic.distribution')
 
518
        
1007
519
        # Check analytic distribution
1008
520
        self.check_analytic_distribution(cr, uid, ids, context=context)
1009
521
        for po in self.browse(cr, uid, ids, context=context):
1010
 
            # prepare some values
1011
 
            is_regular = po.order_type == 'regular' # True if order_type is regular, else False
1012
 
            line_error = []
 
522
            # CODE MOVED TO self.check_analytic_distribution()
1013
523
            # msf_order_date checks
1014
524
            if not po.delivery_confirmed_date:
1015
525
                raise osv.except_osv(_('Error'), _('Delivery Confirmed Date is a mandatory field.'))
1016
526
            # for all lines, if the confirmed date is not filled, we copy the header value
1017
 
            lines_to_update = []
1018
527
            for line in po.order_line:
1019
 
                # Don't accept the confirmation of regular PO with 0.00 unit price lines
1020
 
                if is_regular and line.price_unit == 0.00:
1021
 
                    line_error.append(line.line_number)
1022
 
                elif not line.confirmed_delivery_date:
1023
 
                    lines_to_update.append(line.id)
1024
 
 
1025
 
            if len(line_error) > 0:
1026
 
                errors = ' / '.join(str(x) for x in line_error)
1027
 
                raise osv.except_osv(_('Error !'), _('You cannot have a purchase order line with a 0.00 Unit Price. Lines in exception : %s') % errors)
1028
 
 
1029
 
            po_line_obj.write(cr, uid, lines_to_update, {'confirmed_delivery_date': po.delivery_confirmed_date}, context=context)
 
528
                if not line.confirmed_delivery_date:
 
529
                    line.write({'confirmed_delivery_date': po.delivery_confirmed_date,}, context=context)
1030
530
        # MOVE code for COMMITMENT into wkf_approve_order
1031
531
        return True
1032
 
 
1033
 
    def create_extra_lines_on_fo(self, cr, uid, ids, context=None):
1034
 
        '''
1035
 
        Creates FO/IR lines according to PO extra lines
1036
 
        '''
1037
 
        # Objects
1038
 
        sol_obj = self.pool.get('sale.order.line')
1039
 
        so_obj = self.pool.get('sale.order')
1040
 
        ad_obj = self.pool.get('analytic.distribution')
1041
 
 
1042
 
        if context is None:
1043
 
            context = {}
1044
 
 
1045
 
        if isinstance(ids, (int, long)):
1046
 
            ids = [ids]
1047
 
 
1048
 
        lines = []
1049
 
        sol_ids = set()
1050
 
        for order in self.browse(cr, uid, ids, context=context):
1051
 
            for l in order.order_line:
1052
 
                link_so_id = l.link_so_id and l.link_so_id.state in ('sourced', 'progress', 'manual')
1053
 
                if link_so_id and (not l.procurement_id or not l.procurement_id.sale_id):
1054
 
                    lines.append(l)
1055
 
 
1056
 
        for l in lines:
1057
 
            # Copy the AD
1058
 
            new_distrib = False
1059
 
            if l.analytic_distribution_id:
1060
 
                new_distrib = ad_obj.copy(cr, uid, l.analytic_distribution_id.id, {}, context=context)
1061
 
            elif not l.analytic_distribution_id and l.order_id and l.order_id.analytic_distribution_id:
1062
 
                new_distrib = ad_obj.copy(cr, uid, l.order_id.analytic_distribution_id.id, {}, context=context)
1063
 
            # Creates the FO lines
1064
 
            tmp_sale_context = context.get('sale_id')
1065
 
            # create new line in FOXXXX-Y
1066
 
            context['sale_id'] = l.link_so_id.id
1067
 
            vals = {'order_id': l.link_so_id.id,
1068
 
                    'product_id': l.product_id.id,
1069
 
                    'product_uom': l.product_uom.id,
1070
 
                    'product_uom_qty': l.product_qty,
1071
 
                    'price_unit': l.price_unit,
1072
 
                    'procurement_id': l.procurement_id and l.procurement_id.id or False,
1073
 
                    'type': 'make_to_order',
1074
 
                    'analytic_distribution_id': new_distrib,
1075
 
                    'created_by_po': l.order_id.id,
1076
 
                    'created_by_po_line': l.id,
1077
 
                    'name': '[%s] %s' % (l.product_id.default_code, l.product_id.name)}
1078
 
            sol_obj.create(cr, uid, vals, context=context)
1079
 
            # Create new line in FOXXXX (original FO)
1080
 
            if l.link_so_id.original_so_id_sale_order:
1081
 
                context['sale_id'] = l.link_so_id.original_so_id_sale_order.id
1082
 
                vals.update({'order_id': l.link_so_id.original_so_id_sale_order.id,
1083
 
                             'state': 'done'})
1084
 
                sol_obj.create(cr, uid, vals, context=context)
1085
 
            context['sale_id'] = tmp_sale_context
1086
 
 
1087
 
            sol_ids.add(l.link_so_id.id)
1088
 
 
1089
 
        so_obj.action_ship_proc_create(cr, uid, list(sol_ids), context=context)
1090
 
 
1091
 
        return True
1092
 
 
 
532
    
1093
533
    def wkf_confirm_wait_order(self, cr, uid, ids, context=None):
1094
534
        """
1095
535
        Checks:
1096
536
        1/ if all purchase line could take an analytic distribution
1097
537
        2/ if a commitment voucher should be created after PO approbation
1098
 
 
 
538
        
1099
539
        _> originally in purchase.py from analytic_distribution_supply
1100
 
 
 
540
        
1101
541
        Checks if the Delivery Confirmed Date has been filled
1102
 
 
 
542
        
1103
543
        _> originally in order_dates.py from msf_order_date
1104
544
        """
1105
545
        # Some verifications
1107
547
            context = {}
1108
548
        if isinstance(ids, (int, long)):
1109
549
            ids = [ids]
1110
 
 
 
550
        
1111
551
        # objects
1112
552
        sol_obj = self.pool.get('sale.order.line')
1113
 
        so_obj =  self.pool.get('sale.order')
1114
 
 
1115
 
        # Create extra lines on the linked FO/IR
1116
 
        self.create_extra_lines_on_fo(cr, uid, ids, context=context)
1117
 
 
 
553
        
1118
554
        # code from wkf_approve_order
1119
555
        self.common_code_from_wkf_approve_order(cr, uid, ids, context=context)
1120
556
        # set the state of purchase order to confirmed_wait
1121
557
        self.write(cr, uid, ids, {'state': 'confirmed_wait'}, context=context)
1122
 
        sol_ids = self.get_sol_ids_from_po_ids(cr, uid, ids, context=context)
1123
 
 
1124
 
        # corresponding sale order
1125
 
        so_ids = self.get_so_ids_from_po_ids(cr, uid, ids, context=context, sol_ids=sol_ids)
1126
 
        # from so, list corresponding po
1127
 
        all_po_ids = so_obj.get_po_ids_from_so_ids(cr, uid, so_ids, context=context)
1128
 
        list_po_name = ', '.join([linked_po.name for linked_po in self.browse(cr, uid, all_po_ids, context) if linked_po.id != ids[0]])
1129
 
        self.log(cr, uid, ids[0], _("The order %s is in confirmed (waiting) state and will be confirmed once the related orders [%s] would have been confirmed"
1130
 
                                 ) % (self.read(cr, uid, ids, ['name'])[0]['name'], list_po_name))
1131
558
        # sale order lines with modified state
 
559
        sol_ids = self.get_sol_ids_from_po_ids(cr, uid, ids, context=context)
1132
560
        if sol_ids:
1133
561
            sol_obj.write(cr, uid, sol_ids, {'state': 'confirmed'}, context=context)
1134
 
 
 
562
        
1135
563
        # !!BEWARE!! we must update the So lines before any writing to So objects
1136
 
        for po in self.browse(cr, uid, ids, context=context):
 
564
        for po in self.browse(cr, uid, ids, context=context): 
1137
565
            # hook for corresponding Fo update
1138
 
            self._hook_confirm_order_update_corresponding_so(cr, uid, ids, context=context, po=po, so_ids=so_ids)
1139
 
 
 
566
            self._hook_confirm_order_update_corresponding_so(cr, uid, ids, context=context, po=po)
 
567
        
1140
568
        return True
1141
 
 
 
569
    
1142
570
    def compute_confirmed_delivery_date(self, cr, uid, ids, confirmed, prep_lt, ship_lt, est_transport_lead_time, db_date_format, context=None):
1143
571
        '''
1144
572
        compute the confirmed date
1145
 
 
 
573
        
1146
574
        confirmed must be string
1147
575
        return string corresponding to database format
1148
576
        '''
1152
580
        confirmed = confirmed + relativedelta(days=ship_lt or 0)
1153
581
        confirmed = confirmed + relativedelta(days=est_transport_lead_time or 0)
1154
582
        confirmed = confirmed.strftime(db_date_format)
1155
 
 
 
583
        
1156
584
        return confirmed
1157
 
 
 
585
    
1158
586
    def _hook_confirm_order_update_corresponding_so(self, cr, uid, ids, context=None, *args, **kwargs):
1159
587
        '''
1160
588
        Add a hook to update correspondingn so
1164
592
            context = {}
1165
593
        if isinstance(ids, (int, long)):
1166
594
            ids = [ids]
1167
 
 
 
595
            
1168
596
        # objects
1169
597
        po = kwargs['po']
1170
 
        so_ids= kwargs.get('so_ids')
1171
598
        pol_obj = self.pool.get('purchase.order.line')
1172
599
        so_obj = self.pool.get('sale.order')
1173
600
        sol_obj = self.pool.get('sale.order.line')
1174
 
        move_obj = self.pool.get('stock.move')
1175
601
        date_tools = self.pool.get('date.tools')
1176
602
        fields_tools = self.pool.get('fields.tools')
1177
603
        db_date_format = date_tools.get_db_date_format(cr, uid, context=context)
1178
 
 
 
604
        
1179
605
        # update corresponding fo if exist
1180
 
        if so_ids is None:
1181
 
            so_ids = self.get_so_ids_from_po_ids(cr, uid, ids, context=context)
1182
 
        ctx = context.copy()
1183
 
        ctx['no_store_function'] = ['sale.order.line']
1184
 
        store_to_call = []
 
606
        so_ids = self.get_so_ids_from_po_ids(cr, uid, ids, context=context)
1185
607
        if so_ids:
1186
608
            # date values
1187
609
            ship_lt = fields_tools.get_field_from_company(cr, uid, object=self._name, field='shipment_lead_time', context=context)
1188
610
            prep_lt = fields_tools.get_field_from_company(cr, uid, object=self._name, field='preparation_lead_time', context=context)
1189
 
 
 
611
            
1190
612
            for line in po.order_line:
1191
613
                # get the corresponding so line
1192
614
                sol_ids = pol_obj.get_sol_ids_from_pol_ids(cr, uid, [line.id], context=context)
1193
615
                if sol_ids:
1194
 
                    store_to_call += sol_ids
1195
 
 
1196
 
                    sol = sol_obj.browse(cr, uid, sol_ids[0], context=context)
1197
 
                    so = sol.order_id
1198
 
                    # do not update Internal Requests with internal requestor location
1199
 
                    if so and so.procurement_request and so.location_requestor_id.usage != 'customer':
1200
 
                        continue
1201
 
 
 
616
                    # get so_id
 
617
                    data = sol_obj.read(cr, uid, sol_ids, ['order_id'], context=context)
 
618
                    order_id = data[0]['order_id'][0]
 
619
                    # get est_transport_lead_time of corresponding so
 
620
                    data = so_obj.read(cr, uid, order_id, ['est_transport_lead_time'], context=context)
 
621
                    est_transport_lead_time = data['est_transport_lead_time']
 
622
                    
1202
623
                    line_confirmed = False
1203
624
                    # compute confirmed date for line
1204
625
                    if line.confirmed_delivery_date:
1205
626
                        line_confirmed = self.compute_confirmed_delivery_date(cr, uid, ids, line.confirmed_delivery_date,
1206
 
                                                                              prep_lt, ship_lt, so.est_transport_lead_time,
 
627
                                                                              prep_lt, ship_lt, est_transport_lead_time,
1207
628
                                                                              db_date_format, context=context)
1208
 
 
1209
629
                    # we update the corresponding sale order line
 
630
                    sol = sol_obj.browse(cr, uid, sol_ids[0], context=context)
 
631
                    # do not update Internal Requests
 
632
                    if sol.order_id.procurement_request:
 
633
                        continue
1210
634
                    # {sol: pol}
1211
635
                    # compute the price_unit value - we need to specify the date
1212
636
                    date_context = {'date': po.date_order}
1213
 
 
1214
637
                    # convert from currency of pol to currency of sol
1215
638
                    price_unit_converted = self.pool.get('res.currency').compute(cr, uid, line.currency_id.id,
1216
639
                                                                                 sol.currency_id.id, line.price_unit or 0.0,
1217
 
                                                                                 round=False, context=date_context)
 
640
                                                                                 round=True, context=date_context)
1218
641
                    fields_dic = {'product_id': line.product_id and line.product_id.id or False,
1219
642
                                  'name': line.name,
1220
643
                                  'default_name': line.default_name,
1240
663
                                  'confirmed_delivery_date': line_confirmed,
1241
664
                                  }
1242
665
                    # write the line
1243
 
                    sol_obj.write(cr, uid, sol_ids, fields_dic, context=ctx)
1244
 
 
1245
 
                    if so.procurement_request and so.location_requestor_id.usage == 'customer' \
1246
 
                                              and line.procurement_id.move_id \
1247
 
                                              and not line.procurement_id.move_id.processed_stock_move:
1248
 
                        out_move_id = line.procurement_id.move_id
1249
 
                        if out_move_id.state == 'assigned':
1250
 
                            move_obj.cancel_assign(cr, uid, [out_move_id.id])
1251
 
                        elif out_move_id.state in ('cancel', 'done'):
1252
 
                            continue
1253
 
                        else:
1254
 
                            move_dic = {'product_id': line.product_id and line.product_id.id or False,
1255
 
                                        'name': line.name,
1256
 
                                        'product_uom': line.product_uom and line.product_uom.id or False,
1257
 
                                        'product_uos': line.product_uom and line.product_uom.id or False,
1258
 
                                        'product_qty': line.product_qty,
1259
 
                                        'product_uos_qty': line.product_qty,}
1260
 
                            move_obj.write(cr, uid, [out_move_id.id], move_dic, context=context)
1261
 
 
1262
 
            if store_to_call:
1263
 
                sol_obj._call_store_function(cr, uid, store_to_call, keys=None, bypass=False, context=context)
 
666
                    sol_obj.write(cr, uid, sol_ids, fields_dic, context=context)
 
667
            
1264
668
            # compute so dates -- only if we get a confirmed value, because rts is mandatory on So side
1265
669
            # update after lines update, as so write triggers So workflow, and we dont want the Out document
1266
670
            # to be created with old So datas
1270
674
                    delivery_confirmed_date = datetime.strptime(po.delivery_confirmed_date, db_date_format)
1271
675
                    so_rts = delivery_confirmed_date + relativedelta(days=prep_lt or 0)
1272
676
                    so_rts = so_rts.strftime(db_date_format)
1273
 
 
 
677
                
1274
678
                    # Fo confirmed date = confirmed date + prep_lt + ship_lt + transport_lt
1275
679
                    so_confirmed = self.compute_confirmed_delivery_date(cr, uid, ids, po.delivery_confirmed_date,
1276
680
                                                                        prep_lt, ship_lt, so.est_transport_lead_time,
1278
682
                    # write data to so
1279
683
                    so_obj.write(cr, uid, [so.id], {'delivery_confirmed_date': so_confirmed,
1280
684
                                                   'ready_to_ship_date': so_rts}, context=context)
1281
 
        return True
1282
 
 
1283
 
    def check_if_product(self, cr, uid, ids, context=None):
1284
 
        """
1285
 
        Check if all line have a product before confirming the Purchase Order
1286
 
        """
1287
 
        if isinstance(ids, (int, long)):
1288
 
            ids = [ids]
1289
 
 
1290
 
        for po in self.browse(cr, uid, ids, context=context):
1291
 
            if po.order_line:
1292
 
                for line in po.order_line:
1293
 
                    if not line.product_id:
1294
 
                        raise osv.except_osv(_('Error !'), _('You should have a product on all Purchase Order lines to be able to confirm the Purchase Order.') )
1295
 
        return True
1296
 
 
 
685
            
 
686
        return True
 
687
    
1297
688
    def all_po_confirmed(self, cr, uid, ids, context=None):
1298
689
        '''
1299
690
        condition for the po to leave the act_confirmed_wait state
1300
 
 
 
691
        
1301
692
        if the po is from scratch (no procurement), or from replenishment mechanism (procurement but no sale order line)
1302
693
        the method will return True and therefore the po workflow is not blocked
1303
 
 
 
694
        
1304
695
        only 'make_to_order' sale order lines are checked, we dont care on state of 'make_to_stock' sale order line
1305
696
        _> anyway, thanks to Fo split, make_to_stock and make_to_order so lines are separated in different sale orders
1306
697
        '''
1309
700
            context = {}
1310
701
        if isinstance(ids, (int, long)):
1311
702
            ids = [ids]
1312
 
 
 
703
        
1313
704
        # objects
1314
705
        sol_obj = self.pool.get('sale.order.line')
1315
706
        so_obj = self.pool.get('sale.order')
1316
 
 
 
707
        
1317
708
        # corresponding sale order
1318
709
        so_ids = self.get_so_ids_from_po_ids(cr, uid, ids, context=context)
1319
710
        # from so, list corresponding po
1320
711
        all_po_ids = so_obj.get_po_ids_from_so_ids(cr, uid, so_ids, context=context)
1321
 
 
1322
712
        # from listed po, list corresponding so
1323
713
        all_so_ids = self.get_so_ids_from_po_ids(cr, uid, all_po_ids, context=context)
1324
714
        # if we have sol_ids, we are treating a po which is make_to_order from sale order
1333
723
            all_sol_not_confirmed_ids = sol_obj.search(cr, uid, [('order_id', 'in', all_so_ids),
1334
724
                                                                 ('type', '=', 'make_to_order'),
1335
725
                                                                 ('product_id', '!=', False),
1336
 
                                                                 ('procurement_id.state', '!=', 'cancel'),
1337
726
                                                                 ('state', 'not in', ['confirmed', 'done'])], context=context)
1338
727
            # if any lines exist, we return False
1339
728
            if all_sol_not_confirmed_ids:
1340
729
                return False
1341
 
 
 
730
            
1342
731
        return True
1343
 
 
 
732
    
1344
733
    def wkf_confirm_trigger(self, cr, uid, ids, context=None):
1345
734
        '''
1346
735
        trigger corresponding so then po
1350
739
            context = {}
1351
740
        if isinstance(ids, (int, long)):
1352
741
            ids = [ids]
1353
 
 
 
742
            
1354
743
        # objects
 
744
        sol_obj = self.pool.get('sale.order.line')
1355
745
        so_obj = self.pool.get('sale.order')
 
746
        proc_obj = self.pool.get('procurement.order')
1356
747
        wf_service = netsvc.LocalService("workflow")
1357
 
 
 
748
        
1358
749
        # corresponding sale order
1359
750
        so_ids = self.get_so_ids_from_po_ids(cr, uid, ids, context=context)
1360
751
        # from so, list corresponding po first level
1363
754
        all_so_ids = self.get_so_ids_from_po_ids(cr, uid, all_po_ids, context=context)
1364
755
        # from all so, list all corresponding po second level
1365
756
        all_po_for_all_so_ids = so_obj.get_po_ids_from_so_ids(cr, uid, all_so_ids, context=context)
1366
 
 
 
757
        
1367
758
        # we trigger all the corresponding sale order -> test_lines is called on these so
1368
759
        for so_id in all_so_ids:
1369
760
            wf_service.trg_write(uid, 'sale.order', so_id, cr)
1370
 
 
 
761
            
1371
762
        # we trigger pos of all sale orders -> all_po_confirm is called on these po
1372
763
        for po_id in all_po_for_all_so_ids:
1373
764
            wf_service.trg_write(uid, 'purchase.order', po_id, cr)
1374
 
 
 
765
        
1375
766
        return True
1376
 
 
 
767
    
1377
768
    def wkf_approve_order(self, cr, uid, ids, context=None):
1378
769
        '''
1379
770
        Checks if the invoice should be create from the purchase order
1382
773
        '''
1383
774
        line_obj = self.pool.get('purchase.order.line')
1384
775
        move_obj = self.pool.get('stock.move')
1385
 
        uf_config = self.pool.get('unifield.setup.configuration')
1386
776
        wf_service = netsvc.LocalService("workflow")
1387
 
 
 
777
        
1388
778
        if isinstance(ids, (int, long)):
1389
779
            ids = [ids]
1390
 
 
 
780
        
1391
781
        # duplicated code with wkf_confirm_wait_order because of backward compatibility issue with yml tests for dates,
1392
782
        # which doesnt execute wkf_confirm_wait_order (null value in column "date_expected" violates not-null constraint for stock.move otherwise)
1393
783
        # msf_order_date checks
1394
784
        self.common_code_from_wkf_approve_order(cr, uid, ids, context=context)
1395
785
 
1396
 
        setup = uf_config.get_config(cr, uid)
1397
 
 
1398
786
        for order in self.browse(cr, uid, ids):
1399
 
            if order.amount_total == 0:  # UFTP-69
1400
 
                # total amount could be set to 0 after it was Validated
1401
 
                # or no lines
1402
 
                # (after wkf_confirm_order total amount check)
1403
 
                raise osv.except_osv(_('Error'), _('You can not confirm a purchase order with a total amount of 0.'))
1404
 
 
1405
787
            # Create commitments for each PO only if po is "from picking"
1406
 
            # UTP-114: No Commitment Voucher on PO that are 'purchase_list'!
1407
 
            if (order.invoice_method in ['picking', 'order'] and not order.from_yml_test and order.order_type not in ['in_kind', 'purchase_list'] and order.partner_id.partner_type != 'intermission') or (order.invoice_method == 'manual' and order.order_type == 'direct' and order.partner_id.partner_type == 'esc'):
1408
 
                # UTP-827: no commitment if they are imported for ESC partners
1409
 
                if not (order.partner_id.partner_type == 'esc' and setup.import_commitments):
1410
 
                    self.action_create_commitment(cr, uid, [order.id], order.partner_id and order.partner_id.partner_type, context=context)
 
788
            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':
 
789
                self.action_create_commitment(cr, uid, [order.id], order.partner_id and order.partner_id.partner_type, context=context)
 
790
            # Don't accept the confirmation of regular PO with 0.00 unit price lines
 
791
            if order.order_type == 'regular':
 
792
                line_error = []
 
793
                for line in order.order_line:
 
794
                    if line.price_unit == 0.00:
 
795
                        line_error.append(line.line_number)
 
796
 
 
797
                if len(line_error) > 0:
 
798
                    errors = ' / '.join(str(x) for x in line_error)
 
799
                    raise osv.except_osv(_('Error !'), _('You cannot have a purchase order line with a 0.00 Unit Price. Lines in exception : %s') % errors)
 
800
 
1411
801
            todo = []
1412
802
            todo2 = []
1413
803
            todo3 = []
1414
 
            todo4 = {}
1415
 
            if order.partner_id.partner_type in ('internal', 'esc') and order.order_type == 'regular' or \
 
804
            if order.partner_id.partner_type == 'internal' and order.order_type == 'regular' or \
1416
805
                         order.order_type in ['donation_exp', 'donation_st', 'loan']:
1417
806
                self.write(cr, uid, [order.id], {'invoice_method': 'manual'})
1418
807
                line_obj.write(cr, uid, [x.id for x in order.order_line], {'invoiced': 1})
1419
808
 
1420
809
            message = _("Purchase order '%s' is confirmed.") % (order.name,)
1421
810
            self.log(cr, uid, order.id, message)
1422
 
 
 
811
            
1423
812
            if order.order_type == 'direct':
1424
 
                if order.partner_id.partner_type != 'esc':
1425
 
                    self.write(cr, uid, [order.id], {'invoice_method': 'order'}, context=context)
 
813
                self.write(cr, uid, [order.id], {'invoice_method': 'order'}, context=context)
1426
814
                for line in order.order_line:
1427
 
                    if line.procurement_id:
1428
 
                        todo.append(line.procurement_id.id)
1429
 
                        todo4.update({line.procurement_id.id: line.id})
1430
 
 
 
815
                    if line.procurement_id: todo.append(line.procurement_id.id)
 
816
                    
1431
817
            if todo:
1432
818
                todo2 = self.pool.get('sale.order.line').search(cr, uid, [('procurement_id', 'in', todo)], context=context)
1433
 
 
 
819
            
1434
820
            if todo2:
1435
821
                sm_ids = move_obj.search(cr, uid, [('sale_line_id', 'in', todo2)], context=context)
1436
822
                self.pool.get('stock.move').action_confirm(cr, uid, sm_ids, context=context)
1437
823
                stock_location_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', 'stock_location_stock')[1]
1438
 
                cross_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_cross_docking', 'stock_location_cross_docking')[1]
1439
 
                non_stock_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock_override', 'stock_location_non_stockable')[1]
1440
824
                for move in move_obj.browse(cr, uid, sm_ids, context=context):
1441
 
                    # Reset the location_id to Stock
1442
 
                    location_id = stock_location_id
1443
825
                    # Search if this move has been processed
1444
826
                    backmove_ids = self.pool.get('stock.move').search(cr, uid, [('backmove_id', '=', move.id)])
1445
827
                    if move.state != 'done' and not backmove_ids and not move.backmove_id:
1446
 
                        if move.product_id.type in ('service', 'service_recep'):
1447
 
                            location_id = cross_id
1448
 
                        elif move.product_id.type == 'consu':
1449
 
                            location_id = non_stock_id
1450
 
                        move_obj.write(cr, uid, [move.id], {'dpo_id': order.id,
1451
 
                                                            'state': 'done',
1452
 
                                                            'dpo_line_id': todo4.get(move.sale_line_id.procurement_id.id, False),
1453
 
                                                            'location_id': location_id,
1454
 
                                                            'location_dest_id': location_id,
1455
 
                                                            'date': strftime('%Y-%m-%d %H:%M:%S')}, context=context)
 
828
                        move_obj.write(cr, uid, sm_ids, {'dpo_id': order.id, 'state': 'done',
 
829
                                                         'location_id': stock_location_id,
 
830
                                                         'location_dest_id': stock_location_id, 
 
831
                                                         'date': time.strftime('%Y-%m-%d %H:%M:%S')}, context=context)
1456
832
                        wf_service.trg_trigger(uid, 'stock.move', move.id, cr)
1457
 
                        if move.picking_id:
 
833
                        if move.picking_id: 
1458
834
                            all_move_closed = True
1459
835
                            # Check if the picking should be updated
1460
836
                            if move.picking_id.subtype == 'picking':
1464
840
                            # If all stock moves of the picking is done, trigger the workflow
1465
841
                            if all_move_closed:
1466
842
                                todo3.append(move.picking_id.id)
1467
 
 
 
843
                
1468
844
            if todo3:
1469
845
                for pick_id in todo3:
1470
846
                    wf_service.trg_validate(uid, 'stock.picking', pick_id, 'button_confirm', cr)
1471
847
                    wf_service.trg_write(uid, 'stock.picking', pick_id, cr)
1472
 
 
1473
 
        # @@@override@purchase.purchase.order.wkf_approve_order
1474
 
        self.write(cr, uid, ids, {'state': 'approved', 'date_approve': strftime('%Y-%m-%d')})
1475
 
        return True
1476
 
 
1477
 
    def need_counterpart(self, cr, uid, ids, context=None):
1478
 
        res = False
1479
 
        for po in self.browse(cr, uid, ids, context=context):
1480
 
            if po.order_type == 'loan' and not po.loan_id and not po.is_a_counterpart and po.partner_id.partner_type not in ('internal', 'intermission'):
1481
 
                res = True
1482
 
 
1483
 
        return res
1484
 
 
1485
 
    def go_to_loan_done(self, cr, uid, ids, context=None):
1486
 
        res = False
1487
 
        for po in self.browse(cr, uid, ids, context=context):
1488
 
            if po.order_type not in ('loan', 'direct') or po.loan_id or (po.order_type == 'loan' and po.partner_id.partner_type in ('internal', 'intermission')):
1489
 
                res = True
1490
 
 
1491
 
        return res
1492
 
 
 
848
            
 
849
        return super(purchase_order, self).wkf_approve_order(cr, uid, ids, context=context)
 
850
    
1493
851
    def action_sale_order_create(self, cr, uid, ids, context=None):
1494
852
        '''
1495
853
        Create a sale order as counterpart for the loan.
1498
856
            ids = [ids]
1499
857
        if context is None:
1500
858
            context = {}
1501
 
 
 
859
            
1502
860
        sale_obj = self.pool.get('sale.order')
1503
861
        sale_line_obj = self.pool.get('sale.order.line')
1504
862
        sale_shop = self.pool.get('sale.shop')
1505
863
        partner_obj = self.pool.get('res.partner')
1506
 
 
 
864
            
1507
865
        for order in self.browse(cr, uid, ids):
1508
 
            if order.is_a_counterpart or (order.order_type == 'loan' and order.partner_id.partner_type in ('internal', 'intermission')):
1509
 
                # UTP-392: This PO is created by the synchro from a Loan FO of internal/intermission partner, so do not generate the counterpart FO
1510
 
                return
1511
 
 
1512
866
            loan_duration = Parser.DateFromString(order.minimum_planned_date) + RelativeDateTime(months=+order.loan_duration)
1513
867
            # from yml test is updated according to order value
1514
868
            values = {'shop_id': sale_shop.search(cr, uid, [])[0],
1525
879
                      'categ': order.categ,
1526
880
                      'priority': order.priority,
1527
881
                      'from_yml_test': order.from_yml_test,
1528
 
                      'is_a_counterpart': True,
1529
882
                      }
1530
883
            order_id = sale_obj.create(cr, uid, values, context=context)
1531
884
            for line in order.order_line:
1539
892
                                               'name': line.name,
1540
893
                                               'type': line.product_id.procure_method})
1541
894
            self.write(cr, uid, [order.id], {'loan_id': order_id})
1542
 
 
 
895
            
1543
896
            sale = sale_obj.browse(cr, uid, order_id)
1544
 
            message = _("Loan counterpart '%s' has been created and validated. Please confirm it.") % (sale.name,)
1545
 
 
 
897
            
 
898
            message = _("Loan counterpart '%s' has been created.") % (sale.name,)
 
899
            
1546
900
            sale_obj.log(cr, uid, order_id, message)
1547
 
 
 
901
        
1548
902
        return order_id
1549
 
 
 
903
    
1550
904
    def has_stockable_product(self,cr, uid, ids, *args):
1551
905
        '''
1552
906
        Override the has_stockable_product to return False
1557
911
        for order in self.browse(cr, uid, ids):
1558
912
            if order.order_type != 'direct':
1559
913
                return super(purchase_order, self).has_stockable_product(cr, uid, ids, args)
 
914
        
1560
915
        return False
1561
 
 
 
916
    
1562
917
    def action_invoice_create(self, cr, uid, ids, *args):
1563
918
        '''
1564
919
        Override this method to check the purchase_list box on invoice
1567
922
        '''
1568
923
        invoice_id = super(purchase_order, self).action_invoice_create(cr, uid, ids, args)
1569
924
        invoice_obj = self.pool.get('account.invoice')
1570
 
        inkind_journal_ids = self.pool.get('account.journal').search(cr, uid, [
1571
 
                    ("type", "=", "inkind"),
1572
 
                    ('is_current_instance', '=', True)
1573
 
                ])
 
925
        inkind_journal_ids = self.pool.get('account.journal').search(cr, uid, [("type", "=", "inkind")])
1574
926
 
1575
927
        for order in self.browse(cr, uid, ids):
1576
928
            if order.order_type == 'purchase_list':
1581
933
                invoice_obj.write(cr, uid, [invoice_id], {'journal_id': inkind_journal_ids[0], 'is_inkind_donation': True})
1582
934
 
1583
935
        return invoice_id
1584
 
 
 
936
    
1585
937
    def _hook_action_picking_create_modify_out_source_loc_check(self, cr, uid, ids, context=None, *args, **kwargs):
1586
938
        '''
1587
939
        Please copy this to your module's method also.
1588
940
        This hook belongs to the action_picking_create method from purchase>purchase.py>purchase_order class
1589
 
 
 
941
        
1590
942
        - allow to choose whether or not the source location of the corresponding outgoing stock move should
1591
943
        match the destination location of incoming stock move
1592
944
        '''
1593
945
        order_line = kwargs['order_line']
1594
946
        # by default, we change the destination stock move if the destination stock move exists
1595
947
        return order_line.move_dest_id
1596
 
 
 
948
    
 
949
    def _hook_action_picking_create_stock_picking(self, cr, uid, ids, context=None, *args, **kwargs):
 
950
        '''
 
951
        modify data for stock move creation
 
952
        '''
 
953
        move_values = kwargs['move_values']
 
954
        return move_values
 
955
    
1597
956
    # @@@override@purchase.purchase.order.action_picking_create
1598
957
    def action_picking_create(self,cr, uid, ids, context=None, *args):
1599
 
        if context is None:
1600
 
            context = {}
1601
 
        move_obj = self.pool.get('stock.move')
1602
958
        picking_id = False
1603
959
        for order in self.browse(cr, uid, ids):
1604
 
            moves_to_update = []
1605
960
            loc_id = order.partner_id.property_stock_supplier.id
1606
961
            istate = 'none'
1607
962
            reason_type_id = False
1608
963
            if order.invoice_method=='picking':
1609
964
                istate = '2binvoiced'
1610
 
 
 
965
                
1611
966
            pick_name = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.in')
1612
967
            picking_values = {
1613
968
                'name': pick_name,
1620
975
                'company_id': order.company_id.id,
1621
976
                'move_lines' : [],
1622
977
            }
1623
 
 
1624
 
            if order.order_type in ('regular', 'purchase_list', 'direct') and order.partner_id.partner_type in ('internal', 'intermission', 'section', 'esc'):
1625
 
                reason_type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_internal_supply')[1]
1626
 
            elif order.order_type in ('regular', 'purchase_list', 'direct'):
 
978
            
 
979
            if order.order_type in ('regular', 'purchase_list', 'direct'):
1627
980
                reason_type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_external_supply')[1]
1628
981
            if order.order_type == 'loan':
1629
982
                reason_type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_loan')[1]
1633
986
                reason_type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_donation_expiry')[1]
1634
987
            if order.order_type == 'in_kind':
1635
988
                reason_type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_in_kind_donation')[1]
1636
 
 
 
989
                
1637
990
            if reason_type_id:
1638
991
                picking_values.update({'reason_type_id': reason_type_id})
1639
 
 
 
992
            
1640
993
            picking_id = self.pool.get('stock.picking').create(cr, uid, picking_values, context=context)
1641
994
            todo_moves = []
1642
995
            for order_line in order.order_line:
1643
 
                # Reload the data of the line because if the line comes from an ISR and it's a duplicate line,
1644
 
                # the move_dest_id field has been changed by the _hook_action_picking_create_modify_out_source_loc_check method
1645
 
                order_line = self.pool.get('purchase.order.line').browse(cr, uid, order_line.id, context=context)
1646
996
                if not order_line.product_id:
1647
997
                    continue
1648
 
                dest = order.location_id.id
1649
 
                # service with reception are directed to Service Location
1650
 
                if order_line.product_id.type == 'service_recep' and not order.cross_docking_ok:
1651
 
                    dest = self.pool.get('stock.location').get_service_location(cr, uid)
1652
 
 
1653
 
                move_values = {
1654
 
                    'name': order.name + ': ' +(order_line.name or ''),
1655
 
                    'product_id': order_line.product_id.id,
1656
 
                    'product_qty': order_line.product_qty,
1657
 
                    'product_uos_qty': order_line.product_qty,
1658
 
                    'product_uom': order_line.product_uom.id,
1659
 
                    'product_uos': order_line.product_uom.id,
1660
 
                    'date': order_line.date_planned,
1661
 
                    'date_expected': order_line.date_planned,
1662
 
                    'location_id': loc_id,
1663
 
                    'location_dest_id': dest,
1664
 
                    'picking_id': picking_id,
1665
 
                    'move_dest_id': order_line.move_dest_id.id,
1666
 
                    'state': 'draft',
1667
 
                    'purchase_line_id': order_line.id,
1668
 
                    'company_id': order.company_id.id,
1669
 
                    'price_currency_id': order.pricelist_id.currency_id.id,
1670
 
                    'price_unit': order_line.price_unit,
1671
 
                    'date': order_line.confirmed_delivery_date,
1672
 
                    'date_expected': order_line.confirmed_delivery_date,
1673
 
                    'line_number': order_line.line_number,
1674
 
                }
1675
 
 
1676
 
                if reason_type_id:
1677
 
                    move_values.update({'reason_type_id': reason_type_id})
1678
 
 
1679
 
                ctx = context.copy()
1680
 
                ctx['bypass_store_function'] = [('stock.picking', ['dpo_incoming', 'dpo_out', 'overall_qty', 'line_state'])]
1681
 
                move = self.pool.get('stock.move').create(cr, uid, move_values, context=ctx)
1682
 
                if self._hook_action_picking_create_modify_out_source_loc_check(cr, uid, ids, context=context, order_line=order_line, move_id=move):
1683
 
                    moves_to_update.append(order_line.move_dest_id.id)
1684
 
                todo_moves.append(move)
1685
 
            # compute function fields
1686
 
            if todo_moves:
1687
 
                compute_store = self.pool.get('stock.move')._store_get_values(cr, uid, todo_moves, None, context)
1688
 
                compute_store.sort()
1689
 
                done = []
1690
 
                for _, store_object, store_ids, store_fields2 in compute_store:
1691
 
                    if store_fields2 in ('dpo_incoming', 'dpo_out', 'overall_qty', 'line_state') and not (store_object, store_ids, store_fields2) in done:
1692
 
                        self.pool.get(store_object)._store_set_values(cr, uid, store_ids, store_fields2, context)
1693
 
                        done.append((store_object, store_ids, store_fields2))
1694
 
            move_obj.write(cr, uid, moves_to_update, {'location_id':order.location_id.id})
1695
 
            move_obj.action_confirm(cr, uid, todo_moves)
1696
 
            move_obj.force_assign(cr, uid, todo_moves)
 
998
                if order_line.product_id.product_tmpl_id.type in ('product', 'consu', 'service_recep',):
 
999
                    dest = order.location_id.id
 
1000
                    # service with reception are directed to Service Location
 
1001
                    if order_line.product_id.product_tmpl_id.type == 'service_recep':
 
1002
                        service_loc = self.pool.get('stock.location').search(cr, uid, [('service_location', '=', True)], context=context)
 
1003
                        if service_loc:
 
1004
                            dest = service_loc[0]
 
1005
                            
 
1006
                    move_values = {
 
1007
                        'name': order.name + ': ' +(order_line.name or ''),
 
1008
                        'product_id': order_line.product_id.id,
 
1009
                        'product_qty': order_line.product_qty,
 
1010
                        'product_uos_qty': order_line.product_qty,
 
1011
                        'product_uom': order_line.product_uom.id,
 
1012
                        'product_uos': order_line.product_uom.id,
 
1013
                        'date': order_line.date_planned,
 
1014
                        'date_expected': order_line.date_planned,
 
1015
                        'location_id': loc_id,
 
1016
                        'location_dest_id': dest,
 
1017
                        'picking_id': picking_id,
 
1018
                        'move_dest_id': order_line.move_dest_id.id,
 
1019
                        'state': 'draft',
 
1020
                        'purchase_line_id': order_line.id,
 
1021
                        'company_id': order.company_id.id,
 
1022
                        'price_currency_id': order.pricelist_id.currency_id.id,
 
1023
                        'price_unit': order_line.price_unit
 
1024
                    }
 
1025
                    # hook for stock move values modification
 
1026
                    move_values = self._hook_action_picking_create_stock_picking(cr, uid, ids, context=context, move_values=move_values, order_line=order_line,)
 
1027
                    
 
1028
                    if reason_type_id:
 
1029
                        move_values.update({'reason_type_id': reason_type_id})
 
1030
                    
 
1031
                    move = self.pool.get('stock.move').create(cr, uid, move_values, context=context)
 
1032
                    if self._hook_action_picking_create_modify_out_source_loc_check(cr, uid, ids, context=context, order_line=order_line, move_id=move):
 
1033
                        self.pool.get('stock.move').write(cr, uid, [order_line.move_dest_id.id], {'location_id':order.location_id.id})
 
1034
                    todo_moves.append(move)
 
1035
            self.pool.get('stock.move').action_confirm(cr, uid, todo_moves)
 
1036
            self.pool.get('stock.move').force_assign(cr, uid, todo_moves)
1697
1037
            wf_service = netsvc.LocalService("workflow")
1698
1038
            wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
1699
1039
        return picking_id
1700
1040
        # @@@end
1701
1041
 
1702
 
    def _get_location_id(self, cr, uid, vals, warehouse_id=False, context=None):
1703
 
        """
1704
 
        Get the location_id according to the cross_docking_ok option
1705
 
        Return vals
1706
 
        """
1707
 
        if 'cross_docking_ok' not in vals:
1708
 
            return vals
1709
 
 
1710
 
        if not warehouse_id:
1711
 
            warehouse_id = self.pool.get('stock.warehouse').search(cr, uid, [], context=context)[0]
1712
 
 
1713
 
        if not vals.get('cross_docking_ok', False):
1714
 
            vals.update({'location_id': self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id, context=context).lot_input_id.id})
1715
 
        elif vals.get('cross_docking_ok', False):
1716
 
            vals.update({'location_id': self.pool.get('stock.location').get_cross_docking_location(cr, uid)})
1717
 
 
1718
 
        return vals
1719
 
 
1720
 
 
1721
1042
    def create(self, cr, uid, vals, context=None):
1722
1043
        """
1723
1044
        Filled in 'from_yml_test' to True if we come from tests
1724
 
        # UTP-114 demands purchase_list PO to be 'from picking'.
1725
1045
        """
1726
1046
        if not context:
1727
1047
            context = {}
1728
 
 
 
1048
        if context.get('update_mode') in ['init', 'update'] and 'from_yml_test' not in vals:
 
1049
            logging.getLogger('init').info('PO: set from yml test to True')
 
1050
            vals['from_yml_test'] = True
 
1051
            
1729
1052
        if vals.get('order_type'):
1730
1053
            if vals.get('order_type') in ['donation_exp', 'donation_st', 'loan']:
1731
1054
                vals.update({'invoice_method': 'manual'})
1732
 
            elif vals.get('order_type') in ['direct']:
 
1055
            elif vals.get('order_type') in ['direct', 'purchase_list']:
1733
1056
                vals.update({'invoice_method': 'order'})
1734
 
                if vals.get('partner_id'):
1735
 
                    if self.pool.get('res.partner').browse(cr, uid, vals.get('partner_id'), context=context).partner_type == 'esc':
1736
 
                        vals.update({'invoice_method': 'manual'})
1737
1057
            else:
1738
1058
                vals.update({'invoice_method': 'picking'})
1739
 
 
 
1059
            
1740
1060
        if 'partner_id' in vals:
1741
1061
            self._check_user_company(cr, uid, vals['partner_id'], context=context)
1742
 
        # we need to update the location_id because it is readonly and so does not pass in the vals of create and write
1743
 
        vals = self._get_location_id(cr, uid, vals, warehouse_id=vals.get('warehouse_id', False), context=context)
1744
 
 
1745
 
        res = super(purchase_order, self).create(cr, uid, vals, context=context)
1746
 
        self._check_service(cr, uid, [res], vals, context=context)
1747
 
 
1748
 
        return res
 
1062
    
 
1063
        return super(purchase_order, self).create(cr, uid, vals, context=context)
1749
1064
 
1750
1065
    def wkf_action_cancel_po(self, cr, uid, ids, context=None):
1751
1066
        """
1756
1071
            context = {}
1757
1072
        if isinstance(ids, (int, long)):
1758
1073
            ids = [ids]
1759
 
 
1760
 
        wf_service = netsvc.LocalService("workflow")
1761
 
 
1762
 
        line_ids = []
1763
 
        for order in self.browse(cr, uid, ids, context=context):
1764
 
            for line in order.order_line:
1765
 
                line_ids.append(line.id)
1766
 
                if line.procurement_id and line.procurement_id.move_id:
1767
 
                    self.pool.get('stock.move').write(cr, uid, line.procurement_id.move_id.id, {'state': 'cancel'}, context=context)
1768
 
                    if line.procurement_id.move_id.picking_id:
1769
 
                        wf_service.trg_write(uid, 'stock.picking', line.procurement_id.move_id.picking_id.id, cr)
1770
 
 
1771
 
        self.pool.get('purchase.order.line').cancel_sol(cr, uid, line_ids, context=context)
1772
 
 
1773
1074
        return self.write(cr, uid, ids, {'state':'cancel'}, context=context)
1774
1075
 
1775
 
    def wkf_confirm_cancel(self, cr, uid, ids, context=None):
1776
 
        """
1777
 
        Continue the workflow if all other POs are confirmed
1778
 
        """
1779
 
        wf_service = netsvc.LocalService("workflow")
1780
 
        so_obj = self.pool.get('sale.order')
1781
 
 
1782
 
        # corresponding sale order
1783
 
        so_ids = self.get_so_ids_from_po_ids(cr, uid, ids, context=context)
1784
 
        # from so, list corresponding po first level
1785
 
        all_po_ids = so_obj.get_po_ids_from_so_ids(cr, uid, so_ids, context=context)
1786
 
        # from listed po, list corresponding so
1787
 
        all_so_ids = self.get_so_ids_from_po_ids(cr, uid, all_po_ids, context=context)
1788
 
        # from all so, list all corresponding po second level
1789
 
        all_po_for_all_so_ids = so_obj.get_po_ids_from_so_ids(cr, uid, all_so_ids, context=context)
1790
 
 
1791
 
        # we trigger all the corresponding sale order -> test_lines is called on these so
1792
 
        for so_id in all_so_ids:
1793
 
            wf_service.trg_write(uid, 'sale.order', so_id, cr)
1794
 
 
1795
 
        # we trigger pos of all sale orders -> all_po_confirm is called on these po
1796
 
        for po_id in all_po_for_all_so_ids:
1797
 
            wf_service.trg_write(uid, 'purchase.order', po_id, cr)
1798
 
 
1799
 
        return True
1800
 
 
1801
 
 
1802
1076
    def action_done(self, cr, uid, ids, context=None):
1803
1077
        """
1804
1078
        Done activity in workflow.
1842
1116
                loan_context.update({'loan_id': order.id})
1843
1117
                self.pool.get('sale.order').set_manually_done(cr, uid, order.loan_id.id, all_doc=all_doc, context=loan_context)
1844
1118
 
 
1119
            # Done invoices
 
1120
            invoice_error_ids = []
 
1121
            for invoice in order.invoice_ids:
 
1122
                if invoice.state == 'draft':
 
1123
                    wf_service.trg_validate(uid, 'account.invoice', invoice.id, 'invoice_cancel', cr)
 
1124
                elif invoice.state not in ('cancel', 'done'):
 
1125
                    invoice_error_ids.append(invoice.id)
 
1126
 
 
1127
            if invoice_error_ids:
 
1128
                invoices_ref = ' / '.join(x.number for x in self.pool.get('account.invoice').browse(cr, uid, invoice_error_ids, context=context))
 
1129
                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.' \
 
1130
                                'Invoices references : %s') % invoices_ref)
 
1131
 
1845
1132
        # Done stock moves
1846
1133
        move_ids = self.pool.get('stock.move').search(cr, uid, [('purchase_line_id', 'in', order_lines), ('state', 'not in', ('cancel', 'done'))], context=context)
1847
1134
        self.pool.get('stock.move').set_manually_done(cr, uid, move_ids, all_doc=all_doc, context=context)
1864
1151
                    # Search the method called when the workflow enter in last activity
1865
1152
                    wkf_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'purchase', 'act_done')[1]
1866
1153
                    activity = self.pool.get('workflow.activity').browse(cr, uid, wkf_id, context=context)
1867
 
                    _eval_expr(cr, [uid, 'purchase.order', order_id.id], False, activity.action)
 
1154
                    res = _eval_expr(cr, [uid, 'purchase.order', order_id.id], False, activity.action)
1868
1155
 
1869
1156
        return True
1870
 
 
1871
 
    def _hook_order_infos(self, cr, uid, *args, **kwargs):
1872
 
        '''
1873
 
        Hook to change the values of the PO
1874
 
        '''
1875
 
        order_infos = super(purchase_order, self)._hook_order_infos(cr, uid, *args, **kwargs)
1876
 
        order_id = kwargs['order_id']
1877
 
 
1878
 
        fields = ['invoice_method', 'minimum_planned_date', 'order_type',
1879
 
                  'categ', 'priority', 'internal_type', 'arrival_date',
1880
 
                  'transport_type', 'shipment_date', 'ready_to_ship_date',
1881
 
                  'cross_docking_ok', 'delivery_confirmed_date',
1882
 
                  'est_transport_lead_time', 'transport_mode', 'location_id',
1883
 
                  'dest_address_id', 'incoterm_id']
1884
 
 
1885
 
 
1886
 
        delivery_requested_date = getattr(order_id, 'delivery_requested_date')
1887
 
        if not order_infos.get('delivery_requested_date') or delivery_requested_date < order_infos['delivery_requested_date']:
1888
 
            order_infos['delivery_requested_date'] = delivery_requested_date
1889
 
 
1890
 
 
1891
 
        for field in fields:
1892
 
            field_val = getattr(order_id, field)
1893
 
            if isinstance(field_val, browse_record):
1894
 
                field_val = field_val.id
1895
 
            elif isinstance(field_val, browse_null):
1896
 
                field_val = False
1897
 
            elif isinstance(field_val, list):
1898
 
                field_val = ((6, 0, tuple([v.id for v in field_val])),)
1899
 
            order_infos[field] = field_val
1900
 
 
1901
 
        return order_infos
1902
 
 
1903
 
    def _hook_o_line_value(self, cr, uid, *args, **kwargs):
1904
 
        o_line = super(purchase_order, self)._hook_o_line_value(cr, uid, *args, **kwargs)
1905
 
        order_line = kwargs['order_line']
1906
 
 
1907
 
        # Copy all fields except order_id and analytic_distribution_id
1908
 
        fields = ['product_uom', 'price_unit', 'move_dest_id', 'product_qty', 'partner_id',
1909
 
                  'confirmed_delivery_date', 'nomenclature_description', 'default_code',
1910
 
                  'nomen_manda_0', 'nomen_manda_1', 'nomen_manda_2', 'nomen_manda_3',
1911
 
                  'nomenclature_code', 'name', 'default_name', 'comment', 'date_planned',
1912
 
                  'to_correct_ok', 'text_error',
1913
 
                  'nomen_sub_0', 'nomen_sub_1', 'nomen_sub_2', 'nomen_sub_3', 'nomen_sub_4',
1914
 
                  'nomen_sub_5', 'procurement_id', 'change_price_manually', 'old_price_unit',
1915
 
                  'origin', 'account_analytic_id', 'product_id', 'company_id', 'notes', 'taxes_id']
1916
 
 
1917
 
        for field in fields:
1918
 
            field_val = getattr(order_line, field)
1919
 
            if isinstance(field_val, browse_record):
1920
 
                field_val = field_val.id
1921
 
            elif isinstance(field_val, browse_null):
1922
 
                field_val = False
1923
 
            elif isinstance(field_val, list):
1924
 
                field_val = ((6, 0, tuple([v.id for v in field_val])),)
1925
 
            o_line[field] = field_val
1926
 
 
1927
 
 
1928
 
        # Set the analytic distribution
1929
 
        distrib_id = False
1930
 
        if order_line.analytic_distribution_id:
1931
 
            distrib_id = self.pool.get('analytic.distribution').copy(cr, uid, order_line.analytic_distribution_id.id)
1932
 
        elif order_line.order_id.analytic_distribution_id:
1933
 
            distrib_id = self.pool.get('analytic.distribution').copy(cr, uid, order_line.order_id.analytic_distribution_id.id)
1934
 
 
1935
 
        o_line['analytic_distribution_id'] = distrib_id
1936
 
 
1937
 
        return o_line
1938
 
 
 
1157
    
1939
1158
purchase_order()
1940
1159
 
1941
1160
 
1942
 
class purchase_order_line1(osv.osv):
1943
 
    '''
1944
 
    this modification is placed before merged, because unit price of merged should be Computation as well
1945
 
    '''
1946
 
    _name = 'purchase.order.line'
1947
 
    _inherit = 'purchase.order.line'
1948
 
    _columns = {'price_unit': fields.float('Unit Price', required=True, digits_compute=dp.get_precision('Purchase Price Computation')),
1949
 
                }
1950
 
 
1951
 
purchase_order_line1()
1952
 
 
1953
 
 
1954
1161
class purchase_order_merged_line(osv.osv):
1955
1162
    '''
1956
1163
    A purchase order merged line is a special PO line.
2004
1211
                    self.pool.get('purchase.order.line').write(cr, uid, [po_line.id], {'price_unit': vals['price_unit'],
2005
1212
                                                                                       'old_price_unit': vals['price_unit']}, context=new_context)
2006
1213
 
2007
 
        res = super(purchase_order_merged_line, self).write(cr, uid, ids, vals, context=context)
2008
 
 
2009
 
        return res
2010
 
 
2011
 
    def _update(self, cr, uid, p_id, po_line_id, product_qty, price=0.00, context=None, no_update=False):
 
1214
        return super(purchase_order_merged_line, self).write(cr, uid, ids, vals, context=context)
 
1215
 
 
1216
    def _update(self, cr, uid, id, po_line_id, product_qty, price=0.00, context=None, no_update=False):
2012
1217
        '''
2013
1218
        Update the quantity and the unit price according to the new qty
2014
1219
        '''
2015
 
        line = self.browse(cr, uid, p_id, context=context)
 
1220
        line = self.browse(cr, uid, id, context=context)
2016
1221
        change_price_ok = True
2017
1222
        if not po_line_id:
2018
1223
            change_price_ok = context.get('change_price_ok', True)
2024
1229
 
2025
1230
        # If no PO line attached to this merged line, remove the merged line
2026
1231
        if not line.order_line_ids:
2027
 
            self.unlink(cr, uid, [p_id], context=context)
 
1232
            self.unlink(cr, uid, [id], context=context)
2028
1233
            return False, False
2029
1234
 
2030
1235
        new_price = False
2031
 
        new_qty = line.product_qty + float(product_qty)
2032
 
 
2033
 
        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):
 
1236
        new_qty = line.product_qty + product_qty
 
1237
        
 
1238
        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):    
2034
1239
            # Get the catalogue unit price according to the total qty
2035
 
            new_price = self.pool.get('product.pricelist').price_get(cr, uid,
 
1240
            new_price = self.pool.get('product.pricelist').price_get(cr, uid, 
2036
1241
                                                              [line.order_id.pricelist_id.id],
2037
1242
                                                              line.product_id.id,
2038
1243
                                                              new_qty,
2039
1244
                                                              line.order_id.partner_id.id,
2040
1245
                                                              {'uom': line.product_uom.id,
2041
 
                                                               'date': line.order_id.date_order})[line.order_id.pricelist_id.id]
2042
 
 
2043
 
        # Update the quantity of the merged line
 
1246
                                                               'date': line.order_id.date_order})[line.order_id.pricelist_id.id]                                      
 
1247
        
 
1248
        # Update the quantity of the merged line                  
2044
1249
        values = {'product_qty': new_qty}
2045
1250
        # If a catalogue unit price exist and the unit price is not manually changed
2046
1251
        if new_price:
2052
1257
 
2053
1258
        # Update the unit price and the quantity of the merged line
2054
1259
        if not no_update:
2055
 
            self.write(cr, uid, [p_id], values, context=context)
 
1260
            self.write(cr, uid, [id], values, context=context)
2056
1261
 
2057
 
        return p_id, new_price or False
 
1262
        return id, new_price or False
2058
1263
 
2059
1264
 
2060
1265
purchase_order_merged_line()
2075
1280
            merged_ids = line_obj.search(cr, uid, domain, context=context)
2076
1281
        else:
2077
1282
            merged_ids = []
2078
 
 
 
1283
        
2079
1284
        new_vals = vals.copy()
2080
 
        # Don't include taxes on merged lines
2081
 
        if 'taxes_id' in new_vals:
2082
 
            new_vals.pop('taxes_id')
2083
1285
 
2084
1286
        if not merged_ids:
2085
1287
            new_vals['order_id'] = order_id
2106
1308
        Update the merged line
2107
1309
        '''
2108
1310
        merged_line_obj = self.pool.get('purchase.order.merged.line')
2109
 
 
 
1311
        
2110
1312
        if not vals:
2111
1313
            vals = {}
2112
1314
        tmp_vals = vals.copy()
2114
1316
        # If it's an update of a line
2115
1317
        if vals and line_id:
2116
1318
            line = self.browse(cr, uid, line_id, context=context)
2117
 
 
 
1319
            
2118
1320
            # Set default values if not pass in values
2119
 
            if not 'product_uom' in vals:
 
1321
            if not 'product_uom' in vals: 
2120
1322
                tmp_vals.update({'product_uom': line.product_uom.id})
2121
 
            if not 'product_qty' in vals:
 
1323
            if not 'product_qty' in vals: 
2122
1324
                tmp_vals.update({'product_qty': line.product_qty})
2123
 
 
 
1325
            
2124
1326
            # If the user changed the product or the UoM or both on the PO line
2125
1327
            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']):
2126
1328
                # Need removing the merged_id link before update the merged line because the merged line
2131
1333
                c.update({'change_price_ok': change_price_ok})
2132
1334
                self.write(cr, uid, line_id, {'merged_id': False}, context=context)
2133
1335
                res_merged = merged_line_obj._update(cr, uid, merged_id, line.id, -line.product_qty, line.price_unit, context=c)
2134
 
 
 
1336
                
2135
1337
                # Create or update an existing merged line with the new product
2136
1338
                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)
2137
 
 
 
1339
            
2138
1340
            # If the quantity is changed
2139
1341
            elif 'product_qty' in vals and line.product_qty != vals['product_qty']:
2140
1342
                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)
2141
1343
                # Update the unit price
2142
1344
                if res_merged and res_merged[1]:
2143
1345
                    vals.update({'price_unit': res_merged[1]})
2144
 
 
 
1346
                    
2145
1347
            # If the price unit is changed and the product and the UoM is not modified
2146
1348
            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)):
2147
1349
                # Give 0.00 to quantity because the _update should recompute the price unit with the same quantity
2162
1364
                change_price_ok = line.change_price_ok
2163
1365
                c = context.copy()
2164
1366
                c.update({'change_price_ok': change_price_ok})
2165
 
                noraise_ctx = context.copy()
2166
 
                noraise_ctx.update({'noraise': True})
2167
1367
                # Need removing the merged_id link before update the merged line because the merged line
2168
1368
                # will be removed if it hasn't attached PO line
2169
 
                self.write(cr, uid, [line.id], {'merged_id': False}, context=noraise_ctx)
 
1369
                self.write(cr, uid, [line.id], {'merged_id': False}, context=context)
2170
1370
                res_merged = merged_line_obj._update(cr, uid, merged_id, line.id, -line.product_qty, line.price_unit, context=c)
2171
1371
 
2172
1372
        return vals
2173
1373
 
2174
 
    def _check_restriction_line(self, cr, uid, ids, context=None):
2175
 
        '''
2176
 
        Check if there is restriction on lines
2177
 
        '''
2178
 
        if isinstance(ids, (int, long)):
2179
 
            ids = [ids]
2180
 
 
2181
 
        if not context:
2182
 
            context = {}
2183
 
 
2184
 
        for line in self.browse(cr, uid, ids, context=context):
2185
 
            if line.order_id and line.order_id.partner_id and line.order_id.state != 'done' and line.product_id:
2186
 
                if not self.pool.get('product.product')._get_restriction_error(cr, uid, line.product_id.id, vals={'partner_id': line.order_id.partner_id.id}, context=context):
2187
 
                    return False
2188
 
 
2189
 
        return True
2190
 
 
2191
 
    def _relatedFields(self, cr, uid, vals, context=None):
2192
 
        '''
2193
 
        related fields for create and write
2194
 
        '''
2195
 
        # recreate description because in readonly
2196
 
        if ('product_id' in vals) and (vals['product_id']):
2197
 
            # no nomenclature description
2198
 
            vals.update({'nomenclature_description':False})
2199
 
            # update the name (comment) of order line
2200
 
            # the 'name' is no more the get_name from product, but instead
2201
 
            # the name of product
2202
 
            productObj = self.pool.get('product.product').browse(cr, uid, vals['product_id'], context=context)
2203
 
            vals.update({'name':productObj.name})
2204
 
            vals.update({'default_code':productObj.default_code})
2205
 
            vals.update({'default_name':productObj.name})
2206
 
            # erase the nomenclature - readonly
2207
 
            self.pool.get('product.product')._resetNomenclatureFields(vals)
2208
 
        elif ('product_id' in vals) and (not vals['product_id']):
2209
 
            sale = self.pool.get('sale.order.line')
2210
 
            sale._setNomenclatureInfo(cr, uid, vals, context)
2211
 
            # erase default code
2212
 
            vals.update({'default_code':False})
2213
 
            vals.update({'default_name':False})
2214
 
            
2215
 
            if 'comment' in vals:
2216
 
                vals.update({'name':vals['comment']})
2217
 
        # clear nomenclature filter values
2218
 
        #self.pool.get('product.product')._resetNomenclatureFields(vals)
2219
 
 
2220
 
    def _update_name_attr(self, cr, uid, vals, context=None):
2221
 
        """Update the name attribute in `vals` if a product is selected."""
2222
 
        if context is None:
2223
 
            context = {}
2224
 
        prod_obj = self.pool.get('product.product')
2225
 
        if vals.get('product_id'):
2226
 
            product = prod_obj.browse(cr, uid, vals['product_id'], context=context)
2227
 
            vals['name'] = product.name
2228
 
        elif vals.get('comment'):
2229
 
            vals['name'] = vals.get('comment', False)
2230
 
 
2231
 
    def _check_product_uom(self, cr, uid, product_id, uom_id, context=None):
2232
 
        """Check the product UoM."""
2233
 
        if context is None:
2234
 
            context = {}
2235
 
        uom_tools_obj = self.pool.get('uom.tools')
2236
 
        if not uom_tools_obj.check_uom(cr, uid, product_id, uom_id, context=context):
2237
 
            raise osv.except_osv(
2238
 
                _('Error'),
2239
 
                _('You have to select a product UOM in the same '
2240
 
                  'category than the purchase UOM of the product !'))
2241
 
 
2242
1374
    def create(self, cr, uid, vals, context=None):
2243
1375
        '''
2244
1376
        Create or update a merged line
2245
1377
        '''
2246
 
        if context is None:
 
1378
        if not context:
2247
1379
            context = {}
2248
 
 
2249
 
        po_obj = self.pool.get('purchase.order')
2250
 
        seq_pool = self.pool.get('ir.sequence')
2251
 
        sol_obj = self.pool.get('sale.order.line')
2252
 
 
 
1380
            
 
1381
        order_id = self.pool.get('purchase.order').browse(cr, uid, vals['order_id'], context=context)
 
1382
        if order_id.from_yml_test:
 
1383
            vals.update({'change_price_manually': True})
 
1384
            if not vals.get('product_qty', False):
 
1385
                vals['product_qty'] = 1.00
 
1386
                
 
1387
        # If we are on a RfQ, use the last entered unit price and update other lines with this price
 
1388
        if order_id.rfq_ok:
 
1389
            vals.update({'change_price_manually': True})
 
1390
        else:
 
1391
            if vals.get('product_qty', 0.00) == 0.00:
 
1392
                raise osv.except_osv(_('Error'), _('You cannot save a line with no quantity !'))
 
1393
        
2253
1394
        order_id = vals.get('order_id')
2254
1395
        product_id = vals.get('product_id')
2255
1396
        product_uom = vals.get('product_uom')
2256
 
        order = po_obj.browse(cr, uid, order_id, context=context)
2257
 
 
2258
 
        if order.from_yml_test:
2259
 
            vals.update({'change_price_manually': True})
2260
 
            if not vals.get('product_qty', False):
2261
 
                vals['product_qty'] = 1.00
2262
 
            # [imported and adapted from 'analytic_distribution_supply']
2263
 
            if not vals.get('price_unit', False):
2264
 
                vals['price_unit'] = 1.00
2265
 
            # [/]
2266
 
 
2267
 
        # Update the name attribute if a product is selected
2268
 
        self._update_name_attr(cr, uid, vals, context=context)
2269
 
 
2270
 
        # If we are on a RfQ, use the last entered unit price and update other lines with this price
2271
 
        if order.rfq_ok:
2272
 
            vals.update({'change_price_manually': True})
2273
 
        else:
2274
 
            if order.po_from_fo or order.po_from_ir:
2275
 
                vals['from_fo'] = True
2276
 
            if vals.get('product_qty', 0.00) == 0.00 and not context.get('noraise'):
2277
 
                raise osv.except_osv(_('Error'), _('You cannot save a line with no quantity !'))
2278
 
 
 
1397
        order = self.pool.get('purchase.order').browse(cr, uid, order_id, context=context)
2279
1398
        other_lines = self.search(cr, uid, [('order_id', '=', order_id), ('product_id', '=', product_id), ('product_uom', '=', product_uom)], context=context)
2280
1399
        stages = self._get_stages_price(cr, uid, product_id, product_uom, order, context=context)
2281
1400
 
2282
 
        if vals.get('origin'):
2283
 
            proc = False
2284
 
            if vals.get('procurement_id'):
2285
 
                proc = self.pool.get('procurement.order').browse(cr, uid, vals.get('procurement_id'))
2286
 
            if not proc or not proc.sale_id:
2287
 
                vals.update(self.update_origin_link(cr, uid, vals.get('origin'), context=context))
2288
 
 
2289
1401
        if (other_lines and stages and order.state != 'confirmed'):
2290
1402
            context.update({'change_price_ok': False})
2291
1403
 
2292
 
        if not context.get('offline_synchronization'):
2293
 
            vals = self._update_merged_line(cr, uid, False, vals, context=dict(context, skipResequencing=True))
 
1404
        vals = self._update_merged_line(cr, uid, False, vals, context=context)
2294
1405
 
2295
1406
        vals.update({'old_price_unit': vals.get('price_unit', False)})
2296
1407
 
2297
 
        # [imported from 'order_nomenclature']
2298
 
        # Don't save filtering data
2299
 
        self._relatedFields(cr, uid, vals, context)
2300
 
        # [/]
2301
 
 
2302
 
        # [imported from 'order_line_number']
2303
 
        # Add the corresponding line number
2304
 
        #   I leave this line from QT related to purchase.order.merged.line for compatibility and safety reasons
2305
 
        #   merged lines, set the line_number to 0 when calling create function
2306
 
        #   the following line should *logically* be removed safely
2307
 
        #   copy method should work as well, as merged line do *not* need to keep original line number with copy function (QT confirmed)
2308
 
        if self._name != 'purchase.order.merged.line':
2309
 
            if order_id:
2310
 
                # gather the line number from the sale order sequence if not specified in vals
2311
 
                # either line_number is not specified or set to False from copy, we need a new value
2312
 
                if not vals.get('line_number', False):
2313
 
                    # new number needed - gather the line number from the sequence
2314
 
                    sequence_id = order.sequence_id.id
2315
 
                    line = seq_pool.get_id(cr, uid, sequence_id, code_or_id='id', context=context)
2316
 
                    vals.update({'line_number': line})
2317
 
        # [/]
2318
 
 
2319
 
        # Check the selected product UoM
2320
 
        if not context.get('import_in_progress', False):
2321
 
            if vals.get('product_id') and vals.get('product_uom'):
2322
 
                self._check_product_uom(
2323
 
                    cr, uid, vals['product_id'], vals['product_uom'], context=context)
2324
 
 
2325
 
        # utp-518:we write the comment from the sale.order.line on the PO line through the procurement (only for the create!!)
2326
 
        po_procurement_id = vals.get('procurement_id', False)
2327
 
        if po_procurement_id:
2328
 
            sale_id = sol_obj.search(cr, uid, [('procurement_id', '=', po_procurement_id)], context=context)
2329
 
            if sale_id:
2330
 
                comment_so = sol_obj.read(cr, uid, sale_id, ['comment'], context=context)[0]['comment']
2331
 
                vals.update(comment=comment_so)
2332
 
 
2333
 
        # add the database Id to the sync_order_line_db_id
 
1408
        # add the database Id to the sync_pol_db_id
2334
1409
        po_line_id = super(purchase_order_line, self).create(cr, uid, vals, context=context)
2335
 
        if not vals.get('sync_order_line_db_id', False): #'sync_order_line_db_id' not in vals or vals:
2336
 
            name = order.name
2337
 
            super(purchase_order_line, self).write(cr, uid, [po_line_id], {'sync_order_line_db_id': name + "_" + str(po_line_id),}, context=context)
 
1410
        if 'sync_pol_db_id' not in vals:
 
1411
            super(purchase_order_line, self).write(cr, uid, po_line_id, {'sync_pol_db_id': po_line_id}, context=context)
2338
1412
 
2339
1413
        return po_line_id
2340
1414
 
2341
 
    def default_get(self, cr, uid, fields, context=None):
2342
 
        if not context:
2343
 
            context = {}
2344
 
 
2345
 
        if context.get('purchase_id'):
2346
 
            # Check validity of the purchase order. We write the order to avoid
2347
 
            # the creation of a new line if one line of the order is not valid
2348
 
            # according to the order category
2349
 
            # Example :
2350
 
            #    1/ Create a new PO with 'Other' as Order Category
2351
 
            #    2/ Add a new line with a Stockable product
2352
 
            #    3/ Change the Order Category of the PO to 'Service' -> A warning message is displayed
2353
 
            #    4/ Try to create a new line -> The system displays a message to avoid you to create a new line
2354
 
            #       while the not valid line is not modified/deleted
2355
 
            #
2356
 
            #   Without the write of the order, the message displayed by the system at 4/ is displayed at the saving
2357
 
            #   of the new line that is not very understandable for the user
2358
 
            data = {}
2359
 
            if context.get('partner_id'):
2360
 
                data.update({'partner_id': context.get('partner_id')})
2361
 
            if context.get('categ'):
2362
 
                data.update({'categ': context.get('categ')})
2363
 
            self.pool.get('purchase.order').write(cr, uid, [context.get('purchase_id')], data, context=context)
2364
 
 
2365
 
        return super(purchase_order_line, self).default_get(cr, uid, fields, context=context)
2366
 
 
2367
1415
    def copy(self, cr, uid, line_id, defaults={}, context=None):
2368
1416
        '''
2369
1417
        Remove link to merged line
2370
1418
        '''
2371
 
        defaults.update({'merged_id': False, 'sync_order_line_db_id': False})
 
1419
        defaults.update({'merged_id': False})
2372
1420
 
2373
1421
        return super(purchase_order_line, self).copy(cr, uid, line_id, defaults, context=context)
2374
1422
 
2375
 
    def copy_data(self, cr, uid, p_id, default=None, context=None):
2376
 
        """
2377
 
        """
2378
 
        # Some verifications
2379
 
        if not context:
2380
 
            context = {}
2381
 
        if not default:
2382
 
            default = {}
2383
 
 
2384
 
        if not 'move_dest_id' in default:
2385
 
            default.update({'move_dest_id': False})
2386
 
 
2387
 
        if not 'procurement_id' in default:
2388
 
            default.update({'procurement_id': False})
2389
 
 
2390
 
        default.update({'sync_order_line_db_id': False})
2391
 
        return super(purchase_order_line, self).copy_data(cr, uid, p_id, default=default, context=context)
2392
 
 
2393
1423
    def write(self, cr, uid, ids, vals, context=None):
2394
1424
        '''
2395
1425
        Update merged line
2396
1426
        '''
2397
 
        if context is None:
 
1427
        if not context:
2398
1428
            context = {}
2399
1429
 
2400
1430
        if isinstance(ids, (int, long)):
2401
1431
            ids = [ids]
2402
 
 
2403
 
        # [imported from the 'analytic_distribution_supply']
2404
 
        # Don't save filtering data
2405
 
        self._relatedFields(cr, uid, vals, context)
2406
 
        # [/]
2407
 
 
2408
 
        # Update the name attribute if a product is selected
2409
 
        self._update_name_attr(cr, uid, vals, context=context)
2410
 
 
 
1432
        
 
1433
#        if ids and not isinstance(ids[0], (int, long)):
 
1434
#            ids = [x.id for x in ids]
 
1435
            
2411
1436
        for line in self.browse(cr, uid, ids, context=context):
2412
 
            if vals.get('product_qty', line.product_qty) == 0.00 and not line.order_id.rfq_ok and not context.get('noraise'):
 
1437
            if vals.get('product_qty', line.product_qty) == 0.00 and not line.order_id.rfq_ok:
2413
1438
                raise osv.except_osv(_('Error'), _('You cannot save a line with no quantity !'))
2414
 
 
2415
 
            if vals.get('origin', line.origin):
2416
 
                proc = False
2417
 
                if vals.get('procurement_id', line.procurement_id.id):
2418
 
                    proc = self.pool.get('procurement.order').browse(cr, uid, vals.get('procurement_id', line.procurement_id.id))
2419
 
                if not proc or not proc.sale_id:
2420
 
                    vals.update(self.update_origin_link(cr, uid, vals.get('origin', line.origin), context=context))
2421
 
 
2422
 
            if line.order_id and not line.order_id.rfq_ok and (line.order_id.po_from_fo or line.order_id.po_from_ir):
2423
 
                vals['from_fo'] = True
2424
 
 
 
1439
        
2425
1440
        if not context.get('update_merge'):
2426
1441
            for line in ids:
2427
 
                vals = self._update_merged_line(cr, uid, line, vals, context=dict(context, skipResequencing=True, noraise=True))
2428
 
 
 
1442
                vals = self._update_merged_line(cr, uid, line, vals, context=context)
 
1443
                
2429
1444
        if 'price_unit' in vals:
2430
1445
            vals.update({'old_price_unit': vals.get('price_unit')})
2431
1446
 
2432
 
        res = super(purchase_order_line, self).write(cr, uid, ids, vals, context=context)
2433
 
 
2434
 
        # Check the selected product UoM
2435
 
        if not context.get('import_in_progress', False):
2436
 
            for pol_read in self.read(cr, uid, ids, ['product_id', 'product_uom']):
2437
 
                if pol_read.get('product_id'):
2438
 
                    product_id = pol_read['product_id'][0]
2439
 
                    uom_id = pol_read['product_uom'][0]
2440
 
                    self._check_product_uom(cr, uid, product_id, uom_id, context=context)
2441
 
 
2442
 
        return res
2443
 
 
2444
 
    def update_origin_link(self, cr, uid, origin, context=None):
2445
 
        '''
2446
 
        Return the FO/IR that matches with the origin value
2447
 
        '''
2448
 
        so_obj = self.pool.get('sale.order')
2449
 
 
2450
 
        tmp_proc_context = context.get('procurement_request')
2451
 
        context['procurement_request'] = True
2452
 
        so_ids = so_obj.search(cr, uid, [('name', '=', origin), ('state', 'in', ('sourced', 'progress', 'manual'))], context=context)
2453
 
        context['procurement_request'] = tmp_proc_context
2454
 
        if so_ids:
2455
 
            return {'link_so_id': so_ids[0]}
2456
 
 
2457
 
        return {}
2458
 
 
2459
 
    def ask_unlink(self, cr, uid, ids, context=None):
2460
 
        '''
2461
 
        Call the unlink method for lines and if the PO becomes empty
2462
 
        ask the user if he wants to cancel the PO
2463
 
        '''
2464
 
        # Objects
2465
 
        wiz_obj = self.pool.get('purchase.order.line.unlink.wizard')
2466
 
 
2467
 
        # Variables initialization
2468
 
        if context is None:
2469
 
            context = {}
2470
 
 
2471
 
        if isinstance(ids, (int, long)):
2472
 
            ids = [ids]
2473
 
 
2474
 
        for line_id in ids:
2475
 
            sol_ids = self.get_sol_ids_from_pol_ids(cr, uid, [line_id], context=context)
2476
 
            if sol_ids:
2477
 
                wiz_id = wiz_obj.create(cr, uid, {'line_id': line_id}, context=context)
2478
 
                return {'type': 'ir.actions.act_window',
2479
 
                        'res_model': 'purchase.order.line.unlink.wizard',
2480
 
                        'view_type': 'form',
2481
 
                        'view_mode': 'form',
2482
 
                        'res_id': wiz_id,
2483
 
                        'target': 'new',
2484
 
                        'context': context}
2485
 
 
2486
 
        return self.unlink(cr, uid, ids, context=context)
2487
 
 
2488
 
    def cancel_sol(self, cr, uid, ids, context=None):
2489
 
        '''
2490
 
        Re-source the FO line
2491
 
        '''
2492
 
        context = context or {}
2493
 
        sol_obj = self.pool.get('sale.order.line')
2494
 
        uom_obj = self.pool.get('product.uom')
2495
 
 
2496
 
        if isinstance(ids, (int, long)):
2497
 
            ids = [ids]
2498
 
 
2499
 
        sol_to_update = {}
2500
 
        for line in self.browse(cr, uid, ids, context=context):
2501
 
            sol_ids = self.get_sol_ids_from_pol_ids(cr, uid, [line.id], context=context)
2502
 
            for sol in sol_obj.browse(cr, uid, sol_ids, context=context):
2503
 
                diff_qty = uom_obj._compute_qty(cr, uid, line.product_uom.id, line.product_qty, sol.product_uom.id)
2504
 
                sol_to_update.setdefault(sol.id, 0.00)
2505
 
                sol_to_update[sol.id] += diff_qty
2506
 
                if line.has_to_be_resourced:
2507
 
                    sol_obj.add_resource_line(cr, uid, sol, False, diff_qty, context=context)
2508
 
 
2509
 
        for sol in sol_to_update:
2510
 
            sol_obj.update_or_cancel_line(cr, uid, sol, sol_to_update[sol], context=context)
2511
 
 
2512
 
        return True
2513
 
 
2514
 
    def fake_unlink(self, cr, uid, ids, context=None):
2515
 
        '''
2516
 
        Cancel the line and re-source them
2517
 
        '''
2518
 
        proc_obj = self.pool.get('procurement.order')
2519
 
 
2520
 
        if context is None:
2521
 
            context = {}
2522
 
 
2523
 
        if isinstance(ids, (int, long)):
2524
 
            ids = [ids]
2525
 
 
2526
 
        proc_ids = []
2527
 
        purchase_ids = []
2528
 
        line_to_cancel = []
2529
 
 
2530
 
        for line in self.browse(cr, uid, ids, context=context):
2531
 
            # Set the procurement orders to delete
2532
 
            # Set the list of linked purchase orders
2533
 
            if line.procurement_id:
2534
 
                proc_ids.append(line.procurement_id.id)
2535
 
            if line.order_id.id not in purchase_ids:
2536
 
                purchase_ids.append(line.order_id.id)
2537
 
 
2538
 
            self.cancel_sol(cr, uid, [line.id], context=context)
2539
 
            # we want to skip resequencing because unlink is performed on merged purchase order lines
2540
 
            tmp_Resequencing = context.get('skipResequencing', False)
2541
 
            context['skipResequencing'] = True
2542
 
            self._update_merged_line(cr, uid, line.id, False, context=context)
2543
 
            context['skipResequencing'] = tmp_Resequencing
2544
 
 
2545
 
            line_to_cancel.append(line.id)
2546
 
 
2547
 
        # Cancel the listed procurement orders
2548
 
        for proc_id in proc_ids:
2549
 
            if not self.search(cr, uid, [('procurement_id', '=', proc_id)], context=context):
2550
 
                proc_obj.action_cancel(cr, uid, [proc_id])
2551
 
 
2552
 
        self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
2553
 
        self.unlink(cr, uid, line_to_cancel, context=context)
2554
 
 
2555
 
        return ids
 
1447
        return super(purchase_order_line, self).write(cr, uid, ids, vals, context=context)
2556
1448
 
2557
1449
    def unlink(self, cr, uid, ids, context=None):
2558
1450
        '''
2562
1454
            context = {}
2563
1455
        if isinstance(ids, (int, long)):
2564
1456
            ids = [ids]
 
1457
            
 
1458
        # if the line is linked to a sale order line through procurement process,
 
1459
        # the deletion is impossible
 
1460
        if self.get_sol_ids_from_pol_ids(cr, uid, ids, context=context):
 
1461
            raise osv.except_osv(_('Error'), _('You cannot delete a line which is linked to a Fo line.'))
2565
1462
 
2566
1463
        for line_id in ids:
2567
 
            # we want to skip resequencing because unlink is performed on merged purchase order lines
2568
 
            tmp_skip_resourcing = context.get('skipResourcing')
2569
 
            context['skipResourcing'] = True
2570
1464
            self._update_merged_line(cr, uid, line_id, False, context=context)
2571
 
            context['skipResourcing'] = tmp_skip_resourcing
2572
1465
 
2573
1466
        return super(purchase_order_line, self).unlink(cr, uid, ids, context=context)
2574
1467
 
2579
1472
        for pol in self.read(cr, uid, ids, ['state']):
2580
1473
            ret[pol['id']] = pol['state']
2581
1474
        return ret
2582
 
 
 
1475
    
2583
1476
    def _get_fake_id(self, cr, uid, ids, field_name, args, context=None):
2584
1477
        if isinstance(ids, (int, long)):
2585
1478
            ids = [ids]
2587
1480
        for pol in self.read(cr, uid, ids, ['id']):
2588
1481
            ret[pol['id']] = pol['id']
2589
1482
        return ret
2590
 
 
 
1483
    
2591
1484
    def _get_stages_price(self, cr, uid, product_id, uom_id, order, context=None):
2592
1485
        '''
2593
1486
        Returns True if the product/supplier couple has more than 1 line
2594
1487
        '''
2595
 
        suppinfo_ids = self.pool.get('product.supplierinfo').search(cr, uid, [('name', '=', order.partner_id.id),
 
1488
        suppinfo_ids = self.pool.get('product.supplierinfo').search(cr, uid, [('name', '=', order.partner_id.id), 
2596
1489
                                                                              ('product_id', '=', product_id)], context=context)
2597
1490
        if suppinfo_ids:
2598
1491
            pricelist_ids = self.pool.get('pricelist.partnerinfo').search(cr, uid, [('currency_id', '=', order.pricelist_id.currency_id.id),
2602
1495
                                                                                    ('valid_till', '>=', order.date_order)], context=context)
2603
1496
            if len(pricelist_ids) > 1:
2604
1497
                return True
2605
 
 
 
1498
        
2606
1499
        return False
2607
 
 
 
1500
        
2608
1501
    def _get_price_change_ok(self, cr, uid, ids, field_name, args, context=None):
2609
1502
        '''
2610
1503
        Returns True if the price can be changed by the user
2611
1504
        '''
2612
1505
        res = {}
2613
 
 
 
1506
        
2614
1507
        for line in self.browse(cr, uid, ids, context=context):
2615
1508
            res[line.id] = True
2616
1509
            stages = self._get_stages_price(cr, uid, line.product_id.id, line.product_uom.id, line.order_id, context=context)
2617
1510
            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:
2618
1511
                res[line.id] = False
2619
 
 
2620
 
        return res
2621
 
 
2622
 
    def on_change_select_fo(self, cr, uid, ids, fo_id, context=None):
2623
 
        '''
2624
 
        Fill the origin field if a FO is selected
2625
 
        '''
2626
 
        so_obj = self.pool.get('sale.order')
2627
 
        if fo_id:
2628
 
            res = {'value': {'origin': so_obj.browse(cr, uid, fo_id, context=context).name,
2629
 
                             'select_fo': False}}
2630
 
            return res
2631
 
 
2632
 
        return {}
2633
 
 
2634
 
    def on_change_origin(self, cr, uid, ids, origin, procurement_id=False, partner_type='external', context=None):
2635
 
        '''
2636
 
        Check if the origin is a known FO/IR
2637
 
        '''
2638
 
        res = {}
2639
 
        if not procurement_id and origin:
2640
 
            domain = [('name', '=', origin), ('state', 'in', ('sourced', 'progres', 'manual'))]
2641
 
            o_type = 'a Non-ESC'
2642
 
            if partner_type == 'esc':
2643
 
                o_type = 'an ESC'
2644
 
                domain.append(('split_type_sale_order', '=', 'esc_split_sale_order'))
2645
 
            else:
2646
 
                domain.append(('split_type_sale_order', '=', 'local_purchase_split_sale_order'))
2647
 
            sale_id = self.pool.get('sale.order').search(cr, uid, domain, context=context)
2648
 
            if not sale_id:
2649
 
                res['warning'] = {'title': _('Warning'),
2650
 
                                  'message': _('The reference \'%s\' put in the Origin field doesn\'t match with a confirmed FO/IR sourced with %s supplier. No FO/IR line will be created for this PO line') % (origin, o_type)}
2651
 
 
2652
 
        return res
2653
 
 
 
1512
                        
 
1513
        return res
 
1514
    
2654
1515
    def _vals_get(self, cr, uid, ids, fields, arg, context=None):
2655
1516
        '''
2656
1517
        multi fields function method
2660
1521
            context = {}
2661
1522
        if isinstance(ids, (int, long)):
2662
1523
            ids = [ids]
2663
 
 
 
1524
            
 
1525
        # objects
 
1526
        sol_obj = self.pool.get('sale.order.line')
 
1527
        
2664
1528
        result = {}
2665
1529
        for obj in self.browse(cr, uid, ids, context=context):
2666
1530
            # default values
2668
1532
            # order_state_purchase_order_line
2669
1533
            if obj.order_id:
2670
1534
                result[obj.id].update({'order_state_purchase_order_line': obj.order_id.state})
2671
 
 
 
1535
            
2672
1536
        return result
2673
1537
 
2674
 
    def _get_project_po_ref(self, cr, uid, ids, field_name, args, context=None):
2675
 
        '''
2676
 
        Return the name of the PO at project side
2677
 
        '''
2678
 
        if isinstance(ids, (int, long)):
2679
 
            ids = [ids]
2680
 
 
2681
 
        res = {}
2682
 
        for line_id in ids:
2683
 
            res[line_id] = ''
2684
 
            sol_ids = self.get_sol_ids_from_pol_ids(cr, uid, line_id, context=context)
2685
 
            for sol in self.pool.get('sale.order.line').browse(cr, uid, sol_ids, context=context):
2686
 
                if sol.order_id and sol.order_id.client_order_ref:
2687
 
                    if res[line_id]:
2688
 
                        res[line_id] += ' - '
2689
 
                    res[line_id] += sol.order_id.client_order_ref
2690
 
 
2691
 
        return res
2692
 
 
2693
1538
    _columns = {
2694
 
        'is_line_split': fields.boolean(string='This line is a split line?'), # UTP-972: Use boolean to indicate if the line is a split line
 
1539
        'parent_line_id': fields.many2one('purchase.order.line', string='Parent line'),
2695
1540
        'merged_id': fields.many2one('purchase.order.merged.line', string='Merged line'),
2696
1541
        'origin': fields.char(size=64, string='Origin'),
2697
 
        'link_so_id': fields.many2one('sale.order', string='Linked FO/IR', readonly=True),
2698
 
        'dpo_received': fields.boolean(string='Is the IN has been received at Project side ?'),
2699
1542
        'change_price_ok': fields.function(_get_price_change_ok, type='boolean', method=True, string='Price changing'),
2700
1543
        'change_price_manually': fields.boolean(string='Update price manually'),
2701
1544
        # openerp bug: eval invisible in p.o use the po line state and not the po state !
2702
1545
        'fake_state': fields.function(_get_fake_state, type='char', method=True, string='State', help='for internal use only'),
2703
1546
        # openerp bug: id is not given to onchanqge call if we are into one2many view
2704
1547
        'fake_id':fields.function(_get_fake_id, type='integer', method=True, string='Id', help='for internal use only'),
2705
 
        'old_price_unit': fields.float(string='Old price', digits_compute=dp.get_precision('Purchase Price Computation')),
 
1548
        'old_price_unit': fields.float(digits=(16,2), string='Old price'),
2706
1549
        '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),
2707
1550
 
2708
 
        # This field is used to identify the FO PO line between 2 instances of the sync
2709
 
        'sync_order_line_db_id': fields.text(string='Sync order line DB Id', required=False, readonly=True),
2710
 
        'external_ref': fields.char(size=256, string='Ext. Ref.'),
2711
 
        'project_ref': fields.char(size=256, string='Project Ref.'),
2712
 
        'has_to_be_resourced': fields.boolean(string='Has to be re-sourced'),
2713
 
        'select_fo': fields.many2one('sale.order', string='FO'),
2714
 
        'fnct_project_ref': fields.function(_get_project_po_ref, method=True, string='Project PO',
2715
 
                                            type='char', size=128, store=False),
2716
 
        'from_fo': fields.boolean(string='From FO', readonly=True),
 
1551
        'sync_pol_db_id': fields.integer(string='PO line DB Id', required=False, readonly=True),
 
1552
        'sync_sol_db_id': fields.integer(string='SO line DB Id', required=False, readonly=True),
2717
1553
    }
2718
1554
 
2719
1555
    _defaults = {
2721
1557
        'product_qty': lambda *a: 0.00,
2722
1558
        'price_unit': lambda *a: 0.00,
2723
1559
        'change_price_ok': lambda *a: True,
2724
 
        'is_line_split': False, # UTP-972: by default not a split line
2725
 
        'from_fo': lambda self, cr, uid, c: not c.get('rfq_ok', False) and c.get('from_fo', False),
2726
1560
    }
2727
 
 
 
1561
    
2728
1562
    def product_uom_change(self, cr, uid, ids, pricelist, product, qty, uom,
2729
1563
            partner_id, date_order=False, fiscal_position=False, date_planned=False,
2730
1564
            name=False, price_unit=False, notes=False):
2736
1570
            return res
2737
1571
        res['value'].update({'product_qty': 0.00})
2738
1572
        res.update({'warning': {}})
2739
 
 
 
1573
        
2740
1574
        return res
2741
 
 
 
1575
    
2742
1576
    def product_id_on_change(self, cr, uid, ids, pricelist, product, qty, uom,
2743
1577
            partner_id, date_order=False, fiscal_position=False, date_planned=False,
2744
1578
            name=False, price_unit=False, notes=False, state=False, old_price_unit=False,
2745
1579
            nomen_manda_0=False, comment=False, context=None):
2746
1580
        all_qty = qty
 
1581
        suppinfo_obj = self.pool.get('product.supplierinfo')
2747
1582
        partner_price = self.pool.get('pricelist.partnerinfo')
2748
 
        product_obj = self.pool.get('product.product')
2749
 
 
2750
 
        if not context:
2751
 
            context = {}
2752
 
 
2753
 
        # If the user modify a line, remove the old quantity for the total quantity
2754
 
        if ids:
2755
 
            for line_id in self.browse(cr, uid, ids, context=context):
2756
 
                all_qty -= line_id.product_qty
2757
 
 
 
1583
        
2758
1584
        if product and not uom:
2759
 
            uom = self.pool.get('product.product').browse(cr, uid, product).uom_id.id
2760
 
 
2761
 
        if context and context.get('purchase_id') and state == 'draft' and product:
2762
 
            domain = [('product_id', '=', product),
2763
 
                      ('product_uom', '=', uom),
 
1585
            uom = self.pool.get('product.product').browse(cr, uid, product).uom_po_id.id
 
1586
        
 
1587
        if context and context.get('purchase_id') and state == 'draft' and product:    
 
1588
            domain = [('product_id', '=', product), 
 
1589
                      ('product_uom', '=', uom), 
2764
1590
                      ('order_id', '=', context.get('purchase_id'))]
2765
1591
            other_lines = self.search(cr, uid, domain)
2766
1592
            for l in self.browse(cr, uid, other_lines):
2767
 
                all_qty += l.product_qty
2768
 
 
 
1593
                all_qty += l.product_qty 
 
1594
        
2769
1595
        res = super(purchase_order_line, self).product_id_change(cr, uid, ids, pricelist, product, all_qty, uom,
2770
 
                                                                 partner_id, date_order, fiscal_position,
 
1596
                                                                 partner_id, date_order, fiscal_position, 
2771
1597
                                                                 date_planned, name, price_unit, notes)
2772
 
 
 
1598
        
 
1599
        # Remove the warning message if the product has no staged pricelist
 
1600
#        if res.get('warning'):
 
1601
#            supplier_info = self.pool.get('product.supplierinfo').search(cr, uid, [('product_id', '=', product)])
 
1602
#            product_pricelist = self.pool.get('pricelist.partnerinfo').search(cr, uid, [('suppinfo_id', 'in', supplier_info)])
 
1603
#            if not product_pricelist:
 
1604
#                res['warning'] = {}
2773
1605
        if res.get('warning', {}).get('title', '') == 'No valid pricelist line found !' or qty == 0.00:
2774
1606
            res.update({'warning': {}})
2775
 
 
 
1607
        
2776
1608
        func_curr_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.currency_id.id
2777
1609
        if pricelist:
2778
1610
            currency_id = self.pool.get('product.pricelist').browse(cr, uid, pricelist).currency_id.id
2779
1611
        else:
2780
1612
            currency_id = func_curr_id
2781
 
 
2782
 
        if product and partner_id:
2783
 
            # Test the compatibility of the product with a the partner of the order
2784
 
            res, test = product_obj._on_change_restriction_error(cr, uid, product, field_name='product_id', values=res, vals={'partner_id': partner_id}, context=context)
2785
 
            if test:
2786
 
                return res
2787
 
 
2788
 
        # Update the old price value
 
1613
        
 
1614
        # Update the old price value        
2789
1615
        res['value'].update({'product_qty': qty})
2790
1616
        if product and not res.get('value', {}).get('price_unit', False) and all_qty != 0.00 and qty != 0.00:
2791
1617
            # Display a warning message if the quantity is under the minimal qty of the supplier
2799
1625
                      ('valid_from', '=', False),
2800
1626
                      '|', ('valid_till', '>=', date_order),
2801
1627
                      ('valid_till', '=', False)]
2802
 
 
 
1628
            
2803
1629
            domain_cur = [('currency_id', '=', currency_id)]
2804
1630
            domain_cur.extend(domain)
2805
 
 
 
1631
            
2806
1632
            info_prices = partner_price.search(cr, uid, domain_cur, order='sequence asc, min_quantity asc, id desc', limit=1, context=context)
2807
1633
            if not info_prices:
2808
1634
                info_prices = partner_price.search(cr, uid, domain, order='sequence asc, min_quantity asc, id desc', limit=1, context=context)
2809
 
 
 
1635
                
2810
1636
            if info_prices:
2811
1637
                info_price = partner_price.browse(cr, uid, info_prices[0], context=context)
2812
 
                info_u_price = self.pool.get('res.currency').compute(cr, uid, info_price.currency_id.id, currency_id, info_price.price, round=False, context=context)
 
1638
                info_u_price = self.pool.get('res.currency').compute(cr, uid, info_price.currency_id.id, currency_id, info_price.price)
2813
1639
                res['value'].update({'old_price_unit': info_u_price, 'price_unit': info_u_price})
2814
1640
                res.update({'warning': {'title': _('Warning'), 'message': _('The product unit price has been set ' \
2815
1641
                                                                                'for a minimal quantity of %s (the min quantity of the price list), '\
2816
1642
                                                                                'it might change at the supplier confirmation.') % info_price.min_quantity}})
2817
 
                if info_price.rounding and all_qty%info_price.rounding != 0:
2818
 
                    message = _('A rounding value of %s UoM has been set for ' \
2819
 
                            'this product, you should than modify ' \
2820
 
                            'the quantity ordered to match the supplier criteria.') % info_price.rounding
2821
 
                    message = '%s \n %s' % (res.get('warning', {}).get('message', ''), message)
2822
 
                    res['warning'].update({'message': message})
2823
1643
            else:
2824
 
                old_price = self.pool.get('res.currency').compute(cr, uid, func_curr_id, currency_id, res['value']['price_unit'], round=False, context=context)
 
1644
                old_price = self.pool.get('res.currency').compute(cr, uid, func_curr_id, currency_id, res['value']['price_unit'])
2825
1645
                res['value'].update({'old_price_unit': old_price})
2826
1646
        else:
2827
 
            old_price = self.pool.get('res.currency').compute(cr, uid, func_curr_id, currency_id, res.get('value').get('price_unit'), round=False, context=context)
 
1647
            old_price = self.pool.get('res.currency').compute(cr, uid, func_curr_id, currency_id, res.get('value').get('price_unit'))
2828
1648
            res['value'].update({'old_price_unit': old_price})
2829
 
 
 
1649
                
2830
1650
        # Set the unit price with cost price if the product has no staged pricelist
2831
 
        if product and qty != 0.00:
 
1651
        if product and qty != 0.00: 
2832
1652
            res['value'].update({'comment': False, 'nomen_manda_0': False, 'nomen_manda_1': False,
2833
 
                                 'nomen_manda_2': False, 'nomen_manda_3': False, 'nomen_sub_0': False,
2834
 
                                 'nomen_sub_1': False, 'nomen_sub_2': False, 'nomen_sub_3': False,
 
1653
                                 'nomen_manda_2': False, 'nomen_manda_3': False, 'nomen_sub_0': False, 
 
1654
                                 'nomen_sub_1': False, 'nomen_sub_2': False, 'nomen_sub_3': False, 
2835
1655
                                 'nomen_sub_4': False, 'nomen_sub_5': False})
2836
 
            st_uom = self.pool.get('product.product').browse(cr, uid, product).uom_id.id
2837
1656
            st_price = self.pool.get('product.product').browse(cr, uid, product).standard_price
2838
 
            st_price = self.pool.get('res.currency').compute(cr, uid, func_curr_id, currency_id, st_price, round=False, context=context)
2839
 
            st_price = self.pool.get('product.uom')._compute_price(cr, uid, st_uom, st_price, uom)
2840
 
 
 
1657
            st_price = self.pool.get('res.currency').compute(cr, uid, func_curr_id, currency_id, st_price)
 
1658
        
2841
1659
            if res.get('value', {}).get('price_unit', False) == False and (state and state == 'draft') or not state :
2842
1660
                res['value'].update({'price_unit': st_price, 'old_price_unit': st_price})
2843
1661
            elif state and state != 'draft' and old_price_unit:
2844
1662
                res['value'].update({'price_unit': old_price_unit, 'old_price_unit': old_price_unit})
2845
 
 
 
1663
                
2846
1664
            if res['value']['price_unit'] == 0.00:
2847
1665
                res['value'].update({'price_unit': st_price, 'old_price_unit': st_price})
2848
 
 
 
1666
                
2849
1667
        elif qty == 0.00:
2850
1668
            res['value'].update({'price_unit': 0.00, 'old_price_unit': 0.00})
2851
1669
        elif not product and not comment and not nomen_manda_0:
2852
1670
            res['value'].update({'price_unit': 0.00, 'product_qty': 0.00, 'product_uom': False, 'old_price_unit': 0.00})
2853
 
 
2854
 
 
2855
 
        if context and context.get('categ') and product:
2856
 
            # Check consistency of product
2857
 
            consistency_message = self.pool.get('product.product').check_consistency(cr, uid, product, context.get('categ'), context=context)
2858
 
            if consistency_message:
2859
 
                res.setdefault('warning', {})
2860
 
                res['warning'].setdefault('title', 'Warning')
2861
 
                res['warning'].setdefault('message', '')
2862
 
 
2863
 
                res['warning']['message'] = '%s \n %s' % (res.get('warning', {}).get('message', ''), consistency_message)
2864
 
 
 
1671
        
2865
1672
        return res
2866
1673
 
2867
 
    def price_unit_change(self, cr, uid, ids, fake_id, price_unit, product_id,
2868
 
                          product_uom, product_qty, pricelist, partner_id, date_order,
2869
 
                          change_price_ok, state, old_price_unit,
 
1674
    def price_unit_change(self, cr, uid, ids, fake_id, price_unit, product_id, 
 
1675
                          product_uom, product_qty, pricelist, partner_id, date_order, 
 
1676
                          change_price_ok, state, old_price_unit, 
2870
1677
                          nomen_manda_0=False, comment=False, context=None):
2871
1678
        '''
2872
1679
        Display a warning message on change price unit if there are other lines with the same product and the same uom
2875
1682
 
2876
1683
        if context is None:
2877
1684
            context = {}
2878
 
 
 
1685
            
2879
1686
        if not product_id or not product_uom or not product_qty:
2880
1687
            return res
2881
 
 
 
1688
        
2882
1689
        order_id = context.get('purchase_id', False)
2883
1690
        if not order_id:
2884
1691
            return res
2886
1693
        order = self.pool.get('purchase.order').browse(cr, uid, order_id, context=context)
2887
1694
        other_lines = self.search(cr, uid, [('id', '!=', fake_id), ('order_id', '=', order_id), ('product_id', '=', product_id), ('product_uom', '=', product_uom)], context=context)
2888
1695
        stages = self._get_stages_price(cr, uid, product_id, product_uom, order, context=context)
2889
 
 
 
1696
        
2890
1697
        if not change_price_ok or (other_lines and stages and order.state != 'confirmed' and not context.get('rfq_ok')):
2891
1698
            res.update({'warning': {'title': 'Error',
2892
1699
                                    'message': 'This product get stages prices for this supplier, you cannot change the price manually in draft state '\
2896
1703
            res['value'].update({'old_price_unit': price_unit})
2897
1704
 
2898
1705
        return res
2899
 
 
 
1706
    
2900
1707
    def get_sol_ids_from_pol_ids(self, cr, uid, ids, context=None):
2901
1708
        '''
2902
1709
        input: purchase order line ids
2907
1714
            context = {}
2908
1715
        if isinstance(ids, (int, long)):
2909
1716
            ids = [ids]
2910
 
 
 
1717
            
2911
1718
        # objects
2912
1719
        sol_obj = self.pool.get('sale.order.line')
2913
1720
        # procurement ids list
2914
1721
        proc_ids = []
2915
1722
        # sale order lines list
2916
1723
        sol_ids = []
2917
 
 
 
1724
        
2918
1725
        for line in self.browse(cr, uid, ids, context=context):
2919
1726
            if line.procurement_id:
2920
1727
                proc_ids.append(line.procurement_id.id)
2929
1736
        '''
2930
1737
        if not context:
2931
1738
            context = {}
2932
 
 
 
1739
 
2933
1740
        if isinstance(ids, (int, long)):
2934
1741
            ids = [ids]
2935
1742
 
2947
1754
 
2948
1755
purchase_order_line()
2949
1756
 
2950
 
class purchase_order_group(osv.osv_memory):
2951
 
    _name = "purchase.order.group"
2952
 
    _inherit = "purchase.order.group"
2953
 
    _description = "Purchase Order Merge"
2954
 
 
2955
 
    _columns = {
2956
 
        'po_value_id': fields.many2one('purchase.order', string='Template PO', help='All values in this PO will be used as default values for the merged PO'),
2957
 
        'unmatched_categ': fields.boolean(string='Unmatched categories'),
2958
 
    }
2959
 
 
2960
 
    def default_get(self, cr, uid, fields, context=None):
2961
 
        res = super(purchase_order_group, self).default_get(cr, uid, fields, context=context)
2962
 
        if context.get('active_model','') == 'purchase.order' and len(context['active_ids']) < 2:
2963
 
            raise osv.except_osv(_('Warning'),
2964
 
            _('Please select multiple order to merge in the list view.'))
2965
 
 
2966
 
        res['po_value_id'] = context['active_ids'][-1]
2967
 
 
2968
 
        categories = set()
2969
 
        for po in self.pool.get('purchase.order').read(cr, uid, context['active_ids'], ['categ'], context=context):
2970
 
            categories.add(po['categ'])
2971
 
 
2972
 
        if len(categories) > 1:
2973
 
            res['unmatched_categ'] = True
2974
 
 
2975
 
        return res
2976
 
 
2977
 
    def merge_orders(self, cr, uid, ids, context=None):
2978
 
        res = super(purchase_order_group, self).merge_orders(cr, uid, ids, context=context)
2979
 
        res.update({'context': {'search_default_draft': 1, 'search_default_approved': 0,'search_default_create_uid':uid, 'purchase_order': True}})
2980
 
 
2981
 
        if 'domain' in res and eval(res['domain'])[0][2]:
2982
 
            return res
2983
 
 
2984
 
        raise osv.except_osv(_('Error'), _('No PO merged !'))
2985
 
        return {'type': 'ir.actions.act_window_close'}
2986
 
 
2987
 
purchase_order_group()
2988
 
 
2989
 
class product_product(osv.osv):
2990
 
    _name = 'product.product'
2991
 
    _inherit = 'product.product'
2992
 
 
2993
 
    def _product_price(self, cr, uid, ids, field_name, args, context=None):
2994
 
        res = super(product_product, self)._product_price(cr, uid, ids, field_name, args, context=context)
2995
 
 
2996
 
        for product in res:
2997
 
            if res[product] == 0.00:
2998
 
                try:
2999
 
                    res[product] = self.pool.get('product.product').read(cr, uid, [product], ['standard_price'], context=context)[0]['standard_price']
3000
 
                except:
3001
 
                    pass
3002
 
 
3003
 
        return res
3004
 
 
3005
 
    def _get_purchase_type(self, cr, uid, ids, field_name, args, context=None):
3006
 
        res = {}
3007
 
        for p_id in ids:
3008
 
            res[p_id] = True
3009
 
 
3010
 
        return res
3011
 
 
3012
 
    def _src_purchase_type(self, cr, uid, obj, name, args, context=None):
3013
 
        '''
3014
 
        Returns a domain according to the PO type
3015
 
        '''
3016
 
        res = []
3017
 
        for arg in args:
3018
 
            if arg[0] == 'purchase_type':
3019
 
                if arg[1] != '=':
3020
 
                    raise osv.except_osv(_('Error'), _('Only the \'=\' operator is allowed.'))
3021
 
                # Returns all service products
3022
 
                if arg[2] == 'service':
3023
 
                    res.append(('type', '=', 'service_recep'))
3024
 
                elif arg[2] == 'transport':
3025
 
                    res.append(('transport_ok', '=', True))
3026
 
 
3027
 
        return res
3028
 
 
3029
 
    _columns = {
3030
 
 
3031
 
        'purchase_type': fields.function(_get_purchase_type, fnct_search=_src_purchase_type, type='boolean', string='Purchase type', method=True, store=False),
3032
 
        'price': fields.function(_product_price, method=True, type='float', string='Pricelist', digits_compute=dp.get_precision('Sale Price')),
3033
 
    }
3034
 
 
3035
 
    def check_consistency(self, cr, uid, product_id, category, context=None):
3036
 
        '''
3037
 
        Check the consistency of product according to category
3038
 
        '''
3039
 
        context = context is None and {} or context
3040
 
        display_message = False
3041
 
 
3042
 
        # No check for Other
3043
 
        if category == 'other':
3044
 
            return False
3045
 
 
3046
 
        product = self.read(cr, uid, product_id, ['nomen_manda_0', 'type', 'transport_ok'], context=context)
3047
 
        transport_product = product['transport_ok']
3048
 
        product_type = product['type']
3049
 
        main_type = product['nomen_manda_0'][0]
3050
 
 
3051
 
        if category == 'medical':
3052
 
            try:
3053
 
                med_nomen = self.pool.get('product.nomenclature').search(cr, uid, [('level', '=', 0), ('name', '=', 'MED')], context=context)[0]
3054
 
            except IndexError:
3055
 
                raise osv.except_osv(_('Error'), _('MED nomenclature Main Type not found'))
3056
 
 
3057
 
            if main_type != med_nomen:
3058
 
                display_message = True
3059
 
 
3060
 
        if category == 'log':
3061
 
            try:
3062
 
                log_nomen = self.pool.get('product.nomenclature').search(cr, uid, [('level', '=', 0), ('name', '=', 'LOG')], context=context)[0]
3063
 
            except IndexError:
3064
 
                raise osv.except_osv(_('Error'), _('LOG nomenclature Main Type not found'))
3065
 
 
3066
 
            if main_type != log_nomen:
3067
 
                display_message = True
3068
 
 
3069
 
        if category == 'service' and product_type != 'service_recep':
3070
 
            display_message = True
3071
 
 
3072
 
        if category == 'transport' and (product_type != 'service_recep' or not transport_product):
3073
 
            display_message = True
3074
 
 
3075
 
        if display_message:
3076
 
            return 'Warning you are about to add a product which does not conform to this PO’s order category, do you wish to proceed ?'
3077
 
        else:
3078
 
            return False
3079
 
 
3080
 
product_product()
3081
 
 
3082
 
class purchase_order_line_unlink_wizard(osv.osv_memory):
3083
 
    _name = 'purchase.order.line.unlink.wizard'
3084
 
 
3085
 
    _columns = {
3086
 
        'line_id': fields.many2one('purchase.order.line', 'Line to delete'),
3087
 
    }
3088
 
 
3089
 
    def just_cancel(self, cr, uid, ids, context=None):
3090
 
        '''
3091
 
        Cancel the line
3092
 
        '''
3093
 
        # Objects
3094
 
        line_obj = self.pool.get('purchase.order.line')
3095
 
        order_wiz_obj = self.pool.get('purchase.order.cancel.wizard')
3096
 
        data_obj = self.pool.get('ir.model.data')
3097
 
        po_obj = self.pool.get('purchase.order')
3098
 
 
3099
 
        # Variables
3100
 
        if context is None:
3101
 
            context = {}
3102
 
 
3103
 
        if isinstance(ids, (int, long)):
3104
 
            ids = [ids]
3105
 
 
3106
 
        line_ids =[]
3107
 
        po_ids = set()
3108
 
        for wiz in self.browse(cr, uid, ids, context=context):
3109
 
            po_ids.add(wiz.line_id.order_id.id)
3110
 
            line_ids.append(wiz.line_id.id)
3111
 
 
3112
 
        if context.get('has_to_be_resourced'):
3113
 
            line_obj.write(cr, uid, line_ids, {'has_to_be_resourced': True}, context=context)
3114
 
 
3115
 
        line_obj.fake_unlink(cr, uid, line_ids, context=context)
3116
 
 
3117
 
        for po in po_obj.browse(cr, uid, list(po_ids), context=context):
3118
 
            if all(x.state in ('cancel', 'done') for x in po.order_line):
3119
 
                wiz_id = order_wiz_obj.create(cr, uid, {'order_id': po.id}, context=context)
3120
 
                view_id = data_obj.get_object_reference(cr, uid, 'purchase_override', 'ask_po_cancel_wizard_form_view')[1]
3121
 
                context['view_id'] = False
3122
 
                return {'type': 'ir.actions.act_window',
3123
 
                        'res_model': 'purchase.order.cancel.wizard',
3124
 
                        'view_type': 'form',
3125
 
                        'view_mode': 'form',
3126
 
                        'view_id': [view_id],
3127
 
                        'res_id': wiz_id,
3128
 
                        'target': 'new',
3129
 
                        'context': context}
3130
 
 
3131
 
        return {'type': 'ir.actions.act_window_close'}
3132
 
 
3133
 
 
3134
 
    def cancel_and_resource(self, cr, uid, ids, context=None):
3135
 
        '''
3136
 
        Flag the line to be re-sourced and run cancel method
3137
 
        '''
3138
 
        # Objects
3139
 
        if context is None:
3140
 
            context = {}
3141
 
 
3142
 
        context['has_to_be_resourced'] = True
3143
 
 
3144
 
        return self.just_cancel(cr, uid, ids, context=context)
3145
 
 
3146
 
purchase_order_line_unlink_wizard()
3147
 
 
3148
 
 
3149
 
class purchase_order_cancel_wizard(osv.osv_memory):
3150
 
    _name = 'purchase.order.cancel.wizard'
3151
 
 
3152
 
    _columns = {'order_id': fields.many2one('purchase.order', 'Order to delete'),
3153
 
                'unlink_po': fields.boolean(string='Unlink PO'),}
3154
 
 
3155
 
    def fields_view_get(self, cr, uid, view_id=False, view_type='form', context=None, toolbar=False, submenu=False):
3156
 
        return super(purchase_order_cancel_wizard, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu)
3157
 
 
3158
 
    def ask_unlink(self, cr, uid, order_id, context=None):
3159
 
        '''
3160
 
        Return the wizard
3161
 
        '''
3162
 
        data_obj = self.pool.get('ir.model.data')
3163
 
 
3164
 
        if context is None:
3165
 
            context = {}
3166
 
 
3167
 
        view_id = data_obj.get_object_reference(cr, uid, 'purchase_override', 'ask_po_cancel_wizard_form_view')[1]
3168
 
        wiz_id = self.create(cr, uid, {'order_id': order_id}, context=context)
3169
 
 
3170
 
        return {'type': 'ir.actions.act_window',
3171
 
                'res_model': 'purchase.order.cancel.wizard',
3172
 
                'res_id': wiz_id,
3173
 
                'view_id': [view_id],
3174
 
                'view_type': 'form',
3175
 
                'view_mode': 'form',
3176
 
                'target': 'new',
3177
 
                'context': context}
3178
 
 
3179
 
    def close_window(self, cr, uid, ids, context=None):
3180
 
        '''
3181
 
        Close the pop-up and reload the PO
3182
 
        '''
3183
 
        return {'type': 'ir.actions.act_window_close'}
3184
 
 
3185
 
    def cancel_po(self, cr, uid, ids, context=None):
3186
 
        '''
3187
 
        Cancel the PO and display his form
3188
 
        '''
3189
 
        po_obj = self.pool.get('purchase.order')
3190
 
        line_obj = self.pool.get('purchase.order.line')
3191
 
        wf_service = netsvc.LocalService("workflow")
3192
 
 
3193
 
        if context is None:
3194
 
            context = {}
3195
 
 
3196
 
        if isinstance(ids, (int, long)):
3197
 
            ids = [ids]
3198
 
 
3199
 
        line_ids = []
3200
 
        order_ids = []
3201
 
        for wiz in self.browse(cr, uid, ids, context=context):
3202
 
            order_ids.append(wiz.order_id.id)
3203
 
            if context.get('has_to_be_resourced'):
3204
 
                line_ids.extend([l.id for l in wiz.order_id.order_line])
3205
 
 
3206
 
        # Mark lines as 'To be resourced'
3207
 
        line_obj.write(cr, uid, line_ids, {'has_to_be_resourced': True}, context=context)
3208
 
 
3209
 
        po_obj.write(cr, uid, order_ids, {'canceled_end': True}, context=context)
3210
 
        for order_id in order_ids:
3211
 
            wf_service.trg_validate(uid, 'purchase.order', order_id, 'purchase_cancel', cr)
3212
 
 
3213
 
        return {'type': 'ir.actions.act_window_close'}
3214
 
 
3215
 
    def cancel_and_resource(self, cr, uid, ids, context=None):
3216
 
        if context is None:
3217
 
            context = {}
3218
 
 
3219
 
        context['has_to_be_resourced'] = True
3220
 
 
3221
 
        return self.cancel_po(cr, uid, ids, context=context)
3222
 
 
3223
 
purchase_order_cancel_wizard()
3224
 
 
3225
 
class res_partner(osv.osv):
3226
 
    _inherit = 'res.partner'
3227
 
 
3228
 
    def address_multiple_get(self, cr, uid, ids, adr_pref=['default']):
3229
 
        address_obj = self.pool.get('res.partner.address')
3230
 
        address_ids = address_obj.search(cr, uid, [('partner_id', '=', ids)])
3231
 
        address_rec = address_obj.read(cr, uid, address_ids, ['type'])
3232
 
        res= {}
3233
 
        for addr in address_rec:
3234
 
            res.setdefault(addr['type'], [])
3235
 
            res[addr['type']].append(addr['id'])
3236
 
        if res:
3237
 
            default_address = res.get('default', False)
3238
 
        else:
3239
 
            default_address = False
3240
 
        result = {}
3241
 
        for a in adr_pref:
3242
 
            result[a] = res.get(a, default_address)
3243
 
 
3244
 
        return result
3245
 
 
3246
 
res_partner()
3247
 
 
3248
 
 
3249
 
class res_partner_address(osv.osv):
3250
 
    _inherit = 'res.partner.address'
3251
 
 
3252
 
    def _get_dummy(self, cr, uid, ids, field_name, args, context=None):
3253
 
        res = {}
3254
 
        for a_id in ids:
3255
 
            res[a_id] = True
3256
 
 
3257
 
        return res
3258
 
 
3259
 
    def _src_address(self, cr, uid, obj, name, args, context=None):
3260
 
        '''
3261
 
        Returns all the destination addresses of a partner or all default
3262
 
        addresses if he hasn't destination addresses
3263
 
        '''
3264
 
        partner_obj = self.pool.get('res.partner')
3265
 
        user_obj = self.pool.get('res.users')
3266
 
        res = []
3267
 
 
3268
 
        for arg in args:
3269
 
            if arg[0] == 'dest_address':
3270
 
                addr_type = 'delivery'
3271
 
            elif arg[0] == 'inv_address':
3272
 
                addr_type = 'invoice'
3273
 
 
3274
 
            if arg[2]:
3275
 
                partner_id = arg[2]
3276
 
            else:
3277
 
                partner_id = user_obj.browse(cr, uid, uid, context=context).company_id.partner_id.id
3278
 
                if arg[1] == 'in':
3279
 
                    partner_id = [partner_id]
3280
 
 
3281
 
            addr_ids = []
3282
 
            if isinstance(partner_id, list):
3283
 
                for partner in partner_id:
3284
 
                    if not partner:
3285
 
                        continue
3286
 
                    addr_ids.extend(partner_obj.address_multiple_get(cr, uid, partner, [addr_type])[addr_type])
3287
 
 
3288
 
            else:
3289
 
                addr_ids = partner_obj.address_multiple_get(cr, uid, partner_id, [addr_type])[addr_type]
3290
 
 
3291
 
            res.append(('id', 'in', list(i for i in addr_ids if i)))
3292
 
 
3293
 
        return res
3294
 
 
3295
 
    _columns = {
3296
 
        'dest_address': fields.function(_get_dummy, fnct_search=_src_address, method=True,
3297
 
                                           type='boolean', string='Dest. Address', store=False),
3298
 
        'inv_address': fields.function(_get_dummy, fnct_search=_src_address, method=True,
3299
 
                                           type='boolean', string='Invoice Address', store=False),
3300
 
    }
3301
 
 
3302
 
 
3303
 
res_partner_address()
 
1757
class account_invoice(osv.osv):
 
1758
    _name = 'account.invoice'
 
1759
    _inherit = 'account.invoice'
 
1760
    
 
1761
    _columns = {
 
1762
        '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)]}),
 
1763
    }
 
1764
    
 
1765
account_invoice()
3304
1766
 
3305
1767
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: