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

« back to all changes in this revision

Viewing changes to tender_flow/tender_flow.py

UF-358 [ADD] Initial creation : backup of this day

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: utf-8 -*-
2
 
##############################################################################
3
 
#
4
 
#    Copyright (C) 2011 MSF, TeMPO Consulting
5
 
#
6
 
#    This program is free software: you can redistribute it and/or modify
7
 
#    it under the terms of the GNU Affero General Public License as
8
 
#    published by the Free Software Foundation, either version 3 of the
9
 
#    License, or (at your option) any later version.
10
 
#
11
 
#    This program is distributed in the hope that it will be useful,
12
 
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 
#    GNU Affero General Public License for more details.
15
 
#
16
 
#    You should have received a copy of the GNU Affero General Public License
17
 
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
 
#
19
 
##############################################################################
20
 
 
21
 
from datetime import datetime, timedelta, date
22
 
from dateutil.relativedelta import relativedelta, relativedelta
23
 
from order_types import ORDER_PRIORITY, ORDER_CATEGORY
24
 
from osv import osv, fields
25
 
from osv.orm import browse_record, browse_null
26
 
from tools.translate import _
27
 
from lxml import etree
28
 
 
29
 
import decimal_precision as dp
30
 
import netsvc
31
 
import pooler
32
 
import time
33
 
 
34
 
# xml parser
35
 
from lxml import etree
36
 
 
37
 
from purchase_override import PURCHASE_ORDER_STATE_SELECTION
38
 
 
39
 
class tender(osv.osv):
40
 
    '''
41
 
    tender class
42
 
    '''
43
 
    _name = 'tender'
44
 
    _description = 'Tender'
45
 
 
46
 
    def copy(self, cr, uid, id, default=None, context=None, done_list=[], local=False):
47
 
        if not default:
48
 
            default = {}
49
 
        default['internal_state'] = 'draft' # UF-733: Reset the internal_state
50
 
        return super(osv.osv, self).copy(cr, uid, id, default, context=context)
51
 
    
52
 
    def unlink(self, cr, uid, ids, context=None):
53
 
        '''
54
 
        cannot delete tender not draft
55
 
        '''
56
 
        if context is None:
57
 
            context = {}
58
 
        if isinstance(ids, (int, long)):
59
 
            ids = [ids]
60
 
            
61
 
        for obj in self.browse(cr, uid, ids, context=context):
62
 
            if obj.state != 'draft':
63
 
                raise osv.except_osv(_('Warning !'), _("Cannot delete Tenders not in 'draft' state."))
64
 
        return super(tender, self).unlink(cr, uid, ids, context=context)
65
 
    
66
 
    def _vals_get(self, cr, uid, ids, fields, arg, context=None):
67
 
        '''
68
 
        return function values
69
 
        '''
70
 
        result = {}
71
 
        for obj in self.browse(cr, uid, ids, context=context):
72
 
            result[obj.id] = {'rfq_name_list': '',
73
 
                              }
74
 
            rfq_names = []
75
 
            for rfq in obj.rfq_ids:
76
 
                rfq_names.append(rfq.name)
77
 
            # generate string
78
 
            rfq_names.sort()
79
 
            result[obj.id]['rfq_name_list'] = ','.join(rfq_names)
80
 
            
81
 
        return result
82
 
 
83
 
    def _is_tender_from_fo(self, cr, uid, ids, field_name, args, context=None):
84
 
        res = {}
85
 
        for tender in self.browse(cr, uid, ids, context=context):
86
 
            retour = False
87
 
            ids_proc = self.pool.get('procurement.order').search(cr,uid,[('tender_id','=',tender.id)])
88
 
            ids_sol = self.pool.get('sale.order.line').search(cr,uid,[('procurement_id','in',ids_proc),('order_id.procurement_request','=',False)])
89
 
            if ids_sol:
90
 
                retour = True
91
 
            res[tender.id] = retour
92
 
        return res
93
 
 
94
 
    _columns = {'name': fields.char('Tender Reference', size=64, required=True, select=True, readonly=True),
95
 
                'sale_order_id': fields.many2one('sale.order', string="Sale Order", readonly=True),
96
 
                'state': fields.selection([('draft', 'Draft'),('comparison', 'Comparison'), ('done', 'Closed'), ('cancel', 'Cancelled'),], string="State", readonly=True),
97
 
                'supplier_ids': fields.many2many('res.partner', 'tender_supplier_rel', 'tender_id', 'supplier_id', string="Suppliers", domain="[('id', '!=', company_id)]",
98
 
                                                 states={'draft':[('readonly',False)]}, readonly=True,
99
 
                                                 context={'search_default_supplier': 1,}),
100
 
                'location_id': fields.many2one('stock.location', 'Location', required=True, states={'draft':[('readonly',False)]}, readonly=True, domain=[('usage', '=', 'internal')]),
101
 
                'company_id': fields.many2one('res.company','Company',required=True, states={'draft':[('readonly',False)]}, readonly=True),
102
 
                'rfq_ids': fields.one2many('purchase.order', 'tender_id', string="RfQs", readonly=True),
103
 
                'priority': fields.selection(ORDER_PRIORITY, string='Tender Priority', states={'draft':[('readonly',False)],}, readonly=True,),
104
 
                'categ': fields.selection(ORDER_CATEGORY, string='Tender Category', required=True, states={'draft':[('readonly',False)],}, readonly=True),
105
 
                'creator': fields.many2one('res.users', string="Creator", readonly=True, required=True,),
106
 
                'warehouse_id': fields.many2one('stock.warehouse', string="Warehouse", required=True, states={'draft':[('readonly',False)],}, readonly=True),
107
 
                'creation_date': fields.date(string="Creation Date", readonly=True, states={'draft':[('readonly',False)]}),
108
 
                'details': fields.char(size=30, string="Details", states={'draft':[('readonly',False)],}, readonly=True),
109
 
                'requested_date': fields.date(string="Requested Date", required=True, states={'draft':[('readonly',False)],}, readonly=True),
110
 
                'notes': fields.text('Notes'),
111
 
                'internal_state': fields.selection([('draft', 'Draft'),('updated', 'Rfq Updated'), ], string="Internal State", readonly=True),
112
 
                'rfq_name_list': fields.function(_vals_get, method=True, string='RfQs Ref', type='char', readonly=True, store=False, multi='get_vals',),
113
 
                'product_id': fields.related('tender_line_ids', 'product_id', type='many2one', relation='product.product', string='Product'),
114
 
               'tender_from_fo': fields.function(_is_tender_from_fo, method=True, type='boolean', string='Is tender from FO ?',),
115
 
                }
116
 
    
117
 
    _defaults = {'categ': 'other',
118
 
                 'state': 'draft',
119
 
                 'internal_state': 'draft',
120
 
                 'company_id': lambda obj, cr, uid, context: obj.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id,
121
 
                 'creator': lambda obj, cr, uid, context: uid,
122
 
                 'creation_date': lambda *a: time.strftime('%Y-%m-%d'),
123
 
                 'requested_date': lambda *a: time.strftime('%Y-%m-%d'),
124
 
                 'priority': 'normal',
125
 
                 'warehouse_id': lambda obj, cr, uid, context: len(obj.pool.get('stock.warehouse').search(cr, uid, [])) and obj.pool.get('stock.warehouse').search(cr, uid, [])[0],
126
 
                 }
