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.
9.5 KiB
Time Rounding Preferences - Per-User Settings
Overview
The Time Rounding Preferences feature allows each user to configure how their time entries are rounded when they stop timers. This provides flexibility for different billing practices and time tracking requirements while maintaining accurate time records.
Key Features
- Per-User Configuration: Each user can set their own rounding preferences independently
- Multiple Rounding Intervals: Support for 1, 5, 10, 15, 30, and 60-minute intervals
- Three Rounding Methods:
- Nearest: Round to the closest interval (standard rounding)
- Up: Always round up to the next interval (ceiling)
- Down: Always round down to the previous interval (floor)
- Enable/Disable Toggle: Users can disable rounding to track exact time
- Real-time Preview: Visual examples show how rounding will be applied
User Guide
Accessing Rounding Settings
- Navigate to Settings from the user menu
- Scroll to the Time Rounding Preferences section
- Configure your preferences:
- Toggle Enable Time Rounding on/off
- Select your preferred Rounding Interval
- Choose your Rounding Method
- Click Save Settings to apply changes
Understanding Rounding Methods
Round to Nearest (Default)
Standard mathematical rounding to the closest interval.
Example with 15-minute intervals:
- 7 minutes → 0 minutes
- 8 minutes → 15 minutes
- 62 minutes → 60 minutes
- 68 minutes → 75 minutes
Always Round Up
Always rounds up to the next interval, ensuring you never under-bill.
Example with 15-minute intervals:
- 1 minute → 15 minutes
- 61 minutes → 75 minutes
- 60 minutes → 60 minutes (exact match)
Always Round Down
Always rounds down to the previous interval, ensuring conservative billing.
Example with 15-minute intervals:
- 14 minutes → 0 minutes
- 74 minutes → 60 minutes
- 75 minutes → 75 minutes (exact match)
Choosing the Right Settings
For Freelancers/Contractors:
- Use 15-minute intervals with Round to Nearest for balanced billing
- Use Round Up if client agreements favor rounding up
- Use 5 or 10 minutes for more granular tracking
For Internal Time Tracking:
- Use No rounding (1 minute) for exact time tracking
- Use 15 or 30 minutes for simplified reporting
For Project-Based Billing:
- Use 30 or 60 minutes for project-level granularity
- Use Round Down for conservative estimates
Technical Details
Database Schema
The following fields are added to the users table:
time_rounding_enabled BOOLEAN DEFAULT 1 NOT NULL
time_rounding_minutes INTEGER DEFAULT 1 NOT NULL
time_rounding_method VARCHAR(10) DEFAULT 'nearest' NOT NULL
Default Values
For new and existing users:
- Enabled:
True(rounding is enabled by default) - Minutes:
1(no rounding, exact time) - Method:
'nearest'(standard rounding)
How Rounding is Applied
- Timer Start: When a user starts a timer, no rounding is applied
- Timer Stop: When a user stops a timer:
- Calculate raw duration (end time - start time)
- Apply user's rounding preferences
- Store rounded duration in
duration_secondsfield
- Manual Entries: Rounding is applied when creating/editing manual entries
Backward Compatibility
The feature is fully backward compatible:
- If user preferences don't exist, the system falls back to the global
ROUNDING_MINUTESconfig setting - Existing time entries are not retroactively rounded
- Users without the new fields will use global rounding settings
API Integration
Get User Rounding Settings
from app.utils.time_rounding import get_user_rounding_settings
settings = get_user_rounding_settings(user)
# Returns: {'enabled': True, 'minutes': 15, 'method': 'nearest'}
Apply Rounding to Duration
from app.utils.time_rounding import apply_user_rounding
raw_seconds = 3720 # 62 minutes
rounded_seconds = apply_user_rounding(raw_seconds, user)
# Returns: 3600 (60 minutes) with 15-min nearest rounding
Manual Rounding
from app.utils.time_rounding import round_time_duration
rounded = round_time_duration(
duration_seconds=3720, # 62 minutes
rounding_minutes=15,
rounding_method='up'
)
# Returns: 4500 (75 minutes)
Migration Guide
Applying the Migration
Run the Alembic migration to add the new fields:
# Using Alembic
alembic upgrade head
# Or using the migration script
python migrations/manage_migrations.py upgrade
Migration Details
- Migration File:
migrations/versions/027_add_user_time_rounding_preferences.py - Adds: Three new columns to the
userstable - Safe: Non-destructive, adds columns with default values
- Rollback: Supported via downgrade function
Verifying Migration
from app.models import User
from app import db
# Check if fields exist
user = User.query.first()
assert hasattr(user, 'time_rounding_enabled')
assert hasattr(user, 'time_rounding_minutes')
assert hasattr(user, 'time_rounding_method')
# Check default values
assert user.time_rounding_enabled == True
assert user.time_rounding_minutes == 1
assert user.time_rounding_method == 'nearest'
Configuration
Available Rounding Intervals
The following intervals are supported:
1- No rounding (exact time)5- 5 minutes10- 10 minutes15- 15 minutes30- 30 minutes (half hour)60- 60 minutes (1 hour)
Available Rounding Methods
Three methods are supported:
'nearest'- Round to nearest interval'up'- Always round up (ceiling)'down'- Always round down (floor)
Global Fallback Setting
If per-user rounding is not configured, the system uses the global setting:
# In app/config.py
ROUNDING_MINUTES = int(os.environ.get('ROUNDING_MINUTES', 1))
Testing
Running Tests
# Run all time rounding tests
pytest tests/test_time_rounding*.py -v
# Run specific test suites
pytest tests/test_time_rounding.py -v # Unit tests
pytest tests/test_time_rounding_models.py -v # Model integration tests
pytest tests/test_time_rounding_smoke.py -v # Smoke tests
Test Coverage
The feature includes:
- Unit Tests: Core rounding logic (50+ test cases)
- Model Tests: Database integration and TimeEntry model
- Smoke Tests: End-to-end workflows and edge cases
Examples
Example 1: Freelancer with 15-Minute Billing
# User settings
user.time_rounding_enabled = True
user.time_rounding_minutes = 15
user.time_rounding_method = 'nearest'
# Time entry: 62 minutes
# Result: 60 minutes (rounded to nearest 15-min interval)
Example 2: Contractor with Round-Up Policy
# User settings
user.time_rounding_enabled = True
user.time_rounding_minutes = 15
user.time_rounding_method = 'up'
# Time entry: 61 minutes
# Result: 75 minutes (rounded up to next 15-min interval)
Example 3: Exact Time Tracking
# User settings
user.time_rounding_enabled = False
# Time entry: 62 minutes 37 seconds
# Result: 62 minutes 37 seconds (3757 seconds, exact)
Example 4: Conservative Billing
# User settings
user.time_rounding_enabled = True
user.time_rounding_minutes = 30
user.time_rounding_method = 'down'
# Time entry: 62 minutes
# Result: 60 minutes (rounded down to previous 30-min interval)
Troubleshooting
Rounding Not Applied
Issue: Time entries are not being rounded despite settings being enabled.
Solutions:
- Verify rounding is enabled: Check
user.time_rounding_enabled == True - Check rounding interval: Ensure
user.time_rounding_minutes > 1 - Verify migration was applied: Check if columns exist in database
- Clear cache and restart application
Unexpected Rounding Results
Issue: Durations are rounded differently than expected.
Solutions:
- Verify rounding method setting (nearest/up/down)
- Check the actual rounding interval (minutes value)
- Test with example calculations using the utility functions
- Review the rounding method documentation
Migration Fails
Issue: Alembic migration fails to apply.
Solutions:
- Check database permissions
- Verify no conflicting migrations
- Run
alembic currentto check migration state - Try manual column addition as fallback
- Check logs for specific error messages
Best Practices
- Choose Appropriate Intervals: Match your rounding to billing agreements
- Document Your Choice: Note why you chose specific rounding settings
- Test Before Production: Verify rounding behavior with test entries
- Communicate with Clients: Ensure clients understand your rounding policy
- Review Regularly: Periodically review if rounding settings still make sense
- Keep Records: Document any changes to rounding preferences
Future Enhancements
Potential improvements for future versions:
- Project-specific rounding overrides
- Time-of-day based rounding rules
- Client-specific rounding preferences
- Rounding reports and analytics
- Bulk update of historical entries with new rounding
Support
For issues or questions:
- Check this documentation first
- Review test files for usage examples
- Check the codebase in
app/utils/time_rounding.py - Open an issue on the project repository
Changelog
Version 1.0 (2025-10-24)
- Initial implementation of per-user time rounding preferences
- Support for 6 rounding intervals (1, 5, 10, 15, 30, 60 minutes)
- Support for 3 rounding methods (nearest, up, down)
- UI integration in user settings page
- Comprehensive test coverage
- Full backward compatibility with global rounding settings