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

« back to all changes in this revision

Viewing changes to tender_flow/tender_flow.py

  • Committer: jf
  • Date: 2014-05-28 13:16:31 UTC
  • mto: This revision was merged to the branch mainline in revision 2187.
  • Revision ID: jfb@tempo-consulting.fr-20140528131631-13qcl8f5h390rmtu
UFTP-244 [FIX] In sync context, do not auto create the link between account.account and account.destination.link for default destination
this link is created by a dedicated sync rule

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
50
52
        return super(osv.osv, self).copy(cr, uid, id, default, context=context)
51
53
    
52
54
    def unlink(self, cr, uid, ids, context=None):
53
55
        '''
54
56
        cannot delete tender not draft
55
57
        '''
 
58
        # Objects
 
59
        t_line_obj = self.pool.get('tender.line')
 
60
 
56
61
        if context is None:
57
62
            context = {}
58
63
        if isinstance(ids, (int, long)):
61
66
        for obj in self.browse(cr, uid, ids, context=context):
62
67
            if obj.state != 'draft':
63
68
                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
 
64
77
        return super(tender, self).unlink(cr, uid, ids, context=context)
65
78
    
66
79
    def _vals_get(self, cr, uid, ids, fields, arg, context=None):
111
124
                'internal_state': fields.selection([('draft', 'Draft'),('updated', 'Rfq Updated'), ], string="Internal State", readonly=True),
112
125
                'rfq_name_list': fields.function(_vals_get, method=True, string='RfQs Ref', type='char', readonly=True, store=False, multi='get_vals',),
113
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),
114
128
               'tender_from_fo': fields.function(_is_tender_from_fo, method=True, type='boolean', string='Is tender from FO ?',),
115
129
                }
116
130
    
127
141
    
128
142
    _order = 'name desc'
129
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
 
130
175
    def _check_tender_from_fo(self, cr, uid, ids, context=None):
131
176
        if not context:
132
177
            context = {}
149
194
        '''
150
195
        if not vals.get('name', False):
151
196
            vals.update({'name': self.pool.get('ir.sequence').get(cr, uid, 'tender')})
 
197
 
152
198
        return super(tender, self).create(cr, uid, vals, context=context)
153
 
    
 
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
        self._check_service(cr, uid, ids, vals, context=context)
 
230
        return super(tender, self).write(cr, uid, ids, vals, context=context)
 
231
 
 
232
    def onchange_categ(self, cr, uid, ids, categ, context=None):
 
233
        """ Check that the categ is compatible with the product
 
234
        @param categ: Changed value of categ.
 
235
        @return: Dictionary of values.
 
236
        """
 
237
        if isinstance(ids, (int, long)):
 
238
            ids = [ids]
 
239
        message = {}
 
240
        if ids and categ in ['service', 'transport']:
 
241
            # Avoid selection of non-service producs on Service Tender
 
242
            category = categ=='service' and 'service_recep' or 'transport'
 
243
            transport_cat = ''
 
244
            if category == 'transport':
 
245
                transport_cat = 'OR p.transport_ok = False'
 
246
            cr.execute('''SELECT p.default_code AS default_code, t.name AS name
 
247
                          FROM tender_line l
 
248
                            LEFT JOIN product_product p ON l.product_id = p.id
 
249
                            LEFT JOIN product_template pt ON p.product_tmpl_id = pt.id
 
250
                            LEFT JOIN tender t ON l.tender_id = t.id
 
251
                          WHERE (pt.type != 'service_recep' %s) AND t.id in (%s) LIMIT 1''' % (transport_cat, ','.join(str(x) for x in ids)))
 
252
            res = cr.fetchall()
 
253
            if res:
 
254
                cat_name = categ=='service' and 'Service' or 'Transport'
 
255
                message.update({'title': _('Warning'),
 
256
                                '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)})
 
257
                
 
258
        return {'warning': message}
 
259
 
154
260
    def onchange_warehouse(self, cr, uid, ids, warehouse_id, context=None):
155
261
        '''
156
262
        on_change function for the warehouse
173
279
        partner_obj = self.pool.get('res.partner')
174
280
        pricelist_obj = self.pool.get('product.pricelist')
175
281
        obj_data = self.pool.get('ir.model.data')
 
282
 
176
283
        # no suppliers -> raise error
177
284
        for tender in self.browse(cr, uid, ids, context=context):
178
285
            # check some supplier have been selected
193
300
                raise osv.except_osv(_('Warning !'), _("You can't have inactive supplier! Please remove: %s"
194
301
                                                       ) % ' ,'.join([partner['name'] for partner in inactive_supplier_ids]))
195
302
            # check some products have been selected
196
 
            tender_line_ids = self.pool.get('tender.line').search(cr, uid, [('tender_id', '=', tender.id)], context=context)
 
303
            tender_line_ids = self.pool.get('tender.line').search(cr, uid, [('tender_id', '=', tender.id), ('line_state', '!=', 'cancel')], context=context)
197
304
            if not tender_line_ids:
198
305
                raise osv.except_osv(_('Warning !'), _('You must select at least one product!'))
199
306
            for supplier in tender.supplier_ids:
200
307
                # create a purchase order for each supplier
201
 
                address_id = partner_obj.address_get(cr, uid, [supplier.id], ['delivery'])['delivery']
 
308
                address_id = partner_obj.address_get(cr, uid, [supplier.id], ['default'])['default']
202
309
                if not address_id:
203
310
                    raise osv.except_osv(_('Warning !'), _('The supplier "%s" has no address defined!')%(supplier.name,))
204
311
                pricelist_id = supplier.property_product_pricelist_purchase.id
216
323
                          'priority': tender.priority,
217
324
                          'details': tender.details,
218
325
                          'delivery_requested_date': tender.requested_date,
 
326
                          'rfq_delivery_address': tender.delivery_address and tender.delivery_address.id or False,
219
327
                          }
220
328
                # create the rfq - dic is udpated for default partner_address_id at purchase.order level
221
329
                po_id = po_obj.create(cr, uid, values, context=dict(context, partner_id=supplier.id, rfq_ok=True))
222
330
                
223
331
                for line in tender.tender_line_ids:
 
332
                    if line.line_state == 'cancel':
 
333
                        continue
 
334
 
 
335
                    if line.qty <= 0.00:
 
336
                        raise osv.except_osv(_('Error !'), _('You cannot generate RfQs for an line with a null quantity.'))
 
337
 
224
338
                    if line.product_id.id == obj_data.get_object_reference(cr, uid,'msf_doc_import', 'product_tbd')[1]:
225
339
                        raise osv.except_osv(_('Warning !'), _('You can\'t have "To Be Defined" for the product. Please select an existing product.'))
226
340
                    # create an order line for each tender line
235
349
                              'date_planned': newdate.strftime('%Y-%m-%d'),
236
350
                              'notes': line.product_id.description_purchase,
237
351
                              'order_id': po_id,
 
352
                              'tender_line_id': line.id,
238
353
                              }
239
354
                    # create purchase order line
240
355
                    pol_id = pol_obj.create(cr, uid, values, context=context)
