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

« back to all changes in this revision

Viewing changes to tender_flow/tender_flow.py

  • Committer: chloups208
  • Date: 2011-06-30 14:54:26 UTC
  • mto: (307.2.1 unifield-wm)
  • mto: This revision was merged to the branch mainline in revision 311.
  • Revision ID: chloups208@chloups208-laptop-20110630145426-qsj5j0pp3e5b23bc
[UF-53][UF-58][UF-63] initial commit

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
 
 
28
 
import decimal_precision as dp
29
 
import netsvc
30
 
import pooler
31
 
import time
32
 
 
33
 
 
34
 
class tender(osv.osv):
35
 
    '''
36
 
    tender class
37
 
    '''
38
 
    _name = 'tender'
39
 
    _description = 'Tender'
40
 
    
41
 
    def _vals_get(self, cr, uid, ids, fields, arg, context=None):
42
 
        '''
43
 
        return function values
44
 
        '''
45
 
        result = {}
46
 
        for obj in self.browse(cr, uid, ids, context=context):
47
 
            result[obj.id] = {'rfq_name_list': '',
48
 
                              }
49
 
            
50
 
            rfq_names = []
51
 
            for rfq in obj.rfq_ids:
52
 
                rfq_names.append(rfq.name)
53
 
            # generate string
54
 
            rfq_names.sort()
55
 
            result[obj.id]['rfq_name_list'] = ','.join(rfq_names)
56
 
            
57
 
        return result
58
 
    
59
 
    _columns = {'name': fields.char('Tender Reference', size=64, required=True, select=True, readonly=True),
60
 
                'sale_order_id': fields.many2one('sale.order', string="Sale Order", readonly=True),
61
 
                'state': fields.selection([('draft', 'Draft'),('comparison', 'Comparison'), ('done', 'Done'), ('cancel', 'Canceled'),], string="State", readonly=True),
62
 
                'supplier_ids': fields.many2many('res.partner', 'tender_supplier_rel', 'tender_id', 'supplier_id', string="Suppliers",
63
 
                                                 states={'draft':[('readonly',False)]}, readonly=True,
64
 
                                                 context={'search_default_supplier': 1,}),
65
 
                'location_id': fields.many2one('stock.location', 'Location', required=True, states={'draft':[('readonly',False)]}, readonly=True, domain=[('usage', '=', 'internal')]),
66
 
                'company_id': fields.many2one('res.company','Company',required=True, states={'draft':[('readonly',False)]}, readonly=True),
67
 
                'rfq_ids': fields.one2many('purchase.order', 'tender_id', string="RfQs", readonly=True),
68
 
                'priority': fields.selection(ORDER_PRIORITY, string='Tender Priority', states={'draft':[('readonly',False)],}, readonly=True,),
69
 
                'categ': fields.selection(ORDER_CATEGORY, string='Tender Category', required=True, states={'draft':[('readonly',False)],}, readonly=True),
70
 
                'creator': fields.many2one('res.users', string="Creator", readonly=True, required=True,),
71
 
                'warehouse_id': fields.many2one('stock.warehouse', string="Warehouse", required=True, states={'draft':[('readonly',False)],}, readonly=True),
72
 
                'creation_date': fields.date(string="Creation Date", readonly=True),
73
 
                'details': fields.char(size=30, string="Details", states={'draft':[('readonly',False)],}, readonly=True),
74
 
                'requested_date': fields.date(string="Requested Date", required=True, states={'draft':[('readonly',False)],}, readonly=True),
75
 
                'notes': fields.text('Notes'),
76
 
                'rfq_name_list': fields.function(_vals_get, method=True, string='RfQs Ref', type='char', readonly=True, store=False, multi='get_vals',)
77
 
                }
78
 
    
79
 
    _defaults = {'state': 'draft',
80
 
                 'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'tender'),
81
 
                 'company_id': lambda obj, cr, uid, context: obj.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id,
82
 
                 'creator': lambda obj, cr, uid, context: uid,
83
 
                 'creation_date': lambda *a: time.strftime('%Y-%m-%d'),
84
 
                 'requested_date': lambda *a: time.strftime('%Y-%m-%d'),
85
 
                 'priority': 'normal',
86
 
                 '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],
87
 
                 }
88
 
    
89
 
    _order = 'name desc'
90
 
    
91
 
    def onchange_warehouse(self, cr, uid, ids, warehouse_id, context=None):
92
 
        '''
93
 
        on_change function for the warehouse
94
 
        '''
95
 
        result = {'value':{},}
96
 
        if warehouse_id:
97
 
            input_loc_id = self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id, context=context).lot_input_id.id
