Inspecting the engine directly opens a second connection, which under
SQLite can contend with a write lock held by the current flush and
cause spurious failures during tests. Bind the inspector to the
session's existing connection when available and fall back to the
engine otherwise.
Add multi-select with Shift/Ctrl toggles and marquee selection on the invoice
and quote PDF template editors, plus a shared transformer, group nudge with
snap-aware arrow keys, multi copy/paste/duplicate, align-to-page or
align-to-selection, horizontal/vertical distribute, and Ctrl/Cmd+G group /
Ctrl/Cmd+Shift+G ungroup.
Expose horizontal (left/center/right/justify) and vertical (top/middle/bottom)
text controls in the properties panel; export verticalAlign, group_id,
locked, and hidden in template JSON where set. Skip hidden elements in
ReportLab rendering; draw justified text on canvas via Paragraph; honor
verticalAlign when a text box height is present.
Introduce app/static/js/pdf_editor helpers (loaded with dynamic import from
the templates), pdf_editor.css, a layers panel with visibility and lock toggles,
smart-guide snapping, and optional ruler chrome. Document behaviour and manual
QA in docs/PDF_LAYOUT_EDITOR.md; add tests/test_pdf_template_schema.py for
optional template fields.
Closes#619.
Drives ``mypy app/`` from 567 errors in 208 files to 0 errors across the
376 source files checked by ``./scripts/run-ci-local.sh code-quality``.
Configuration & dependencies
- pyproject.toml: enable implicit_optional (Flask-style ``x: str = None``
defaults), silence truthy-function/truthy-bool (legitimate import-guard
checks like ``KanbanColumn``), and disable warn_return_any (SQLAlchemy
1.x ``Query`` API returns Any pervasively). Add module overrides for
``app.models.*``, repositories, base CRUD service, and known
``joinedload`` / ``Query.paginate`` callers where mypy cannot model the
Flask-SQLAlchemy runtime API without a plugin.
- requirements-test.txt: pin ``types-requests``, ``types-bleach``,
``types-Markdown``, ``types-python-dateutil`` so mypy stops complaining
about missing stubs.
Latent bugs fixed while driving mypy to zero
- app/utils/logger.py, app/utils/datetime_utils.py: drop imports of
symbols that don't exist (``get_performance_metrics``,
``from_app_timezone``, ``to_app_timezone``) — these would have raised
at import time on first use.
- app/services/currency_service.py: ``from typing import Decimal`` was a
bug (typing has no Decimal); switch to ``decimal.Decimal`` and rename
the ``D`` alias.
- app/utils/env_validation.py, app/utils/role_migration.py: ``Dict[str,
any]`` → ``Dict[str, Any]`` (built-in ``any`` is not a type).
- app/utils/email.py: introduce ``send_template_email`` and update the
three callers (``client_approval_service``,
``client_notification_service``, ``workflow_engine``) that were
passing ``to=``/``template=``/etc. to ``send_email`` whose signature
doesn't accept them — calls would have raised TypeError at runtime.
- app/services/permission_service.py: rewrite ``grant_permission`` /
``revoke_permission`` to use the actual ``Role`` ↔ ``Permission``
many-to-many relationship; the old code referenced non-existent
``Permission.role_id`` / ``Permission.granted`` columns.
- app/services/gps_tracking_service.py: pass the required ``title`` and
``expense_date`` fields when creating mileage ``Expense`` rows.
- app/services/workflow_engine.py: ``_perform_action`` now forwards the
``rule`` argument to ``_action_log_time``, and ``_action_webhook``
short-circuits when ``url`` is missing.
- app/services/time_tracking_service.py: validate ``start_time`` /
``end_time`` before comparing them.
- app/services/export_service.py: build CSV in a ``StringIO`` then wrap
the bytes in ``BytesIO`` — ``csv.writer`` requires text I/O.
- app/integrations/peppol_smp.py: avoid attribute access on ``None`` in
the SMP ``href`` fallback.
- app/integrations/{github,gitlab,slack}.py: coerce query-string params
to strings so ``requests.get(params=...)`` matches the typed signature
(and is what the HTTP layer expects anyway).
- app/integrations/{xero,quickbooks}.py: guard ``get_access_token()``
returning ``None`` before calling private ``_api_request`` helpers.
Annotation-only changes
- Add ``Dict[str, Any]`` / ``list`` / ``Optional[...]`` annotations to
service dict-literals that mypy could not infer from heterogeneous
values (``ai_suggestion_service``, ``ai_categorization_service``,
``custom_report_service``, ``unpaid_hours_service``,
``integration_service``, ``invoice_service``, ``backup_service``,
``inventory_report_service``, ``analytics_service``, etc.).
- ``app/utils/event_bus.py``: ``emit_event`` accepts ``str |
WebhookEvent`` and normalizes to ``str`` so all call-sites type-check.
- ``app/utils/api_responses.py``: introduce ``ApiResponse`` alias for
``Response | tuple[Response, int] | tuple[str, int]``.
- ``app/utils/budget_forecasting.py``: forecasting helpers return
``Optional[Dict]`` (they already returned ``None`` when the project
was missing).
- ``app/utils/pdf_generator_reportlab.py``: ``_normalize_color`` is
``Optional[str]``.
- ``app/utils/pdfa3.py``: remove invalid ``force_version=None`` retry
call.
- Narrow ``type: ignore`` markers on optional-dependency fallbacks
(``redis``, ``bleach``, ``markdown``, ``babel``,
``powerpoint_export``) and on the documented ``requests.Session``
/ ``RotatingFileHandler`` typeshed limitations.
Pure formatting pass to satisfy ``./scripts/run-ci-local.sh code-quality``:
no behavioural changes, just consistent line wrapping, import ordering,
and trailing-newline normalization across routes, models, services, and
utility modules.
Admin restore runs in a background thread; the finally block must not use current_app.logger outside an application context. Use the captured Flask app instance for safe_file_remove logging instead.
While restore_backup runs (extract through Alembic upgrade), set a per-app _database_restore_in_progress flag and expose is_database_restore_in_progress(). The client portal blueprint registers a global app_context_processor; get_current_client() now skips database access during restore and catches SQLAlchemy errors with session rollback so error pages and login can still render when the schema is briefly torn on PostgreSQL.
Documentation: add docs/admin/BACKUP_AND_RESTORE.md, link it from the admin index and import/export docs, cross-reference from DATABASE_RECOVERY.md, and extend IMPORT_EXPORT_GUIDE.md with concurrent-restore guidance.
CommonMark and GFM both define ~~text~~ as strikethrough, and Toast UI
emits it when the user toggles strikethrough in the WYSIWYG editor.
Python markdown's 'extra' extension does not implement strikethrough,
so the wrapping tildes leak through to the rendered HTML and the user
sees ~~text~~ instead of struck-through text on /tasks/<id>, notes,
client view, etc.
Add a regex pass to _normalize_toastui_markdown that converts
~~text~~ to <del>text</del> before markdown parsing. The bleach
sanitizer already permits <del> via the existing allowed_tags list,
so the rendered HTML survives the sanitization pass intact.
The regex is non-greedy and stays on a single line so multiple
strikethroughs on the same line each get their own pair.
Task and note descriptions saved through the Toast UI WYSIWYG editor
came back wrapped in CommonMark-style escapes that Python markdown
either does not honour (\, → literal \,) or honours in a way that
breaks rendering (line-leading \- prevents list parsing).
The visible symptom on /tasks/<id>: bullet lists rendered as a single
flat paragraph with literal backslashes peppered between words.
Add a normaliser run before _md.markdown(...) that:
- Restores line-leading bullets that the editor escaped (\-/\*/\+
followed by whitespace at start of line).
- Strips backslashes before punctuation Python markdown does not
recognise as a valid escape (commas, colons, semicolons, etc.).
This handles existing rows in the DB without any data migration. The
normaliser leaves alone backslashes before punctuation Python markdown
does handle natively (\. \( \) \+ \- mid-line, \* \_ \# etc.)
so author-intent escapes still render correctly.
Strikethrough (~~text~~) still does not render because the 'extra'
extension does not include it; that is a separate enhancement.
Ensure quote list/detail access uses shared quote scope resolution so users with quote-management permissions can view records they can edit, including post-edit redirects in web and API flows. Add regression coverage for non-admin edit_quotes behavior and document the scope-alignment requirement in advanced permissions docs.
Introduce AUTH_METHOD values ldap and all, with LDAP_* environment settings, ldap3-based LDAPService (search, optional groupOfNames checks, user bind, DB sync), and users.auth_provider (local|oidc|ldap) via migration 153_add_user_auth_provider.
Login supports LDAP-only and combined all (local then LDAP where appropriate); OIDC callback sets auth_provider. Forgot/reset/change password flows skip LDAP-managed accounts. Admin System Settings gains a read-only LDAP summary and POST /admin/ldap/test. Production env validation requires core LDAP variables when LDAP is enabled; OIDC registration and docs recognize all.
Documentation: new docs/admin/configuration/LDAP_SETUP.md; updates to OIDC_SETUP, GETTING_STARTED, Docker guides, Render deploy notes, docs README, and CHANGELOG. Tests: tests/test_ldap_auth.py; test_oidc_logout allows auth_method all.
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.
Add render_sandboxed_string() using SandboxedEnvironment so stored invoice and
quote HTML, ReportLab text templates, admin PDF previews, and invoice email
HTML are not evaluated with Flask's full template globals (mitigating SSTI).
Add regression tests for sandbox behavior and demo user permissions.
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)
FixesDRYTRIX/TimeTracker#592.
Tests: extend test_scope_filter with client_portal_scoped_user and API
isolation for GET /api/v1/projects.
Delete app/utils/posthog_features.py. It was unused by routes, and
is_posthog_enabled() always returned False, so flags never activated and
the API was misleading.
Document the real model: deployment behavior uses environment variables
and app/config.py; per-user UI preferences stay on the user record.
Refresh PostHog monitoring guides and README accordingly, update the
incomplete-implementations note, and soften posthog_segmentation wording
so it does not imply an in-app PostHog flag layer.
Document the dual HTTP surface everywhere integrators look: OpenAPI intro and servers, ARCHITECTURE, REST_API, API_VERSIONING (deprecated vs internal routes, shared modules), and CONTRIBUTING (v1-first rule).
Session JSON routes in app/routes/api.py that overlap REST v1 now return X-API-Deprecated and a Link header with rel successor-version, implemented via app/utils/api_deprecation.py.
Extract shared global search into app/services/global_search_service.py for both GET /api/search and GET /api/v1/search while preserving legacy short-query 200 empty responses and v1 400 validation.
Refactor legacy POST /api/timer/start, /api/timer/stop, and the start path of /api/timer/resume to use TimeTrackingService; keep existing socketio emits for the web UI.
Add tests/test_api_deprecation_headers.py and adjust search partial-failure tests to patch Project.query on the service module.
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.
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.
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.
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.
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 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.
- Add POST /api/v1/time-entries/import-csv and POST /api/v1/time-entries/bulk.
- Support Idempotency-Key on POST /api/v1/time-entries with 24h replay per token.
- Enforce per-token minute/hour limits (Redis when available, local fallback otherwise).
- Extract bulk and CSV import logic into dedicated services; trim legacy api route surface.
Introduce a centralized OTel layer (app/telemetry/otel_setup.py) that reuses
the existing OTLP endpoint and token, exports traces and metrics over OTLP/HTTP,
and instruments Flask plus SQLAlchemy. Manual OTLP log export remains for base
and product analytics; log records now include trace_id, span_id, and
event_category where tracing is active.
Business spans and product metrics cover invoices, timers, reports, auth,
webhook delivery, and scheduled jobs. RED-style HTTP metrics are recorded in
after_request alongside existing Prometheus counters. ENABLE_TRACING and
ENABLE_METRICS default on when credentials exist; graceful no-op when they do not.
Privacy is preserved: user_id appears on traces only when detailed analytics is
opted in; metrics never carry user_id; _remove_pii behavior for analytics is
unchanged. Responses inject traceparent when tracing is enabled for future
browser correlation.
Tests: test_otel_integration.py and per-test reset_for_testing() in conftest
so each app factory can reinitialize OTel.
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.
Switch product and installation telemetry to OTLP/Grafana across runtime config, CI injection, docs, and tests to unify telemetry transport and simplify privacy-focused opt-in behavior.
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.
- New scopes read:inventory and write:inventory; existing read/write:projects
still grant same inventory access for backward compatibility
- require_api_token() accepts tuple of scopes (any one required); inventory
endpoints accept (read:inventory | read:projects) and (write:inventory | write:projects)
- ApiTokenService: add new scopes to allowed list; document in API_TOKEN_SCOPES.md
- Add tests for inventory report endpoints with scope checks
- Add keyboard_shortcuts_defaults utility for default bindings and overrides
- Update Settings keyboard shortcuts template for customization UI
- Add KEYBOARD_SHORTCUTS_DEVELOPER.md for implementation and extension
- Register send_base_telemetry_heartbeat_with_app cron at 03:00 UTC
- setup: call check_and_send_telemetry when user opts in during setup
- admin: call check_and_send_telemetry when toggling detailed analytics on
- Delegate track_event, identify_user, track_page_view to telemetry service
- Only send detailed analytics when user has opted in (is_detailed_analytics_enabled)
- Call send_base_first_seen() once at app startup (idempotent per install)
- posthog_funnels: require telemetry_enabled for funnel tracking
- posthog_monitoring: require telemetry_enabled for error/performance events
- Webhook models: remove duplicate index definitions so db.create_all()
no longer raises 'index already exists' (columns already have index=True)
- ImportService: fix circular import by late-importing ClientService,
ProjectService, TimeTrackingService in __init__
- reports: fix F823 by renaming unpack variable _ to _entry_count to avoid
shadowing gettext _ in export_task_excel()
- Code quality: add .flake8 with extend-ignore so flake8 CI passes;
simplify pyproject.toml isort config (drop unsupported options)
- Format: run black and isort on app/
- tests: restore minimal app fixture in test_import_export_models
- Add RecurringInvoiceRepository and RecurringInvoiceService; refactor recurring_invoice model
- Add GanttService and move gantt logic from route to service
- Expand ReportingService and simplify reports route
- Add license_utils and user license template/settings
- Refactor routes to use scope_filter, api_responses, and services (API v1, timer, admin, invoices, etc.)
- Extend invoice_service for recurring; cache and scope_filter utils; base/template updates
- Mileage: Add GET /mileage/export/csv and /mileage/export/pdf with same
filters as list (status, project, client, date range, search). Export
buttons in list header; JS builds export URL from current filter form.
- Mileage PDF: New app/utils/mileage_pdf.py (ReportLab, landscape A4,
totals row for distance and amount).
- Per diem: Add Client filter to list (with client-lock/single-client
handling). Add GET /per-diem/export/csv and /per-diem/export/pdf.
- Per diem PDF: New app/utils/per_diem_pdf.py (same style as mileage).
- Export links always use current filters (no need to submit first).
- CHANGELOG and docs/import_export/README updated.
- get_overtime_ytd(user): returns overtime from Jan 1 through today
- get_overtime_last_12_months(user): returns rolling 12-month overtime
- Reuses calculate_period_overtime; no new DB columns
Architecture and maintainability improvements per production-readiness plan:
- API v1: Split monolithic api_v1.py into per-resource blueprints
(api_v1_projects, api_v1_tasks, api_v1_clients, api_v1_invoices,
api_v1_expenses, api_v1_payments, api_v1_mileage, api_v1_deals,
api_v1_leads, api_v1_contacts). Register all in blueprint_registry;
keep info, health, auth and remaining routes in api_v1.py.
- Bootstrap: Move setup_logging to app/utils/setup_logging.py and
legacy migrations (task management, issues tables) to
app/utils/legacy_migrations.py. Use SQLAlchemy 2-compatible
db.engine.begin() in legacy_migrations.
- Dashboard: Add AnalyticsService.get_dashboard_top_projects and
get_time_by_project_chart; thin main dashboard route to call
services only and remove inline TimeEntry aggregation.
- Docs: Update ARCHITECTURE.md (module table, API structure, data
flow, design decisions), DEVELOPMENT.md (workflow, build steps,
test examples), CHANGELOG.md (Unreleased refactor entry).
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
- In _on_page(), only skip IMAGE elements when their rect does not
intersect the page (was skipping when top-left was outside, which
hid overflowing images).
- Coerce element x, y, width, height to float to avoid type issues.
- In _draw_image_on_canvas(), clip all image drawing to the page
rectangle (beginPath + rect + clipPath) so the visible portion
is shown and overflow is clipped like in the preview.
- Try template image file path before get_image_base64 for more
reliable loading across deployments.
- Add INFO log when drawing template images for debugging.
Fixes#537 (export not showing decorative image when over border).
Before deleting a user, explicitly remove their donation_interactions rows so the delete succeeds even when the table is missing (e.g. after support banner was removed via license). Also handle missing donation_interactions table in safe_commit like time_entry_approvals.
Switch embedded invoice PDFs to Factur-X CII payloads and tighten the PDF/A-3 and AS4 handling so exports better match the standards they advertise. Document the experimental native Peppol transport path and cover the new validation and embedding behavior with focused tests.
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.
- UBL: add unitCode C62 to InvoicedQuantity (required by EN 16931 / Peppol BR-CL-23)
- ZugFerd: attach XML as Associated File with relationship Alternative
- ZugFerd: inject ZUGFeRD XMP RDF for validators
- Docs: document AF/XMP, PDF/A-3 limitation, and validation (b2brouter, portinvoice)
- Tests: assert UBL contains InvoicedQuantity with unitCode in ZugFerd and Peppol tests
Implement the advertised 'Task tags and categorization' feature that was
listed in docs but missing from the task edit form.
- Add tags column to tasks table (String 500, comma-separated)
- Add tags field to task create and edit forms with validation
- Display tags as badges on task view page and list pages
- Add tags filter to main tasks list and My Tasks with AJAX support
- Include tags in task export (CSV) and data export
- Add tags to Task API (create, update, list with filter)
- Add tag_list property to Task model for convenient parsing
Also: fix(oidc) use domain instead of IP for HTTPS metadata fetch
to fix TLS SNI (Fixes#540)
- Add run_seed() in app/utils/seed_dev_data.py: users, clients, projects,
tasks, time entries, expenses, comments, warehouses, stock items,
warehouse stock, stock movements, currencies, tax rules, invoices,
invoice items, and payments. Only runs when FLASK_ENV=development.
- Register 'flask seed' CLI command with options (users, clients,
projects-per-client, tasks-per-project, days-back).
- Add scripts/seed-dev-data.py and docker/seed-dev-data.sh for local
and Docker runs. Include seed scripts in image via Dockerfile chmod.
- Document in docs/development/SEED_DEV_DATA.md; update
DATABASE_RECOVERY.md, DOCKER_COMPOSE_SETUP.md, and development README.
- Add render.yaml Blueprint: PostgreSQL + Python web service, auto-deploy on push
- Add demo mode (DEMO_MODE, DEMO_USERNAME, DEMO_PASSWORD): single fixed user only
- Login page shows demo credentials when demo mode is active
- Disable self-registration, admin user creation, and OIDC user creation in demo mode
- DB init creates demo user with password when DEMO_MODE is true
- Add docs/deploy/RENDER.md with deployment and demo mode instructions
Decorative images now survive save/load and no longer cause a black PDF preview:
- Sync imageUrl onto groups before generateCode() so template_json has
correct source (invoice and quote layout editors).
- Inject name/imageUrl into design_json with position-based matching so
reordering does not swap or drop URLs.
- Restore name and imageUrl from saved JSON onto canvas on load
(synchronous) so Konva custom attrs are not required.
- Omit decorative image elements with empty source from template_json;
placeholders stay visible in the editor but are not sent to ReportLab.
- ReportLab: explicitly skip decorative images with empty source; validate
base64 data URI payload and decode in try/except to avoid bad PDF output.
Documentation: PDF_LAYOUT_CUSTOMIZATION.md and PDF_EDITOR_ENHANCED_FEATURES.md
updated with decorative image description, state persistence details, and
troubleshooting. CHANGELOG.md updated under [Unreleased] Fixed.
- 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).