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.
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
- 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.
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.
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.
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
- Dashboard cached template data containing ORM objects (active_timer,
recent_entries, top_projects, templates) that became detached when
served in a different request, causing 'Instance not bound to a Session'
and 'Database Error' on second visit
- Add migration 133 to merge heads 132 (timesheet governance) and 129
(task tags) so flask db upgrade runs without conflicts
- Update CHANGELOG
- 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
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.
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)
Resolves 'Multiple head revisions' error during pre-deploy (flask db upgrade)
on Render. Two branches existed: 118_add_role_hidden_module_ids and
128_add_invoices_zugferd_pdf. New revision 129_merge_118_128_heads merges
them so 'alembic upgrade head' runs successfully.
- 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).
- 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).
- API: include time_entry_requirements in GET /users/me (require_task, require_description, min length)
- Timer: validate task/description requirements when starting timer; pass time_approvals_enabled and can_request_approval to view_timer; pass entry_ids_with_pending_approval to time_entries_overview
- Import: pass skip_entry_requirements=True when creating entries from CSV import
- 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
- 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>
PDF invoices were missing extra goods (and expenses) because the ReportLab
template renderer only used invoice.items as the table data source.
- Add invoice.all_line_items to template context: merged list of items,
extra_goods, and expenses with normalized description/quantity/price fields
- Update default template schema to use invoice.all_line_items instead of
invoice.items for the items table
- Add migration to update existing saved templates with the new data source
- Update PDF layout designer: add all_line_items and extra_goods loop options,
default items table to all_line_items
- Add expenses to fallback ReportLab generator for consistency with
pdf_default.html
Fixes#503
Co-authored-by: Cursor <cursoragent@cursor.com>
- 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>
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>
- 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>
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>
- 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>
- Add merge migration 116_merge_three_heads to join heads
090_add_push_subscriptions, 100_gantt_colors_modules, and
115_add_exclude_weekends so 'flask db upgrade' runs cleanly.
- Fix undefined-name and scope issues for CI code-quality:
- api_v1: import InvalidOperation in amount_paid Decimal block
- custom_reports, invoice_approvals, reports, scheduled_reports,
tasks: add current_app to Flask imports
- integrations: set user_integration before POST branches so
update_config path can use it
- invoices: use _ign for unpacking in export_invoice_ubl to avoid
shadowing gettext _ (F823)
- data_import, excel_export: add module logger (logging.getLogger)
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.
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
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).
- 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.
- 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
- 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.
- Create migration 090 to add push_subscriptions table
- Fixes database error when deleting users (relation 'push_subscriptions' does not exist)
- Table includes foreign key to users with CASCADE delete
- Adds index on user_id column
- Follows idempotent migration pattern for safe execution
Fixes#428
- 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
- 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.
- 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
Add existence checks before adding columns to report_email_schedules table
to prevent DuplicateColumn errors when columns already exist in the database.
This ensures the migration can be safely rerun and handles cases where
columns were manually added or migration partially completed.
Fixes: split_by_salesman and salesman_field_name columns in
report_email_schedules table
- Inventory: Group stock lots by warehouse, unit cost, lot type, and date
to prevent duplicate display entries
- Inventory: Add total value calculation and display per warehouse
- Migration: Make 082_add_global_integrations migration idempotent with
existence checks for columns and indexes
- Migration: Add 101_add_issues_table migration for client-reported issues
tracking with multi-database support (SQLite, PostgreSQL, MySQL)
- Version: Bump to 4.9.5
- 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.
Add existence checks to older migrations (002, 004, 013, 021, 031, 034, 044,
046, 071, 086, 087) to prevent errors when objects already exist.
Also fixes syntax errors in migrations 046 and 086 where index creation
code was incorrectly placed outside try/except blocks.
All affected migrations now:
- Check for table/column/index existence before creating
- Handle existing objects gracefully with informative messages
- Have safe downgrade functions
- Use proper try/except error handling
Add existence checks to recent migrations (032, 049, 053, 055, 058, 064, 099)
to prevent DuplicateTable and DuplicateColumn errors. These migrations now:
- Check if tables/columns exist before creating them
- Handle cases where objects already exist gracefully
- Provide informative messages about operations
- Have safe downgrade functions with existence checks
This makes migrations more resilient to partial failures and manual
schema changes.
Fix migration 098 to check if invoice_peppol_transmissions table exists
before creating it. This prevents DuplicateTable errors when the migration
is run on databases where the table was already created manually or
during a previous partial migration attempt.
The migration now:
- Checks for table existence before creating
- Handles existing tables gracefully
- Provides informative messages
- Has safe downgrade function