From a7d637adcbc879ec885817b75a6103e29ed78c90 Mon Sep 17 00:00:00 2001 From: Danny McClelland Date: Wed, 22 Apr 2026 20:56:30 +0100 Subject: [PATCH 1/3] feat: add hatchback, station wagon/estate, and pickup/ute vehicle types --- app/models.py | 7 +++++++ app/templates/dashboard.html | 2 +- app/templates/vehicles/index.html | 6 +++--- app/templates/vehicles/report_pdf.html | 2 +- app/templates/vehicles/view.html | 2 +- 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/app/models.py b/app/models.py index db928b3..55ac079 100644 --- a/app/models.py +++ b/app/models.py @@ -245,6 +245,10 @@ def get_total_expense_cost(self): def get_total_cost(self): return self.get_total_fuel_cost() + self.get_total_expense_cost() + @property + def vehicle_type_label(self): + return dict(VEHICLE_TYPES).get(self.vehicle_type, self.vehicle_type.replace('_', ' ').title()) + @property def currency_symbol(self): return get_currency_symbol(self.owner.currency if self.owner else None) @@ -709,6 +713,9 @@ def get_all_branding(): ('scooter', _l('Scooter')), ('truck', _l('Truck')), ('suv', _l('SUV')), + ('hatchback', _l('Hatchback')), + ('station_wagon', _l('Station Wagon / Estate')), + ('pickup', _l('Pickup / Ute')), ('tractor', _l('Tractor')), ('atv_utv', _l('ATV/UTV')), ('boat', _l('Boat')), diff --git a/app/templates/dashboard.html b/app/templates/dashboard.html index 98f2c99..5f15e4f 100644 --- a/app/templates/dashboard.html +++ b/app/templates/dashboard.html @@ -162,7 +162,7 @@

