#
##############################################################################
-from osv import osv, fields
+from openerp.osv import fields, osv
import time
import datetime
-import tools
-from osv.orm import except_orm
-from tools.translate import _
+from openerp import tools
+from openerp.osv.orm import except_orm
+from openerp.tools.translate import _
from dateutil.relativedelta import relativedelta
-def str_to_date(strdate):
+def str_to_datetime(strdate):
return datetime.datetime.strptime(strdate, tools.DEFAULT_SERVER_DATE_FORMAT)
class fleet_vehicle_cost(osv.Model):
def _year_get_fnc(self, cr, uid, ids, name, unknow_none, context=None):
res = {}
for record in self.browse(cr, uid, ids, context=context):
- res[record.id] = str(time.strptime(record.date, tools.DEFAULT_SERVER_DATE_FORMAT).tm_year) #TODO: why is it a char?
+ if (record.date):
+ res[record.id] = str(time.strptime(record.date, tools.DEFAULT_SERVER_DATE_FORMAT).tm_year)
+ else:
+ res[record.id] = _('Unknown')
return res
def _cost_name_get_fnc(self, cr, uid, ids, name, unknow_none, context=None):
res = {}
for record in self.browse(cr, uid, ids, context=context):
name = record.vehicle_id.name
- if record.cost_subtype.name:
- name += ' / '+ record.cost_subtype.name
+ if record.cost_subtype_id.name:
+ name += ' / '+ record.cost_subtype_id.name
if record.date:
name += ' / '+ record.date
res[record.id] = name
_columns = {
'name': fields.function(_cost_name_get_fnc, type="char", string='Name', store=True),
'vehicle_id': fields.many2one('fleet.vehicle', 'Vehicle', required=True, help='Vehicle concerned by this log'),
- 'cost_subtype': fields.many2one('fleet.service.type', 'Type', help='Cost type purchased with this cost'),
+ 'cost_subtype_id': fields.many2one('fleet.service.type', 'Type', help='Cost type purchased with this cost'),
'amount': fields.float('Total Price'),
'cost_type': fields.selection([('contract', 'Contract'), ('services','Services'), ('fuel','Fuel'), ('other','Other')], 'Category of the cost', help='For internal purpose only', required=True),
'parent_id': fields.many2one('fleet.vehicle.cost', 'Parent', help='Parent cost to this current cost'),
'odometer_unit': fields.related('vehicle_id', 'odometer_unit', type="char", string="Unit", readonly=True),
'date' :fields.date('Date',help='Date when the cost has been executed'),
'contract_id': fields.many2one('fleet.vehicle.log.contract', 'Contract', help='Contract attached to this cost'),
- 'auto_generated': fields.boolean('automatically generated', readonly=True, required=True),
+ 'auto_generated': fields.boolean('Automatically Generated', readonly=True, required=True),
'year': fields.function(_year_get_fnc, type="char", string='Year', store=True),
}
}
def create(self, cr, uid, data, context=None):
- #TODO: should be managed by onchanges() rather by this
+ #make sure that the data are consistent with values of parent and contract records given
if 'parent_id' in data and data['parent_id']:
parent = self.browse(cr, uid, data['parent_id'], context=context)
data['vehicle_id'] = parent.vehicle_id.id
if 'contract_id' in data and data['contract_id']:
contract = self.pool.get('fleet.vehicle.log.contract').browse(cr, uid, data['contract_id'], context=context)
data['vehicle_id'] = contract.vehicle_id.id
- data['cost_subtype'] = contract.cost_subtype.id
+ data['cost_subtype_id'] = contract.cost_subtype_id.id
data['cost_type'] = contract.cost_type
+ if 'odometer' in data and not data['odometer']:
+ #if received value for odometer is 0, then remove it from the data as it would result to the creation of a
+ #odometer log with 0, which is to be avoided
+ del(data['odometer'])
return super(fleet_vehicle_cost, self).create(cr, uid, data, context=context)
'name': fields.char('Name', required=True, translate=True),
}
-
class fleet_vehicle_state(osv.Model):
_name = 'fleet.vehicle.state'
_order = 'sequence asc'
_columns = {
'name': fields.char('Name', required=True),
- 'sequence': fields.integer('Order', help="Used to order the note stages")
+ 'sequence': fields.integer('Sequence', help="Used to order the note stages")
}
_sql_constraints = [('fleet_state_name_unique','unique(name)', 'State name already exists')]
res = {}
for record in self.browse(cr, uid, ids, context=context):
name = record.modelname
- if record.brand.name:
- name = record.brand.name+' / '+name
+ if record.brand_id.name:
+ name = record.brand_id.name + ' / ' + name
res[record.id] = name
return res
_columns = {
'name': fields.function(_model_name_get_fnc, type="char", string='Name', store=True),
'modelname': fields.char('Model name', size=32, required=True),
- 'brand': fields.many2one('fleet.vehicle.model.brand', 'Model Brand', required=True, help='Brand of the vehicle'),
+ 'brand_id': fields.many2one('fleet.vehicle.model.brand', 'Model Brand', required=True, help='Brand of the vehicle'),
'vendors': fields.many2many('res.partner', 'fleet_vehicle_model_vendors', 'model_id', 'partner_id', string='Vendors'),
- 'image': fields.related('brand', 'image', type="binary", string="Logo"),
- 'image_medium': fields.related('brand', 'image_medium', type="binary", string="Logo"),
- 'image_small': fields.related('brand', 'image_small', type="binary", string="Logo"),
+ 'image': fields.related('brand_id', 'image', type="binary", string="Logo"),
+ 'image_medium': fields.related('brand_id', 'image_medium', type="binary", string="Logo"),
+ 'image_small': fields.related('brand_id', 'image_small', type="binary", string="Logo"),
}
def _vehicle_name_get_fnc(self, cr, uid, ids, prop, unknow_none, context=None):
res = {}
for record in self.browse(cr, uid, ids, context=context):
- res[record.id] = record.model_id.brand.name + '/' + record.model_id.modelname + ' / ' + record.license_plate
+ res[record.id] = record.model_id.brand_id.name + '/' + record.model_id.modelname + ' / ' + record.license_plate
return res
def return_action_to_open(self, cr, uid, ids, context=None):
""" This opens the xml view specified in xml_id for the current vehicle """
- if context['xml_id']:
+ if context is None:
+ context = {}
+ if context.get('xml_id'):
res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid ,'fleet', context['xml_id'], context=context)
- res['context'] = {
- 'default_vehicle_id': ids[0]
- }
- res['domain']=[('vehicle_id','=', ids[0])]
+ res['context'] = context
+ res['context'].update({'default_vehicle_id': ids[0]})
+ res['domain'] = [('vehicle_id','=', ids[0])]
return res
- else:
- return False
+ return False
def act_show_log_cost(self, cr, uid, ids, context=None):
""" This opens log view to view and add new log for this vehicle, groupby default to only show effective costs
@return: the costs log view
"""
- res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid ,'fleet','fleet_vehicle_costs_act', context)
- res['context'] = {
+ if context is None:
+ context = {}
+ res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid ,'fleet','fleet_vehicle_costs_act', context=context)
+ res['context'] = context
+ res['context'].update({
'default_vehicle_id': ids[0],
- 'search_default_parent_false' : True
- }
- res['domain']=[('vehicle_id','=', ids[0])]
+ 'search_default_parent_false': True
+ })
+ res['domain'] = [('vehicle_id','=', ids[0])]
return res
def _get_odometer(self, cr, uid, ids, odometer_id, arg, context):
def _search_get_overdue_contract_reminder(self, cr, uid, obj, name, args, context):
res = []
for field, operator, value in args:
- vehicle_ids = self.search(cr, uid, [])
- 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)
- res_ids = []
- for renew_key,renew_value in contracts_needed.items():
- if eval(str(renew_value['contract_renewal_overdue']) + " " + str(operator) + " " + str(value)):
- res_ids.append(renew_key)
- res.append(('id', 'in', res_ids))
+ assert operator in ('=', '!=', '<>') and value in (True, False), 'Operation not supported'
+ if (operator == '=' and value == True) or (operator in ('<>', '!=') and value == False):
+ search_operator = 'in'
+ else:
+ search_operator = 'not in'
+ today = fields.date.context_today(self, cr, uid, context=context)
+ cr.execute('select cost.vehicle_id, count(contract.id) as contract_number FROM fleet_vehicle_cost cost left join fleet_vehicle_log_contract contract on contract.cost_id = cost.id WHERE contract.expiration_date is not null AND contract.expiration_date < %s AND contract.state IN (\'open\', \'toclose\') GROUP BY cost.vehicle_id', (today,))
+ res_ids = [x[0] for x in cr.fetchall()]
+ res.append(('id', search_operator, res_ids))
return res
-
+
def _search_contract_renewal_due_soon(self, cr, uid, obj, name, args, context):
res = []
for field, operator, value in args:
- vehicle_ids = self.search(cr, uid, [])
- 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)
- res_ids = []
- for renew_key,renew_value in contracts_needed.items():
- if eval(str(renew_value['contract_renewal_due_soon']) + " " + str(operator) + " " + str(value)):
- res_ids.append(renew_key)
- res.append(('id', 'in', res_ids))
+ assert operator in ('=', '!=', '<>') and value in (True, False), 'Operation not supported'
+ if (operator == '=' and value == True) or (operator in ('<>', '!=') and value == False):
+ search_operator = 'in'
+ else:
+ search_operator = 'not in'
+ today = fields.date.context_today(self, cr, uid, context=context)
+ datetime_today = datetime.datetime.strptime(today, tools.DEFAULT_SERVER_DATE_FORMAT)
+ limit_date = str((datetime_today + relativedelta(days=+15)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT))
+ cr.execute('select cost.vehicle_id, count(contract.id) as contract_number FROM fleet_vehicle_cost cost left join fleet_vehicle_log_contract contract on contract.cost_id = cost.id WHERE contract.expiration_date is not null AND contract.expiration_date > %s AND contract.expiration_date < %s AND contract.state IN (\'open\', \'toclose\') GROUP BY cost.vehicle_id', (today, limit_date))
+ res_ids = [x[0] for x in cr.fetchall()]
+ res.append(('id', search_operator, res_ids))
return res
- def _get_contract_reminder_fnc(self, cr, uid, ids, prop, unknow_none, context=None):
+ def _get_contract_reminder_fnc(self, cr, uid, ids, field_names, unknow_none, context=None):
res= {}
for record in self.browse(cr, uid, ids, context=context):
- overdue = 0
- due_soon = 0
+ overdue = False
+ due_soon = False
+ total = 0
name = ''
for element in record.log_contracts:
- if (element.state in ('open', 'toclose') and element.expiration_date):
+ if element.state in ('open', 'toclose') and element.expiration_date:
current_date_str = fields.date.context_today(self, cr, uid, context=context)
due_time_str = element.expiration_date
- current_date = str_to_date(current_date_str)
- due_time = str_to_date(due_time_str)
+ current_date = str_to_datetime(current_date_str)
+ due_time = str_to_datetime(due_time_str)
diff_time = (due_time-current_date).days
if diff_time < 0:
- overdue += 1
- if diff_time<15 and diff_time>=0:
- due_soon = due_soon +1;
- if overdue+due_soon>0:
- 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')
+ overdue = True
+ total += 1
+ if diff_time < 15 and diff_time >= 0:
+ due_soon = True;
+ total += 1
+ if overdue or due_soon:
+ ids = self.pool.get('fleet.vehicle.log.contract').search(cr,uid,[('vehicle_id', '=', record.id), ('state', 'in', ('open', 'toclose'))], limit=1, order='expiration_date asc')
if len(ids) > 0:
- name=(self.pool.get('fleet.vehicle.log.contract').browse(cr,uid,ids[0],context=context).cost_subtype.name)
+ #we display only the name of the oldest overdue/due soon contract
+ name=(self.pool.get('fleet.vehicle.log.contract').browse(cr, uid, ids[0], context=context).cost_subtype_id.name)
res[record.id] = {
- 'contract_renewal_overdue':overdue,
- 'contract_renewal_due_soon':due_soon,
- 'contract_renewal_total':(overdue+due_soon-1),
- 'contract_renewal_name':name,
+ 'contract_renewal_overdue': overdue,
+ 'contract_renewal_due_soon': due_soon,
+ 'contract_renewal_total': (total - 1), #we remove 1 from the real total for display purposes
+ 'contract_renewal_name': name,
}
return res
- def run_scheduler(self, cr, uid, context=None):
- datetime_today = datetime.datetime.strptime(fields.date.context_today(self, cr, uid, context=context), tools.DEFAULT_SERVER_DATE_FORMAT)
- limit_date = (datetime_today + relativedelta(days=+15)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
- 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)
- res = {}
- for contract in self.pool.get('fleet.vehicle.log.contract').browse(cr, uid, ids, context=context):
- if contract.vehicle_id.id in res:
- res[contract.vehicle_id.id] += 1
- else:
- res[contract.vehicle_id.id] = 1
-
- for vehicle, value in res.items():
- self.message_post(cr, uid, vehicle, body=_('%s contract(s) need(s) to be renewed and/or closed!') % (str(value)), context=context)
-
- return self.pool.get('fleet.vehicle.log.contract').write(cr, uid, ids, {'state': 'toclose'}, context=context)
-
def _get_default_state(self, cr, uid, context):
try:
model, model_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'fleet', 'vehicle_state_active')
'company_id': fields.many2one('res.company', 'Company'),
'license_plate': fields.char('License Plate', size=32, required=True, help='License plate number of the vehicle (ie: plate number for a car)'),
'vin_sn': fields.char('Chassis Number', size=32, help='Unique number written on the vehicle motor (VIN/SN number)'),
- 'driver': fields.many2one('res.partner', 'Driver', help='Driver of the vehicle'),
+ 'driver_id': fields.many2one('res.partner', 'Driver', help='Driver of the vehicle'),
'model_id': fields.many2one('fleet.vehicle.model', 'Model', required=True, help='Model of the vehicle'),
'log_fuel': fields.one2many('fleet.vehicle.log.fuel', 'vehicle_id', 'Fuel Logs'),
'log_services': fields.one2many('fleet.vehicle.log.services', 'vehicle_id', 'Services Logs'),
'log_contracts': fields.one2many('fleet.vehicle.log.contract', 'vehicle_id', 'Contracts'),
'acquisition_date': fields.date('Acquisition Date', required=False, help='Date when the vehicle has been bought'),
'color': fields.char('Color', size=32, help='Color of the vehicle'),
- 'state': fields.many2one('fleet.vehicle.state', 'State', help='Current state of the vehicle', ondelete="set null"),
+ 'state_id': fields.many2one('fleet.vehicle.state', 'State', help='Current state of the vehicle', ondelete="set null"),
'location': fields.char('Location', size=128, help='Location of the vehicle (garage, ...)'),
'seats': fields.integer('Seats Number', help='Number of seats of the vehicle'),
'doors': fields.integer('Doors Number', help='Number of doors of the vehicle'),
'tag_ids' :fields.many2many('fleet.vehicle.tag', 'fleet_vehicle_vehicle_tag_rel', 'vehicle_tag_id','tag_id', 'Tags'),
- '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'),
+ 'odometer': fields.function(_get_odometer, fnct_inv=_set_odometer, type='float', string='Last Odometer', help='Odometer measure of the vehicle at the moment of this log'),
'odometer_unit': fields.selection([('kilometers', 'Kilometers'),('miles','Miles')], 'Odometer Unit', help='Unit of the odometer ',required=True),
'transmission': fields.selection([('manual', 'Manual'), ('automatic', 'Automatic')], 'Transmission', help='Transmission Used by the vehicle'),
'fuel_type': fields.selection([('gasoline', 'Gasoline'), ('diesel', 'Diesel'), ('electric', 'Electric'), ('hybrid', 'Hybrid')], 'Fuel Type', help='Fuel Used by the vehicle'),
'image': fields.related('model_id', 'image', type="binary", string="Logo"),
'image_medium': fields.related('model_id', 'image_medium', type="binary", string="Logo"),
'image_small': fields.related('model_id', 'image_small', type="binary", string="Logo"),
- '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'),
- 'contract_renewal_overdue': fields.function(_get_contract_reminder_fnc, fnct_search=_search_get_overdue_contract_reminder, type="integer", string='Contracts Overdued', multi='contract_info'),
+ 'contract_renewal_due_soon': fields.function(_get_contract_reminder_fnc, fnct_search=_search_contract_renewal_due_soon, type="boolean", string='Has Contracts to renew', multi='contract_info'),
+ 'contract_renewal_overdue': fields.function(_get_contract_reminder_fnc, fnct_search=_search_get_overdue_contract_reminder, type="boolean", string='Has Contracts Overdued', multi='contract_info'),
'contract_renewal_name': fields.function(_get_contract_reminder_fnc, type="text", string='Name of contract to renew soon', multi='contract_info'),
'contract_renewal_total': fields.function(_get_contract_reminder_fnc, type="integer", string='Total of contracts due or overdue minus one', multi='contract_info'),
'car_value': fields.float('Car Value', help='Value of the bought vehicle'),
_defaults = {
'doors': 5,
'odometer_unit': 'kilometers',
- 'state': _get_default_state,
+ 'state_id': _get_default_state,
}
def copy(self, cr, uid, id, default=None, context=None):
}
def create(self, cr, uid, data, context=None):
- #TODO: why is there a try..except? No idea, this is denis code, we should ask him
vehicle_id = super(fleet_vehicle, self).create(cr, uid, data, context=context)
- try:
- vehicle = self.browse(cr, uid, vehicle_id, context=context)
- self.message_post(cr, uid, [vehicle_id], body=_('Vehicle %s has been added to the fleet!') % (vehicle.license_plate), context=context)
- except:
- pass # group deleted: do not push a message
+ vehicle = self.browse(cr, uid, vehicle_id, context=context)
+ self.message_post(cr, uid, [vehicle_id], body=_('Vehicle %s has been added to the fleet!') % (vehicle.license_plate), context=context)
return vehicle_id
def write(self, cr, uid, ids, vals, context=None):
- #TODO: put comments
- #TODO: use _() to translate labels
- #TODO: why is there a try..except?
- #TODO: shorten the code (e.g: oldmodel = vehicle.model_id and olmodel.model_id.name or _('None')
- #TODO: in PEP 8 standard, a coma should be followed by a space, '+' operator and equal sign should be in between 2 spaces
- #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.
+ """
+ This function write an entry in the openchatter whenever we change important information
+ on the vehicle like the model, the drive, the state of the vehicle or its license plate
+ """
for vehicle in self.browse(cr, uid, ids, context):
changes = []
if 'model_id' in vals and vehicle.model_id.id != vals['model_id']:
value = self.pool.get('fleet.vehicle.model').browse(cr,uid,vals['model_id'],context=context).name
- oldmodel = vehicle.model_id
- if oldmodel:
- oldmodel = oldmodel.name
- else:
- oldmodel = 'None'
- changes.append(_('Model: from \' %s \' to \' %s \'') %(oldmodel, value))
- if 'driver' in vals and vehicle.driver.id != vals['driver']:
- value = self.pool.get('res.partner').browse(cr,uid,vals['driver'],context=context).name
- olddriver = vehicle.driver
- if olddriver:
- olddriver = olddriver.name
- else:
- olddriver = 'None'
- changes.append(_('Driver: from \' %s \' to \' %s \'') %(olddriver, value))
- if 'state' in vals and vehicle.state.id != vals['state']:
- value = self.pool.get('fleet.vehicle.state').browse(cr,uid,vals['state'],context=context).name
- oldstate = vehicle.state
- if oldstate:
- oldstate=oldstate.name
- else:
- oldstate = 'None'
- changes.append(_('State: from \' %s \' to \' %s \'') %(oldstate, value))
+ oldmodel = vehicle.model_id.name or _('None')
+ changes.append(_("Model: from '%s' to '%s'") %(oldmodel, value))
+ if 'driver_id' in vals and vehicle.driver_id.id != vals['driver_id']:
+ value = self.pool.get('res.partner').browse(cr,uid,vals['driver_id'],context=context).name
+ olddriver = (vehicle.driver_id.name) or _('None')
+ changes.append(_("Driver: from '%s' to '%s'") %(olddriver, value))
+ if 'state_id' in vals and vehicle.state_id.id != vals['state_id']:
+ value = self.pool.get('fleet.vehicle.state').browse(cr,uid,vals['state_id'],context=context).name
+ oldstate = vehicle.state_id.name or _('None')
+ changes.append(_("State: from '%s' to '%s'") %(oldstate, value))
if 'license_plate' in vals and vehicle.license_plate != vals['license_plate']:
- old_license_plate = vehicle.license_plate
- if not old_license_plate:
- old_license_plate = 'None'
- changes.append(_('License Plate: from \' %s \' to \' %s \'') %(old_license_plate, vals['license_plate']))
-
- try:
- if len(changes) > 0:
- self.message_post(cr, uid, [self.browse(cr, uid, vehicle.id, context)[0].id], body=", ".join(changes), context=context)
- except Exception as e:
- print e
- pass
+ old_license_plate = vehicle.license_plate or _('None')
+ changes.append(_("License Plate: from '%s' to '%s'") %(old_license_plate, vals['license_plate']))
+
+ if len(changes) > 0:
+ self.message_post(cr, uid, [vehicle.id], body=", ".join(changes), context=context)
vehicle_id = super(fleet_vehicle,self).write(cr, uid, ids, vals, context)
return True
def on_change_vehicle(self, cr, uid, ids, vehicle_id, context=None):
if not vehicle_id:
return {}
- odometer_unit = self.pool.get('fleet.vehicle').browse(cr, uid, vehicle_id, context=context).odometer_unit
+ vehicle = self.pool.get('fleet.vehicle').browse(cr, uid, vehicle_id, context=context)
+ odometer_unit = vehicle.odometer_unit
+ driver = vehicle.driver_id.id
return {
'value': {
'odometer_unit': odometer_unit,
+ 'purchaser_id': driver,
}
}
def on_change_liter(self, cr, uid, ids, liter, price_per_liter, amount, context=None):
- if liter > 0 and price_per_liter > 0:
- return {'value' : {'amount' : liter * price_per_liter,}}
- elif liter > 0 and amount > 0:
- return {'value' : {'price_per_liter' : amount / liter,}}
- elif price_per_liter > 0 and amount > 0:
- return {'value' : {'liter' : amount / price_per_liter,}}
+ #need to cast in float because the value receveid from web client maybe an integer (Javascript and JSON do not
+ #make any difference between 3.0 and 3). This cause a problem if you encode, for example, 2 liters at 1.5 per
+ #liter => total is computed as 3.0, then trigger an onchange that recomputes price_per_liter as 3/2=1 (instead
+ #of 3.0/2=1.5)
+ #If there is no change in the result, we return an empty dict to prevent an infinite loop due to the 3 intertwine
+ #onchange. And in order to verify that there is no change in the result, we have to limit the precision of the
+ #computation to 2 decimal
+ liter = float(liter)
+ price_per_liter = float(price_per_liter)
+ amount = float(amount)
+ if liter > 0 and price_per_liter > 0 and round(liter*price_per_liter,2) != amount:
+ return {'value' : {'amount' : round(liter * price_per_liter,2),}}
+ elif amount > 0 and liter > 0 and round(amount/liter,2) != price_per_liter:
+ return {'value' : {'price_per_liter' : round(amount / liter,2),}}
+ elif amount > 0 and price_per_liter > 0 and round(amount/price_per_liter,2) != liter:
+ return {'value' : {'liter' : round(amount / price_per_liter,2),}}
else :
return {}
def on_change_price_per_liter(self, cr, uid, ids, liter, price_per_liter, amount, context=None):
-
- if price_per_liter > 0 and liter > 0:
- return {'value' : {'amount' : liter * price_per_liter,}}
- elif price_per_liter > 0 and amount > 0:
- return {'value' : {'liter' : amount / price_per_liter,}}
- elif liter > 0 and amount > 0:
- return {'value' : {'price_per_liter' : amount / liter,}}
+ #need to cast in float because the value receveid from web client maybe an integer (Javascript and JSON do not
+ #make any difference between 3.0 and 3). This cause a problem if you encode, for example, 2 liters at 1.5 per
+ #liter => total is computed as 3.0, then trigger an onchange that recomputes price_per_liter as 3/2=1 (instead
+ #of 3.0/2=1.5)
+ #If there is no change in the result, we return an empty dict to prevent an infinite loop due to the 3 intertwine
+ #onchange. And in order to verify that there is no change in the result, we have to limit the precision of the
+ #computation to 2 decimal
+ liter = float(liter)
+ price_per_liter = float(price_per_liter)
+ amount = float(amount)
+ if liter > 0 and price_per_liter > 0 and round(liter*price_per_liter,2) != amount:
+ return {'value' : {'amount' : round(liter * price_per_liter,2),}}
+ elif amount > 0 and price_per_liter > 0 and round(amount/price_per_liter,2) != liter:
+ return {'value' : {'liter' : round(amount / price_per_liter,2),}}
+ elif amount > 0 and liter > 0 and round(amount/liter,2) != price_per_liter:
+ return {'value' : {'price_per_liter' : round(amount / liter,2),}}
else :
return {}
def on_change_amount(self, cr, uid, ids, liter, price_per_liter, amount, context=None):
-
- if amount > 0 and liter > 0:
- return {'value': {'price_per_liter': amount / liter}}
- elif amount > 0 and price_per_liter > 0:
- return {'value': {'liter': amount / price_per_liter}}
- elif liter > 0 and price_per_liter > 0:
- return {'value': {'amount': liter * price_per_liter}}
- return {}
+ #need to cast in float because the value receveid from web client maybe an integer (Javascript and JSON do not
+ #make any difference between 3.0 and 3). This cause a problem if you encode, for example, 2 liters at 1.5 per
+ #liter => total is computed as 3.0, then trigger an onchange that recomputes price_per_liter as 3/2=1 (instead
+ #of 3.0/2=1.5)
+ #If there is no change in the result, we return an empty dict to prevent an infinite loop due to the 3 intertwine
+ #onchange. And in order to verify that there is no change in the result, we have to limit the precision of the
+ #computation to 2 decimal
+ liter = float(liter)
+ price_per_liter = float(price_per_liter)
+ amount = float(amount)
+ if amount > 0 and liter > 0 and round(amount/liter,2) != price_per_liter:
+ return {'value': {'price_per_liter': round(amount / liter,2),}}
+ elif amount > 0 and price_per_liter > 0 and round(amount/price_per_liter,2) != liter:
+ return {'value': {'liter': round(amount / price_per_liter,2),}}
+ elif liter > 0 and price_per_liter > 0 and round(liter*price_per_liter,2) != amount:
+ return {'value': {'amount': round(liter * price_per_liter,2),}}
+ else :
+ return {}
def _get_default_service_type(self, cr, uid, context):
try:
'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
}
_defaults = {
- 'purchaser_id': lambda self, cr, uid, ctx: uid,
'date': fields.date.context_today,
- 'cost_subtype': _get_default_service_type,
+ 'cost_subtype_id': _get_default_service_type,
'cost_type': 'fuel',
}
def on_change_vehicle(self, cr, uid, ids, vehicle_id, context=None):
if not vehicle_id:
return {}
- odometer_unit = self.pool.get('fleet.vehicle').browse(cr, uid, vehicle_id, context=context).odometer_unit
+ vehicle = self.pool.get('fleet.vehicle').browse(cr, uid, vehicle_id, context=context)
+ odometer_unit = vehicle.odometer_unit
+ driver = vehicle.driver_id.id
return {
'value': {
'odometer_unit': odometer_unit,
+ 'purchaser_id': driver,
}
}
'notes': fields.text('Notes'),
}
_defaults = {
- 'purchaser_id': lambda self, cr, uid, ctx: uid,
'date': fields.date.context_today,
- 'cost_subtype': _get_default_service_type,
+ 'cost_subtype_id': _get_default_service_type,
'cost_type': 'services'
}
class fleet_vehicle_log_contract(osv.Model):
- def run_scheduler(self,cr,uid,context=None):
- #TODO: add comments, will ask denis, this is his code
+ def scheduler_manage_auto_costs(self, cr, uid, context=None):
+ #This method is called by a cron task
+ #It creates costs for contracts having the "recurring cost" field setted, depending on their frequency
+ #For example, if a contract has a reccuring cost of 200 with a weekly frequency, this method creates a cost of 200 on the first day of each week, from the date of the last recurring costs in the database to today
+ #If the contract has not yet any recurring costs in the database, the method generates the recurring costs from the start_date to today
+ #The created costs are associated to a contract thanks to the many2one field contract_id
+ #If the contract has no start_date, no cost will be created, even if the contract has recurring costs
vehicle_cost_obj = self.pool.get('fleet.vehicle.cost')
d = datetime.datetime.strptime(fields.date.context_today(self, cr, uid, context=context), tools.DEFAULT_SERVER_DATE_FORMAT).date()
contract_ids = self.pool.get('fleet.vehicle.log.contract').search(cr, uid, [('state','!=','closed')], offset=0, limit=None, order=None,context=None, count=False)
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)
if last_autogenerated_cost_id:
found = True
- last_cost_date = vehicle_cost_obj.browse(cr, uid, last_cost_id[0], context=context).date
+ last_cost_date = vehicle_cost_obj.browse(cr, uid, last_autogenerated_cost_id[0], context=context).date
startdate = datetime.datetime.strptime(last_cost_date, tools.DEFAULT_SERVER_DATE_FORMAT).date()
if found:
startdate += deltas.get(contract.cost_frequency)
- while (startdate < d) & (startdate < datetime.datetime.strptime(contract.expiration_date, tools.DEFAULT_SERVER_DATE_FORMAT).date()):
+ while (startdate <= d) & (startdate <= datetime.datetime.strptime(contract.expiration_date, tools.DEFAULT_SERVER_DATE_FORMAT).date()):
data = {
'amount': contract.cost_generated,
'date': startdate.strftime(tools.DEFAULT_SERVER_DATE_FORMAT),
'vehicle_id': contract.vehicle_id.id,
- 'cost_subtype': contract.cost_subtype.id,
+ 'cost_subtype_id': contract.cost_subtype_id.id,
'contract_id': contract.id,
'auto_generated': True
}
cost_id = self.pool.get('fleet.vehicle.cost').create(cr, uid, data, context=context)
startdate += deltas.get(contract.cost_frequency)
+ return True
- #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
- #function here.
- self.pool.get('fleet.vehicle').run_scheduler(cr,uid,context=context)
+ def scheduler_manage_contract_expiration(self, cr, uid, context=None):
+ #This method is called by a cron task
+ #It manages the state of a contract, possibly by posting a message on the vehicle concerned and updating its status
+ datetime_today = datetime.datetime.strptime(fields.date.context_today(self, cr, uid, context=context), tools.DEFAULT_SERVER_DATE_FORMAT)
+ limit_date = (datetime_today + relativedelta(days=+15)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
+ ids = self.search(cr, uid, ['&', ('state', '=', 'open'), ('expiration_date', '<', limit_date)], offset=0, limit=None, order=None, context=context, count=False)
+ res = {}
+ for contract in self.browse(cr, uid, ids, context=context):
+ if contract.vehicle_id.id in res:
+ res[contract.vehicle_id.id] += 1
+ else:
+ res[contract.vehicle_id.id] = 1
+ for vehicle, value in res.items():
+ self.pool.get('fleet.vehicle').message_post(cr, uid, vehicle, body=_('%s contract(s) need(s) to be renewed and/or closed!') % (str(value)), context=context)
+ return self.write(cr, uid, ids, {'state': 'toclose'}, context=context)
+
+ def run_scheduler(self, cr, uid, context=None):
+ self.scheduler_manage_auto_costs(cr, uid, context=context)
+ self.scheduler_manage_contract_expiration(cr, uid, context=context)
return True
def _vehicle_contract_name_get_fnc(self, cr, uid, ids, prop, unknow_none, context=None):
res = {}
for record in self.browse(cr, uid, ids, context=context):
name = record.vehicle_id.name
- if record.cost_subtype.name:
- name += ' / '+ record.cost_subtype.name
+ if record.cost_subtype_id.name:
+ name += ' / '+ record.cost_subtype_id.name
if record.date:
name += ' / '+ record.date
res[record.id] = name
def compute_next_year_date(self, strdate):
oneyear = datetime.timedelta(days=365)
- curdate = str_to_date(strdate)
+ curdate = str_to_datetime(strdate)
return datetime.datetime.strftime(curdate + oneyear, tools.DEFAULT_SERVER_DATE_FORMAT)
def on_change_start_date(self, cr, uid, ids, strdate, enddate, context=None):
return {'value': {'expiration_date': self.compute_next_year_date(strdate),}}
return {}
- def get_days_left(self,cr,uid,ids,prop,unknow_none,context=None):
+ def get_days_left(self, cr, uid, ids, prop, unknow_none, context=None):
"""return a dict with as value for each contract an integer
if contract is in an open state and is overdue, return 0
if contract is in a closed state, return -1
otherwise return the number of days before the contract expires
"""
- reads = self.browse(cr,uid,ids,context=context)
- res={}
- for record in reads:
- if (record.expiration_date and (record.state=='open' or record.state=='toclose')):
- today= str_to_date(time.strftime('%Y-%m-%d'))
- renew_date = str_to_date(record.expiration_date)
- diff_time=int((renew_date-today).days)
- if (diff_time<=0):
- res[record.id]=0
- else:
- res[record.id]=diff_time
+ res = {}
+ for record in self.browse(cr, uid, ids, context=context):
+ if (record.expiration_date and (record.state == 'open' or record.state == 'toclose')):
+ today = str_to_datetime(time.strftime(tools.DEFAULT_SERVER_DATE_FORMAT))
+ renew_date = str_to_datetime(record.expiration_date)
+ diff_time = (renew_date-today).days
+ res[record.id] = diff_time > 0 and diff_time or 0
else:
- res[record.id]=-1
+ res[record.id] = -1
return res
- def act_renew_contract(self,cr,uid,ids,context=None):
- default={}
- contracts = self.browse(cr,uid,ids,context=context)
- for element in contracts:
- default['date']=fields.date.context_today(self, cr, uid, context=context)
- default['start_date']=str(str_to_date(element.expiration_date)+datetime.timedelta(days=1))
+ def act_renew_contract(self, cr, uid, ids, context=None):
+ assert len(ids) == 1, "This operation should only be done for 1 single contract at a time, as it it suppose to open a window as result"
+ for element in self.browse(cr, uid, ids, context=context):
#compute end date
- startdate = str_to_date(element.start_date)
- enddate = str_to_date(element.expiration_date)
- diffdate = (enddate-startdate)
- newenddate = enddate+diffdate
- default['expiration_date']=str(newenddate)
-
- newid = super(fleet_vehicle_log_contract, self).copy(cr, uid, ids[0], default, context=context)
- mod,modid = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'fleet', 'fleet_vehicle_log_contract_form')
+ startdate = str_to_datetime(element.start_date)
+ enddate = str_to_datetime(element.expiration_date)
+ diffdate = (enddate - startdate)
+ default = {
+ 'date': fields.date.context_today(self, cr, uid, context=context),
+ 'start_date': datetime.datetime.strftime(str_to_datetime(element.expiration_date) + datetime.timedelta(days=1), tools.DEFAULT_SERVER_DATE_FORMAT),
+ 'expiration_date': datetime.datetime.strftime(enddate + diffdate, tools.DEFAULT_SERVER_DATE_FORMAT),
+ }
+ newid = super(fleet_vehicle_log_contract, self).copy(cr, uid, element.id, default, context=context)
+ mod, modid = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'fleet', 'fleet_vehicle_log_contract_form')
return {
'name':_("Renew Contract"),
'view_mode': 'form',
'start_date': fields.date('Contract Start Date', help='Date when the coverage of the contract begins'),
'expiration_date': fields.date('Contract Expiration Date', help='Date when the coverage of the contract expirates (by default, one year after begin date)'),
'days_left': fields.function(get_days_left, type='integer', string='Warning Date'),
- 'insurer_id' :fields.many2one('res.partner', 'Supplier', domain="[('supplier','=',True)]"),
- 'purchaser_id': fields.many2one('res.partner', 'Contractor', domain="['|', ('customer','=',True), ('employee','=',True)]",help='Person to which the contract is signed for'),
+ 'insurer_id' :fields.many2one('res.partner', 'Supplier'),
+ 'purchaser_id': fields.many2one('res.partner', 'Contractor', help='Person to which the contract is signed for'),
'ins_ref': fields.char('Contract Reference', size=64),
'state': fields.selection([('open', 'In Progress'), ('toclose','To Close'), ('closed', 'Terminated')], 'Status', readonly=True, help='Choose wheter the contract is still valid or not'),
'notes': fields.text('Terms and Conditions', help='Write here all supplementary informations relative to this contract'),
'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
}
_defaults = {
- 'purchaser_id': lambda self, cr, uid, ctx: uid,
+ 'purchaser_id': lambda self, cr, uid, ctx: self.pool.get('res.users').browse(cr, uid, uid, context=ctx).partner_id.id or False,
'date': fields.date.context_today,
'start_date': fields.date.context_today,
'state':'open',
'expiration_date': lambda self, cr, uid, ctx: self.compute_next_year_date(fields.date.context_today(self, cr, uid, context=ctx)),
'cost_frequency': 'no',
- 'cost_subtype': _get_default_contract_type,
+ 'cost_subtype_id': _get_default_contract_type,
'cost_type': 'contract',
}
return super(fleet_vehicle_log_contract, self).copy(cr, uid, id, default, context=context)
def contract_close(self, cr, uid, ids, context=None):
- return self.write(cr, uid, ids, {'state': 'closed'},context=context)
+ return self.write(cr, uid, ids, {'state': 'closed'}, context=context)
def contract_open(self, cr, uid, ids, context=None):
- return self.write(cr, uid, ids, {'state': 'open'},context=context)
+ return self.write(cr, uid, ids, {'state': 'open'}, context=context)
class fleet_contract_state(osv.Model):
_name = 'fleet.contract.state'
_description = 'Contains the different possible status of a leasing contract'
_columns = {
- 'name':fields.char('Contract Status', size=32, required=True),
+ 'name':fields.char('Contract Status', size=64, required=True),
}