~unifield-team/unifield-wm/us-671-homere

« back to all changes in this revision

Viewing changes to tender_flow/tender_flow.py

  • Committer: chloups208
  • Date: 2012-11-21 11:15:15 UTC
  • mto: This revision was merged to the branch mainline in revision 1340.
  • Revision ID: chloups208@chloups208-laptop-20121121111515-myqv282h6xmgh053
utp-171 modification of fields po, po line, product, so, so line

Show diffs side-by-side

added added

removed removed

Lines of Context:
47
47
        if not default:
48
48
            default = {}
49
49
        default['internal_state'] = 'draft' # UF-733: Reset the internal_state
50
 
        if not 'sale_order_id' in default:
51
 
            default['sale_order_id'] = False
52
50
        return super(osv.osv, self).copy(cr, uid, id, default, context=context)
53
51
    
54
52
    def unlink(self, cr, uid, ids, context=None):
55
53
        '''
56
54
        cannot delete tender not draft
57
55
        '''
58
 
        # Objects
59
 
        t_line_obj = self.pool.get('tender.line')
60
 
 
61
56
        if context is None:
62
57
            context = {}
63
58
        if isinstance(ids, (int, long)):
66
61
        for obj in self.browse(cr, uid, ids, context=context):
67
62
            if obj.state != 'draft':
68
63
                raise osv.except_osv(_('Warning !'), _("Cannot delete Tenders not in 'draft' state."))
69
 
 
70
 
            if obj.sale_order_id:
71
 
                obj_name = obj.sale_order_id.procurement_request and _('an Internal Request') or _('a Field Order')
72
 
                raise osv.except_osv(_('Warning !'), _("This tender is linked to %s, so you cannot delete it. Please cancel it instead.") % obj_name)
73
 
 
74
 
            for line in obj.tender_line_ids:
75
 
               t_line_obj.fake_unlink(cr, uid, [line.id], context=context)
76
 
 
77
64
        return super(tender, self).unlink(cr, uid, ids, context=context)
78
65
    
79
66
    def _vals_get(self, cr, uid, ids, fields, arg, context=None):
84
71
        for obj in self.browse(cr, uid, ids, context=context):
85
72
            result[obj.id] = {'rfq_name_list': '',
86
73
                              }
 
74
            
87
75
            rfq_names = []
88
76
            for rfq in obj.rfq_ids:
89
77
                rfq_names.append(rfq.name)
92
80
            result[obj.id]['rfq_name_list'] = ','.join(rfq_names)
93
81
            
94
82
        return result
95
 
 
96
 
    def _is_tender_from_fo(self, cr, uid, ids, field_name, args, context=None):
97
 
        res = {}
98
 
        for tender in self.browse(cr, uid, ids, context=context):
99
 
            retour = False
100
 
            ids_proc = self.pool.get('procurement.order').search(cr,uid,[('tender_id','=',tender.id)])
101
 
            ids_sol = self.pool.get('sale.order.line').search(cr,uid,[('procurement_id','in',ids_proc),('order_id.procurement_request','=',False)])
102
 
            if ids_sol:
103
 
                retour = True
104
 
            res[tender.id] = retour
105
 
        return res
106
 
 
 
83
    
107
84
    _columns = {'name': fields.char('Tender Reference', size=64, required=True, select=True, readonly=True),
108
85
                'sale_order_id': fields.many2one('sale.order', string="Sale Order", readonly=True),
109
86
                'state': fields.selection([('draft', 'Draft'),('comparison', 'Comparison'), ('done', 'Closed'), ('cancel', 'Cancelled'),], string="State", readonly=True),
123
100
                'notes': fields.text('Notes'),
124
101
                'internal_state': fields.selection([('draft', 'Draft'),('updated', 'Rfq Updated'), ], string="Internal State", readonly=True),
125
102
                'rfq_name_list': fields.function(_vals_get, method=True, string='RfQs Ref', type='char', readonly=True, store=False, multi='get_vals',),
126
 
                'product_id': fields.related('tender_line_ids', 'product_id', type='many2one', relation='product.product', string='Product'),
127
 
                'delivery_address': fields.many2one('res.partner.address', string='Delivery address', required=True),
128
 
               'tender_from_fo': fields.function(_is_tender_from_fo, method=True, type='boolean', string='Is tender from FO ?',),
 
103
                'product_id': fields.related('tender_line_ids', 'product_id', type='many2one', relation='product.product', string='Product')
129
104
                }
130
105
    
