Commit Graph

155 Commits

Author SHA1 Message Date
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
Dries Peeters f0b7e7a6df feat(reports): unpaid-by-salesman scheduled reports and report builder fixes
Scheduled reports (per-salesman, unpaid-only)
- Schedule form: add email distribution (single / SalesmanEmailMapping /
  template) and recipient template when "Split by custom field" is on.
- Support {value} and {value_lower} in recipient_email_template
  (e.g. {value_lower}@test.de).
- Add use_last_month_dates on ReportEmailSchedule: optional "Use previous
  calendar month" for monthly runs; migration 111.
- Override report date range in ScheduledReportService when
  cadence=monthly and use_last_month_dates=true.
- Wire use_last_month_dates through schedule form, API, and
  create_schedule.

Report Builder
- Add "Unpaid time entries" quick-start (Time Entries + unpaid only +
  last 30 days) and applyUnpaidPreset().
- Clarify "Unpaid only" help: "Unpaid = billable, not yet on any invoice."
- Define canvas at top of script so edit-mode IIFE and addDataSourceToCanvas
  can use it; fix applyUnpaidPreset/onclick when script failed to parse.
- Click-to-add for Data Sources and Components (in addition to drag-and-drop).
- Set dataTransfer.effectAllowed = 'copy' in dragstart for drop compatibility.
- Fix save-form handler: remove orphan try { with no catch that caused
  SyntaxError and prevented applyUnpaidPreset (and rest of script) from loading.

Documentation
- Add docs/reports/UNPAID_BY_SALESMAN_AND_SCHEDULED_REPORTS.md (unpaid
  definition, unpaid-by-salesman setup, SalesmanEmailMapping, template use).
2026-01-21 15:11:12 +01:00
Dries Peeters 8c070d08d5 feat(admin): restore admin-defined module visibility
- Add settings.disabled_module_ids (JSON) to store admin-disabled module IDs
- Migration 110: add disabled_module_ids column to settings
- ModuleRegistry.is_enabled() respects settings.disabled_module_ids
- Admin > System Settings: new 'Module visibility' section with toggles
  for all non-core modules; disabled modules are hidden from all users.
- Core modules stay always on; default empty list keeps current behavior.
2026-01-21 14:20:36 +01:00
Dries Peeters 6881e554ce fix: restore audit logging by using before_flush for updates/deletes
Audit logs were not recording any changes because after_flush runs
after SQL is emitted; by then session.new, session.dirty, and
session.deleted can be cleared and attribute history for updates is
often consumed, so the handler saw nothing to log.

Changes:
- Add receive_before_flush: process session.dirty (updates) and
  session.deleted (deletes) while history is still valid; stash
  session.new (creates) in session.info for after_flush.
- Simplify receive_after_flush: only handle pending creates from
  session.info (instances now have ids), then session.flush() so
  audit rows are in the same transaction.
- Register receive_before_flush for before_flush on Session,
  sessionmaker class, and SignallingSession.
- Make receive_before_flush accept (session, flush_context, instances)
  to match SQLAlchemy's before_flush signature.
- Remove db.session.flush() from AuditLog.log_change to avoid
  nested flush; rely on main flush or explicit flush in after_flush.
- check_audit_logging.py: use entity_type='TimeEntry' to match
  get_entity_type (model __class__.__name__).
- test_audit_logging: assert at least one AuditLog for create/update/
  delete; use test_client for create; fix update to merge then mutate.
2026-01-21 13:59:13 +01:00
Dries Peeters 2615fefa91 feat(gantt): project bar colors and Pickr color picker
- Add Project.color (hex) and migration for projects.color
- Projects create/edit: Gantt color field with Pickr (swatch + hex input),
  Pickr theme CSS and gantt-color-picker.js for init and sync
- Gantt API: include color in JSON for projects and tasks (tasks use project color)
- Gantt view: set custom_class from color, inject CSS for .bar and .bar-progress,
  fix selectors for .gantt .bar-wrapper and :hover/.active overrides; add
  fallback styles for gantt-project and gantt-task
2026-01-20 21:12:51 +01:00
Dries Peeters dafefd5d67 fix(inventory): stock devaluation and lot logic (fixes #385)
InventoryReportService: use moved_at and reference_type/reference_id instead of non-existent movement_date and reference to avoid AttributeError in get_inventory_turnover, _get_stock_at_date, get_movement_history.

_ensure_legacy_lot: run only for outflows (movement_qty < 0) or record_devaluation (movement_qty None). For outflows use pre-movement total (updated_stock + abs(qty)) so FIFO consumption stays in sync with WarehouseStock. Skip for inbound to prevent double-count of new lots.

record_movement: stop swallowing exceptions from _apply_lot_changes; re-raise so callers can roll back and avoid inconsistent WarehouseStock vs StockLot state.

Movement form: dynamic quantity hint by type (return/waste/devaluation/default), required devaluation fields when shown, client-side sign checks for return (positive) and waste (negative).

Tests: test_first_inbound_with_no_lots_matches_warehouse_stock, test_first_outbound_with_no_lots_matches_warehouse_stock.
2026-01-20 19:59:28 +01:00
Dries Peeters 0b76df53e2 feat: Add configurable date format for PDF templates
- Add date_format column to invoice_pdf_templates and quote_pdf_templates tables
- Default date format set to DD.MM.YYYY (%d.%m.%Y)
- Update PDF generators to use template-specific date format
- Add date format configuration in admin PDF template editor
- Replace Babel date formatting with strftime for consistent formatting
- Update template filters to use DD.MM.YYYY format by default

This allows users to customize date formatting per PDF template while
maintaining backward compatibility with existing templates.
2026-01-14 21:15:18 +01:00
Dries Peeters ba9b789c51 feat: Add decorative images to PDF templates with full sync between invoice and quote editors
- Add decorative image element to PDF Layout Designer for both invoice and quote templates
- Implement template-level decorative image upload and management
- Add backend routes for template image upload and serving
- Update PDF generation (HTML preview and ReportLab) to handle template images with transparency preservation
- Sync all decorative image functionality between invoice and quote PDF layout editors

Fixes:
- Fix upload button not opening file picker in invoice template (use fresh DOM references)
- Fix element name matching to handle 'decorative-image element-overlap' format (use .includes() instead of strict equality)
- Fix image restoration after page reload with enhanced JSON searching and position matching
- Fix image persistence in Konva.js serialization/deserialization

Improvements:
- Enhanced image restoration logic with fallback mechanisms
- Improved error handling and console logging for debugging
- Better handling of transparent backgrounds in PDF export
- Consistent behavior between invoice and quote template editors
2026-01-13 13:33:55 +01:00
Dries Peeters 61bead4cb2 feat: add devaluation support to stock movements
- Add 'devaluation' to stock movement types
- Add validation to ensure trackable items for devaluation
- Improve error handling for devaluation operations
2026-01-12 20:09:00 +01:00
Dries Peeters bad397fbdb feat: Enhance invoice management, expenses, and PDF generation
- Improve invoice model with enhanced prefix handling and validation
- Enhance expense routes with better error handling and validation
- Refactor PDF generator with improved template support and formatting
- Update Google Calendar integration with improved error handling
- Enhance scheduled tasks with better logging and reliability
- Update admin routes with improved permission checks
- Improve email utility with better template handling
2026-01-11 08:37:48 +01:00
Dries Peeters 4a681f0f48 feat: Enhance invoice and quote management system
- Improve invoice model with additional fields and methods
- Enhance quote model with better validation and relationships
- Add invoice repository for data access layer abstraction
- Update invoice and quote routes with improved functionality
- Add quote service for business logic separation
- Improve quote view and edit templates with better UX
2026-01-09 11:43:51 +01:00
Dries Peeters 4eeaa2a842 feat: Migrate PDF templates to ReportLab JSON format
- Add ReportLab template renderer with JSON-based template system
- Implement template schema validation and helper functions
- Add database migration for template_json columns
- Update visual editor to generate ReportLab JSON alongside HTML/CSS
- Maintain backward compatibility with legacy templates
- Add comprehensive migration documentation

BREAKING CHANGE: Existing PDF templates need to be saved again through
the visual editor to generate the new template_json format. Templates
will continue to work using the legacy fallback generator until saved.
2026-01-09 11:43:42 +01:00
Dries Peeters 83a03f9a99 feat: enhance application features and routes
- Improve expenses route with additional functionality
- Enhance admin route with new features
- Update auth route with improved authentication handling
- Extend user model with new capabilities
- Update expenses view template
- Improve config manager utility
2026-01-07 20:04:50 +01:00
Dries Peeters 14d673a4a8 Allow auto-imported time entries without project or client
- Add migration to update check constraint allowing NULL project_id and client_id for source='auto' entries
- Update TimeEntry model validation to allow entries without project/client when source='auto'
- Update TimeEntryCreateSchema to allow entries without project/client when source='auto'
- Enables calendar integrations to import entries that don't have project/client mapping yet
2026-01-07 13:18:40 +01:00
Dries Peeters ad9bfbf1ed Fix client deletion errors and add invoice validation
This commit fixes multiple issues preventing client deletion and adds proper validation to prevent deletion when invoices exist.

Database Schema Fixes:

- Migration 103: Add missing quote_number column to quotes table

  - Handles migration from offer_number to quote_number

  - Generates quote numbers for existing quotes if needed

  - Creates required unique index

- Migration 104: Add all missing columns to quotes table

  - Adds subtotal, tax_amount, visible_to_client columns

  - Adds discount fields (discount_type, discount_amount, discount_reason, coupon_code)

  - Adds payment_terms column

  - Adds approval workflow columns (approval_status, approved_by, approved_at, rejected_by, rejection_reason)

  - Creates required indexes and foreign keys

- Migration 105: Fix client_notifications foreign key cascade

  - Updates client_notifications.client_id FK to ON DELETE CASCADE

  - Updates client_notification_preferences.client_id FK to ON DELETE CASCADE

  - Prevents NOT NULL constraint violations during client deletion

Model Updates:

- Add passive_deletes=True to ClientNotification.client relationship

- Add passive_deletes=True to ClientNotificationPreferences.client relationship

- Add passive_deletes=True to ClientAttachment.client relationship

- Update ClientNote.client relationship to use passive_deletes

Route Updates:

- Add invoice validation to delete_client() and bulk_delete_clients()

- Manually delete notifications before client deletion to prevent SQLAlchemy update issues

Fixes:

- Resolves IntegrityError when deleting clients with notifications

- Resolves missing quote_number column errors

- Resolves missing quotes table columns errors

- Prevents deletion of clients with invoices (data integrity)
2026-01-05 22:07:50 +01:00
Dries Peeters f160fac9c1 refactor: improve error handling with specific exceptions and logging
Replace bare except clauses with specific exception types and add
appropriate logging throughout the codebase. This improves:
- Debugging capabilities with proper error messages
- Code maintainability by catching specific exceptions
- Error tracking through structured logging

Changes include:
- Replace bare except: with specific exception types (JSONDecodeError,
  TypeError, ValueError, OSError, AttributeError, RuntimeError)
- Add logging for error conditions that were previously silently ignored
- Improve error messages with context information
2026-01-05 19:32:25 +01:00
Dries Peeters b5c65b0ce8 feat: Add comment attachment routes and API support
- Add upload_comment_attachment route with file validation
- Add download_attachment route for file downloads
- Add delete_attachment route with permission checks
- Enhance Comment.to_dict() to include attachments array
- Support file size limits (10 MB) and type validation
- Proper error handling and file cleanup on errors

Routes follow existing attachment patterns from projects/clients.
2026-01-04 06:23:42 +01:00
Dries Peeters f205904742 feat: Add comment attachments model and migration
- Create CommentAttachment model following ProjectAttachment pattern
- Add database migration for comment_attachments table
- Register CommentAttachment in models __init__.py
- Support file uploads (images, PDFs, documents, archives)
- Include file metadata (size, type, uploader, timestamp)
- Cascade delete attachments when comments are deleted

Enables file attachments to comments for better team collaboration.
2026-01-04 06:23:39 +01:00
Dries Peeters eb0cd0005e feat: enhance inventory management features
- Update stock movement model with improved functionality
- Enhance inventory routes and API endpoints
- Improve inventory templates for movements, reports, and stock items
- Add better history tracking and valuation reporting
2026-01-03 20:27:54 +01:00
Dries Peeters 753b98d272 feat: add Peppol e-invoicing and stock lot valuation
Add Peppol BIS Billing 3.0 (UBL) invoice sending via a configurable access point, including admin-configurable settings, per-invoice send history, and documentation/README updates.

Also introduce stock lots/allocations (valuation layers) with supporting inventory route/report/UI updates and hardened startup migration handling.
2026-01-03 07:00:30 +01:00
Dries Peeters 3218ab012a feat: expand client portal and approval workflows
Add new client portal pages (dashboard, approvals, notifications, documents, reports) and extend API/routes/services to support client approvals, invoices/quotes views, and related notifications.

Update email templates and docs; add/adjust tests for new models/routes.
2026-01-02 07:52:32 +01:00