123
96
'notes': fields.text('Notes'),
124
97
'internal_state': fields.selection([('draft', 'Draft'),('updated', 'Rfq Updated'), ], string="Internal State", readonly=True),
125
98
'rfq_name_list': fields.function(_vals_get, method=True, string='RfQs Ref', type='char', readonly=True, store=False, multi='get_vals',),
126
'product_id': fields.related('tender_line_ids', 'product_id', type='many2one', relation='product.product', string='Product'),
127
'delivery_address': fields.many2one('res.partner.address', string='Delivery address', required=True),
128
'tender_from_fo': fields.function(_is_tender_from_fo, method=True, type='boolean', string='Is tender from FO ?',),
99
'product_id': fields.related('tender_line_ids', 'product_id', type='many2one', relation='product.product', string='Product')
131
_defaults = {'categ': 'other',
102
_defaults = {'state': 'draft',
133
103
'internal_state': 'draft',
104
'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'tender'),
134
105
'company_id': lambda obj, cr, uid, context: obj.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id,
135
106
'creator': lambda obj, cr, uid, context: uid,
136
107
'creation_date': lambda *a: time.strftime('%Y-%m-%d'),
142
113
_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
self._check_service(cr, uid, ids, vals, context=context)
230
return super(tender, self).write(cr, uid, ids, vals, context=context)
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.
237
if isinstance(ids, (int, long)):
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'
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
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)))
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)})
258
return {'warning': message}
260
115
def onchange_warehouse(self, cr, uid, ids, warehouse_id, context=None):
262
117
on_change function for the warehouse
278
133
pol_obj = self.pool.get('purchase.order.line')
279
134
partner_obj = self.pool.get('res.partner')
280
135
pricelist_obj = self.pool.get('product.pricelist')
281
obj_data = self.pool.get('ir.model.data')
283
136
# no suppliers -> raise error
284
137
for tender in self.browse(cr, uid, ids, context=context):
285
138
# check some supplier have been selected
286
139
if not tender.supplier_ids:
287
140
raise osv.except_osv(_('Warning !'), _('You must select at least one supplier!'))
288
#utp-315: check that the suppliers are not inactive (I use a SQL request because the inactive partner are ignored with the browse)
290
select tsr.supplier_id, rp.name, rp.active
291
from tender_supplier_rel tsr
292
left join res_partner rp
293
on tsr.supplier_id = rp.id
294
where tsr.tender_id=%s
297
cr.execute(sql, (ids[0],))
298
inactive_supplier_ids = cr.dictfetchall()
299
if any(inactive_supplier_ids):
300
raise osv.except_osv(_('Warning !'), _("You can't have inactive supplier! Please remove: %s"
301
) % ' ,'.join([partner['name'] for partner in inactive_supplier_ids]))
302
141
# check some products have been selected
303
tender_line_ids = self.pool.get('tender.line').search(cr, uid, [('tender_id', '=', tender.id), ('line_state', '!=', 'cancel')], context=context)
142
tender_line_ids = self.pool.get('tender.line').search(cr, uid, [('tender_id', '=', tender.id)], context=context)
304
143
if not tender_line_ids:
305
144
raise osv.except_osv(_('Warning !'), _('You must select at least one product!'))
306
145
for supplier in tender.supplier_ids:
307
146
# create a purchase order for each supplier
308
address_id = partner_obj.address_get(cr, uid, [supplier.id], ['default'])['default']
147
address_id = partner_obj.address_get(cr, uid, [supplier.id], ['delivery'])['delivery']
309
148
if not address_id:
310
149
raise osv.except_osv(_('Warning !'), _('The supplier "%s" has no address defined!')%(supplier.name,))
311
150
pricelist_id = supplier.property_product_pricelist_purchase.id
312
values = {'origin': tender.sale_order_id and tender.sale_order_id.name + ';' + tender.name or tender.name,
151
values = {'name': self.pool.get('ir.sequence').get(cr, uid, 'rfq'),
152
'origin': tender.sale_order_id and tender.sale_order_id.name + '/' + tender.name or tender.name,
314
154
'partner_id': supplier.id,
315
155
'partner_address_id': address_id,
391
215
raise osv.except_osv(_('Warning !'), _("Generated RfQs must be Updated or Cancelled."))
393
217
# integrity check, all lines must have purchase_order_line_id
394
if not all([line.purchase_order_line_id.id for line in tender.tender_line_ids if line.line_state != 'cancel']):
218
if not all([line.purchase_order_line_id.id for line in tender.tender_line_ids]):
395
219
raise osv.except_osv(_('Error !'), _('All tender lines must have been compared!'))
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
403
wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_cancel', cr)
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',
422
'tender_id': tender.id,
423
'tender_line_id': line.id,
424
'price_unit': line.price_unit,
426
'origin': tender.sale_order_id.name,
427
'supplier': line.purchase_order_line_id.order_id.partner_id.id,
428
'name': '[%s] %s' % (line.product_id.default_code, line.product_id.name),
429
'location_id': tender.sale_order_id.warehouse_id.lot_stock_id.id,
432
proc_id = proc_obj.create(cr, uid, create_vals, context=context)
433
wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr)
434
wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_check', cr)
436
221
# update product supplierinfo and pricelist
437
222
self.update_supplier_info(cr, uid, ids, context=context, integrity_test=False,)
438
223
# change tender state
732
482
_SELECTION_TENDER_STATE = [('draft', 'Draft'),('comparison', 'Comparison'), ('done', 'Closed'),]
734
def on_product_change(self, cr, uid, id, product_id, uom_id, product_qty, context=None):
484
def on_product_change(self, cr, uid, id, product_id, context=None):
736
486
product is changed, we update the UoM
741
488
prod_obj = self.pool.get('product.product')
742
489
result = {'value': {}}
744
# Test the compatibility of the product with a tender
745
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)
749
product = prod_obj.browse(cr, uid, product_id, context=context)
750
result['value']['product_uom'] = product.uom_id.id
751
result['value']['text_error'] = False
752
result['value']['to_correct_ok'] = False
754
res_qty = self.onchange_uom_qty(cr, uid, id, uom_id or result.get('value', {}).get('product_uom',False), product_qty)
755
result['value']['qty'] = res_qty.get('value', {}).get('qty', product_qty)
758
result['value']['product_uom'] = uom_id
491
result['value']['product_uom'] = prod_obj.browse(cr, uid, product_id, context=context).uom_po_id.id
762
def onchange_uom_qty(self, cr, uid, ids, uom_id, qty):
764
Check round of qty according to the UoM
769
res = self.pool.get('product.uom')._change_round_up_qty(cr, uid, uom_id, qty, 'qty', result=res)
773
495
def _get_total_price(self, cr, uid, ids, field_name, arg, context=None):
806
528
_columns = {'product_id': fields.many2one('product.product', string="Product", required=True),
807
529
'qty': fields.float(string="Qty", required=True),
808
'tender_id': fields.many2one('tender', string="Tender", required=True, ondelete='cascade'),
530
'tender_id': fields.many2one('tender', string="Tender", required=True),
809
531
'purchase_order_line_id': fields.many2one('purchase.order.line', string="Related RfQ line", readonly=True),
810
532
'sale_order_line_id': fields.many2one('sale.order.line', string="Sale Order Line"),
811
533
'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
812
534
'date_planned': fields.related('tender_id', 'requested_date', type='date', string='Requested Date', store=False,),
814
536
'supplier_id': fields.related('purchase_order_line_id', 'order_id', 'partner_id', type='many2one', relation='res.partner', string="Supplier", readonly=True),
815
'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!
816
'total_price': fields.function(_get_total_price, method=True, type='float', string="Total Price", digits_compute=dp.get_precision('Purchase Price'), multi='total'),
537
'price_unit': fields.related('purchase_order_line_id', 'price_unit', type="float", string="Price unit", readonly=True),
538
'total_price': fields.function(_get_total_price, method=True, type='float', string="Total Price", multi='total'),
817
539
'currency_id': fields.function(_get_total_price, method=True, type='many2one', relation='res.currency', string='Cur.', multi='total'),
818
'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'),
540
'func_total_price': fields.function(_get_total_price, method=True, type='float', string="Func. Total Price", multi='total'),
819
541
'func_currency_id': fields.function(_get_total_price, method=True, type='many2one', relation='res.currency', string='Func. Cur.', multi='total'),
820
542
'purchase_order_id': fields.related('purchase_order_line_id', 'order_id', type='many2one', relation='purchase.order', string="Related RfQ", readonly=True,),
821
'purchase_order_line_number': fields.related('purchase_order_line_id', 'line_number', type="char", string="Related Line Number", readonly=True,),
543
'purchase_order_line_number': fields.related('purchase_order_line_id', 'line_number', type="integer", string="Related Line Number", readonly=True,),
822
544
'state': fields.related('tender_id', 'state', type="selection", selection=_SELECTION_TENDER_STATE, string="State",),
823
'line_state': fields.selection([('draft','Draft'), ('cancel', 'Canceled'), ('done', 'Done')], string='State', readonly=True),
824
'comment': fields.char(size=128, string='Comment'),
825
'has_to_be_resourced': fields.boolean(string='Has to be resourced'),
826
'created_by_rfq': fields.boolean(string='Created by RfQ'),
828
546
_defaults = {'qty': lambda *a: 1.0,
829
547
'state': lambda *a: 'draft',
830
'line_state': lambda *a: 'draft',
833
def _check_restriction_line(self, cr, uid, ids, context=None):
835
Check if there is no restrictive products in lines
837
if isinstance(ids, (int, long)):
840
for line in self.browse(cr, uid, ids, context=context):
841
if line.tender_id and line.product_id:
842
if not self.pool.get('product.product')._get_restriction_error(cr, uid, line.product_id.id, vals={'constraints': ['external']}, context=context):
848
# ('product_qty_check', 'CHECK( qty > 0 )', 'Product Quantity must be greater than zero.'),
852
def copy(self, cr, uid, id, default=None, context=None):
856
if not 'created_by_rf' in default:
857
default['created_by_rfq'] = False
859
return super(tender_line, self).copy(cr, uid, id, default, context=context)
861
def cancel_sourcing(self,cr, uid, ids, context=None):
863
Cancel the line and re-source the FO line
866
sol_obj = self.pool.get('sale.order.line')
867
uom_obj = self.pool.get('product.uom')
868
tender_obj = self.pool.get('tender')
871
wf_service = netsvc.LocalService("workflow")
877
tender_to_update = set()
879
for line in self.browse(cr, uid, ids, context=context):
880
tender_to_update.add(line.tender_id.id)
881
if line.sale_order_line_id:
882
so_to_update.add(line.sale_order_line_id.order_id.id)
883
to_cancel.append(line.id)
884
# Get the ID and the product qty of the FO line to re-source
885
diff_qty = uom_obj._compute_qty(cr, uid, line.product_uom.id, line.qty, line.sale_order_line_id.product_uom.id)
887
if line.has_to_be_resourced:
888
sol_ids.update({line.sale_order_line_id.id: diff_qty})
890
sol_to_update.setdefault(line.sale_order_line_id.id, 0.00)
891
sol_to_update[line.sale_order_line_id.id] += diff_qty
892
elif line.tender_id.state == 'draft':
893
to_remove.append(line.id)
895
to_cancel.append(line.id)
898
self.write(cr, uid, to_cancel, {'line_state': 'cancel'}, context=context)
902
sol_obj.add_resource_line(cr, uid, sol, False, sol_ids[sol], context=context)
904
# Update sale order lines
905
for sol in sol_to_update:
906
sol_obj.update_or_cancel_line(cr, uid, sol, sol_to_update[sol], context=context)
908
# Update the FO state
909
for so in so_to_update:
910
wf_service.trg_write(uid, 'sale.order', so, cr)
912
# UF-733: if all tender lines have been compared (have PO Line id), then set the tender to be ready
913
# for proceeding to other actions (create PO, Done etc)
914
for tender in tender_obj.browse(cr, uid, list(tender_to_update), context=context):
915
if tender.internal_state == 'draft':
917
for line in tender.tender_line_ids:
918
if line.line_state != 'cancel' and not line.purchase_order_line_id:
921
tender_obj.write(cr, uid, [tender.id], {'internal_state': 'updated'})
923
if context.get('fake_unlink'):
928
def fake_unlink(self, cr, uid, ids, context=None):
930
Cancel the line if it is linked to a FO line
932
to_remove = self.cancel_sourcing(cr, uid, ids, context=dict(context, fake_unlink=True))
934
return self.unlink(cr, uid, to_remove, context=context)
936
def ask_unlink(self, cr, uid, ids, context=None):
938
Ask user if he wants to re-source the needs
941
wiz_obj = self.pool.get('tender.line.cancel.wizard')
947
if isinstance(ids, (int, long)):
951
for line in self.browse(cr, uid, ids, context=context):
952
tender_id = line.tender_id.id
953
if line.sale_order_line_id:
954
wiz_id = wiz_obj.create(cr, uid, {'tender_line_id': line.id}, context=context)
956
return {'type': 'ir.actions.act_window',
957
'res_model': 'tender.line.cancel.wizard',
964
self.fake_unlink(cr, uid, ids, context=context)
966
return {'type': 'ir.actions.act_window',
967
'res_model': 'tender',
969
'view_mode': 'form,tree',
1022
597
_inherit = 'procurement.order'
1024
def _is_tender_rfq(self, cr, uid, ids, field_name, arg, context=None):
599
def _is_tender(self, cr, uid, ids, field_name, arg, context=None):
1026
tell if the corresponding sale order line is tender/rfq sourcing or not
601
tell if the corresponding sale order line is tender sourcing or not
1029
607
for proc in self.browse(cr, uid, ids, context=context):
1030
result[proc.id] = {'is_tender': False, 'is_rfq': False}
1031
608
for line in proc.sale_order_line_ids:
1032
result[proc.id]['is_tender'] = line.po_cft == 'cft'
1033
result[proc.id]['is_rfq'] = line.po_cft == 'rfq'
609
result[proc.id] = line.po_cft == 'cft'
1037
_columns = {'is_tender': fields.function(_is_tender_rfq, method=True, type='boolean', string='Is Tender', readonly=True, multi='tender_rfq'),
1038
'is_rfq': fields.function(_is_tender_rfq, method=True, type='boolean', string='Is RfQ', readonly=True, multi='tender_rfq'),
613
_columns = {'is_tender': fields.function(_is_tender, method=True, type='boolean', string='Is Tender', readonly=True,),
1039
614
'sale_order_line_ids': fields.one2many('sale.order.line', 'procurement_id', string="Sale Order Lines"),
1040
615
'tender_id': fields.many2one('tender', string='Tender', readonly=True),
1041
'tender_line_id': fields.many2one('tender.line', string='Tender line', readonly=True),
1042
616
'is_tender_done': fields.boolean(string="Tender Closed"),
1043
'rfq_id': fields.many2one('purchase.order', string='RfQ', readonly=True),
1044
'rfq_line_id': fields.many2one('purchase.order.line', string='RfQ line', readonly=True),
1045
'is_rfq_done': fields.boolean(string="RfQ Closed"),
1046
617
'state': fields.selection([('draft','Draft'),
1047
618
('confirmed','Confirmed'),
1048
619
('exception','Exception'),
1051
622
('ready','Ready'),
1052
623
('done','Closed'),
1053
624
('tender', 'Tender'),
1054
('rfq', 'Request for Quotation'),
1055
625
('waiting','Waiting'),], 'State', required=True,
1056
626
help='When a procurement is created the state is set to \'Draft\'.\n If the procurement is confirmed, the state is set to \'Confirmed\'.\
1057
627
\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.'),
1058
'price_unit': fields.float('Unit Price from Tender', digits_compute=dp.get_precision('Purchase Price Computation')),
628
'price_unit': fields.float('Unit Price from Tender', digits_compute= dp.get_precision('Purchase Price')),
1061
'is_tender_done': False,
1062
'is_rfq_done': False,
1065
def wkf_action_rfq_create(self, cr, uid, ids, context=None):
1067
creation of rfq from procurement workflow
1069
rfq_obj = self.pool.get('purchase.order')
1070
rfq_line_obj = self.pool.get('purchase.order.line')
1071
partner_obj = self.pool.get('res.partner')
1076
# find the corresponding sale order id for rfq
1077
for proc in self.browse(cr, uid, ids, context=context):
1081
sale_order_line = False
1082
for sol in proc.sale_order_line_ids:
1083
sale_order = sol.order_id
1084
sale_order_line = sol
1088
# UTP-934: If source rfq to different supplier, different rfq must be created, and cannot be using the same rfq
1089
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)
1092
# create if not found
1094
supplier = proc.supplier
1095
company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id
1096
pricelist_id = supplier.property_product_pricelist_purchase.id
1097
address_id = partner_obj.address_get(cr, uid, [supplier.id], ['default'])['default']
1099
raise osv.except_osv(_('Warning !'), _('The supplier "%s" has no address defined!')%(supplier.name,))
1101
context['rfq_ok'] = True
1102
rfq_id = rfq_obj.create(cr, uid, {'sale_order_id': sale_order.id,
1103
'categ': sale_order.categ,
1104
'priority': sale_order.priority,
1105
'fiscal_position': supplier.property_account_position and supplier.property_account_position.id or False,
1106
'rfq_delivery_address': partner_obj.address_get(cr, uid, company.partner_id.id, ['delivery'])['delivery'],
1107
'warehouse_id': sale_order.shop_id.warehouse_id.id,
1108
'location_id': proc.location_id.id,
1109
'partner_id': supplier.id,
1110
'partner_address_id': address_id,
1111
'pricelist_id': pricelist_id,
1113
'from_procurement': True,
1114
'order_type': sale_order.order_type,
1115
'origin': sale_order.name,}, context=context)
1117
# add a line to the RfQ
1118
rfq_line_id = rfq_line_obj.create(cr, uid, {'product_id': proc.product_id.id,
1119
'comment': sale_order_line.comment,
1120
'name': sale_order_line.name,
1122
'product_qty': proc.product_qty,
1123
'origin': sale_order.name,
1125
'sale_order_line_id': sale_order_line.id,
1126
'location_id': proc.location_id.id,
1127
'product_uom': proc.product_uom.id,
1128
'procurement_id': proc.id,
1129
#'date_planned': proc.date_planned, # function at line level
1132
self.write(cr, uid, ids, {'rfq_id': rfq_id, 'rfq_line_id': rfq_line_id}, context=context)
1134
# log message concerning RfQ creation
1135
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})
1136
# state of procurement is Tender
1137
self.write(cr, uid, ids, {'state': 'rfq'}, context=context)
630
_defaults = {'is_tender_done': False,}
1141
632
def wkf_action_tender_create(self, cr, uid, ids, context=None):
1221
695
data = self.read(cr, uid, ids, ['so_back_update_dest_po_id_procurement_order'], context=context)
1222
696
if not data[0]['so_back_update_dest_po_id_procurement_order']:
1223
697
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)
698
if self.browse(cr, uid, ids[0], context=context).is_tender:
699
wf_service = netsvc.LocalService("workflow")
700
wf_service.trg_validate(uid, 'purchase.order', result, 'purchase_confirm', cr)
703
def create_po_hook(self, cr, uid, ids, context=None, *args, **kwargs):
705
if the procurement corresponds to a tender, the created po is confirmed but not validated
707
po_obj = self.pool.get('purchase.order')
708
procurement = kwargs['procurement']
709
purchase_id = super(procurement_order, self).create_po_hook(cr, uid, ids, context=context, *args, **kwargs)
712
if procurement.is_tender:
713
wf_service = netsvc.LocalService("workflow")
714
wf_service.trg_validate(uid, 'purchase.order', purchase_id, 'purchase_confirm', cr)
1226
717
def po_values_hook(self, cr, uid, ids, context=None, *args, **kwargs):
1228
719
data for the purchase order creation
1230
721
values = super(procurement_order, self).po_values_hook(cr, uid, ids, context=context, *args, **kwargs)
1231
722
procurement = kwargs['procurement']
1233
values['partner_address_id'] = self.pool.get('res.partner').address_get(cr, uid, [values['partner_id']], ['default'])['default']
1235
# set tender link in purchase order
1236
if procurement.tender_id:
1237
values['origin_tender_id'] = procurement.tender_id.id
1239
# set rfq link in purchase order
1240
if procurement.rfq_id:
1241
values['origin_rfq_id'] = procurement.rfq_id.id
1243
values['date_planned'] = procurement.date_planned
1245
if procurement.product_id:
1246
if procurement.product_id.type == 'consu':
1247
values['location_id'] = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock_override', 'stock_location_non_stockable')[1]
1248
elif procurement.product_id.type == 'service_recep':
1249
values['location_id'] = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_config_locations', 'stock_location_service')[1]
1251
wh_ids = self.pool.get('stock.warehouse').search(cr, uid, [])
1253
values['location_id'] = self.pool.get('stock.warehouse').browse(cr, uid, wh_ids[0]).lot_input_id.id
1255
values['location_id'] = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_config_locations', 'stock_location_service')[1]
724
values['date_planned'] = procurement.date_planned
1272
741
if obj.state == 'rfq_updated' and not obj.valid_till:
1276
744
_columns = {'tender_id': fields.many2one('tender', string="Tender", readonly=True),
1277
'rfq_delivery_address': fields.many2one('res.partner.address', string='Delivery address'),
1278
745
'origin_tender_id': fields.many2one('tender', string='Tender', readonly=True),
1279
'from_procurement': fields.boolean(string='RfQ created by a procurement order'),
1280
746
'rfq_ok': fields.boolean(string='Is RfQ ?'),
1281
747
'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),
1282
748
'valid_till': fields.date(string='Valid Till', states={'rfq_updated': [('required', True), ('readonly', True)], 'rfq_sent':[('required',False), ('readonly', False),]}, readonly=True,),
1283
749
# add readonly when state is Done
1284
'sale_order_id': fields.many2one('sale.order', string='Link between RfQ and FO', readonly=True),
1288
753
'rfq_ok': lambda self, cr, uid, c: c.get('rfq_ok', False),
754
'name': lambda obj, cr, uid, c: obj.pool.get('ir.sequence').get(cr, uid, c.get('rfq_ok', False) and 'rfq' or 'purchase.order'),
1291
757
_constraints = [
1292
758
(_check_valid_till,
1293
759
'You must specify a Valid Till date.',
1294
760
['valid_till']),]
1296
def default_get(self, cr, uid, fields, context=None):
1300
# Object declaration
1301
partner_obj = self.pool.get('res.partner')
1302
user_obj = self.pool.get('res.users')
1304
res = super(purchase_order, self).default_get(cr, uid, fields, context=context)
1306
# Get the delivery address
1307
company = user_obj.browse(cr, uid, uid, context=context).company_id
1308
res['rfq_delivery_address'] = partner_obj.address_get(cr, uid, company.partner_id.id, ['delivery'])['delivery']
1312
def create(self, cr, uid, vals, context=None):
1314
Set the reference at this step
1318
if context.get('rfq_ok', False) and not vals.get('name', False):
1319
vals.update({'name': self.pool.get('ir.sequence').get(cr, uid, 'rfq')})
1320
elif not vals.get('name', False):
1321
vals.update({'name': self.pool.get('ir.sequence').get(cr, uid, 'purchase.order')})
1323
return super(purchase_order, self).create(cr, uid, vals, context=context)
1325
762
def unlink(self, cr, uid, ids, context=None):
1407
823
if not rfq.valid_till:
1408
824
raise osv.except_osv(_('Error'), _('You must specify a Valid Till date.'))
1410
if rfq.rfq_ok and rfq.tender_id:
1411
for line in rfq.order_line:
1412
if not line.tender_line_id:
1413
tl_ids = tl_obj.search(cr, uid, [('product_id', '=', line.product_id.id), ('tender_id', '=', rfq.tender_id.id), ('line_state', '=', 'draft')], context=context)
1417
tl_vals = {'product_id': line.product_id.id,
1418
'product_uom': line.product_uom.id,
1419
'qty': line.product_qty,
1420
'tender_id': rfq.tender_id.id,
1421
'created_by_rfq': True}
1422
tl_id = tl_obj.create(cr, uid, tl_vals, context=context)
1423
line_obj.write(cr, uid, [line.id], {'tender_line_id': tl_id}, context=context)
1425
826
wf_service.trg_validate(uid, 'purchase.order', rfq.id, 'rfq_updated', cr)
1428
'type': 'ir.actions.act_window',
1429
'res_model': 'purchase.order',
1430
'view_mode': 'form,tree,graph,calendar',
1431
'view_type': 'form',
1433
'context': {'rfq_ok': True, 'search_default_draft_rfq': 1},
1434
'domain': [('rfq_ok', '=', True)],
828
return {'type': 'ir.actions.act_window',
829
'res_model': 'purchase.order',
830
'view_mode': 'form,tree,graph,calendar',
833
'context': {'rfq_ok': True, 'search_default_draft_rfq': 1,},
834
'domain': [('rfq_ok', '=', True)],
1438
837
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
1461
860
if view_type == 'form':
1462
861
if context.get('rfq_ok', False):
1463
862
# the title of the screen depends on po type
1464
form = etree.fromstring(result['arch'])
1466
fields = form.xpath('//form[@string="%s"]' % _('Purchase Order'))
1467
for field in fields:
1468
field.set('string', _("Request for Quotation"))
1470
fields2 = form.xpath('//page[@string="%s"]' % _('Purchase Order'))
1471
for field2 in fields2:
1472
field2.set('string', _("Request for Quotation"))
1474
result['arch'] = etree.tostring(form)
863
arch = result['arch']
864
arch = arch.replace('<form string="Purchase Order">', '<form string="Requests for Quotation">')
865
result['arch'] = arch
1478
def wkf_act_rfq_done(self, cr, uid, ids, context=None):
1480
Set the state to done and update the price unit in the procurement order
1482
wf_service = netsvc.LocalService("workflow")
1483
proc_obj = self.pool.get('procurement.order')
1484
date_tools = self.pool.get('date.tools')
1485
fields_tools = self.pool.get('fields.tools')
1486
db_date_format = date_tools.get_db_date_format(cr, uid, context=context)
1488
if isinstance(ids, (int, long)):
1491
for rfq in self.browse(cr, uid, ids, context=context):
1492
if rfq.from_procurement:
1493
for line in rfq.order_line:
1494
if line.procurement_id:
1495
self.pool.get('procurement.order').write(cr, uid, [line.procurement_id.id], {'price_unit': line.price_unit}, context=context)
1496
elif not rfq.tender_id:
1497
prep_lt = fields_tools.get_field_from_company(cr, uid, object='sale.order', field='preparation_lead_time', context=context)
1498
rts = datetime.strptime(rfq.sale_order_id.ready_to_ship_date, db_date_format)
1499
rts = rts - relativedelta(days=prep_lt or 0)
1500
rts = rts.strftime(db_date_format)
1501
vals = {'product_id': line.product_id.id,
1502
'product_uom': line.product_uom.id,
1503
'product_uos': line.product_uom.id,
1504
'product_qty': line.product_qty,
1505
'product_uos_qty': line.product_qty,
1506
'price_unit': line.price_unit,
1507
'procure_method': 'make_to_order',
1510
'rfq_line_id': line.id,
1513
'tender_line_id': False,
1514
'date_planned': rts,
1515
'origin': rfq.sale_order_id.name,
1516
'supplier': rfq.partner_id.id,
1517
'name': '[%s] %s' % (line.product_id.default_code, line.product_id.name),
1518
'location_id': rfq.sale_order_id.warehouse_id.lot_stock_id.id,
1521
proc_id = proc_obj.create(cr, uid, vals, context=context)
1522
wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr)
1523
wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_check', cr)
1525
return self.write(cr, uid, ids, {'state': 'done'}, context=context)
1527
869
purchase_order()
1609
917
pricelist_partnerinfo()
1612
class tender_line_cancel_wizard(osv.osv_memory):
1613
_name = 'tender.line.cancel.wizard'
1616
'tender_line_id': fields.many2one('tender.line', string='Tender line', required=True),
1620
def just_cancel(self, cr, uid, ids, context=None):
1625
line_obj = self.pool.get('tender.line')
1626
tender_obj = self.pool.get('tender')
1627
data_obj = self.pool.get('ir.model.data')
1628
tender_wiz_obj = self.pool.get('tender.cancel.wizard')
1634
if isinstance(ids, (int, long)):
1639
for wiz in self.browse(cr, uid, ids, context=context):
1640
tender_ids.add(wiz.tender_line_id.tender_id.id)
1641
line_ids.append(wiz.tender_line_id.id)
1643
if context.get('has_to_be_resourced'):
1644
line_obj.write(cr, uid, line_ids, {'has_to_be_resourced': True}, context=context)
1646
line_obj.fake_unlink(cr, uid, line_ids, context=context)
1648
for tender in tender_obj.browse(cr, uid, list(tender_ids), context=context):
1649
if all(x.line_state in ('cancel', 'done') for x in tender.tender_line_ids):
1650
wiz_id = tender_wiz_obj.create(cr, uid, {'tender_id': tender.id}, context=context)
1651
view_id = data_obj.get_object_reference(cr, uid, 'tender_flow', 'ask_tender_cancel_wizard_form_view')[1]
1652
return {'type': 'ir.actions.act_window',
1653
'res_model': 'tender.cancel.wizard',
1654
'view_type': 'form',
1655
'view_mode': 'form',
1656
'view_id': [view_id],
1661
return {'type': 'ir.actions.act_window_close'}
1663
def cancel_and_resource(self, cr, uid, ids, context=None):
1665
Flag the line to be re-sourced and run cancel method
1671
context['has_to_be_resourced'] = True
1673
return self.just_cancel(cr, uid, ids, context=context)
1675
tender_line_cancel_wizard()
1678
class tender_cancel_wizard(osv.osv_memory):
1679
_name = 'tender.cancel.wizard'
1682
'tender_id': fields.many2one('tender', string='Tender', required=True),
1683
'not_draft': fields.boolean(string='Tender not draft'),
1686
def just_cancel(self, cr, uid, ids, context=None):
1688
Just cancel the wizard and the lines
1691
line_obj = self.pool.get('tender.line')
1697
if isinstance(ids, (int, long)):
1700
wf_service = netsvc.LocalService("workflow")
1704
for wiz in self.browse(cr, uid, ids, context=context):
1705
tender_ids.append(wiz.tender_id.id)
1706
for line in wiz.tender_id.tender_line_ids:
1707
line_ids.append(line.id)
1708
for rfq in wiz.tender_id.rfq_ids:
1709
rfq_ids.append(rfq.id)
1711
if context.get('has_to_be_resourced'):
1712
line_obj.write(cr, uid, line_ids, {'has_to_be_resourced': True}, context=context)
1714
line_obj.fake_unlink(cr, uid, line_ids, context=context)
1717
wf_service.trg_validate(uid, 'purchase.order', rfq, 'purchase_cancel', cr)
1719
for tender in tender_ids:
1720
wf_service.trg_validate(uid, 'tender', tender, 'tender_cancel', cr)
1722
return {'type': 'ir.actions.act_window_close'}
1724
def cancel_and_resource(self, cr, uid, ids, context=None):
1726
Flag the line to be re-sourced and run cancel method
1732
context['has_to_be_resourced'] = True
1734
return self.just_cancel(cr, uid, ids, context=context)
1736
def close_window(self, cr, uid, ids, context=None):
1738
Just close the wizard and reload the tender
1740
return {'type': 'ir.actions.act_window_close'}
1742
tender_cancel_wizard()
1746
919
class ir_values(osv.osv):
1747
920
_name = 'ir.values'
1748
921
_inherit = 'ir.values'