131
 
    _defaults = {'categ': 'other',
132
 
                 'state': 'draft',
 
106
    _defaults = {'state': 'draft',
133
107
                 'internal_state': 'draft',
 
108
                 'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'tender'),
134
109
                 'company_id': lambda obj, cr, uid, context: obj.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id,
135
110
                 'creator': lambda obj, cr, uid, context: uid,
136
111
                 'creation_date': lambda *a: time.strftime('%Y-%m-%d'),
140
115
                 }
141
116
    
142
117
    _order = 'name desc'
143
 
 
144
 
    def _check_restriction_line(self, cr, uid, ids, context=None):
145
 
        '''
146
 
        Check if there is no restrictive products in lines
147
 
        '''
148
 
        if isinstance(ids, (int, long)):
149
 
            ids = [ids]
150
 
 
151
 
        line_obj = self.pool.get('tender.line')
152
 
 
153
 
        res = True
154
 
        for tender in self.browse(cr, uid, ids, context=context):
155
 
            res = res and line_obj._check_restriction_line(cr, uid, [x.id for x in tender.tender_line_ids if x.line_state != 'cancel'], context=context)
156
 
 
157
 
        return res
158
 
 
159
 
    def default_get(self, cr, uid, fields, context=None):
160
 
        '''
161
 
        Set default data
162
 
        '''
163
 
        # Object declaration
164
 
        partner_obj = self.pool.get('res.partner')
165
 
        user_obj = self.pool.get('res.users')
166
 
 
167
 
        res = super(tender, self).default_get(cr, uid, fields, context=context)
168
 
 
169
 
        # Get the delivery address
170
 
        company = user_obj.browse(cr, uid, uid, context=context).company_id
171
 
        res['delivery_address'] = partner_obj.address_get(cr, uid, company.partner_id.id, ['delivery'])['delivery']
172
 
 
173
 
        return res
174
 
 
175
 
    def _check_tender_from_fo(self, cr, uid, ids, context=None):
176
 
        if not context:
177
 
            context = {}
178
 
        retour = True
179
 
        for tender in self.browse(cr, uid, ids, context=context):
180
 
            if not tender.tender_from_fo:
181
 
                return retour
182
 
            for sup in tender.supplier_ids:
183
 
                if sup.partner_type == 'internal' :
184
 
                    retour = False
185
 
        return retour
186
 
 
187
 
    _constraints = [
188
 
        (_check_tender_from_fo, 'You cannot choose an internal supplier for this tender', []),
189
 
    ]
190
118
    
191
 
    def create(self, cr, uid, vals, context=None):
192
 
        '''
193
 
        Set the reference of the tender at this time
194
 
        '''
195
 
        if not vals.get('name', False):
196
 
            vals.update({'name': self.pool.get('ir.sequence').get(cr, uid, 'tender')})
197
 
 
198
 
        return super(tender, self).create(cr, uid, vals, context=context)
199
 
 
200
 
    def _check_service(self, cr, uid, ids, vals, context=None):
201
 
        '''
202
 
        Avoid the saving of a Tender with non service products on Service Tender
203
 
        '''
204
 
        if isinstance(ids, (int, long)):
205
 
            ids = [ids]
206
 
        categ = {'transport': _('Transport'),
207
 
                 'service': _('Service')}
208
 
        if context is None:
209
 
            context = {}
210
 
        if context.get('import_in_progress'):
211
 
            return True
212
 
        for tender in self.browse(cr, uid, ids, context=context):
213
 
            for line in tender.tender_line_ids:
214
 
                if line.line_state == 'cancel':
215
 
                    continue
216
 
                if vals.get('categ', tender.categ) == 'transport' and line.product_id and (line.product_id.type not in ('service', 'service_recep') or not line.product_id.transport_ok):
217
 
                    raise osv.except_osv(_('Error'), _('The product [%s]%s is not a \'Transport\' product. You can have only \'Transport\' products on a \'Transport\' tender. Please remove this line.') % (line.product_id.default_code, line.product_id.name))
218
 
                    return False
219
 
                elif vals.get('categ', tender.categ) == 'service' and line.product_id and line.product_id.type not in ('service', 'service_recep'):
220
 
                    raise osv.except_osv(_('Error'), _('The product [%s] %s is not a \'Service\' product. You can have only \'Service\' products on a \'Service\' tender. Please remove this line.') % (line.product_id.default_code, line.product_id.name))
221
 
                    return False
222
 
                
223
 
        return True
224
 
 
225
 
    def write(self, cr, uid, ids, vals, context=None):
226
 
        """
227
 
        Check consistency between lines and categ of tender
228
 
        """
229
 
        # UFTP-317: Make sure ids is a list
230
 
        if isinstance(ids, (int, long)):
231
 
            ids = [ids]
232
 
        exp_sol_obj = self.pool.get('expected.sale.order.line')
233
 
 
234
 
        self._check_service(cr, uid, ids, vals, context=context)
235
 
 
236
 
        if ('state' in vals and vals.get('state') not in ('draft', 'comparison')) or \
237
 
           ('sale_order_line_id' in vals and vals.get('sale_order_line_id')):
238
 
            exp_sol_ids = exp_sol_obj.search(cr, uid, [
239
 
                ('tender_id', 'in', ids),
240
 
            ], context=context)
241
 
            exp_sol_obj.unlink(cr, uid, exp_sol_ids, context=context)
242
 
 
243
 
        return super(tender, self).write(cr, uid, ids, vals, context=context)
244
 
 
245
 
    def onchange_categ(self, cr, uid, ids, categ, context=None):
246
 
        """ Check that the categ is compatible with the product
247
 
        @param categ: Changed value of categ.
248
 
        @return: Dictionary of values.
249
 
        """
250
 
        if isinstance(ids, (int, long)):
251
 
            ids = [ids]
252
 
        message = {}
253
 
        if ids and categ in ['service', 'transport']:
254
 
            # Avoid selection of non-service producs on Service Tender
255
 
            category = categ=='service' and 'service_recep' or 'transport'
256
 
            transport_cat = ''
257
 
            if category == 'transport':
258
 
                transport_cat = 'OR p.transport_ok = False'
259
 
            cr.execute('''SELECT p.default_code AS default_code, t.name AS name
260
 
                          FROM tender_line l
261
 
                            LEFT JOIN product_product p ON l.product_id = p.id
262
 
                            LEFT JOIN product_template pt ON p.product_tmpl_id = pt.id
263
 
                            LEFT JOIN tender t ON l.tender_id = t.id
264
 
                          WHERE (pt.type != 'service_recep' %s) AND t.id in (%s) LIMIT 1''' % (transport_cat, ','.join(str(x) for x in ids)))
265
 
            res = cr.fetchall()
266
 
            if res:
267
 
                cat_name = categ=='service' and 'Service' or 'Transport'
268
 
                message.update({'title': _('Warning'),
269
 
                                'message': _('The product [%s] %s is not a \'%s\' product. You can have only \'%s\' products on a \'%s\' tender. Please remove this line before saving.') % (res[0][0], res[0][1], cat_name, cat_name, cat_name)})
270
 
                
271
 
        return {'warning': message}
272
 
 
273
119
    def onchange_warehouse(self, cr, uid, ids, warehouse_id, context=None):
274
120
        '''
275
121
        on_change function for the warehouse
292
138
        partner_obj = self.pool.get('res.partner')
293
139
        pricelist_obj = self.pool.get('product.pricelist')
294
140
        obj_data = self.pool.get('ir.model.data')
295
 
 
296
141
        # no suppliers -> raise error
297
142
        for tender in self.browse(cr, uid, ids, context=context):
298
143
            # check some supplier have been selected
299
144
            if not tender.supplier_ids:
300
145
                raise osv.except_osv(_('Warning !'), _('You must select at least one supplier!'))
301
 
            #utp-315: check that the suppliers are not inactive (I use a SQL request because the inactive partner are ignored with the browse)
302
 
            sql = """
303
 
            select tsr.supplier_id, rp.name, rp.active
304
 
            from tender_supplier_rel tsr
305
 
            left join res_partner rp
306
 
            on tsr.supplier_id = rp.id
307
 
            where tsr.tender_id=%s
308
 
            and rp.active=False
309
 
            """
310
 
            cr.execute(sql, (ids[0],))
311
 
            inactive_supplier_ids = cr.dictfetchall()
312
 
            if any(inactive_supplier_ids):
313
 
                raise osv.except_osv(_('Warning !'), _("You can't have inactive supplier! Please remove: %s"
314
 
                                                       ) % ' ,'.join([partner['name'] for partner in inactive_supplier_ids]))
315
146
            # check some products have been selected
316
 
            tender_line_ids = self.pool.get('tender.line').search(cr, uid, [('tender_id', '=', tender.id), ('line_state', '!=', 'cancel')], context=context)
 
147
            tender_line_ids = self.pool.get('tender.line').search(cr, uid, [('tender_id', '=', tender.id)], context=context)
317
148
            if not tender_line_ids:
318
149
                raise osv.except_osv(_('Warning !'), _('You must select at least one product!'))
319
150
            for supplier in tender.supplier_ids:
320
151
                # create a purchase order for each supplier
321
 
                address_id = partner_obj.address_get(cr, uid, [supplier.id], ['default'])['default']
 
152
                address_id = partner_obj.address_get(cr, uid, [supplier.id], ['delivery'])['delivery']
322
153
                if not address_id:
323
154
                    raise osv.except_osv(_('Warning !'), _('The supplier "%s" has no address defined!')%(supplier.name,))
324
155
                pricelist_id = supplier.property_product_pricelist_purchase.id
325
 
                values = {'origin': tender.sale_order_id and tender.sale_order_id.name + ';' + tender.name or tender.name,
 
156
                values = {'name': self.pool.get('ir.sequence').get(cr, uid, 'rfq'),
 
157
                          'origin': tender.sale_order_id and tender.sale_order_id.name + ';' + tender.name or tender.name,
326
158
                          'rfq_ok': True,
327
159
                          'partner_id': supplier.id,
328
160
                          'partner_address_id': address_id,
336
168
                          'priority': tender.priority,
337
169
                          'details': tender.details,
338
170
                          'delivery_requested_date': tender.requested_date,
339
 
                          'rfq_delivery_address': tender.delivery_address and tender.delivery_address.id or False,
340
171
                          }
341
172
                # create the rfq - dic is udpated for default partner_address_id at purchase.order level
342
 
                po_id = po_obj.create(cr, uid, values, context=dict(context, partner_id=supplier.id, rfq_ok=True))
 
173
                po_id = po_obj.create(cr, uid, values, context=dict(context, partner_id=supplier.id))
343
174
                
344
175
                for line in tender.tender_line_ids:
345
 
                    if line.line_state == 'cancel':
346
 
                        continue
347
 
 
348
 
                    if line.qty <= 0.00:
349
 
                        raise osv.except_osv(_('Error !'), _('You cannot generate RfQs for an line with a null quantity.'))
350
 
 
351
 
                    if line.product_id.id == obj_data.get_object_reference(cr, uid,'msf_doc_import', 'product_tbd')[1]:
 
176
                    if line.product_id.id == obj_data.get_object_reference(cr, uid,'msf_supply_doc_import', 'product_tbd')[1]:
352
177
                        raise osv.except_osv(_('Warning !'), _('You can\'t have "To Be Defined" for the product. Please select an existing product.'))
353
178
                    # create an order line for each tender line
354
179
                    price = pricelist_obj.price_get(cr, uid, [pricelist_id], line.product_id.id, line.qty, supplier.id, {'uom': line.product_uom.id})[pricelist_id]
362
187
                              'date_planned': newdate.strftime('%Y-%m-%d'),
363
188
                              'notes': line.product_id.description_purchase,
364
189
                              'order_id': po_id,
365
 
                              'tender_line_id': line.id,
366
190
                              }
367
191
                    # create purchase order line
368
192
                    pol_id = pol_obj.create(cr, uid, values, context=context)
375
199
                                                            'res_id': po_id,
376
200
                                                            'domain': [('rfq_ok', '=', True)],
377
201
                                                            }, context={'rfq_ok': True})
378
 
                self.infolog(cr, uid, "The RfQ id:%s has been generated from tender id:%s" % (
379
 
                    po_id, tender.id,
380
 
                ))
381
202
            
382
203
        self.write(cr, uid, ids, {'state':'comparison'}, context=context)
383
204
        return True
388
209
        '''
389
210
        # done all related rfqs
390
211
        wf_service = netsvc.LocalService("workflow")
391
 
        so_obj = self.pool.get('sale.order')
392
 
        sol_obj = self.pool.get('sale.order.line')
393
 
        proc_obj = self.pool.get('procurement.order')
394
 
        date_tools = self.pool.get('date.tools')                                
395
 
        fields_tools = self.pool.get('fields.tools')                            
396
 
        db_date_format = date_tools.get_db_date_format(cr, uid, context=context)
397
 
 
398
 
        if context is None:
399
 
            context= {}
400
 
 
401
 
        sol_ids = set()
402
 
 
403
212
        for tender in self.browse(cr, uid, ids, context=context):
404
213
            rfq_list = []
405
214
            for rfq in tender.rfq_ids:
413
222
                raise osv.except_osv(_('Warning !'), _("Generated RfQs must be Updated or Cancelled."))
414
223
            
415
224
            # integrity check, all lines must have purchase_order_line_id
416
 
            if not all([line.purchase_order_line_id.id for line in tender.tender_line_ids if line.line_state != 'cancel']):
 
225
            if not all([line.purchase_order_line_id.id for line in tender.tender_line_ids]):
417
226
                raise osv.except_osv(_('Error !'), _('All tender lines must have been compared!'))
418
 
 
419
 
            if tender.sale_order_id:
420
 
                # Update procurement order
421
 
                for line in tender.tender_line_ids:
422
 
                    if line.line_state == 'cancel':
423
 
                        proc_id = line.sale_order_line_id and line.sale_order_line_id.procurement_id and line.sale_order_line_id.procurement_id.id
424
 
                        if proc_id:
425
 
                            wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_cancel', cr)
426
 
                        continue
427
 
                    vals = {'product_id': line.product_id.id,
428
 
                            'product_uom': line.product_uom.id,
429
 
                            'product_uos': line.product_uom.id,
430
 
                            'product_qty': line.qty,
431
 
                            'price_unit': line.price_unit,
432
 
                            'product_uos_qty': line.qty}
433
 
                    if line.product_id.type in ('service', 'service_recep'):
434
 
                        if not tender.sale_order_id.procurement_request:
435
 
                            vals['po_cft'] = 'dpo'
436
 
 
437
 
                    if line.sale_order_line_id and line.sale_order_line_id.procurement_id:
438
 
                        proc_id = line.sale_order_line_id.procurement_id.id
439
 
                        proc_obj.write(cr, uid, [proc_id], vals, context=context)
440
 
                    else: # Create procurement order to add the lines in a PO
441
 
                        create_vals = vals.copy()
442
 
                        context['sale_id'] = tender.sale_order_id.id
443
 
                        create_vals.update({
444
 
                            'order_id': tender.sale_order_id.id,
445
 
                            'product_uom_qty': line.qty,
446
 
                            'type': 'make_to_order',
447
 
                            'po_cft': 'cft',
448
 
                            'supplier': line.supplier_id.id,
449
 
                            'created_by_tender': tender.id,
450
 
                            'created_by_tender_line': line.id,
451
 
                            'name': '[%s] %s' % (line.product_id.default_code, line.product_id.name),
452
 
                        })
453
 
                        sol_obj.create(cr, uid, create_vals, context=context)
454
 
 
455
 
                        if tender.sale_order_id.original_so_id_sale_order:
456
 
                            context['sale_id'] = tender.sale_order_id.original_so_id_sale_order.id
457
 
                            create_vals.update({
458
 
                                'order_id': tender.sale_order_id.original_so_id_sale_order.id,
459
 
                                'state': 'done',
460
 
                            })
461
 
                            sol_obj.create(cr, uid, create_vals, context=context)
462
 
 
463
 
                        sol_ids.add(tender.sale_order_id.id)
464
 
 
465
 
            self.infolog(cr, uid, "The tender id:%s has been closed" % tender.id)
466
 
 
467
 
        if sol_ids:
468
 
            so_obj.action_ship_proc_create(cr, uid, list(sol_ids), context=context)
469
 
                    
 
227
        
470
228
        # update product supplierinfo and pricelist
471
229
        self.update_supplier_info(cr, uid, ids, context=context, integrity_test=False,)
472
 
 
473
230
        # change tender state
474
231
        self.write(cr, uid, ids, {'state':'done'}, context=context)
475
232
        return True
511
268
            # gather the product_id -> supplier_id relationship to display it back in the compare wizard
512
269
            suppliers = {}
513
270
            for line in tender.tender_line_ids:
514
 
                if line.product_id and line.supplier_id and line.line_state != 'cancel':
 
271
                if line.product_id and line.supplier_id:
515
272
                    suppliers.update({line.product_id.id:line.supplier_id.id,})
516
273
            # rfq corresponding to this tender with done state (has been updated and not canceled)
517
274
            # the list of rfq which will be compared
535
292
            if integrity_test:
536
293
                self.tender_integrity(cr, uid, tender, context=context)
537
294
            for line in tender.tender_line_ids:
538
 
                if line.line_state == 'cancel':
539
 
                    continue
540
295
                # if a supplier has been selected
541
296
                if line.purchase_order_line_id:
542
297
                    # set the flag
545
300
                    product = line.product_id
546
301
                    # find the corresponding suppinfo with sequence -99
547
302
                    info_99_list = info_obj.search(cr, uid, [('product_id', '=', product.product_tmpl_id.id),
548
 
                                                             ('name', '=', line.purchase_order_line_id.order_id.partner_id.id),
549
 
                                                             ('sequence', '=', -99),], context=context)
 
303
                                                        ('sequence', '=', -99),], context=context)
550
304
                    
551
305
                    if info_99_list:
552
306
                        # we drop it
617
371
            # check if corresponding rfqs are in the good state
618
372
            self.tender_integrity(cr, uid, tender, context=context)
619
373
            # integrity check, all lines must have purchase_order_line_id