127
 
    
128
 
    _order = 'name desc'
129
 
 
130
 
    def _check_tender_from_fo(self, cr, uid, ids, context=None):
131
 
        if not context:
132
 
            context = {}
133
 
        retour = True
134
 
        for tender in self.browse(cr, uid, ids, context=context):
135
 
            if not tender.tender_from_fo:
136
 
                return retour
137
 
            for sup in tender.supplier_ids:
138
 
                if sup.partner_type == 'internal' :
139
 
                    retour = False
140
 
        return retour
141
 
 
142
 
    _constraints = [
143
 
        (_check_tender_from_fo, 'You cannot choose an internal supplier for this tender', []),
144
 
    ]
145
 
    
146
 
    def create(self, cr, uid, vals, context=None):
147
 
        '''
148
 
        Set the reference of the tender at this time
149
 
        '''
150
 
        if not vals.get('name', False):
151
 
            vals.update({'name': self.pool.get('ir.sequence').get(cr, uid, 'tender')})
152
 
        return super(tender, self).create(cr, uid, vals, context=context)
153
 
    
154
 
    def onchange_warehouse(self, cr, uid, ids, warehouse_id, context=None):
155
 
        '''
156
 
        on_change function for the warehouse
157
 
        '''
158
 
        result = {'value':{},}
159
 
        if warehouse_id:
160
 
            input_loc_id = self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id, context=context).lot_input_id.id
161
 
            result['value'].update(location_id=input_loc_id)
162
 
        
163
 
        return result
164
 
    
165
 
    def wkf_generate_rfq(self, cr, uid, ids, context=None):
166
 
        '''
167
 
        generate the rfqs for each specified supplier
168
 
        '''
169
 
        if context is None:
170
 
            context = {}
171
 
        po_obj = self.pool.get('purchase.order')
172
 
        pol_obj = self.pool.get('purchase.order.line')
173
 
        partner_obj = self.pool.get('res.partner')
174
 
        pricelist_obj = self.pool.get('product.pricelist')
175
 
        obj_data = self.pool.get('ir.model.data')
176
 
        # no suppliers -> raise error
177
 
        for tender in self.browse(cr, uid, ids, context=context):
178
 
            # check some supplier have been selected
179
 
            if not tender.supplier_ids:
180
 
                raise osv.except_osv(_('Warning !'), _('You must select at least one supplier!'))
181
 
            #utp-315: check that the suppliers are not inactive (I use a SQL request because the inactive partner are ignored with the browse)
182
 
            sql = """
183
 
            select tsr.supplier_id, rp.name, rp.active
184
 
            from tender_supplier_rel tsr
185
 
            left join res_partner rp
186
 
            on tsr.supplier_id = rp.id
187
 
            where tsr.tender_id=%s
188
 
            and rp.active=False
189
 
            """
190
 
            cr.execute(sql, (ids[0],))
191
 
            inactive_supplier_ids = cr.dictfetchall()
192
 
            if any(inactive_supplier_ids):
193
 
                raise osv.except_osv(_('Warning !'), _("You can't have inactive supplier! Please remove: %s"
194
 
                                                       ) % ' ,'.join([partner['name'] for partner in inactive_supplier_ids]))
195
 
            # check some products have been selected
196
 
            tender_line_ids = self.pool.get('tender.line').search(cr, uid, [('tender_id', '=', tender.id)], context=context)
197
 
            if not tender_line_ids:
198
 
                raise osv.except_osv(_('Warning !'), _('You must select at least one product!'))
199
 
            for supplier in tender.supplier_ids:
200
 
                # create a purchase order for each supplier
201
 
                address_id = partner_obj.address_get(cr, uid, [supplier.id], ['delivery'])['delivery']
202
 
                if not address_id:
203
 
                    raise osv.except_osv(_('Warning !'), _('The supplier "%s" has no address defined!')%(supplier.name,))
204
 
                pricelist_id = supplier.property_product_pricelist_purchase.id
205
 
                values = {'origin': tender.sale_order_id and tender.sale_order_id.name + ';' + tender.name or tender.name,
206
 
                          'rfq_ok': True,
207
 
                          'partner_id': supplier.id,
208
 
                          'partner_address_id': address_id,
209
 
                          'location_id': tender.location_id.id,
210
 
                          'pricelist_id': pricelist_id,
211
 
                          'company_id': tender.company_id.id,
212
 
                          'fiscal_position': supplier.property_account_position and supplier.property_account_position.id or False,
213
 
                          'tender_id': tender.id,
214
 
                          'warehouse_id': tender.warehouse_id.id,
215
 
                          'categ': tender.categ,
216
 
                          'priority': tender.priority,
217
 
                          'details': tender.details,
218
 
                          'delivery_requested_date': tender.requested_date,
219
 
                          }
220
 
                # create the rfq - dic is udpated for default partner_address_id at purchase.order level
221
 
                po_id = po_obj.create(cr, uid, values, context=dict(context, partner_id=supplier.id, rfq_ok=True))
222
 
                
223
 
                for line in tender.tender_line_ids:
224
 
                    if line.product_id.id == obj_data.get_object_reference(cr, uid,'msf_doc_import', 'product_tbd')[1]:
225
 
                        raise osv.except_osv(_('Warning !'), _('You can\'t have "To Be Defined" for the product. Please select an existing product.'))
226
 
                    # create an order line for each tender line
227
 
                    price = pricelist_obj.price_get(cr, uid, [pricelist_id], line.product_id.id, line.qty, supplier.id, {'uom': line.product_uom.id})[pricelist_id]
228
 
                    newdate = datetime.strptime(line.date_planned, '%Y-%m-%d')
229
 
                    #newdate = (newdate - relativedelta(days=tender.company_id.po_lead)) - relativedelta(days=int(supplier.default_delay)) # requested by Magali uf-489
230
 
                    values = {'name': line.product_id.partner_ref,
231
 
                              'product_qty': line.qty,
232
 
                              'product_id': line.product_id.id,
233
 
                              'product_uom': line.product_uom.id,
234
 
                              'price_unit': 0.0, # was price variable - uf-607
235
 
                              'date_planned': newdate.strftime('%Y-%m-%d'),
236
 
                              'notes': line.product_id.description_purchase,
237
 
                              'order_id': po_id,
238
 
                              }
239
 
                    # create purchase order line
240
 
                    pol_id = pol_obj.create(cr, uid, values, context=context)
241
 
                    message = "Request for Quotation '%s' has been created."%po_obj.browse(cr, uid, po_id, context=context).name
242
 
                    # create the log message
243
 
                    self.pool.get('res.log').create(cr, uid,
244
 
                                                           {'name': message,
245
 
                                                            'res_model': po_obj._name,
246
 
                                                            'secondary': False,
247
 
                                                            'res_id': po_id,
248
 
                                                            'domain': [('rfq_ok', '=', True)],
249
 
                                                            }, context={'rfq_ok': True})
250
 
            
251
 
        self.write(cr, uid, ids, {'state':'comparison'}, context=context)
252
 
        return True
253
 
    
254
 
    def wkf_action_done(self, cr, uid, ids, context=None):