98
 
            result['value'].update(location_id=input_loc_id)
99
 
        
100
 
        return result
101
 
    
102
 
    def wkf_generate_rfq(self, cr, uid, ids, context=None):
103
 
        '''
104
 
        generate the rfqs for each specified supplier
105
 
        '''
106
 
        if context is None:
107
 
            context = {}
108
 
        po_obj = self.pool.get('purchase.order')
109
 
        pol_obj = self.pool.get('purchase.order.line')
110
 
        partner_obj = self.pool.get('res.partner')
111
 
        pricelist_obj = self.pool.get('product.pricelist')
112
 
        # no suppliers -> raise error
113
 
        for tender in self.browse(cr, uid, ids, context=context):
114
 
            if not tender.supplier_ids:
115
 
                raise osv.except_osv(_('Warning !'), _('You must select at least one supplier!'))
116
 
            for supplier in tender.supplier_ids:
117
 
                # create a purchase order for each supplier
118
 
                address_id = partner_obj.address_get(cr, uid, [supplier.id], ['delivery'])['delivery']
119
 
                if not address_id:
120
 
                    raise osv.except_osv(_('Warning !'), _('The supplier "%s" has no address defined!'%supplier.name))
121
 
                pricelist_id = supplier.property_product_pricelist_purchase.id
122
 
                values = {'name': self.pool.get('ir.sequence').get(cr, uid, 'rfq'),
123
 
                          'origin': tender.sale_order_id and tender.sale_order_id.name + '/' + tender.name or tender.name,
124
 
                          'rfq_ok': True,
125
 
                          'partner_id': supplier.id,
126
 
                          'partner_address_id': address_id,
127
 
                          'location_id': tender.location_id.id,
128
 
                          'pricelist_id': pricelist_id,
129
 
                          'company_id': tender.company_id.id,
130
 
                          'fiscal_position': supplier.property_account_position and supplier.property_account_position.id or False,
131
 
                          'tender_id': tender.id,
132
 
                          'warehouse_id': tender.warehouse_id.id,
133
 
                          'categ': tender.categ,
134
 
                          'priority': tender.priority,
135
 
                          'details': tender.details,
136
 
                          'delivery_requested_date': tender.requested_date,
137
 
                          }
138
 
                # create the rfq - dic is udpated for default partner_address_id at purchase.order level
139
 
                po_id = po_obj.create(cr, uid, values, context=dict(context, partner_id=supplier.id))
140
 
                
141
 
                for line in tender.tender_line_ids:
142
 
                    # create an order line for each tender line
143
 
                    price = pricelist_obj.price_get(cr, uid, [pricelist_id], line.product_id.id, line.qty, supplier.id, {'uom': line.product_uom.id})[pricelist_id]
144
 
                    newdate = datetime.strptime(line.date_planned, '%Y-%m-%d')
145
 
                    #newdate = (newdate - relativedelta(days=tender.company_id.po_lead)) - relativedelta(days=int(supplier.default_delay)) # requested by Magali uf-489
146
 
                    values = {'name': line.product_id.partner_ref,
147
 
                              'product_qty': line.qty,
148
 
                              'product_id': line.product_id.id,
149
 
                              'product_uom': line.product_uom.id,
150
 
                              'price_unit': 0.0, # was price variable - uf-607
151
 
                              'date_planned': newdate.strftime('%Y-%m-%d'),
152
 
                              'notes': line.product_id.description_purchase,
153
 
                              'order_id': po_id,
154
 
                              }
155
 
                    # create purchase order line
156
 
                    pol_id = pol_obj.create(cr, uid, values, context=context)
157
 
                
158
 
                po_obj.log(cr, uid, po_id, "Request for Quotation '%s' has been created."%po_obj.browse(cr, uid, po_id, context=context).name)
159
 
            
160
 
        self.write(cr, uid, ids, {'state':'comparison'}, context=context)
161
 
        return True
162
 
    
163
 
    def wkf_action_done(self, cr, uid, ids, context=None):
164
 
        '''
165
 
        tender is done
166
 
        '''
167
 
        # done all related rfqs
168
 
        wf_service = netsvc.LocalService("workflow")
169
 
        for tender in self.browse(cr, uid, ids, context=context):
170
 
            rfq_list = []
171
 
            for rfq in tender.rfq_ids:
172
 
                if rfq.state not in ('rfq_updated', 'cancel',):
173
 
                    rfq_list.append(rfq.id)
174
 
                else:
175
 
                    wf_service.trg_validate(uid, 'purchase.order', rfq.id, 'rfq_done', cr)