620
 
            if not all([line.purchase_order_line_id.id for line in tender.tender_line_ids if line.line_state != 'cancel']):
 
374
            if not all([line.purchase_order_line_id.id for line in tender.tender_line_ids]):
621
375
                raise osv.except_osv(_('Error !'), _('All tender lines must have been compared!'))
622
376
            data = {}
623
377
            for line in tender.tender_line_ids:
624
 
                if line.line_state == 'cancel':
625
 
                    continue
626
378
                data.setdefault(line.supplier_id.id, {}) \
627
379
                    .setdefault('order_line', []).append((0,0,{'name': line.product_id.partner_ref,
628
380
                                                               'product_qty': line.qty,
636
388
                                                               }))
637
389
                    
638
390
                # fill data corresponding to po creation
639
 
                address_id = partner_obj.address_get(cr, uid, [line.supplier_id.id], ['default'])['default']
 
391
                address_id = partner_obj.address_get(cr, uid, [line.supplier_id.id], ['delivery'])['delivery']
640
392
                pricelist = line.supplier_id.property_product_pricelist_purchase.id,
641
393
                if line.currency_id:
642
394
                    price_ids = self.pool.get('product.pricelist').search(cr, uid, [('type', '=', 'purchase'), ('currency_id', '=', line.currency_id.id)], context=context)
643
395
                    if price_ids:
644
396
                        pricelist = price_ids[0]
645
 
                po_values = {'origin': (tender.sale_order_id and tender.sale_order_id.name or "") + '; ' + tender.name,
 
397
                po_values = {'origin': (tender.sale_order_id and tender.sale_order_id.name or "") + ';' + tender.name,
646
398
                             'partner_id': line.supplier_id.id,
647
399
                             'partner_address_id': address_id,
648
400
                             'location_id': tender.location_id.id,
656
408
                             'warehouse_id': tender.warehouse_id.id,
657
409
                             'details': tender.details,
658
410
                             'delivery_requested_date': tender.requested_date,
659
 
                             'dest_address_id': tender.delivery_address.id,
660
411
                             }
661
412
                data[line.supplier_id.id].update(po_values)
662
413
            
665
416
                po_id = po_obj.create(cr, uid, po_data, context=context)
666
417
                po = po_obj.browse(cr, uid, po_id, context=context)
667
418
                po_obj.log(cr, uid, po_id, 'The Purchase order %s for supplier %s has been created.'%(po.name, po.partner_id.name))
668
 
                self.infolog(cr, uid, "The PO id:%s has been generated from tender" % po_id)
669
419
                #UF-802: the PO created must be in draft state, and not validated!
670
420
                #wf_service.trg_validate(uid, 'purchase.order', po_id, 'purchase_confirm', cr)
671
421
                
673
423
            self.done(cr, uid, [tender.id], context=context)
674
424
        
675
425
        return po_id
676
 
 
677
 
    def cancel_tender(self, cr, uid, ids, context=None):
678
 
        '''
679
 
        Ask the user if he wants to re-source all lines
680
 
        '''
681
 
        wiz_obj = self.pool.get('tender.cancel.wizard')
682
 
 
683
 
        if context is None:
684
 
            context = {}
685
 
 
686
 
        if isinstance(ids, (int, long)):
687
 
            ids = [ids]
688
 
 
689
 
        for tender_id in ids:
690
 
            tender = self.read(cr, uid, ids[0], ['state', 'sale_order_id'], context=context)
691
 
 
692
 
            wiz_id = wiz_obj.create(cr, uid, {
693
 
                'tender_id': tender['id'],
694
 
                'not_draft': tender['state'] != 'draft',
695
 
                'no_need': not tender['sale_order_id'],
696
 
            }, context=context)
697
 
 
698
 
            if tender['sale_order_id'] or tender['state'] != 'draft':
699
 
                return {'type': 'ir.actions.act_window',
700
 
                        'res_model': 'tender.cancel.wizard',
701
 
                        'res_id': wiz_id,
702
 
                        'view_mode': 'form',
703
 
                        'view_type': 'form',
704
 
                        'target': 'new',
705
 
                        'context': context}
706
 
            else:
707
 
                wiz_obj.just_cancel(cr, uid, [wiz_id], context=context)
708
 
 
709
 
        return {}
710
 
 
 
426
    
711
427
    def wkf_action_cancel(self, cr, uid, ids, context=None):
712
428
        '''
713
429
        cancel all corresponding rfqs
714
430
        '''
715
 
        if context is None:
716
 
            context = {}
717
 
 
718
431
        po_obj = self.pool.get('purchase.order')
719
 
        t_line_obj = self.pool.get('tender.line')
720
432
        wf_service = netsvc.LocalService("workflow")
721
 
 
722
433
        # set state
723
434
        self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
724
435
        for tender in self.browse(cr, uid, ids, context=context):
726
437
            rfq_ids = po_obj.search(cr, uid, [('tender_id', '=', tender.id),], context=context)
727
438
            for rfq_id in rfq_ids:
728
439
                wf_service.trg_validate(uid, 'purchase.order', rfq_id, 'purchase_cancel', cr)
729
 
 
730
 
            for line in tender.tender_line_ids:
731
 
                t_line_obj.cancel_sourcing(cr, uid, [line.id], context=context)
732
 
            self.infolog(cr, uid, "The tender id:%s has been canceled" % tender.id)
733
 
 
 
440
                
734
441
        return True
735
442
 
736
443
    def set_manually_done(self, cr, uid, ids, all_doc=True, context=None):
769
476
 
770
477
        return True
771
478
 
772
 
    def check_empty_tender(self, cr, uid, ids, context=None):
773
 
        """
774
 
        If the tender is empty, return a wizard to ask user if he wants to
775
 
        cancel the whole tender
776
 
        """
777
 
        tender_wiz_obj = self.pool.get('tender.cancel.wizard')
778
 
        data_obj = self.pool.get('ir.model.data')
779
 
 
780
 
        for tender in self.browse(cr, uid, ids, context=context):
781
 
            if all(x.line_state in ('cancel', 'done') for x in tender.tender_line_ids):
782
 
                wiz_id = tender_wiz_obj.create(cr, uid, {'tender_id': tender.id}, context=context)
783
 
                view_id = data_obj.get_object_reference(cr, uid, 'tender_flow', 'ask_tender_cancel_wizard_form_view')[1]
784
 
                return {'type': 'ir.actions.act_window',
785
 
                        'res_model': 'tender.cancel.wizard',
786
 
                        'view_type': 'form',
787
 
                        'view_mode': 'form',
788
 
                        'view_id': [view_id],
789
 
                        'res_id': wiz_id,
790
 
                        'target': 'new',
791
 
                        'context': context}
792
 
 
793
 
        return {
794
 
            'type': 'ir.actions.act_window',
795
 
            'res_model': 'tender',
796
 
            'view_type': 'form',
797
 
            'view_mode': 'form, tree',
798
 
            'res_id': ids[0],
799
 
            'context': context,
800
 
            'target': 'crush',
801
 
        }
802
 
 
803
 
    def sourcing_document_state(self, cr, uid, ids, context=None):
804
 
        """
805
 
        Returns all documents that are in the sourcing for a givent tender
806
 
        """
807
 
        if not context:
808
 
            context = {}
809
 
 
810
 
        if isinstance(ids, (int, long)):
811
 
            ids = [ids]
812
 
 
813
 
        sol_obj = self.pool.get('sale.order.line')
814
 
        so_obj = self.pool.get('sale.order')
815
 
        po_obj = self.pool.get('purchase.order')
816
 
 
817
 
        # corresponding sale order
818
 
        so_ids = []
819
 
        for tender in self.browse(cr, uid, ids, context=context):
820
 
            if tender.sale_order_id and tender.sale_order_id.id not in so_ids:
821
 
                so_ids.append(tender.sale_order_id.id)
822
 
 
823
 
        # from so, list corresponding po
824
 
        all_po_ids = so_obj.get_po_ids_from_so_ids(cr, uid, so_ids, context=context)
825
 
 
826
 
        # from listed po, list corresponding so
827
 
        all_so_ids = po_obj.get_so_ids_from_po_ids(cr, uid, all_po_ids, context=context)
828
 
 
829
 
        all_sol_not_confirmed_ids = []
830
 
        # if we have sol_ids, we are treating a po which is make_to_order from sale order
831
 
        if all_so_ids:
832
 
            all_sol_not_confirmed_ids = sol_obj.search(cr, uid, [
833
 
                ('order_id', 'in', all_so_ids),
834
 
                ('type', '=', 'make_to_order'),
835
 
                ('product_id', '!=', False),
836
 
                ('procurement_id.state', '!=', 'cancel'),
837
 
                ('state', 'not in', ['confirmed', 'done']),
838
 
            ], context=context)
839
 
 
840
 
        return so_ids, all_po_ids, all_so_ids, all_sol_not_confirmed_ids
841
 
 
842
479
tender()
843
480
 
844
481
 
847
484
    tender lines
848
485
    '''
849
486
    _name = 'tender.line'
850
 
    _rec_name = 'product_id'
851
487
    _description= 'Tender Line'
852
488
    
853
489
    _SELECTION_TENDER_STATE = [('draft', 'Draft'),('comparison', 'Comparison'), ('done', 'Closed'),]
854
490
    
855
 
    def on_product_change(self, cr, uid, id, product_id, uom_id, product_qty, context=None):
 
491
    def on_product_change(self, cr, uid, id, product_id, context=None):
856
492
        '''
857
493
        product is changed, we update the UoM
858
494
        '''
859
 
        if not context:
860
 
            context = {}
861
 
 
862
495
        prod_obj = self.pool.get('product.product')
863
496
        result = {'value': {}}
864
497
        if product_id:
865
 
            # Test the compatibility of the product with a tender
866
 
            result, test = prod_obj._on_change_restriction_error(cr, uid, product_id, field_name='product_id', values=result, vals={'constraints': ['external', 'esc', 'internal']}, context=context)
867
 
            if test:
868
 
                return result
869
 
 
870
 
            product = prod_obj.browse(cr, uid, product_id, context=context)
871
 
            result['value']['product_uom'] = product.uom_id.id
872
 
            result['value']['text_error'] = False
873
 
            result['value']['to_correct_ok'] = False
874
 
        
875
 
        res_qty = self.onchange_uom_qty(cr, uid, id, uom_id or result.get('value', {}).get('product_uom',False), product_qty)
876
 
        result['value']['qty'] = res_qty.get('value', {}).get('qty', product_qty)
877
 
        
878
 
        if uom_id:
879
 
            result['value']['product_uom'] = uom_id
880
 
 
 
498
            result['value']['product_uom'] = prod_obj.browse(cr, uid, product_id, context=context).uom_po_id.id
 
499
            
881
500
        return result
882
 
 
883
 
    def onchange_uom_qty(self, cr, uid, ids, uom_id, qty):
884
 
        '''
885
 
        Check round of qty according to the UoM
886
 
        '''
887
 
        res = {}
888
 
 
889
 
        if qty:
890
 
            res = self.pool.get('product.uom')._change_round_up_qty(cr, uid, uom_id, qty, 'qty', result=res)
891
 
 
892
 
        return res
893
501
    
894
502
    def _get_total_price(self, cr, uid, ids, field_name, arg, context=None):
895
503
        '''
933
541
                'date_planned': fields.related('tender_id', 'requested_date', type='date', string='Requested Date', store=False,),
934
542
                # functions
935
543
                'supplier_id': fields.related('purchase_order_line_id', 'order_id', 'partner_id', type='many2one', relation='res.partner', string="Supplier", readonly=True),
936
 
                '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!
937
 
                'total_price': fields.function(_get_total_price, method=True, type='float', string="Total Price", digits_compute=dp.get_precision('Purchase Price'), multi='total'),
 
544
                'price_unit': fields.related('purchase_order_line_id', 'price_unit', type="float", string="Price unit", readonly=True),
 
545
                'total_price': fields.function(_get_total_price, method=True, type='float', string="Total Price", multi='total'),
938
546
                'currency_id': fields.function(_get_total_price, method=True, type='many2one', relation='res.currency', string='Cur.', multi='total'),
939
 
                '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'),
 