257
372
        '''
258
373
        # done all related rfqs
259
374
        wf_service = netsvc.LocalService("workflow")
 
375
        sol_obj = self.pool.get('sale.order.line')
 
376
        proc_obj = self.pool.get('procurement.order')
 
377
        date_tools = self.pool.get('date.tools')                                
 
378
        fields_tools = self.pool.get('fields.tools')                            
 
379
        db_date_format = date_tools.get_db_date_format(cr, uid, context=context)
 
380
 
260
381
        for tender in self.browse(cr, uid, ids, context=context):
261
382
            rfq_list = []
262
383
            for rfq in tender.rfq_ids:
270
391
                raise osv.except_osv(_('Warning !'), _("Generated RfQs must be Updated or Cancelled."))
271
392
            
272
393
            # integrity check, all lines must have purchase_order_line_id
273
 
            if not all([line.purchase_order_line_id.id for line in tender.tender_line_ids]):
 
394
            if not all([line.purchase_order_line_id.id for line in tender.tender_line_ids if line.line_state != 'cancel']):
274
395
                raise osv.except_osv(_('Error !'), _('All tender lines must have been compared!'))
275
 
        
 
396
 
 
397
            if tender.sale_order_id:
 
398
                # Update procurement order
 
399
                for line in tender.tender_line_ids:
 
400
                    if line.line_state == 'cancel':
 
401
                        proc_id = line.sale_order_line_id and line.sale_order_line_id.procurement_id and line.sale_order_line_id.procurement_id.id
 
402
                        if proc_id:
 
403
                            wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_cancel', cr)
 
404
                        continue
 
405
                    vals = {'product_id': line.product_id.id,
 
406
                            'product_uom': line.product_uom.id,
 
407
                            'product_uos': line.product_uom.id,
 
408
                            'product_qty': line.qty,
 
409
                            'price_unit': line.price_unit,
 
410
                            'product_uos_qty': line.qty}
 
411
                    if line.sale_order_line_id and line.sale_order_line_id.procurement_id:
 
412
                        proc_id = line.sale_order_line_id.procurement_id.id
 
413
                        proc_obj.write(cr, uid, [proc_id], vals, context=context)
 
414
                    else: # Create procurement order to add the lines in a PO
 
415
                        create_vals = vals.copy()
 
416
                        prep_lt = fields_tools.get_field_from_company(cr, uid, object='sale.order', field='preparation_lead_time', context=context)
 
417
                        rts = datetime.strptime(tender.sale_order_id.ready_to_ship_date, db_date_format)       
 
418
                        rts = rts - relativedelta(days=prep_lt or 0)                            
 
419
                        rts = rts.strftime(db_date_format)                                      
 
420
                        create_vals.update({'procure_method': 'make_to_order',
 
421
                                            'is_tender': True,
 
422
                                            'tender_id': tender.id,
 
423
                                            'tender_line_id': line.id,
 
424
                                            'tender_done': True,
 
425
                                            'price_unit': line.price_unit,
 
426
                                            'date_planned': rts,
 
427
                                            'origin': tender.sale_order_id.name,
 
428
                                            'supplier': line.purchase_order_line_id.order_id.partner_id.id,
 
429
                                            'name': '[%s] %s' % (line.product_id.default_code, line.product_id.name),
 
430
                                            'location_id': tender.sale_order_id.warehouse_id.lot_stock_id.id,
 
431
                                            'po_cft': 'cft',
 
432
                                            })
 
433
                        proc_id = proc_obj.create(cr, uid, create_vals, context=context)
 
434
                        wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr)
 
435
                        wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_check', cr)
 
436
                    
276
437
        # update product supplierinfo and pricelist
277
438
        self.update_supplier_info(cr, uid, ids, context=context, integrity_test=False,)
278
439
        # change tender state
316
477
            # gather the product_id -> supplier_id relationship to display it back in the compare wizard
317
478
            suppliers = {}
318
479
            for line in tender.tender_line_ids:
319
 
                if line.product_id and line.supplier_id:
 
480
                if line.product_id and line.supplier_id and line.line_state != 'cancel':
320
481
                    suppliers.update({line.product_id.id:line.supplier_id.id,})
321
482
            # rfq corresponding to this tender with done state (has been updated and not canceled)
322
483
            # the list of rfq which will be compared
340
501
            if integrity_test:
341
502
                self.tender_integrity(cr, uid, tender, context=context)
342
503
            for line in tender.tender_line_ids:
 
504
                if line.line_state == 'cancel':
 
505
                    continue
343
506
                # if a supplier has been selected
344
507
                if line.purchase_order_line_id:
345
508
                    # set the flag
419
582
            # check if corresponding rfqs are in the good state
420
583
            self.tender_integrity(cr, uid, tender, context=context)
421
584
            # integrity check, all lines must have purchase_order_line_id
422
 
            if not all([line.purchase_order_line_id.id for line in tender.tender_line_ids]):
 
585
            if not all([line.purchase_order_line_id.id for line in tender.tender_line_ids if line.line_state != 'cancel']):
423
586
                raise osv.except_osv(_('Error !'), _('All tender lines must have been compared!'))
424
587
            data = {}
425
588
            for line in tender.tender_line_ids:
 
589
                if line.line_state == 'cancel':
 
590
                    continue
426
591
                data.setdefault(line.supplier_id.id, {}) \
427
592
                    .setdefault('order_line', []).append((0,0,{'name': line.product_id.partner_ref,
428
593
                                                               'product_qty': line.qty,
436
601
                                                               }))
437
602
                    
438
603
                # fill data corresponding to po creation
439
 
                address_id = partner_obj.address_get(cr, uid, [line.supplier_id.id], ['delivery'])['delivery']
 
604
                address_id = partner_obj.address_get(cr, uid, [line.supplier_id.id], ['default'])['default']
440
605
                pricelist = line.supplier_id.property_product_pricelist_purchase.id,
441
606
                if line.currency_id:
442
607
                    price_ids = self.pool.get('product.pricelist').search(cr, uid, [('type', '=', 'purchase'), ('currency_id', '=', line.currency_id.id)], context=context)
443
608
                    if price_ids:
444
609
                        pricelist = price_ids[0]
445
 
                po_values = {'origin': (tender.sale_order_id and tender.sale_order_id.name or "") + ';' + tender.name,
 
610
                po_values = {'origin': (tender.sale_order_id and tender.sale_order_id.name or "") + '; ' + tender.name,
446
611
                             'partner_id': line.supplier_id.id,
447
612
                             'partner_address_id': address_id,
448
613
                             'location_id': tender.location_id.id,
456
621
                             'warehouse_id': tender.warehouse_id.id,
457
622
                             'details': tender.details,
458
623
                             'delivery_requested_date': tender.requested_date,
 
624
                             'dest_address_id': tender.delivery_address.id,
459
625
                             }
460
626
                data[line.supplier_id.id].update(po_values)
461
627
            
471
637
            self.done(cr, uid, [tender.id], context=context)
472
638
        
473
639
        return po_id
 
640
 
 
641
    def cancel_tender(self, cr, uid, ids, context=None):
 
642
        '''
 
