Commit Graph

175 Commits

Author SHA1 Message Date
Dries Peeters ea913c6c4b feat(ai,security): add web AI helper, secret encryption, and 2FA
Introduce a web-first AI helper with admin-configurable providers (Ollama or hosted OpenAI-compatible), server-side context building, and confirmed write actions. Expose the feature via session /api/ai/* endpoints and scoped /api/v1/ai/* endpoints.

Harden security by requiring a strong SECRET_KEY for Docker Compose, adding optional settings encryption-at-rest (Fernet), and introducing TOTP-based 2FA plus password reset flows. Update admin UI, API docs, and install documentation.
2026-04-26 07:55:47 +02:00
Dries Peeters 568933c3b9 fix(auth): scope client portal users to their assigned client
Client-portal-enabled users (main app login, typically viewer) were not
included in get_allowed_client_ids(), so ProjectService and other callers
saw scope_client_ids=None and listed every project.

- Return [client_id] for is_client_portal_user in User.get_allowed_client_ids
- Derive get_allowed_project_ids from allowed client IDs for all non-admins
- Apply client/project scope and access checks from allowed IDs, not only
  subcontractor is_scope_restricted (fixes user_can_access_* for portal)

Fixes DRYTRIX/TimeTracker#592.

Tests: extend test_scope_filter with client_portal_scoped_user and API
isolation for GET /api/v1/projects.
2026-04-24 16:15:24 +02:00
Dries Peeters ef9b66f5e7 fix(api): align client search, OpenAPI version, and Client construction
Global search referenced Client.company, which is not a column on Client, so client matches failed at runtime. Legacy and v1 search, plus search_clients(), now filter on name, email, description, and contact_person; result descriptions use the same fields. Legacy /api/search returns count: 0 for queries shorter than two characters so responses stay consistent.

OpenAPI info.version is taken from get_version_from_setup(), with a config fallback when the resolved version is unknown. get_version_from_setup() also honors TIMETRACKER_VERSION and APP_VERSION for CI and container builds.

Client.__init__ accepts custom_fields. ClientService no longer passes status= into Client(), which the initializer does not support.

Tests add HTTP route contract checks and OpenAPI version alignment, fix subcontractor search fixtures (Client/Task construction and v1 client fixture naming), and update related API integration tests.
2026-04-15 12:57:01 +02:00
Dries Peeters 91127bf188 feat: smart in-app notifications, value dashboard stats, and search scope helpers
Smart notifications (opt-in under user settings): NotificationService builds candidates from the user's local day and active timers; GET /api/notifications and POST /api/notifications/dismiss; migration 150 adds user columns and user_smart_notification_dismissals. /api/summary/today uses the same local-day totals. Client polls from smart-notifications.js; toastManager.show gains onDismiss for server dismiss sync. Config and env.example document SMART_NOTIFY_* variables.

Value dashboard: StatsService with Redis-backed caching, GET /api/stats/value-dashboard, dashboard template and dashboard-enhancements polling alongside existing widgets.

API v1 token search now uses apply_project_scope and apply_client_scope on queries; scope_filter adds apply_project_scope; tests extended for the new helper.
2026-04-15 12:15:23 +02:00
Dries Peeters b0dde80ba9 feat(web): high-visibility support modal, prompts, and supporter UX
Add a support modal with usage stats, tier and license links, share control, and offline-safe outbound CTAs. Surface support from the header, sidebar, user menu, dashboard card, and settings "Support & Community" section without hiding entry points when a supporter license is active.

Introduce UsageStatsService and a persisted users.support_stats_reports_generated counter incremented on key report exports and custom report views. Add SupportPromptService for session-scoped soft toasts (after export, dashboard milestones, long session via POST /donate/request-soft-prompt).

Wire consent-aware track_event names support.* and mirror funnel rows in DonationInteraction; fix has_recent_donation_click to treat link_clicked as a recent click. Document events and SUPPORT_* / migration notes in docs.

Tests: tests/test_support_services.py for prompt and usage stats behavior.
2026-04-15 10:55:37 +02:00
Dries Peeters 96955aee62 feat(admin): GitHub-based version update notification for admins
Add VersionService to fetch and cache the latest GitHub release, compare it to the installed semver (APP_VERSION when valid, else setup.py), and expose admin-only GET /api/version/check and POST /api/version/dismiss on the legacy /api blueprint (session or Bearer token).

Persist per-user dismissal in users.dismissed_release_version (Alembic 148) and show a non-blocking update card in base.html for administrators. Add packaging for semver parsing and tests for comparison, service, and routes.

Document configuration in docs/admin/deployment/VERSION_MANAGEMENT.md and endpoints in docs/api/REST_API.md and docs/API.md.
2026-04-15 09:39:32 +02:00
Dries Peeters 974f5cdd50 feat(quotes): invoice-style line items, costs, and extra goods (#585)
Add quote_items.line_kind (item | expense | good) plus display_name,
category, line_date, and sku so one table still drives totals, PDFs,
and acceptance stock logic.

- Migration 147: new columns with backfill line_kind=item
- Quote create/edit: three sections; stock and warehouse only when a
  line item is sourced from inventory; shared JS partial
- POST parsing, positions, and duplicate-quote copying for all fields
- API v1 quote items accept the new fields with defaults for old clients
- View, client portal, and PDF/fallback rendering use display_name and
  line metadata where relevant
- Integration tests: stock POST shape; expense and good line creation

Docs: extend INVENTORY_MANAGEMENT_PLAN QuoteItem and migration notes.
2026-04-12 14:00:12 +02:00
Dries Peeters 70510a6622 fix: quote create 500, line order, and Factur-X PDF parity
Quotes (#583):
- Add requires_approval, approval_level, and can_be_sent; wire create form
- Migrations 145 (approval columns) and 146 (quote_items.position)
- Order Quote.items by position; set positions on create/edit/duplicate/API
- Fix view template approval branch (not_required); add web regression test

Invoices / PEPPOL:
- Use the same Factur-X embed and PDF/A-3 normalization for export and
  email attachments; Associated File Data + text/xml metadata
- CII/UBL validators, pdfa3, zugferd, and invoice_pdf_postprocess helper
- Bundle compact sRGB ICC (app/resources/icc/); INVOICE_SRGB_ICC_PATH override
- Package data in setup.py; extend PEPPOL_EINVOICING.md and tests
2026-04-12 13:34:58 +02:00
Dries Peeters 9449a46a42 feat(integrations): Linear connector and shared HTTP/sync helpers
- Add Linear import (GraphQL, personal API key, optional team key filter).
- Centralize integration HTTP via integration_session and session_request.
- Add integration_sync_context for project/task refs and custom_fields metadata.
- Refactor Asana, GitHub, GitLab, Jira, Trello, ActivityWatch, and QuickBooks to use helpers.
- Extend integration UI, settings, and scheduled sync behavior as needed.
2026-04-05 08:39:18 +02:00
Dries Peeters 98e6eb87b9 feat(db): add task custom_fields and api_idempotency_keys
- Add tasks.custom_fields (JSONB) for integration metadata (migration 143).
- Add api_idempotency_keys table keyed by token, scope, and key hash (migration 144).
- Register ApiIdempotencyKey model for safe replay of POST /api/v1/time-entries.
2026-04-05 08:39:11 +02:00
Dries Peeters 07387ebf2e fix(tests): stabilize smoke suite for SA2 flush, auth, and admin routes
Detect SQLAlchemy 2.0 flush execution via _warn_on_events in
Settings._session_in_flush so get_settings() does not add/commit
during flush_context.execute(), avoiding ResourceClosedError in
invoice currency smoke tests.

Payment smoke tests now set a password on factory users and submit
it on login, matching local AUTH_METHOD requirements (fixes 302 to
login on /payments).

Allow HTTP 403 in test_non_admin_cannot_access_roles when the app
denies non-admins directly instead of redirecting with 200.
2026-03-28 20:07:19 +01:00
evilguy4000 1b5d019560 feat(admin): persist test recipient and send invoice email template tests
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.
2026-03-27 06:39:16 +01:00
evilguy4000 5fd0625e3a fix(inventory): stop 500 on purchase order detail view (#576)
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.
2026-03-27 06:38:47 +01:00
Dries Peeters 4e2183caed fix(inventory): harden purchase-order creation and stock idempotency
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.
2026-03-26 14:52:06 +01:00
Dries Peeters 60d4d55027 feat(invoices): add fully configurable invoice number patterns
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.
2026-03-26 14:51:55 +01:00
Dries Peeters 404647eea3 feat(models): add ClientPortalDashboardPreference and update user/audit/link models
- 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
2026-03-16 15:15:21 +01:00
Dries Peeters b4486a627f fix: CI tests, code quality, and duplicate DB indexes
- 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
2026-03-15 10:51:52 +01:00
Dries Peeters 8bb42ddd02 feat(app): recurring invoices, gantt/reporting services, license UI
- 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
2026-03-15 09:37:00 +01:00
Dries Peeters 8752d3d6aa feat(break-time): add break_seconds and pause support to TimeEntry and schemas (Issue #561)
- TimeEntry: break_seconds, paused_at; pause_timer(), resume_timer(); current_duration_seconds and calculate_duration() account for break
- Settings: break_after_hours_1/2, break_minutes_1/2 for default break rules
- Repository: create_manual_entry accepts break_seconds
- Schemas: break_seconds on TimeEntrySchema, Create, Update
2026-03-11 17:58:37 +01:00
Dries Peeters f150b73b94 feat: product value improvements (dashboard, reports, timer, reminders)
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
2026-03-11 08:59:13 +01:00
Dries Peeters 55f2ef29fd feat(overtime): allow overtime calculation by weekly hours (Issue #551) 2026-03-09 20:33:48 +01:00
Dries Peeters 531f16b597 feat(workforce): add timesheet governance, time-off, and workforce dashboard
- 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
2026-03-06 15:44:35 +01:00
Dries Peeters 777d6ad3bf fix(invoicing): harden PEPPOL transport and PDF/A-3 export compliance
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.
2026-03-02 20:55:02 +01:00
Dries Peeters 3c5a937234 Add task tags and categorization (fixes #539)
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)
2026-02-28 17:28:54 +01:00
Dries Peeters b0809e2f90 feat(invoicing): add ZugFerd/Factur-X support and document Peppol & ZugFerd
- Add optional embedding of EN 16931 UBL XML in invoice PDFs (ZugFerd/Factur-X)
  when 'Embed EN 16931 XML in invoice PDFs' is enabled in Admin > Peppol e-Invoicing.
  Exported PDFs then contain ZUGFeRD-invoice.xml for hybrid human- and machine-readable
  invoices; same UBL as Peppol, usable via Peppol or email.
- New setting invoices_zugferd_pdf (migration 128), pikepdf dependency, and
  app.utils.zugferd helper (best-effort supplier/customer from Settings and client).
- Wire embed in export_invoice_pdf (and fallback path); admin checkbox and persistence.
- Docs: PEPPOL_EINVOICING.md retitled to 'Peppol and ZugFerd', new section for
  ZugFerd embedding; README and CHANGELOG updated; migration 128 noted.
- Tests: test_zugferd.py (embed adds attachment with expected XML; invalid PDF
  returns original bytes and error).
2026-02-16 07:36:49 +01:00
Dries Peeters ae9ee9dec1 feat: add subcontractor role with assigned clients (scope-restricted access)
- Add user_clients table and UserClient model for many-to-many user-client assignment
- Add 'subcontractor' system role; users with this role see only assigned clients and their projects
- User helpers: is_scope_restricted, get_allowed_client_ids(), get_allowed_project_ids()
- Admin user form: assign clients when role is Subcontractor (multi-select, JS toggle)
- Scope filtering: clients, projects, time entries, reports, invoices, timer, API v1
- Direct access to out-of-scope client/project returns 403 (web and API)
- Migration 127_add_user_clients_table; scope_filter utility and ProjectService scope_client_ids
- Docs: SUBCONTRACTOR_ROLE.md, ADVANCED_PERMISSIONS.md, RBAC, CLIENT_PORTAL, README, CHANGELOG

Addresses GitHub Discussion #476 (user with limited clients/projects).
2026-02-16 07:12:57 +01:00
Dries Peeters 115db52b2b feat(time-approvals): complete time entry approval workflow and fix bugs
- Fix approve crash: make _mark_entry_approved a no-op (approval state from
  TimeEntryApproval only; TimeEntry has no metadata column).
- Fix PostgreSQL enum: use values_callable on ApprovalStatus so DB receives
  'pending' not 'PENDING' (matches approvalstatus enum).
- Fix approval templates: use requester/approver, request_comment,
  time_entry.notes; reject form field name 'reason'; add can_approve to
  view and pass from route.
- Filter get_pending_approvals by current approver (policy/project fallback).
- Add Time Approvals nav link under Work in base.html (module-enabled).
- Add Request approval in time entries list (icon) and view_timer (sidebar);
  redirect to list_approvals after request.
- Fix empty_state calls in approvals/list.html (use icon_class positional
  args to match components/ui.html macro).
2026-02-13 21:06:18 +01:00
Dries Peeters 54533ec95e feat(reports): add Time Entries report with Excel/CSV export (Discussion #463)
- Add /reports/time-entries page listing all time entries (billed and unbilled)
- Columns: Date, Start, Stop, Duration, Project, Task, Notes, Billed, Client
- Filters: date range, user, project, client, task, billed (all/yes/no)
- Export to Excel and CSV with same filters; add Billed column to excel export
- Resolve client from entry.client or project.client in export
- Add Time Entries Report card to Reports index
2026-02-13 20:56:07 +01:00
Dries Peeters cdb579ae38 feat(dashboard): show live elapsed time in Timer and Recent Entries
Add client-side 1s ticker so elapsed time updates in real time on the dashboard when a timer is running. Also update Today's/Week's/Month's hours live and show the running time entry on the calendar in a distinct color.

- Timer section: display live elapsed (HH:MM:SS) using active_timer.start_time
- Recent Entries: live-updating duration for the row that is the active timer
- Stats cards: Today/Week/Month hours include running timer and update every second
- Calendar: include active timer in time_entries with is_running and cyan color

Ref: Issue #516
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 20:43:02 +01:00
Dries Peeters 17b67cc578 feat(calendar): default view setting and remember last view
- Resolve calendar view from URL param, then user default, then session
- Add user preference calendar_default_view (day/week/month or unset)
- Persist last-used view in session when opening with ?view=
- Settings: Calendar default view dropdown (Remember last view / Day / Week / Month)
- PATCH /api/preferences supports calendar_default_view
- Migration 123: add calendar_default_view to users
- Tests for calendar view resolution and settings form/API

Ref #518

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 20:22:08 +01:00
Dries Peeters a7f2fec930 feat(support): system-wide support visibility (admin-only)
- Add Settings.donate_ui_hidden and migration 122; admin verify in Admin → Settings
- Support visibility section in admin settings: System ID, code input, Verify and hide for everyone
- New route POST /admin/settings/verify-donate-hide-code (Ed25519 or HMAC) sets donate_ui_hidden
- Templates (base, dashboard, about, help) hide donate UI when settings.donate_ui_hidden
- User settings: remove per-user code/System ID block; add note that admins configure in Admin → Settings
- Config: DONATE_HIDE_PUBLIC_KEY_PEM / _FILE and HMAC fallback; refuse private key PEM
- Dockerfile: set DONATE_HIDE_PUBLIC_KEY_FILE=/app/donate_hide_public.pem
- .gitignore: docs/internal/, scripts/generate_donate_hide_code.py, donate_hide_private.pem
- Update SUPPORT_VISIBILITY.md for system-wide flow and admin-only setup

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 14:00:30 +01:00
Dries Peeters 91685bb9c6 feat: allow users to hide donate/support UI via verified code
- Add Support visibility in Settings: users see a stable System ID and can
  enter a code to permanently hide donate buttons, support banner, and
  donate widgets.
- Verification supports two modes:
  - Ed25519: server stores only public key; codes are signatures generated
    offline with the private key (no secret on server).
  - HMAC: server stores a secret; code = HMAC(secret, system_id).
- Add User.ui_show_donate and Settings.system_instance_id (migration 121).
- Add donate_hide_code utility (HMAC + Ed25519 verify) and config for
  DONATE_HIDE_PUBLIC_KEY(_FILE) and DONATE_HIDE_UNLOCK_SECRET(_FILE).
- Wrap all donate UI in base, dashboard, about, help with conditional.
- Add admin doc SUPPORT_VISIBILITY.md; ignore docs/internal/ and
  code-generation script in .gitignore.
- Add translations for new strings.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 11:11:03 +01:00
Dries Peeters bd00e01876 Add configurable date/time format and misc fixes
- Settings: add date_format and time_format (model, migration 119, admin UI)

- Use user date/time prefs in templates, calendar, and PDF export

- Expense: eager-load client instead of category in repo, service, and API

- Mobile: clarify server URL and certificate docs, bump to 4.18.0, improve connection diagnostics

- Ignore mobile/android/.gradle/

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-07 08:22:34 +01:00
Dries Peeters 8b6e61873b Use system date/time format by default with optional user override
Display formats for dates and times now follow the system settings (Admin
settings) by default. Users can override in their profile (User settings) or
choose "Use system default" so their view matches the rest of the system.

Backend:
- User.date_format and User.time_format are nullable; null means use system.
- Migration 120 makes these columns nullable (existing rows unchanged).
- get_resolved_date_format_key() and get_resolved_time_format_key() in
  timezone utils return the effective key (user or system) for templates and API.
- Context processor injects resolved_date_format_key and resolved_time_format_key
  so base.html and JS (window.userPrefs) always see the resolved format.
- User settings form: "Use system default" option and save logic for null.
- User.to_dict() includes resolved date_format, time_format, and timezone for
  API clients (e.g. mobile).

Web:
- base.html uses resolved keys for window.userPrefs (no hardcoded fallback).
- Replaced display-only strftime() in templates with |user_date, |user_datetime,
  |user_time, and |format_date so all visible dates/times respect settings.
  Left <input type="date"> values and URL/API params as YYYY-MM-DD where required.

Mobile:
- ApiClient.getCurrentUser() and user prefs provider load resolved prefs from
  /api/v1/users/me.
- date_format_utils maps API keys to intl patterns; formatDate, formatTime,
  formatDateTime, formatDateRange used for display.
- Time entries screen (filter dialog), time entry form, time entry card, and
  home dashboard use user prefs for formatting; API requests still send ISO dates.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-07 08:20:08 +01:00
Dries Peeters 1ebfbf39de refactor: comprehensive code quality, security, and performance improvements
Performance:
- Fix N+1 queries in reports.py with joinedload for TimeEntry.project,
  TimeEntry.user, TimeEntry.task, and Project.client across 6 query locations
- Replace per-task time_entries loops with batch UPDATE queries in tasks.py
- Use efficient subquery for favorite project IDs in projects.py

Architecture:
- Add get_by_id() and get_by_name() methods to ProjectService and ClientService
- Route project/client lookups through service layer in timer.py, projects.py,
  and clients.py instead of direct Model.query calls

Security:
- Add sanitize_input() with length limits to form inputs in clients.py,
  projects.py, timer.py, issues.py, and auth.py
- Add email format validation for client creation
- Warn at startup when SECRET_KEY uses the default value or is too short
  in ProductionConfig
- Replace 7 bare except: pass clauses with specific exception types
  (OSError, IOError, TypeError, ValueError) in admin.py, settings.py,
  and invoice.py

Authorization:
- Migrate all @admin_required decorators to @admin_or_permission_required()
  with granular permissions (manage_roles, manage_kanban, manage_webhooks,
  manage_api_tokens, manage_integrations, access_admin) across permissions.py,
  kanban.py, webhooks.py, and admin.py (28 routes total)

Frontend:
- Remove 40+ console.log debug statements across 18 JS files
- Replace 42 inline onclick/onchange handlers in base.html with delegated
  event listeners using data-dropdown and data-no-propagation attributes
- Migrate 6 inline handlers in time_entries_overview.html to addEventListener
- Extract shared typing detection into typing-utils.js, eliminating 5
  duplicate isTyping() implementations across keyboard shortcut files
- Add missing aria-label attributes to icon-only buttons

Dependencies:
- Migrate from pytz to stdlib zoneinfo (Python 3.9+) across all 6 files
  that used pytz; replace pytz with tzdata in requirements.txt
- Separate dev/test dependencies into requirements-dev.txt
- Configure RotatingFileHandler (10MB, 5 backups) for app and JSON logs

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-06 07:56:23 +01:00
Dries Peeters 3c01fb34c8 feat(modules): lock client selection and make Clients admin-only
- Add settings.locked_client_id and admin UI to select locked client
- Allow disabling Clients for non-admins while keeping admin access
- Gate Clients UI routes and API endpoints when module is disabled
- Auto-select and enforce the locked client across filters and form submissions

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-02 19:20:48 +01:00
Dries Peeters 807e6370ee feat(roles): add per-role module visibility (hide modules by role)
Admins can hide whole app modules (Analytics, Finance & Expenses, CRM, etc.)
per role so users in that role neither see them in the nav nor access them
by URL/API.

- Add Role.hidden_module_ids (JSON denylist) and migration
- Extend ModuleRegistry.is_enabled() with role-based hide check; module is
  hidden only if ALL of the user's roles hide it (super admins bypass)
- Add Module visibility section to role create/edit form with checkboxes
  by category; persist via hidden_modules form field
- Add tests for registry hide/allow semantics and route decorator 403

Closes #484

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-02 19:18:28 +01:00
Dries Peeters 0e7656134e Backend: add API auth login and fix time entry duration calculation
- Add POST /api/v1/auth/login (rate-limited) returning API token for mobile
- Fix time entry duration when DB returns timezone-aware datetimes (_naive_dt)
- Add _parse_date_range helper; expose timezone in API info
- Extend time entries API tests

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-01 16:50:42 +01:00
Dries Peeters fe53c926ed feat(calendar): user-configurable colors for calendar item types
- Add User preferences: calendar_color_events, calendar_color_tasks,
  calendar_color_time_entries (nullable hex)
- Calendar API: attach color to each event/task/time_entry and return
  typeColors; view_calendar passes type_colors to template
- PATCH /api/preferences: accept and validate calendar color fields
- Migration 117: add the three user columns

Events, tasks, and time entries can now be distinguished by user-chosen
colors (defaults: blue, amber, green). Frontend color pickers and
legend are on the calendar page (already in previous commit).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-01-31 07:48:09 +01:00
Dries Peeters fe0a31c583 feat(invoices): improve UX for time-based invoice items (Issue #469)
- Show Project and Task instead of Stock Item and Warehouse for items from time entries
- Make quantity read-only for time-based items (billed hours) to prevent accidental edits
- Preserve time_entry_ids when saving invoice edits (fixes lost linkage)
- Enforce quantity from time entries on save for defense in depth
- Add task_name_from_time_entries property to InvoiceItem
- Add translations for new UI strings
2026-01-30 16:49:54 +01:00
Dries Peeters d7f3683a11 fix(calendar): align time entries with timestamps in weekly view
Fixes #458 - Time entries now properly align with their start and end
timestamps in both FullCalendar and custom calendar weekly views.

FullCalendar (timer calendar):
- Add explicit timeZone: 'local' to ensure consistent date interpretation
- Normalize time entry ISO format to YYYY-MM-DDTHH:mm:ss (no microseconds)

Custom calendar week view:
- Fix data loading: merge events, tasks, and time_entries from separate
  API arrays into unified structure with extendedProps.item_type
- Implement proper spanning: entries now span across multiple hour cells
  based on their actual start/end times
- Add precise positioning: entries align with minute-level accuracy using
  percentage-based top/height calculations
- Apply same spanning logic to calendar events with duration
- Add hour labels column (00:00-23:00) for better visual reference

Time entries and events now correctly display their full duration and
align with both start and end timestamps in the weekly grid view.
2026-01-28 07:33:58 +01:00
Dries Peeters 12074fc29b fix: resolve integration test failures (install config dir, settings flush)
- Make InstallationConfig config dir overridable via INSTALLATION_CONFIG_DIR
  so tests and CI use a writable path instead of /data (fixes PermissionError
  on redirect to /admin/settings after logo upload).
- Set INSTALLATION_CONFIG_DIR in conftest before app import and in
  ci-comprehensive.yml for integration-tests and full-test-suite jobs.
- In Settings.get_settings(), add _session_in_flush() and a re-entrancy
  guard to skip add+commit when called during another commit's flush,
  fixing ResourceClosedError in currency_display test setup.
- Update test_installation_config fixture to set INSTALLATION_CONFIG_DIR
  so it continues to use its temp dir with the new env-based behavior.
2026-01-25 10:09:29 +01:00
Dries Peeters afd793f1cb fix(models): persist Client custom fields and ClientNote cascade delete in tests
- Client: call flag_modified() after mutating custom_fields in
  set_custom_field() and remove_custom_field() so SQLAlchemy persists
  JSON changes (in-place dict updates are not tracked by default).
  Fixes test_count_clients_with_value_ignores_empty and
  test_count_clients_with_value_ignores_other_fields.

- ClientNote: add ondelete=CASCADE to client_id FK so schema from
  db.create_all() matches migration 024 and notes are deleted when
  client is deleted. Fixes test_client_note_cascade_delete.
2026-01-25 10:03:25 +01:00
Dries Peeters 8f3041f260 Fix Client.notes backref: add delete-orphan cascade and fix dynamic loader test
- Add cascade='all, delete-orphan' to the notes backref so client notes
  are removed when a client is deleted.
- In test_client_has_notes_relationship use client.notes.count() instead
  of len(client.notes) since the backref uses lazy='dynamic'.
2026-01-25 09:44:46 +01:00
Dries Peeters 01dea80245 fix(weekly-goals): use 5 workdays for days_remaining when exclude_weekends is on
- days_remaining now counts only Mon-Fri when exclude_weekends=True
- Fixes avg hours/day for 5-day goals (e.g. 30h shows 6h/day not 5h/day)
- Add tests for exclude_weekends: creation, days_remaining, avg hours/day,
  weekend exclusion, and actual_hours excluding weekend entries

Fixes GitHub Discussion #441
2026-01-25 08:36:02 +01:00
Dries Peeters 456d2074b7 feat: Add persistent chat widget with user selection and status indicators
- Add user status tracking (online/offline/away) based on last_login
  - Implement is_online() and get_status() methods in User model
  - Include status in user.to_dict() for API responses

- Create persistent chat widget that remains open across page navigation
  - Floating chat button in bottom-right corner (always visible)
  - Expandable chat panel with channels, direct messages, and message input
  - State persistence using localStorage (remembers open/closed and active channel)
  - Auto-refreshes channels and messages periodically
  - Integrates with user selector for starting new chats

- Add chat user selection popup component
  - Searchable user list with avatars and status indicators
  - Color-coded status badges (green=online, yellow=away, gray=offline)
  - Creates or finds existing direct message channels

- Add API endpoints for chat functionality
  - GET /api/chat/users: List all active users with status
  - POST /api/chat/direct-message/<user_id>: Create or find direct message channel

- Add chat button to header navigation
  - Opens persistent chat widget or user selector
  - Only visible when team_chat module is enabled

- Add status indicators throughout chat interface
  - Show user status in direct messages list
  - Display status badges in channel member lists
  - Status visible in user selection popup

- Fix z-index and positioning issues
  - Chat widget positioned above other floating elements (z-[60])
  - Adjusted position to avoid overlap with quick actions button

- Fix CSRF token handling
  - Use FormData for message submission to properly handle CSRF
  - Include CSRF token in user selector requests
  - Fix file_size variable in upload_attachment endpoint
2026-01-23 22:39:06 +01:00
Dries Peeters fca242033e feat: Add 5-day work week option for weekly time goals
Add exclude_weekends feature to allow users to set weekly goals for
Monday-Friday (5-day work week) instead of Monday-Sunday (7-day week).

This addresses client feedback requesting the ability to exclude weekends
when setting weekly goals, making it more realistic for standard work
schedules.

Changes:
- Add exclude_weekends boolean column to weekly_time_goals table
- Update WeeklyTimeGoal model to calculate week_end_date as Friday when
  exclude_weekends is True
- Filter weekend hours from actual_hours calculation when enabled
- Update days_remaining to count only weekdays for 5-day goals
- Add checkbox option in create/edit forms with helpful tooltips
- Update view template to show 5-day work week badge and exclude weekends
  from daily breakdown
- Update tips section to mention the 5-day option

The feature is backward compatible - existing goals default to 7-day weeks.
All calculations (hours, days remaining, averages) correctly account for
the selected work week type.
2026-01-22 20:17:04 +01:00
Dries Peeters 606e7a9dc1 feat: Add comprehensive module management system with route protection
- Add @module_enabled decorator to all module routes (30+ route files)
  - Protects routes for inventory, mileage, per_diem, project_templates,
    gantt, kanban, weekly_goals, issues, time_entry_templates, reports,
    custom_reports, scheduled_reports, invoice_approvals, recurring_invoices,
    payments, payment_gateways, budget_alerts, analytics, integrations,
    import_export, saved_filters, workflows, time_approvals, activity_feed,
    recurring_tasks, team_chat, client_portal, kiosk, and more
  - Ensures disabled modules are not accessible to users

- Fix indentation errors in route files
  - Remove duplicate module_enabled imports incorrectly placed inside
    function bodies in 8 files (import_export, client_portal, custom_reports,
    integrations, kiosk, payment_gateways, scheduled_reports, team_chat)
  - Move all imports to top of files for proper scope

- Fix Jinja2 template error in admin/settings.html
  - Replace invalid loop.parent.loop.last with namespace-based approach
  - Use Jinja2 namespace feature to track first item for comma placement
  - Fixes UndefinedError when rendering module dependencies and names

- Fix JavaScript syntax error in admin/settings.html
  - Remove orphaned closing braces causing parse errors
  - Restores toggleCategory function availability

- Update task templates to include module visibility checks
  - Add module_enabled checks to task creation and editing templates

This commit completes the module management system, allowing administrators
to globally enable/disable modules, with all routes properly protected and
UI elements conditionally rendered based on module status.
2026-01-22 19:47:22 +01:00
Dries Peeters 7dcd58608a feat: Enhance TimeEntry audit logging with comprehensive tracking
Add comprehensive audit logging for TimeEntry operations including:
- Client/project context and creation timestamps
- Full entity state before/after changes
- User-provided reasons for deletions and modifications
- Enhanced UI for entering reasons in delete/edit dialogs

Database Changes:
- Add migration 114: reason, entity_metadata, full_old_state, full_new_state columns
- Use JSON column type for entity_metadata for better type handling

Model Updates:
- Extend AuditLog model with new fields and helper methods
- Update log_change() to accept reason, metadata, and full states
- Add get_entity_metadata(), get_full_old_state(), get_full_new_state() methods
- Use JSON column for entity_metadata (returns dict/list directly)

Service Layer:
- Update TimeTrackingService to capture full TimeEntry state and metadata
- Accept reason parameter in delete_entry() and update_entry()
- Create comprehensive audit logs with all context

API Routes:
- Update api.py, api_v1.py, and timer.py routes to accept reason parameter
- Refactor routes to use service layer for consistent audit logging
- Add reason support to bulk delete operations

UI Enhancements:
- Add reason textarea to bulk delete confirmation dialog
- Add reason textarea to time entry edit forms (admin and regular users)
- Update JavaScript to handle reason submission

Audit Log Display:
- Show client/project information and creation timestamp in list view
- Display full old/new states, reason, and metadata in detail view
- Format JSON states for better readability

Bug Fixes:
- Fix duration_seconds reference in timer stop route
- Improve error handling in timer operations with proper exception handling
- Add dashboard cache invalidation after manual entry creation
2026-01-22 13:36:04 +01:00
Dries Peeters a15eb0c97a Add setting to make all invoices PEPPOL compliant
- Add invoices_peppol_compliant in Settings (Admin > Peppol e-Invoicing).
  When on: PDFs include seller/buyer PEPPOL identifiers; invoice view
  shows warnings for missing company/client PEPPOL data; UBL gets
  mandatory BIS Billing 3.0 elements.

- UBL: add InvoiceTypeCode 380 and BuyerReference (buyer_reference,
  project name, or invoice number).

- Optional buyer_reference on Invoice and create/edit forms (PEPPOL BT-10).

- Download UBL route and button when client is PEPPOL-ready and
  invoices_peppol_compliant or peppol_enabled.

- Migrations: 112 (settings.invoices_peppol_compliant),
  113 (invoices.buyer_reference).

- Update PEPPOL_EINVOICING.md; extend test_peppol_service UBL checks.
2026-01-21 15:13:26 +01:00