547
                'func_total_price': fields.function(_get_total_price, method=True, type='float', string="Func. Total Price", multi='total'),
940
548
                'func_currency_id': fields.function(_get_total_price, method=True, type='many2one', relation='res.currency', string='Func. Cur.', multi='total'),
941
549
                'purchase_order_id': fields.related('purchase_order_line_id', 'order_id', type='many2one', relation='purchase.order', string="Related RfQ", readonly=True,),
942
 
                'purchase_order_line_number': fields.related('purchase_order_line_id', 'line_number', type="char", string="Related Line Number", readonly=True,),
 
550
                'purchase_order_line_number': fields.related('purchase_order_line_id', 'line_number', type="integer", string="Related Line Number", readonly=True,),
943
551
                'state': fields.related('tender_id', 'state', type="selection", selection=_SELECTION_TENDER_STATE, string="State",),
944
 
                'line_state': fields.selection([('draft','Draft'), ('cancel', 'Canceled'), ('done', 'Done')], string='State', readonly=True),
945
552
                'comment': fields.char(size=128, string='Comment'),
946
 
                'has_to_be_resourced': fields.boolean(string='Has to be resourced'),
947
 
                'created_by_rfq': fields.boolean(string='Created by RfQ'),
948
553
                }
949
554
    _defaults = {'qty': lambda *a: 1.0,
950
555
                 'state': lambda *a: 'draft',
951
 
                 'line_state': lambda *a: 'draft',
952
556
                 }
953
557
    
954
 
    def _check_restriction_line(self, cr, uid, ids, context=None):
955
 
        '''
956
 
        Check if there is no restrictive products in lines
957
 
        '''
958
 
        if isinstance(ids, (int, long)):
959
 
            ids = [ids]
960
 
 
961
 
        for line in self.browse(cr, uid, ids, context=context):
962
 
            if line.tender_id and line.product_id:
963
 
                if not self.pool.get('product.product')._get_restriction_error(cr, uid, line.product_id.id, vals={'constraints': ['external']}, context=context):
964
 
                    return False
965
 
 
966
 
        return True
967
 
 
968
558
    _sql_constraints = [
969
 
#        ('product_qty_check', 'CHECK( qty > 0 )', 'Product Quantity must be greater than zero.'),
 
559
        ('product_qty_check', 'CHECK( qty > 0 )', 'Product Quantity must be greater than zero.'),
970
560
    ]
971
 
 
972
 
    def create(self, cr, uid, vals, context=None):
973
 
        exp_sol_obj = self.pool.get('expected.sale.order.line')
974
 
        tender_obj = self.pool.get('tender')
975
 
 
976
 
        res = super(tender_line, self).create(cr, uid, vals, context=context)
977
 
 
978
 
        if 'tender_id' in vals and not vals.get('sale_order_line_id'):
979
 
            so_id = tender_obj.read(cr, uid, vals.get('tender_id'), ['sale_order_id'], context=context)['sale_order_id']
980
 
            if so_id:
981
 
                exp_sol_obj.create(cr, uid, {
982
 
                    'order_id': so_id[0],
983
 
                    'tender_line_id': res,
984
 
                }, context=context)
985
 
 
986
 
        return res
987
 
 
988
 
    def write(self, cr, uid, ids, vals, context=None):
989
 
        exp_sol_obj = self.pool.get('expected.sale.order.line')
990
 
 
991
 
        if 'state' in vals and vals.get('state') != 'draft':
992
 
            exp_sol_ids = exp_sol_obj.search(cr, uid, [
993
 
                ('tender_line_id', 'in', ids),
994
 
            ], context=context)
995
 
            exp_sol_obj.unlink(cr, uid, exp_sol_ids, context=context)
996
 
 
997
 
        return super(tender_line, self).write(cr, uid, ids, vals, context=context)
998
 
 
999
 
    def copy(self, cr, uid, id, default=None, context=None):
1000
 
        if default is None:
1001
 
            default = {}
1002
 
 
1003
 
        if not 'created_by_rfq' in default:
1004
 
            default['created_by_rfq'] = False
1005
 
 
1006
 
        return super(tender_line, self).copy(cr, uid, id, default, context=context)
1007
 
 
1008
 
    def cancel_sourcing(self,cr, uid, ids, context=None):
1009
 
        '''
1010
 
        Cancel the line and re-source the FO line
1011
 
        '''
1012
 
        # Objects
1013
 
        sol_obj = self.pool.get('sale.order.line')
1014
 
        uom_obj = self.pool.get('product.uom')
1015
 
        tender_obj = self.pool.get('tender')
1016
 
 
1017
 
        # Variables
1018
 
        wf_service = netsvc.LocalService("workflow")
1019
 
        to_remove = []
1020
 
        to_cancel = []
1021
 
        sol_ids = {}
1022
 
        sol_to_update = {}
1023
 
        sol_not_to_delete = []
1024
 
        so_to_update = set()
1025
 
        tender_to_update = set()
1026
 
 
1027
 
        for line in self.browse(cr, uid, ids, context=context):
1028
 
            tender_to_update.add(line.tender_id.id)
1029
 
            if line.sale_order_line_id and line.sale_order_line_id.state not in ('cancel', 'done'):
1030
 
                so_to_update.add(line.sale_order_line_id.order_id.id)
1031
 
                if line.sale_order_line_id.order_id.procurement_request:
1032
 
                    sol_not_to_delete.append(line.sale_order_line_id.id)
1033
 
                to_cancel.append(line.id)
1034
 
                # Get the ID and the product qty of the FO line to re-source
1035
 
                diff_qty = uom_obj._compute_qty(cr, uid, line.product_uom.id, line.qty, line.sale_order_line_id.product_uom.id)
1036
 
 
1037
 
                if line.has_to_be_resourced:
1038
 
                    sol_ids.update({line.sale_order_line_id.id: diff_qty})
1039
 
 
1040
 
                sol_to_update.setdefault(line.sale_order_line_id.id, 0.00)
1041
 
                sol_to_update[line.sale_order_line_id.id] += diff_qty
1042
 
            elif line.tender_id.state == 'draft':
1043
 
                to_remove.append(line.id)
1044
 
            else:
1045
 
                to_cancel.append(line.id)
1046
 
 
1047
 
        if to_cancel:
1048
 
            self.write(cr, uid, to_cancel, {'line_state': 'cancel'}, context=context)
1049
 
 
1050
 
        if sol_ids:
1051
 
            for sol in sol_ids:
1052
 
                sol_obj.add_resource_line(cr, uid, sol, False, sol_ids[sol], context=context)
1053
 
 
1054
 
        # Update sale order lines
1055
 
        so_to_cancel_ids = []
1056
 
        for sol in sol_to_update:
1057
 
            context['update_or_cancel_line_not_delete'] = sol in sol_not_to_delete
1058
 
            so_to_cancel_id = sol_obj.update_or_cancel_line(cr, uid, sol, sol_to_update[sol], context=context)
1059
 
            if so_to_cancel_id:
1060
 
                so_to_cancel_ids.append(so_to_cancel_id)
1061
 
 
1062
 
        if context.get('update_or_cancel_line_not_delete', False):
1063
 
            del context['update_or_cancel_line_not_delete']
1064
 
 
1065
 
        # Update the FO state
1066
 
        #for so in so_to_update:
1067
 
        #    wf_service.trg_write(uid, 'sale.order', so, cr)
1068
 
 
1069
 
        # UF-733: if all tender lines have been compared (have PO Line id), then set the tender to be ready
1070
 
        # for proceeding to other actions (create PO, Done etc) 
1071
 
        for tender in tender_obj.browse(cr, uid, list(tender_to_update), context=context):
1072
 
            if tender.internal_state == 'draft':
1073
 
                flag = True
1074
 
                for line in tender.tender_line_ids:
1075
 
                    if line.line_state != 'cancel' and not line.purchase_order_line_id:
1076
 
                        flag = False
1077
 
                if flag:
1078
 
                    tender_obj.write(cr, uid, [tender.id], {'internal_state': 'updated'})
1079
 
 
1080
 
        if context.get('fake_unlink'):
1081
 
            return to_remove
1082
 
 
1083
 
        return so_to_cancel_ids
1084
 
 
1085
 
    def fake_unlink(self, cr, uid, ids, context=None):
1086
 
        '''
1087
 
        Cancel the line if it is linked to a FO line
1088
 
        '''
1089
 
        to_remove = self.cancel_sourcing(cr, uid, ids, context=dict(context, fake_unlink=True))
1090
 
 
1091
 
        for tl_id in ids:
1092
 
            self.infolog(cr, uid, "The tender line id:%s has been canceled" % tl_id)
1093
 
 
1094
 
        return self.unlink(cr, uid, to_remove, context=context)
1095
 
 
1096
 
    def ask_unlink(self, cr, uid, ids, context=None):
1097
 
        '''
1098
 
        Ask user if he wants to re-source the needs
1099
 
        '''
1100
 
        # Objects
1101
 
        wiz_obj = self.pool.get('tender.line.cancel.wizard')
1102
 
        tender_obj = self.pool.get('tender')
1103
 
        exp_sol_obj = self.pool.get('expected.sale.order.line')
1104
 
 
1105
 
        # Variables
1106
 
        if context is None:
1107
 
            context = {}
1108
 
 
1109
 
        if isinstance(ids, (int, long)):
1110
 
            ids = [ids]
1111
 
 
1112
 
        # Check if the line has been already deleted
1113
 
        ids = self.search(cr, uid, [('id', 'in', ids), ('line_state', '!=', 'cancel')], context=context)
1114
 
        if not ids:
1115
 
            raise osv.except_osv(
1116
 
                _('Error'),
1117
 
                _('The line has already been canceled - Please refresh the page'),
1118
 
            )
1119
 
 
1120
 
        tender_id = False
1121
 
        for line in self.browse(cr, uid, ids, context=context):
1122
 
            tender_id = line.tender_id.id
1123
 
            wiz_id = False
1124
 
            last_line = False
1125
 
            exp_sol_ids = None
1126
 
 
1127
 
            if line.tender_id.sale_order_id:
1128
 
                exp_sol_ids = exp_sol_obj.search(cr, uid, [
1129
 
                    ('tender_id', '=', tender_id),
1130
 
                    ('tender_line_id', '!=', line.id),
1131
 
                ], context=context)
1132
 
 
1133
 
                tender_so_ids, po_ids, so_ids, sol_nc_ids = tender_obj.sourcing_document_state(cr, uid, [tender_id], context=context)