643
        Ask the user if he wants to re-source all lines
 
644
        '''
 
645
        wiz_obj = self.pool.get('tender.cancel.wizard')
 
646
 
 
647
        if context is None:
 
648
            context = {}
 
649
 
 
650
        if isinstance(ids, (int, long)):
 
651
            ids = [ids]
 
652
 
 
653
        tender = self.read(cr, uid, ids[0], ['state'], context=context)
 
654
        wiz_id = wiz_obj.create(cr, uid, {'tender_id': tender['id'], 'not_draft': tender['state'] != 'draft'}, context=context)
 
655
 
 
656
        return {'type': 'ir.actions.act_window',
 
657
                'res_model': 'tender.cancel.wizard',
 
658
                'res_id': wiz_id,
 
659
                'view_mode': 'form',
 
660
                'view_type': 'form',
 
661
                'target': 'new',
 
662
                'context': context}
474
663
    
475
664
    def wkf_action_cancel(self, cr, uid, ids, context=None):
476
665
        '''
477
666
        cancel all corresponding rfqs
478
667
        '''
 
668
        if context is None:
 
669
            context = {}
 
670
 
479
671
        po_obj = self.pool.get('purchase.order')
 
672
        t_line_obj = self.pool.get('tender.line')
480
673
        wf_service = netsvc.LocalService("workflow")
481
674
        # set state
482
675
        self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
485
678
            rfq_ids = po_obj.search(cr, uid, [('tender_id', '=', tender.id),], context=context)
486
679
            for rfq_id in rfq_ids:
487
680
                wf_service.trg_validate(uid, 'purchase.order', rfq_id, 'purchase_cancel', cr)
 
681
 
 
682
            for line in tender.tender_line_ids:
 
683
                t_line_obj.cancel_sourcing(cr, uid, [line.id], context=context)
488
684
                
489
685
        return True
490
686
 
536
732
    
537
733
    _SELECTION_TENDER_STATE = [('draft', 'Draft'),('comparison', 'Comparison'), ('done', 'Closed'),]
538
734
    
539
 
    def on_product_change(self, cr, uid, id, product_id, context=None):
 
735
    def on_product_change(self, cr, uid, id, product_id, uom_id, product_qty, context=None):
540
736
        '''
541
737
        product is changed, we update the UoM
542
738
        '''
 
739
        if not context:
 
740
            context = {}
 
741
 
543
742
        prod_obj = self.pool.get('product.product')
544
743
        result = {'value': {}}
545
744
        if product_id:
546
 
            result['value']['product_uom'] = prod_obj.browse(cr, uid, product_id, context=context).uom_po_id.id
547
 
            
 
745
            # Test the compatibility of the product with a tender
 
746
            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)
 
747
            if test:
 
748
                return result
 
749
 
 
750
            product = prod_obj.browse(cr, uid, product_id, context=context)
 
751
            result['value']['product_uom'] = product.uom_id.id
 
752
            result['value']['text_error'] = False
 
753
            result['value']['to_correct_ok'] = False
 
754
        
 
755
        res_qty = self.onchange_uom_qty(cr, uid, id, uom_id or result.get('value', {}).get('product_uom',False), product_qty)
 
756
        result['value']['qty'] = res_qty.get('value', {}).get('qty', product_qty)
 
757
        
 
758
        if uom_id:
 
759
            result['value']['product_uom'] = uom_id
 
760
 
548
761
        return result
 
762
 
 
763
    def onchange_uom_qty(self, cr, uid, ids, uom_id, qty):
 
764
        '''
 
765
        Check round of qty according to the UoM
 
766
        '''
 
767
        res = {}
 
768
 
 
769
        if qty:
 
770
            res = self.pool.get('product.uom')._change_round_up_qty(cr, uid, uom_id, qty, 'qty', result=res)
 
771
 
 
772
        return res
549
773
    
550
774
    def _get_total_price(self, cr, uid, ids, field_name, arg, context=None):
551
775
        '''
595
819
                'func_total_price': fields.function(_get_total_price, method=True, type='float', string="Func. Total Price", digits_compute=dp.get_precision('Purchase Price'), multi='total'),
596
820
                'func_currency_id': fields.function(_get_total_price, method=True, type='many2one', relation='res.currency', string='Func. Cur.', multi='total'),
597
821
                'purchase_order_id': fields.related('purchase_order_line_id', 'order_id', type='many2one', relation='purchase.order', string="Related RfQ", readonly=True,),
598
 
                'purchase_order_line_number': fields.related('purchase_order_line_id', 'line_number', type="integer", string="Related Line Number", readonly=True,),
 
822
                'purchase_order_line_number': fields.related('purchase_order_line_id', 'line_number', type="char", string="Related Line Number", readonly=True,),
599
823
                'state': fields.related('tender_id', 'state', type="selection", selection=_SELECTION_TENDER_STATE, string="State",),
 
824
                'line_state': fields.selection([('draft','Draft'), ('cancel', 'Canceled'), ('done', 'Done')], string='State', readonly=True),
600
825
                'comment': fields.char(size=128, string='Comment'),
 
826
                'has_to_be_resourced': fields.boolean(string='Has to be resourced'),
 
827
                'created_by_rfq': fields.boolean(string='Created by RfQ'),
601
828
                }
602
829
    _defaults = {'qty': lambda *a: 1.0,
603
830
                 'state': lambda *a: 'draft',
 
831
                 'line_state': lambda *a: 'draft',
604
832
                 }
605
833
    
 
834
    def _check_restriction_line(self, cr, uid, ids, context=None):
 
835
        '''
 
836
        Check if there is no restrictive products in lines
 
837
        '''
 
838
        if isinstance(ids, (int, long)):
 
839
            ids = [ids]
 
840
 
 
841
        for line in self.browse(cr, uid, ids, context=context):
 
842
            if line.tender_id and line.product_id:
 
843
                if not self.pool.get('product.product')._get_restriction_error(cr, uid, line.product_id.id, vals={'constraints': ['external']}, context=context):
 
844
                    return False
 
845
 
 
846
        return True
 
847
 
606
848
    _sql_constraints = [
607
 
        ('product_qty_check', 'CHECK( qty > 0 )', 'Product Quantity must be greater than zero.'),
 
849
#        ('product_qty_check', 'CHECK( qty > 0 )', 'Product Quantity must be greater than zero.'),
608
850
    ]
 
851
 
 
852
 
 
853
    def copy(self, cr, uid, id, default=None, context=None):
 
854
        if default is None:
 
855
            default = {}
 
856
 
 
857
        if not 'created_by_rf' in default:
 
858
            default['created_by_rfq'] = False
 
859
 
 
860
        return super(tender_line, self).copy(cr, uid, id, default, context=context)
 
861
 
 
862
    def cancel_sourcing(self,cr, uid, ids, context=None):
 
863
        '''
 
864
        Cancel the line and re-source the FO line
 
865
        '''
 
866
        # Objects
 
867
        sol_obj = self.pool.get('sale.order.line')
 
868
        uom_obj = self.pool.get('product.uom')
 
869
        tender_obj = self.pool.get('tender')
 
870
 
 
871
        # Variables
 