255
 
        '''
256
 
        tender is done
257
 
        '''
258
 
        # done all related rfqs
259
 
        wf_service = netsvc.LocalService("workflow")
260
 
        for tender in self.browse(cr, uid, ids, context=context):
261
 
            rfq_list = []
262
 
            for rfq in tender.rfq_ids:
263
 
                if rfq.state not in ('rfq_updated', 'cancel',):
264
 
                    rfq_list.append(rfq.id)
265
 
                else:
266
 
                    wf_service.trg_validate(uid, 'purchase.order', rfq.id, 'rfq_done', cr)
267
 
                
268
 
            # if some rfq have wrong state, we display a message
269
 
            if rfq_list:
270
 
                raise osv.except_osv(_('Warning !'), _("Generated RfQs must be Updated or Cancelled."))
271
 
            
272
 
            # integrity check, all lines must have purchase_order_line_id
273
 
            if not all([line.purchase_order_line_id.id for line in tender.tender_line_ids]):
274
 
                raise osv.except_osv(_('Error !'), _('All tender lines must have been compared!'))
275
 
        
276
 
        # update product supplierinfo and pricelist
277
 
        self.update_supplier_info(cr, uid, ids, context=context, integrity_test=False,)
278
 
        # change tender state
279
 
        self.write(cr, uid, ids, {'state':'done'}, context=context)
280
 
        return True
281
 
    
282
 
    def tender_integrity(self, cr, uid, tender, context=None):
283
 
        '''
284
 
        check the state of corresponding RfQs
285
 
        '''
286
 
        po_obj = self.pool.get('purchase.order')
287
 
        # no rfq in done state
288
 
        rfq_ids = po_obj.search(cr, uid, [('tender_id', '=', tender.id),
289
 
                                          ('state', 'in', ('done',)),], context=context)
290
 
        if rfq_ids:
291
 
            raise osv.except_osv(_('Error !'), _("Some RfQ are already Closed. Integrity failure."))
292
 
        # all rfqs must have been treated
293
 
        rfq_ids = po_obj.search(cr, uid, [('tender_id', '=', tender.id),
294
 
                                          ('state', 'in', ('draft', 'rfq_sent',)),], context=context)
295
 
        if rfq_ids:
296
 
            raise osv.except_osv(_('Warning !'), _("Generated RfQs must be Updated or Cancelled."))
297
 
        # at least one rfq must be updated and not canceled
298
 
        rfq_ids = po_obj.search(cr, uid, [('tender_id', '=', tender.id),
299
 
                                          ('state', 'in', ('rfq_updated',)),], context=context)
300
 
        if not rfq_ids:
301
 
            raise osv.except_osv(_('Warning !'), _("At least one RfQ must be in state Updated."))
302
 
        
303
 
        return rfq_ids
304
 
    
305
 
    def compare_rfqs(self, cr, uid, ids, context=None):
306
 
        '''
307
 
        compare rfqs button
308
 
        '''
309
 
        if len(ids) > 1:
310
 
            raise osv.except_osv(_('Warning !'), _('Cannot compare rfqs of more than one tender at a time!'))
311
 
        po_obj = self.pool.get('purchase.order')
312
 
        wiz_obj = self.pool.get('wizard.compare.rfq')
313
 
        for tender in self.browse(cr, uid, ids, context=context):
314
 
            # check if corresponding rfqs are in the good state
315
 
            rfq_ids = self.tender_integrity(cr, uid, tender, context=context)
316
 
            # gather the product_id -> supplier_id relationship to display it back in the compare wizard
317
 
            suppliers = {}
318
 
            for line in tender.tender_line_ids:
319
 
                if line.product_id and line.supplier_id:
320
 
                    suppliers.update({line.product_id.id:line.supplier_id.id,})
321
 
            # rfq corresponding to this tender with done state (has been updated and not canceled)
322
 
            # the list of rfq which will be compared
323
 
            c = dict(context, active_ids=rfq_ids, tender_id=tender.id, end_wizard=False, suppliers=suppliers,)
324
 
            # open the wizard
325
 
            action = wiz_obj.start_compare_rfq(cr, uid, ids, context=c)
326
 
        return action
327
 
    
328
 
    def update_supplier_info(self, cr, uid, ids, context=None, *args, **kwargs):
329
 
        '''
330
 
        update the supplier info of corresponding products
331
 
        '''
332
 
        info_obj = self.pool.get('product.supplierinfo')
333
 
        pricelist_info_obj = self.pool.get('pricelist.partnerinfo')
334
 
        # integrity check flag
335
 
        integrity_test = kwargs.get('integrity_test', False)
336
 
        for tender in self.browse(cr, uid, ids, context=context):
337
 
            # flag if at least one update
338
 
            updated = tender.tender_line_ids and False or True
339
 
            # check if corresponding rfqs are in the good state
340
 
            if integrity_test:
341
 
                self.tender_integrity(cr, uid, tender, context=context)
342
 
            for line in tender.tender_line_ids:
343
 
                # if a supplier has been selected
344
 
                if line.purchase_order_line_id:
345
 
                    # set the flag
346
 
                    updated = True
347
 
                    # get the product
348
 
                    product = line.product_id
349
 
                    # find the corresponding suppinfo with sequence -99
350
 
                    info_99_list = info_obj.search(cr, uid, [('product_id', '=', product.product_tmpl_id.id),
351
 
                                                        ('sequence', '=', -99),], context=context)
352
 
                    
353
 
                    if info_99_list:
354
 
                        # we drop it
355
 
                        info_obj.unlink(cr, uid, info_99_list, context=context)
356
 
                    
357
 
                    # create the new one
358
 
                    values = {'name': line.supplier_id.id,
359
 
                              'product_name': False,
360
 
                              'product_code': False,
361
 
                              'sequence' : -99,
362
 
                              #'product_uom': line.product_uom.id,
363
 
                              #'min_qty': 0.0,
364
 
                              #'qty': function
365
 
                              'product_id' : product.product_tmpl_id.id,
366
 
                              'delay' : int(line.supplier_id.default_delay),
367
 
                              #'pricelist_ids': created just after
368
 
                              #'company_id': default value
369
 
                              }
370
 
                    
371
 
                    new_info_id = info_obj.create(cr, uid, values, context=context)
372
 
                    # price lists creation - 'pricelist.partnerinfo
373
 
                    values = {'suppinfo_id': new_info_id,
374
 
                              'min_quantity': 1.00,
375
 
                              'price': line.price_unit,
376
 
                              'uom_id': line.product_uom.id,
377
 
                              'currency_id': line.purchase_order_line_id.currency_id.id,
378
 
                              'valid_till': line.purchase_order_id.valid_till,
379
 
                              'purchase_order_line_id': line.purchase_order_line_id.id,
380
 
                              'comment': 'RfQ original quantity for price : %s' % line.qty,
381
 
                              }
382
 
                    new_pricelist_id = pricelist_info_obj.create(cr, uid, values, context=context)
383
 
            
384
 
            # warn the user if no update has been performed
385
 
            if not updated:
386
 
                raise osv.except_osv(_('Warning !'), _('No information available for update!'))
387
 
                    
388
 
        return True
389
 
    
390
 
    def done(self, cr, uid, ids, context=None):