1134
 
                if line.sale_order_line_id and line.sale_order_line_id.id in sol_nc_ids:
1135
 
                    sol_nc_ids.remove(line.sale_order_line_id.id)
1136
 
 
1137
 
                if po_ids and not exp_sol_ids and not sol_nc_ids:
1138
 
                    last_line = True
1139
 
 
1140
 
            if line.sale_order_line_id:
1141
 
                wiz_id = wiz_obj.create(cr, uid, {
1142
 
                    'tender_line_id': line.id,
1143
 
                    'last_line': last_line,
1144
 
                }, context=context)
1145
 
            elif not exp_sol_ids and line.tender_id.sale_order_id:
1146
 
                wiz_id = wiz_obj.create(cr, uid, {
1147
 
                    'tender_line_id': line.id,
1148
 
                    'only_exp': True,
1149
 
                    'last_line': last_line,
1150
 
                }, context=context)
1151
 
 
1152
 
            if wiz_id:
1153
 
                return {'type': 'ir.actions.act_window',
1154
 
                        'res_model': 'tender.line.cancel.wizard',
1155
 
                        'view_type': 'form',
1156
 
                        'view_mode': 'form',
1157
 
                        'res_id': wiz_id,
1158
 
                        'target': 'new',
1159
 
                        'context': context}
1160
 
 
1161
 
        for line_id in ids:
1162
 
            wiz_id = wiz_obj.create(cr, uid, {
1163
 
                'tender_line_id': line_id,
1164
 
            }, context=context)
1165
 
 
1166
 
        if wiz_id:
1167
 
            return wiz_obj.just_cancel(cr, uid, wiz_id, context=context)
1168
 
 
1169
 
        return {'type': 'ir.actions.act_window',
1170
 
                'res_model': 'tender',
1171
 
                'view_type': 'form',
1172
 
                'view_mode': 'form,tree',
1173
 
                'res_id': tender_id,
1174
 
                'target': 'crush',
1175
 
                'context': context}
1176
 
 
 
561
    
1177
562
tender_line()
1178
563
 
1179
564
 
1211
596
        # reset the tender line
1212
597
        for line in result['tender_line_ids']:
1213
598
            line[2].update(sale_order_line_id=False,
1214
 
                           purchase_order_line_id=False,
1215
 
                           line_state='draft',)
 
599
                           purchase_order_line_id=False,)
1216
600
        return result
1217
601
 
1218
602
tender2()
1224
608
    '''
1225
609
    _inherit = 'procurement.order'
1226
610
    
1227
 
    def _is_tender_rfq(self, cr, uid, ids, field_name, arg, context=None):
 
611
    def _is_tender(self, cr, uid, ids, field_name, arg, context=None):
1228
612
        '''
1229
 
        tell if the corresponding sale order line is tender/rfq sourcing or not
 
613
        tell if the corresponding sale order line is tender sourcing or not
1230
614
        '''
1231
615
        result = {}
 
616
        for id in ids:
 
617
            result[id] = False
 
618
            
1232
619
        for proc in self.browse(cr, uid, ids, context=context):
1233
 
            result[proc.id] = {'is_tender': False, 'is_rfq': False}
1234
620
            for line in proc.sale_order_line_ids:
1235
 
                result[proc.id]['is_tender'] = line.po_cft == 'cft'
1236
 
                result[proc.id]['is_rfq'] = line.po_cft == 'rfq'
 
621
                result[proc.id] = line.po_cft == 'cft'
1237
622
                                
1238
623
        return result
1239
624
    
1240
 
    _columns = {'is_tender': fields.function(_is_tender_rfq, method=True, type='boolean', string='Is Tender', readonly=True, multi='tender_rfq'),
1241
 
                'is_rfq': fields.function(_is_tender_rfq, method=True, type='boolean', string='Is RfQ', readonly=True, multi='tender_rfq'),
 
625
    _columns = {'is_tender': fields.function(_is_tender, method=True, type='boolean', string='Is Tender', readonly=True,),
1242
626
                'sale_order_line_ids': fields.one2many('sale.order.line', 'procurement_id', string="Sale Order Lines"),
1243
627
                'tender_id': fields.many2one('tender', string='Tender', readonly=True),
1244
 
                'tender_line_id': fields.many2one('tender.line', string='Tender line', readonly=True),
1245
628
                'is_tender_done': fields.boolean(string="Tender Closed"),
1246
 
                'rfq_id': fields.many2one('purchase.order', string='RfQ', readonly=True),
1247
 
                'rfq_line_id': fields.many2one('purchase.order.line', string='RfQ line', readonly=True),
1248
 
                'is_rfq_done': fields.boolean(string="RfQ Closed"),
1249
629
                'state': fields.selection([('draft','Draft'),
1250
630
                                           ('confirmed','Confirmed'),
1251
631
                                           ('exception','Exception'),
1254
634
                                           ('ready','Ready'),
1255
635
                                           ('done','Closed'),
1256
636
                                           ('tender', 'Tender'),
1257
 
                                           ('rfq', 'Request for Quotation'),
1258
637
                                           ('waiting','Waiting'),], 'State', required=True,
1259
638
                                          help='When a procurement is created the state is set to \'Draft\'.\n If the procurement is confirmed, the state is set to \'Confirmed\'.\
1260
639
                                                \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.'),
1261
 
                'price_unit': fields.float('Unit Price from Tender', digits_compute=dp.get_precision('Purchase Price Computation')),
 
640
                'price_unit': fields.float('Unit Price from Tender', digits_compute= dp.get_precision('Purchase Price')),
1262
641
        }
1263
 
    _defaults = {
1264
 
        'is_tender_done': False,
1265
 
        'is_rfq_done': False,
1266
 
    }
1267
 
 
1268
 
    def no_address_error(self, cr, uid, ids, context=None):
1269
 
        '''
1270
 
        Put the procurement order in exception state with the good error message
1271
 
        '''
1272
 
        for proc in self.browse(cr, uid, ids, context=context):
1273
 
            if proc.supplier and not proc.supplier.address:
1274
 
                self.write(cr, uid, [proc.id], {
1275
 
                    'state': 'exception',
1276
 
                    'message': _('The supplier "%s" has no address defined!')%(proc.supplier.name,),
1277
 
                }, context=context)
1278
 
 
1279
 
        return True
1280
 
 
1281
 
    def wkf_action_rfq_create(self, cr, uid, ids, context=None):
1282
 
        '''
1283
 
        creation of rfq from procurement workflow
1284
 
        '''
1285
 
        rfq_obj = self.pool.get('purchase.order')
1286
 
        rfq_line_obj = self.pool.get('purchase.order.line')
1287
 
        partner_obj = self.pool.get('res.partner')
1288
 
        prsd_obj = self.pool.get('procurement.request.sourcing.document')
1289
 
 
1290
 
        if not context:
1291
 
            context = {}
1292
 
 
1293
 
        # find the corresponding sale order id for rfq
1294
 
        for proc in self.browse(cr, uid, ids, context=context):
1295
 
            if proc.rfq_id:
1296
 
                return proc.rfq_id
1297
 
            sale_order = False
1298
 
            sale_order_line = False
1299
 
            for sol in proc.sale_order_line_ids:
1300
 
                sale_order = sol.order_id
1301
 
                sale_order_line = sol
1302
 
                break
1303
 
            # find the rfq
1304
 
            rfq_id = False
1305
 
            # UTP-934: If source rfq to different supplier, different rfq must be created, and cannot be using the same rfq 
1306
 
            rfq_ids = rfq_obj.search(cr, uid, [('sale_order_id', '=', sale_order.id),('partner_id', '=', proc.supplier.id), ('state', '=', 'draft'), ('rfq_ok', '=', True),], context=context)
1307
 
            if rfq_ids:
1308
 
                rfq_id = rfq_ids[0]
1309
 
            # create if not found
1310
 
            if not rfq_id:
1311
 
                supplier = proc.supplier
1312
 
                company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id
1313
 
                pricelist_id = supplier.property_product_pricelist_purchase.id
1314
 
                address_id = partner_obj.address_get(cr, uid, [supplier.id], ['default'])['default']
1315
 
                if not address_id:
1316
 
                    self.write(cr, uid, [proc.id], {
1317
 
                        'message': _('The supplier "%s" has no address defined!')%(supplier.name,),
1318
 
                    }, context=context)
1319
 
                    continue
1320
 
 
1321
 
                context['rfq_ok'] = True
1322
 
                rfq_id = rfq_obj.create(cr, uid, {'sale_order_id': sale_order.id,
1323
 
                                                  'categ': sale_order.categ,
1324
 
                                                  'priority': sale_order.priority,
1325
 
                                                  'fiscal_position': supplier.property_account_position and supplier.property_account_position.id or False,
1326
 
                                                  'rfq_delivery_address': partner_obj.address_get(cr, uid, company.partner_id.id, ['delivery'])['delivery'],
1327
 
                                                  'warehouse_id': sale_order.shop_id.warehouse_id.id,
1328
 
                                                  'location_id': proc.location_id.id,
1329
 
                                                  'partner_id': supplier.id,
1330
 
                                                  'partner_address_id': address_id,
1331
 
                                                  'pricelist_id': pricelist_id,
1332
 
                                                  'rfq_ok': True,
1333
 
                                                  'from_procurement': True,
1334
 
                                                  'order_type': sale_order.order_type,
1335
 
                                                  'origin': sale_order.name,}, context=context)
1336
 
 
1337
 
            prsd_obj.chk_create(cr, uid, {
1338
 
                'order_id': sale_order.id,
1339
 
                'sourcing_document_id': rfq_id,
1340
 
                'sourcing_document_model': 'purchase.order',
1341
 
                'sourcing_document_type': 'rfq',
1342
 
            }, context=context)
1343
 
 
1344
 
            # add a line to the RfQ
1345
 
            rfq_line_id = rfq_line_obj.create(cr, uid, {'product_id': proc.product_id.id,
1346
 
                                                        'comment': sale_order_line.comment,
1347
 
                                                        'name': sale_order_line.name,
1348
 
                                                        'price_unit': 0.00,
1349
 
                                                        'product_qty': proc.product_qty,
1350
 
                                                        'origin': sale_order.name,
1351
 
                                                        'order_id': rfq_id,
1352
 
                                                        'sale_order_line_id': sale_order_line.id,
1353
 
                                                        'location_id': proc.location_id.id,
1354
 
                                                        'product_uom': proc.product_uom.id,
1355
 
                                                        'procurement_id': proc.id,
1356
 
                                                        #'date_planned': proc.date_planned, # function at line level
1357
 
                                                        }, context=context)
1358
 
            
1359
 
            self.write(cr, uid, ids, {'rfq_id': rfq_id, 'rfq_line_id': rfq_line_id}, context=context)
1360
 
            
1361
 
            # log message concerning RfQ creation
1362
 
            rfq_obj.log(cr, uid, rfq_id, "The Request for Quotation '%s' has been created and must be completed before purchase order creation."%rfq_obj.browse(cr, uid, rfq_id, context=context).name, context={'rfq_ok': 1})
1363
 
            self.infolog(cr, uid, "The FO/IR line id:%s has been sourced on order to RfQ line id:%s of the RfQ id:%s" % (
1364
 
                sale_order_line.id, rfq_line_id, rfq_id,
1365
 
            ))
1366
 
        # state of procurement is Tender
1367
 
        self.write(cr, uid, ids, {'state': 'rfq'}, context=context)
1368
 
        
1369
 
        return rfq_id
 
642
    _defaults = {'is_tender_done': False,}
1370
643
    
1371
644
    def wkf_action_tender_create(self, cr, uid, ids, context=None):
1372
645
        '''