872
        wf_service = netsvc.LocalService("workflow")
 
873
        to_remove = []
 
874
        to_cancel = []
 
875
        sol_ids = {}
 
876
        sol_to_update = {}
 
877
        so_to_update = set()
 
878
        tender_to_update = set()
 
879
 
 
880
        for line in self.browse(cr, uid, ids, context=context):
 
881
            tender_to_update.add(line.tender_id.id)
 
882
            if line.sale_order_line_id:
 
883
                so_to_update.add(line.sale_order_line_id.order_id.id)
 
884
                to_cancel.append(line.id)
 
885
                # Get the ID and the product qty of the FO line to re-source
 
886
                diff_qty = uom_obj._compute_qty(cr, uid, line.product_uom.id, line.qty, line.sale_order_line_id.product_uom.id)
 
887
 
 
888
                if line.has_to_be_resourced:
 
889
                    sol_ids.update({line.sale_order_line_id.id: diff_qty})
 
890
 
 
891
                sol_to_update.setdefault(line.sale_order_line_id.id, 0.00)
 
892
                sol_to_update[line.sale_order_line_id.id] += diff_qty
 
893
            elif line.tender_id.state == 'draft':
 
894
                to_remove.append(line.id)
 
895
            else:
 
896
                to_cancel.append(line.id)
 
897
 
 
898
        if to_cancel:
 
899
            self.write(cr, uid, to_cancel, {'line_state': 'cancel'}, context=context)
 
900
 
 
901
        if sol_ids:
 
902
            for sol in sol_ids:
 
903
                sol_obj.add_resource_line(cr, uid, sol, False, sol_ids[sol], context=context)
 
904
 
 
905
        # Update sale order lines
 
906
        for sol in sol_to_update:
 
907
            sol_obj.update_or_cancel_line(cr, uid, sol, sol_to_update[sol], context=context)
 
908
 
 
909
        # Update the FO state
 
910
        for so in so_to_update:
 
911
            wf_service.trg_write(uid, 'sale.order', so, cr)
 
912
 
 
913
        # UF-733: if all tender lines have been compared (have PO Line id), then set the tender to be ready
 
914
        # for proceeding to other actions (create PO, Done etc) 
 
915
        for tender in tender_obj.browse(cr, uid, list(tender_to_update), context=context):
 
916
            if tender.internal_state == 'draft':
 
917
                flag = True
 
918
                for line in tender.tender_line_ids:
 
919
                    if line.line_state != 'cancel' and not line.purchase_order_line_id:
 
920
                        flag = False
 
921
                if flag:
 
922
                    tender_obj.write(cr, uid, [tender.id], {'internal_state': 'updated'})
 
923
 
 
924
        if context.get('fake_unlink'):
 
925
            return to_remove
 
926
 
 
927
        return True
 
928
 
 
929
    def fake_unlink(self, cr, uid, ids, context=None):
 
930
        '''
 
931
        Cancel the line if it is linked to a FO line
 
932
        '''
 
933
        to_remove = self.cancel_sourcing(cr, uid, ids, context=dict(context, fake_unlink=True))
 
934
 
 
935
        return self.unlink(cr, uid, to_remove, context=context)
 
936
 
 
937
    def ask_unlink(self, cr, uid, ids, context=None):
 
938
        '''
 
939
        Ask user if he wants to re-source the needs
 
940
        '''
 
941
        # Objects
 
942
        wiz_obj = self.pool.get('tender.line.cancel.wizard')
 
943
 
 
944
        # Variables
 
945
        if context is None:
 
946
            context = {}
 
947
 
 
948
        if isinstance(ids, (int, long)):
 
949
            ids = [ids]
 
950
 
 
951
        tender_id = False
 
952
        for line in self.browse(cr, uid, ids, context=context):
 
953
            tender_id = line.tender_id.id
 
954
            if line.sale_order_line_id:
 
955
                wiz_id = wiz_obj.create(cr, uid, {'tender_line_id': line.id}, context=context)
 
956
        
 
957
                return {'type': 'ir.actions.act_window',
 
958
                        'res_model': 'tender.line.cancel.wizard',
 
959
                        'view_type': 'form',
 
960
                        'view_mode': 'form',
 
961
                        'res_id': wiz_id,
 
962
                        'target': 'new',
 
963
                        'context': context}
 
964
        
 
965
        self.fake_unlink(cr, uid, ids, context=context)
 
966
 
 
967
        return {'type': 'ir.actions.act_window',
 
968
                'res_model': 'tender',
 
969
                'view_type': 'form',
 
970
                'view_mode': 'form,tree',
 
971
                'res_id': tender_id,
 
972
                'target': 'crush',
 
973
                'context': context}
609
974
    
610
975
tender_line()
611
976
 
644
1009
        # reset the tender line
645
1010
        for line in result['tender_line_ids']:
646
1011
            line[2].update(sale_order_line_id=False,
647
 
                           purchase_order_line_id=False,)
 
1012
                           purchase_order_line_id=False,
 
1013
                           line_state='draft',)
648
1014
        return result
649
1015
 
650
1016
tender2()
656
1022
    '''
657
1023
    _inherit = 'procurement.order'
658
1024
    
659
 
    def _is_tender(self, cr, uid, ids, field_name, arg, context=None):
 
1025
    def _is_tender_rfq(self, cr, uid, ids, field_name, arg, context=None):
660
1026
        '''
661
 
        tell if the corresponding sale order line is tender sourcing or not
 
1027
        tell if the corresponding sale order line is tender/rfq sourcing or not
662
1028
        '''
663
1029
        result = {}
664
 
        for id in ids:
665
 
            result[id] = False
666
 
            
667
1030
        for proc in self.browse(cr, uid, ids, context=context):
 
1031
            result[proc.id] = {'is_tender': False, 'is_rfq': False}
668
1032
            for line in proc.sale_order_line_ids:
669
 
                result[proc.id] = line.po_cft == 'cft'
 
1033
                result[proc.id]['is_tender'] = line.po_cft == 'cft'
 
1034
                result[proc.id]['is_rfq'] = line.po_cft == 'rfq'
670
1035
                                
671
1036
        return result
672
1037
    
673
 
    _columns = {'is_tender': fields.function(_is_tender, method=True, type='boolean', string='Is Tender', readonly=True,),
 
1038
    _columns = {'is_tender': fields.function(_is_tender_rfq, method=True, type='boolean', string='Is Tender', readonly=True, multi='tender_rfq'),
 
1039
                'is_rfq': fields.function(_is_tender_rfq, method=True, type='boolean', string='Is RfQ', readonly=True, multi='tender_rfq'),
674
1040
                'sale_order_line_ids': fields.one2many('sale.order.line', 'procurement_id', string="Sale Order Lines"),
675
1041
                'tender_id': fields.many2one('tender', string='Tender', readonly=True),
 
1042
                'tender_line_id': fields.many2one('tender.line', string='Tender line', readonly=True),
676
1043
                'is_tender_done': fields.boolean(string="Tender Closed"),
 
1044
                'rfq_id': fields.many2one('purchase.order', string='RfQ', readonly=True),
 
1045
                'rfq_line_id': fields.many2one('purchase.order.line', string='RfQ line', readonly=True),
 
1046
                'is_rfq_done': fields.boolean(string="RfQ Closed"),
677
1047
                'state': fields.selection([('draft','Draft'),
678
1048
                                           ('confirmed','Confirmed'),
679
1049
                                           ('exception','Exception'),
682
1052
                                           ('ready','Ready'),
683
1053
                                           ('done','Closed'),
684
1054
                                           ('tender', 'Tender'),
 
1055
                                           ('rfq', 'Request for Quotation'),
685
1056
                                           ('waiting','Waiting'),], 'State', required=True,
686
1057
                                          help='When a procurement is created the state is set to \'Draft\'.\n If the procurement is confirmed, the state is set to \'Confirmed\'.\
687
1058
                                                \nAfter confirming the state is set to \'Running\'.\n If any exception arises in the order then the state is set to \'Exception\'.\n Once the exception is removed the state becomes \'Ready\'.\n It is in \'Waiting\'. state when the procurement is waiting for another one to finish.'),
688
1059
                'price_unit': fields.float('Unit Price from Tender', digits_compute=dp.get_precision('Purchase Price Computation')),
689
1060
        }
690
 
    _defaults = {'is_tender_done': False,}
 
1061
    _defaults = {
 
1062
        'is_tender_done': False,
 
1063
        'is_rfq_done': False,
 
1064
    }
 
1065
 
 
1066
    def no_address_error(self, cr, uid, ids, context=None):
 
1067
        '''
 