391
 
        '''
392
 
        method to perform checks before call to workflow
393
 
        '''
394
 
        po_obj = self.pool.get('purchase.order')
395
 
        wf_service = netsvc.LocalService("workflow")
396
 
        for tender in self.browse(cr, uid, ids, context=context):
397
 
            # check if corresponding rfqs are in the good state
398
 
            self.tender_integrity(cr, uid, tender, context=context)
399
 
            wf_service.trg_validate(uid, 'tender', tender.id, 'button_done', cr)
400
 
            # trigger all related rfqs
401
 
            rfq_ids = po_obj.search(cr, uid, [('tender_id', '=', tender.id),], context=context)
402
 
            for rfq_id in rfq_ids:
403
 
                wf_service.trg_validate(uid, 'purchase.order', rfq_id, 'rfq_done', cr)
404
 
            
405
 
        return True
406
 
    
407
 
    def create_po(self, cr, uid, ids, context=None):
408
 
        '''
409
 
        create a po from the updated RfQs
410
 
        '''
411
 
        if isinstance(ids, (int, long)):
412
 
            ids = [ids]
413
 
        
414
 
        partner_obj = self.pool.get('res.partner')
415
 
        po_obj = self.pool.get('purchase.order')
416
 
        wf_service = netsvc.LocalService("workflow")
417
 
        
418
 
        for tender in self.browse(cr, uid, ids, context=context):
419
 
            # check if corresponding rfqs are in the good state
420
 
            self.tender_integrity(cr, uid, tender, context=context)
421
 
            # integrity check, all lines must have purchase_order_line_id
422
 
            if not all([line.purchase_order_line_id.id for line in tender.tender_line_ids]):
423
 
                raise osv.except_osv(_('Error !'), _('All tender lines must have been compared!'))
424
 
            data = {}
425
 
            for line in tender.tender_line_ids:
426
 
                data.setdefault(line.supplier_id.id, {}) \
427
 
                    .setdefault('order_line', []).append((0,0,{'name': line.product_id.partner_ref,
428
 
                                                               'product_qty': line.qty,
429
 
                                                               'product_id': line.product_id.id,
430
 
                                                               'product_uom': line.product_uom.id,
431
 
                                                               'change_price_manually': 'True',
432
 
                                                               'price_unit': line.price_unit,
433
 
                                                               'date_planned': line.date_planned,
434
 
                                                               'move_dest_id': False,
435
 
                                                               'notes': line.product_id.description_purchase,
436
 
                                                               }))
437
 
                    
438
 
                # fill data corresponding to po creation
439
 
                address_id = partner_obj.address_get(cr, uid, [line.supplier_id.id], ['delivery'])['delivery']
440
 
                pricelist = line.supplier_id.property_product_pricelist_purchase.id,
441
 
                if line.currency_id:
442
 
                    price_ids = self.pool.get('product.pricelist').search(cr, uid, [('type', '=', 'purchase'), ('currency_id', '=', line.currency_id.id)], context=context)
443
 
                    if price_ids:
444
 
                        pricelist = price_ids[0]
445
 
                po_values = {'origin': (tender.sale_order_id and tender.sale_order_id.name or "") + ';' + tender.name,
446
 
                             'partner_id': line.supplier_id.id,
447
 
                             'partner_address_id': address_id,
448
 
                             'location_id': tender.location_id.id,
449
 
                             'pricelist_id': pricelist,
450
 
                             'company_id': tender.company_id.id,
451
 
                             'fiscal_position': line.supplier_id.property_account_position and line.supplier_id.property_account_position.id or False,
452
 
                             'categ': tender.categ,
453
 
                             'priority': tender.priority,
454
 
                             'origin_tender_id': tender.id,
455
 
                             #'tender_id': tender.id, # not for now, because tender_id is the flag for a po to be considered as RfQ
456
 
                             'warehouse_id': tender.warehouse_id.id,
457
 
                             'details': tender.details,
458
 
                             'delivery_requested_date': tender.requested_date,
459
 
                             }
460
 
                data[line.supplier_id.id].update(po_values)
461
 
            
462
 
            # create the pos, one for each selected supplier
463
 
            for po_data in data.values():
464
 
                po_id = po_obj.create(cr, uid, po_data, context=context)
465
 
                po = po_obj.browse(cr, uid, po_id, context=context)
466
 
                po_obj.log(cr, uid, po_id, 'The Purchase order %s for supplier %s has been created.'%(po.name, po.partner_id.name))
467
 
                #UF-802: the PO created must be in draft state, and not validated!
468
 
                #wf_service.trg_validate(uid, 'purchase.order', po_id, 'purchase_confirm', cr)
469
 
                
470
 
            # when the po is generated, the tender is done - no more modification or comparison
471
 
            self.done(cr, uid, [tender.id], context=context)
472
 
        
473
 
        return po_id
474
 
    
475
 
    def wkf_action_cancel(self, cr, uid, ids, context=None):
476
 
        '''
477
 
        cancel all corresponding rfqs
478
 
        '''
479
 
        po_obj = self.pool.get('purchase.order')
480
 
        wf_service = netsvc.LocalService("workflow")
481
 
        # set state
482
 
        self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
483
 
        for tender in self.browse(cr, uid, ids, context=context):
484
 
            # trigger all related rfqs
485
 
            rfq_ids = po_obj.search(cr, uid, [('tender_id', '=', tender.id),], context=context)
486
 
            for rfq_id in rfq_ids:
487
 
                wf_service.trg_validate(uid, 'purchase.order', rfq_id, 'purchase_cancel', cr)
488
 
                
489
 
        return True
490
 
 
491
 
    def set_manually_done(self, cr, uid, ids, all_doc=True, context=None):
492
 
        '''
493
 
        Set the tender and all related documents to done state
494
 
        '''
495
 
        if isinstance(ids, (int, long)):
496
 
            ids = [ids]
497
 
 
498
 
        wf_service = netsvc.LocalService("workflow")
499
 
 
500
 
        for tender in self.browse(cr, uid, ids, context=context):
501
 
            line_updated = False
502
 
            if tender.state not in ('done', 'cancel'):
503
 
                for line in tender.tender_line_ids:
504
 
                    if line.purchase_order_line_id:
505
 
                        line_updated = True
506
 
                # Cancel or done all RfQ related to the tender
507
 
                for rfq in tender.rfq_ids:
508
 
                    if rfq.state not in ('done', 'cancel'):
509
 
                        if rfq.state == 'draft' or not line_updated:
510
 
                            wf_service.trg_validate(uid, 'purchase.order', rfq.id, 'purchase_cancel', cr)
511
 
                        else:
512
 
                            wf_service.trg_validate(uid, 'purchase.order', rfq.id, 'rfq_sent', cr)
513
 
                            if not rfq.valid_till:
514
 
                                self.pool.get('purchase.order').write(cr, uid, [rfq.id], {'valid_till': time.strftime('%Y-%m-%d')}, context=context)
515
 
                            wf_service.trg_validate(uid, 'purchase.order', rfq.id, 'rfq_updated', cr)
516
 
 
517
 
                if all_doc:
518
 
                    if tender.state == 'draft' or not tender.tender_line_ids or not line_updated:
519
 
                        # Call the cancel method of the tender