1374
647
        '''
1375
648
        tender_obj = self.pool.get('tender')
1376
649
        tender_line_obj = self.pool.get('tender.line')
1377
 
        prsd_obj = self.pool.get('procurement.request.sourcing.document')
1378
650
        # find the corresponding sale order id for tender
1379
651
        for proc in self.browse(cr, uid, ids, context=context):
1380
 
            if proc.tender_id:
1381
 
                return proc.tender_id
1382
652
            sale_order = False
1383
653
            sale_order_line = False
1384
654
            for sol in proc.sale_order_line_ids:
1398
668
                                                        'warehouse_id': sale_order.shop_id.warehouse_id.id,
1399
669
                                                        'requested_date': proc.date_planned,
1400
670
                                                        }, context=context)
1401
 
            prsd_obj.chk_create(cr, uid, {
1402
 
                'order_id': sale_order.id,
1403
 
                'sourcing_document_id': tender_id,
1404
 
                'sourcing_document_model': 'tender',
1405
 
                'sourcing_document_type': 'tender',
1406
 
            }, context=context)
1407
671
            # add a line to the tender
1408
 
            tender_line_id = tender_line_obj.create(cr, uid, {'product_id': proc.product_id.id,
1409
 
                                                              'comment': sale_order_line.comment,
1410
 
                                                              'qty': proc.product_qty,
1411
 
                                                              'tender_id': tender_id,
1412
 
                                                              'sale_order_line_id': sale_order_line.id,
1413
 
                                                              'location_id': proc.location_id.id,
1414
 
                                                              'product_uom': proc.product_uom.id,
1415
 
                                                              #'date_planned': proc.date_planned, # function at line level
1416
 
                                                              }, context=context)
 
672
            tender_line_obj.create(cr, uid, {'product_id': proc.product_id.id,
 
673
                                             'comment': sale_order_line.comment,
 
674
                                             'qty': proc.product_qty,
 
675
                                             'tender_id': tender_id,
 
676
                                             'sale_order_line_id': sale_order_line.id,
 
677
                                             'location_id': proc.location_id.id,
 
678
                                             'product_uom': proc.product_uom.id,
 
679
                                             #'date_planned': proc.date_planned, # function at line level
 
680
                                             }, context=context)
1417
681
            
1418
 
            self.write(cr, uid, ids, {'tender_id': tender_id, 'tender_line_id': tender_line_id}, context=context)
 
682
            self.write(cr, uid, ids, {'tender_id': tender_id}, context=context)
1419
683
            
1420
684
            # log message concerning tender creation
1421
685
            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)
1422
 
            self.infolog(cr, uid, "The FO/IR line id:%s has been sourced on order to tender line id:%s of the tender id:%s" % (
1423
 
                sale_order_line.id, tender_line_id, tender_id,
1424
 
            ))
1425
686
        # state of procurement is Tender
1426
687
        self.write(cr, uid, ids, {'state': 'tender'}, context=context)
1427
688
        
1433
694
        '''
1434
695
        self.write(cr, uid, ids, {'is_tender_done': True, 'state': 'exception',}, context=context)
1435
696
        return True
1436
 
 
1437
 
    def wkf_action_rfq_done(self, cr, uid, ids, context=None):
1438
 
        '''
1439
 
        set is_rfq_done value
1440
 
        '''
1441
 
        self.write(cr, uid, ids, {'is_rfq_done': True, 'state': 'exception',}, context=context)
1442
 
        return True
1443
 
 
1444
 
    def _get_pricelist_from_currency(self, cr, uid, currency_id, context=None):
1445
 
        price_obj = self.pool.get('product.pricelist')
1446
 
        price_ids = price_obj.search(cr, uid, [
1447
 
            ('currency_id', '=', currency_id),
1448
 
            ('type', '=', 'purchase'),
1449
 
        ], context=context)
1450
 
 
1451
 
        return price_ids and price_ids[0] or False
1452
697
    
1453
698
    def action_po_assign(self, cr, uid, ids, context=None):
1454
699
        '''
1456
701
        - add message at po creation during on_order workflow
1457
702
        '''
1458
703
        po_obj = self.pool.get('purchase.order')
1459
 
        sol_obj = self.pool.get('sale.order.line')
1460
 
 
1461
 
        # If the line has been created by a confirmed PO, doesn't create a new PO
1462
 
        sol_ids = sol_obj.search(cr, uid, [('procurement_id', 'in', ids), ('created_by_po', '!=', False)], context=context)
1463
 
        if sol_ids:
1464
 
            return sol_obj.read(cr, uid, sol_ids[0], ['created_by_po'], context=context)['created_by_po'][0]
1465
 
 
1466
704
        result = super(procurement_order, self).action_po_assign(cr, uid, ids, context=context)
1467
705
        # The quotation 'SO001' has been converted to a sales order.
1468
706
        if result:
1478
716
        '''
1479
717
        values = super(procurement_order, self).po_values_hook(cr, uid, ids, context=context, *args, **kwargs)
1480
718
        procurement = kwargs['procurement']
1481
 
 
1482
 
        values['partner_address_id'] = self.pool.get('res.partner').address_get(cr, uid, [values['partner_id']], ['default'])['default']
1483
 
 
1484
 
        # set tender link in purchase order
1485
 
        if procurement.tender_id:
1486
 
            values['origin_tender_id'] = procurement.tender_id.id
1487
 
 
1488
 
        # set tender line currency in purchase order
1489
 
        if procurement.tender_line_id:
1490
 
            cur_id = procurement.tender_line_id.currency_id.id
1491
 
            pricelist_id = self._get_pricelist_from_currency(cr, uid, cur_id, context=context)
1492
 
            if pricelist_id:
1493
 
                values['pricelist_id'] = pricelist_id
1494
 
 
1495
 
        # set rfq link in purchase order
1496
 
        if procurement.rfq_id:
1497
 
            values.update({
1498
 
                'origin_rfq_id': procurement.rfq_id.id,
1499
 
                'pricelist_id': procurement.rfq_id.pricelist_id.id,
1500
 
            })
1501
 
 
 
719
        
1502
720
        values['date_planned'] = procurement.date_planned
1503
721
        
1504
722
        if procurement.product_id:
1514
732
                    values['location_id'] = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_config_locations', 'stock_location_service')[1]
1515
733
        
1516
734
        return values
1517
 
 
 
735
    
1518
736
procurement_order()
1519
737
 
1520
738
 
1523
741
    add link to tender
1524
742
    '''
1525
743
    _inherit = 'purchase.order'
1526
 
 
 
744
    
1527
745
    def _check_valid_till(self, cr, uid, ids, context=None):
1528
746
        """ Checks if valid till has been completed
1529
747
        """
1531
749
            if obj.state == 'rfq_updated' and not obj.valid_till:
1532
750
                return False
1533
751
        return True
1534
 
 
1535
752
    _columns = {'tender_id': fields.many2one('tender', string="Tender", readonly=True),
1536
 
                'rfq_delivery_address': fields.many2one('res.partner.address', string='Delivery address'),
1537
753
                'origin_tender_id': fields.many2one('tender', string='Tender', readonly=True),
1538
 
                'from_procurement': fields.boolean(string='RfQ created by a procurement order'),
1539
754
                'rfq_ok': fields.boolean(string='Is RfQ ?'),
1540
755
                '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),
1541
756
                'valid_till': fields.date(string='Valid Till', states={'rfq_updated': [('required', True), ('readonly', True)], 'rfq_sent':[('required',False), ('readonly', False),]}, readonly=True,),
1542
757
                # add readonly when state is Done
1543
 
                'sale_order_id': fields.many2one('sale.order', string='Link between RfQ and FO', readonly=True),
1544
758
                }
1545
759
 
1546
760
    _defaults = {
1547
761
                'rfq_ok': lambda self, cr, uid, c: c.get('rfq_ok', False),
 
762
                'name': lambda obj, cr, uid, c: obj.pool.get('ir.sequence').get(cr, uid, c.get('rfq_ok', False) and 'rfq' or 'purchase.order'),
1548
763
                 }
1549
764
    
1550
765
    _constraints = [
1551
766
        (_check_valid_till,
1552
767
            'You must specify a Valid Till date.',
1553
768
            ['valid_till']),]
1554
 
 
1555
 
    def default_get(self, cr, uid, fields, context=None):
1556
 
        '''
1557
 
        Set default data
1558
 
        '''
1559
 
        # Object declaration
1560
 
        partner_obj = self.pool.get('res.partner')
1561
 
        user_obj = self.pool.get('res.users')
1562
 
 
1563
 
        res = super(purchase_order, self).default_get(cr, uid, fields, context=context)
1564
 
 
1565
 
        # Get the delivery address
1566
 
        company = user_obj.browse(cr, uid, uid, context=context).company_id
1567
 
        res['rfq_delivery_address'] = partner_obj.address_get(cr, uid, company.partner_id.id, ['delivery'])['delivery']
1568
 
 
1569
 
        return res
1570
 
 
1571
 
    def create(self, cr, uid, vals, context=None):
1572
 
        '''
1573
 
        Set the reference at this step
1574
 
        '''
1575
 
        if context is None:
1576
 
            context = {}
1577
 
        if context.get('rfq_ok', False) and not vals.get('name', False):
1578
 
            vals.update({'name': self.pool.get('ir.sequence').get(cr, uid, 'rfq')})
1579
 
        elif not vals.get('name', False):
1580
 
            vals.update({'name': self.pool.get('ir.sequence').get(cr, uid, 'purchase.order')})
1581
 
 
1582
 
        return super(purchase_order, self).create(cr, uid, vals, context=context)
1583
769
    
1584
770
    def unlink(self, cr, uid, ids, context=None):
1585
771
        '''
1606
792
        HOOK from purchase>purchase.py for COPY function. Modification of default copy values
1607
793
        define which name value will be used
1608
794
        '''
1609
 
        # default values from copy function
1610
 
        default = kwargs.get('default', False)
1611
 
        # flag defining if the new object will be a rfq
1612
 
        is_rfq = False
1613
 
        # calling super function
1614
795
        result = super(purchase_order, self)._hook_copy_name(cr, uid, ids, context=context, *args, **kwargs)
1615
 
        if default.get('rfq_ok', False):
1616
 
            is_rfq = True
1617
 
        elif 'rfq_ok' not in default:
1618
 
            for obj in self.browse(cr, uid, ids, context=context):
1619
 
                # if rfq_ok is specified as default value for new object, we base our decision on this value
1620
 
                if obj.rfq_ok:
1621
 
                    is_rfq = True
1622
 
        if is_rfq:
1623
 
            result.update(name=self.pool.get('ir.sequence').get(cr, uid, 'rfq'))
 
796
        for obj in self.browse(cr, uid, ids, context=context):
 
797
            if obj.rfq_ok:
 
798
                result.update(name=self.pool.get('ir.sequence').get(cr, uid, 'rfq'))
1624
799
        return result
1625
800
 
1626
801
    def hook_rfq_sent_check_lines(self, cr, uid, ids, context=None):
