1
# -*- coding: utf-8 -*-
2
##############################################################################
4
# OpenERP, Open Source Management Solution
5
# Copyright (C) 2011 TeMPO Consulting, MSF
7
# This program is free software: you can redistribute it and/or modify
8
# it under the terms of the GNU Affero General Public License as
9
# published by the Free Software Foundation, either version 3 of the
10
# License, or (at your option) any later version.
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU Affero General Public License for more details.
17
# You should have received a copy of the GNU Affero General Public License
18
# along with this program. If not, see <http://www.gnu.org/licenses/>.
20
##############################################################################
22
from osv import osv, fields
23
from tools.translate import _
25
class sale_order_followup_test(osv.osv_memory):
26
_name = 'sale.order.followup.test'
28
_columns = {'name': fields.char(size=64, string='Name')}
30
def create_test(self, cr, uid, ids, context={}):
31
self.pool.get('product.category').create(cr, uid, {'name': 'test category'}, context=context)
34
sale_order_followup_test()
36
class sale_order_followup(osv.osv_memory):
37
_name = 'sale.order.followup'
38
_description = 'Sales Order Followup'
40
def get_selection(self, cr, uid, o, field):
43
sel = self.pool.get(o._name).fields_get(cr, uid, [field])
44
res = dict(sel[field]['selection']).get(getattr(o,field),getattr(o,field))
45
name = '%s,%s' % (o._name, field)
46
tr_ids = self.pool.get('ir.translation').search(cr, uid, [('type', '=', 'selection'), ('name', '=', name),('src', '=', res)])
48
return self.pool.get('ir.translation').read(cr, uid, tr_ids, ['value'])[0]['value']
52
def _get_order_state(self, cr, uid, ids, field_name, args, context={}):
58
for follow in self.browse(cr, uid, ids, context=context):
62
res[follow.id] = self.get_selection(cr, uid, follow.order_id, 'state')
67
'order_id': fields.many2one('sale.order', string='Internal reference', readonly=True),
68
'cust_ref': fields.related('order_id', 'client_order_ref', string='Customer reference', readonly=True, type='char'),
69
'creation_date': fields.related('order_id', 'create_date', string='Creation date', readonly=True, type='date'),
70
'state': fields.function(_get_order_state, method=True, type='char', string='Order state', readonly=True),
71
'requested_date': fields.related('order_id', 'delivery_requested_date', string='Requested date', readonly=True, type='date'),
72
'confirmed_date': fields.related('order_id', 'delivery_confirmed_date', string='Confirmed date', readonly=True, type='date'),
73
'line_ids': fields.one2many('sale.order.line.followup', 'followup_id', string='Lines', readonly=True),
74
'choose_type': fields.selection([('documents', 'Documents view'), ('progress', 'Progress view')], string='Type of view'),
78
'choose_type': lambda *a: 'progress',
81
def go_to_view(self, cr, uid, ids, context={}):
83
Launches the correct view according to the user's choice
85
for followup in self.browse(cr, uid, ids, context=context):
86
if followup.choose_type == 'documents':
87
view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'sales_followup', 'sale_order_followup_document_view')[1]
89
view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'sales_followup', 'sale_order_followup_progress_view')[1]
91
return {'type': 'ir.actions.act_window',
92
'res_model': 'sale.order.followup',
93
'res_id': followup.id,
99
def switch_documents(self, cr, uid, ids, context={}):
101
Switch to documents view
103
self.write(cr, uid, ids, {'choose_type': 'documents'})
105
return self.go_to_view(cr, uid, ids, context=context)
107
def switch_progress(self, cr, uid, ids, context={}):
109
Switch to progress view
111
self.write(cr, uid, ids, {'choose_type': 'progress'})
113
return self.go_to_view(cr, uid, ids, context=context)
115
def update_followup(self, cr, uid, ids, context={}):
117
Updates data in followup view
119
new_context = context.copy()
121
# Get information of the old followup before deletion
122
for followup in self.browse(cr, uid, ids, context=new_context):
123
new_context['active_ids'] = [followup.order_id.id]
124
new_context['view_type'] = followup.choose_type
126
# Get the id of the new followup object
127
result = self.start_order_followup(cr, uid, ids, context=new_context).get('res_id')
129
raise osv.except_osv(_('Error'), _('No followup found ! Cannot update !'))
131
# Remove the old followup object and all his lines (on delete cascade)
132
self.unlink(cr, uid, ids, context=new_context)
134
# Returns the same view as before
135
if new_context.get('view_type') == 'documents':
136
return self.switch_documents(cr, uid, [result], context=new_context)
138
return self.switch_progress(cr, uid, [result], context=new_context)
140
def start_order_followup(self, cr, uid, ids, context={}):
142
Creates and display a followup object
144
order_obj = self.pool.get('sale.order')
145
line_obj = self.pool.get('sale.order.line.followup')
148
ids = context.get('active_ids',[])
151
raise osv.except_osv(_('Error'), _('No order found !'))
153
raise osv.except_osv(_('Error'), _('You should select one order to follow !'))
157
for o in order_obj.browse(cr, uid, ids, context=context):
158
followup_id = self.create(cr, uid, {'order_id': o.id})
160
for line in o.order_line:
161
purchase_ids = self.get_purchase_ids(cr, uid, line.id, context=context)
162
purchase_line_ids = self.get_purchase_line_ids(cr, uid, line.id, purchase_ids, context=context)
163
incoming_ids = self.get_incoming_ids(cr, uid, line.id, purchase_ids, context=context)
164
outgoing_ids = self.get_outgoing_ids(cr, uid, line.id, context=context)
165
tender_ids = self.get_tender_ids(cr, uid, line.id, context=context)
166
# quotation_ids = self.get_quotation_ids(cr, uid, line.id, context=context)
168
line_obj.create(cr, uid, {'followup_id': followup_id,
170
'tender_ids': [(6,0,tender_ids)],
171
# 'quotation_ids': [(6,0,quotation_ids)],
172
'purchase_ids': [(6,0,purchase_ids)],
173
'purchase_line_ids': [(6,0,purchase_line_ids)],
174
'incoming_ids': [(6,0,incoming_ids)],
175
'outgoing_ids': [(6,0,outgoing_ids)],})
177
view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'sales_followup', 'sale_order_line_follow_choose_view')[1]
179
return {'type': 'ir.actions.act_window',
180
'res_model': 'sale.order.followup',
181
'res_id': followup_id,
182
'view_id': [view_id],
184
'view_mode': 'form',}
186
def get_purchase_ids(self, cr, uid, line_id, context={}):
188
Returns a list of purchase orders related to the sale order line
190
line_obj = self.pool.get('sale.order.line')
192
if isinstance(line_id, (int, long)):
197
for line in line_obj.browse(cr, uid, line_id, context=context):
198
if line.type == 'make_to_order' and line.procurement_id:
199
if line.procurement_id.purchase_id and not line.procurement_id.purchase_id.rfq_ok:
200
purchase_ids.append(line.procurement_id.purchase_id.id)
201
elif line.procurement_id.tender_id and line.procurement_id.tender_id.rfq_ids:
202
for rfq in line.procurement_id.tender_id.rfq_ids:
204
purchase_ids.append(rfq.id)
208
def get_purchase_line_ids(self, cr, uid, line_id, purchase_ids, context={}):
210
Returns a list of purchase order lines related to the sale order line
212
po_line_obj = self.pool.get('purchase.order.line')
213
line_obj = self.pool.get('sale.order.line')
216
if isinstance(purchase_ids, (int, long)):
217
purchase_ids = [purchase_ids]
219
if isinstance(line_id, (int, long)):
222
for line in line_obj.browse(cr, uid, line_id, context=context):
223
po_line_ids = po_line_obj.search(cr, uid, [('order_id', 'in', purchase_ids), ('product_id', '=', line.product_id.id)], context=context)
227
def get_quotation_ids(self, cr, uid, line_id, context={}):
229
Returns a list of request for quotation related to the sale order line
231
line_obj = self.pool.get('sale.order.line')
233
if isinstance(line_id, (int, long)):
238
for line in line_obj.browse(cr, uid, line_id, context=context):
239
if line.type == 'make_to_order' and line.procurement_id:
240
if line.procurement_id.purchase_id and line.procurement_id.purchase_id.rfq_ok:
241
quotation_ids.append(line.procurement_id.purchase_id.id)
242
elif line.procurement_id.tender_id and line.procurement_id.tender_id.rfq_ids:
243
for rfq in line.procurement_id.tender_id.rfq_ids:
245
quotation_ids.append(rfq.id)
250
def get_incoming_ids(self, cr, uid, line_id, purchase_ids, context={}):
252
Returns a list of incoming shipments related to the sale order line
254
line_obj = self.pool.get('sale.order.line')
255
purchase_obj = self.pool.get('purchase.order')
257
if isinstance(line_id, (int, long)):
260
if isinstance(purchase_ids, (int, long)):
261
purchase_ids= [purchase_ids]
265
for line in line_obj.browse(cr, uid, line_id, context=context):
266
for po in purchase_obj.browse(cr, uid, purchase_ids, context=context):
267
for po_line in po.order_line:
268
if po_line.product_id.id == line.product_id.id:
269
for move in po_line.move_ids:
270
incoming_ids.append(move.id)
274
def get_outgoing_ids(self, cr, uid, line_id, context={}):
276
Returns a list of outgoing deliveries related to the sale order line
278
line_obj = self.pool.get('sale.order.line')
279
move_obj = self.pool.get('stock.move')
281
if isinstance(line_id, (int, long)):
286
# Get all stock.picking associated to the sale order line
287
for line in line_obj.browse(cr, uid, line_id, context=context):
288
for move in line.move_ids:
289
if move.id not in outgoing_ids:
290
outgoing_ids.append(move.id)
291
# if move.picking_id and move.picking_id.id not in outgoing_ids:
292
# outgoing_ids.append(move.picking_id.id)
296
def get_tender_ids(self, cr, uid, line_id, context={}):
298
Returns a list of call for tender related to the sale order line
300
line_obj = self.pool.get('sale.order.line')
302
if isinstance(line_id, (int, long)):
307
for line in line_obj.browse(cr, uid, line_id, context=context):
308
for tender in line.tender_line_ids:
309
tender_ids.append(tender.id)
314
sale_order_followup()
316
class sale_order_line_followup(osv.osv_memory):
317
_name = 'sale.order.line.followup'
318
_description = 'Sales Order Lines Followup'
320
def _get_status(self, cr, uid, ids, field_name, arg, context={}):
322
Get all status about the line
324
move_obj = self.pool.get('stock.move')
328
for line in self.browse(cr, uid, ids, context=context):
329
res[line.id] = {'sourced_ok': 'No',
330
# 'quotation_status': 'No quotation',
331
'tender_status': 'N/A',
332
'purchase_status': 'N/A',
333
'incoming_status': 'N/A',
334
'outgoing_status': 'No deliveries',
335
'product_available': 'Waiting',
337
'available_qty': 0.00}
339
# Set the available qty in stock
340
res[line.id]['available_qty'] = self.pool.get('product.product').browse(cr, uid, line.line_id.product_id.id, context=context).qty_available
342
# Define if the line is sourced or not according to the state on the SO line
343
if line.line_id.state == 'draft':
344
res[line.id]['sourced_ok'] = 'No'
345
if line.line_id.state in ('confirmed', 'done'):
346
res[line.id]['sourced_ok'] = 'Done'
347
if line.line_id.state == 'cancel':
348
res[line.id]['sourced_ok'] = 'Cancelled'
349
if line.line_id.state == 'exception':
350
res[line.id]['sourced_ok'] = 'Exception'
352
####################################################
353
# Get information about the state of call for tender
354
####################################################
355
tender_status = {'n_a': 'N/A',
356
'no_tender': 'No tender',
357
'partial': 'Partial',
359
'comparison': 'In Progress',
361
'cancel': 'Cancelled'}
363
if line.line_id.type == 'make_to_stock' or line.line_id.po_cft == 'po':
364
res[line.id]['tender_status'] = tender_status.get('n_a', 'Error on state !')
365
elif line.line_id.po_cft == 'cft' and not line.tender_ids:
366
res[line.id]['tender_status'] = tender_status.get('no_tender', 'Error on state !')
368
# Check if all generated tenders are in the same state
370
for tender in line.tender_ids:
372
tender_state = tender.state
373
if tender_state != tender.state:
374
tender_state = 'partial'
376
res[line.id]['tender_status'] = tender_status.get(tender_state, 'Error on state !')
378
####################################################
379
# Get information about the state of purchase orders
380
####################################################
381
purchase_status = {'n_a': 'N/A',
382
'no_order': 'No order',
383
'partial': 'Partial',
385
'confirmed': 'Confirmed',
387
'approved': 'Approved',
389
'cancel': 'Cancelled',
390
'except_picking': 'Exception',
391
'except_invoice': 'Exception',}
393
if line.line_id.type == 'make_to_stock':
394
res[line.id]['purchase_status'] = purchase_status.get('n_a', 'Error on state !')
395
elif not line.purchase_ids:
396
res[line.id]['purchase_status'] = purchase_status.get('no_order', 'Error on state !')
398
# Check if all generated PO are in the same state
399
purchase_state = False
400
for order in line.purchase_ids:
401
if not purchase_state:
402
purchase_state = order.state
403
if purchase_state != order.state:
404
purchase_state = 'partial'
406
res[line.id]['purchase_status'] = purchase_status.get(purchase_state, 'Error on state !')
408
###########################################################
409
# Get information about the state of all incoming shipments
410
###########################################################
411
incoming_status = {'n_a': 'N/A',
412
'no_incoming': 'No shipment',
413
'partial': 'Partial',
415
'confirmed': 'Waiting',
416
'assigned': 'Available',
418
'cancel': 'Cancelled'}
420
if line.line_id.type == 'make_to_stock':
421
res[line.id]['incoming_status'] = incoming_status.get('n_a', 'Error on state !')
422
elif not line.incoming_ids:
423
res[line.id]['incoming_status'] = incoming_status.get('no_incoming', 'Error on state !')
425
shipment_state = False
426
for shipment in line.incoming_ids:
427
if not shipment_state:
428
shipment_state = shipment.state
429
if shipment_state != shipment.state:
430
shipment_state = 'partial'
432
res[line.id]['incoming_status'] = incoming_status.get(shipment_state, 'Error on state !')
434
#######################################################################
435
# Get information about the step and the state of all outgoing delivery
436
#######################################################################
437
out_status = {'no_out': 'No deliveries',
438
'partial': 'Partial',
440
'confirmed': 'Waiting',
441
'assigned': 'Available',
445
'shipped': 'Shipped',
446
'cancel': 'Cancelled',}
448
if not line.outgoing_ids:
449
res[line.id]['outgoing_status'] = out_status.get('no_out', 'Error on state !')
450
res[line.id]['outgoing_nb'] = '0'
452
# Get the first stock.picking
455
moves_first_out_ids = []
456
for out in line.outgoing_ids:
457
if out.picking_id and not out.picking_id.previous_step_id:
458
first_out = out.picking_id
459
moves_first_out.append(out)
460
moves_first_out_ids.append(out.id)
462
# Check the flow type of the first picking (full or quick)
463
if first_out.subtype == 'standard':
464
res[line.id]['outgoing_nb'] = len(moves_first_out)
466
for out in moves_first_out:
468
out_state = out.state
469
if out.state != out_state:
470
out_state = 'partial'
472
res[line.id]['outgoing_status'] = out_status.get(out_state, 'Error on state !')
473
res[line.id]['product_available'] = out_status.get(out_state, 'Error on state !')
476
# Begin from the first out moves
478
out_step = {'general': {'moves': [], 'state': False},
479
'picking': {'moves': [], 'state': False},
480
'packing': {'moves': [], 'state': False},
481
'dispatch': {'moves': [], 'state': False},
482
'distrib': {'moves': [], 'state': False},
483
'customer': {'moves': [], 'state': False},}
485
# Sort outgoing moves by type (picking, packing, dispatch, distrib, sending)
486
for out in line.outgoing_ids:
487
if not out.backmove_id:
488
out_step['general']['moves'].append(out)
489
elif out.backmove_id.id in moves_first_out_ids and out.picking_id and out.picking_id.subtype == 'picking':
490
out_step['picking']['moves'].append(out)
491
elif out.backmove_id.id in moves_first_out_ids and out.picking_id and out.picking_id.subtype == 'ppl':
492
out_step['packing']['moves'].append(out)
493
ppl_ids.append(out.picking_id.id)
494
elif out.backmove_id.id in moves_first_out_ids and out.picking_id and out.picking_id.subtype == 'packing':
495
if out.location_dest_id.usage == 'customer':
496
out_step['customer']['moves'].append(out)
497
elif out.location_dest_id.id == out.picking_id.warehouse_id.lot_distribution_id.id:
498
out_step['distrib']['moves'].append(out)
499
elif out.location_dest_id.id == out.picking_id.warehouse_id.lot_dispatch_id.id:
500
out_step['dispatch']['moves'].append(out)
501
elif out.location_id.id == out.picking_id.warehouse_id.lot_dispatch_id.id:
502
out_step['dispatch']['moves'].append(out)
503
elif out.location_id.id == out.picking_id.warehouse_id.lot_distribution_id.id:
504
out_step['distrib']['moves'].append(out)
506
nb_out = len(out_step['picking']['moves'])
510
for pack in out_step['packing']['moves']:
511
if pack.state == 'done' and pack.product_qty == 0.00:
515
for cust in out_step['customer']['moves']:
516
if cust.state == 'done' and cust.product_qty == 0.00:
519
# Set the state for the step 'customer'
521
for cust in out_step['customer']['moves']:
522
if cust.state == 'done' and ret_iter != nb_return_dist:
525
if not out_step['customer']['state']:
526
out_step['customer']['state'] = cust.state
527
if out_step['customer']['state'] != cust.state:
528
out_step['customer']['state'] = 'partial'
530
# Set the state for the step 'distrib'
532
for dist in out_step['distrib']['moves']:
533
if dist.state == 'cancel' or dist.product_qty == 0.00:
535
if dist.state == 'done' and ret_iter != nb_return_dist:
538
if not out_step['distrib']['state']:
539
out_step['distrib']['state'] = dist.state
540
if out_step['distrib']['state'] != dist.state:
541
out_step['distrib']['state'] = 'partial'
543
# Set the state for the step 'dispatch'
544
for disp in out_step['dispatch']['moves']:
545
if disp.location_id.id == disp.picking_id.warehouse_id.lot_dispatch_id.id:
548
if not out_step['dispatch']['state']:
549
out_step['dispatch']['state'] = disp.state
550
if out_step['dispatch']['state'] != disp.state:
551
out_step['dispatch']['state'] = 'partial'
553
# Set the state for the step 'packing'
555
for pack in out_step['packing']['moves']:
556
if pack.product_qty == 0.00 or pack.location_dest_id.id != pack.picking_id.warehouse_id.lot_dispatch_id.id:
558
if pack.state == 'done' and ret_iter != (nb_return_pack + nb_return_pack2):
561
if not out_step['packing']['state']:
562
out_step['packing']['state'] = pack.state
563
if out_step['packing']['state'] != pack.state:
564
out_step['packing']['state'] = 'partial'
566
# Set the state for the step 'picking'
569
for pick in out_step['picking']['moves']:
570
if pick.state == 'cancel':
573
if pick.state == 'done' and ret_iter != nb_return_pack:
577
elif pick.state == 'done' and ret_iter2 != nb_return_pack2:
581
if not out_step['picking']['state']:
582
out_step['picking']['state'] = pick.state
583
if out_step['picking']['state'] != pick.state:
584
out_step['picking']['state'] = 'partial'
586
# Increase the nb of out if there are products in general picking ticket
588
for general in out_step['general']['moves']:
589
if general.product_qty != 0.00 and general.state != 'cancel':
590
total_line += general.product_qty
592
out_step['general']['state'] = general.state
594
# If all products should be processed from the main picking ticket or if the main picking ticket is done
595
if total_line == line.line_id.product_uom_qty:
596
res[line.id]['product_available'] = out_status.get(out_step['general']['state'], 'Error on state !')
597
res[line.id]['outgoing_status'] = out_status.get(out_step['general']['state'], 'Error on state !')
598
elif out_step['customer']['state'] == 'done':
599
res[line.id]['product_available'] = out_status.get('done', 'Error on state !')
600
res[line.id]['outgoing_status'] = out_status.get('done', 'Error on state !')
601
elif total_line < line.line_id.product_uom_qty and out_step['general']['state']:
602
res[line.id]['product_available'] = out_status.get('partial', 'Error on state !')
603
res[line.id]['outgoing_status'] = out_status.get('partial', 'Error on state !')
605
# If not all products are sent to the supplier
606
if out_step['customer']['state'] and out_step['customer']['state'] == 'partial':
607
res[line.id]['outgoing_status'] = out_status.get('partial', 'Error on state !')
608
res[line.id]['product_available'] = out_status.get('done', 'Error on state !')
609
# If all products are waiting to send to customer
610
elif out_step['customer']['state'] and out_step['customer']['state'] == 'assigned':
611
res[line.id]['outgoing_status'] = out_status.get('shipped', 'Error on state !')
612
res[line.id]['product_available'] = out_status.get('done', 'Error on state !')
614
# If all products are not in distribution
615
if out_step['distrib']['state'] and out_step['distrib']['state'] == 'partial':
616
res[line.id]['outgoing_status'] = out_status.get('partial', 'Error on state !')
617
elif out_step['distrib']['state'] and out_step['distrib']['state'] == 'assigned':
618
res[line.id]['outgoing_status'] = out_status.get('packed', 'Error on state !')
619
res[line.id]['product_available'] = out_status.get('done', 'Error on state !')
621
# If all products are not in dispatch zone
622
if out_step['dispatch']['state'] == 'partial':
623
res[line.id]['outgoing_status'] = out_status.get('partial', 'Error on state !')
625
# If all products are not picked
626
if out_step['picking']['state'] == 'partial' or out_step['packing']['state'] == 'partial':
627
res[line.id]['outgoing_status'] = out_status.get('partial', 'Error on state !')
628
res[line.id]['product_available'] = out_status.get(out_step['picking']['state'], 'Error on state !')
629
elif out_step['picking']['state'] == 'assigned':
630
res[line.id]['outgoing_status'] = out_status.get('assigned', 'Error on state !')
631
res[line.id]['product_available'] = out_status.get('assigned', 'Error on state !')
632
elif out_step['picking']['state'] == 'done' and out_step['packing']['state'] == 'assigned':
633
res[line.id]['outgoing_status'] = out_status.get('picked', 'Error on state !')
634
res[line.id]['product_available'] = out_status.get('done', 'Error on state !')
636
# Set the number of the outgoing deliveries
637
res[line.id]['outgoing_nb'] = '%s' %nb_out
642
'followup_id': fields.many2one('sale.order.followup', string='Sale Order Followup', required=True, on_delete='cascade'),
643
'line_id': fields.many2one('sale.order.line', string='Order line', required=True, readonly=True),
644
'procure_method': fields.related('line_id', 'type', type='selection', selection=[('make_to_stock','From stock'), ('make_to_order','On order')], readonly=True, string='Proc. Method'),
645
'po_cft': fields.related('line_id', 'po_cft', type='selection', selection=[('po','PO'), ('cft','CFT')], readonly=True, string='PO/CFT'),
646
'line_number': fields.related('line_id', 'line_number', string='Order line', readonly=True, type='integer'),
647
'product_id': fields.related('line_id', 'product_id', string='Product reference', readondy=True,
648
type='many2one', relation='product.product'),
649
'qty_ordered': fields.related('line_id', 'product_uom_qty', string='Ordered qty', readonly=True),
650
'uom_id': fields.related('line_id', 'product_uom', type='many2one', relation='product.uom', string='UoM', readonly=True),
651
'sourced_ok': fields.function(_get_status, method=True, string='Sourced', type='char',
652
readonly=True, multi='status'),
653
'tender_ids': fields.many2many('tender', 'call_tender_follow_rel',
654
'follow_line_id', 'tender_id', string='Tender'),
655
'tender_status': fields.function(_get_status, method=True, string='Tender', type='char',
656
readonly=True, multi='status'),
657
# 'quotation_ids': fields.many2many('purchase.order', 'quotation_follow_rel', 'follow_line_id',
658
# 'quotation_id', string='Requests for Quotation', readonly=True),
659
# 'quotation_status': fields.function(_get_status, method=True, string='Request for Quotation',
660
# type='char', readonly=True, multi='status'),
661
'purchase_ids': fields.many2many('purchase.order', 'purchase_follow_rel', 'follow_line_id',
662
'purchase_id', string='Purchase Orders', readonly=True),
663
'purchase_line_ids': fields.many2many('purchase.order.line', 'purchase_line_follow_rel', 'follow_line_id',
664
'purchase_line_id', string='Purchase Orders', readonly=True),
665
'purchase_status': fields.function(_get_status, method=True, string='Purchase Order',
666
type='char', readonly=True, multi='status'),
667
'incoming_ids': fields.many2many('stock.move', 'incoming_follow_rel', 'follow_line_id',
668
'incoming_id', string='Incoming Shipment', readonly=True),
669
'incoming_status': fields.function(_get_status, method=True, string='Incoming Shipment',
670
type='char', readonly=True, multi='status'),
671
'product_available': fields.function(_get_status, method=True, string='Product available',
672
type='char', readonly=True, multi='status'),
673
'available_qty': fields.function(_get_status, method=True, string='Product available',
674
type='float', readonly=True, multi='status'),
675
'outgoing_ids': fields.many2many('stock.move', 'outgoing_follow_rel', 'outgoing_id',
676
'follow_line_id', string='Outgoing Deliveries', readonly=True),
677
'outgoing_status': fields.function(_get_status, method=True, string='Outgoing delivery',
678
type='char', readonly=True, multi='status'),
679
'outgoing_nb': fields.function(_get_status, method=True, string='Outgoing delivery',
680
type='char', readonly=True, multi='status'),
683
sale_order_line_followup()
686
class sale_order_followup_from_menu(osv.osv_memory):
687
_name = 'sale.order.followup.from.menu'
688
_description = 'Sale order followup menu entry'
691
'order_id': fields.many2one('sale.order', string='Internal reference', required=True),
692
'cust_order_id': fields.many2one('sale.order', string='Customer reference', required=True),
695
def go_to_followup(self, cr, uid, ids, context={}):
696
new_context = context.copy()
698
for menu in self.browse(cr, uid, ids, context=context):
699
new_ids.append(menu.order_id and menu.order_id.id or menu.cust_order_id.id)
701
new_context['active_ids'] = new_ids
703
return self.pool.get('sale.order.followup').start_order_followup(cr, uid, ids, context=new_context)
705
def change_order_id(self, cr, uid, ids, order_id, cust_order_id, type='order_id'):
708
if type == 'cust_order_id' and cust_order_id:
709
res.update({'order_id': False})
711
res.update({'cust_order_id': False})
713
return {'value': res}
716
sale_order_followup_from_menu()
719
class tender_line(osv.osv):
720
_name = 'tender.line'
721
_inherit = 'tender.line'
723
def go_to_tender_info(self, cr, uid, ids, context={}):
725
Return the form of the object
727
view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'tender_flow', 'tender_form')[1]
728
tender_id = self.pool.get('tender.line').browse(cr, uid, ids[0], context=context).tender_id.id
729
return {'type': 'ir.actions.act_window',
730
'res_model': 'tender',
733
'view_id': [view_id],
734
'res_id': tender_id,}
739
class purchase_order(osv.osv):
740
_name = 'purchase.order'
741
_inherit = 'purchase.order'
743
def go_to_po_info(self, cr, uid, ids, context={}):
745
Return the form of the object
747
view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'purchase', 'purchase_order_form')[1]
748
po_id = self.pool.get('purchase.order.line').browse(cr, uid, ids, context=context).order_id.id
749
return {'type': 'ir.actions.act_window',
750
'res_model': 'purchase.order',
753
'view_id': [view_id],
759
class request_for_quotation(osv.osv):
760
_name = 'purchase.order'
761
_inherit = 'purchase.order'
763
def go_to_rfq_info(self, cr, uid, ids, context={}):
765
Return the form of the object
767
view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'purchase', 'purchase_order_form')[1]
768
return {'type': 'ir.actions.act_window',
769
'res_model': 'purchase.order',
772
'view_id': [view_id],
775
request_for_quotation()
778
class stock_move(osv.osv):
780
_inherit = 'stock.move'
782
def _get_view_id(self, cr, uid, ids, context={}):
784
Returns the good view id
786
if isinstance(ids, (int,long)):
789
obj_data = self.pool.get('ir.model.data')
791
pick = self.pool.get('stock.move').browse(cr, uid, ids, context=context)[0].picking_id
793
view_list = {'out': ('stock', 'view_picking_out_form'),
794
'in': ('stock', 'view_picking_in_form'),
795
'internal': ('stock', 'view_picking_form'),
796
'picking': ('msf_outgoing', 'view_picking_ticket_form'),
797
'ppl': ('msf_outgoing', 'view_ppl_form'),
798
'packing': ('msf_outgoing', 'view_packing_form')
800
if pick.type == 'out':
801
module, view = view_list.get(pick.subtype,('msf_outgoing', 'view_picking_ticket_form'))
803
return obj_data.get_object_reference(cr, uid, module, view)[1], pick.id
804
except ValueError, e:
807
module, view = view_list.get(pick.type,('stock', 'view_picking_form'))
809
return self.pool.get('ir.model.data').get_object_reference(cr, uid, module, view)[1], pick.id
811
def go_to_incoming_info(self, cr, uid, ids, context={}):
813
Return the form of the object
815
view_id = self._get_view_id(cr, uid, ids, context=context)
816
return {'type': 'ir.actions.act_window',
817
'res_model': 'stock.picking',
820
'view_id': [view_id[0]],
821
'res_id': view_id[1],}
823
def go_to_outgoing_info(self, cr, uid, ids, context={}):
825
Return the form of the object
827
view_id = self._get_view_id(cr, uid, ids, context=context)
828
return {'type': 'ir.actions.act_window',
829
'res_model': 'stock.picking',
832
'view_id': [view_id[0]],
833
'res_id': view_id[1],}
838
class purchase_order_line(osv.osv):
839
_name = 'purchase.order.line'
840
_inherit = 'purchase.order.line'
845
('confirmed', 'Waiting Approval'),
846
('approved', 'Approved'),
847
('except_picking', 'Shipping Exception'),
848
('except_invoice', 'Invoice Exception'),
850
('cancel', 'Cancelled'),
851
('rfq_sent', 'RfQ Sent'),
852
('rfq_updated', 'RfQ Updated'),
855
ORDER_TYPE = [('regular', 'Regular'), ('donation_exp', 'Donation before expiry'),
856
('donation_st', 'Standard donation'), ('loan', 'Loan'),
857
('in_kind', 'In Kind Donation'), ('purchase_list', 'Purchase List'),
858
('direct', 'Direct Purchase Order')]
861
'order_type': fields.related('order_id', 'order_type', type='selection', selection=ORDER_TYPE, readonly=True),
862
'po_state': fields.related('order_id', 'state', type='selection', selection=STATE_SELECTION, readonly=True),
865
def go_to_po_info(self, cr, uid, ids, context={}):
867
Return the form of the object
869
view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'purchase', 'purchase_order_form')[1]
870
if isinstance(ids, (int,long)):
872
po_id = self.pool.get('purchase.order.line').browse(cr, uid, ids, context=context)[0].order_id.id
873
return {'type': 'ir.actions.act_window',
874
'res_model': 'purchase.order',
877
'view_id': [view_id],
880
purchase_order_line()
883
class sale_order(osv.osv):
885
_inherit = 'sale.order'
887
def name_search(self, cr, uid, name='', args=None, operator='ilike', context={}, limit=80):
889
Search all SO by internal or customer reference
891
if context.get('from_followup'):
893
if name and len(name) > 1:
894
ids.extend(self.search(cr, uid, [('client_order_ref', operator, name)], context=context))
896
return self.name_get(cr, uid, ids, context=context)
898
return super(sale_order, self).name_search(cr, uid, name, args, operator, context, limit)
900
def name_get(self, cr, uid, ids, context={}):
902
If the method is called from followup wizard, set the customer ref in brackets
904
if context.get('from_followup'):
906
for r in self.browse(cr, uid, ids, context=context):
907
if r.client_order_ref:
908
res.append((r.id, '%s' % r.client_order_ref))
910
res.append((r.id, '%s' % r.name))
913
return super(sale_order, self).name_get(cr, uid, ids, context=context)