520
 
                        wf_service.trg_validate(uid, 'tender', tender.id, 'tender_cancel', cr)
521
 
                    else:
522
 
                        # Call the cancel method of the tender
523
 
                        wf_service.trg_validate(uid, 'tender', tender.id, 'button_done', cr)
524
 
 
525
 
        return True
526
 
 
527
 
tender()
528
 
 
529
 
 
530
 
class tender_line(osv.osv):
531
 
    '''
532
 
    tender lines
533
 
    '''
534
 
    _name = 'tender.line'
535
 
    _description= 'Tender Line'
536
 
    
537
 
    _SELECTION_TENDER_STATE = [('draft', 'Draft'),('comparison', 'Comparison'), ('done', 'Closed'),]
538
 
    
539
 
    def on_product_change(self, cr, uid, id, product_id, context=None):
540
 
        '''
541
 
        product is changed, we update the UoM
542
 
        '''
543
 
        prod_obj = self.pool.get('product.product')
544
 
        result = {'value': {}}
545
 
        if product_id:
546
 
            result['value']['product_uom'] = prod_obj.browse(cr, uid, product_id, context=context).uom_po_id.id
547
 
            
548
 
        return result
549
 
    
550
 
    def _get_total_price(self, cr, uid, ids, field_name, arg, context=None):
551
 
        '''
552
 
        return the total price
553
 
        '''
554
 
        result = {}
555
 
        for line in self.browse(cr, uid, ids, context=context):
556
 
            result[line.id] = {}
557
 
            if line.price_unit and line.qty:
558
 
                result[line.id]['total_price'] = line.price_unit * line.qty
559
 
            else:
560
 
                result[line.id]['total_price'] = 0.0
561
 
            
562
 
            result[line.id]['func_currency_id'] = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
563
 
            if line.purchase_order_line_id:
564
 
                result[line.id]['currency_id'] = line.purchase_order_line_id.order_id.pricelist_id.currency_id.id
565
 
            else:
566
 
                result[line.id]['currency_id'] = result[line.id]['func_currency_id']
567
 
            
568
 
            result[line.id]['func_total_price'] = self.pool.get('res.currency').compute(cr, uid, result[line.id]['currency_id'],  
569
 
                                                                                            result[line.id]['func_currency_id'], 
570
 
                                                                                            result[line.id]['total_price'], 
571
 
                                                                                            round=True, context=context)
572
 
                
573
 
        return result
574
 
    
575
 
    def name_get(self, cr, user, ids, context=None):
576
 
        result = self.browse(cr, user, ids, context=context)
577
 
        res = []
578
 
        for rs in result:
579
 
            code = rs.product_id and rs.product_id.name or ''
580
 
            res += [(rs.id, code)]
581
 
        return res
582
 
    
583
 
    _columns = {'product_id': fields.many2one('product.product', string="Product", required=True),
584
 
                'qty': fields.float(string="Qty", required=True),
585
 
                'tender_id': fields.many2one('tender', string="Tender", required=True, ondelete='cascade'),
586
 
                'purchase_order_line_id': fields.many2one('purchase.order.line', string="Related RfQ line", readonly=True),
587
 
                'sale_order_line_id': fields.many2one('sale.order.line', string="Sale Order Line"),
588
 
                'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
589
 
                'date_planned': fields.related('tender_id', 'requested_date', type='date', string='Requested Date', store=False,),
590
 
                # functions
591
 
                'supplier_id': fields.related('purchase_order_line_id', 'order_id', 'partner_id', type='many2one', relation='res.partner', string="Supplier", readonly=True),
592
 
                'price_unit': fields.related('purchase_order_line_id', 'price_unit', type="float", string="Price unit", digits_compute=dp.get_precision('Purchase Price Computation'), readonly=True), # same precision as related field!
593
 
                'total_price': fields.function(_get_total_price, method=True, type='float', string="Total Price", digits_compute=dp.get_precision('Purchase Price'), multi='total'),
594
 
                'currency_id': fields.function(_get_total_price, method=True, type='many2one', relation='res.currency', string='Cur.', multi='total'),
595
 
                'func_total_price': fields.function(_get_total_price, method=True, type='float', string="Func. Total Price", digits_compute=dp.get_precision('Purchase Price'), multi='total'),
596
 
                'func_currency_id': fields.function(_get_total_price, method=True, type='many2one', relation='res.currency', string='Func. Cur.', multi='total'),
597
 
                'purchase_order_id': fields.related('purchase_order_line_id', 'order_id', type='many2one', relation='purchase.order', string="Related RfQ", readonly=True,),
598
 
                'purchase_order_line_number': fields.related('purchase_order_line_id', 'line_number', type="integer", string="Related Line Number", readonly=True,),
599
 
                'state': fields.related('tender_id', 'state', type="selection", selection=_SELECTION_TENDER_STATE, string="State",),
600
 
                'comment': fields.char(size=128, string='Comment'),
601
 
                }
602
 
    _defaults = {'qty': lambda *a: 1.0,
603
 
                 'state': lambda *a: 'draft',
604
 
                 }
605
 
    
606
 
    _sql_constraints = [
607
 
        ('product_qty_check', 'CHECK( qty > 0 )', 'Product Quantity must be greater than zero.'),
608
 
    ]
609
 
    
610
 
tender_line()
611
 
 
612
 
 
613
 
class tender2(osv.osv):
614
 
    '''
615
 
    tender class
616
 
    '''
617
 
    _inherit = 'tender'
618
 
    _columns = {'tender_line_ids': fields.one2many('tender.line', 'tender_id', string="Tender lines", states={'draft':[('readonly',False)]}, readonly=True),
619
 
                }
620
 
    
621
 
    def copy(self, cr, uid, id, default=None, context=None):
622
 
        '''
623
 
        reset the name to get new sequence number
624
 
        
625
 
        the copy method is here because upwards it goes in infinite loop
626
 
        '''
627
 
        line_obj = self.pool.get('tender.line')
628
 
        if default is None:
629
 
            default = {}
630
 
        
631
 
        default.update(name=self.pool.get('ir.sequence').get(cr, uid, 'tender'),
632
 
                       rfq_ids=[],
633
 
                       sale_order_line_id=False,)
634
 
            
635
 
        result = super(tender2, self).copy(cr, uid, id, default, context)
636
 
        
637
 
        return result
638
 
    
639
 
    def copy_data(self, cr, uid, id, default=None, context=None):
640
 
        '''
641
 
        reset the tender line
642
 
        '''
643
 
        result = super(tender, self).copy_data(cr, uid, id, default=default, context=context)
644
 
        # reset the tender line
645
 
        for line in result['tender_line_ids']:
646
 
            line[2].update(sale_order_line_id=False,
647
 
                           purchase_order_line_id=False,)
648
 
        return result
649
 
 
650
 
tender2()
651
 
 
652
 
 
653
 
class procurement_order(osv.osv):
654
 
    '''
655
 
    tender capabilities
656
 
    '''
657
 
    _inherit = 'procurement.order'
658
 
    
659
 
    def _is_tender(self, cr, uid, ids, field_name, arg, context=None):
660
 
        '''
661
 
        tell if the corresponding sale order line is tender sourcing or not
662
 
        '''
663
 
        result = {}
664
 
        for id in ids:
665
 
            result[id] = False