1629
804
        This hook belongs to the rfq_sent method from tender_flow>tender_flow.py
1630
805
        - check lines after import
1631
806
        '''
1632
 
        pol_obj = self.pool.get('purchase.order.line')                          
1633
 
        
1634
 
        res = True                                                              
1635
 
        empty_lines = pol_obj.search(cr, uid, [                                 
1636
 
            ('order_id', 'in', ids),                                            
1637
 
            ('product_qty', '<=', 0.00),                                        
1638
 
        ], context=context)                                                     
1639
 
        if empty_lines:                                                         
1640
 
            raise osv.except_osv(                                               
1641
 
                _('Error'),                                                     
1642
 
                _('All lines of the RfQ should have a quantity before sending the RfQ to the supplier'),
1643
 
                    ) 
 
807
        res = True
1644
808
        return res
1645
 
 
 
809
        
1646
810
        
1647
811
    def rfq_sent(self, cr, uid, ids, context=None):
1648
 
        if not ids:
1649
 
            return {}
1650
 
        if isinstance(ids, (int, long)):
1651
 
            ids = [ids]
1652
812
        self.hook_rfq_sent_check_lines(cr, uid, ids, context=context)
1653
813
        for rfq in self.browse(cr, uid, ids, context=context):
1654
814
            wf_service = netsvc.LocalService("workflow")
1657
817
        self.write(cr, uid, ids, {'date_confirm': time.strftime('%Y-%m-%d')}, context=context)
1658
818
 
1659
819
        datas = {'ids': ids}
1660
 
        if len(ids) == 1:
1661
 
            # UFTP-92: give a name to report when generated from RfQ worklow sent_rfq stage
1662
 
            datas['target_filename'] = 'RFQ_' + rfq.name
1663
820
 
1664
821
        return {'type': 'ir.actions.report.xml',
1665
822
                'report_name': 'msf.purchase.quotation',
1666
823
                'datas': datas}
1667
824
 
1668
825
    def check_rfq_updated(self, cr, uid, ids, context=None):
1669
 
        tl_obj = self.pool.get('tender.line')
1670
 
        line_obj = self.pool.get('purchase.order.line')
1671
 
 
1672
826
        if isinstance(ids, (int, long)):
1673
827
            ids = [ids]
1674
828
 
1677
831
            if not rfq.valid_till:
1678
832
                raise osv.except_osv(_('Error'), _('You must specify a Valid Till date.'))
1679
833
 
1680
 
            if rfq.rfq_ok and rfq.tender_id:
1681
 
                for line in rfq.order_line:
1682
 
                    if not line.tender_line_id:
1683
 
                        tl_ids = tl_obj.search(cr, uid, [('product_id', '=', line.product_id.id), ('tender_id', '=', rfq.tender_id.id), ('line_state', '=', 'draft')], context=context)
1684
 
                        if tl_ids:
1685
 
                            tl_id = tl_ids[0]
1686
 
                        else:
1687
 
                            tl_vals = {'product_id': line.product_id.id,
1688
 
                                       'product_uom': line.product_uom.id,
1689
 
                                       'qty': line.product_qty,
1690
 
                                       'tender_id': rfq.tender_id.id,
1691
 
                                       'created_by_rfq': True}
1692
 
                            tl_id = tl_obj.create(cr, uid, tl_vals, context=context)
1693
 
                            self.infolog(cr, uid, "The tender line id:%s has been created by the RfQ line id:%s" % (
1694
 
                                tl_id, line.id,
1695
 
                            ))
1696
 
                        line_obj.write(cr, uid, [line.id], {'tender_line_id': tl_id}, context=context)
1697
 
            elif rfq.rfq_ok:
1698
 
                line_ids = line_obj.search(cr, uid, [
1699
 
                    ('order_id', '=', rfq.id),
1700
 
                    ('price_unit', '=', 0.00),
1701
 
                ], count=True, context=context)
1702
 
                if line_ids:
1703
 
                    raise osv.except_osv(
1704
 
                        _('Error'),
1705
 
                        _('''You cannot update an RfQ with lines without unit
1706
 
price. Please set unit price on these lines or cancel them'''),
1707
 
                    )
1708
 
 
1709
834
            wf_service.trg_validate(uid, 'purchase.order', rfq.id, 'rfq_updated', cr)
1710
 
            self.infolog(cr, uid, "The RfQ id:%s has been updated" % rfq.id)
1711
835
 
1712
 
        return {
1713
 
            'type': 'ir.actions.act_window',
1714
 
            'res_model': 'purchase.order',
1715
 
            'view_mode': 'form,tree,graph,calendar',
1716
 
            'view_type': 'form',
1717
 
            'target': 'crush',
1718
 
            'context': {'rfq_ok': True, 'search_default_draft_rfq': 1},
1719
 
            'domain': [('rfq_ok', '=', True)],
1720
 
            'res_id': rfq.id,
1721
 
        }
 
836
        return {'type': 'ir.actions.act_window',
 
837
                'res_model': 'purchase.order',
 
838
                'view_mode': 'form,tree,graph,calendar',
 
839
                'view_type': 'form',
 
840
                'target': 'crush',
 
841
                'context': {'rfq_ok': True, 'search_default_draft_rfq': 1,},
 
842
                'domain': [('rfq_ok', '=', True)],
 
843
                'res_id': rfq.id}
1722
844
        
1723
845
    def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
1724
846
        """
1747
869
            if context.get('rfq_ok', False):
1748
870
                # the title of the screen depends on po type
1749
871
                form = etree.fromstring(result['arch'])
1750
 
                
1751
 
                fields = form.xpath('//form[@string="%s"]' % _('Purchase Order'))
 
872
                fields = form.xpath('//form[@string="Purchase Order"]')
1752
873
                for field in fields:
1753
 
                    field.set('string', _("Request for Quotation"))
1754
 
                
1755
 
                fields2 = form.xpath('//page[@string="%s"]' % _('Purchase Order'))
1756
 
                for field2 in fields2:
1757
 
                    field2.set('string', _("Request for Quotation"))
1758
 
 
 
874
                    field.set('string', "Requests for Quotation")
1759
875
                result['arch'] = etree.tostring(form)
1760
876
        
1761
877
        return result
1762
878
 
1763
 
    def wkf_act_rfq_done(self, cr, uid, ids, context=None):
1764
 
        '''
1765
 
        Set the state to done and update the price unit in the procurement order
1766
 
        '''
1767
 
        wf_service = netsvc.LocalService("workflow")
1768
 
        proc_obj = self.pool.get('procurement.order')
1769
 
        date_tools = self.pool.get('date.tools')
1770
 
        fields_tools = self.pool.get('fields.tools')
1771
 
        db_date_format = date_tools.get_db_date_format(cr, uid, context=context)
1772
 
 
1773
 
        if isinstance(ids, (int, long)):
1774
 
            ids = [ids]
1775
 
 
1776
 
        for rfq in self.browse(cr, uid, ids, context=context):
1777
 
            self.infolog(cr, uid, "The RfQ id:%s has been closed" % rfq.id)
1778
 
            if rfq.from_procurement:
1779
 
                for line in rfq.order_line:
1780
 
                    if line.procurement_id:
1781
 
                        vals = {'price_unit': line.price_unit}
1782
 
                        self.pool.get('procurement.order').write(cr, uid, [line.procurement_id.id], vals, context=context)
1783
 
#                    elif not rfq.tender_id:
1784
 
#                        prep_lt = fields_tools.get_field_from_company(cr, uid, object='sale.order', field='preparation_lead_time', context=context)
1785
 
#                        rts = datetime.strptime(rfq.sale_order_id.ready_to_ship_date, db_date_format)
1786
 
#                        rts = rts - relativedelta(days=prep_lt or 0)
1787
 
#                        rts = rts.strftime(db_date_format)
1788
 
#                        vals = {'product_id': line.product_id.id,
1789
 
#                                'product_uom': line.product_uom.id,
1790
 
#                                'product_uos': line.product_uom.id,
1791
 
#                                'product_qty': line.product_qty,
1792
 
#                                'product_uos_qty': line.product_qty,
1793
 
#                                'price_unit': line.price_unit,
1794
 
#                                'procure_method': 'make_to_order',
1795
 
#                                'is_rfq': True,
1796
 
#                                'rfq_id': rfq.id,
1797
 
#                                'rfq_line_id': line.id,
1798
 
#                                'is_tender': False,
1799
 
#                                'tender_id': False,
1800
 
#                                'tender_line_id': False,
1801
 
#                                'date_planned': rts,
1802
 
#                                'origin': rfq.sale_order_id.name,
1803
 
#                                'supplier': rfq.partner_id.id,
1804
 
#                                'name': '[%s] %s' % (line.product_id.default_code, line.product_id.name),
1805
 
#                                'location_id': rfq.sale_order_id.warehouse_id.lot_stock_id.id,
1806
 
#                                'po_cft': 'rfq',
1807
 
#                        }
1808
 
#                        proc_id = proc_obj.create(cr, uid, vals, context=context)
1809
 
#                        wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr)
1810
 
#                        wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_check', cr)
1811
 
        self.create_extra_lines_on_fo(cr, uid, ids, context=context)
1812
 
 
1813
 
 
1814
 
        return self.write(cr, uid, ids, {'state': 'done'}, context=context)
1815
 
 
1816
879
purchase_order()
1817
880
 
1818
881
 
1822
885
    '''
1823
886
    _inherit = 'purchase.order.line'
1824
887
    _columns = {'tender_id': fields.related('order_id', 'tender_id', type='many2one', relation='tender', string='Tender',),
1825
 
                'tender_line_id': fields.many2one('tender.line', string='Tender Line'),
1826
888
                'rfq_ok': fields.related('order_id', 'rfq_ok', type='boolean', string='RfQ ?'),
1827
 
                'sale_order_line_id': fields.many2one('sale.order.line', string='FO line', readonly=True),
1828
889
                }
1829
890
    
1830
 
    def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
1831
 
        """
1832
 
        columns for the tree
1833
 
        """
1834
 
        if context is None:
1835
 
            context = {}
1836
 
 
1837
 
        # call super
1838
 
        result = super(purchase_order_line, self).fields_view_get(cr, uid, view_id, view_type, context=context, toolbar=toolbar, submenu=submenu)
1839
 
        if view_type == 'form':
1840
 
            if context.get('rfq_ok', False):
1841
 
                # the title of the screen depends on po type
1842
 
                form = etree.fromstring(result['arch'])
1843
 
                fields = form.xpath('//form[@string="%s"]' % _('Purchase Order Line'))
1844
 
                for field in fields:
1845
 
                    field.set('string', _("Request for Quotation Line"))
1846
 
                result['arch'] = etree.tostring(form)
1847
 
        
1848
 
        return result
1849
 
 
1850
891
purchase_order_line()
1851
892
 
1852
893
 
1856
897
    '''
1857
898
    _inherit = 'sale.order.line'
1858
899
    
1859
 
    _columns = {
1860
 
        'tender_line_ids': fields.one2many('tender.line', 'sale_order_line_id', string="Tender Lines", readonly=True),
1861
 
        'created_by_tender': fields.many2one('tender', string='Created by tender'),
1862
 
        'created_by_tender_line': fields.many2one('tender.line', string='Created by tender line'),
1863
 
    }
1864
 
 
1865
 
    def copy_data(self, cr, uid, ids, default=None, context=None):
1866
 
        '''
