1
# -*- coding: utf-8 -*-
2
##############################################################################
4
# Copyright (C) 2011 MSF, TeMPO Consulting
6
# This program is free software: you can redistribute it and/or modify
7
# it under the terms of the GNU Affero General Public License as
8
# published by the Free Software Foundation, either version 3 of the
9
# License, or (at your option) any later version.
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
# GNU Affero General Public License for more details.
16
# You should have received a copy of the GNU Affero General Public License
17
# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
##############################################################################
21
from datetime import datetime, timedelta, date
22
from dateutil.relativedelta import relativedelta, relativedelta
23
from order_types import ORDER_PRIORITY, ORDER_CATEGORY
24
from osv import osv, fields
25
from osv.orm import browse_record, browse_null
26
from tools.translate import _
28
import decimal_precision as dp
34
class tender(osv.osv):
39
_description = 'Tender'
41
def _vals_get(self, cr, uid, ids, fields, arg, context=None):
43
return function values
46
for obj in self.browse(cr, uid, ids, context=context):
47
result[obj.id] = {'rfq_name_list': '',
51
for rfq in obj.rfq_ids:
52
rfq_names.append(rfq.name)
55
result[obj.id]['rfq_name_list'] = ','.join(rfq_names)
59
_columns = {'name': fields.char('Tender Reference', size=64, required=True, select=True, readonly=True),
60
'sale_order_id': fields.many2one('sale.order', string="Sale Order", readonly=True),
61
'state': fields.selection([('draft', 'Draft'),('comparison', 'Comparison'), ('done', 'Done'), ('cancel', 'Canceled'),], string="State", readonly=True),
62
'supplier_ids': fields.many2many('res.partner', 'tender_supplier_rel', 'tender_id', 'supplier_id', string="Suppliers",
63
states={'draft':[('readonly',False)]}, readonly=True,
64
context={'search_default_supplier': 1,}),
65
'location_id': fields.many2one('stock.location', 'Location', required=True, states={'draft':[('readonly',False)]}, readonly=True, domain=[('usage', '=', 'internal')]),
66
'company_id': fields.many2one('res.company','Company',required=True, states={'draft':[('readonly',False)]}, readonly=True),
67
'rfq_ids': fields.one2many('purchase.order', 'tender_id', string="RfQs", readonly=True),
68
'priority': fields.selection(ORDER_PRIORITY, string='Tender Priority', states={'draft':[('readonly',False)],}, readonly=True,),
69
'categ': fields.selection(ORDER_CATEGORY, string='Tender Category', required=True, states={'draft':[('readonly',False)],}, readonly=True),
70
'creator': fields.many2one('res.users', string="Creator", readonly=True, required=True,),
71
'warehouse_id': fields.many2one('stock.warehouse', string="Warehouse", required=True, states={'draft':[('readonly',False)],}, readonly=True),
72
'creation_date': fields.date(string="Creation Date", readonly=True),
73
'details': fields.char(size=30, string="Details", states={'draft':[('readonly',False)],}, readonly=True),
74
'requested_date': fields.date(string="Requested Date", required=True, states={'draft':[('readonly',False)],}, readonly=True),
75
'notes': fields.text('Notes'),
76
'rfq_name_list': fields.function(_vals_get, method=True, string='RfQs Ref', type='char', readonly=True, store=False, multi='get_vals',)
79
_defaults = {'state': 'draft',
80
'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'tender'),
81
'company_id': lambda obj, cr, uid, context: obj.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id,
82
'creator': lambda obj, cr, uid, context: uid,
83
'creation_date': lambda *a: time.strftime('%Y-%m-%d'),
84
'requested_date': lambda *a: time.strftime('%Y-%m-%d'),
86
'warehouse_id': lambda obj, cr, uid, context: len(obj.pool.get('stock.warehouse').search(cr, uid, [])) and obj.pool.get('stock.warehouse').search(cr, uid, [])[0],
91
def onchange_warehouse(self, cr, uid, ids, warehouse_id, context=None):
93
on_change function for the warehouse
95
result = {'value':{},}
97
input_loc_id = self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id, context=context).lot_input_id.id
98
result['value'].update(location_id=input_loc_id)
102
def wkf_generate_rfq(self, cr, uid, ids, context=None):
104
generate the rfqs for each specified supplier
108
po_obj = self.pool.get('purchase.order')
109
pol_obj = self.pool.get('purchase.order.line')
110
partner_obj = self.pool.get('res.partner')
111
pricelist_obj = self.pool.get('product.pricelist')
112
# no suppliers -> raise error
113
for tender in self.browse(cr, uid, ids, context=context):
114
if not tender.supplier_ids:
115
raise osv.except_osv(_('Warning !'), _('You must select at least one supplier!'))
116
for supplier in tender.supplier_ids:
117
# create a purchase order for each supplier
118
address_id = partner_obj.address_get(cr, uid, [supplier.id], ['delivery'])['delivery']
120
raise osv.except_osv(_('Warning !'), _('The supplier "%s" has no address defined!'%supplier.name))
121
pricelist_id = supplier.property_product_pricelist_purchase.id
122
values = {'name': self.pool.get('ir.sequence').get(cr, uid, 'rfq'),
123
'origin': tender.sale_order_id and tender.sale_order_id.name + '/' + tender.name or tender.name,
125
'partner_id': supplier.id,
126
'partner_address_id': address_id,
127
'location_id': tender.location_id.id,
128
'pricelist_id': pricelist_id,
129
'company_id': tender.company_id.id,
130
'fiscal_position': supplier.property_account_position and supplier.property_account_position.id or False,
131
'tender_id': tender.id,
132
'warehouse_id': tender.warehouse_id.id,
133
'categ': tender.categ,
134
'priority': tender.priority,
135
'details': tender.details,
136
'delivery_requested_date': tender.requested_date,
138
# create the rfq - dic is udpated for default partner_address_id at purchase.order level
139
po_id = po_obj.create(cr, uid, values, context=dict(context, partner_id=supplier.id))
141
for line in tender.tender_line_ids:
142
# create an order line for each tender line
143
price = pricelist_obj.price_get(cr, uid, [pricelist_id], line.product_id.id, line.qty, supplier.id, {'uom': line.product_uom.id})[pricelist_id]
144
newdate = datetime.strptime(line.date_planned, '%Y-%m-%d')
145
#newdate = (newdate - relativedelta(days=tender.company_id.po_lead)) - relativedelta(days=int(supplier.default_delay)) # requested by Magali uf-489
146
values = {'name': line.product_id.partner_ref,
147
'product_qty': line.qty,
148
'product_id': line.product_id.id,
149
'product_uom': line.product_uom.id,
150
'price_unit': 0.0, # was price variable - uf-607
151
'date_planned': newdate.strftime('%Y-%m-%d'),
152
'notes': line.product_id.description_purchase,
155
# create purchase order line
156
pol_id = pol_obj.create(cr, uid, values, context=context)
158
po_obj.log(cr, uid, po_id, "Request for Quotation '%s' has been created."%po_obj.browse(cr, uid, po_id, context=context).name)
160
self.write(cr, uid, ids, {'state':'comparison'}, context=context)
163
def wkf_action_done(self, cr, uid, ids, context=None):
167
# done all related rfqs
168
wf_service = netsvc.LocalService("workflow")
169
for tender in self.browse(cr, uid, ids, context=context):
171
for rfq in tender.rfq_ids:
172
if rfq.state not in ('rfq_updated', 'cancel',):
173
rfq_list.append(rfq.id)
175
wf_service.trg_validate(uid, 'purchase.order', rfq.id, 'rfq_done', cr)
177
# if some rfq have wrong state, we display a message
179
raise osv.except_osv(_('Warning !'), _("Generated RfQs must be Updated or Canceled."))
181
# integrity check, all lines must have purchase_order_line_id
182
if not all([line.purchase_order_line_id.id for line in tender.tender_line_ids]):
183
raise osv.except_osv(_('Error !'), _('All tender lines must have been compared!'))
185
# update product supplierinfo and pricelist
186
self.update_supplier_info(cr, uid, ids, context=context, integrity_test=False,)
187
# change tender state
188
self.write(cr, uid, ids, {'state':'done'}, context=context)
191
def tender_integrity(self, cr, uid, tender, context=None):
193
check the state of corresponding RfQs
195
po_obj = self.pool.get('purchase.order')
196
# no rfq in done state
197
rfq_ids = po_obj.search(cr, uid, [('tender_id', '=', tender.id),
198
('state', 'in', ('done',)),], context=context)
200
raise osv.except_osv(_('Error !'), _("Some RfQ are already Done. Integrity failure."))
201
# all rfqs must have been treated
202
rfq_ids = po_obj.search(cr, uid, [('tender_id', '=', tender.id),
203
('state', 'in', ('draft', 'rfq_sent',)),], context=context)
205
raise osv.except_osv(_('Warning !'), _("Generated RfQs must be Updated or Canceled."))
206
# at least one rfq must be updated and not canceled
207
rfq_ids = po_obj.search(cr, uid, [('tender_id', '=', tender.id),
208
('state', 'in', ('rfq_updated',)),], context=context)
210
raise osv.except_osv(_('Warning !'), _("At least one RfQ must be in state Updated."))
214
def compare_rfqs(self, cr, uid, ids, context=None):
219
raise osv.except_osv(_('Warning !'), _('Cannot compare rfqs of more than one tender at a time!'))
220
po_obj = self.pool.get('purchase.order')
221
wiz_obj = self.pool.get('wizard.compare.rfq')
222
for tender in self.browse(cr, uid, ids, context=context):
223
# check if corresponding rfqs are in the good state
224
rfq_ids = self.tender_integrity(cr, uid, tender, context=context)
225
# gather the product_id -> supplier_id relationship to display it back in the compare wizard
227
for line in tender.tender_line_ids:
228
if line.product_id and line.supplier_id:
229
suppliers.update({line.product_id.id:line.supplier_id.id,})
230
# rfq corresponding to this tender with done state (has been updated and not canceled)
231
# the list of rfq which will be compared
232
c = dict(context, active_ids=rfq_ids, tender_id=tender.id, end_wizard=False, suppliers=suppliers,)
234
action = wiz_obj.start_compare_rfq(cr, uid, ids, context=c)
237
def update_supplier_info(self, cr, uid, ids, context=None, *args, **kwargs):
239
update the supplier info of corresponding products
241
info_obj = self.pool.get('product.supplierinfo')
242
pricelist_info_obj = self.pool.get('pricelist.partnerinfo')
243
# integrity check flag
244
integrity_test = kwargs.get('integrity_test', False)
245
for tender in self.browse(cr, uid, ids, context=context):
246
# flag if at least one update
248
# check if corresponding rfqs are in the good state
250
self.tender_integrity(cr, uid, tender, context=context)
251
for line in tender.tender_line_ids:
252
# if a supplier has been selected
253
if line.purchase_order_line_id:
257
product = line.product_id
258
# find the corresponding suppinfo with sequence -99
259
info_99_list = info_obj.search(cr, uid, [('product_id', '=', product.product_tmpl_id.id),
260
('sequence', '=', -99),], context=context)
264
info_obj.unlink(cr, uid, info_99_list, context=context)
267
values = {'name': line.supplier_id.id,
268
'product_name': False,
269
'product_code': False,
271
'product_uom': line.product_uom.id,
274
'product_id' : product.product_tmpl_id.id,
275
'delay' : int(line.supplier_id.default_delay),
276
#'pricelist_ids': created just after
277
#'company_id': default value
280
new_info_id = info_obj.create(cr, uid, values, context=context)
281
# price lists creation - 'pricelist.partnerinfo
282
values = {'suppinfo_id': new_info_id,
283
'min_quantity': line.qty,
284
'price': line.price_unit,
285
'currency_id': line.purchase_order_line_id.currency_id.id,
286
'valid_till': line.purchase_order_id.valid_till,
287
'purchase_order_line_id': line.purchase_order_line_id.id,
289
new_pricelist_id = pricelist_info_obj.create(cr, uid, values, context=context)
291
# warn the user if no update has been performed
293
raise osv.except_osv(_('Warning !'), _('No information available for update!'))
297
def done(self, cr, uid, ids, context=None):
299
method to perform checks before call to workflow
301
po_obj = self.pool.get('purchase.order')
302
wf_service = netsvc.LocalService("workflow")
303
for tender in self.browse(cr, uid, ids, context=context):
304
# check if corresponding rfqs are in the good state
305
self.tender_integrity(cr, uid, tender, context=context)
306
wf_service.trg_validate(uid, 'tender', tender.id, 'button_done', cr)
307
# trigger all related rfqs
308
rfq_ids = po_obj.search(cr, uid, [('tender_id', '=', tender.id),], context=context)
309
for rfq_id in rfq_ids:
310
wf_service.trg_validate(uid, 'purchase.order', rfq_id, 'rfq_done', cr)
314
def create_po(self, cr, uid, ids, context=None):
316
create a po from the updated RfQs
318
partner_obj = self.pool.get('res.partner')
319
po_obj = self.pool.get('purchase.order')
320
wf_service = netsvc.LocalService("workflow")
322
for tender in self.browse(cr, uid, ids, context=context):
323
# check if corresponding rfqs are in the good state
324
self.tender_integrity(cr, uid, tender, context=context)
325
# integrity check, all lines must have purchase_order_line_id
326
if not all([line.purchase_order_line_id.id for line in tender.tender_line_ids]):
327
raise osv.except_osv(_('Error !'), _('All tender lines must have been compared!'))
329
for line in tender.tender_line_ids:
330
data.setdefault(line.supplier_id.id, {}) \
331
.setdefault('order_line', []).append((0,0,{'name': line.product_id.partner_ref,
332
'product_qty': line.qty,
333
'product_id': line.product_id.id,
334
'product_uom': line.product_uom.id,
335
'price_unit': line.price_unit,
336
'date_planned': line.date_planned,
337
'move_dest_id': False,
338
'notes': line.product_id.description_purchase,
341
# fill data corresponding to po creation
342
address_id = partner_obj.address_get(cr, uid, [line.supplier_id.id], ['delivery'])['delivery']
343
po_values = {'origin': tender.name,
344
'partner_id': line.supplier_id.id,
345
'partner_address_id': address_id,
346
'location_id': tender.location_id.id,
347
'pricelist_id': line.supplier_id.property_product_pricelist_purchase.id,
348
'company_id': tender.company_id.id,
349
'fiscal_position': line.supplier_id.property_account_position and line.supplier_id.property_account_position.id or False,
350
'categ': tender.categ,
351
'priority': tender.priority,
352
#'tender_id': tender.id, # not for now, because tender_id is the flag for a po to be considered as RfQ
353
'warehouse_id': tender.warehouse_id.id,
354
'details': tender.details,
355
'delivery_requested_date': tender.requested_date,
357
data[line.supplier_id.id].update(po_values)
359
# create the pos, one for each selected supplier
360
for po_data in data.values():
361
po_id = po_obj.create(cr, uid, po_data, context=context)
362
po = po_obj.browse(cr, uid, po_id, context=context)
363
po_obj.log(cr, uid, po_id, 'The Purchase order %s for supplier %s has been created.'%(po.name, po.partner_id.name))
364
wf_service.trg_validate(uid, 'purchase.order', po_id, 'purchase_confirm', cr)
366
# when the po is generated, the tender is done - no more modification or comparison
367
self.done(cr, uid, [tender.id], context=context)
371
def wkf_action_cancel(self, cr, uid, ids, context=None):
373
cancel all corresponding rfqs
375
po_obj = self.pool.get('purchase.order')
376
wf_service = netsvc.LocalService("workflow")
378
self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
379
for tender in self.browse(cr, uid, ids, context=context):
380
# trigger all related rfqs
381
rfq_ids = po_obj.search(cr, uid, [('tender_id', '=', tender.id),], context=context)
382
for rfq_id in rfq_ids:
383
wf_service.trg_validate(uid, 'purchase.order', rfq_id, 'purchase_cancel', cr)
390
class tender_line(osv.osv):
394
_name = 'tender.line'
395
_description= 'Tender Line'
397
_SELECTION_TENDER_STATE = [('draft', 'Draft'),('comparison', 'Comparison'), ('done', 'Done'),]
399
def on_product_change(self, cr, uid, id, product_id, context=None):
401
product is changed, we update the UoM
403
prod_obj = self.pool.get('product.product')
404
result = {'value': {}}
406
result['value']['product_uom'] = prod_obj.browse(cr, uid, product_id, context=context).uom_po_id.id
410
def _get_total_price(self, cr, uid, ids, field_name, arg, context=None):
412
return the total price
415
for line in self.browse(cr, uid, ids, context=context):
416
if line.price_unit and line.qty:
417
result[line.id] = line.price_unit * line.qty
419
result[line.id] = 0.0
423
def name_get(self, cr, user, ids, context=None):
424
result = self.browse(cr, user, ids, context=context)
427
code = rs.product_id and rs.product_id.name or ''
428
res += [(rs.id, code)]
431
_columns = {'product_id': fields.many2one('product.product', string="Product", required=True),
432
'qty': fields.float(string="Qty", required=True),
433
'tender_id': fields.many2one('tender', string="Tender", required=True),
434
'purchase_order_line_id': fields.many2one('purchase.order.line', string="Related RfQ line", readonly=True),
435
'sale_order_line_id': fields.many2one('sale.order.line', string="Sale Order Line"),
436
'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
437
'date_planned': fields.related('tender_id', 'requested_date', type='date', string='Requested Date', store=False,),
439
'supplier_id': fields.related('purchase_order_line_id', 'order_id', 'partner_id', type='many2one', relation='res.partner', string="Supplier", readonly=True),
440
'price_unit': fields.related('purchase_order_line_id', 'price_unit', type="float", string="Price unit", readonly=True),
441
'total_price': fields.function(_get_total_price, method=True, type='float', string="Total Price"),
442
'purchase_order_id': fields.related('purchase_order_line_id', 'order_id', type='many2one', relation='purchase.order', string="Related RfQ", readonly=True,),
443
'purchase_order_line_number': fields.related('purchase_order_line_id', 'line_number', type="integer", string="Related Line Number", readonly=True,),
444
'state': fields.related('tender_id', 'state', type="selection", selection=_SELECTION_TENDER_STATE, string="State",),
446
_defaults = {'qty': 1.0,
452
class tender(osv.osv):
457
_columns = {'tender_line_ids': fields.one2many('tender.line', 'tender_id', string="Tender lines", states={'draft':[('readonly',False)]}, readonly=True),
460
def copy(self, cr, uid, id, default=None, context=None):
462
reset the name to get new sequence number
464
the copy method is here because upwards it goes in infinite loop
466
line_obj = self.pool.get('tender.line')
470
default.update(name=self.pool.get('ir.sequence').get(cr, uid, 'tender'),
472
sale_order_line_id=False,)
474
result = super(tender, self).copy(cr, uid, id, default, context)
478
def copy_data(self, cr, uid, id, default=None, context=None):
480
reset the tender line
482
result = super(tender, self).copy_data(cr, uid, id, default=default, context=context)
483
# reset the tender line
484
for line in result['tender_line_ids']:
485
line[2].update(sale_order_line_id=False,
486
purchase_order_line_id=False,)
492
class procurement_order(osv.osv):
496
_inherit = 'procurement.order'
498
def _is_tender(self, cr, uid, ids, field_name, arg, context=None):
500
tell if the corresponding sale order line is tender sourcing or not
506
for proc in self.browse(cr, uid, ids, context=context):
507
for line in proc.sale_order_line_ids:
508
result[proc.id] = line.po_cft == 'cft'
512
_columns = {'is_tender': fields.function(_is_tender, method=True, type='boolean', string='Is Tender', readonly=True,),
513
'sale_order_line_ids': fields.one2many('sale.order.line', 'procurement_id', string="Sale Order Lines"),
514
'tender_id': fields.many2one('tender', string='Tender', readonly=True),
515
'is_tender_done': fields.boolean(string="Tender Done"),
516
'state': fields.selection([('draft','Draft'),
517
('confirmed','Confirmed'),
518
('exception','Exception'),
519
('running','Running'),
523
('tender', 'Tender'),
524
('waiting','Waiting'),], 'State', required=True,
525
help='When a procurement is created the state is set to \'Draft\'.\n If the procurement is confirmed, the state is set to \'Confirmed\'.\
526
\nAfter confirming the state is set to \'Running\'.\n If any exception arises in the order then the state is set to \'Exception\'.\n Once the exception is removed the state becomes \'Ready\'.\n It is in \'Waiting\'. state when the procurement is waiting for another one to finish.'),
527
'price_unit': fields.float('Unit Price from Tender', digits_compute= dp.get_precision('Purchase Price')),
529
_defaults = {'is_tender_done': False,}
531
def wkf_action_tender_create(self, cr, uid, ids, context=None):
533
creation of tender from procurement workflow
535
tender_obj = self.pool.get('tender')
536
tender_line_obj = self.pool.get('tender.line')
537
# find the corresponding sale order id for tender
538
for proc in self.browse(cr, uid, ids, context=context):
540
sale_order_line = False
541
for sol in proc.sale_order_line_ids:
542
sale_order = sol.order_id
543
sale_order_line = sol
546
tender_ids = tender_obj.search(cr, uid, [('sale_order_id', '=', sale_order.id),('state', '=', 'draft'),], context=context)
548
tender_id = tender_ids[0]
549
# create if not found
551
tender_id = tender_obj.create(cr, uid, {'sale_order_id': sale_order.id,
552
'location_id': proc.location_id.id,
553
'categ': sale_order.categ,
554
'priority': sale_order.priority,
555
'warehouse_id': sale_order.shop_id.warehouse_id.id,
556
'requested_date': proc.date_planned,
558
# add a line to the tender
559
tender_line_obj.create(cr, uid, {'product_id': proc.product_id.id,
560
'qty': proc.product_qty,
561
'tender_id': tender_id,
562
'sale_order_line_id': sale_order_line.id,
563
'location_id': proc.location_id.id,
564
'product_uom': proc.product_uom.id,
565
#'date_planned': proc.date_planned, # function at line level
568
self.write(cr, uid, ids, {'tender_id': tender_id}, context=context)
570
# log message concerning tender creation
571
tender_obj.log(cr, uid, tender_id, "The tender '%s' has been created and must be completed before purchase order creation."%tender_obj.browse(cr, uid, tender_id, context=context).name)
572
# state of procurement is Tender
573
self.write(cr, uid, ids, {'state': 'tender'}, context=context)
577
def wkf_action_tender_done(self, cr, uid, ids, context=None):
579
set is_tender_done value
581
self.write(cr, uid, ids, {'is_tender_done': True, 'state': 'exception',}, context=context)
584
def action_po_assign(self, cr, uid, ids, context=None):
586
- convert the created rfq by the tender to a po
587
- add message at po creation during on_order workflow
589
po_obj = self.pool.get('purchase.order')
590
result = super(procurement_order, self).action_po_assign(cr, uid, ids, context=context)
591
# The quotation 'SO001' has been converted to a sales order.
593
po_obj.log(cr, uid, result, "The Purchase Order '%s' has been created following 'on order' sourcing."%po_obj.browse(cr, uid, result, context=context).name)
594
if self.browse(cr, uid, ids[0], context=context).is_tender:
595
wf_service = netsvc.LocalService("workflow")
596
wf_service.trg_validate(uid, 'purchase.order', result, 'purchase_confirm', cr)
599
def create_po_hook(self, cr, uid, ids, context=None, *args, **kwargs):
601
if the procurement corresponds to a tender, the created po is confirmed but not validated
603
po_obj = self.pool.get('purchase.order')
604
procurement = kwargs['procurement']
605
purchase_id = super(procurement_order, self).create_po_hook(cr, uid, ids, context=context, *args, **kwargs)
607
if procurement.is_tender:
608
wf_service = netsvc.LocalService("workflow")
609
wf_service.trg_validate(uid, 'purchase.order', purchase_id, 'purchase_confirm', cr)
615
class purchase_order(osv.osv):
619
_inherit = 'purchase.order'
624
('confirmed', 'Waiting Approval'),
625
('approved', 'Approved'),
626
('except_picking', 'Shipping Exception'),
627
('except_invoice', 'Invoice Exception'),
629
('cancel', 'Cancelled'),
630
('rfq_sent', 'RfQ Sent'),
631
('rfq_updated', 'RfQ Updated'),
632
#('rfq_done', 'RfQ Done'),
635
def _check_valid_till(self, cr, uid, ids, context=None):
636
""" Checks if valid till has been completed
638
for obj in self.browse(cr, uid, ids, context=context):
639
if obj.state == 'rfq_updated' and not obj.valid_till:
644
_columns = {'tender_id': fields.many2one('tender', string="Tender", readonly=True),
645
'rfq_ok': fields.boolean(string='Is RfQ ?'),
646
'state': fields.selection(STATE_SELECTION, 'State', readonly=True, help="The state of the purchase order or the quotation request. A quotation is a purchase order in a 'Draft' state. Then the order has to be confirmed by the user, the state switch to 'Confirmed'. Then the supplier must confirm the order to change the state to 'Approved'. When the purchase order is paid and received, the state becomes 'Done'. If a cancel action occurs in the invoice or in the reception of goods, the state becomes in exception.", select=True),
647
'valid_till': fields.date(string='Valid Till', states={'rfq_sent':[('required',True), ('readonly', False),]}, readonly=True,),
648
# add readonly when state is Done
649
'name': fields.char('Order Reference', size=64, required=True, states={'done':[('readonly',True)],}, select=True, help="unique number of the purchase order,computed automatically when the purchase order is created"),
650
'date_order':fields.date('Date Ordered', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)], 'done':[('readonly',True)],}, select=True, help="Date on which this document has been created."),
654
'rfq_ok': lambda self, cr, uid, c: c.get('rfq_ok', False),
655
'name': lambda obj, cr, uid, c: obj.pool.get('ir.sequence').get(cr, uid, c.get('rfq_ok', False) and 'rfq' or 'purchase.order'),
660
'You must specify a Valid Till date.',
663
def _hook_copy_name(self, cr, uid, ids, context=None, *args, **kwargs):
665
HOOK from purchase>purchase.py for COPY function. Modification of default copy values
666
define which name value will be used
668
result = super(purchase_order, self)._hook_copy_name(cr, uid, ids, context=context, *args, **kwargs)
669
for obj in self.browse(cr, uid, ids, context=context):
671
result.update(name=self.pool.get('ir.sequence').get(cr, uid, 'rfq'))
677
class purchase_order_line(osv.osv):
679
add a tender_id related field
681
_inherit = 'purchase.order.line'
682
_columns = {'tender_id': fields.related('order_id', 'tender_id', type='many2one', relation='tender', string='Tender',),
685
purchase_order_line()
688
class sale_order_line(osv.osv):
690
add link one2many to tender.line
692
_inherit = 'sale.order.line'
694
_columns = {'tender_line_ids': fields.one2many('tender.line', 'sale_order_line_id', string="Tender Lines", readonly=True),}
699
class pricelist_partnerinfo(osv.osv):
701
add new information from specifications
703
_inherit = 'pricelist.partnerinfo'
704
_columns = {'currency_id': fields.many2one('res.currency', string='Currency',),
705
'valid_till': fields.date(string="Valid Till",),
706
'purchase_order_id': fields.related('purchase_order_line_id', 'order_id', type='many2one', relation='purchase.order', string="Related RfQ", readonly=True,),
707
'purchase_order_line_id': fields.many2one('purchase.order.line', string="RfQ Line Ref",),
708
'purchase_order_line_number': fields.related('purchase_order_line_id', 'line_number', type="integer", string="Related Line Number", readonly=True,),
710
pricelist_partnerinfo()