176
 
                
177
 
            # if some rfq have wrong state, we display a message
178
 
            if rfq_list:
179
 
                raise osv.except_osv(_('Warning !'), _("Generated RfQs must be Updated or Canceled."))
180
 
            
181
 
            # integrity check, all lines must have purchase_order_line_id
182
 
            if not all([line.purchase_order_line_id.id for line in tender.tender_line_ids]):
183
 
                raise osv.except_osv(_('Error !'), _('All tender lines must have been compared!'))
184
 
        
185
 
        # update product supplierinfo and pricelist
186
 
        self.update_supplier_info(cr, uid, ids, context=context, integrity_test=False,)
187
 
        # change tender state
188
 
        self.write(cr, uid, ids, {'state':'done'}, context=context)
189
 
        return True
190
 
    
191
 
    def tender_integrity(self, cr, uid, tender, context=None):
192
 
        '''
193
 
        check the state of corresponding RfQs
194
 
        '''
195
 
        po_obj = self.pool.get('purchase.order')
196
 
        # no rfq in done state
197
 
        rfq_ids = po_obj.search(cr, uid, [('tender_id', '=', tender.id),
198
 
                                          ('state', 'in', ('done',)),], context=context)
199
 
        if rfq_ids:
200
 
            raise osv.except_osv(_('Error !'), _("Some RfQ are already Done. Integrity failure."))
201
 
        # all rfqs must have been treated
202
 
        rfq_ids = po_obj.search(cr, uid, [('tender_id', '=', tender.id),
203
 
                                          ('state', 'in', ('draft', 'rfq_sent',)),], context=context)
204
 
        if rfq_ids:
205
 
            raise osv.except_osv(_('Warning !'), _("Generated RfQs must be Updated or Canceled."))
206
 
        # at least one rfq must be updated and not canceled
207
 
        rfq_ids = po_obj.search(cr, uid, [('tender_id', '=', tender.id),
208
 
                                          ('state', 'in', ('rfq_updated',)),], context=context)
209
 
        if not rfq_ids:
210
 
            raise osv.except_osv(_('Warning !'), _("At least one RfQ must be in state Updated."))
211
 
        
212
 
        return rfq_ids
213
 
    
214
 
    def compare_rfqs(self, cr, uid, ids, context=None):
215
 
        '''
216
 
        compare rfqs button
217
 
        '''
218
 
        if len(ids) > 1:
219
 
            raise osv.except_osv(_('Warning !'), _('Cannot compare rfqs of more than one tender at a time!'))
220
 
        po_obj = self.pool.get('purchase.order')
221
 
        wiz_obj = self.pool.get('wizard.compare.rfq')
222
 
        for tender in self.browse(cr, uid, ids, context=context):
223
 
            # check if corresponding rfqs are in the good state
224
 
            rfq_ids = self.tender_integrity(cr, uid, tender, context=context)
225
 
            # gather the product_id -> supplier_id relationship to display it back in the compare wizard
226
 
            suppliers = {}
227
 
            for line in tender.tender_line_ids:
228
 
                if line.product_id and line.supplier_id:
229
 
                    suppliers.update({line.product_id.id:line.supplier_id.id,})
230
 
            # rfq corresponding to this tender with done state (has been updated and not canceled)
231
 
            # the list of rfq which will be compared
232
 
            c = dict(context, active_ids=rfq_ids, tender_id=tender.id, end_wizard=False, suppliers=suppliers,)
233
 
            # open the wizard
234
 
            action = wiz_obj.start_compare_rfq(cr, uid, ids, context=c)
235
 
        return action
236
 
    
237
 
    def update_supplier_info(self, cr, uid, ids, context=None, *args, **kwargs):
238
 
        '''
239
 
        update the supplier info of corresponding products
240
 
        '''
241
 
        info_obj = self.pool.get('product.supplierinfo')
242
 
        pricelist_info_obj = self.pool.get('pricelist.partnerinfo')
243
 
        # integrity check flag
244
 
        integrity_test = kwargs.get('integrity_test', False)
245
 
        for tender in self.browse(cr, uid, ids, context=context):
246
 
            # flag if at least one update
247
 
            updated = False
248
 
            # check if corresponding rfqs are in the good state
249
 
            if integrity_test:
250
 
                self.tender_integrity(cr, uid, tender, context=context)
251
 
            for line in tender.tender_line_ids:
252
 
                # if a supplier has been selected
253
 
                if line.purchase_order_line_id:
254
 
                    # set the flag
255
 
                    updated = True
256
 
                    # get the product
257
 
                    product = line.product_id
