4396f21181c22bdd5a98e67e1aee422ebb38bd26
[odoo/odoo.git] / addons / fleet / fleet.py
1 # -*- coding: utf-8 -*-
2 from itertools import chain
3 from osv import osv, fields
4 import time
5 import datetime
6 import tools
7 from osv.orm import except_orm
8 from tools.translate import _
9 from dateutil.relativedelta import relativedelta
10 ############################
11 ############################
12 #Vehicle.cost class
13 ############################
14 ############################
15
16 class fleet_vehicle_cost(osv.Model):
17     _name = 'fleet.vehicle.cost'
18     _description = 'Cost of vehicle'
19     _order = 'date desc, vehicle_id asc'
20
21     def name_get(self, cr, uid, ids, context=None):
22         if context is None:
23             context = {}
24         if not ids:
25             return []
26         reads = self.browse(cr, uid, ids, context=context)
27         res = []
28         for record in reads:
29             if record.vehicle_id.license_plate:
30                 name = record.vehicle_id.license_plate
31             if record.cost_type.name:
32                 name = name + ' / '+ record.cost_type.name
33             if record.date:
34                 name = name + ' / '+ record.date
35             res.append((record.id, name))
36         return res
37
38     def _cost_name_get_fnc(self, cr, uid, ids, name, unknow_none, context=None):
39         res = self.name_get(cr, uid, ids, context=context)
40         return dict(res)
41
42     _columns = {
43         'name' : fields.function(_cost_name_get_fnc, type="char", string='Name', store=True),
44         #'name' : fields.char('Name',size=32),
45         'vehicle_id': fields.many2one('fleet.vehicle', 'Vehicle', required=True, help='Vehicle concerned by this fuel log'),
46         'cost_type': fields.many2one('fleet.service.type', 'Service type', required=False, help='Service type purchased with this cost'),
47         'amount': fields.float('Total Price'),
48
49         'parent_id': fields.many2one('fleet.vehicle.cost', 'Parent', required=False, help='Parent cost to this current cost'),
50         'cost_ids' : fields.one2many('fleet.vehicle.cost', 'parent_id', 'Included Services'),
51
52         'date' :fields.date('Date',help='Date when the cost has been executed'),
53
54         'contract_id' : fields.many2one('fleet.vehicle.log.contract', 'Contract', required=False, help='Contract attached to this cost'),
55         'auto_generated' : fields.boolean('automatically generated',readonly=True,required=True)
56     }
57
58     _default ={
59         'parent_id':None,
60         'auto_generated' : False,
61     }
62
63     
64
65     def create(self, cr, uid, data, context=None):
66         if 'parent_id' in data and data['parent_id']:
67             data['vehicle_id'] = self.browse(cr, uid, data['parent_id'], context=context).vehicle_id.id
68             data['date'] = self.browse(cr, uid, data['parent_id'], context=context).date
69         if 'contract_id' in data and data['contract_id']:
70             data['vehicle_id'] = self.pool.get('fleet.vehicle.log.contract').browse(cr, uid, data['contract_id'], context=context).vehicle_id.id
71             data['cost_type'] = self.pool.get('fleet.vehicle.log.contract').browse(cr, uid, data['contract_id'], context=context).cost_type.id
72         cost_id = super(fleet_vehicle_cost, self).create(cr, uid, data, context=context)
73         return cost_id
74
75 ############################
76 ############################
77 #Vehicle.tag class
78 ############################
79 ############################
80
81 class fleet_vehicle_tag(osv.Model):
82     _name = 'fleet.vehicle.tag'
83     _columns = {
84         'name': fields.char('Name', required=True, translate=True),
85     }
86
87 ############################
88 ############################
89 #Vehicle.state class
90 ############################
91 ############################
92
93 class fleet_vehicle_state(osv.Model):
94     _name = 'fleet.vehicle.state'
95     _columns = {
96         'name': fields.char('Name', required=True),
97         'sequence': fields.integer('Order',help="Used to order the note stages")
98     }
99     _order = 'sequence asc'
100     _sql_constraints = [('fleet_state_name_unique','unique(name)','State name already exists')]
101
102
103 ############################
104 ############################
105 #Vehicle.model class
106 ############################
107 ############################
108
109 class fleet_vehicle_model(osv.Model):
110
111     def name_get(self, cr, uid, ids, context=None):
112         if context is None:
113             context = {}
114         if not ids:
115             return []
116         reads = self.browse(cr, uid, ids, context=context)
117         res = []
118         for record in reads:
119             name = record.modelname
120             if record.brand.name:
121                 name = record.brand.name+' / '+name
122             res.append((record.id, name))
123         return res
124
125     def _model_name_get_fnc(self, cr, uid, ids, prop, unknow_none, context=None):
126         res = self.name_get(cr, uid, ids, context=context)
127         return dict(res)
128
129     def on_change_brand(self, cr, uid, ids, model_id, context=None):
130
131         if not model_id:
132             return {}
133
134         brand = self.pool.get('fleet.vehicle.model.brand').browse(cr, uid, model_id, context=context)
135
136         return {
137             'value' : {
138                 'image' : brand.image,
139             }
140         }
141
142     _name = 'fleet.vehicle.model'
143     _description = 'Model of a vehicle'
144
145     _columns = {
146         'name' : fields.function(_model_name_get_fnc, type="char", string='Name', store=True),
147         'modelname' : fields.char('Model name', size=32, required=True), 
148         'brand' : fields.many2one('fleet.vehicle.model.brand', 'Model Brand', required=True, help='Brand of the vehicle'),
149         'vendors': fields.many2many('res.partner','fleet_vehicle_model_vendors','model_id', 'partner_id',string='Vendors',required=False),
150         'image': fields.related('brand','image',type="binary",string="Logo",store=False),
151         'image_medium': fields.related('brand','image_medium',type="binary",string="Logo",store=False),
152         'image_small': fields.related('brand','image_small',type="binary",string="Logo",store=False),
153     }
154
155 ############################
156 ############################
157 #Vehicle.brand class
158 ############################
159 ############################
160
161 class fleet_vehicle_model_brand(osv.Model):
162     _name = 'fleet.vehicle.model.brand'
163     _description = 'Brand model of the vehicle'
164
165     _order = 'name asc'
166
167     def _get_image(self, cr, uid, ids, name, args, context=None):
168         result = dict.fromkeys(ids, False)
169         for obj in self.browse(cr, uid, ids, context=context):
170             result[obj.id] = tools.image_get_resized_images(obj.image)
171         return result
172     
173     def _set_image(self, cr, uid, id, name, value, args, context=None):
174         return self.write(cr, uid, [id], {'image': tools.image_resize_image_big(value)}, context=context)
175
176     _columns = {
177         'name' : fields.char('Brand Name',size=32, required=True),
178
179         'image': fields.binary("Logo",
180             help="This field holds the image used as logo for the brand, limited to 1024x1024px."),
181         'image_medium': fields.function(_get_image, fnct_inv=_set_image,
182             string="Medium-sized photo", type="binary", multi="_get_image",
183             store = {
184                 'fleet.vehicle.model.brand': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
185             },
186             help="Medium-sized logo of the brand. It is automatically "\
187                  "resized as a 128x128px image, with aspect ratio preserved. "\
188                  "Use this field in form views or some kanban views."),
189         'image_small': fields.function(_get_image, fnct_inv=_set_image,
190             string="Smal-sized photo", type="binary", multi="_get_image",
191             store = {
192                 'fleet.vehicle.model.brand': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
193             },
194             help="Small-sized photo of the brand. It is automatically "\
195                  "resized as a 64x64px image, with aspect ratio preserved. "\
196                  "Use this field anywhere a small image is required."),
197     }
198
199 ############################
200 ############################
201 #Vehicle class
202 ############################
203 ############################
204
205
206 class fleet_vehicle(osv.Model):
207
208     _inherit = 'mail.thread'
209
210     def name_get(self, cr, uid, ids, context=None):
211         if context is None:
212             context = {}
213         if not ids:
214             return []
215         reads = self.browse(cr, uid, ids, context=context)
216         res = []
217         for record in reads:
218             if record.license_plate:
219                 name = record.license_plate
220             if record.model_id.modelname:
221                 name = record.model_id.modelname + ' / ' + name
222             if record.model_id.brand.name:
223                 name = record.model_id.brand.name+' / '+ name
224             res.append((record.id, name))
225         return res
226
227     def _vehicle_name_get_fnc(self, cr, uid, ids, prop, unknow_none, context=None):
228         res = self.name_get(cr, uid, ids, context=context)
229         return dict(res)
230
231     def act_show_log_services(self, cr, uid, ids, context=None):
232         """ This opens log view to view and add new log for this vehicle
233             @return: the service log view
234         """
235         res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid ,'fleet','fleet_vehicle_log_services_act', context)
236         res['context'] = {
237             'default_vehicle_id': ids[0]
238         }
239         res['domain']=[('vehicle_id','=', ids[0])]
240         return res
241
242     def act_show_log_contract(self, cr, uid, ids, context=None):
243         """ This opens log view to view and add new log for this vehicle
244             @return: the contract log view
245         """
246         res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid ,'fleet','fleet_vehicle_log_contract_act', context)
247         res['context'] = {
248             'default_vehicle_id': ids[0]
249         }
250         res['domain']=[('vehicle_id','=', ids[0])]
251         return res
252
253     def act_show_log_fuel(self, cr, uid, ids, context=None):
254         """ This opens log view to view and add new log for this vehicle
255             @return: the fuel log view
256         """
257         res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid ,'fleet','fleet_vehicle_log_fuel_act', context)
258         res['context'] = {
259             'default_vehicle_id': ids[0]
260         }
261         res['domain']=[('vehicle_id','=', ids[0])]
262         return res
263
264     def act_show_log_cost(self, cr, uid, ids, context=None):
265         """ This opens log view to view and add new log for this vehicle
266             @return: the costs log view
267         """
268         res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid ,'fleet','fleet_vehicle_costs_act', context)
269         res['context'] = {
270             'default_vehicle_id': ids[0],
271             'search_default_parent_false' : True
272         }
273         res['domain']=[('vehicle_id','=', ids[0])]
274         return res
275
276     def act_show_log_odometer(self, cr, uid, ids, context=None):
277         """ This opens log view to view and add new log for this vehicle
278             @return: the odometer log view
279         """
280         res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid ,'fleet','fleet_vehicle_odometer_act', context)
281         res['context'] = {
282             'default_vehicle_id': ids[0]
283         }
284         res['domain']=[('vehicle_id','=', ids[0])]
285         return res
286
287     def _get_odometer(self, cr, uid, ids, odometer_id, arg, context):
288         res = dict.fromkeys(ids, False)
289         for record in self.browse(cr,uid,ids,context=context):    
290             ids = self.pool.get('fleet.vehicle.odometer').search(cr,uid,[('vehicle_id','=',record.id)],limit=1, order='value desc')
291             if len(ids) > 0:
292                 res[record.id] = str(self.pool.get('fleet.vehicle.odometer').browse(cr,uid,ids[0],context=context).value)
293             else:
294                 res[record.id] = str(0)
295         return res
296
297     def _set_odometer(self, cr, uid, id, name, value, args=None, context=None):
298         if value:
299             try:
300                 value = float(value)
301             except ValueError:
302                 #_logger.exception(value+' is not a correct odometer value. Please, fill a float for this field')
303                 raise except_orm(_('Error!'), value+' is not a correct odometer value. Please, fill a float for this field')
304             
305             date = time.strftime('%Y-%m-%d')
306             data = {'value' : value,'date' : date,'vehicle_id' : id}
307             odometer_id = self.pool.get('fleet.vehicle.odometer').create(cr, uid, data, context=context)
308             return value
309         self.write(cr, uid, id, {'odometer_id': ''})
310         return False  
311
312     def str_to_date(self,strdate):
313         return datetime.datetime(int(strdate[:4]),int(strdate[5:7]),int(strdate[8:]))
314
315     def get_overdue_contract_reminder_fnc(self,cr,uid,ids,context=None):
316         if context is None:
317             context={}
318         if not ids:
319             return dict([])
320         reads = self.browse(cr,uid,ids,context=context)
321         res=[]
322         
323         for record in reads:
324             overdue=0
325             if (record.log_contracts):
326                 for element in record.log_contracts:
327                     if (element.state=='open' and element.expiration_date):
328                         current_date_str=time.strftime('%Y-%m-%d')
329                         due_time_str=element.expiration_date
330                             #due_time_str=element.browse()
331                         current_date=self.str_to_date(current_date_str)
332                         due_time=self.str_to_date(due_time_str)
333              
334                         diff_time=int((due_time-current_date).days)
335                         if diff_time<0:
336                             overdue = overdue +1;
337                 res.append((record.id,overdue))
338             else:
339                 res.append((record.id,0))
340
341         return dict(res)
342
343     def get_overdue_contract_reminder(self,cr,uid,ids,prop,unknow_none,context=None):
344         res = self.get_overdue_contract_reminder_fnc(cr, uid, ids, context=context)
345         return res
346
347     def get_next_contract_reminder_fnc(self,cr,uid,ids,context=None):
348         if context is None:
349             context={}
350         if not ids:
351             return dict([])
352         reads = self.browse(cr,uid,ids,context=context)
353         res=[]
354
355         for record in reads:
356             due_soon=0
357             if (record.log_contracts):
358                 for element in record.log_contracts:
359                     if (element.state=='open' and element.expiration_date):
360                         current_date_str=time.strftime('%Y-%m-%d')
361                         due_time_str=element.expiration_date
362                             #due_time_str=element.browse()
363                         current_date=self.str_to_date(current_date_str)
364                         due_time=self.str_to_date(due_time_str)
365              
366                         diff_time=int((due_time-current_date).days)
367                         if diff_time<15 and diff_time>=0:
368                             due_soon = due_soon +1;
369                 res.append((record.id,due_soon))
370             else:
371                 res.append((record.id,0))
372         
373         return dict(res)
374
375     def _search_get_overdue_contract_reminder(self, cr, uid, obj, name, args, context):
376         res = []
377         for field, operator, value in args:
378             #assert field == name
379             vehicle_ids = self.search(cr, uid, [])
380             renew_ids = self.get_overdue_contract_reminder_fnc(cr,uid,vehicle_ids,context=context)
381             res_ids = []
382             for renew_key,renew_value in renew_ids.items():
383                 if eval(str(renew_value) + " " + str(operator) + " " + str(value)):
384                     res_ids.append(renew_key)
385             res.append(('id', 'in', res_ids))      
386         return res
387     
388     def _search_contract_renewal_due_soon(self, cr, uid, obj, name, args, context):
389         res = []
390         for field, operator, value in args:
391             #assert field == name
392             vehicle_ids = self.search(cr, uid, [])
393             renew_ids = self.get_next_contract_reminder_fnc(cr,uid,vehicle_ids,context=context)
394             res_ids = []
395             for renew_key,renew_value in renew_ids.items():
396                 if eval(str(renew_value) + " " + str(operator) + " " + str(value)):
397                     res_ids.append(renew_key)
398             res.append(('id', 'in', res_ids))      
399         return res
400
401     def get_next_contract_reminder(self, cr, uid, ids, prop, unknow_none, context=None):
402         res = self.get_next_contract_reminder_fnc(cr, uid, ids, context=context)
403         return res
404
405     def get_contract_renewal_names(self,cr,uid,ids,function_name,args,context=None):
406         if not ids:
407             return dict([])
408         reads = self.browse(cr,uid,ids,context=context)
409         res=[]
410         for record in reads:
411             if (record.log_contracts):
412                 ids = self.pool.get('fleet.vehicle.log.contract').search(cr,uid,[('vehicle_id','=',record.id),('state','=','open')],limit=1,order='expiration_date asc')
413                 if len(ids) > 0:
414                     res.append((record.id,self.pool.get('fleet.vehicle.log.contract').browse(cr,uid,ids[0],context=context).cost_type.name))
415             else:
416                 res.append((record.id,''))
417         return dict(res)
418
419     def get_total_contract_reminder(self,cr,uid,ids,prop,unknow_none,context=None):
420         if context is None:
421             context={}
422         if not ids:
423             return dict([])
424         reads = self.browse(cr,uid,ids,context=context)
425         res=[]
426
427         for record in reads:
428             due_soon=0
429             if (record.log_contracts):
430                 for element in record.log_contracts:
431                     if (element.state=='open' and element.expiration_date):
432                         current_date_str=time.strftime('%Y-%m-%d')
433                         due_time_str=element.expiration_date
434
435                         current_date=self.str_to_date(current_date_str)
436                         due_time=self.str_to_date(due_time_str)
437              
438                         diff_time=int((due_time-current_date).days)
439                         if diff_time<15:
440                             due_soon = due_soon +1;
441                 if due_soon>0:
442                     due_soon=due_soon-1
443                 res.append((record.id,due_soon))
444             else:
445                 res.append((record.id,0))
446         
447         return dict(res)
448
449     def run_scheduler(self,cr,uid,context=None):
450         ids = self.pool.get('fleet.vehicle').search(cr, uid, [], offset=0, limit=None, order=None,context=None, count=False)
451         nexts = self.get_next_contract_reminder_fnc(cr,uid,ids,context=context)
452         overdues = self.get_overdue_contract_reminder_fnc(cr,uid,ids,context=context)
453         for key,value in nexts.items():
454             if value > 0 and overdues[key] > 0:
455                 self.message_post(cr, uid, [key], body=str(value) + ' contract(s) has to be renewed soon and '+str(overdues[key])+' contract(s) is (are) overdued', context=context)
456             elif value > 0:
457                 self.message_post(cr, uid, [key], body=str(value) + ' contract(s) has to be renewed soon!', context=context)
458             elif overdues[key] > 0 : 
459                 self.message_post(cr, uid, [key], body=str(overdues[key]) + ' contract(s) is(are) overdued!', context=context)
460         return True
461
462     _name = 'fleet.vehicle'
463     _description = 'Fleet Vehicle'
464     #_order = 'contract_renewal_overdue desc, contract_renewal_due_soon desc'
465     _order= 'license_plate asc'
466     _columns = {
467         'name' : fields.function(_vehicle_name_get_fnc, type="char", string='Name', store=True),
468
469         'company_id': fields.many2one('res.company', 'Company'),
470         'license_plate' : fields.char('License Plate', size=32, required=True, help='License plate number of the vehicle (ie: plate number for a car)'),
471         'vin_sn' : fields.char('Chassis Number', size=32, required=False, help='Unique number written on the vehicle motor (VIN/SN number)'),
472         'driver' : fields.many2one('res.partner', 'Driver',required=False, help='Driver of the vehicle'),
473         'model_id' : fields.many2one('fleet.vehicle.model', 'Model', required=True, help='Model of the vehicle'),
474         'log_fuel' : fields.one2many('fleet.vehicle.log.fuel','vehicle_id', 'Fuel Logs'),
475         'log_services' : fields.one2many('fleet.vehicle.log.services','vehicle_id', 'Services Logs'),
476         'log_contracts' : fields.one2many('fleet.vehicle.log.contract','vehicle_id', 'Contracts'),
477         'acquisition_date' : fields.date('Acquisition Date', required=False, help='Date when the vehicle has been bought'),
478         'color' : fields.char('Color',size=32, help='Color of the vehicle'),
479         'state': fields.many2one('fleet.vehicle.state', 'State', help='Current state of the vehicle',ondelete="set null"),
480         'location' : fields.char('Location',size=32, help='Location of the vehicle (garage, ...)'),
481         'seats' : fields.integer('Seats Number', help='Number of seats of the vehicle'),
482         'doors' : fields.integer('Doors Number', help='Number of doors of the vehicle'),
483         'tag_ids' :fields.many2many('fleet.vehicle.tag','fleet_vehicle_vehicle_tag_rel','vehicle_tag_id','tag_id','Tags'),
484
485         'odometer' : fields.function(_get_odometer,fnct_inv=_set_odometer,type='char',string='Odometer Value',store=False,help='Odometer measure of the vehicle at the moment of this log'),
486         'odometer_unit': fields.selection([('kilometers', 'Kilometers'),('miles','Miles')], 'Odometer Unit', help='Unit of the odometer ',required=True),
487
488         'transmission' : fields.selection([('manual', 'Manual'),('automatic','Automatic')], 'Transmission', help='Transmission Used by the vehicle',required=False),
489         'fuel_type' : fields.selection([('gasoline', 'Gasoline'),('diesel','Diesel'),('electric','Electric'),('hybrid','Hybrid')], 'Fuel Type', help='Fuel Used by the vehicle',required=False),
490         'horsepower' : fields.integer('Horsepower',required=False),
491         'horsepower_tax': fields.float('Horsepower Taxation'),
492         'power' : fields.integer('Power (kW)',required=False,help='Power in kW of the vehicle'),
493         'co2' : fields.float('CO2 Emissions',required=False,help='CO2 emissions of the vehicle'),
494
495         'image': fields.related('model_id','image',type="binary",string="Logo",store=False),
496         'image_medium': fields.related('model_id','image_medium',type="binary",string="Logo",store=False),
497         'image_small': fields.related('model_id','image_small',type="binary",string="Logo",store=False),
498
499         'contract_renewal_due_soon' : fields.function(get_next_contract_reminder,fnct_search=_search_contract_renewal_due_soon,type="integer",string='Contracts to renew',store=False),
500         'contract_renewal_overdue' : fields.function(get_overdue_contract_reminder,fnct_search=_search_get_overdue_contract_reminder,type="integer",string='Contracts Overdued',store=False),
501         'contract_renewal_name' : fields.function(get_contract_renewal_names,type="text",string='Name of contract to renew soon',store=False),
502         'contract_renewal_total' : fields.function(get_total_contract_reminder,type="integer",string='Total of contracts due or overdue minus one',store=False),
503
504         'car_value': fields.float('Car Value', help='Value of the bought vehicle'),
505         #'leasing_value': fields.float('Leasing value',help='Value of the leasing(Monthly, usually'),
506         }
507
508     _defaults = {
509         'doors' : 5,
510         'odometer_unit' : 'kilometers',
511         'state' : lambda s,cr,uid,c:s.get_state(cr,uid,'Active',context=c),
512     }
513
514     def get_state(self,cr,uid,state_name,context=None):
515         states=self.pool.get('fleet.vehicle.state').search(cr,uid,[('name','=',state_name)],context=context,limit=1)
516         return states
517
518     def copy(self, cr, uid, id, default=None, context=None):
519         if not default:
520             default = {}
521
522         default.update({
523         #    'name': self.pool.get('ir.sequence').get(cr, uid, 'stock.orderpoint') or '',
524             #'log_ids':[],
525             'log_fuel':[],
526             'log_contracts':[],
527             'log_services':[],
528             'tag_ids':[],
529             'vin_sn':'',
530         })
531         return super(fleet_vehicle, self).copy(cr, uid, id, default, context=context)
532
533     def on_change_model(self, cr, uid, ids, model_id, context=None):
534
535         if not model_id:
536             return {}
537
538         model = self.pool.get('fleet.vehicle.model').browse(cr, uid, model_id, context=context)
539
540         return {
541             'value' : {
542                 'image' : model.image,
543             }
544         }
545     def create(self, cr, uid, data, context=None):
546         vehicle_id = super(fleet_vehicle, self).create(cr, uid, data, context=context)
547         try:
548             vehicle = self.browse(cr, uid, vehicle_id, context=context)
549             self.message_post(cr, uid, [vehicle_id], body='Vehicle %s has been added to the fleet!' % (vehicle.license_plate), context=context)
550         except:
551             pass # group deleted: do not push a message
552         return vehicle_id
553
554     def write(self, cr, uid, ids, vals, context=None):
555         changes = []
556         if 'driver' in vals:
557             value = self.pool.get('res.partner').browse(cr,uid,vals['driver'],context=context).name
558             olddriver = self.browse(cr, uid, ids, context)[0].driver
559             if olddriver:
560                 olddriver = olddriver.name
561             else:
562                 olddriver = 'None'
563             changes.append('Driver: from \'' + olddriver + '\' to \'' + value+'\'')
564         if 'state' in vals:
565             value = self.pool.get('fleet.vehicle.state').browse(cr,uid,vals['state'],context=context).name
566             oldstate = self.browse(cr, uid, ids, context)[0].state
567             if oldstate:
568                 oldstate=oldstate.name
569             else:
570                 oldstate = 'None'
571             changes.append('State: from \'' + oldstate + '\' to \'' + value+'\'')
572         if 'license_plate' in vals:
573             old_license_plate = self.browse(cr, uid, ids, context)[0].license_plate
574             if not old_license_plate:
575                 old_license_plate = 'None'
576             changes.append('License Plate: from \'' + old_license_plate + '\' to \'' + vals['license_plate']+'\'')   
577        
578         vehicle_id = super(fleet_vehicle,self).write(cr, uid, ids, vals, context)
579
580         try:
581             if len(changes) > 0:
582                 self.message_post(cr, uid, [self.browse(cr, uid, ids, context)[0].id], body=", ".join(changes), context=context)
583         except Exception as e:
584             print e
585             pass
586         return vehicle_id
587
588 ############################
589 ############################
590 #Vehicle.odometer class
591 ############################
592 ############################
593
594 class fleet_vehicle_odometer(osv.Model):
595     _name='fleet.vehicle.odometer'
596     _description='Odometer log for a vehicle'
597
598     _order='date desc'
599
600     def name_get(self, cr, uid, ids, context=None):
601         if context is None:
602             context = {}
603         if not ids:
604             return []
605         reads = self.browse(cr, uid, ids, context=context)
606         res = []
607         for record in reads:
608             if record.vehicle_id.name:
609                 name = str(record.vehicle_id.name)
610             if record.date:
611                 name = name+ ' / '+ str(record.date)
612             res.append((record.id, name))
613         return res
614
615     def _vehicle_log_name_get_fnc(self, cr, uid, ids, prop, unknow_none, context=None):
616         res = self.name_get(cr, uid, ids, context=context)
617         return dict(res)
618     def on_change_vehicle(self, cr, uid, ids, vehicle_id, context=None):
619
620         if not vehicle_id:
621             return {}
622
623         odometer_unit = self.pool.get('fleet.vehicle').browse(cr, uid, vehicle_id, context=context).odometer_unit
624
625         return {
626             'value' : {
627                 'unit' : odometer_unit,
628             }
629         }
630
631     _columns = {
632         'name' : fields.function(_vehicle_log_name_get_fnc, type="char", string='Name', store=True),
633
634         'date' : fields.date('Purchase Date'),
635         'value' : fields.float('Odometer Value',group_operator="max"),
636         'vehicle_id' : fields.many2one('fleet.vehicle', 'Vehicle', required=True),
637         'unit': fields.related('vehicle_id','odometer_unit',type="char",string="Unit",store=False, readonly=True),
638         
639     }
640     _defaults = {
641         'date' : time.strftime('%Y-%m-%d')
642     }
643
644 ############################
645 ############################
646 #Vehicle.log classes
647 ############################
648 ############################
649
650
651 ############################
652 ############################
653 #Vehicle.log.fuel class
654 ############################
655 ############################
656
657
658 class fleet_vehicle_log_fuel(osv.Model):
659
660     #_inherits = {'fleet.vehicle.odometer': 'odometer_id'}
661     _inherits = {'fleet.vehicle.cost': 'cost_id'}
662
663     def on_change_vehicle(self, cr, uid, ids, vehicle_id, context=None):
664
665         if not vehicle_id:
666             return {}
667
668         odometer_unit = self.pool.get('fleet.vehicle').browse(cr, uid, vehicle_id, context=context).odometer_unit
669
670         return {
671             'value' : {
672                 'odometer_unit' : odometer_unit,
673             }
674         }
675
676     def on_change_liter(self, cr, uid, ids, liter, price_per_liter, amount, context=None):
677
678         if liter > 0 and price_per_liter > 0:
679             return {'value' : {'amount' : float(liter) * float(price_per_liter),}}
680         elif liter > 0 and amount > 0:
681             return {'value' : {'price_per_liter' : float(amount) / float(liter),}}
682         elif price_per_liter > 0 and amount > 0:
683             return {'value' : {'liter' : float(amount) / float(price_per_liter),}}
684         else :
685             return {}
686
687     def on_change_price_per_liter(self, cr, uid, ids, liter, price_per_liter, amount, context=None):
688
689         liter = float(liter);
690         price_per_liter = float(price_per_liter);
691         if price_per_liter > 0 and liter > 0:
692             return {'value' : {'amount' : float(liter) * float(price_per_liter),}}
693         elif price_per_liter > 0 and amount > 0:
694             return {'value' : {'liter' : float(amount) / float(price_per_liter),}}
695         elif liter > 0 and amount > 0:
696             return {'value' : {'price_per_liter' : float(amount) / float(liter),}}
697         else :
698             return {}
699
700     def on_change_amount(self, cr, uid, ids, liter, price_per_liter, amount, context=None):
701
702         if amount > 0 and liter > 0:
703             return {'value' : {'price_per_liter' : float(amount) / float(liter),}}
704         elif amount > 0 and price_per_liter > 0:
705             return {'value' : {'liter' : float(amount) / float(price_per_liter),}}
706         elif liter > 0 and price_per_liter > 0:
707             return {'value' : {'amount' : float(liter) * float(price_per_liter),}}
708         else :
709             return {}
710         
711     def _get_odometer(self, cr, uid, ids, odometer_id, arg, context):
712         res = dict.fromkeys(ids, False)
713         for record in self.browse(cr,uid,ids,context=context):
714             if record.odometer_id:
715                 res[record.id] = record.odometer_id.value
716         return res
717
718     def _set_odometer(self, cr, uid, id, name, value, args=None, context=None):
719         if value:
720             try:
721                 value = float(value)
722             except ValueError:
723                 #_logger.exception(value+' is not a correct odometer value. Please, fill a float for this field')
724                 raise except_orm(_('Error!'), value+' is not a correct odometer value. Please, fill a float for this field')
725                
726             date = self.browse(cr, uid, id, context=context).date
727             if not(date):
728                 date = time.strftime('%Y-%m-%d')
729             vehicle_id = self.browse(cr, uid, id, context=context).vehicle_id
730             data = {'value' : value,'date' : date,'vehicle_id' : vehicle_id.id}
731             odometer_id = self.pool.get('fleet.vehicle.odometer').create(cr, uid, data, context=context)
732             self.write(cr, uid, id, {'odometer_id': odometer_id})
733             return value
734         self.write(cr, uid, id, {'odometer_id': ''})
735         return False
736
737     def _get_default_service_type(self, cr, uid, context):
738         model, model_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'fleet', 'type_service_refueling')
739         return model_id
740
741     _name = 'fleet.vehicle.log.fuel'
742
743     _columns = {
744         #'name' : fields.char('Name',size=64),
745         'liter' : fields.float('Liter'),
746         'price_per_liter' : fields.float('Price Per Liter'),
747         'purchaser_id' : fields.many2one('res.partner', 'Purchaser',domain="['|',('customer','=',True),('employee','=',True)]"),
748         'inv_ref' : fields.char('Invoice Reference', size=64),
749         'vendor_id' : fields.many2one('res.partner', 'Supplier', domain="[('supplier','=',True)]"),
750         'notes' : fields.text('Notes'),
751         'odometer_id' : fields.many2one('fleet.vehicle.odometer', 'Odometer', required=False, help='Odometer measure of the vehicle at the moment of this log'),
752         'odometer' : fields.function(_get_odometer,fnct_inv=_set_odometer,type='char',string='Odometer',store=False),
753         'odometer_unit': fields.related('vehicle_id','odometer_unit',type="char",string="Unit",store=False, readonly=True),
754         'cost_amount': fields.related('cost_id','amount',type="float",string="Amount",store=True, readonly=True),
755     }
756     _defaults = {
757         'purchaser_id': lambda self, cr, uid, ctx: uid,
758         'date' : time.strftime('%Y-%m-%d'),
759         'cost_type': _get_default_service_type,
760     }
761
762 ############################
763 ############################
764 #Vehicle.log.service class
765 ############################
766 ############################
767
768
769 class fleet_vehicle_log_services(osv.Model):
770
771     _inherits = {'fleet.vehicle.cost': 'cost_id'}
772
773     def on_change_vehicle(self, cr, uid, ids, vehicle_id, context=None):
774
775         if not vehicle_id:
776             return {}
777
778         odometer_unit = self.pool.get('fleet.vehicle').browse(cr, uid, vehicle_id, context=context).odometer_unit
779
780         return {
781             'value' : {
782                 'odometer_unit' : odometer_unit,
783             }
784         }
785
786     def _get_odometer(self, cr, uid, ids, odometer_id, arg, context):
787         res = dict.fromkeys(ids, False)
788         for record in self.browse(cr,uid,ids,context=context):
789             if record.odometer_id:
790                 res[record.id] = record.odometer_id.value
791         return res
792
793     def _set_odometer(self, cr, uid, id, name, value, args=None, context=None):
794         if value:
795             try:
796                 value = float(value)
797             except ValueError:
798                 #_logger.exception(value+' is not a correct odometer value. Please, fill a float for this field')
799                 raise except_orm(_('Error!'), value+' is not a correct odometer value. Please, fill a float for this field')
800                
801             date = self.browse(cr, uid, id, context=context).date
802             if not(date):
803                 date = time.strftime('%Y-%m-%d')
804             vehicle_id = self.browse(cr, uid, id, context=context).vehicle_id
805             data = {'value' : value,'date' : date,'vehicle_id' : vehicle_id.id}
806             odometer_id = self.pool.get('fleet.vehicle.odometer').create(cr, uid, data, context=context)
807             self.write(cr, uid, id, {'odometer_id': odometer_id})
808             return value
809         self.write(cr, uid, id, {'odometer_id': ''})
810         return False
811
812     _name = 'fleet.vehicle.log.services'
813     _columns = {
814
815         #'name' : fields.char('Name',size=64),
816
817         'purchaser_id' : fields.many2one('res.partner', 'Purchaser',domain="['|',('customer','=',True),('employee','=',True)]"),
818         'inv_ref' : fields.char('Invoice Reference', size=64),
819         'vendor_id' :fields.many2one('res.partner', 'Supplier', domain="[('supplier','=',True)]"),
820         'notes' : fields.text('Notes'),
821
822         'odometer_id' : fields.many2one('fleet.vehicle.odometer', 'Odometer', required=False, help='Odometer measure of the vehicle at the moment of this log'),
823         'odometer' : fields.function(_get_odometer,fnct_inv=_set_odometer,type='char',string='Odometer Value',store=False),
824         'odometer_unit': fields.related('vehicle_id','odometer_unit',type="char",string="Unit",store=False, readonly=True),
825         'cost_amount': fields.related('cost_id','amount',type="float",string="Amount",store=True, readonly=True),
826     }
827     _defaults = {
828         'purchaser_id': lambda self, cr, uid, ctx: uid,
829         'date' : time.strftime('%Y-%m-%d'),
830     }
831
832 ############################
833 ############################
834 #Vehicle.service.type class
835 ############################
836 ############################
837
838 class fleet_service_type(osv.Model):
839     _name = 'fleet.service.type'
840     _columns = {
841         'name': fields.char('Name', required=True, translate=True),
842         'category': fields.selection([('contract', 'Contract'), ('service', 'Service'),('both', 'Both')], 'Category',required=True, help='Choose wheter the service refer to contracts, vehicle services or both'),
843     }
844     #_defaults = {
845     #    'category': 'both'
846     #}
847
848 ############################
849 ############################
850 #Vehicle.log.contract class
851 ############################
852 ############################
853
854 class fleet_vehicle_log_contract(osv.Model):
855
856     _inherits = {'fleet.vehicle.cost': 'cost_id'}
857
858     def run_scheduler(self,cr,uid,context=None):
859
860         d = datetime.date.today()
861         #d = datetime.datetime(2012, 12, 01)
862
863         contract_ids = self.pool.get('fleet.vehicle.log.contract').search(cr, uid, [('state','=','open')], offset=0, limit=None, order=None,context=None, count=False)
864         for contract in self.pool.get('fleet.vehicle.log.contract').browse(cr,uid,contract_ids,context=context):
865             if not contract.start_date:
866                 break;
867             if contract.generated_cost_ids != []:
868                 last_cost_id = self.pool.get('fleet.vehicle.cost').search(cr, uid, ['&',('contract_id','=',contract.id),('auto_generated','=',True)], offset=0, limit=1, order='date desc',context=None, count=False)
869                 last_cost_date = self.pool.get('fleet.vehicle.cost').browse(cr,uid,last_cost_id[0],context=None).date
870                 found = True
871             else : 
872                 found = False
873                 last_cost_date = contract.start_date
874             startdate = datetime.datetime.strptime(last_cost_date,'%Y-%m-%d').date() 
875             if contract.cost_frequency == 'yearly':
876                 delta = relativedelta(years=+1)
877             elif contract.cost_frequency == 'monthly':
878                 delta = relativedelta(months=+1)
879             elif contract.cost_frequency == 'weekly':
880                 delta = relativedelta(weeks=+1)
881             elif contract.cost_frequency == 'daily':
882                 delta = relativedelta(days=+1)
883             elif contract.cost_frequency == 'no':
884                 break;
885             if found:
886                 startdate += delta
887             while startdate < d:
888                 data = {'amount' : contract.cost_generated,'date' : startdate.strftime('%Y-%m-%d'),'vehicle_id' : contract.vehicle_id.id,'cost_type' : contract.cost_type.id,'contract_id' : contract.id,'auto_generated' : True}
889                 print data
890                 cost_id = self.pool.get('fleet.vehicle.cost').create(cr, uid, data, context=context)
891                 startdate += delta
892         return True
893     
894     def name_get(self, cr, uid, ids, context=None):
895         if context is None:
896             context = {} 
897         if not ids:
898             return []
899         reads = self.browse(cr, uid, ids, context=context)
900         res = []
901         for record in reads:
902             if record.vehicle_id.name:
903                 name = str(record.vehicle_id.name)
904             if record.cost_type.name:
905                 name = name+ ' / '+ str(record.cost_type.name)
906             if record.date:
907                 name = name+ ' / '+ record.date
908             res.append((record.id, name))
909         return res
910
911     def _vehicle_contract_name_get_fnc(self, cr, uid, ids, prop, unknow_none, context=None):
912         res = self.name_get(cr, uid, ids, context=context)
913         return dict(res)
914
915     def _get_odometer(self, cr, uid, ids, odometer_id, arg, context):
916         res = dict.fromkeys(ids, False)
917         for record in self.browse(cr,uid,ids,context=context):
918             if record.odometer_id:
919                 res[record.id] = record.odometer_id.value
920         return res
921
922     def _set_odometer(self, cr, uid, id, name, value, args=None, context=None):
923         if value:
924             try:
925                 value = float(value)
926             except ValueError:
927                 #_logger.exception(value+' is not a correct odometer value. Please, fill a float for this field')
928                 raise except_orm(_('Error!'), value+' is not a correct odometer value. Please, fill a float for this field')
929                
930             date = self.browse(cr, uid, id, context=context).date
931             if not(date):
932                 date = time.strftime('%Y-%m-%d')
933             vehicle_id = self.browse(cr, uid, id, context=context).vehicle_id
934             data = {'value' : value,'date' : date,'vehicle_id' : vehicle_id.id}
935             odometer_id = self.pool.get('fleet.vehicle.odometer').create(cr, uid, data, context=context)
936             self.write(cr, uid, id, {'odometer_id': odometer_id})
937             return value
938         self.write(cr, uid, id, {'odometer_id': ''})
939         return False
940
941     def on_change_vehicle(self, cr, uid, ids, vehicle_id, context=None):
942
943         if not vehicle_id:
944             return {}
945
946         odometer_unit = self.pool.get('fleet.vehicle').browse(cr, uid, vehicle_id, context=context).odometer_unit
947
948         return {
949             'value' : {
950                 'odometer_unit' : odometer_unit,
951             }
952         }
953
954     def _hide_generated_cost(self, cursor, user, ids, name, arg, context=None):
955         res = {}
956         for contract in self.browse(cursor, user, ids, context=context):
957             res[contract.id] = (contract.cost_frequency == 'no')
958         return res
959
960     def compute_next_year_date(self, strdate):
961         oneyear=datetime.timedelta(days=365)
962         curdate = self.str_to_date(strdate)
963         nextyear=curdate+oneyear#int(strdate[:4])+1
964         return str(nextyear)#+strdate[4:]
965
966     def on_change_start_date(self, cr, uid, ids, strdate, enddate, context=None):
967         
968         if (strdate):
969            
970             return {'value' : {'expiration_date' : self.compute_next_year_date(strdate),}}
971         else:
972             return {}
973
974     def str_to_date(self,strdate):
975         return datetime.datetime(int(strdate[:4]),int(strdate[5:7]),int(strdate[8:]))
976
977     def get_warning_date(self,cr,uid,ids,prop,unknow_none,context=None):
978         if context is None:
979             context={}
980         if not ids:
981             return dict([])
982         reads = self.browse(cr,uid,ids,context=context)
983         res=[]
984         for record in reads:
985             #if (record.reminder==True):
986             if (record.expiration_date and record.state=='open'):
987                 today=self.str_to_date(time.strftime('%Y-%m-%d'))
988                 renew_date = self.str_to_date(record.expiration_date)
989                 diff_time=int((renew_date-today).days)
990                 if (diff_time<=0):
991                     res.append((record.id,0))
992                 else:
993                     res.append((record.id,diff_time))
994             else:
995                 res.append((record.id,-1))
996             #else:
997             #    res.append((record.id,-1))
998         return dict(res)
999
1000     def act_renew_contract(self,cr,uid,ids,context=None):
1001         contracts = self.browse(cr,uid,ids,context=context)
1002         res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid ,'fleet','act_renew_contract', context)
1003         for element in contracts:
1004             temp = []
1005             temp.append(('default_vehicle_id',element.vehicle_id.id))
1006             temp.append(('default_cost_type',element.cost_type.id))
1007             temp.append(('default_amount',element.amount))
1008             temp.append(('default_odometer_id',element.odometer_id.id))
1009             temp.append(('default_odometer_unit',element.odometer_unit))
1010             temp.append(('default_insurer_id',element.insurer_id.id))
1011             cost_temp = []
1012             for costs in element.cost_ids:
1013                 cost_temp.append(costs.id)
1014             temp.append(('default_cost_ids',cost_temp))
1015             temp.append(('default_date',time.strftime('%Y-%m-%d')))
1016             temp.append(('default_start_date',str(self.str_to_date(element.expiration_date)+datetime.timedelta(days=1))))
1017             temp.append(('default_purchaser_id',element.purchaser_id.id))
1018             temp.append(('default_ins_ref',element.ins_ref))
1019             temp.append(('default_state','open'))
1020             temp.append(('default_notes',element.notes))
1021             temp.append(('default_cost_frequency',element.cost_frequency))
1022             generated_cost = []
1023             for gen_cost in element.generated_cost_ids:
1024                 generated_cost.append(gen_cost.id)
1025             temp.append(('default_generated_cost_ids',generated_cost))
1026
1027             #compute end date
1028             startdate = self.str_to_date(element.start_date)
1029             enddate = self.str_to_date(element.expiration_date)
1030             diffdate = (enddate-startdate)
1031             newenddate = enddate+diffdate
1032             temp.append(('default_expiration_date',str(newenddate)))
1033
1034             res['context'] = dict(temp)
1035             
1036         return res
1037
1038         
1039
1040     _name = 'fleet.vehicle.log.contract'
1041     _order='state,expiration_date'
1042     _columns = {
1043         'name' : fields.function(_vehicle_contract_name_get_fnc, type="text", string='Name', store=True),
1044
1045         'start_date' : fields.date('Contract Start Date', required=False, help='Date when the coverage of the contract begins'),
1046         'expiration_date' : fields.date('Contract Expiration Date', required=False, help='Date when the coverage of the contract expirates (by default, one year after begin date)'),
1047         'warning_date' : fields.function(get_warning_date,type='integer',string='Warning Date',store=False),
1048
1049         'insurer_id' :fields.many2one('res.partner', 'Supplier', domain="[('supplier','=',True)]"),
1050         'purchaser_id' : fields.many2one('res.partner', 'Contractor',domain="['|',('customer','=',True),('employee','=',True)]",help='Person to which the contract is signed for'),
1051         'ins_ref' : fields.char('Contract Reference', size=64),
1052         'state' : fields.selection([('open', 'In Progress'), ('closed', 'Terminated')], 'Status', readonly=True, help='Choose wheter the contract is still valid or not'),
1053         #'reminder' : fields.boolean('Renewal Reminder', help="Warn the user a few days before the expiration date of this contract"),
1054         'notes' : fields.text('Terms and Conditions', help='Write here all supplementary informations relative to this contract'),
1055         'odometer_id' : fields.many2one('fleet.vehicle.odometer', 'Odometer', required=False, help='Odometer measure of the vehicle at the moment of this log'),
1056         'odometer' : fields.function(_get_odometer,fnct_inv=_set_odometer,type='char',string='Odometer Value',store=False,help='Odometer measure of the vehicle at the moment of this log'),
1057         'odometer_unit': fields.related('vehicle_id','odometer_unit',type="char",string="Unit",store=False, readonly=True),
1058         'cost_amount': fields.related('cost_id','amount',type="float",string="Amount",store=True, readonly=True),
1059         'cost_generated': fields.float('Recuring Costs Amount',help="Costs paid at regular intervals, depending on the cost frequency. If the cost frequency is set to unique, the cost will be logged at the start date"),
1060         'cost_frequency': fields.selection([('no','No'),('daily', 'Daily'),('weekly','Weekly'),('monthly','Monthly'),('yearly','Yearly')], 'Cost Frequency', help='Frequency of the costs',required=True),
1061         'generated_cost_ids' : fields.one2many('fleet.vehicle.cost', 'contract_id', 'Generated Costs',ondelete='cascade'),
1062         'hide_generated_cost': fields.function(_hide_generated_cost, string='Frequency', type='boolean', help='This field is for internal purpose. It is used to decide if the field generated_cost lot has to be shown on the moves or not.'),
1063     }
1064     _defaults = {
1065         'purchaser_id': lambda self, cr, uid, ctx: uid,
1066         'date' : time.strftime('%Y-%m-%d'),
1067         'start_date' : time.strftime('%Y-%m-%d'),
1068         'state':'open',
1069         'expiration_date' : lambda self,cr,uid,ctx: self.compute_next_year_date(time.strftime('%Y-%m-%d')),
1070     
1071     }
1072
1073     def copy(self, cr, uid, id, default=None, context=None):
1074         default = default or {}
1075         current_object = self.browse(cr,uid,id,context)
1076         default['date'] = time.strftime('%Y-%m-%d')
1077         default['start_date'] = time.strftime('%Y-%m-%d')
1078         default['expiration_date'] = self.compute_next_year_date(time.strftime('%Y-%m-%d'))
1079         #default['name'] = current_object.name
1080         default['ins_ref'] = ''
1081         default['state'] = 'open'
1082         default['notes'] = ''
1083         default['date'] = time.strftime('%Y-%m-%d')
1084
1085         #default['odometer'] = current_object.odometer
1086         #default['odometer_unit'] = current_object.odometer_unit
1087         return super(fleet_vehicle_log_contract, self).copy(cr, uid, id, default, context=context)
1088
1089     def contract_close(self, cr, uid, ids, *args):
1090         self.write(cr, uid, ids, {'state': 'closed'})
1091         return True
1092
1093     def contract_open(self, cr, uid, ids, *args):
1094         self.write(cr, uid, ids, {'state': 'open'})
1095         return True
1096
1097
1098 ############################
1099 ############################
1100 #Vehicle.log.contract.state class
1101 ############################
1102 ############################
1103
1104 class fleet_contract_state(osv.Model):
1105     _name = 'fleet.contract.state'
1106     _columns = {
1107         'name':fields.char('Contract Status',size=32),
1108     }