1068
        Put the procurement order in exception state with the good error message
 
1069
        '''
 
1070
        for proc in self.browse(cr, uid, ids, context=context):
 
1071
            if proc.supplier and not proc.supplier.address:
 
1072
                self.write(cr, uid, [proc.id], {
 
1073
                    'state': 'exception',
 
1074
                    'message': _('The supplier "%s" has no address defined!')%(proc.supplier.name,),
 
1075
                }, context=context)
 
1076
 
 
1077
        return True
 
1078
 
 
1079
    def wkf_action_rfq_create(self, cr, uid, ids, context=None):
 
1080
        '''
 
1081
        creation of rfq from procurement workflow
 
1082
        '''
 
1083
        rfq_obj = self.pool.get('purchase.order')
 
1084
        rfq_line_obj = self.pool.get('purchase.order.line')
 
1085
        partner_obj = self.pool.get('res.partner')
 
1086
 
 
1087
        if not context:
 
1088
            context = {}
 
1089
 
 
1090
        # find the corresponding sale order id for rfq
 
1091
        for proc in self.browse(cr, uid, ids, context=context):
 
1092
            if proc.rfq_id:
 
1093
                return proc.rfq_id
 
1094
            sale_order = False
 
1095
            sale_order_line = False
 
1096
            for sol in proc.sale_order_line_ids:
 
1097
                sale_order = sol.order_id
 
1098
                sale_order_line = sol
 
1099
                break
 
1100
            # find the rfq
 
1101
            rfq_id = False
 
1102
            # UTP-934: If source rfq to different supplier, different rfq must be created, and cannot be using the same rfq 
 
1103
            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)
 
1104
            if rfq_ids:
 
1105
                rfq_id = rfq_ids[0]
 
1106
            # create if not found
 
1107
            if not rfq_id:
 
1108
                supplier = proc.supplier
 
1109
                company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id
 
1110
                pricelist_id = supplier.property_product_pricelist_purchase.id
 
1111
                address_id = partner_obj.address_get(cr, uid, [supplier.id], ['default'])['default']
 
1112
                if not address_id:
 
1113
                    self.write(cr, uid, [proc.id], {
 
1114
                        'message': _('The supplier "%s" has no address defined!')%(supplier.name,),
 
1115
                    }, context=context)
 
1116
                    continue
 
1117
 
 
1118
                context['rfq_ok'] = True
 
1119
                rfq_id = rfq_obj.create(cr, uid, {'sale_order_id': sale_order.id,
 
1120
                                                  'categ': sale_order.categ,
 
1121
                                                  'priority': sale_order.priority,
 
1122
                                                  'fiscal_position': supplier.property_account_position and supplier.property_account_position.id or False,
 
1123
                                                  'rfq_delivery_address': partner_obj.address_get(cr, uid, company.partner_id.id, ['delivery'])['delivery'],
 
1124
                                                  'warehouse_id': sale_order.shop_id.warehouse_id.id,
 
1125
                                                  'location_id': proc.location_id.id,
 
1126
                                                  'partner_id': supplier.id,
 
1127
                                                  'partner_address_id': address_id,
 
1128
                                                  'pricelist_id': pricelist_id,
 
1129
                                                  'rfq_ok': True,
 
1130
                                                  'from_procurement': True,
 
1131
                                                  'order_type': sale_order.order_type,
 
1132
                                                  'origin': sale_order.name,}, context=context)
 
1133
 
 
1134
            # add a line to the RfQ
 
1135
            rfq_line_id = rfq_line_obj.create(cr, uid, {'product_id': proc.product_id.id,
 
1136
                                                        'comment': sale_order_line.comment,
 
1137
                                                        'name': sale_order_line.name,
 
1138
                                                        'price_unit': 0.00,
 
1139
                                                        'product_qty': proc.product_qty,
 
1140
                                                        'origin': sale_order.name,
 
1141
                                                        'order_id': rfq_id,
 
1142
                                                        'sale_order_line_id': sale_order_line.id,
 
1143
                                                        'location_id': proc.location_id.id,
 
1144
                                                        'product_uom': proc.product_uom.id,
 
1145
                                                        'procurement_id': proc.id,
 
1146
                                                        #'date_planned': proc.date_planned, # function at line level
 
1147
                                                        }, context=context)
 
1148
            
 
1149
            self.write(cr, uid, ids, {'rfq_id': rfq_id, 'rfq_line_id': rfq_line_id}, context=context)
 
1150
            
 
1151
            # log message concerning RfQ creation
 
1152
            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})
 
1153
        # state of procurement is Tender
 
1154
        self.write(cr, uid, ids, {'state': 'rfq'}, context=context)
 
1155
        
 
1156
        return rfq_id
691
1157
    
692
1158
    def wkf_action_tender_create(self, cr, uid, ids, context=None):
693
1159
        '''
697
1163
        tender_line_obj = self.pool.get('tender.line')
698
1164
        # find the corresponding sale order id for tender
699
1165
        for proc in self.browse(cr, uid, ids, context=context):
 
1166
            if proc.tender_id:
 
1167
                return proc.tender_id
700
1168
            sale_order = False
701
1169
            sale_order_line = False
702
1170
            for sol in proc.sale_order_line_ids:
717
1185
                                                        'requested_date': proc.date_planned,
718
1186
                                                        }, context=context)
719
1187
            # add a line to the tender