258
 
                    # find the corresponding suppinfo with sequence -99
259
 
                    info_99_list = info_obj.search(cr, uid, [('product_id', '=', product.product_tmpl_id.id),
260
 
                                                        ('sequence', '=', -99),], context=context)
261
 
                    
262
 
                    if info_99_list:
263
 
                        # we drop it
264
 
                        info_obj.unlink(cr, uid, info_99_list, context=context)
265
 
                    
266
 
                    # create the new one
267
 
                    values = {'name': line.supplier_id.id,
268
 
                              'product_name': False,
269
 
                              'product_code': False,
270
 
                              'sequence' : -99,
271
 
                              'product_uom': line.product_uom.id,
272
 
                              'min_qty': 0.0,
273
 
                              #'qty': function
274
 
                              'product_id' : product.product_tmpl_id.id,
275
 
                              'delay' : int(line.supplier_id.default_delay),
276
 
                              #'pricelist_ids': created just after
277
 
                              #'company_id': default value
278
 
                              }
279
 
                    
280
 
                    new_info_id = info_obj.create(cr, uid, values, context=context)
281
 
                    # price lists creation - 'pricelist.partnerinfo
282
 
                    values = {'suppinfo_id': new_info_id,
283
 
                              'min_quantity': line.qty,
284
 
                              'price': line.price_unit,
285
 
                              'currency_id': line.purchase_order_line_id.currency_id.id,
286
 
                              'valid_till': line.purchase_order_id.valid_till,
287
 
                              'purchase_order_line_id': line.purchase_order_line_id.id,
288
 
                              }
289
 
                    new_pricelist_id = pricelist_info_obj.create(cr, uid, values, context=context)
290
 
            
291
 
            # warn the user if no update has been performed
292
 
            if not updated:
293
 
                raise osv.except_osv(_('Warning !'), _('No information available for update!'))
294
 
                    
295
 
        return True
296
 
    
297
 
    def done(self, cr, uid, ids, context=None):
298
 
        '''
299
 
        method to perform checks before call to workflow
300
 
        '''
301
 
        po_obj = self.pool.get('purchase.order')
302
 
        wf_service = netsvc.LocalService("workflow")
303
 
        for tender in self.browse(cr, uid, ids, context=context):
304
 
            # check if corresponding rfqs are in the good state
305
 
            self.tender_integrity(cr, uid, tender, context=context)
306
 
            wf_service.trg_validate(uid, 'tender', tender.id, 'button_done', cr)
307
 
            # trigger all related rfqs
308
 
            rfq_ids = po_obj.search(cr, uid, [('tender_id', '=', tender.id),], context=context)
309
 
            for rfq_id in rfq_ids:
310
 
                wf_service.trg_validate(uid, 'purchase.order', rfq_id, 'rfq_done', cr)
311
 
            
312
 
        return True
313
 
    
314
 
    def create_po(self, cr, uid, ids, context=None):
315
 
        '''
316
 
        create a po from the updated RfQs
317
 
        '''
318
 
        partner_obj = self.pool.get('res.partner')
319
 
        po_obj = self.pool.get('purchase.order')
320
 
        wf_service = netsvc.LocalService("workflow")
321
 
        
322
 
        for tender in self.browse(cr, uid, ids, context=context):
323
 
            # check if corresponding rfqs are in the good state
324
 
            self.tender_integrity(cr, uid, tender, context=context)
325
 
            # integrity check, all lines must have purchase_order_line_id
326
 
            if not all([line.purchase_order_line_id.id for line in tender.tender_line_ids]):
327
 
                raise osv.except_osv(_('Error !'), _('All tender lines must have been compared!'))
328
 
            data = {}
329
 
            for line in tender.tender_line_ids:
330
 
                data.setdefault(line.supplier_id.id, {}) \
331
 
                    .setdefault('order_line', []).append((0,0,{'name': line.product_id.partner_ref,
332
 
                                                               'product_qty': line.qty,
333
 
                                                               'product_id': line.product_id.id,
334
 
                                                               'product_uom': line.product_uom.id,
335
 
                                                               'price_unit': line.price_unit,
336
 
                                                               'date_planned': line.date_planned,
337
 
                                                               'move_dest_id': False,
338
 
                                                               'notes': line.product_id.description_purchase,
339
 
                                                               }))
340
 
                    
341
 
                # fill data corresponding to po creation
342
 
                address_id = partner_obj.address_get(cr, uid, [line.supplier_id.id], ['delivery'])['delivery']