{{ _('Your Vehicle
  • - {% if vehicle.vehicle_type == 'car' %} + {% if vehicle.vehicle_type in ('car', 'hatchback', 'station_wagon') %} {% elif vehicle.vehicle_type == 'motorbike' %} diff --git a/app/templates/vehicles/index.html b/app/templates/vehicles/index.html index dee9ff2..b9fccdd 100644 --- a/app/templates/vehicles/index.html +++ b/app/templates/vehicles/index.html @@ -47,13 +47,13 @@

    {% if show_archived

    {% else %}
    - {% if vehicle.vehicle_type == 'car' %} + {% if vehicle.vehicle_type in ('car', 'hatchback', 'station_wagon') %} - {% elif vehicle.vehicle_type == 'van' %} + {% elif vehicle.vehicle_type in ('van', 'pickup') %} @@ -85,7 +85,7 @@

    {{ vehicle.name }}

    {{ vehicle.make or '' }} {{ vehicle.model or '' }} {% if vehicle.year %}({{ vehicle.year }}){% endif %}

    - {{ vehicle.vehicle_type }} + {{ vehicle.vehicle_type_label }} {{ "%.2f"|format(vehicle.get_total_cost()) }} {{ current_user.currency }}
    diff --git a/app/templates/vehicles/report_pdf.html b/app/templates/vehicles/report_pdf.html index 0ff3954..7143fe3 100644 --- a/app/templates/vehicles/report_pdf.html +++ b/app/templates/vehicles/report_pdf.html @@ -241,7 +241,7 @@

    Vehicle Details

    Type - {{ vehicle.vehicle_type }} + {{ vehicle.vehicle_type_label }} Make diff --git a/app/templates/vehicles/view.html b/app/templates/vehicles/view.html index e95adae..f032f85 100644 --- a/app/templates/vehicles/view.html +++ b/app/templates/vehicles/view.html @@ -49,7 +49,7 @@

    {{ vehicle.name }}<
    {{ _('Type') }}
    -
    {{ vehicle.vehicle_type }}
    +
    {{ vehicle.vehicle_type_label }}
    {{ _('Fuel') }}
    From bdffec0315383298dbe34e789bb56ec2c038a0ea Mon Sep 17 00:00:00 2001 From: Danny McClelland Date: Wed, 22 Apr 2026 21:29:42 +0100 Subject: [PATCH 2/3] feat: support secondary fuel type per vehicle for tracking e.g. AdBlue alongside diesel --- app/models.py | 3 ++ app/routes/fuel.py | 10 +++-- app/routes/vehicles.py | 2 + app/templates/fuel/form.html | 43 ++++++++++++++++++- app/templates/vehicles/form.html | 12 ++++++ ...dd_secondary_fuel_type_to_vehicles_and_.py | 32 ++++++++++++++ 6 files changed, 97 insertions(+), 5 deletions(-) create mode 100644 migrations/versions/ee92897cc33b_add_secondary_fuel_type_to_vehicles_and_.py diff --git a/app/models.py b/app/models.py index 55ac079..910620d 100644 --- a/app/models.py +++ b/app/models.py @@ -181,6 +181,7 @@ class Vehicle(db.Model): # Fuel info fuel_type = db.Column(db.String(20), default='petrol') # petrol, diesel, electric, hybrid, lpg + secondary_fuel_type = db.Column(db.String(20), nullable=True) # e.g. adblue, lpg tank_capacity = db.Column(db.Float) # in liters battery_capacity = db.Column(db.Float) # in kWh for EVs @@ -369,6 +370,7 @@ def to_dict(self): 'registration': self.registration, 'vin': self.vin, 'fuel_type': self.fuel_type, + 'secondary_fuel_type': self.secondary_fuel_type, 'tank_capacity': self.tank_capacity, 'is_active': self.is_active, 'created_at': self.created_at.isoformat() if self.created_at else None, @@ -419,6 +421,7 @@ class FuelLog(db.Model): price_per_unit = db.Column(db.Float) # price per liter total_cost = db.Column(db.Float) + fuel_type = db.Column(db.String(20), nullable=True) # overrides vehicle primary; set when vehicle has secondary fuel type is_full_tank = db.Column(db.Boolean, default=True) is_missed = db.Column(db.Boolean, default=False) # missed fill-up flag diff --git a/app/routes/fuel.py b/app/routes/fuel.py index 03977c0..742ac45 100644 --- a/app/routes/fuel.py +++ b/app/routes/fuel.py @@ -5,7 +5,7 @@ from flask_login import login_required, current_user from werkzeug.utils import secure_filename from app import db -from app.models import Vehicle, FuelLog, Attachment, FuelStation, FuelPriceHistory +from app.models import Vehicle, FuelLog, Attachment, FuelStation, FuelPriceHistory, FUEL_TYPES from app.security import validate_file_upload, secure_filename_with_uuid, validate_positive_number from flask_babel import gettext as _ from app.services.tessie import TessieService @@ -84,6 +84,7 @@ def new(): volume=volume, price_per_unit=price_per_unit, total_cost=total_cost, + fuel_type=request.form.get('fuel_type') or None, is_full_tank=request.form.get('is_full_tank') == 'on', is_missed=request.form.get('is_missed') == 'on', station=request.form.get('station'), @@ -107,7 +108,7 @@ def new(): station_id=station_id, user_id=current_user.id, date=log.date, - fuel_type=vehicle.fuel_type or 'petrol', + fuel_type=log.fuel_type or vehicle.fuel_type or 'petrol', price_per_unit=log.price_per_unit ) db.session.add(price_history) @@ -146,6 +147,7 @@ def new(): log=None, vehicles=vehicles, stations=stations, + fuel_types=FUEL_TYPES, selected_vehicle_id=selected_vehicle_id) @@ -176,6 +178,7 @@ def edit(log_id): log.volume = float(request.form.get('volume')) if request.form.get('volume') else None log.price_per_unit = float(request.form.get('price_per_unit')) if request.form.get('price_per_unit') else None log.total_cost = float(request.form.get('total_cost')) if request.form.get('total_cost') else None + log.fuel_type = request.form.get('fuel_type') or None log.is_full_tank = request.form.get('is_full_tank') == 'on' log.is_missed = request.form.get('is_missed') == 'on' log.station = request.form.get('station') @@ -208,7 +211,7 @@ def edit(log_id): station_id=station_id, user_id=current_user.id, date=log.date, - fuel_type=log.vehicle.fuel_type or 'petrol', + fuel_type=log.fuel_type or log.vehicle.fuel_type or 'petrol', price_per_unit=log.price_per_unit )) @@ -241,6 +244,7 @@ def edit(log_id): log=log, vehicles=vehicles, stations=stations, + fuel_types=FUEL_TYPES, selected_vehicle_id=log.vehicle_id) diff --git a/app/routes/vehicles.py b/app/routes/vehicles.py index 1a929b5..a4762ed 100644 --- a/app/routes/vehicles.py +++ b/app/routes/vehicles.py @@ -54,6 +54,7 @@ def new(): registration=request.form.get('registration'), vin=request.form.get('vin'), fuel_type=request.form.get('fuel_type'), + secondary_fuel_type=request.form.get('secondary_fuel_type') or None, tank_capacity=float(request.form.get('tank_capacity')) if request.form.get('tank_capacity') else None, notes=request.form.get('notes') ) @@ -178,6 +179,7 @@ def edit(vehicle_id): vehicle.registration = request.form.get('registration') vehicle.vin = request.form.get('vin') vehicle.fuel_type = request.form.get('fuel_type') + vehicle.secondary_fuel_type = request.form.get('secondary_fuel_type') or None vehicle.tank_capacity = float(request.form.get('tank_capacity')) if request.form.get('tank_capacity') else None vehicle.notes = request.form.get('notes') diff --git a/app/templates/fuel/form.html b/app/templates/fuel/form.html index 03e3935..726cbd1 100644 --- a/app/templates/fuel/form.html +++ b/app/templates/fuel/form.html @@ -23,7 +23,9 @@

    {% if log %}Ed data-last-odometer="{{ vehicle.get_last_odometer(vehicle.get_effective_odometer_unit()) }}" data-odometer-unit="{{ vehicle.get_effective_odometer_unit() }}" data-uses-tessie="{{ 'true' if vehicle.uses_tessie_odometer() else 'false' }}" - data-tessie-odometer="{% if vehicle.tessie_last_odometer %}{% if vehicle.get_effective_odometer_unit() == 'mi' %}{{ (vehicle.tessie_last_odometer * 0.621371)|round|int }}{% else %}{{ vehicle.tessie_last_odometer|round|int }}{% endif %}{% endif %}"> + data-tessie-odometer="{% if vehicle.tessie_last_odometer %}{% if vehicle.get_effective_odometer_unit() == 'mi' %}{{ (vehicle.tessie_last_odometer * 0.621371)|round|int }}{% else %}{{ vehicle.tessie_last_odometer|round|int }}{% endif %}{% endif %}" + data-primary-fuel="{{ vehicle.fuel_type }}" + data-secondary-fuel="{{ vehicle.secondary_fuel_type or '' }}"> {{ vehicle.name }} {% endfor %} @@ -104,6 +106,13 @@

    {% if log %}Ed {% endif %}

    + +
    {% if log %}Ed } } +function updateFuelTypeSelector(selectedOption) { + const fuelTypeField = document.getElementById('fuel-type-field'); + const fuelTypeSelect = document.getElementById('fuel_type'); + const primaryFuel = selectedOption.getAttribute('data-primary-fuel') || ''; + const secondaryFuel = selectedOption.getAttribute('data-secondary-fuel') || ''; + const fuelLabels = {{ dict(fuel_types)|tojson }}; + + if (secondaryFuel) { + fuelTypeSelect.innerHTML = ''; + [primaryFuel, secondaryFuel].forEach(function(value) { + const opt = document.createElement('option'); + opt.value = value; + opt.textContent = fuelLabels[value] || value; + {% if log and log.fuel_type %} + if (value === '{{ log.fuel_type }}') opt.selected = true; + {% endif %} + fuelTypeSelect.appendChild(opt); + }); + fuelTypeField.classList.remove('hidden'); + } else { + fuelTypeField.classList.add('hidden'); + fuelTypeSelect.innerHTML = ''; + } +} + function calculateTotal() { const volume = parseFloat(document.getElementById('volume').value) || 0; const pricePerUnit = parseFloat(document.getElementById('price_per_unit').value) || 0; @@ -214,10 +248,15 @@

    {% if log %}Ed } } -// Initialize odometer for selected vehicle +// Initialize odometer and fuel type for selected vehicle document.addEventListener('DOMContentLoaded', function() { const vehicleSelect = document.getElementById('vehicle_id'); updateVehicleOdometer(vehicleSelect.value); + const selectedOption = vehicleSelect.options[vehicleSelect.selectedIndex]; + updateFuelTypeSelector(selectedOption); + vehicleSelect.addEventListener('change', function() { + updateFuelTypeSelector(this.options[this.selectedIndex]); + }); }); {% endblock %} diff --git a/app/templates/vehicles/form.html b/app/templates/vehicles/form.html index 76f231b..4ca99bf 100644 --- a/app/templates/vehicles/form.html +++ b/app/templates/vehicles/form.html @@ -65,6 +65,18 @@

    {{ _('Basic I

    +
    + + +

    {{ _('Optional. Use for vehicles with a secondary fluid to track, e.g. AdBlue alongside Diesel.') }}

    +
    +
    Date: Wed, 22 Apr 2026 21:31:05 +0100 Subject: [PATCH 3/3] chore: bump version to 0.21.0 --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index 7eda3f7..bfd6dcb 100644 --- a/config.py +++ b/config.py @@ -4,7 +4,7 @@ basedir = Path(__file__).parent.absolute() -APP_VERSION = '0.20.0' +APP_VERSION = '0.21.0' RELEASE_CHANNEL = os.environ.get('RELEASE_CHANNEL', 'stable') GIT_SHA = os.environ.get('GIT_SHA', '')[:7] # Short SHA GITHUB_REPO = 'dannymcc/may'