Remove default form-input shadow and top margin inside tinted invoice and
quote line rows. The row already has a border and hover shadow; inputs
were doubling that edge so the top of each row looked overly heavy.
- Allow schedule edits (project, task, start/end, break) for users with
edit_own_time_entries on their own entries in API update_entry and
timer edit; scope project lists for subcontractors; admin-only source
dropdown on edit timer form.
- App shell: min-width/overflow fixes, header layout, compact bottom nav
on very narrow viewports (#573), dashboard timer block responsive layout.
- Invoice and quote edit: min-w-0 on grids/cells; scoped stronger neutral
borders for .form-input on #editInvoiceForm and #quote-form (#574).
Add Settings.mail_test_recipient (migration 142) and wire Admin → Email Configuration to save, load, and prefill the SMTP test recipient field.
Extract invoice email PDF/HTML building into _build_invoice_email_payload for reuse. New send_invoice_template_test_email sends a real message with subject "Invoice template test: …", reusing production rendering and PDF attachment, without updating invoice status or creating InvoiceEmail records.
Add POST /admin/email-templates/<id>/send-test (optional recipient override, optional invoice_id) and Send test email UI on template view and edit pages. Extend smoke and unit tests.
Purchase orders with line items crashed the detail page because the template
referenced item.stock_item without a SQLAlchemy relationship, and used
datetime.now() in Jinja where datetime was not in context.
Add PurchaseOrderItem.stock_item, pass default_received_date from the view
route, and add a regression test for viewing a PO that includes line items.
Read telemetry/sentry status in the admin dashboard from build-injected analytics defaults with env fallback, so release images correctly show OTLP configuration. Include both install_id and telemetry_fingerprint in base and detailed telemetry payloads to reliably differentiate installations, and add tests for the new fingerprint fields.
Switch product and installation telemetry to OTLP/Grafana across runtime config, CI injection, docs, and tests to unify telemetry transport and simplify privacy-focused opt-in behavior.
Sync editor content before submit validation and replace hidden required-field blocking with explicit inline checks. Preserve posted form values and enforce non-empty HTML on backend validation to avoid data loss and confusing no-op behavior.
Prevent 500s during first/parallel purchase-order creation by using collision-safe PO numbering, explicit validation, and reliable commit/error handling in web and API flows. Add regressions for purchase-order edge cases and invoice stock-reduction idempotency to catch adjacent inventory failures before release.
Implement issue #575 by introducing token-based invoice number patterns in settings and unifying number generation across invoice creation paths. This removes hardcoded INV/date formatting and aligns export filenames and bootstrap schemas with stored invoice numbers.
Increase quantity column spans for Invoice Items and Extra Goods on the invoice edit page so values are easier to read, and keep static and JS-added rows aligned.
- queueForOffline now saves url, method, headers, body (replay-safe for localStorage);
legacy items with options only still replayed via fallback
- processOfflineQueue builds fetch options from stored method/body so replayed
requests send the same payload when back online
- Make queueForOffline async and await it in handleFetchResponse/handleFetchException
- Add tests asserting queue stores method/body and replay uses them
- Catch AttributeError/KeyError/TypeError and generic Exception in PEPPOL block;
log with exc_info and show generic warning to user so view still renders
- Avoid silent pass that hid configuration or data errors
- Add test for exception path (mock get_custom_field to raise)
- Reports accept ?days=1-365 (default 30) for configurable date range
- ?format=csv returns CSV download (summary, hours by project, time by date)
with same access control as reports page
- Subtitle shows 'Last N days' when date range is applied
- Add tests for days param and CSV export
- New scopes read:inventory and write:inventory; existing read/write:projects
still grant same inventory access for backward compatibility
- require_api_token() accepts tuple of scopes (any one required); inventory
endpoints accept (read:inventory | read:projects) and (write:inventory | write:projects)
- ApiTokenService: add new scopes to allowed list; document in API_TOKEN_SCOPES.md
- Add tests for inventory report endpoints with scope checks
- /api/activity: return 400 with clear message when start_date/end_date
are not valid ISO 8601; avoid silent pass on parse errors
- Web route /activity: catch ValueError, log and skip filter instead of 500
- Add tests for invalid date formats on API and web routes
- When webhook_secret is set in Jira integration, verify incoming webhooks
via X-Hub-Signature-256, X-Atlassian-Webhook-Signature, or X-Hub-Signature
- Reject requests with missing or invalid signature; no secret = accept all (unchanged)
- Add webhook_secret password field to Connection Settings in Jira config
- Add tests for verification success, missing sig, and invalid sig
- Add keyboard_shortcuts_defaults utility for default bindings and overrides
- Update Settings keyboard shortcuts template for customization UI
- Add KEYBOARD_SHORTCUTS_DEVELOPER.md for implementation and extension
- Add ClientPortalDashboardPreference for per-client/widget dashboard layout and order
- Export new model in models __init__; minor updates to audit_log, link_template, user as needed
- settings: distinguish minimal install telemetry (always on) vs optional detailed analytics
- telemetry: update toggle label and data-collection copy for base vs opt-in layers
- List what is collected in each layer and what is never collected
- 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).