343
 
                po_values = {'origin': tender.name,
344
 
                             'partner_id': line.supplier_id.id,
345
 
                             'partner_address_id': address_id,
346
 
                             'location_id': tender.location_id.id,
347
 
                             'pricelist_id': line.supplier_id.property_product_pricelist_purchase.id,
348
 
                             'company_id': tender.company_id.id,
349
 
                             'fiscal_position': line.supplier_id.property_account_position and line.supplier_id.property_account_position.id or False,
350
 
                             'categ': tender.categ,
351
 
                             'priority': tender.priority,
352
 
                             #'tender_id': tender.id, # not for now, because tender_id is the flag for a po to be considered as RfQ
353
 
                             'warehouse_id': tender.warehouse_id.id,
354
 
                             'details': tender.details,
355
 
                             'delivery_requested_date': tender.requested_date,
356
 
                             }
357
 
                data[line.supplier_id.id].update(po_values)
358
 
            
359
 
            # create the pos, one for each selected supplier
360
 
            for po_data in data.values():
361
 
                po_id = po_obj.create(cr, uid, po_data, context=context)
362
 
                po = po_obj.browse(cr, uid, po_id, context=context)
363
 
                po_obj.log(cr, uid, po_id, 'The Purchase order %s for supplier %s has been created.'%(po.name, po.partner_id.name))
364
 
                wf_service.trg_validate(uid, 'purchase.order', po_id, 'purchase_confirm', cr)
365
 
                
366
 
            # when the po is generated, the tender is done - no more modification or comparison
367
 
            self.done(cr, uid, [tender.id], context=context)
368
 
        
369
 
        return True
370
 
    
371
 
    def wkf_action_cancel(self, cr, uid, ids, context=None):
372
 
        '''
373
 
        cancel all corresponding rfqs
374
 
        '''
375
 
        po_obj = self.pool.get('purchase.order')
376
 
        wf_service = netsvc.LocalService("workflow")
377
 
        # set state
378
 
        self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
379
 
        for tender in self.browse(cr, uid, ids, context=context):
380
 
            # trigger all related rfqs
381
 
            rfq_ids = po_obj.search(cr, uid, [('tender_id', '=', tender.id),], context=context)
382
 
            for rfq_id in rfq_ids:
383
 
                wf_service.trg_validate(uid, 'purchase.order', rfq_id, 'purchase_cancel', cr)
384
 
                
385
 
        return True
386
 
 
387
 
tender()
388
 
 
389
 
 
390
 
class tender_line(osv.osv):
391
 
    '''
392
 
    tender lines
393
 
    '''
394
 
    _name = 'tender.line'
395
 
    _description= 'Tender Line'
396
 
    
397
 
    _SELECTION_TENDER_STATE = [('draft', 'Draft'),('comparison', 'Comparison'), ('done', 'Done'),]
398
 
    
399
 
    def on_product_change(self, cr, uid, id, product_id, context=None):
400
 
        '''
401
 
        product is changed, we update the UoM
402
 
        '''
403
 
        prod_obj = self.pool.get('product.product')
404
 
        result = {'value': {}}
405
 
        if product_id:
406
 
            result['value']['product_uom'] = prod_obj.browse(cr, uid, product_id, context=context).uom_po_id.id
407
 
            
408
 
        return result
409
 
    
410
 
    def _get_total_price(self, cr, uid, ids, field_name, arg, context=None):
411
 
        '''
412
 
        return the total price
413
 
        '''
414
 
        result = {}
415
 
        for line in self.browse(cr, uid, ids, context=context):
416
 
            if line.price_unit and line.qty:
417
 
                result[line.id] = line.price_unit * line.qty
418
 
            else:
419
 
                result[line.id] = 0.0
420
 
                
421
 
        return result
422
 
    
423
 
    def name_get(self, cr, user, ids, context=None):
424
 
        result = self.browse(cr, user, ids, context=context)
425
 
        res = []
426
 
        for rs in result:
427
 
            code = rs.product_id and rs.product_id.name or ''
428
 
            res += [(rs.id, code)]
429
 
        return res