666
 
            
667
 
        for proc in self.browse(cr, uid, ids, context=context):
668
 
            for line in proc.sale_order_line_ids:
669
 
                result[proc.id] = line.po_cft == 'cft'
670
 
                                
671
 
        return result
672
 
    
673
 
    _columns = {'is_tender': fields.function(_is_tender, method=True, type='boolean', string='Is Tender', readonly=True,),
674
 
                'sale_order_line_ids': fields.one2many('sale.order.line', 'procurement_id', string="Sale Order Lines"),
675
 
                'tender_id': fields.many2one('tender', string='Tender', readonly=True),
676
 
                'is_tender_done': fields.boolean(string="Tender Closed"),
677
 
                'state': fields.selection([('draft','Draft'),
678
 
                                           ('confirmed','Confirmed'),
679
 
                                           ('exception','Exception'),
680
 
                                           ('running','Converted'),
681
 
                                           ('cancel','Cancelled'),
682
 
                                           ('ready','Ready'),
683
 
                                           ('done','Closed'),
684
 
                                           ('tender', 'Tender'),
685
 
                                           ('waiting','Waiting'),], 'State', required=True,
686
 
                                          help='When a procurement is created the state is set to \'Draft\'.\n If the procurement is confirmed, the state is set to \'Confirmed\'.\
687
 
                                                \nAfter confirming the state is set to \'Running\'.\n If any exception arises in the order then the state is set to \'Exception\'.\n Once the exception is removed the state becomes \'Ready\'.\n It is in \'Waiting\'. state when the procurement is waiting for another one to finish.'),
688
 
                'price_unit': fields.float('Unit Price from Tender', digits_compute=dp.get_precision('Purchase Price Computation')),
689
 
        }
690
 
    _defaults = {'is_tender_done': False,}
691
 
    
692
 
    def wkf_action_tender_create(self, cr, uid, ids, context=None):
693
 
        '''
694
 
        creation of tender from procurement workflow
695
 
        '''
696
 
        tender_obj = self.pool.get('tender')
697
 
        tender_line_obj = self.pool.get('tender.line')
698
 
        # find the corresponding sale order id for tender
699
 
        for proc in self.browse(cr, uid, ids, context=context):
700
 
            sale_order = False
701
 
            sale_order_line = False
702
 
            for sol in proc.sale_order_line_ids:
703
 
                sale_order = sol.order_id
704
 
                sale_order_line = sol
705
 
            # find the tender
706
 
            tender_id = False
707
 
            tender_ids = tender_obj.search(cr, uid, [('sale_order_id', '=', sale_order.id),('state', '=', 'draft'),], context=context)
708
 
            if tender_ids:
709
 
                tender_id = tender_ids[0]
710
 
            # create if not found
711
 
            if not tender_id:
712
 
                tender_id = tender_obj.create(cr, uid, {'sale_order_id': sale_order.id,
713
 
                                                        'location_id': proc.location_id.id,
714
 
                                                        'categ': sale_order.categ,
715
 
                                                        'priority': sale_order.priority,
716
 
                                                        'warehouse_id': sale_order.shop_id.warehouse_id.id,
717
 
                                                        'requested_date': proc.date_planned,
718
 
                                                        }, context=context)
719
 
            # add a line to the tender
720
 
            tender_line_obj.create(cr, uid, {'product_id': proc.product_id.id,
721
 
                                             'comment': sale_order_line.comment,
722
 
                                             'qty': proc.product_qty,
723
 
                                             'tender_id': tender_id,
724
 
                                             'sale_order_line_id': sale_order_line.id,
725
 
                                             'location_id': proc.location_id.id,
726
 
                                             'product_uom': proc.product_uom.id,
727
 
                                             #'date_planned': proc.date_planned, # function at line level
728
 
                                             }, context=context)
729
 
            
730
 
            self.write(cr, uid, ids, {'tender_id': tender_id}, context=context)
731
 
            
732
 
            # log message concerning tender creation
733
 
            tender_obj.log(cr, uid, tender_id, "The tender '%s' has been created and must be completed before purchase order creation."%tender_obj.browse(cr, uid, tender_id, context=context).name)
734
 
        # state of procurement is Tender
735
 
        self.write(cr, uid, ids, {'state': 'tender'}, context=context)
736
 
        
737
 
        return tender_id
738
 
    
739
 
    def wkf_action_tender_done(self, cr, uid, ids, context=None):
740
 
        '''
741
 
        set is_tender_done value
742
 
        '''
743
 
        self.write(cr, uid, ids, {'is_tender_done': True, 'state': 'exception',}, context=context)
744
 
        return True
745
 
    
746
 
    def action_po_assign(self, cr, uid, ids, context=None):
747
 
        '''
748
 
        - convert the created rfq by the tender to a po
749
 
        - add message at po creation during on_order workflow
750
 
        '''
751
 
        po_obj = self.pool.get('purchase.order')
752
 
        result = super(procurement_order, self).action_po_assign(cr, uid, ids, context=context)
753
 
        # The quotation 'SO001' has been converted to a sales order.
754
 
        if result:
755
 
            # do not display a log if we come from po update backward update of so
756
 
            data = self.read(cr, uid, ids, ['so_back_update_dest_po_id_procurement_order'], context=context)
757
 
            if not data[0]['so_back_update_dest_po_id_procurement_order']:
758
 
                po_obj.log(cr, uid, result, "The Purchase Order '%s' has been created following 'on order' sourcing."%po_obj.browse(cr, uid, result, context=context).name)
759
 
        return result
760
 
    
761
 
    def po_values_hook(self, cr, uid, ids, context=None, *args, **kwargs):
762
 
        '''
763
 
        data for the purchase order creation
764
 
        '''
765
 
        values = super(procurement_order, self).po_values_hook(cr, uid, ids, context=context, *args, **kwargs)
766
 
        procurement = kwargs['procurement']
767
 
 
768
 
        # set tender link in purchase order
769
 
        if procurement.tender_id:
770
 
            values['origin_tender_id'] = procurement.tender_id.id
771
 
 
772
 
        values['date_planned'] = procurement.date_planned
773
 
        
774
 
        if procurement.product_id:
775
 
            if procurement.product_id.type == 'consu':
776
 
                values['location_id'] = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock_override', 'stock_location_non_stockable')[1]
777
 
            elif procurement.product_id.type == 'service_recep':
778
 
                values['location_id'] = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_config_locations', 'stock_location_service')[1]
779
 
            else:
780
 
                wh_ids = self.pool.get('stock.warehouse').search(cr, uid, [])
781
 
                if wh_ids:
782
 
                    values['location_id'] = self.pool.get('stock.warehouse').browse(cr, uid, wh_ids[0]).lot_input_id.id
783
 
                else:
784
 
                    values['location_id'] = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_config_locations', 'stock_location_service')[1]
785
 
        
786
 
        return values
787
 
    
788
 
procurement_order()
789
 
 
790
 
 
791
 
class purchase_order(osv.osv):
792
 
    '''
793
 
    add link to tender
794
 
    '''
795
 
    _inherit = 'purchase.order'
796
 
    
797
 
    def _check_valid_till(self, cr, uid, ids, context=None):
798
 
        """ Checks if valid till has been completed
799
 
        """
