1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
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
26 from osv.orm import except_orm
27 from tools.translate import _
28 from dateutil.relativedelta import relativedelta
30 def str_to_date(strdate):
31 return datetime.datetime.strptime(strdate, tools.DEFAULT_SERVER_DATE_FORMAT)
33 class fleet_vehicle_cost(osv.Model):
34 _name = 'fleet.vehicle.cost'
35 _description = 'Cost related to a vehicle'
36 _order = 'date desc, vehicle_id asc'
38 def _get_odometer(self, cr, uid, ids, odometer_id, arg, context):
39 res = dict.fromkeys(ids, False)
40 for record in self.browse(cr,uid,ids,context=context):
41 if record.odometer_id:
42 res[record.id] = record.odometer_id.value
45 def _set_odometer(self, cr, uid, id, name, value, args=None, context=None):
47 raise except_orm(_('Operation not allowed!'), _('Emptying the odometer value of a vehicle is not allowed.'))
48 date = self.browse(cr, uid, id, context=context).date
50 date = fields.date.context_today(self, cr, uid, context=context)
51 vehicle_id = self.browse(cr, uid, id, context=context).vehicle_id
52 data = {'value': value, 'date': date, 'vehicle_id': vehicle_id.id}
53 odometer_id = self.pool.get('fleet.vehicle.odometer').create(cr, uid, data, context=context)
54 return self.write(cr, uid, id, {'odometer_id': odometer_id}, context=context)
56 def _year_get_fnc(self, cr, uid, ids, name, unknow_none, context=None):
58 for record in self.browse(cr, uid, ids, context=context):
59 res[record.id] = str(time.strptime(record.date, tools.DEFAULT_SERVER_DATE_FORMAT).tm_year) #TODO: why is it a char?
62 def _cost_name_get_fnc(self, cr, uid, ids, name, unknow_none, context=None):
64 for record in self.browse(cr, uid, ids, context=context):
65 name = record.vehicle_id.name
66 if record.cost_subtype.name:
67 name += ' / '+ record.cost_subtype.name
69 name += ' / '+ record.date
74 'name': fields.function(_cost_name_get_fnc, type="char", string='Name', store=True),
75 'vehicle_id': fields.many2one('fleet.vehicle', 'Vehicle', required=True, help='Vehicle concerned by this log'),
76 'cost_subtype': fields.many2one('fleet.service.type', 'Type', help='Cost type purchased with this cost'),
77 'amount': fields.float('Total Price'),
78 'cost_type': fields.selection([('contract', 'Contract'), ('services','Services'), ('fuel','Fuel'), ('other','Other')], 'Category of the cost', help='For internal purpose only', required=True),
79 'parent_id': fields.many2one('fleet.vehicle.cost', 'Parent', help='Parent cost to this current cost'),
80 'cost_ids': fields.one2many('fleet.vehicle.cost', 'parent_id', 'Included Services'),
81 'odometer_id': fields.many2one('fleet.vehicle.odometer', 'Odometer', help='Odometer measure of the vehicle at the moment of this log'),
82 'odometer': fields.function(_get_odometer, fnct_inv=_set_odometer, type='float', string='Odometer Value', help='Odometer measure of the vehicle at the moment of this log'),
83 'odometer_unit': fields.related('vehicle_id', 'odometer_unit', type="char", string="Unit", readonly=True),
84 'date' :fields.date('Date',help='Date when the cost has been executed'),
85 'contract_id': fields.many2one('fleet.vehicle.log.contract', 'Contract', help='Contract attached to this cost'),
86 'auto_generated': fields.boolean('automatically generated', readonly=True, required=True),
87 'year': fields.function(_year_get_fnc, type="char", string='Year', store=True),
94 def create(self, cr, uid, data, context=None):
95 #TODO: should be managed by onchanges() rather by this
96 if 'parent_id' in data and data['parent_id']:
97 parent = self.browse(cr, uid, data['parent_id'], context=context)
98 data['vehicle_id'] = parent.vehicle_id.id
99 data['date'] = parent.date
100 data['cost_type'] = parent.cost_type
101 if 'contract_id' in data and data['contract_id']:
102 contract = self.pool.get('fleet.vehicle.log.contract').browse(cr, uid, data['contract_id'], context=context)
103 data['vehicle_id'] = contract.vehicle_id.id
104 data['cost_subtype'] = contract.cost_subtype.id
105 data['cost_type'] = contract.cost_type
106 return super(fleet_vehicle_cost, self).create(cr, uid, data, context=context)
109 class fleet_vehicle_tag(osv.Model):
110 _name = 'fleet.vehicle.tag'
112 'name': fields.char('Name', required=True, translate=True),
116 class fleet_vehicle_state(osv.Model):
117 _name = 'fleet.vehicle.state'
118 _order = 'sequence asc'
120 'name': fields.char('Name', required=True),
121 'sequence': fields.integer('Order', help="Used to order the note stages")
123 _sql_constraints = [('fleet_state_name_unique','unique(name)', 'State name already exists')]
126 class fleet_vehicle_model(osv.Model):
128 def _model_name_get_fnc(self, cr, uid, ids, field_name, arg, context=None):
130 for record in self.browse(cr, uid, ids, context=context):
131 name = record.modelname
132 if record.brand.name:
133 name = record.brand.name+' / '+name
134 res[record.id] = name
137 def on_change_brand(self, cr, uid, ids, model_id, context=None):
139 return {'value': {'image_medium': False}}
140 brand = self.pool.get('fleet.vehicle.model.brand').browse(cr, uid, model_id, context=context)
143 'image_medium': brand.image,
147 _name = 'fleet.vehicle.model'
148 _description = 'Model of a vehicle'
152 'name': fields.function(_model_name_get_fnc, type="char", string='Name', store=True),
153 'modelname': fields.char('Model name', size=32, required=True),
154 'brand': fields.many2one('fleet.vehicle.model.brand', 'Model Brand', required=True, help='Brand of the vehicle'),
155 'vendors': fields.many2many('res.partner', 'fleet_vehicle_model_vendors', 'model_id', 'partner_id', string='Vendors'),
156 'image': fields.related('brand', 'image', type="binary", string="Logo"),
157 'image_medium': fields.related('brand', 'image_medium', type="binary", string="Logo"),
158 'image_small': fields.related('brand', 'image_small', type="binary", string="Logo"),
162 class fleet_vehicle_model_brand(osv.Model):
163 _name = 'fleet.vehicle.model.brand'
164 _description = 'Brand model of the vehicle'
168 def _get_image(self, cr, uid, ids, name, args, context=None):
169 result = dict.fromkeys(ids, False)
170 for obj in self.browse(cr, uid, ids, context=context):
171 result[obj.id] = tools.image_get_resized_images(obj.image)
174 def _set_image(self, cr, uid, id, name, value, args, context=None):
175 return self.write(cr, uid, [id], {'image': tools.image_resize_image_big(value)}, context=context)
178 'name': fields.char('Brand Name', size=64, required=True),
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",
184 'fleet.vehicle.model.brand': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
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",
192 'fleet.vehicle.model.brand': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
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."),
200 class fleet_vehicle(osv.Model):
202 _inherit = 'mail.thread'
204 def _vehicle_name_get_fnc(self, cr, uid, ids, prop, unknow_none, context=None):
206 for record in self.browse(cr, uid, ids, context=context):
207 res[record.id] = record.model_id.brand.name + '/' + record.model_id.modelname + ' / ' + record.license_plate
210 def return_action_to_open(self, cr, uid, ids, context=None):
211 """ This opens the xml view specified in xml_id for the current vehicle """
212 if context['xml_id']:
213 res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid ,'fleet', context['xml_id'], context=context)
215 'default_vehicle_id': ids[0]
217 res['domain']=[('vehicle_id','=', ids[0])]
222 def act_show_log_cost(self, cr, uid, ids, context=None):
223 """ This opens log view to view and add new log for this vehicle, groupby default to only show effective costs
224 @return: the costs log view
226 res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid ,'fleet','fleet_vehicle_costs_act', context)
228 'default_vehicle_id': ids[0],
229 'search_default_parent_false' : True
231 res['domain']=[('vehicle_id','=', ids[0])]
234 def _get_odometer(self, cr, uid, ids, odometer_id, arg, context):
235 res = dict.fromkeys(ids, 0)
236 for record in self.browse(cr,uid,ids,context=context):
237 ids = self.pool.get('fleet.vehicle.odometer').search(cr, uid, [('vehicle_id', '=', record.id)], limit=1, order='value desc')
239 res[record.id] = self.pool.get('fleet.vehicle.odometer').browse(cr, uid, ids[0], context=context).value
242 def _set_odometer(self, cr, uid, id, name, value, args=None, context=None):
244 date = fields.date.context_today(self, cr, uid, context=context)
245 data = {'value': value, 'date': date, 'vehicle_id': id}
246 return self.pool.get('fleet.vehicle.odometer').create(cr, uid, data, context=context)
248 def _search_get_overdue_contract_reminder(self, cr, uid, obj, name, args, context):
250 for field, operator, value in args:
251 vehicle_ids = self.search(cr, uid, [])
252 contracts_needed = self._get_contract_reminder_fnc(cr,uid,vehicle_ids,['contract_renewal_total', 'contract_renewal_due_soon', 'contract_renewal_overdue', 'contract_renewal_name'],None,context=context)
254 for renew_key,renew_value in contracts_needed.items():
255 if eval(str(renew_value['contract_renewal_overdue']) + " " + str(operator) + " " + str(value)):
256 res_ids.append(renew_key)
257 res.append(('id', 'in', res_ids))
260 def _search_contract_renewal_due_soon(self, cr, uid, obj, name, args, context):
262 for field, operator, value in args:
263 vehicle_ids = self.search(cr, uid, [])
264 contracts_needed = self._get_contract_reminder_fnc(cr,uid,vehicle_ids,['contract_renewal_total', 'contract_renewal_due_soon', 'contract_renewal_overdue', 'contract_renewal_name'],None,context=context)
266 for renew_key,renew_value in contracts_needed.items():
267 if eval(str(renew_value['contract_renewal_due_soon']) + " " + str(operator) + " " + str(value)):
268 res_ids.append(renew_key)
269 res.append(('id', 'in', res_ids))
272 def _get_contract_reminder_fnc(self, cr, uid, ids, prop, unknow_none, context=None):
274 for record in self.browse(cr, uid, ids, context=context):
278 for element in record.log_contracts:
279 if (element.state in ('open', 'toclose') and element.expiration_date):
280 current_date_str = fields.date.context_today(self, cr, uid, context=context)
281 due_time_str = element.expiration_date
282 current_date = str_to_date(current_date_str)
283 due_time = str_to_date(due_time_str)
284 diff_time = (due_time-current_date).days
287 if diff_time<15 and diff_time>=0:
288 due_soon = due_soon +1;
289 if overdue+due_soon>0:
290 ids = self.pool.get('fleet.vehicle.log.contract').search(cr,uid,[('vehicle_id','=',record.id),'|',('state','=','open'),('state','=','toclose')],limit=1,order='expiration_date asc')
292 name=(self.pool.get('fleet.vehicle.log.contract').browse(cr,uid,ids[0],context=context).cost_subtype.name)
295 'contract_renewal_overdue':overdue,
296 'contract_renewal_due_soon':due_soon,
297 'contract_renewal_total':(overdue+due_soon-1),
298 'contract_renewal_name':name,
302 def run_scheduler(self, cr, uid, context=None):
303 datetime_today = datetime.datetime.strptime(fields.date.context_today(self, cr, uid, context=context), tools.DEFAULT_SERVER_DATE_FORMAT)
304 limit_date = (datetime_today + relativedelta(days=+15)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
305 ids = self.pool.get('fleet.vehicle.log.contract').search(cr, uid, ['&', ('state', '=', 'open'), ('expiration_date', '<', limit_date)], offset=0, limit=None, order=None, context=context, count=False)
307 for contract in self.pool.get('fleet.vehicle.log.contract').browse(cr, uid, ids, context=context):
308 if contract.vehicle_id.id in res:
309 res[contract.vehicle_id.id] += 1
311 res[contract.vehicle_id.id] = 1
313 for vehicle, value in res.items():
314 self.message_post(cr, uid, vehicle, body=_('%s contract(s) need(s) to be renewed and/or closed!') % (str(value)), context=context)
316 return self.pool.get('fleet.vehicle.log.contract').write(cr, uid, ids, {'state': 'toclose'}, context=context)
318 def _get_default_state(self, cr, uid, context):
320 model, model_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'fleet', 'vehicle_state_active')
325 _name = 'fleet.vehicle'
326 _description = 'Information on a vehicle'
327 _order= 'license_plate asc'
329 'name': fields.function(_vehicle_name_get_fnc, type="char", string='Name', store=True),
330 'company_id': fields.many2one('res.company', 'Company'),
331 'license_plate': fields.char('License Plate', size=32, required=True, help='License plate number of the vehicle (ie: plate number for a car)'),
332 'vin_sn': fields.char('Chassis Number', size=32, help='Unique number written on the vehicle motor (VIN/SN number)'),
333 'driver': fields.many2one('res.partner', 'Driver', help='Driver of the vehicle'),
334 'model_id': fields.many2one('fleet.vehicle.model', 'Model', required=True, help='Model of the vehicle'),
335 'log_fuel': fields.one2many('fleet.vehicle.log.fuel', 'vehicle_id', 'Fuel Logs'),
336 'log_services': fields.one2many('fleet.vehicle.log.services', 'vehicle_id', 'Services Logs'),
337 'log_contracts': fields.one2many('fleet.vehicle.log.contract', 'vehicle_id', 'Contracts'),
338 'acquisition_date': fields.date('Acquisition Date', required=False, help='Date when the vehicle has been bought'),
339 'color': fields.char('Color', size=32, help='Color of the vehicle'),
340 'state': fields.many2one('fleet.vehicle.state', 'State', help='Current state of the vehicle', ondelete="set null"),
341 'location': fields.char('Location', size=128, help='Location of the vehicle (garage, ...)'),
342 'seats': fields.integer('Seats Number', help='Number of seats of the vehicle'),
343 'doors': fields.integer('Doors Number', help='Number of doors of the vehicle'),
344 'tag_ids' :fields.many2many('fleet.vehicle.tag', 'fleet_vehicle_vehicle_tag_rel', 'vehicle_tag_id','tag_id', 'Tags'),
345 'odometer': fields.function(_get_odometer, fnct_inv=_set_odometer, type='float', string='Odometer Value', help='Odometer measure of the vehicle at the moment of this log'),
346 'odometer_unit': fields.selection([('kilometers', 'Kilometers'),('miles','Miles')], 'Odometer Unit', help='Unit of the odometer ',required=True),
347 'transmission': fields.selection([('manual', 'Manual'), ('automatic', 'Automatic')], 'Transmission', help='Transmission Used by the vehicle'),
348 'fuel_type': fields.selection([('gasoline', 'Gasoline'), ('diesel', 'Diesel'), ('electric', 'Electric'), ('hybrid', 'Hybrid')], 'Fuel Type', help='Fuel Used by the vehicle'),
349 'horsepower': fields.integer('Horsepower'),
350 'horsepower_tax': fields.float('Horsepower Taxation'),
351 'power': fields.integer('Power (kW)', help='Power in kW of the vehicle'),
352 'co2': fields.float('CO2 Emissions', help='CO2 emissions of the vehicle'),
353 'image': fields.related('model_id', 'image', type="binary", string="Logo"),
354 'image_medium': fields.related('model_id', 'image_medium', type="binary", string="Logo"),
355 'image_small': fields.related('model_id', 'image_small', type="binary", string="Logo"),
356 'contract_renewal_due_soon': fields.function(_get_contract_reminder_fnc, fnct_search=_search_contract_renewal_due_soon, type="integer", string='Contracts to renew', multi='contract_info'),
357 'contract_renewal_overdue': fields.function(_get_contract_reminder_fnc, fnct_search=_search_get_overdue_contract_reminder, type="integer", string='Contracts Overdued', multi='contract_info'),
358 'contract_renewal_name': fields.function(_get_contract_reminder_fnc, type="text", string='Name of contract to renew soon', multi='contract_info'),
359 'contract_renewal_total': fields.function(_get_contract_reminder_fnc, type="integer", string='Total of contracts due or overdue minus one', multi='contract_info'),
360 'car_value': fields.float('Car Value', help='Value of the bought vehicle'),
365 'odometer_unit': 'kilometers',
366 'state': _get_default_state,
369 def copy(self, cr, uid, id, default=None, context=None):
379 return super(fleet_vehicle, self).copy(cr, uid, id, default, context=context)
381 def on_change_model(self, cr, uid, ids, model_id, context=None):
384 model = self.pool.get('fleet.vehicle.model').browse(cr, uid, model_id, context=context)
387 'image_medium': model.image,
391 def create(self, cr, uid, data, context=None):
392 #TODO: why is there a try..except? No idea, this is denis code, we should ask him
393 vehicle_id = super(fleet_vehicle, self).create(cr, uid, data, context=context)
395 vehicle = self.browse(cr, uid, vehicle_id, context=context)
396 self.message_post(cr, uid, [vehicle_id], body=_('Vehicle %s has been added to the fleet!') % (vehicle.license_plate), context=context)
398 pass # group deleted: do not push a message
401 def write(self, cr, uid, ids, vals, context=None):
403 #TODO: use _() to translate labels
404 #TODO: why is there a try..except?
405 #TODO: shorten the code (e.g: oldmodel = vehicle.model_id and olmodel.model_id.name or _('None')
406 #TODO: in PEP 8 standard, a coma should be followed by a space, '+' operator and equal sign should be in between 2 spaces
407 #TODO: you're looping on `ids´, and in this loop you're writing again and posting logs on `ids´. Use message_post only on vehicle.id and put super write() outside of the loop.
408 for vehicle in self.browse(cr, uid, ids, context):
410 if 'model_id' in vals and vehicle.model_id.id != vals['model_id']:
411 value = self.pool.get('fleet.vehicle.model').browse(cr,uid,vals['model_id'],context=context).name
412 oldmodel = vehicle.model_id
414 oldmodel = oldmodel.name
417 changes.append(_('Model: from \' %s \' to \' %s \'') %(oldmodel, value))
418 if 'driver' in vals and vehicle.driver.id != vals['driver']:
419 value = self.pool.get('res.partner').browse(cr,uid,vals['driver'],context=context).name
420 olddriver = vehicle.driver
422 olddriver = olddriver.name
425 changes.append(_('Driver: from \' %s \' to \' %s \'') %(olddriver, value))
426 if 'state' in vals and vehicle.state.id != vals['state']:
427 value = self.pool.get('fleet.vehicle.state').browse(cr,uid,vals['state'],context=context).name
428 oldstate = vehicle.state
430 oldstate=oldstate.name
433 changes.append(_('State: from \' %s \' to \' %s \'') %(oldstate, value))
434 if 'license_plate' in vals and vehicle.license_plate != vals['license_plate']:
435 old_license_plate = vehicle.license_plate
436 if not old_license_plate:
437 old_license_plate = 'None'
438 changes.append(_('License Plate: from \' %s \' to \' %s \'') %(old_license_plate, vals['license_plate']))
442 self.message_post(cr, uid, [self.browse(cr, uid, vehicle.id, context)[0].id], body=", ".join(changes), context=context)
443 except Exception as e:
447 vehicle_id = super(fleet_vehicle,self).write(cr, uid, ids, vals, context)
451 class fleet_vehicle_odometer(osv.Model):
452 _name='fleet.vehicle.odometer'
453 _description='Odometer log for a vehicle'
456 def _vehicle_log_name_get_fnc(self, cr, uid, ids, prop, unknow_none, context=None):
458 for record in self.browse(cr, uid, ids, context=context):
459 name = record.vehicle_id.name
461 name = name+ ' / '+ str(record.date)
462 res[record.id] = name
465 def on_change_vehicle(self, cr, uid, ids, vehicle_id, context=None):
468 odometer_unit = self.pool.get('fleet.vehicle').browse(cr, uid, vehicle_id, context=context).odometer_unit
471 'unit': odometer_unit,
476 'name': fields.function(_vehicle_log_name_get_fnc, type="char", string='Name', store=True),
477 'date': fields.date('Date'),
478 'value': fields.float('Odometer Value', group_operator="max"),
479 'vehicle_id': fields.many2one('fleet.vehicle', 'Vehicle', required=True),
480 'unit': fields.related('vehicle_id', 'odometer_unit', type="char", string="Unit", readonly=True),
483 'date': fields.date.context_today,
487 class fleet_vehicle_log_fuel(osv.Model):
489 def on_change_vehicle(self, cr, uid, ids, vehicle_id, context=None):
492 odometer_unit = self.pool.get('fleet.vehicle').browse(cr, uid, vehicle_id, context=context).odometer_unit
495 'odometer_unit': odometer_unit,
499 def on_change_liter(self, cr, uid, ids, liter, price_per_liter, amount, context=None):
500 if liter > 0 and price_per_liter > 0:
501 return {'value' : {'amount' : liter * price_per_liter,}}
502 elif liter > 0 and amount > 0:
503 return {'value' : {'price_per_liter' : amount / liter,}}
504 elif price_per_liter > 0 and amount > 0:
505 return {'value' : {'liter' : amount / price_per_liter,}}
509 def on_change_price_per_liter(self, cr, uid, ids, liter, price_per_liter, amount, context=None):
511 if price_per_liter > 0 and liter > 0:
512 return {'value' : {'amount' : liter * price_per_liter,}}
513 elif price_per_liter > 0 and amount > 0:
514 return {'value' : {'liter' : amount / price_per_liter,}}
515 elif liter > 0 and amount > 0:
516 return {'value' : {'price_per_liter' : amount / liter,}}
520 def on_change_amount(self, cr, uid, ids, liter, price_per_liter, amount, context=None):
522 if amount > 0 and liter > 0:
523 return {'value': {'price_per_liter': amount / liter}}
524 elif amount > 0 and price_per_liter > 0:
525 return {'value': {'liter': amount / price_per_liter}}
526 elif liter > 0 and price_per_liter > 0:
527 return {'value': {'amount': liter * price_per_liter}}
530 def _get_default_service_type(self, cr, uid, context):
532 model, model_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'fleet', 'type_service_refueling')
537 _name = 'fleet.vehicle.log.fuel'
538 _description = 'Fuel log for vehicles'
539 _inherits = {'fleet.vehicle.cost': 'cost_id'}
542 'liter': fields.float('Liter'),
543 'price_per_liter': fields.float('Price Per Liter'),
544 'purchaser_id': fields.many2one('res.partner', 'Purchaser', domain="['|',('customer','=',True),('employee','=',True)]"),
545 'inv_ref': fields.char('Invoice Reference', size=64),
546 'vendor_id': fields.many2one('res.partner', 'Supplier', domain="[('supplier','=',True)]"),
547 'notes': fields.text('Notes'),
548 'cost_amount': fields.related('cost_id', 'amount', string='Amount', type='float', store=True), #we need to keep this field as a related with store=True because the graph view doesn't support (1) to address fields from inherited table and (2) fields that aren't stored in database
551 'purchaser_id': lambda self, cr, uid, ctx: uid,
552 'date': fields.date.context_today,
553 'cost_subtype': _get_default_service_type,
558 class fleet_vehicle_log_services(osv.Model):
560 def on_change_vehicle(self, cr, uid, ids, vehicle_id, context=None):
563 odometer_unit = self.pool.get('fleet.vehicle').browse(cr, uid, vehicle_id, context=context).odometer_unit
566 'odometer_unit': odometer_unit,
570 def _get_default_service_type(self, cr, uid, context):
572 model, model_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'fleet', 'type_service_service_8')
577 _inherits = {'fleet.vehicle.cost': 'cost_id'}
578 _name = 'fleet.vehicle.log.services'
579 _description = 'Services for vehicles'
581 'purchaser_id': fields.many2one('res.partner', 'Purchaser', domain="['|',('customer','=',True),('employee','=',True)]"),
582 'inv_ref': fields.char('Invoice Reference', size=64),
583 'vendor_id': fields.many2one('res.partner', 'Supplier', domain="[('supplier','=',True)]"),
584 'cost_amount': fields.related('cost_id', 'amount', string='Amount', type='float', store=True), #we need to keep this field as a related with store=True because the graph view doesn't support (1) to address fields from inherited table and (2) fields that aren't stored in database
585 'notes': fields.text('Notes'),
588 'purchaser_id': lambda self, cr, uid, ctx: uid,
589 'date': fields.date.context_today,
590 'cost_subtype': _get_default_service_type,
591 'cost_type': 'services'
595 class fleet_service_type(osv.Model):
596 _name = 'fleet.service.type'
597 _description = 'Type of services available on a vehicle'
599 'name': fields.char('Name', required=True, translate=True),
600 'category': fields.selection([('contract', 'Contract'), ('service', 'Service'), ('both', 'Both')], 'Category', required=True, help='Choose wheter the service refer to contracts, vehicle services or both'),
604 class fleet_vehicle_log_contract(osv.Model):
606 def run_scheduler(self,cr,uid,context=None):
607 #TODO: add comments, will ask denis, this is his code
608 vehicle_cost_obj = self.pool.get('fleet.vehicle.cost')
609 d = datetime.datetime.strptime(fields.date.context_today(self, cr, uid, context=context), tools.DEFAULT_SERVER_DATE_FORMAT).date()
610 contract_ids = self.pool.get('fleet.vehicle.log.contract').search(cr, uid, [('state','!=','closed')], offset=0, limit=None, order=None,context=None, count=False)
611 deltas = {'yearly': relativedelta(years=+1), 'monthly': relativedelta(months=+1), 'weekly': relativedelta(weeks=+1), 'daily': relativedelta(days=+1)}
612 for contract in self.pool.get('fleet.vehicle.log.contract').browse(cr, uid, contract_ids, context=context):
613 if not contract.start_date or contract.cost_frequency == 'no':
616 last_cost_date = contract.start_date
617 if contract.generated_cost_ids:
618 last_autogenerated_cost_id = vehicle_cost_obj.search(cr, uid, ['&', ('contract_id','=',contract.id), ('auto_generated','=',True)], offset=0, limit=1, order='date desc',context=context, count=False)
619 if last_autogenerated_cost_id:
621 last_cost_date = vehicle_cost_obj.browse(cr, uid, last_cost_id[0], context=context).date
622 startdate = datetime.datetime.strptime(last_cost_date, tools.DEFAULT_SERVER_DATE_FORMAT).date()
624 startdate += deltas.get(contract.cost_frequency)
625 while (startdate < d) & (startdate < datetime.datetime.strptime(contract.expiration_date, tools.DEFAULT_SERVER_DATE_FORMAT).date()):
627 'amount': contract.cost_generated,
628 'date': startdate.strftime(tools.DEFAULT_SERVER_DATE_FORMAT),
629 'vehicle_id': contract.vehicle_id.id,
630 'cost_subtype': contract.cost_subtype.id,
631 'contract_id': contract.id,
632 'auto_generated': True
634 cost_id = self.pool.get('fleet.vehicle.cost').create(cr, uid, data, context=context)
635 startdate += deltas.get(contract.cost_frequency)
637 #for some reason when 2 scheduler run at the same time on the cron, we have a deadlock, so we only run it once and we call the other
639 self.pool.get('fleet.vehicle').run_scheduler(cr,uid,context=context)
643 def _vehicle_contract_name_get_fnc(self, cr, uid, ids, prop, unknow_none, context=None):
645 for record in self.browse(cr, uid, ids, context=context):
646 name = record.vehicle_id.name
647 if record.cost_subtype.name:
648 name += ' / '+ record.cost_subtype.name
650 name += ' / '+ record.date
651 res[record.id] = name
654 def on_change_vehicle(self, cr, uid, ids, vehicle_id, context=None):
657 odometer_unit = self.pool.get('fleet.vehicle').browse(cr, uid, vehicle_id, context=context).odometer_unit
660 'odometer_unit': odometer_unit,
664 def compute_next_year_date(self, strdate):
665 oneyear = datetime.timedelta(days=365)
666 curdate = str_to_date(strdate)
667 return datetime.datetime.strftime(curdate + oneyear, tools.DEFAULT_SERVER_DATE_FORMAT)
669 def on_change_start_date(self, cr, uid, ids, strdate, enddate, context=None):
671 return {'value': {'expiration_date': self.compute_next_year_date(strdate),}}
674 def get_days_left(self,cr,uid,ids,prop,unknow_none,context=None):
675 """return a dict with as value for each contract an integer
676 if contract is in an open state and is overdue, return 0
677 if contract is in a closed state, return -1
678 otherwise return the number of days before the contract expires
680 reads = self.browse(cr,uid,ids,context=context)
683 if (record.expiration_date and (record.state=='open' or record.state=='toclose')):
684 today= str_to_date(time.strftime('%Y-%m-%d'))
685 renew_date = str_to_date(record.expiration_date)
686 diff_time=int((renew_date-today).days)
690 res[record.id]=diff_time
695 def act_renew_contract(self,cr,uid,ids,context=None):
697 contracts = self.browse(cr,uid,ids,context=context)
698 for element in contracts:
699 default['date']=fields.date.context_today(self, cr, uid, context=context)
700 default['start_date']=str(str_to_date(element.expiration_date)+datetime.timedelta(days=1))
702 startdate = str_to_date(element.start_date)
703 enddate = str_to_date(element.expiration_date)
704 diffdate = (enddate-startdate)
705 newenddate = enddate+diffdate
706 default['expiration_date']=str(newenddate)
708 newid = super(fleet_vehicle_log_contract, self).copy(cr, uid, ids[0], default, context=context)
709 mod,modid = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'fleet', 'fleet_vehicle_log_contract_form')
711 'name':_("Renew Contract"),
714 'view_type': 'tree,form',
715 'res_model': 'fleet.vehicle.log.contract',
716 'type': 'ir.actions.act_window',
720 'context': {'active_id':newid},
723 def _get_default_contract_type(self, cr, uid, context=None):
725 model, model_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'fleet', 'type_contract_leasing')
730 def on_change_indic_cost(self, cr, uid, ids, cost_ids, context=None):
732 for element in cost_ids:
733 if element and len(element) == 3 and element[2] is not False:
734 totalsum += element[2].get('amount', 0.0)
737 'sum_cost': totalsum,
741 def _get_sum_cost(self, cr, uid, ids, field_name, arg, context=None):
743 for contract in self.browse(cr, uid, ids, context=context):
745 for cost in contract.cost_ids:
746 totalsum += cost.amount
747 res[contract.id] = totalsum
750 _inherits = {'fleet.vehicle.cost': 'cost_id'}
751 _name = 'fleet.vehicle.log.contract'
752 _description = 'Contract information on a vehicle'
753 _order='state desc,expiration_date'
755 'name': fields.function(_vehicle_contract_name_get_fnc, type="text", string='Name', store=True),
756 'start_date': fields.date('Contract Start Date', help='Date when the coverage of the contract begins'),
757 'expiration_date': fields.date('Contract Expiration Date', help='Date when the coverage of the contract expirates (by default, one year after begin date)'),
758 'days_left': fields.function(get_days_left, type='integer', string='Warning Date'),
759 'insurer_id' :fields.many2one('res.partner', 'Supplier', domain="[('supplier','=',True)]"),
760 'purchaser_id': fields.many2one('res.partner', 'Contractor', domain="['|', ('customer','=',True), ('employee','=',True)]",help='Person to which the contract is signed for'),
761 'ins_ref': fields.char('Contract Reference', size=64),
762 'state': fields.selection([('open', 'In Progress'), ('toclose','To Close'), ('closed', 'Terminated')], 'Status', readonly=True, help='Choose wheter the contract is still valid or not'),
763 'notes': fields.text('Terms and Conditions', help='Write here all supplementary informations relative to this contract'),
764 'cost_generated': fields.float('Recurring Cost 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"),
765 'cost_frequency': fields.selection([('no','No'), ('daily', 'Daily'), ('weekly','Weekly'), ('monthly','Monthly'), ('yearly','Yearly')], 'Recurring Cost Frequency', help='Frequency of the recuring cost', required=True),
766 'generated_cost_ids': fields.one2many('fleet.vehicle.cost', 'contract_id', 'Generated Costs', ondelete='cascade'),
767 'sum_cost': fields.function(_get_sum_cost, type='float', string='Indicative Costs Total'),
768 'cost_amount': fields.related('cost_id', 'amount', string='Amount', type='float', store=True), #we need to keep this field as a related with store=True because the graph view doesn't support (1) to address fields from inherited table and (2) fields that aren't stored in database
771 'purchaser_id': lambda self, cr, uid, ctx: uid,
772 'date': fields.date.context_today,
773 'start_date': fields.date.context_today,
775 'expiration_date': lambda self, cr, uid, ctx: self.compute_next_year_date(fields.date.context_today(self, cr, uid, context=ctx)),
776 'cost_frequency': 'no',
777 'cost_subtype': _get_default_contract_type,
778 'cost_type': 'contract',
781 def copy(self, cr, uid, id, default=None, context=None):
784 today = fields.date.context_today(self, cr, uid, context=context)
785 default['date'] = today
786 default['start_date'] = today
787 default['expiration_date'] = self.compute_next_year_date(today)
788 default['ins_ref'] = ''
789 default['state'] = 'open'
790 default['notes'] = ''
791 return super(fleet_vehicle_log_contract, self).copy(cr, uid, id, default, context=context)
793 def contract_close(self, cr, uid, ids, context=None):
794 return self.write(cr, uid, ids, {'state': 'closed'},context=context)
796 def contract_open(self, cr, uid, ids, context=None):
797 return self.write(cr, uid, ids, {'state': 'open'},context=context)
799 class fleet_contract_state(osv.Model):
800 _name = 'fleet.contract.state'
801 _description = 'Contains the different possible status of a leasing contract'
804 'name':fields.char('Contract Status', size=32, required=True),