142
117
_order = 'name desc'
144
def _check_restriction_line(self, cr, uid, ids, context=None):
146
Check if there is no restrictive products in lines
148
if isinstance(ids, (int, long)):
151
line_obj = self.pool.get('tender.line')
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)
159
def default_get(self, cr, uid, fields, context=None):
164
partner_obj = self.pool.get('res.partner')
165
user_obj = self.pool.get('res.users')
167
res = super(tender, self).default_get(cr, uid, fields, context=context)
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']
175
def _check_tender_from_fo(self, cr, uid, ids, context=None):
179
for tender in self.browse(cr, uid, ids, context=context):
180
if not tender.tender_from_fo:
182
for sup in tender.supplier_ids:
183
if sup.partner_type == 'internal' :
188
(_check_tender_from_fo, 'You cannot choose an internal supplier for this tender', []),
191
def create(self, cr, uid, vals, context=None):
193
Set the reference of the tender at this time
195
if not vals.get('name', False):
196
vals.update({'name': self.pool.get('ir.sequence').get(cr, uid, 'tender')})
198
return super(tender, self).create(cr, uid, vals, context=context)
200
def _check_service(self, cr, uid, ids, vals, context=None):
202
Avoid the saving of a Tender with non service products on Service Tender
204
if isinstance(ids, (int, long)):
206
categ = {'transport': _('Transport'),
207
'service': _('Service')}
210
if context.get('import_in_progress'):
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':
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))
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))
225
def write(self, cr, uid, ids, vals, context=None):
227
Check consistency between lines and categ of tender
229
# UFTP-317: Make sure ids is a list
230
if isinstance(ids, (int, long)):
232
exp_sol_obj = self.pool.get('expected.sale.order.line')
234
self._check_service(cr, uid, ids, vals, context=context)
236
if ('state' in vals and vals.get('state') not in ('draft', 'comparison')) or \
237
('sale_order_line_id' in vals and vals.get('sale_order_line_id')):
238
exp_sol_ids = exp_sol_obj.search(cr, uid, [
239
('tender_id', 'in', ids),
241
exp_sol_obj.unlink(cr, uid, exp_sol_ids, context=context)
243
return super(tender, self).write(cr, uid, ids, vals, context=context)
245
def onchange_categ(self, cr, uid, ids, categ, context=None):
246
""" Check that the categ is compatible with the product
247
@param categ: Changed value of categ.
248
@return: Dictionary of values.
250
if isinstance(ids, (int, long)):
253
if ids and categ in ['service', 'transport']:
254
# Avoid selection of non-service producs on Service Tender
255
category = categ=='service' and 'service_recep' or 'transport'
257
if category == 'transport':
258
transport_cat = 'OR p.transport_ok = False'
259
cr.execute('''SELECT p.default_code AS default_code, t.name AS name
261
LEFT JOIN product_product p ON l.product_id = p.id
262
LEFT JOIN product_template pt ON p.product_tmpl_id = pt.id
263
LEFT JOIN tender t ON l.tender_id = t.id
264
WHERE (pt.type != 'service_recep' %s) AND t.id in (%s) LIMIT 1''' % (transport_cat, ','.join(str(x) for x in ids)))
267
cat_name = categ=='service' and 'Service' or 'Transport'
268
message.update({'title': _('Warning'),
269
'message': _('The product [%s] %s is not a \'%s\' product. You can have only \'%s\' products on a \'%s\' tender. Please remove this line before saving.') % (res[0][0], res[0][1], cat_name, cat_name, cat_name)})
271
return {'warning': message}
273
119
def onchange_warehouse(self, cr, uid, ids, warehouse_id, context=None):
275
121
on_change function for the warehouse
292
138
partner_obj = self.pool.get('res.partner')
293
139
pricelist_obj = self.pool.get('product.pricelist')
294
140
obj_data = self.pool.get('ir.model.data')
296
141
# no suppliers -> raise error
297
142
for tender in self.browse(cr, uid, ids, context=context):
298
143
# check some supplier have been selected
299
144
if not tender.supplier_ids:
300
145
raise osv.except_osv(_('Warning !'), _('You must select at least one supplier!'))
301
#utp-315: check that the suppliers are not inactive (I use a SQL request because the inactive partner are ignored with the browse)
303
select tsr.supplier_id, rp.name, rp.active
304
from tender_supplier_rel tsr
305
left join res_partner rp
306
on tsr.supplier_id = rp.id
307
where tsr.tender_id=%s
310
cr.execute(sql, (ids[0],))
311
inactive_supplier_ids = cr.dictfetchall()
312
if any(inactive_supplier_ids):
313
raise osv.except_osv(_('Warning !'), _("You can't have inactive supplier! Please remove: %s"
314
) % ' ,'.join([partner['name'] for partner in inactive_supplier_ids]))
315
146
# check some products have been selected
316
tender_line_ids = self.pool.get('tender.line').search(cr, uid, [('tender_id', '=', tender.id), ('line_state', '!=', 'cancel')], context=context)
147
tender_line_ids = self.pool.get('tender.line').search(cr, uid, [('tender_id', '=', tender.id)], context=context)
317
148
if not tender_line_ids:
318
149
raise osv.except_osv(_('Warning !'), _('You must select at least one product!'))
319
150
for supplier in tender.supplier_ids:
320
151
# create a purchase order for each supplier
321
address_id = partner_obj.address_get(cr, uid, [supplier.id], ['default'])['default']
152
address_id = partner_obj.address_get(cr, uid, [supplier.id], ['delivery'])['delivery']
322
153
if not address_id:
323
154
raise osv.except_osv(_('Warning !'), _('The supplier "%s" has no address defined!')%(supplier.name,))
324
155
pricelist_id = supplier.property_product_pricelist_purchase.id
325
values = {'origin': tender.sale_order_id and tender.sale_order_id.name + ';' + tender.name or tender.name,
156
values = {'name': self.pool.get('ir.sequence').get(cr, uid, 'rfq'),
157
'origin': tender.sale_order_id and tender.sale_order_id.name + ';' + tender.name or tender.name,
327
159
'partner_id': supplier.id,
328
160
'partner_address_id': address_id,
413
222
raise osv.except_osv(_('Warning !'), _("Generated RfQs must be Updated or Cancelled."))
415
224
# integrity check, all lines must have purchase_order_line_id
416
if not all([line.purchase_order_line_id.id for line in tender.tender_line_ids if line.line_state != 'cancel']):
225
if not all([line.purchase_order_line_id.id for line in tender.tender_line_ids]):
417
226
raise osv.except_osv(_('Error !'), _('All tender lines must have been compared!'))
419
if tender.sale_order_id:
420
# Update procurement order
421
for line in tender.tender_line_ids:
422
if line.line_state == 'cancel':
423
proc_id = line.sale_order_line_id and line.sale_order_line_id.procurement_id and line.sale_order_line_id.procurement_id.id
425
wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_cancel', cr)
427
vals = {'product_id': line.product_id.id,
428
'product_uom': line.product_uom.id,
429
'product_uos': line.product_uom.id,
430
'product_qty': line.qty,
431
'price_unit': line.price_unit,
432
'product_uos_qty': line.qty}
433
if line.product_id.type in ('service', 'service_recep'):
434
if not tender.sale_order_id.procurement_request:
435
vals['po_cft'] = 'dpo'
437
if line.sale_order_line_id and line.sale_order_line_id.procurement_id:
438
proc_id = line.sale_order_line_id.procurement_id.id
439
proc_obj.write(cr, uid, [proc_id], vals, context=context)
440
else: # Create procurement order to add the lines in a PO
441
create_vals = vals.copy()
442
context['sale_id'] = tender.sale_order_id.id
444
'order_id': tender.sale_order_id.id,
445
'product_uom_qty': line.qty,
446
'type': 'make_to_order',
448
'supplier': line.supplier_id.id,
449
'created_by_tender': tender.id,
450
'created_by_tender_line': line.id,
451
'name': '[%s] %s' % (line.product_id.default_code, line.product_id.name),
453
sol_obj.create(cr, uid, create_vals, context=context)
455
if tender.sale_order_id.original_so_id_sale_order:
456
context['sale_id'] = tender.sale_order_id.original_so_id_sale_order.id
458
'order_id': tender.sale_order_id.original_so_id_sale_order.id,
461
sol_obj.create(cr, uid, create_vals, context=context)
463
sol_ids.add(tender.sale_order_id.id)
465
self.infolog(cr, uid, "The tender id:%s has been closed" % tender.id)
468
so_obj.action_ship_proc_create(cr, uid, list(sol_ids), context=context)
470
228
# update product supplierinfo and pricelist
471
229
self.update_supplier_info(cr, uid, ids, context=context, integrity_test=False,)
473
230
# change tender state
474
231
self.write(cr, uid, ids, {'state':'done'}, context=context)
772
def check_empty_tender(self, cr, uid, ids, context=None):
774
If the tender is empty, return a wizard to ask user if he wants to
775
cancel the whole tender
777
tender_wiz_obj = self.pool.get('tender.cancel.wizard')
778
data_obj = self.pool.get('ir.model.data')
780
for tender in self.browse(cr, uid, ids, context=context):
781
if all(x.line_state in ('cancel', 'done') for x in tender.tender_line_ids):
782
wiz_id = tender_wiz_obj.create(cr, uid, {'tender_id': tender.id}, context=context)
783
view_id = data_obj.get_object_reference(cr, uid, 'tender_flow', 'ask_tender_cancel_wizard_form_view')[1]
784
return {'type': 'ir.actions.act_window',
785
'res_model': 'tender.cancel.wizard',
788
'view_id': [view_id],
794
'type': 'ir.actions.act_window',
795
'res_model': 'tender',
797
'view_mode': 'form, tree',
803
def sourcing_document_state(self, cr, uid, ids, context=None):
805
Returns all documents that are in the sourcing for a givent tender
810
if isinstance(ids, (int, long)):
813
sol_obj = self.pool.get('sale.order.line')
814
so_obj = self.pool.get('sale.order')
815
po_obj = self.pool.get('purchase.order')
817
# corresponding sale order
819
for tender in self.browse(cr, uid, ids, context=context):
820
if tender.sale_order_id and tender.sale_order_id.id not in so_ids:
821
so_ids.append(tender.sale_order_id.id)
823
# from so, list corresponding po
824
all_po_ids = so_obj.get_po_ids_from_so_ids(cr, uid, so_ids, context=context)
826
# from listed po, list corresponding so
827
all_so_ids = po_obj.get_so_ids_from_po_ids(cr, uid, all_po_ids, context=context)
829
all_sol_not_confirmed_ids = []
830
# if we have sol_ids, we are treating a po which is make_to_order from sale order
832
all_sol_not_confirmed_ids = sol_obj.search(cr, uid, [
833
('order_id', 'in', all_so_ids),
834
('type', '=', 'make_to_order'),
835
('product_id', '!=', False),
836
('procurement_id.state', '!=', 'cancel'),
837
('state', 'not in', ['confirmed', 'done']),
840
return so_ids, all_po_ids, all_so_ids, all_sol_not_confirmed_ids
849
486
_name = 'tender.line'
850
_rec_name = 'product_id'
851
487
_description= 'Tender Line'
853
489
_SELECTION_TENDER_STATE = [('draft', 'Draft'),('comparison', 'Comparison'), ('done', 'Closed'),]
855
def on_product_change(self, cr, uid, id, product_id, uom_id, product_qty, context=None):
491
def on_product_change(self, cr, uid, id, product_id, context=None):
857
493
product is changed, we update the UoM
862
495
prod_obj = self.pool.get('product.product')
863
496
result = {'value': {}}
865
# Test the compatibility of the product with a tender
866
result, test = prod_obj._on_change_restriction_error(cr, uid, product_id, field_name='product_id', values=result, vals={'constraints': ['external', 'esc', 'internal']}, context=context)
870
product = prod_obj.browse(cr, uid, product_id, context=context)
871
result['value']['product_uom'] = product.uom_id.id
872
result['value']['text_error'] = False
873
result['value']['to_correct_ok'] = False
875
res_qty = self.onchange_uom_qty(cr, uid, id, uom_id or result.get('value', {}).get('product_uom',False), product_qty)
876
result['value']['qty'] = res_qty.get('value', {}).get('qty', product_qty)
879
result['value']['product_uom'] = uom_id
498
result['value']['product_uom'] = prod_obj.browse(cr, uid, product_id, context=context).uom_po_id.id
883
def onchange_uom_qty(self, cr, uid, ids, uom_id, qty):
885
Check round of qty according to the UoM
890
res = self.pool.get('product.uom')._change_round_up_qty(cr, uid, uom_id, qty, 'qty', result=res)
894
502
def _get_total_price(self, cr, uid, ids, field_name, arg, context=None):
933
541
'date_planned': fields.related('tender_id', 'requested_date', type='date', string='Requested Date', store=False,),
935
543
'supplier_id': fields.related('purchase_order_line_id', 'order_id', 'partner_id', type='many2one', relation='res.partner', string="Supplier", readonly=True),
936
'price_unit': fields.related('purchase_order_line_id', 'price_unit', type="float", string="Price unit", digits_compute=dp.get_precision('Purchase Price Computation'), readonly=True), # same precision as related field!
937
'total_price': fields.function(_get_total_price, method=True, type='float', string="Total Price", digits_compute=dp.get_precision('Purchase Price'), multi='total'),
544
'price_unit': fields.related('purchase_order_line_id', 'price_unit', type="float", string="Price unit", readonly=True),
545
'total_price': fields.function(_get_total_price, method=True, type='float', string="Total Price", multi='total'),
938
546
'currency_id': fields.function(_get_total_price, method=True, type='many2one', relation='res.currency', string='Cur.', multi='total'),
939
'func_total_price': fields.function(_get_total_price, method=True, type='float', string="Func. Total Price", digits_compute=dp.get_precision('Purchase Price'), multi='total'),
547
'func_total_price': fields.function(_get_total_price, method=True, type='float', string="Func. Total Price", multi='total'),
940
548
'func_currency_id': fields.function(_get_total_price, method=True, type='many2one', relation='res.currency', string='Func. Cur.', multi='total'),
941
549
'purchase_order_id': fields.related('purchase_order_line_id', 'order_id', type='many2one', relation='purchase.order', string="Related RfQ", readonly=True,),
942
'purchase_order_line_number': fields.related('purchase_order_line_id', 'line_number', type="char", string="Related Line Number", readonly=True,),
550
'purchase_order_line_number': fields.related('purchase_order_line_id', 'line_number', type="integer", string="Related Line Number", readonly=True,),
943
551
'state': fields.related('tender_id', 'state', type="selection", selection=_SELECTION_TENDER_STATE, string="State",),
944
'line_state': fields.selection([('draft','Draft'), ('cancel', 'Canceled'), ('done', 'Done')], string='State', readonly=True),
945
552
'comment': fields.char(size=128, string='Comment'),
946
'has_to_be_resourced': fields.boolean(string='Has to be resourced'),
947
'created_by_rfq': fields.boolean(string='Created by RfQ'),
949
554
_defaults = {'qty': lambda *a: 1.0,
950
555
'state': lambda *a: 'draft',
951
'line_state': lambda *a: 'draft',
954
def _check_restriction_line(self, cr, uid, ids, context=None):
956
Check if there is no restrictive products in lines
958
if isinstance(ids, (int, long)):
961
for line in self.browse(cr, uid, ids, context=context):
962
if line.tender_id and line.product_id:
963
if not self.pool.get('product.product')._get_restriction_error(cr, uid, line.product_id.id, vals={'constraints': ['external']}, context=context):
968
558
_sql_constraints = [
969
# ('product_qty_check', 'CHECK( qty > 0 )', 'Product Quantity must be greater than zero.'),
559
('product_qty_check', 'CHECK( qty > 0 )', 'Product Quantity must be greater than zero.'),
972
def create(self, cr, uid, vals, context=None):
973
exp_sol_obj = self.pool.get('expected.sale.order.line')
974
tender_obj = self.pool.get('tender')
976
res = super(tender_line, self).create(cr, uid, vals, context=context)
978
if 'tender_id' in vals and not vals.get('sale_order_line_id'):
979
so_id = tender_obj.read(cr, uid, vals.get('tender_id'), ['sale_order_id'], context=context)['sale_order_id']
981
exp_sol_obj.create(cr, uid, {
982
'order_id': so_id[0],
983
'tender_line_id': res,
988
def write(self, cr, uid, ids, vals, context=None):
989
exp_sol_obj = self.pool.get('expected.sale.order.line')
991
if 'state' in vals and vals.get('state') != 'draft':
992
exp_sol_ids = exp_sol_obj.search(cr, uid, [
993
('tender_line_id', 'in', ids),
995
exp_sol_obj.unlink(cr, uid, exp_sol_ids, context=context)
997
return super(tender_line, self).write(cr, uid, ids, vals, context=context)
999
def copy(self, cr, uid, id, default=None, context=None):
1003
if not 'created_by_rfq' in default:
1004
default['created_by_rfq'] = False
1006
return super(tender_line, self).copy(cr, uid, id, default, context=context)
1008
def cancel_sourcing(self,cr, uid, ids, context=None):
1010
Cancel the line and re-source the FO line
1013
sol_obj = self.pool.get('sale.order.line')
1014
uom_obj = self.pool.get('product.uom')
1015
tender_obj = self.pool.get('tender')
1018
wf_service = netsvc.LocalService("workflow")
1023
sol_not_to_delete = []
1024
so_to_update = set()
1025
tender_to_update = set()
1027
for line in self.browse(cr, uid, ids, context=context):
1028
tender_to_update.add(line.tender_id.id)
1029
if line.sale_order_line_id and line.sale_order_line_id.state not in ('cancel', 'done'):
1030
so_to_update.add(line.sale_order_line_id.order_id.id)
1031
if line.sale_order_line_id.order_id.procurement_request:
1032
sol_not_to_delete.append(line.sale_order_line_id.id)
1033
to_cancel.append(line.id)
1034
# Get the ID and the product qty of the FO line to re-source
1035
diff_qty = uom_obj._compute_qty(cr, uid, line.product_uom.id, line.qty, line.sale_order_line_id.product_uom.id)
1037
if line.has_to_be_resourced:
1038
sol_ids.update({line.sale_order_line_id.id: diff_qty})
1040
sol_to_update.setdefault(line.sale_order_line_id.id, 0.00)
1041
sol_to_update[line.sale_order_line_id.id] += diff_qty
1042
elif line.tender_id.state == 'draft':
1043
to_remove.append(line.id)
1045
to_cancel.append(line.id)
1048
self.write(cr, uid, to_cancel, {'line_state': 'cancel'}, context=context)
1052
sol_obj.add_resource_line(cr, uid, sol, False, sol_ids[sol], context=context)
1054
# Update sale order lines
1055
so_to_cancel_ids = []
1056
for sol in sol_to_update:
1057
context['update_or_cancel_line_not_delete'] = sol in sol_not_to_delete
1058
so_to_cancel_id = sol_obj.update_or_cancel_line(cr, uid, sol, sol_to_update[sol], context=context)
1060
so_to_cancel_ids.append(so_to_cancel_id)
1062
if context.get('update_or_cancel_line_not_delete', False):
1063
del context['update_or_cancel_line_not_delete']
1065
# Update the FO state
1066
#for so in so_to_update:
1067
# wf_service.trg_write(uid, 'sale.order', so, cr)
1069
# UF-733: if all tender lines have been compared (have PO Line id), then set the tender to be ready
1070
# for proceeding to other actions (create PO, Done etc)
1071
for tender in tender_obj.browse(cr, uid, list(tender_to_update), context=context):
1072
if tender.internal_state == 'draft':
1074
for line in tender.tender_line_ids:
1075
if line.line_state != 'cancel' and not line.purchase_order_line_id:
1078
tender_obj.write(cr, uid, [tender.id], {'internal_state': 'updated'})
1080
if context.get('fake_unlink'):
1083
return so_to_cancel_ids
1085
def fake_unlink(self, cr, uid, ids, context=None):
1087
Cancel the line if it is linked to a FO line
1089
to_remove = self.cancel_sourcing(cr, uid, ids, context=dict(context, fake_unlink=True))
1092
self.infolog(cr, uid, "The tender line id:%s has been canceled" % tl_id)
1094
return self.unlink(cr, uid, to_remove, context=context)
1096
def ask_unlink(self, cr, uid, ids, context=None):
1098
Ask user if he wants to re-source the needs
1101
wiz_obj = self.pool.get('tender.line.cancel.wizard')
1102
tender_obj = self.pool.get('tender')
1103
exp_sol_obj = self.pool.get('expected.sale.order.line')
1109
if isinstance(ids, (int, long)):
1112
# Check if the line has been already deleted
1113
ids = self.search(cr, uid, [('id', 'in', ids), ('line_state', '!=', 'cancel')], context=context)
1115
raise osv.except_osv(
1117
_('The line has already been canceled - Please refresh the page'),
1121
for line in self.browse(cr, uid, ids, context=context):
1122
tender_id = line.tender_id.id
1127
if line.tender_id.sale_order_id:
1128
exp_sol_ids = exp_sol_obj.search(cr, uid, [
1129
('tender_id', '=', tender_id),
1130
('tender_line_id', '!=', line.id),
1133
tender_so_ids, po_ids, so_ids, sol_nc_ids = tender_obj.sourcing_document_state(cr, uid, [tender_id], context=context)
1134
if line.sale_order_line_id and line.sale_order_line_id.id in sol_nc_ids:
1135
sol_nc_ids.remove(line.sale_order_line_id.id)
1137
if po_ids and not exp_sol_ids and not sol_nc_ids:
1140
if line.sale_order_line_id:
1141
wiz_id = wiz_obj.create(cr, uid, {
1142
'tender_line_id': line.id,
1143
'last_line': last_line,
1145
elif not exp_sol_ids and line.tender_id.sale_order_id:
1146
wiz_id = wiz_obj.create(cr, uid, {
1147
'tender_line_id': line.id,
1149
'last_line': last_line,
1153
return {'type': 'ir.actions.act_window',
1154
'res_model': 'tender.line.cancel.wizard',
1155
'view_type': 'form',
1156
'view_mode': 'form',
1162
wiz_id = wiz_obj.create(cr, uid, {
1163
'tender_line_id': line_id,
1167
return wiz_obj.just_cancel(cr, uid, wiz_id, context=context)
1169
return {'type': 'ir.actions.act_window',
1170
'res_model': 'tender',
1171
'view_type': 'form',
1172
'view_mode': 'form,tree',
1173
'res_id': tender_id,
1225
609
_inherit = 'procurement.order'
1227
def _is_tender_rfq(self, cr, uid, ids, field_name, arg, context=None):
611
def _is_tender(self, cr, uid, ids, field_name, arg, context=None):
1229
tell if the corresponding sale order line is tender/rfq sourcing or not
613
tell if the corresponding sale order line is tender sourcing or not
1232
619
for proc in self.browse(cr, uid, ids, context=context):
1233
result[proc.id] = {'is_tender': False, 'is_rfq': False}
1234
620
for line in proc.sale_order_line_ids:
1235
result[proc.id]['is_tender'] = line.po_cft == 'cft'
1236
result[proc.id]['is_rfq'] = line.po_cft == 'rfq'
621
result[proc.id] = line.po_cft == 'cft'
1240
_columns = {'is_tender': fields.function(_is_tender_rfq, method=True, type='boolean', string='Is Tender', readonly=True, multi='tender_rfq'),
1241
'is_rfq': fields.function(_is_tender_rfq, method=True, type='boolean', string='Is RfQ', readonly=True, multi='tender_rfq'),
625
_columns = {'is_tender': fields.function(_is_tender, method=True, type='boolean', string='Is Tender', readonly=True,),
1242
626
'sale_order_line_ids': fields.one2many('sale.order.line', 'procurement_id', string="Sale Order Lines"),
1243
627
'tender_id': fields.many2one('tender', string='Tender', readonly=True),
1244
'tender_line_id': fields.many2one('tender.line', string='Tender line', readonly=True),
1245
628
'is_tender_done': fields.boolean(string="Tender Closed"),
1246
'rfq_id': fields.many2one('purchase.order', string='RfQ', readonly=True),
1247
'rfq_line_id': fields.many2one('purchase.order.line', string='RfQ line', readonly=True),
1248
'is_rfq_done': fields.boolean(string="RfQ Closed"),
1249
629
'state': fields.selection([('draft','Draft'),
1250
630
('confirmed','Confirmed'),
1251
631
('exception','Exception'),
1254
634
('ready','Ready'),
1255
635
('done','Closed'),
1256
636
('tender', 'Tender'),
1257
('rfq', 'Request for Quotation'),
1258
637
('waiting','Waiting'),], 'State', required=True,
1259
638
help='When a procurement is created the state is set to \'Draft\'.\n If the procurement is confirmed, the state is set to \'Confirmed\'.\
1260
639
\nAfter confirming the state is set to \'Running\'.\n If any exception arises in the order then the state is set to \'Exception\'.\n Once the exception is removed the state becomes \'Ready\'.\n It is in \'Waiting\'. state when the procurement is waiting for another one to finish.'),
1261
'price_unit': fields.float('Unit Price from Tender', digits_compute=dp.get_precision('Purchase Price Computation')),
640
'price_unit': fields.float('Unit Price from Tender', digits_compute= dp.get_precision('Purchase Price')),
1264
'is_tender_done': False,
1265
'is_rfq_done': False,
1268
def no_address_error(self, cr, uid, ids, context=None):
1270
Put the procurement order in exception state with the good error message
1272
for proc in self.browse(cr, uid, ids, context=context):
1273
if proc.supplier and not proc.supplier.address:
1274
self.write(cr, uid, [proc.id], {
1275
'state': 'exception',
1276
'message': _('The supplier "%s" has no address defined!')%(proc.supplier.name,),
1281
def wkf_action_rfq_create(self, cr, uid, ids, context=None):
1283
creation of rfq from procurement workflow
1285
rfq_obj = self.pool.get('purchase.order')
1286
rfq_line_obj = self.pool.get('purchase.order.line')
1287
partner_obj = self.pool.get('res.partner')
1288
prsd_obj = self.pool.get('procurement.request.sourcing.document')
1293
# find the corresponding sale order id for rfq
1294
for proc in self.browse(cr, uid, ids, context=context):
1298
sale_order_line = False
1299
for sol in proc.sale_order_line_ids:
1300
sale_order = sol.order_id
1301
sale_order_line = sol
1305
# UTP-934: If source rfq to different supplier, different rfq must be created, and cannot be using the same rfq
1306
rfq_ids = rfq_obj.search(cr, uid, [('sale_order_id', '=', sale_order.id),('partner_id', '=', proc.supplier.id), ('state', '=', 'draft'), ('rfq_ok', '=', True),], context=context)
1309
# create if not found
1311
supplier = proc.supplier
1312
company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id
1313
pricelist_id = supplier.property_product_pricelist_purchase.id
1314
address_id = partner_obj.address_get(cr, uid, [supplier.id], ['default'])['default']
1316
self.write(cr, uid, [proc.id], {
1317
'message': _('The supplier "%s" has no address defined!')%(supplier.name,),
1321
context['rfq_ok'] = True
1322
rfq_id = rfq_obj.create(cr, uid, {'sale_order_id': sale_order.id,
1323
'categ': sale_order.categ,
1324
'priority': sale_order.priority,
1325
'fiscal_position': supplier.property_account_position and supplier.property_account_position.id or False,
1326
'rfq_delivery_address': partner_obj.address_get(cr, uid, company.partner_id.id, ['delivery'])['delivery'],
1327
'warehouse_id': sale_order.shop_id.warehouse_id.id,
1328
'location_id': proc.location_id.id,
1329
'partner_id': supplier.id,
1330
'partner_address_id': address_id,
1331
'pricelist_id': pricelist_id,
1333
'from_procurement': True,
1334
'order_type': sale_order.order_type,
1335
'origin': sale_order.name,}, context=context)
1337
prsd_obj.chk_create(cr, uid, {
1338
'order_id': sale_order.id,
1339
'sourcing_document_id': rfq_id,
1340
'sourcing_document_model': 'purchase.order',
1341
'sourcing_document_type': 'rfq',
1344
# add a line to the RfQ
1345
rfq_line_id = rfq_line_obj.create(cr, uid, {'product_id': proc.product_id.id,
1346
'comment': sale_order_line.comment,
1347
'name': sale_order_line.name,
1349
'product_qty': proc.product_qty,
1350
'origin': sale_order.name,
1352
'sale_order_line_id': sale_order_line.id,
1353
'location_id': proc.location_id.id,
1354
'product_uom': proc.product_uom.id,
1355
'procurement_id': proc.id,
1356
#'date_planned': proc.date_planned, # function at line level
1359
self.write(cr, uid, ids, {'rfq_id': rfq_id, 'rfq_line_id': rfq_line_id}, context=context)
1361
# log message concerning RfQ creation
1362
rfq_obj.log(cr, uid, rfq_id, "The Request for Quotation '%s' has been created and must be completed before purchase order creation."%rfq_obj.browse(cr, uid, rfq_id, context=context).name, context={'rfq_ok': 1})
1363
self.infolog(cr, uid, "The FO/IR line id:%s has been sourced on order to RfQ line id:%s of the RfQ id:%s" % (
1364
sale_order_line.id, rfq_line_id, rfq_id,
1366
# state of procurement is Tender
1367
self.write(cr, uid, ids, {'state': 'rfq'}, context=context)
642
_defaults = {'is_tender_done': False,}
1371
644
def wkf_action_tender_create(self, cr, uid, ids, context=None):
1398
668
'warehouse_id': sale_order.shop_id.warehouse_id.id,
1399
669
'requested_date': proc.date_planned,
1400
670
}, context=context)
1401
prsd_obj.chk_create(cr, uid, {
1402
'order_id': sale_order.id,
1403
'sourcing_document_id': tender_id,
1404
'sourcing_document_model': 'tender',
1405
'sourcing_document_type': 'tender',
1407
671
# add a line to the tender
1408
tender_line_id = tender_line_obj.create(cr, uid, {'product_id': proc.product_id.id,
1409
'comment': sale_order_line.comment,
1410
'qty': proc.product_qty,
1411
'tender_id': tender_id,
1412
'sale_order_line_id': sale_order_line.id,
1413
'location_id': proc.location_id.id,
1414
'product_uom': proc.product_uom.id,
1415
#'date_planned': proc.date_planned, # function at line level
672
tender_line_obj.create(cr, uid, {'product_id': proc.product_id.id,
673
'comment': sale_order_line.comment,
674
'qty': proc.product_qty,
675
'tender_id': tender_id,
676
'sale_order_line_id': sale_order_line.id,
677
'location_id': proc.location_id.id,
678
'product_uom': proc.product_uom.id,
679
#'date_planned': proc.date_planned, # function at line level
1418
self.write(cr, uid, ids, {'tender_id': tender_id, 'tender_line_id': tender_line_id}, context=context)
682
self.write(cr, uid, ids, {'tender_id': tender_id}, context=context)
1420
684
# log message concerning tender creation
1421
685
tender_obj.log(cr, uid, tender_id, "The tender '%s' has been created and must be completed before purchase order creation."%tender_obj.browse(cr, uid, tender_id, context=context).name)
1422
self.infolog(cr, uid, "The FO/IR line id:%s has been sourced on order to tender line id:%s of the tender id:%s" % (
1423
sale_order_line.id, tender_line_id, tender_id,
1425
686
# state of procurement is Tender
1426
687
self.write(cr, uid, ids, {'state': 'tender'}, context=context)
1531
749
if obj.state == 'rfq_updated' and not obj.valid_till:
1535
752
_columns = {'tender_id': fields.many2one('tender', string="Tender", readonly=True),
1536
'rfq_delivery_address': fields.many2one('res.partner.address', string='Delivery address'),
1537
753
'origin_tender_id': fields.many2one('tender', string='Tender', readonly=True),
1538
'from_procurement': fields.boolean(string='RfQ created by a procurement order'),
1539
754
'rfq_ok': fields.boolean(string='Is RfQ ?'),
1540
755
'state': fields.selection(PURCHASE_ORDER_STATE_SELECTION, 'State', readonly=True, help="The state of the purchase order or the quotation request. A quotation is a purchase order in a 'Draft' state. Then the order has to be confirmed by the user, the state switch to 'Confirmed'. Then the supplier must confirm the order to change the state to 'Approved'. When the purchase order is paid and received, the state becomes 'Closed'. If a cancel action occurs in the invoice or in the reception of goods, the state becomes in exception.", select=True),
1541
756
'valid_till': fields.date(string='Valid Till', states={'rfq_updated': [('required', True), ('readonly', True)], 'rfq_sent':[('required',False), ('readonly', False),]}, readonly=True,),
1542
757
# add readonly when state is Done
1543
'sale_order_id': fields.many2one('sale.order', string='Link between RfQ and FO', readonly=True),
1547
761
'rfq_ok': lambda self, cr, uid, c: c.get('rfq_ok', False),
762
'name': lambda obj, cr, uid, c: obj.pool.get('ir.sequence').get(cr, uid, c.get('rfq_ok', False) and 'rfq' or 'purchase.order'),
1550
765
_constraints = [
1551
766
(_check_valid_till,
1552
767
'You must specify a Valid Till date.',
1553
768
['valid_till']),]
1555
def default_get(self, cr, uid, fields, context=None):
1559
# Object declaration
1560
partner_obj = self.pool.get('res.partner')
1561
user_obj = self.pool.get('res.users')
1563
res = super(purchase_order, self).default_get(cr, uid, fields, context=context)
1565
# Get the delivery address
1566
company = user_obj.browse(cr, uid, uid, context=context).company_id
1567
res['rfq_delivery_address'] = partner_obj.address_get(cr, uid, company.partner_id.id, ['delivery'])['delivery']
1571
def create(self, cr, uid, vals, context=None):
1573
Set the reference at this step
1577
if context.get('rfq_ok', False) and not vals.get('name', False):
1578
vals.update({'name': self.pool.get('ir.sequence').get(cr, uid, 'rfq')})
1579
elif not vals.get('name', False):
1580
vals.update({'name': self.pool.get('ir.sequence').get(cr, uid, 'purchase.order')})
1582
return super(purchase_order, self).create(cr, uid, vals, context=context)
1584
770
def unlink(self, cr, uid, ids, context=None):
1677
831
if not rfq.valid_till:
1678
832
raise osv.except_osv(_('Error'), _('You must specify a Valid Till date.'))
1680
if rfq.rfq_ok and rfq.tender_id:
1681
for line in rfq.order_line:
1682
if not line.tender_line_id:
1683
tl_ids = tl_obj.search(cr, uid, [('product_id', '=', line.product_id.id), ('tender_id', '=', rfq.tender_id.id), ('line_state', '=', 'draft')], context=context)
1687
tl_vals = {'product_id': line.product_id.id,
1688
'product_uom': line.product_uom.id,
1689
'qty': line.product_qty,
1690
'tender_id': rfq.tender_id.id,
1691
'created_by_rfq': True}
1692
tl_id = tl_obj.create(cr, uid, tl_vals, context=context)
1693
self.infolog(cr, uid, "The tender line id:%s has been created by the RfQ line id:%s" % (
1696
line_obj.write(cr, uid, [line.id], {'tender_line_id': tl_id}, context=context)
1698
line_ids = line_obj.search(cr, uid, [
1699
('order_id', '=', rfq.id),
1700
('price_unit', '=', 0.00),
1701
], count=True, context=context)
1703
raise osv.except_osv(
1705
_('''You cannot update an RfQ with lines without unit
1706
price. Please set unit price on these lines or cancel them'''),
1709
834
wf_service.trg_validate(uid, 'purchase.order', rfq.id, 'rfq_updated', cr)
1710
self.infolog(cr, uid, "The RfQ id:%s has been updated" % rfq.id)
1713
'type': 'ir.actions.act_window',
1714
'res_model': 'purchase.order',
1715
'view_mode': 'form,tree,graph,calendar',
1716
'view_type': 'form',
1718
'context': {'rfq_ok': True, 'search_default_draft_rfq': 1},
1719
'domain': [('rfq_ok', '=', True)],
836
return {'type': 'ir.actions.act_window',
837
'res_model': 'purchase.order',
838
'view_mode': 'form,tree,graph,calendar',
841
'context': {'rfq_ok': True, 'search_default_draft_rfq': 1,},
842
'domain': [('rfq_ok', '=', True)],
1723
845
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
1747
869
if context.get('rfq_ok', False):
1748
870
# the title of the screen depends on po type
1749
871
form = etree.fromstring(result['arch'])
1751
fields = form.xpath('//form[@string="%s"]' % _('Purchase Order'))
872
fields = form.xpath('//form[@string="Purchase Order"]')
1752
873
for field in fields:
1753
field.set('string', _("Request for Quotation"))
1755
fields2 = form.xpath('//page[@string="%s"]' % _('Purchase Order'))
1756
for field2 in fields2:
1757
field2.set('string', _("Request for Quotation"))
874
field.set('string', "Requests for Quotation")
1759
875
result['arch'] = etree.tostring(form)
1763
def wkf_act_rfq_done(self, cr, uid, ids, context=None):
1765
Set the state to done and update the price unit in the procurement order
1767
wf_service = netsvc.LocalService("workflow")
1768
proc_obj = self.pool.get('procurement.order')
1769
date_tools = self.pool.get('date.tools')
1770
fields_tools = self.pool.get('fields.tools')
1771
db_date_format = date_tools.get_db_date_format(cr, uid, context=context)
1773
if isinstance(ids, (int, long)):
1776
for rfq in self.browse(cr, uid, ids, context=context):
1777
self.infolog(cr, uid, "The RfQ id:%s has been closed" % rfq.id)
1778
if rfq.from_procurement:
1779
for line in rfq.order_line:
1780
if line.procurement_id:
1781
vals = {'price_unit': line.price_unit}
1782
self.pool.get('procurement.order').write(cr, uid, [line.procurement_id.id], vals, context=context)
1783
# elif not rfq.tender_id:
1784
# prep_lt = fields_tools.get_field_from_company(cr, uid, object='sale.order', field='preparation_lead_time', context=context)
1785
# rts = datetime.strptime(rfq.sale_order_id.ready_to_ship_date, db_date_format)
1786
# rts = rts - relativedelta(days=prep_lt or 0)
1787
# rts = rts.strftime(db_date_format)
1788
# vals = {'product_id': line.product_id.id,
1789
# 'product_uom': line.product_uom.id,
1790
# 'product_uos': line.product_uom.id,
1791
# 'product_qty': line.product_qty,
1792
# 'product_uos_qty': line.product_qty,
1793
# 'price_unit': line.price_unit,
1794
# 'procure_method': 'make_to_order',
1797
# 'rfq_line_id': line.id,
1798
# 'is_tender': False,
1799
# 'tender_id': False,
1800
# 'tender_line_id': False,
1801
# 'date_planned': rts,
1802
# 'origin': rfq.sale_order_id.name,
1803
# 'supplier': rfq.partner_id.id,
1804
# 'name': '[%s] %s' % (line.product_id.default_code, line.product_id.name),
1805
# 'location_id': rfq.sale_order_id.warehouse_id.lot_stock_id.id,
1808
# proc_id = proc_obj.create(cr, uid, vals, context=context)
1809
# wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr)
1810
# wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_check', cr)
1811
self.create_extra_lines_on_fo(cr, uid, ids, context=context)
1814
return self.write(cr, uid, ids, {'state': 'done'}, context=context)
1816
879
purchase_order()
1902
927
pricelist_partnerinfo()
1905
class tender_line_cancel_wizard(osv.osv_memory):
1906
_name = 'tender.line.cancel.wizard'
1909
'tender_line_id': fields.many2one('tender.line', string='Tender line', required=True),
1910
'only_exp': fields.boolean(
1911
string='Only added lines',
1913
'last_line': fields.boolean(
1914
string='Last line of the FO to source',
1919
def just_cancel(self, cr, uid, ids, context=None):
1924
line_obj = self.pool.get('tender.line')
1925
tender_obj = self.pool.get('tender')
1926
data_obj = self.pool.get('ir.model.data')
1927
so_obj = self.pool.get('sale.order')
1928
tender_wiz_obj = self.pool.get('tender.cancel.wizard')
1929
wf_service = netsvc.LocalService("workflow")
1935
if isinstance(ids, (int, long)):
1941
for wiz in self.browse(cr, uid, ids, context=context):
1942
tender_ids.add(wiz.tender_line_id.tender_id.id)
1943
line_ids.append(wiz.tender_line_id.id)
1944
if wiz.tender_line_id.tender_id.sale_order_id:
1945
so_ids.add(wiz.tender_line_id.tender_id.sale_order_id.id)
1947
if context.get('has_to_be_resourced'):
1948
line_obj.write(cr, uid, line_ids, {'has_to_be_resourced': True}, context=context)
1950
line_obj.fake_unlink(cr, uid, line_ids, context=context)
1952
tender_so_ids, po_ids, so_ids, sol_nc_ids = tender_obj.sourcing_document_state(cr, uid, list(tender_ids), context=context)
1953
for po_id in po_ids:
1954
wf_service.trg_write(uid, 'purchase.order', po_id, cr)
1956
so_to_cancel_ids = []
1958
for so_id in tender_so_ids:
1959
if so_obj._get_ready_to_cancel(cr, uid, so_id, context=context)[so_id]:
1960
so_to_cancel_ids.append(so_id)
1962
if so_to_cancel_ids:
1963
# Ask user to choose what must be done on the FO/IR
1965
'from_tender': True,
1966
'tender_ids': list(tender_ids),
1968
return so_obj.open_cancel_wizard(cr, uid, set(so_to_cancel_ids), context=context)
1970
return tender_obj.check_empty_tender(cr, uid, list(tender_ids), context=context)
1972
def cancel_and_resource(self, cr, uid, ids, context=None):
1974
Flag the line to be re-sourced and run cancel method
1980
context['has_to_be_resourced'] = True
1982
return self.just_cancel(cr, uid, ids, context=context)
1984
tender_line_cancel_wizard()
1987
class tender_cancel_wizard(osv.osv_memory):
1988
_name = 'tender.cancel.wizard'
1991
'tender_id': fields.many2one('tender', string='Tender', required=True),
1992
'not_draft': fields.boolean(string='Tender not draft'),
1993
'no_need': fields.boolean(string='No need'),
1996
def just_cancel(self, cr, uid, ids, context=None):
1998
Just cancel the wizard and the lines
2001
line_obj = self.pool.get('tender.line')
2002
so_obj = self.pool.get('sale.order')
2008
if isinstance(ids, (int, long)):
2011
wf_service = netsvc.LocalService("workflow")
2016
for wiz in self.browse(cr, uid, ids, context=context):
2017
tender_ids.append(wiz.tender_id.id)
2018
if wiz.tender_id.sale_order_id and wiz.tender_id.sale_order_id.id not in so_ids:
2019
so_ids.append(wiz.tender_id.sale_order_id.id)
2020
for line in wiz.tender_id.tender_line_ids:
2021
line_ids.append(line.id)
2022
for rfq in wiz.tender_id.rfq_ids:
2023
rfq_ids.append(rfq.id)
2025
if context.get('has_to_be_resourced'):
2026
line_obj.write(cr, uid, line_ids, {'has_to_be_resourced': True}, context=context)
2028
line_obj.fake_unlink(cr, uid, line_ids, context=context)
2031
wf_service.trg_validate(uid, 'purchase.order', rfq, 'purchase_cancel', cr)
2033
for tender in tender_ids:
2034
wf_service.trg_validate(uid, 'tender', tender, 'tender_cancel', cr)
2036
so_to_cancel_ids = []
2038
for so_id in so_ids:
2039
if so_obj._get_ready_to_cancel(cr, uid, so_id, context=context)[so_id]:
2040
so_to_cancel_ids.append(so_id)
2042
if so_to_cancel_ids:
2043
# Ask user to choose what must be done on the FO/IR
2044
return so_obj.open_cancel_wizard(cr, uid, set(so_to_cancel_ids), context=context)
2046
return {'type': 'ir.actions.act_window_close'}
2048
def cancel_and_resource(self, cr, uid, ids, context=None):
2050
Flag the line to be re-sourced and run cancel method
2056
context['has_to_be_resourced'] = True
2058
return self.just_cancel(cr, uid, ids, context=context)
2060
def close_window(self, cr, uid, ids, context=None):
2062
Just close the wizard and reload the tender
2064
return {'type': 'ir.actions.act_window_close'}
2066
tender_cancel_wizard()
2069
class expected_sale_order_line(osv.osv):
2070
_inherit = 'expected.sale.order.line'
2073
'tender_line_id': fields.many2one(
2075
string='Tender line',
2078
'tender_id': fields.related(
2087
expected_sale_order_line()
2090
929
class ir_values(osv.osv):
2091
930
_name = 'ir.values'
2092
931
_inherit = 'ir.values'