800
 
        for obj in self.browse(cr, uid, ids, context=context):
801
 
            if obj.state == 'rfq_updated' and not obj.valid_till:
802
 
                return False
803
 
        return True
804
 
    _columns = {'tender_id': fields.many2one('tender', string="Tender", readonly=True),
805
 
                'origin_tender_id': fields.many2one('tender', string='Tender', readonly=True),
806
 
                'rfq_ok': fields.boolean(string='Is RfQ ?'),
807
 
                'state': fields.selection(PURCHASE_ORDER_STATE_SELECTION, 'State', readonly=True, help="The state of the purchase order or the quotation request. A quotation is a purchase order in a 'Draft' state. Then the order has to be confirmed by the user, the state switch to 'Confirmed'. Then the supplier must confirm the order to change the state to 'Approved'. When the purchase order is paid and received, the state becomes 'Closed'. If a cancel action occurs in the invoice or in the reception of goods, the state becomes in exception.", select=True),
808
 
                'valid_till': fields.date(string='Valid Till', states={'rfq_updated': [('required', True), ('readonly', True)], 'rfq_sent':[('required',False), ('readonly', False),]}, readonly=True,),
809
 
                # add readonly when state is Done
810
 
                }
811
 
 
812
 
    _defaults = {
813
 
                'rfq_ok': lambda self, cr, uid, c: c.get('rfq_ok', False),
814
 
                 }
815
 
    
816
 
    _constraints = [
817
 
        (_check_valid_till,
818
 
            'You must specify a Valid Till date.',
819
 
            ['valid_till']),]
820
 
 
821
 
    def create(self, cr, uid, vals, context=None):
822
 
        '''
823
 
        Set the reference at this step
824
 
        '''
825
 
        if context is None:
826
 
            context = {}
827
 
        if context.get('rfq_ok', False) and not vals.get('name', False):
828
 
            vals.update({'name': self.pool.get('ir.sequence').get(cr, uid, 'rfq')})
829
 
        elif not vals.get('name', False):
830
 
            vals.update({'name': self.pool.get('ir.sequence').get(cr, uid, 'purchase.order')})
831
 
 
832
 
        return super(purchase_order, self).create(cr, uid, vals, context=context)
833
 
    
834
 
    def unlink(self, cr, uid, ids, context=None):
835
 
        '''
836
 
        Display an error message if the PO has associated IN
837
 
        '''
838
 
        in_ids = self.pool.get('stock.picking').search(cr, uid, [('purchase_id', 'in', ids)], context=context)
839
 
        if in_ids:
840
 
            raise osv.except_osv(_('Error !'), _('Cannot delete a document if its associated ' \
841
 
            'document remains open. Please delete it (associated IN) first.'))
842
 
            
843
 
        # Copy a part of purchase_order standard unlink method to fix the bad state on error message
844
 
        purchase_orders = self.read(cr, uid, ids, ['state'], context=context)
845
 
        unlink_ids = []
846
 
        for s in purchase_orders:
847
 
            if s['state'] in ['draft','cancel']:
848
 
                unlink_ids.append(s['id'])
849
 
            else:
850
 
                raise osv.except_osv(_('Invalid action !'), _('Cannot delete Purchase Order(s) which are in %s State!')  % _(dict(PURCHASE_ORDER_STATE_SELECTION).get(s['state'])))
851
 
            
852
 
        return super(purchase_order, self).unlink(cr, uid, ids, context=context)
853
 
    
854
 
    def _hook_copy_name(self, cr, uid, ids, context=None, *args, **kwargs):
855
 
        '''
856
 
        HOOK from purchase>purchase.py for COPY function. Modification of default copy values
857
 
        define which name value will be used
858
 
        '''
859
 
        # default values from copy function
860
 
        default = kwargs.get('default', False)
861
 
        # flag defining if the new object will be a rfq
862
 
        is_rfq = False
863
 
        # calling super function
864
 
        result = super(purchase_order, self)._hook_copy_name(cr, uid, ids, context=context, *args, **kwargs)
865
 
        if default.get('rfq_ok', False):
866
 
            is_rfq = True
867
 
        elif 'rfq_ok' not in default:
868
 
            for obj in self.browse(cr, uid, ids, context=context):
869
 
                # if rfq_ok is specified as default value for new object, we base our decision on this value
870
 
                if obj.rfq_ok:
871
 
                    is_rfq = True
872
 
        if is_rfq:
873
 
            result.update(name=self.pool.get('ir.sequence').get(cr, uid, 'rfq'))
874
 
        return result
875
 
 
876
 
    def hook_rfq_sent_check_lines(self, cr, uid, ids, context=None):
877
 
        '''
878
 
        Please copy this to your module's method also.
879
 
        This hook belongs to the rfq_sent method from tender_flow>tender_flow.py
880
 
        - check lines after import
881
 
        '''
882
 
        res = True
883
 
        return res
884
 
        
885
 
        
886
 
    def rfq_sent(self, cr, uid, ids, context=None):
887
 
        self.hook_rfq_sent_check_lines(cr, uid, ids, context=context)
888
 
        for rfq in self.browse(cr, uid, ids, context=context):
889
 
            wf_service = netsvc.LocalService("workflow")
890
 
            wf_service.trg_validate(uid, 'purchase.order', rfq.id, 'rfq_sent', cr)
891
 
            
892
 
        self.write(cr, uid, ids, {'date_confirm': time.strftime('%Y-%m-%d')}, context=context)
893
 
 
894
 
        datas = {'ids': ids}
895
 
 
896
 
        return {'type': 'ir.actions.report.xml',
897
 
                'report_name': 'msf.purchase.quotation',
898
 
                'datas': datas}
899
 
 
900
 
    def check_rfq_updated(self, cr, uid, ids, context=None):
901
 
        if isinstance(ids, (int, long)):
902
 
            ids = [ids]
903
 
 
904
 
        wf_service = netsvc.LocalService("workflow")
905
 
        for rfq in self.browse(cr, uid, ids, context=context):
906
 
            if not rfq.valid_till:
907
 
                raise osv.except_osv(_('Error'), _('You must specify a Valid Till date.'))
908
 
 
909
 
            wf_service.trg_validate(uid, 'purchase.order', rfq.id, 'rfq_updated', cr)
910
 
 
911
 
        return {
912
 
            'type': 'ir.actions.act_window',
913
 
            'res_model': 'purchase.order',
914
 
            'view_mode': 'form,tree,graph,calendar',
915
 
            'view_type': 'form',
916
 
            'target': 'crush',
917
 
            'context': {'rfq_ok': True, 'search_default_draft_rfq': 1},
918
 
            'domain': [('rfq_ok', '=', True)],
919
 
            'res_id': rfq.id,
920
 
        }
921
 
        
922
 
    def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
923
 
        """
924
 
        columns for the tree
925
 
        """
926
 
        if context is None:
927
 
            context = {}
928
 
        # the search view depends on the type we want to display
929
 
        if view_type == 'search':
930
 
            if context.get('rfq_ok', False):
931
 
                # rfq search view
932
 
                view = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'tender_flow', 'view_rfq_filter')
933
 
                if view:
934
 
                    view_id = view[1]
935
 
        if view_type == 'tree':
936
 
            # the view depends on po type
