mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2025-12-30 15:49:44 -06:00
Implement comprehensive time rounding preferences that allow each user to configure how their time entries are rounded when stopping timers. Features: - Per-user rounding settings (independent from global config) - Multiple rounding intervals: 1, 5, 10, 15, 30, 60 minutes - Three rounding methods: nearest, up (ceiling), down (floor) - Enable/disable toggle for flexible time tracking - Real-time preview showing rounding examples - Backward compatible with existing global rounding settings Database Changes: - Add migration 027 with three new user columns: * time_rounding_enabled (Boolean, default: true) * time_rounding_minutes (Integer, default: 1) * time_rounding_method (String, default: 'nearest') Implementation: - Update User model with rounding preference fields - Modify TimeEntry.calculate_duration() to use per-user rounding - Create app/utils/time_rounding.py with core rounding logic - Update user settings route and template with rounding UI - Add comprehensive unit, model, and smoke tests (50+ test cases) UI/UX: - Add "Time Rounding Preferences" section to user settings page - Interactive controls with live example visualization - Descriptive help text and method explanations - Fix navigation: Settings link now correctly points to user.settings - Fix CSRF token in settings form Documentation: - Add comprehensive user guide (docs/TIME_ROUNDING_PREFERENCES.md) - Include API documentation and usage examples - Provide troubleshooting guide and best practices - Add deployment instructions for migration Testing: - Unit tests for rounding logic (tests/test_time_rounding.py) - Model integration tests (tests/test_time_rounding_models.py) - End-to-end smoke tests (tests/test_time_rounding_smoke.py) Fixes: - Correct settings navigation link in user dropdown menu - Fix CSRF token format in user settings template This feature enables flexible billing practices, supports different client requirements, and maintains exact time tracking when needed.
106 lines
3.8 KiB
Python
106 lines
3.8 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Simple script to apply the time rounding preferences migration
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
|
|
# Add the project root to the path
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
from app import create_app, db
|
|
from sqlalchemy import inspect, text
|
|
|
|
def check_columns_exist():
|
|
"""Check if the time rounding columns already exist"""
|
|
app = create_app()
|
|
with app.app_context():
|
|
inspector = inspect(db.engine)
|
|
columns = [col['name'] for col in inspector.get_columns('users')]
|
|
|
|
has_enabled = 'time_rounding_enabled' in columns
|
|
has_minutes = 'time_rounding_minutes' in columns
|
|
has_method = 'time_rounding_method' in columns
|
|
|
|
return has_enabled, has_minutes, has_method
|
|
|
|
def apply_migration():
|
|
"""Apply the migration to add time rounding columns"""
|
|
app = create_app()
|
|
with app.app_context():
|
|
print("Applying time rounding preferences migration...")
|
|
|
|
# Check if columns already exist
|
|
has_enabled, has_minutes, has_method = check_columns_exist()
|
|
|
|
if has_enabled and has_minutes and has_method:
|
|
print("✓ Migration already applied! All columns exist.")
|
|
return True
|
|
|
|
# Apply the migration
|
|
try:
|
|
if not has_enabled:
|
|
print("Adding time_rounding_enabled column...")
|
|
db.session.execute(text(
|
|
"ALTER TABLE users ADD COLUMN time_rounding_enabled BOOLEAN DEFAULT 1 NOT NULL"
|
|
))
|
|
|
|
if not has_minutes:
|
|
print("Adding time_rounding_minutes column...")
|
|
db.session.execute(text(
|
|
"ALTER TABLE users ADD COLUMN time_rounding_minutes INTEGER DEFAULT 1 NOT NULL"
|
|
))
|
|
|
|
if not has_method:
|
|
print("Adding time_rounding_method column...")
|
|
db.session.execute(text(
|
|
"ALTER TABLE users ADD COLUMN time_rounding_method VARCHAR(10) DEFAULT 'nearest' NOT NULL"
|
|
))
|
|
|
|
db.session.commit()
|
|
print("✓ Migration applied successfully!")
|
|
|
|
# Verify
|
|
has_enabled, has_minutes, has_method = check_columns_exist()
|
|
if has_enabled and has_minutes and has_method:
|
|
print("✓ Verification passed! All columns exist.")
|
|
return True
|
|
else:
|
|
print("✗ Verification failed! Some columns are missing.")
|
|
return False
|
|
|
|
except Exception as e:
|
|
print(f"✗ Migration failed: {e}")
|
|
db.session.rollback()
|
|
return False
|
|
|
|
if __name__ == '__main__':
|
|
print("=== Time Rounding Preferences Migration ===")
|
|
print()
|
|
|
|
# Check current state
|
|
try:
|
|
has_enabled, has_minutes, has_method = check_columns_exist()
|
|
print("Current database state:")
|
|
print(f" - time_rounding_enabled: {'✓ exists' if has_enabled else '✗ missing'}")
|
|
print(f" - time_rounding_minutes: {'✓ exists' if has_minutes else '✗ missing'}")
|
|
print(f" - time_rounding_method: {'✓ exists' if has_method else '✗ missing'}")
|
|
print()
|
|
except Exception as e:
|
|
print(f"✗ Could not check database state: {e}")
|
|
sys.exit(1)
|
|
|
|
# Apply migration if needed
|
|
if has_enabled and has_minutes and has_method:
|
|
print("All columns already exist. No migration needed.")
|
|
else:
|
|
success = apply_migration()
|
|
if success:
|
|
print("\n✓ Migration complete! You can now use the time rounding preferences feature.")
|
|
print(" Please restart your application to load the changes.")
|
|
else:
|
|
print("\n✗ Migration failed. Please check the error messages above.")
|
|
sys.exit(1)
|
|
|