720
 
            tender_line_obj.create(cr, uid, {'product_id': proc.product_id.id,
721
 
                                             'comment': sale_order_line.comment,
722
 
                                             'qty': proc.product_qty,
723
 
                                             'tender_id': tender_id,
724
 
                                             'sale_order_line_id': sale_order_line.id,
725
 
                                             'location_id': proc.location_id.id,
726
 
                                             'product_uom': proc.product_uom.id,
727
 
                                             #'date_planned': proc.date_planned, # function at line level
728
 
                                             }, context=context)
 
1188
            tender_line_id = tender_line_obj.create(cr, uid, {'product_id': proc.product_id.id,
 
1189
                                                              'comment': sale_order_line.comment,
 
1190
                                                              'qty': proc.product_qty,
 
1191
                                                              'tender_id': tender_id,
 
1192
                                                              'sale_order_line_id': sale_order_line.id,
 
1193
                                                              'location_id': proc.location_id.id,
 
1194
                                                              'product_uom': proc.product_uom.id,
 
1195
                                                              #'date_planned': proc.date_planned, # function at line level
 
1196
                                                              }, context=context)
729
1197
            
730
 
            self.write(cr, uid, ids, {'tender_id': tender_id}, context=context)
 
1198
            self.write(cr, uid, ids, {'tender_id': tender_id, 'tender_line_id': tender_line_id}, context=context)
731
1199
            
732
1200
            # log message concerning tender creation
733
1201
            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)
742
1210
        '''
743
1211
        self.write(cr, uid, ids, {'is_tender_done': True, 'state': 'exception',}, context=context)
744
1212
        return True
 
1213
 
 
1214
    def wkf_action_rfq_done(self, cr, uid, ids, context=None):
 
1215
        '''
 
1216
        set is_rfq_done value
 
1217
        '''
 
1218
        self.write(cr, uid, ids, {'is_rfq_done': True, 'state': 'exception',}, context=context)
 
1219
        return True
745
1220
    
746
1221
    def action_po_assign(self, cr, uid, ids, context=None):
747
1222
        '''
749
1224
        - add message at po creation during on_order workflow
750
1225
        '''
751
1226
        po_obj = self.pool.get('purchase.order')
 
1227
        sol_obj = self.pool.get('sale.order.line')
 
1228
 
 
1229
        # If the line has been created by a confirmed PO, doesn't create a new PO
 
1230
        sol_ids = sol_obj.search(cr, uid, [('procurement_id', 'in', ids), ('created_by_po', '!=', False)], context=context)
 
1231
        if sol_ids:
 
1232
            return sol_obj.read(cr, uid, sol_ids[0], ['created_by_po'], context=context)['created_by_po'][0]
 
1233
 
752
1234
        result = super(procurement_order, self).action_po_assign(cr, uid, ids, context=context)
753
1235
        # The quotation 'SO001' has been converted to a sales order.
754
1236
        if result:
765
1247
        values = super(procurement_order, self).po_values_hook(cr, uid, ids, context=context, *args, **kwargs)
766
1248
        procurement = kwargs['procurement']
767
1249
 
 
1250
        values['partner_address_id'] = self.pool.get('res.partner').address_get(cr, uid, [values['partner_id']], ['default'])['default']
 
1251
 
768
1252
        # set tender link in purchase order
769
1253
        if procurement.tender_id:
770
1254
            values['origin_tender_id'] = procurement.tender_id.id
771
1255
 
 
1256
        # set rfq link in purchase order
 
1257
        if procurement.rfq_id:
 
1258
            values['origin_rfq_id'] = procurement.rfq_id.id
 
1259
 
772
1260
        values['date_planned'] = procurement.date_planned
773
1261
        
774
1262
        if procurement.product_id:
801
1289
            if obj.state == 'rfq_updated' and not obj.valid_till:
802
1290
                return False
803
1291
        return True
 
1292
 
804
1293
    _columns = {'tender_id': fields.many2one('tender', string="Tender", readonly=True),
 
1294
                'rfq_delivery_address': fields.many2one('res.partner.address', string='Delivery address'),
805
1295
                'origin_tender_id': fields.many2one('tender', string='Tender', readonly=True),
 
1296
                'from_procurement': fields.boolean(string='RfQ created by a procurement order'),
806
1297
                'rfq_ok': fields.boolean(string='Is RfQ ?'),
807
1298
                'state': fields.selection(PURCHASE_ORDER_STATE_SELECTION, 'State', readonly=True, help="The state of the purchase order or the quotation request. A quotation is a purchase order in a 'Draft' state. Then the order has to be confirmed by the user, the state switch to 'Confirmed'. Then the supplier must confirm the order to change the state to 'Approved'. When the purchase order is paid and received, the state becomes 'Closed'. If a cancel action occurs in the invoice or in the reception of goods, the state becomes in exception.", select=True),
808
1299
                'valid_till': fields.date(string='Valid Till', states={'rfq_updated': [('required', True), ('readonly', True)], 'rfq_sent':[('required',False), ('readonly', False),]}, readonly=True,),
809
1300
                # add readonly when state is Done
 
1301
                'sale_order_id': fields.many2one('sale.order', string='Link between RfQ and FO', readonly=True),
810
1302
                }
811
1303
 
812
1304
    _defaults = {
818
1310
            'You must specify a Valid Till date.',
819
1311
            ['valid_till']),]
820
1312
 
 
1313
    def default_get(self, cr, uid, fields, context=None):
 
1314
        '''
 
1315
        Set default data
 
1316
        '''
 
1317
        # Object declaration
 
1318
        partner_obj = self.pool.get('res.partner')
 
1319
        user_obj = self.pool.get('res.users')
 
1320
 
 
1321
        res = super(purchase_order, self).default_get(cr, uid, fields, context=context)
 
1322
 
 
1323
        # Get the delivery address
 
1324
        company = user_obj.browse(cr, uid, uid, context=context).company_id
 
1325
        res['rfq_delivery_address'] = partner_obj.address_get(cr, uid, company.partner_id.id, ['delivery'])['delivery']
 
1326
 
 
1327
        return res
 
1328
 
821
1329
    def create(self, cr, uid, vals, context=None):
822
1330
        '''
823
1331
        Set the reference at this step
879
1387
        This hook belongs to the rfq_sent method from tender_flow>tender_flow.py
880
1388
        - check lines after import