430
 
    
431
 
    _columns = {'product_id': fields.many2one('product.product', string="Product", required=True),
432
 
                'qty': fields.float(string="Qty", required=True),
433
 
                'tender_id': fields.many2one('tender', string="Tender", required=True),
434
 
                'purchase_order_line_id': fields.many2one('purchase.order.line', string="Related RfQ line", readonly=True),
435
 
                'sale_order_line_id': fields.many2one('sale.order.line', string="Sale Order Line"),
436
 
                'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
437
 
                'date_planned': fields.related('tender_id', 'requested_date', type='date', string='Requested Date', store=False,),
438
 
                # functions
439
 
                'supplier_id': fields.related('purchase_order_line_id', 'order_id', 'partner_id', type='many2one', relation='res.partner', string="Supplier", readonly=True),
440
 
                'price_unit': fields.related('purchase_order_line_id', 'price_unit', type="float", string="Price unit", readonly=True),
441
 
                'total_price': fields.function(_get_total_price, method=True, type='float', string="Total Price"),
442
 
                'purchase_order_id': fields.related('purchase_order_line_id', 'order_id', type='many2one', relation='purchase.order', string="Related RfQ", readonly=True,),
443
 
                'purchase_order_line_number': fields.related('purchase_order_line_id', 'line_number', type="integer", string="Related Line Number", readonly=True,),
444
 
                'state': fields.related('tender_id', 'state', type="selection", selection=_SELECTION_TENDER_STATE, string="State",),
445
 
                }
446
 
    _defaults = {'qty': 1.0,
447
 
                 }
448
 
    
449
 
tender_line()
450
 
 
451
 
 
452
 
class tender(osv.osv):
453
 
    '''
454
 
    tender class
455
 
    '''
456
 
    _inherit = 'tender'
457
 
    _columns = {'tender_line_ids': fields.one2many('tender.line', 'tender_id', string="Tender lines", states={'draft':[('readonly',False)]}, readonly=True),
458
 
                }
459
 
    
460
 
    def copy(self, cr, uid, id, default=None, context=None):
461
 
        '''
462
 
        reset the name to get new sequence number
463
 
        
464
 
        the copy method is here because upwards it goes in infinite loop
465
 
        '''
466
 
        line_obj = self.pool.get('tender.line')
467
 
        if default is None:
468
 
            default = {}
469
 
        
470
 
        default.update(name=self.pool.get('ir.sequence').get(cr, uid, 'tender'),
471
 
                       rfq_ids=[],
472
 
                       sale_order_line_id=False,)
473
 
            
474
 
        result = super(tender, self).copy(cr, uid, id, default, context)
475
 
        
476
 
        return result
477
 
    
478
 
    def copy_data(self, cr, uid, id, default=None, context=None):
479
 
        '''
480
 
        reset the tender line
481
 
        '''
482
 
        result = super(tender, self).copy_data(cr, uid, id, default=default, context=context)
483
 
        # reset the tender line
484
 
        for line in result['tender_line_ids']:
485
 
            line[2].update(sale_order_line_id=False,
486
 
                           purchase_order_line_id=False,)
487
 
        return result
488
 
 
489
 
tender()
490
 
 
491
 
 
492
 
class procurement_order(osv.osv):
493
 
    '''
494
 
    tender capabilities
495
 
    '''
496
 
    _inherit = 'procurement.order'
497
 
    
498
 
    def _is_tender(self, cr, uid, ids, field_name, arg, context=None):
499
 
        '''
500
 
        tell if the corresponding sale order line is tender sourcing or not
501
 
        '''
502
 
        result = {}
503
 
        for id in ids:
504
 
            result[id] = False
505
 
            
506
 
        for proc in self.browse(cr, uid, ids, context=context):
507
 
            for line in proc.sale_order_line_ids:
508
 
                result[proc.id] = line.po_cft == 'cft'
509
 
                                
510
 
        return result
