- Register send_base_telemetry_heartbeat_with_app cron at 03:00 UTC
- setup: call check_and_send_telemetry when user opts in during setup
- admin: call check_and_send_telemetry when toggling detailed analytics on
- Delegate track_event, identify_user, track_page_view to telemetry service
- Only send detailed analytics when user has opted in (is_detailed_analytics_enabled)
- Call send_base_first_seen() once at app startup (idempotent per install)
- posthog_funnels: require telemetry_enabled for funnel tracking
- posthog_monitoring: require telemetry_enabled for error/performance events
- Webhook models: remove duplicate index definitions so db.create_all()
no longer raises 'index already exists' (columns already have index=True)
- ImportService: fix circular import by late-importing ClientService,
ProjectService, TimeTrackingService in __init__
- reports: fix F823 by renaming unpack variable _ to _entry_count to avoid
shadowing gettext _ in export_task_excel()
- Code quality: add .flake8 with extend-ignore so flake8 CI passes;
simplify pyproject.toml isort config (drop unsupported options)
- Format: run black and isort on app/
- tests: restore minimal app fixture in test_import_export_models
- Add RecurringInvoiceRepository and RecurringInvoiceService; refactor recurring_invoice model
- Add GanttService and move gantt logic from route to service
- Expand ReportingService and simplify reports route
- Add license_utils and user license template/settings
- Refactor routes to use scope_filter, api_responses, and services (API v1, timer, admin, invoices, etc.)
- Extend invoice_service for recurring; cache and scope_filter utils; base/template updates
Defer manual entry worked-time recalculation to the next microtask so
the DOM has the latest start/end date and time before reading. Add
input listeners so recalculation runs on every date/time change.
Fixes incorrect duration when end date is in the past (e.g. yesterday)
until the user reselected the end date.
- Add data-devaluation-supported on stock item options (trackable + default cost).
- When return/waste is selected and item cannot be devalued, disable 'Apply
devaluation', show message: 'Devaluation requires a trackable item with a
default cost.'
- Same message for standalone devaluation type when item is unsupported.
- Add en translation string for the new message.
OAuth: Replace deprecated accounting.transactions scope with accounting.invoices and accounting.payments so new Xero Developer apps (on or after 2026-03-02) complete the authorization flow.
Expense sync: Use /api.xro/2.0/ExpenseClaims instead of non-existent /api.xro/2.0/Expenses; read ExpenseClaimID from response.
API: Add optional json_body to _api_request and send invoice/expense payloads (Invoices and ExpenseClaims wrappers) to Xero.
Docs: Add docs/integrations/XERO.md (setup, scopes, sync, troubleshooting) and CHANGELOG entry.
When the IdP returns an encrypted ID token (e.g. Authentik with Encryption Key
set), Authlib raises UnsupportedAlgorithmError. Previously this was caught
generically and users saw a misleading message about session/cookie/proxy.
- Detect algorithm/JWE-related errors via exception type, module, or message
- Log reason=unsupported_algorithm_or_jwe and a specific warning
- Flash: disable ID token encryption (e.g. leave Authentik Encryption Key empty)
- Keep existing message for real session/state/code failures
The timer blueprint had two view functions named resume_timer, both
registering as endpoint 'timer.resume_timer' and causing Flask to raise
AssertionError on app load.
- Give the 'resume by id' route a unique endpoint: resume_timer_by_id
- Rename the view for GET /timer/resume/<timer_id> to resume_timer_by_id
- Update templates to use timer.resume_timer_by_id for links with timer_id
- Keep timer.resume_timer for POST (resume current paused timer)
- Mileage: Add GET /mileage/export/csv and /mileage/export/pdf with same
filters as list (status, project, client, date range, search). Export
buttons in list header; JS builds export URL from current filter form.
- Mileage PDF: New app/utils/mileage_pdf.py (ReportLab, landscape A4,
totals row for distance and amount).
- Per diem: Add Client filter to list (with client-lock/single-client
handling). Add GET /per-diem/export/csv and /per-diem/export/pdf.
- Per diem PDF: New app/utils/per_diem_pdf.py (same style as mileage).
- Export links always use current filters (no need to submit first).
- CHANGELOG and docs/import_export/README updated.
- Add visible Apply filters button in filter header so users can apply
Start/End date and other filters without scrolling; expand panel if collapsed
- Keep CSV/PDF export links in sync with current filters: set href from URL
on load and update on form change so export (including right-click Open in
new tab / Save link as) always uses the filtered date range
- Document fix in CHANGELOG under [Unreleased]
- Backend: WorkforceGovernanceService.delete_period, delete_leave_request,
delete_leave_type, delete_holiday with permission and state checks
- Web: POST delete routes in workforce blueprint; delete buttons in dashboard
for periods (draft/rejected), time-off (draft/submitted/cancelled), leave
types list, and company holidays (admin only)
- API v1: DELETE endpoints for timesheet-periods, time-off/requests,
time-off/leave-types, time-off/holidays (scopes and admin where required)
- Desktop: deleteTimesheetPeriod/deleteTimeOffRequest in API client; Delete
buttons and handlers in workforce view with confirmation and refresh
- Mobile: deleteTimesheetPeriod/deleteTimeOffRequest in API client; Delete
in popup menus for periods and time-off requests
- Docs: WORKFORCE_DELETE.md, PROJECT_STRUCTURE and API_TOKEN_SCOPES updates
- Dashboard: Pause/Resume buttons, break and Paused badge, elapsed uses break-adjusted duration
- Timer page: Pause/Resume/Stop, break display
- Floating bar: paused state, Resume on click when paused; use server current_duration when paused
- Manual entry: Break field (HH:MM), Suggest button using default break rules
- Edit time entry: Break field (HH:MM) for admins
- Workforce dashboard: show Accumulated overtime (YTD) next to Leave Balances
- Add get_overtime_leave_type() and validate requested_hours <= YTD for overtime leave
- Time-off form: 'Take as paid leave' link, overtime type preset, available hours hint
- create_leave_request rejects overtime requests exceeding YTD with clear error
- Main dashboard: compute and display Overtime (YTD) in Month's Hours card
- Analytics: GET /api/analytics/overtime supports period=ytd and start_date/end_date
- API: dashboard stats endpoints include overtime_ytd_hours in response
- get_overtime_ytd(user): returns overtime from Jan 1 through today
- get_overtime_last_12_months(user): returns rolling 12-month overtime
- Reuses calculate_period_overtime; no new DB columns
- Enforce scope in timer routes: start_timer (POST), start_timer_for_project (GET),
and start_timer_from_template; deny with flash+redirect when project/client not allowed
- Add user_can_access_project check in api_start_timer (legacy API), API v1 timer/start,
and kiosk start-timer; return 403 with clear error message
- Scope dashboard Start Timer modal: load active_projects and active_clients via
apply_project_scope_to_model/apply_client_scope_to_model so subcontractors only see
assigned options
- Document timer start scope in SUBCONTRACTOR_ROLE.md (web, API, kiosk, 403/redirect)
On viewports <=767px, skip loading Toast UI Editor for the notes field on manual entry and edit timer pages; use a plain textarea instead. Toast UI is heavy and was freezing/crashing mobile Safari and Chrome. Desktop behavior unchanged. Document in CHANGELOG and MOBILE_IMPROVEMENTS.md.
- Use timer.time_entries_overview instead of timer.time_entries when
building the 'View time entries' URL in the dashboard. The invalid
route name caused BuildError and an error page after stopping the
timer, even though the time entry was saved.
- Document the fix in CHANGELOG under Unreleased / Fixed.
Issue #555: Users could set start/end date but had no visible way to apply filters, and CSV/PDF export could ignore the date range if applied before the AJAX filter ran.
- Add explicit 'Apply filters' submit button so date and other filters are applied on click (and on Enter).
- Export CSV/PDF: on click, build URL from current form params so export always reflects the selected date range and filters.
- Initialize export links from form state on load so they match visible filters.
Fixes#555
Architecture and maintainability improvements per production-readiness plan:
- API v1: Split monolithic api_v1.py into per-resource blueprints
(api_v1_projects, api_v1_tasks, api_v1_clients, api_v1_invoices,
api_v1_expenses, api_v1_payments, api_v1_mileage, api_v1_deals,
api_v1_leads, api_v1_contacts). Register all in blueprint_registry;
keep info, health, auth and remaining routes in api_v1.py.
- Bootstrap: Move setup_logging to app/utils/setup_logging.py and
legacy migrations (task management, issues tables) to
app/utils/legacy_migrations.py. Use SQLAlchemy 2-compatible
db.engine.begin() in legacy_migrations.
- Dashboard: Add AnalyticsService.get_dashboard_top_projects and
get_time_by_project_chart; thin main dashboard route to call
services only and remove inline TimeEntry aggregation.
- Docs: Update ARCHITECTURE.md (module table, API structure, data
flow, design decisions), DEVELOPMENT.md (workflow, build steps,
test examples), CHANGELOG.md (Unreleased refactor entry).
- Move Timer card to top as hero (primary action: start/stop, quick start, repeat last)
- Replace four adjust-time forms with single form and JS-driven delta submit
- Show only last 5 recent entries with columns: Project, Duration, Date, Actions
- Add View all link to Time entries overview; fix post-stop toast URL to time_entries_overview
- Apply max-w-7xl mx-auto to main content for readable width on large screens
- Add first-class sidebar links for Timer and Time entries (above Calendar)
- Fix active state: only Time entries highlighted on time_entries_overview,
only Timer highlighted on other timer.* routes
- Keep Time Tracking dropdown closed on Log time and Time entries pages
- Remove duplicate Time Entries link from Time Tracking submenu
- Add form-input-error and disabled state to form-input in input.css
- Add empty_state_compact and loading_overlay macros to components/ui.html
- Migrate tasks/overdue.html from Bootstrap (_components.html) to Tailwind
(page_header, empty_state, alert from ui.html; consistent cards and grid)
Dashboard:
- Add time-by-project chart (last 7 days) with Chart.js horizontal bar; link to Summary report
Summary report:
- Add time-by-project (last 30d) bar chart and daily trend (14d) line chart
- Add one-page PDF export (today/week/month hours + top projects table)
Post-timer flow:
- After stop, show toast "Logged Xh on [Project]" with action link "View time entries"
- Toast manager: optional actionLink/actionLabel for action links in toasts
- Session carries timer_stopped_toast to dashboard; no duplicate flash
Remind to log:
- User setting "Remind me to log time at end of day" + time picker (Settings)
- Hourly job: send one email per day if user has <0.5h logged that day (user TZ)
- Migration 135: notification_remind_to_log, reminder_to_log_time on users
Quick wins (Phase A):
- A1: Quick timer actions — last timer context, Repeat last button, Quick start one-click form; pre-fill modal and tags from last entry
- A2: Unified empty states using empty_state macro on custom_view, time_entry_templates, saved_filters, issues; add loading_placeholder macro
- A3: Dashboard hierarchy — Activity and Support/Donate moved to secondary row below fold with reduced visual weight
- A4: Error/feedback consistency (flash-to-toast already in place)
Medium impact (Phase B):
- B5: Split API v1 — api_v1_common.py (shared helpers), api_v1_time_entries.py sub-blueprint for time-entries and timer/*; register api_v1_time_entries_bp
- B6: Start Timer UX — templates as prominent chips at top of modal; default last context and quick start from A1
- B7: Week in review — ReportingService.get_week_in_review(), route /reports/week-in-review, template and link from reports index
- B8: Tags discoverability — GET /api/tags, recent_tags in dashboard, tags input with datalist in Start Timer modal; last context includes tags
- B9: Frontend consolidation — document onboarding.js vs onboarding-enhanced.js in base.html
- B10: API validation — Marshmallow TimeEntryCreateSchema/TimeEntryUpdateSchema and handle_validation_error in api_v1_time_entries create/update
UX: Remove duplicate Timer actions — single Repeat last and Start Timer in header; body shows only Resume when recent entries exist (no duplicate Repeat last or Start new).
- In _on_page(), only skip IMAGE elements when their rect does not
intersect the page (was skipping when top-left was outside, which
hid overflowing images).
- Coerce element x, y, width, height to float to avoid type issues.
- In _draw_image_on_canvas(), clip all image drawing to the page
rectangle (beginPath + rect + clipPath) so the visible portion
is shown and overflow is clipped like in the preview.
- Try template image file path before get_image_base64 for more
reliable loading across deployments.
- Add INFO log when drawing template images for debugging.
Fixes#537 (export not showing decorative image when over border).
Before deleting a user, explicitly remove their donation_interactions rows so the delete succeeds even when the table is missing (e.g. after support banner was removed via license). Also handle missing donation_interactions table in safe_commit like time_entry_approvals.
- Dashboard cached template data containing ORM objects (active_timer,
recent_entries, top_projects, templates) that became detached when
served in a different request, causing 'Instance not bound to a Session'
and 'Database Error' on second visit
- Add migration 133 to merge heads 132 (timesheet governance) and 129
(task tags) so flask db upgrade runs without conflicts
- Update CHANGELOG
Switch embedded invoice PDFs to Factur-X CII payloads and tighten the PDF/A-3 and AS4 handling so exports better match the standards they advertise. Document the experimental native Peppol transport path and cover the new validation and embedding behavior with focused tests.
Unify buttons, cards, headers, toasts, and form treatments across the app so screens feel consistent and are easier to scan on desktop and mobile. Update the broader template set to use the shared UI primitives and responsive spacing patterns introduced in this refresh.
- Add TimesheetPeriod, TimesheetPolicy, TimeOff models and migration 132
- Add workforce blueprint, routes, and workforce_governance_service
- Add workforce dashboard template; register blueprint via blueprint_registry
- Extend User model for time-off and policy associations
Sync sidebar-collapsed class on document.documentElement when toggling.
The early script adds the class to documentElement on load; applyCollapsed
only updated appShell, so expanding left the class on html and labels
stayed hidden. Now both are updated so expand/collapse works correctly.
The flyout submenu used document coordinates (getBoundingClientRect +
scrollX/scrollY) while having position:fixed, causing it to shift with
scroll and disappear off-screen. Use viewport-relative coordinates only
so the flyout stays aligned with the trigger.
Implement native PEPPOL transport plumbing (identifier validation, SMP/SML discovery, and AS4 send path) and make ZUGFeRD/PDF export fail fast when embedding or PDF/A-3 normalization fails. Add settings, migrations, validators, tests, and docs so compliance issues are visible and verifiable.
Task lists: add Estimated column to All Tasks table and project tasks table; show estimated hours in task view Details. Also fix Pause/Complete button text disappearing after canceling confirm dialog.
Decorative images are only managed via Admin PDF layout templates. Remove the per-document upload section and related JS from invoice and quote edit pages so users do not add images there; template-based decorative images in pdf_layout/quote_pdf_layout remain unchanged.
- UBL: add unitCode C62 to InvoicedQuantity (required by EN 16931 / Peppol BR-CL-23)
- ZugFerd: attach XML as Associated File with relationship Alternative
- ZugFerd: inject ZUGFeRD XMP RDF for validators
- Docs: document AF/XMP, PDF/A-3 limitation, and validation (b2brouter, portinvoice)
- Tests: assert UBL contains InvoicedQuantity with unitCode in ZugFerd and Peppol tests
When saving the PDF template design, decorative image dimensions were exported from the inner Image's width/height only, which do not include scale. Resizing via the transformer applies scale to the Group, so the saved template always had the original image size.
Use the group's getClientRect() for position and size when building template_json and legacy HTML preview, with fallback to image dimensions × scaleX/scaleY. Applied to both Invoice and Quote PDF layout editors.
Implement the advertised 'Task tags and categorization' feature that was
listed in docs but missing from the task edit form.
- Add tags column to tasks table (String 500, comma-separated)
- Add tags field to task create and edit forms with validation
- Display tags as badges on task view page and list pages
- Add tags filter to main tasks list and My Tasks with AJAX support
- Include tags in task export (CSV) and data export
- Add tags to Task API (create, update, list with filter)
- Add tag_list property to Task model for convenient parsing
Also: fix(oidc) use domain instead of IP for HTTPS metadata fetch
to fix TLS SNI (Fixes#540)
- Add tooltips on sidebar and header support links (key option on hover)
- Add muted 'Get a one-time key' line under donate page hero
- Add muted key hint in about page support section
- Rephrase dashboard widget to 'You can hide this with a one-time key'
- Add new translatable strings to en messages.po
Admin menu:
- PDF Templates is now a top-level submenu under Admin (same level as System Settings) so it opens without opening System Settings first.
- Remove PDF routes from admin_settings_open so only PDF Templates expands on invoice/quote PDF pages.
- Set pdfDropdown parent to adminDropdown in nested dropdown click handler.
PDF layout (invoice & quote):
- Preserve table groups (Items, Expenses) on save by skipping Groups in cleanup and ensuring table names are set and restored from design_json.
- Add test for saving and reloading layout with tables.
Docs:
- Update PDF layout access path to Admin → PDF Templates → Invoice PDF (and Quote PDF) in PDF_LAYOUT_CUSTOMIZATION.md, PDF_EDITOR_ENHANCED_FEATURES.md, PDF_EDITOR_QUICK_START.md, INVOICE_EXTRA_GOODS_PDF_EXPORT.md.