1867
 
        Remove tender lines linked
1868
 
        '''
1869
 
        default = default or {}
1870
 
 
1871
 
        if not 'tender_line_ids' in default:
1872
 
            default['tender_line_ids'] = []
1873
 
 
1874
 
        return super(sale_order_line, self).copy_data(cr, uid, ids, default, context=context)
 
900
    _columns = {'tender_line_ids': fields.one2many('tender.line', 'sale_order_line_id', string="Tender Lines", readonly=True),}
1875
901
    
1876
902
sale_order_line()
1877
903
 
1890
916
        return res
1891
917
    
1892
918
    _inherit = 'pricelist.partnerinfo'
1893
 
    _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"),
1894
 
                'currency_id': fields.many2one('res.currency', string='Currency', required=True, domain="[('partner_currency', '=', partner_id)]", select=True),
 
919
    _columns = {'currency_id': fields.many2one('res.currency', string='Currency', required=True, domain="[('partner_currency', '=', partner_id)]"),
1895
920
                'valid_till': fields.date(string="Valid Till",),
1896
921
                'comment': fields.char(size=128, string='Comment'),
1897
922
                'purchase_order_id': fields.related('purchase_order_line_id', 'order_id', type='many2one', relation='purchase.order', string="Related RfQ", readonly=True,),
1901
926
                }
1902
927
pricelist_partnerinfo()
1903
928
 
1904
 
 
1905
 
class tender_line_cancel_wizard(osv.osv_memory):
1906
 
    _name = 'tender.line.cancel.wizard'
1907
 
 
1908
 
    _columns = {
1909
 
        'tender_line_id': fields.many2one('tender.line', string='Tender line', required=True),
1910
 
        'only_exp': fields.boolean(
1911
 
            string='Only added lines',
1912
 
        ),
1913
 
        'last_line': fields.boolean(
1914
 
            string='Last line of the FO to source',
1915
 
        ),
1916
 
    }
1917
 
 
1918
 
 
1919
 
    def just_cancel(self, cr, uid, ids, context=None):
1920
 
        '''
1921
 
        Cancel the line 
1922
 
        '''
1923
 
        # Objects
1924
 
        line_obj = self.pool.get('tender.line')
1925
 
        tender_obj = self.pool.get('tender')
1926
 
        data_obj = self.pool.get('ir.model.data')
1927
 
        so_obj = self.pool.get('sale.order')
1928
 
        tender_wiz_obj = self.pool.get('tender.cancel.wizard')
1929
 
        wf_service = netsvc.LocalService("workflow")
1930
 
 
1931
 
        # Variables
1932
 
        if context is None:
1933
 
            context = {}
1934
 
 
1935
 
        if isinstance(ids, (int, long)):
1936
 
            ids = [ids]
1937
 
 
1938
 
        line_ids = []
1939
 
        tender_ids = set()
1940
 
        so_ids = set()
1941
 
        for wiz in self.browse(cr, uid, ids, context=context):
1942
 
            tender_ids.add(wiz.tender_line_id.tender_id.id)
1943
 
            line_ids.append(wiz.tender_line_id.id)
1944
 
            if wiz.tender_line_id.tender_id.sale_order_id:
1945
 
                so_ids.add(wiz.tender_line_id.tender_id.sale_order_id.id)
1946
 
 
1947
 
        if context.get('has_to_be_resourced'):
1948
 
            line_obj.write(cr, uid, line_ids, {'has_to_be_resourced': True}, context=context)
1949
 
 
1950
 
        line_obj.fake_unlink(cr, uid, line_ids, context=context)
1951
 
 
1952
 
        tender_so_ids, po_ids, so_ids, sol_nc_ids = tender_obj.sourcing_document_state(cr, uid, list(tender_ids), context=context)
1953
 
        for po_id in po_ids:
1954
 
            wf_service.trg_write(uid, 'purchase.order', po_id, cr)
1955
 
 
1956
 
        so_to_cancel_ids = []
1957
 
        if tender_so_ids:
1958
 
            for so_id in tender_so_ids:
1959
 
                if so_obj._get_ready_to_cancel(cr, uid, so_id, context=context)[so_id]:
1960
 
                    so_to_cancel_ids.append(so_id)
1961
 
 
1962
 
        if so_to_cancel_ids:
1963
 
            # Ask user to choose what must be done on the FO/IR
1964
 
            context.update({
1965
 
                'from_tender': True,
1966
 
                'tender_ids': list(tender_ids),
1967
 
            })
1968
 
            return so_obj.open_cancel_wizard(cr, uid, set(so_to_cancel_ids), context=context)
1969
 
 
1970
 
        return tender_obj.check_empty_tender(cr, uid, list(tender_ids), context=context)
1971
 
 
1972
 
    def cancel_and_resource(self, cr, uid, ids, context=None):
1973
 
        '''
1974
 
        Flag the line to be re-sourced and run cancel method
1975
 
        '''
1976
 
        # Objects
1977
 
        if context is None:
1978
 
            context = {}
1979
 
 
1980
 
        context['has_to_be_resourced'] = True
1981
 
 
1982
 
        return self.just_cancel(cr, uid, ids, context=context)
1983
 
 
1984
 
tender_line_cancel_wizard()
1985
 
 
1986
 
 
1987
 
class tender_cancel_wizard(osv.osv_memory):
1988
 
    _name = 'tender.cancel.wizard'
1989
 
 
1990
 
    _columns = {
1991
 
        'tender_id': fields.many2one('tender', string='Tender', required=True),
1992
 
        'not_draft': fields.boolean(string='Tender not draft'),
1993
 
        'no_need': fields.boolean(string='No need'),
1994
 
    }
1995
 
 
1996
 
    def just_cancel(self, cr, uid, ids, context=None):
1997
 
        '''
1998
 
        Just cancel the wizard and the lines
1999
 
        '''
2000
 
        # Objects
2001
 
        line_obj = self.pool.get('tender.line')
2002
 
        so_obj = self.pool.get('sale.order')
2003
 
 
2004
 
        # Variables
2005
 
        if context is None:
2006
 
            context = {}
2007
 
 
2008
 
        if isinstance(ids, (int, long)):
2009
 
            ids = [ids]
2010
 
 
2011
 
        wf_service = netsvc.LocalService("workflow")
2012
 
        line_ids = []
2013
 
        tender_ids = []
2014
 
        rfq_ids = []
2015
 
        so_ids = []
2016
 
        for wiz in self.browse(cr, uid, ids, context=context):
2017
 
            tender_ids.append(wiz.tender_id.id)
2018
 
            if wiz.tender_id.sale_order_id and wiz.tender_id.sale_order_id.id not in so_ids:
2019
 
                so_ids.append(wiz.tender_id.sale_order_id.id)
2020
 
            for line in wiz.tender_id.tender_line_ids:
2021
 
                line_ids.append(line.id)
2022
 
            for rfq in wiz.tender_id.rfq_ids:
2023
 
                rfq_ids.append(rfq.id)
2024
 
 
2025
 
        if context.get('has_to_be_resourced'):
2026
 
            line_obj.write(cr, uid, line_ids, {'has_to_be_resourced': True}, context=context)
2027
 
 
2028
 
        line_obj.fake_unlink(cr, uid, line_ids, context=context)
2029
 
 
2030
 
        for rfq in rfq_ids:
2031
 
            wf_service.trg_validate(uid, 'purchase.order', rfq, 'purchase_cancel', cr)
2032
 
 
2033
 
        for tender in tender_ids:
2034
 
            wf_service.trg_validate(uid, 'tender', tender, 'tender_cancel', cr)
2035
 
 
2036
 
        so_to_cancel_ids = []
2037
 
        if so_ids:
2038
 
            for so_id in so_ids:
2039
 
                if so_obj._get_ready_to_cancel(cr, uid, so_id, context=context)[so_id]:
2040
 
                    so_to_cancel_ids.append(so_id)
2041
 
 
2042
 
        if so_to_cancel_ids:
2043
 
            # Ask user to choose what must be done on the FO/IR
2044
 
            return so_obj.open_cancel_wizard(cr, uid, set(so_to_cancel_ids), context=context)
2045
 
 
2046
 
        return {'type': 'ir.actions.act_window_close'}
2047
 
 
2048
 
    def cancel_and_resource(self, cr, uid, ids, context=None):
2049
 
        '''
2050
 
        Flag the line to be re-sourced and run cancel method
2051
 
        '''
2052
 
        # Objects
2053
 
        if context is None:
2054
 
            context = {}
2055
 
 
2056
 
        context['has_to_be_resourced'] = True
2057
 
 
2058
 
        return self.just_cancel(cr, uid, ids, context=context)
2059
 
 
2060
 
    def close_window(self, cr, uid, ids, context=None):
2061
 
        '''
2062
 
        Just close the wizard and reload the tender
2063
 
        '''
2064
 
        return {'type': 'ir.actions.act_window_close'}
2065
 
 
2066
 
tender_cancel_wizard()
2067
 
 
2068
 
 
2069
 
class expected_sale_order_line(osv.osv):
2070
 
    _inherit = 'expected.sale.order.line'
2071
 
 
2072
 
    _columns = {
2073
 
        'tender_line_id': fields.many2one(
2074
 
            'tender.line',
2075
 
            string='Tender line',
2076
 
            ondelete='cascade',
2077
 
        ),
2078
 
        'tender_id': fields.related(
2079
 
            'tender_line_id',
2080
 
            'tender_id',
2081
 
            string='Tender',
2082
 
            type='many2one',
2083
 
            relation='tender',
2084
 
        ),
2085
 
    }
2086
 
 
2087
 
expected_sale_order_line()
2088
 
 
2089
 
 
2090
929
class ir_values(osv.osv):
2091
930
    _name = 'ir.values'
2092
931
    _inherit = 'ir.values'
2118
957
            new_values = []
2119
958
            for v in values:
2120
959
                if key == 'action' and v[1] in po_accepted_values[key2] \
2121
 
                or v[1] == 'Purchase Order Excel Export' \
2122
 
                or v[1] == 'Purchase Order' \
2123
 
                or v[1] == 'Purchase Order (Merged)' \
2124
 
                or v[1] == 'Allocation report' \
2125
 
                or v[1] == 'Order impact vs. Budget' :
 
960
                or v[2]['name'] == 'Purchase Order Excel Export' \
 
961
                or v[2]['name'] == 'Purchase Order' \
 
962
                or v[2]['name'] == 'Purchase Order (Merged)' \
 
963
                or v[2]['name'] == 'Allocation report' \
 
964
                or v[2]['name'] == 'Order impact vs. Budget' :
2126
965
                    new_values.append(v)
2127
966
        elif context.get('request_for_quotation', False) and 'purchase.order' in [x[0] for x in models]:
2128
967
            new_values = []
2129
968
            for v in values:
2130
969
                if key == 'action' and v[1] in rfq_accepted_values[key2] \
2131
 
                or v[1] == 'Request for Quotation' \
2132
 
                or v[1] == 'Request For Quotation Excel Export' :
 
970
                or v[2]['name'] == 'Request for Quotation' \
 
971
                or v[2]['name'] == 'Request For Quotation Excel Export' :
2133
972
                    new_values.append(v)
2134
973
 
2135
974
        return new_values