511
 
    
512
 
    _columns = {'is_tender': fields.function(_is_tender, method=True, type='boolean', string='Is Tender', readonly=True,),
513
 
                'sale_order_line_ids': fields.one2many('sale.order.line', 'procurement_id', string="Sale Order Lines"),
514
 
                'tender_id': fields.many2one('tender', string='Tender', readonly=True),
515
 
                'is_tender_done': fields.boolean(string="Tender Done"),
516
 
                'state': fields.selection([('draft','Draft'),
517
 
                                           ('confirmed','Confirmed'),
518
 
                                           ('exception','Exception'),
519
 
                                           ('running','Running'),
520
 
                                           ('cancel','Cancel'),
521
 
                                           ('ready','Ready'),
522
 
                                           ('done','Done'),
523
 
                                           ('tender', 'Tender'),
524
 
                                           ('waiting','Waiting'),], 'State', required=True,
525
 
                                          help='When a procurement is created the state is set to \'Draft\'.\n If the procurement is confirmed, the state is set to \'Confirmed\'.\
526
 
                                                \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.'),
527
 
                'price_unit': fields.float('Unit Price from Tender', digits_compute= dp.get_precision('Purchase Price')),
528
 
        }
529
 
    _defaults = {'is_tender_done': False,}
530
 
    
531
 
    def wkf_action_tender_create(self, cr, uid, ids, context=None):
532
 
        '''
533
 
        creation of tender from procurement workflow
534
 
        '''
535
 
        tender_obj = self.pool.get('tender')
536
 
        tender_line_obj = self.pool.get('tender.line')
537
 
        # find the corresponding sale order id for tender
538
 
        for proc in self.browse(cr, uid, ids, context=context):
539
 
            sale_order = False
540
 
            sale_order_line = False
541
 
            for sol in proc.sale_order_line_ids:
542
 
                sale_order = sol.order_id
543
 
                sale_order_line = sol
544
 
            # find the tender
545
 
            tender_id = False
546
 
            tender_ids = tender_obj.search(cr, uid, [('sale_order_id', '=', sale_order.id),('state', '=', 'draft'),], context=context)
547
 
            if tender_ids:
548
 
                tender_id = tender_ids[0]
549
 
            # create if not found
550
 
            if not tender_id:
551
 
                tender_id = tender_obj.create(cr, uid, {'sale_order_id': sale_order.id,
552
 
                                                        'location_id': proc.location_id.id,
553
 
                                                        'categ': sale_order.categ,
554
 
                                                        'priority': sale_order.priority,
555
 
                                                        'warehouse_id': sale_order.shop_id.warehouse_id.id,
556
 
                                                        'requested_date': proc.date_planned,
557
 
                                                        }, context=context)
558
 
            # add a line to the tender
559
 
            tender_line_obj.create(cr, uid, {'product_id': proc.product_id.id,
560
 
                                             'qty': proc.product_qty,
561
 
                                             'tender_id': tender_id,
562
 
                                             'sale_order_line_id': sale_order_line.id,
563
 
                                             'location_id': proc.location_id.id,
564
 
                                             'product_uom': proc.product_uom.id,
565
 
                                             #'date_planned': proc.date_planned, # function at line level
566
 
                                             }, context=context)
567
 
            
568
 
            self.write(cr, uid, ids, {'tender_id': tender_id}, context=context)
569
 
            
570
 
            # log message concerning tender creation
571
 
            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)
572
 
        # state of procurement is Tender
573
 
        self.write(cr, uid, ids, {'state': 'tender'}, context=context)
574
 
        
575
 
        return tender_id
576
 
    
577
 
    def wkf_action_tender_done(self, cr, uid, ids, context=None):
578
 
        '''
579
 
        set is_tender_done value
580
 
        '''
581
 
        self.write(cr, uid, ids, {'is_tender_done': True, 'state': 'exception',}, context=context)
582
 
        return True
583
 
    
584
 
    def action_po_assign(self, cr, uid, ids, context=None):
585
 
        '''
586
 
        - convert the created rfq by the tender to a po
587
 
        - add message at po creation during on_order workflow
588
 
        '''
589
 
        po_obj = self.pool.get('purchase.order')
590
 
        result = super(procurement_order, self).action_po_assign(cr, uid, ids, context=context)
591
 
        # The quotation 'SO001' has been converted to a sales order.
592
 
        if result:
593
 
            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)
594
 
            if self.browse(cr, uid, ids[0], context=context).is_tender:
595
 
                wf_service = netsvc.LocalService("workflow")
596
 
                wf_service.trg_validate(uid, 'purchase.order', result, 'purchase_confirm', cr)
597
 
        return result
598
 
    
599
 
    def create_po_hook(self, cr, uid, ids, context=None, *args, **kwargs):
600
 
        '''
601
 
        if the procurement corresponds to a tender, the created po is confirmed but not validated
602
 
        '''
603
 
        po_obj = self.pool.get('purchase.order')
604
 
        procurement = kwargs['procurement']
605
 
        purchase_id = super(procurement_order, self).create_po_hook(cr, uid, ids, context=context, *args, **kwargs)
606
 
        # if tender
607
 
        if procurement.is_tender:
608
 
            wf_service = netsvc.LocalService("workflow")
609
 
            wf_service.trg_validate(uid, 'purchase.order', purchase_id, 'purchase_confirm', cr)
610
 
        return purchase_id
611
 
    
612
 
procurement_order()
613
 
 
614
 
 
615
 
class purchase_order(osv.osv):
616
 
    '''
617
 
    add link to tender
618
 
    '''
619
 
    _inherit = 'purchase.order'