937
 
            if context.get('rfq_ok', False):
938
 
                # rfq search view
939
 
                view = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'tender_flow', 'view_rfq_tree')
940
 
                if view:
941
 
                    view_id = view[1]
942
 
                 
943
 
        # call super
944
 
        result = super(purchase_order, self).fields_view_get(cr, uid, view_id, view_type, context=context, toolbar=toolbar, submenu=submenu)
945
 
        if view_type == 'form':
946
 
            if context.get('rfq_ok', False):
947
 
                # the title of the screen depends on po type
948
 
                form = etree.fromstring(result['arch'])
949
 
                
950
 
                fields = form.xpath('//form[@string="%s"]' % _('Purchase Order'))
951
 
                for field in fields:
952
 
                    field.set('string', _("Request for Quotation"))
953
 
                
954
 
                fields2 = form.xpath('//page[@string="%s"]' % _('Purchase Order'))
955
 
                for field2 in fields2:
956
 
                    field2.set('string', _("Request for Quotation"))
957
 
 
958
 
                result['arch'] = etree.tostring(form)
959
 
        
960
 
        return result
961
 
 
962
 
purchase_order()
963
 
 
964
 
 
965
 
class purchase_order_line(osv.osv):
966
 
    '''
967
 
    add a tender_id related field
968
 
    '''
969
 
    _inherit = 'purchase.order.line'
970
 
    _columns = {'tender_id': fields.related('order_id', 'tender_id', type='many2one', relation='tender', string='Tender',),
971
 
                'rfq_ok': fields.related('order_id', 'rfq_ok', type='boolean', string='RfQ ?'),
972
 
                }
973
 
    
974
 
    def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
975
 
        """
976
 
        columns for the tree
977
 
        """
978
 
        if context is None:
979
 
            context = {}
980
 
                 
981
 
        # call super
982
 
        result = super(purchase_order_line, self).fields_view_get(cr, uid, view_id, view_type, context=context, toolbar=toolbar, submenu=submenu)
983
 
        if view_type == 'form':
984
 
            if context.get('rfq_ok', False):
985
 
                # the title of the screen depends on po type
986
 
                form = etree.fromstring(result['arch'])
987
 
                fields = form.xpath('//form[@string="%s"]' % _('Purchase Order Line'))
988
 
                for field in fields:
989
 
                    field.set('string', _("Request for Quotation Line"))
990
 
                result['arch'] = etree.tostring(form)
991
 
        
992
 
        return result
993
 
 
994
 
purchase_order_line()
995
 
 
996
 
 
997
 
class sale_order_line(osv.osv):
998
 
    '''
999
 
    add link one2many to tender.line
1000
 
    '''
1001
 
    _inherit = 'sale.order.line'
1002
 
    
1003
 
    _columns = {'tender_line_ids': fields.one2many('tender.line', 'sale_order_line_id', string="Tender Lines", readonly=True),}
1004
 
    
1005
 
sale_order_line()
1006
 
 
1007
 
 
1008
 
class pricelist_partnerinfo(osv.osv):
1009
 
    '''
1010
 
    add new information from specifications
1011
 
    '''
1012
 
    def _get_line_number(self, cr, uid, ids, field_name, args, context=None):
1013
 
        res = {}
1014
 
        for price in self.browse(cr, uid, ids, context=context):
1015
 
            res[price.id] = 0
1016
 
            if price.purchase_order_line_id:
1017
 
                res[price.id] = price.purchase_order_line_id.line_number
1018
 
                
1019
 
        return res
1020
 
    
1021
 
    _inherit = 'pricelist.partnerinfo'
1022
 
    _columns = {'price': fields.float('Unit Price', required=True, digits_compute=dp.get_precision('Purchase Price Computation'), help="This price will be considered as a price for the supplier UoM if any or the default Unit of Measure of the product otherwise"),
1023
 
                'currency_id': fields.many2one('res.currency', string='Currency', required=True, domain="[('partner_currency', '=', partner_id)]"),
1024
 
                'valid_till': fields.date(string="Valid Till",),
1025
 
                'comment': fields.char(size=128, string='Comment'),
1026
 
                'purchase_order_id': fields.related('purchase_order_line_id', 'order_id', type='many2one', relation='purchase.order', string="Related RfQ", readonly=True,),
1027
 
                'purchase_order_line_id': fields.many2one('purchase.order.line', string="RfQ Line Ref",),
1028
 
                #'purchase_order_line_number': fields.related('purchase_order_line_id', 'line_number', type="integer", string="Related Line Number", ),
1029
 
                'purchase_order_line_number': fields.function(_get_line_number, method=True, type="integer", string="Related Line Number", readonly=True),
1030
 
                }
1031
 
pricelist_partnerinfo()
1032
 
 
1033
 
class ir_values(osv.osv):
1034
 
    _name = 'ir.values'
1035
 
    _inherit = 'ir.values'
1036
 
 
1037
 
    def get(self, cr, uid, key, key2, models, meta=False, context=None, res_id_req=False, without_user=True, key2_req=True):
1038
 
        if context is None:
1039
 
            context = {}
1040
 
        values = super(ir_values, self).get(cr, uid, key, key2, models, meta, context, res_id_req, without_user, key2_req)
1041
 
        new_values = values
1042
 
        
1043
 
        po_accepted_values = {'client_action_multi': ['Order Follow Up',
1044
 
                                                      'action_view_purchase_order_group'],
1045
 
                              'client_print_multi': ['Purchase Order (Merged)', 
1046
 
                                                     'Purchase Order',
1047
 
                                                     'Allocation report',
1048
 
                                                     'Order impact vs. Budget'],
1049
 
                              'client_action_relate': ['ir_open_product_list_export_view',
1050
 
                                                       'View_log_purchase.order',
1051
 
                                                       'Allocation report'],
1052
 
                              'tree_but_action': [],
1053
 
                              'tree_but_open': []}
1054
 
        
1055
 
        rfq_accepted_values = {'client_action_multi': [],
1056
 
                               'client_print_multi': ['Request for Quotation'],
1057
 
                               'client_action_relate': [],
1058
 
                               'tree_but_action': [],
1059
 
                               'tree_but_open': []}
1060
 
        if context.get('purchase_order', False) and 'purchase.order' in [x[0] for x in models]:
1061
 
            new_values = []
1062
 
            for v in values:
1063
 
                if key == 'action' and v[1] in po_accepted_values[key2] \
1064
 
                or v[1] == 'Purchase Order Excel Export' \
1065
 
                or v[1] == 'Purchase Order' \
1066
 
                or v[1] == 'Purchase Order (Merged)' \
1067
 
                or v[1] == 'Allocation report' \
1068
 
                or v[1] == 'Order impact vs. Budget' :
1069
 
                    new_values.append(v)
1070
 
        elif context.get('request_for_quotation', False) and 'purchase.order' in [x[0] for x in models]:
1071
 
            new_values = []
1072
 
            for v in values:
1073
 
                if key == 'action' and v[1] in rfq_accepted_values[key2] \
1074
 
                or v[1] == 'Request for Quotation' \
1075
 
                or v[1] == 'Request For Quotation Excel Export' :
1076
 
                    new_values.append(v)
1077
 
 
1078
 
        return new_values
1079
 
 
1080
 
ir_values()