881
1389
        '''
882
 
        res = True
 
1390
        pol_obj = self.pool.get('purchase.order.line')                          
 
1391
        
 
1392
        res = True                                                              
 
1393
        empty_lines = pol_obj.search(cr, uid, [                                 
 
1394
            ('order_id', 'in', ids),                                            
 
1395
            ('product_qty', '<=', 0.00),                                        
 
1396
        ], context=context)                                                     
 
1397
        if empty_lines:                                                         
 
1398
            raise osv.except_osv(                                               
 
1399
                _('Error'),                                                     
 
1400
                _('All lines of the RfQ should have a quantity before sending the RfQ to the supplier'),
 
1401
                    ) 
883
1402
        return res
884
 
        
 
1403
 
885
1404
        
886
1405
    def rfq_sent(self, cr, uid, ids, context=None):
 
1406
        if not ids:
 
1407
            return {}
 
1408
        if isinstance(ids, (int, long)):
 
1409
            ids = [ids]
887
1410
        self.hook_rfq_sent_check_lines(cr, uid, ids, context=context)
888
1411
        for rfq in self.browse(cr, uid, ids, context=context):
889
1412
            wf_service = netsvc.LocalService("workflow")
892
1415
        self.write(cr, uid, ids, {'date_confirm': time.strftime('%Y-%m-%d')}, context=context)
893
1416
 
894
1417
        datas = {'ids': ids}
 
1418
        if len(ids) == 1:
 
1419
            # UFTP-92: give a name to report when generated from RfQ worklow sent_rfq stage
 
1420
            datas['target_filename'] = 'RFQ_' + rfq.name
895
1421
 
896
1422
        return {'type': 'ir.actions.report.xml',
897
1423
                'report_name': 'msf.purchase.quotation',
898
1424
                'datas': datas}
899
1425
 
900
1426
    def check_rfq_updated(self, cr, uid, ids, context=None):
 
1427
        tl_obj = self.pool.get('tender.line')
 
1428
        line_obj = self.pool.get('purchase.order.line')
 
1429
 
901
1430
        if isinstance(ids, (int, long)):
902
1431
            ids = [ids]
903
1432
 
906
1435
            if not rfq.valid_till:
907
1436
                raise osv.except_osv(_('Error'), _('You must specify a Valid Till date.'))
908
1437
 
 
1438
            if rfq.rfq_ok and rfq.tender_id:
 
1439
                for line in rfq.order_line:
 
1440
                    if not line.tender_line_id:
 
1441
                        tl_ids = tl_obj.search(cr, uid, [('product_id', '=', line.product_id.id), ('tender_id', '=', rfq.tender_id.id), ('line_state', '=', 'draft')], context=context)
 
1442
                        if tl_ids:
 
1443
                            tl_id = tl_ids[0]
 
1444
                        else:
 
1445
                            tl_vals = {'product_id': line.product_id.id,
 
1446
                                       'product_uom': line.product_uom.id,
 
1447
                                       'qty': line.product_qty,
 
1448
                                       'tender_id': rfq.tender_id.id,
 
1449
                                       'created_by_rfq': True}
 
1450
                            tl_id = tl_obj.create(cr, uid, tl_vals, context=context)
 
1451
                        line_obj.write(cr, uid, [line.id], {'tender_line_id': tl_id}, context=context)
 
1452
 
909
1453
            wf_service.trg_validate(uid, 'purchase.order', rfq.id, 'rfq_updated', cr)
910
1454
 
911
1455
        return {
959
1503
        
960
1504
        return result
961
1505
 
 
1506
    def wkf_act_rfq_done(self, cr, uid, ids, context=None):
 
1507
        '''
 
1508
        Set the state to done and update the price unit in the procurement order
 
1509
        '''
 
1510
        wf_service = netsvc.LocalService("workflow")
 
1511
        proc_obj = self.pool.get('procurement.order')
 
1512
        date_tools = self.pool.get('date.tools')
 
1513
        fields_tools = self.pool.get('fields.tools')
 
1514
        db_date_format = date_tools.get_db_date_format(cr, uid, context=context)
 
1515
 
 
1516
        if isinstance(ids, (int, long)):
 
1517
            ids = [ids]
 
1518
 
 
1519
        for rfq in self.browse(cr, uid, ids, context=context):
 
1520
            if rfq.from_procurement:
 
1521
                for line in rfq.order_line:
 
1522
                    if line.procurement_id:
 
1523
                        self.pool.get('procurement.order').write(cr, uid, [line.procurement_id.id], {'price_unit': line.price_unit}, context=context)
 
1524
                    elif not rfq.tender_id:
 
1525
                        prep_lt = fields_tools.get_field_from_company(cr, uid, object='sale.order', field='preparation_lead_time', context=context)
 
1526
                        rts = datetime.strptime(rfq.sale_order_id.ready_to_ship_date, db_date_format)
 
1527
                        rts = rts - relativedelta(days=prep_lt or 0)
 
1528
                        rts = rts.strftime(db_date_format)
 
1529
                        vals = {'product_id': line.product_id.id,
 
1530
                                'product_uom': line.product_uom.id,
 
1531
                                'product_uos': line.product_uom.id,
 
1532
                                'product_qty': line.product_qty,
 
1533
                                'product_uos_qty': line.product_qty,
 
1534
                                'price_unit': line.price_unit,
 
1535
                                'procure_method': 'make_to_order',
 
1536
                                'is_rfq': True,
 
1537
                                'rfq_id': rfq.id,
 
1538
                                'rfq_line_id': line.id,
 
1539
                                'is_tender': False,
 
1540
                                'tender_id': False,
 
1541
                                'tender_line_id': False,
 
1542
                                'date_planned': rts,
 
1543
                                'origin': rfq.sale_order_id.name,
 
1544
                                'supplier': rfq.partner_id.id,
 
1545
                                'name': '[%s] %s' % (line.product_id.default_code, line.product_id.name),
 
1546
                                'location_id': rfq.sale_order_id.warehouse_id.lot_stock_id.id,
 
1547
                                'po_cft': 'rfq',
 
1548
                                }
 
1549
                        proc_id = proc_obj.create(cr, uid, vals, context=context)
 
1550
                        wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr)
 
1551
                        wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_check', cr)
 
1552
 
 
1553
        return self.write(cr, uid, ids, {'state': 'done'}, context=context)
 
1554
 
962
1555
purchase_order()
963
1556
 
964
1557
 
968
1561
    '''
969
1562
    _inherit = 'purchase.order.line'
970
1563
    _columns = {'tender_id': fields.related('order_id', 'tender_id', type='many2one', relation='tender', string='Tender',),
 
1564
                'tender_line_id': fields.many2one('tender.line', string='Tender Line'),
971
1565
                'rfq_ok': fields.related('order_id', 'rfq_ok', type='boolean', string='RfQ ?'),
 
1566
                'sale_order_line_id': fields.many2one('sale.order.line', string='FO line', readonly=True),
972
1567
                }
973
1568
    
974
1569
    def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