620
 
    
621
 
    STATE_SELECTION = [
622
 
                       ('draft', 'Draft'),
623
 
                       ('wait', 'Waiting'),
624
 
                       ('confirmed', 'Waiting Approval'),
625
 
                       ('approved', 'Approved'),
626
 
                       ('except_picking', 'Shipping Exception'),
627
 
                       ('except_invoice', 'Invoice Exception'),
628
 
                       ('done', 'Done'),
629
 
                       ('cancel', 'Cancelled'),
630
 
                       ('rfq_sent', 'RfQ Sent'),
631
 
                       ('rfq_updated', 'RfQ Updated'),
632
 
                       #('rfq_done', 'RfQ Done'),
633
 
                       ]
634
 
    
635
 
    def _check_valid_till(self, cr, uid, ids, context=None):
636
 
        """ Checks if valid till has been completed
637
 
        """
638
 
        for obj in self.browse(cr, uid, ids, context=context):
639
 
            if obj.state == 'rfq_updated' and not obj.valid_till:
640
 
                return False
641
 
        return True
642
 
    
643
 
    
644
 
    _columns = {'tender_id': fields.many2one('tender', string="Tender", readonly=True),
645
 
                'rfq_ok': fields.boolean(string='Is RfQ ?'),
646
 
                'state': fields.selection(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 'Done'. If a cancel action occurs in the invoice or in the reception of goods, the state becomes in exception.", select=True),
647
 
                'valid_till': fields.date(string='Valid Till', states={'rfq_sent':[('required',True), ('readonly', False),]}, readonly=True,),
648
 
                # add readonly when state is Done
649
 
                'name': fields.char('Order Reference', size=64, required=True, states={'done':[('readonly',True)],}, select=True, help="unique number of the purchase order,computed automatically when the purchase order is created"),
650
 
                'date_order':fields.date('Date Ordered', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)], 'done':[('readonly',True)],}, select=True, help="Date on which this document has been created."),
651
 
                }
652
 
 
653
 
    _defaults = {
654
 
                'rfq_ok': lambda self, cr, uid, c: c.get('rfq_ok', False),
655
 
                'name': lambda obj, cr, uid, c: obj.pool.get('ir.sequence').get(cr, uid, c.get('rfq_ok', False) and 'rfq' or 'purchase.order'),
656
 
                 }
657
 
    
658
 
    _constraints = [
659
 
        (_check_valid_till,
660
 
            'You must specify a Valid Till date.',
661
 
            ['valid_till']),]
662
 
    
663
 
    def _hook_copy_name(self, cr, uid, ids, context=None, *args, **kwargs):
664
 
        '''
665
 
        HOOK from purchase>purchase.py for COPY function. Modification of default copy values
666
 
        define which name value will be used
667
 
        '''
668
 
        result = super(purchase_order, self)._hook_copy_name(cr, uid, ids, context=context, *args, **kwargs)
669
 
        for obj in self.browse(cr, uid, ids, context=context):
670
 
            if obj.rfq_ok:
671
 
                result.update(name=self.pool.get('ir.sequence').get(cr, uid, 'rfq'))
672
 
        return result
673
 
    
674
 
purchase_order()
675
 
 
676
 
 
677
 
class purchase_order_line(osv.osv):
678
 
    '''
679
 
    add a tender_id related field
680
 
    '''
681
 
    _inherit = 'purchase.order.line'
682
 
    _columns = {'tender_id': fields.related('order_id', 'tender_id', type='many2one', relation='tender', string='Tender',),
683
 
                }
684
 
    
685
 
purchase_order_line()
686
 
 
687
 
 
688
 
class sale_order_line(osv.osv):
689
 
    '''
690
 
    add link one2many to tender.line
691
 
    '''
692
 
    _inherit = 'sale.order.line'
693
 
    
694
 
    _columns = {'tender_line_ids': fields.one2many('tender.line', 'sale_order_line_id', string="Tender Lines", readonly=True),}
695
 
    
696
 
sale_order_line()
697
 
 
698
 
 
699
 
class pricelist_partnerinfo(osv.osv):
700
 
    '''
701
 
    add new information from specifications
702
 
    '''
703
 
    _inherit = 'pricelist.partnerinfo'
704
 
    _columns = {'currency_id': fields.many2one('res.currency', string='Currency',),
705
 
                'valid_till': fields.date(string="Valid Till",),
706
 
                'purchase_order_id': fields.related('purchase_order_line_id', 'order_id', type='many2one', relation='purchase.order', string="Related RfQ", readonly=True,),
707
 
                'purchase_order_line_id': fields.many2one('purchase.order.line', string="RfQ Line Ref",),
708
 
                'purchase_order_line_number': fields.related('purchase_order_line_id', 'line_number', type="integer", string="Related Line Number", readonly=True,),
709
 
                }
710
 
pricelist_partnerinfo()
711