977
1572
        """
978
1573
        if context is None:
979
1574
            context = {}
980
 
                 
 
1575
 
981
1576
        # call super
982
1577
        result = super(purchase_order_line, self).fields_view_get(cr, uid, view_id, view_type, context=context, toolbar=toolbar, submenu=submenu)
983
1578
        if view_type == 'form':
1001
1596
    _inherit = 'sale.order.line'
1002
1597
    
1003
1598
    _columns = {'tender_line_ids': fields.one2many('tender.line', 'sale_order_line_id', string="Tender Lines", readonly=True),}
 
1599
 
 
1600
    def copy_data(self, cr, uid, ids, default=None, context=None):
 
1601
        '''
 
1602
        Remove tender lines linked
 
1603
        '''
 
1604
        default = default or {}
 
1605
 
 
1606
        if not 'tender_line_ids' in default:
 
1607
            default['tender_line_ids'] = []
 
1608
 
 
1609
        return super(sale_order_line, self).copy_data(cr, uid, ids, default, context=context)
1004
1610
    
1005
1611
sale_order_line()
1006
1612
 
1020
1626
    
1021
1627
    _inherit = 'pricelist.partnerinfo'
1022
1628
    _columns = {'price': fields.float('Unit Price', required=True, digits_compute=dp.get_precision('Purchase Price Computation'), help="This price will be considered as a price for the supplier UoM if any or the default Unit of Measure of the product otherwise"),
1023
 
                'currency_id': fields.many2one('res.currency', string='Currency', required=True, domain="[('partner_currency', '=', partner_id)]"),
 
1629
                'currency_id': fields.many2one('res.currency', string='Currency', required=True, domain="[('partner_currency', '=', partner_id)]", select=True),
1024
1630
                'valid_till': fields.date(string="Valid Till",),
1025
1631
                'comment': fields.char(size=128, string='Comment'),
1026
1632
                'purchase_order_id': fields.related('purchase_order_line_id', 'order_id', type='many2one', relation='purchase.order', string="Related RfQ", readonly=True,),
1030
1636
                }
1031
1637
pricelist_partnerinfo()
1032
1638
 
 
1639
 
 
1640
class tender_line_cancel_wizard(osv.osv_memory):
 
1641
    _name = 'tender.line.cancel.wizard'
 
1642
 
 
1643
    _columns = {
 
1644
        'tender_line_id': fields.many2one('tender.line', string='Tender line', required=True),
 
1645
    }
 
1646
 
 
1647
 
 
1648
    def just_cancel(self, cr, uid, ids, context=None):
 
1649
        '''
 
1650
        Cancel the line 
 
1651
        '''
 
1652
        # Objects
 
1653
        line_obj = self.pool.get('tender.line')
 
1654
        tender_obj = self.pool.get('tender')
 
1655
        data_obj = self.pool.get('ir.model.data')
 
1656
        tender_wiz_obj = self.pool.get('tender.cancel.wizard')
 
1657
 
 
1658
        # Variables
 
1659
        if context is None:
 
1660
            context = {}
 
1661
 
 
1662
        if isinstance(ids, (int, long)):
 
1663
            ids = [ids]
 
1664
 
 
1665
        line_ids = []
 
1666
        tender_ids = set()
 
1667
        for wiz in self.browse(cr, uid, ids, context=context):
 
1668
            tender_ids.add(wiz.tender_line_id.tender_id.id)
 
1669
            line_ids.append(wiz.tender_line_id.id)
 
1670
 
 
1671
        if context.get('has_to_be_resourced'):
 
1672
            line_obj.write(cr, uid, line_ids, {'has_to_be_resourced': True}, context=context)
 
1673
 
 
1674
        line_obj.fake_unlink(cr, uid, line_ids, context=context)
 
1675
 
 
1676
        for tender in tender_obj.browse(cr, uid, list(tender_ids), context=context):
 
1677
            if all(x.line_state in ('cancel', 'done') for x in tender.tender_line_ids):
 
1678
                wiz_id = tender_wiz_obj.create(cr, uid, {'tender_id': tender.id}, context=context)
 
1679
                view_id = data_obj.get_object_reference(cr, uid, 'tender_flow', 'ask_tender_cancel_wizard_form_view')[1]
 
1680
                return {'type': 'ir.actions.act_window',
 
1681
                        'res_model': 'tender.cancel.wizard',
 
1682
                        'view_type': 'form',
 
1683
                        'view_mode': 'form',
 
1684
                        'view_id': [view_id],
 
1685
                        'res_id': wiz_id,
 
1686
                        'target': 'new',
 
1687
                        'context': context}
 
1688
 
 
1689
        return {'type': 'ir.actions.act_window_close'}
 
1690
 
 
1691
    def cancel_and_resource(self, cr, uid, ids, context=None):
 
1692
        '''
 
1693
        Flag the line to be re-sourced and run cancel method
 
1694
        '''
 
1695
        # Objects
 
1696
        if context is None:
 
1697
            context = {}
 
1698
 
 
1699
        context['has_to_be_resourced'] = True
 
1700
 
 
1701
        return self.just_cancel(cr, uid, ids, context=context)
 
1702
 
 
1703
tender_line_cancel_wizard()
 
1704
 
 
1705
 
 
1706
class tender_cancel_wizard(osv.osv_memory):
 
1707
    _name = 'tender.cancel.wizard'
 
1708
 
 
1709
    _columns = {
 
1710
        'tender_id': fields.many2one('tender', string='Tender', required=True),
 
1711
        'not_draft': fields.boolean(string='Tender not draft'),
 
1712
    }
 
1713
 
 
1714
    def just_cancel(self, cr, uid, ids, context=None):
 
1715
        '''
 
1716
        Just cancel the wizard and the lines
 
1717
        '''
 
1718
        # Objects
 
1719
        line_obj = self.pool.get('tender.line')
 
1720
 
 
1721
        # Variables
 
1722
        if context is None:
 
1723
            context = {}
 
1724
 
 
1725
        if isinstance(ids, (int, long)):
 
1726
            ids = [ids]
 
1727
 
 
1728
        wf_service = netsvc.LocalService("workflow")
 
1729
        line_ids = []
 
1730
        tender_ids = []
 
1731
        rfq_ids = []
 
1732
        for wiz in self.browse(cr, uid, ids, context=context):
 
1733
            tender_ids.append(wiz.tender_id.id)
 
1734
            for line in wiz.tender_id.tender_line_ids:
 
1735
                line_ids.append(line.id)
 
1736
            for rfq in wiz.tender_id.rfq_ids:
 
1737
                rfq_ids.append(rfq.id)
 
1738
 
 
1739
        if context.get('has_to_be_resourced'):
 
1740
            line_obj.write(cr, uid, line_ids, {'has_to_be_resourced': True}, context=context)
 
1741
 
 
1742
        line_obj.fake_unlink(cr, uid, line_ids, context=context)
 
1743
 
 
1744
        for rfq in rfq_ids:
 
1745
            wf_service.trg_validate(uid, 'purchase.order', rfq, 'purchase_cancel', cr)
 
1746
 
 
1747
        for tender in tender_ids:
 
1748
            wf_service.trg_validate(uid, 'tender', tender, 'tender_cancel', cr)
 
1749
 
 
1750
        return {'type': 'ir.actions.act_window_close'}
 
1751
 
 
1752
    def cancel_and_resource(self, cr, uid, ids, context=None):
 
1753
        '''
 
1754
        Flag the line to be re-sourced and run cancel method
 
1755
        '''
 
1756
        # Objects
 
1757
        if context is None:
 
1758
            context = {}
 
1759
 
 
1760
        context['has_to_be_resourced'] = True
 
1761
 
 
1762
        return self.just_cancel(cr, uid, ids, context=context)
 
1763
 
 
1764
    def close_window(self, cr, uid, ids, context=None):
 
1765
        '''
 
1766
        Just close the wizard and reload the tender
 
1767
        '''
 
1768
        return {'type': 'ir.actions.act_window_close'}
 
1769
 
 
1770
tender_cancel_wizard()
 
1771
 
 
1772
 
 
1773
 
1033
1774
class ir_values(osv.osv):
1034
1775
    _name = 'ir.values'
1035
1776
    _inherit = 'ir.values'