Add Move up / Move down controls and a 13-column desktop grid on quote
item rows so users can change line order without deleting entries. Order
matches persisted quote_items.position (migration 146) for PDFs and views.
Update CHANGELOG [Unreleased] with Issue #584. Fix inventory integration
test to read quote.items[0] (relationship returns a list, not a query).
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.
Detect SQLAlchemy 2.0 flush execution via _warn_on_events in
Settings._session_in_flush so get_settings() does not add/commit
during flush_context.execute(), avoiding ResourceClosedError in
invoice currency smoke tests.
Payment smoke tests now set a password on factory users and submit
it on login, matching local AUTH_METHOD requirements (fixes 302 to
login on /payments).
Allow HTTP 403 in test_non_admin_cannot_access_roles when the app
denies non-admins directly instead of redirecting with 200.
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.
Cover GET edit form and PATCH API when the user has edit_own_time_entries
on their own entries, complementing the timer route and API changes in the
mobile shell / timer edit work.
Add Settings.mail_test_recipient (migration 142) and wire Admin → Email Configuration to save, load, and prefill the SMTP test recipient field.
Extract invoice email PDF/HTML building into _build_invoice_email_payload for reuse. New send_invoice_template_test_email sends a real message with subject "Invoice template test: …", reusing production rendering and PDF attachment, without updating invoice status or creating InvoiceEmail records.
Add POST /admin/email-templates/<id>/send-test (optional recipient override, optional invoice_id) and Send test email UI on template view and edit pages. Extend smoke and unit tests.
Purchase orders with line items crashed the detail page because the template
referenced item.stock_item without a SQLAlchemy relationship, and used
datetime.now() in Jinja where datetime was not in context.
Add PurchaseOrderItem.stock_item, pass default_received_date from the view
route, and add a regression test for viewing a PO that includes line items.
Read telemetry/sentry status in the admin dashboard from build-injected analytics defaults with env fallback, so release images correctly show OTLP configuration. Include both install_id and telemetry_fingerprint in base and detailed telemetry payloads to reliably differentiate installations, and add tests for the new fingerprint fields.
Switch product and installation telemetry to OTLP/Grafana across runtime config, CI injection, docs, and tests to unify telemetry transport and simplify privacy-focused opt-in behavior.
Prevent 500s during first/parallel purchase-order creation by using collision-safe PO numbering, explicit validation, and reliable commit/error handling in web and API flows. Add regressions for purchase-order edge cases and invoice stock-reduction idempotency to catch adjacent inventory failures before release.
Implement issue #575 by introducing token-based invoice number patterns in settings and unifying number generation across invoice creation paths. This removes hardcoded INV/date formatting and aligns export filenames and bootstrap schemas with stored invoice numbers.
- queueForOffline now saves url, method, headers, body (replay-safe for localStorage);
legacy items with options only still replayed via fallback
- processOfflineQueue builds fetch options from stored method/body so replayed
requests send the same payload when back online
- Make queueForOffline async and await it in handleFetchResponse/handleFetchException
- Add tests asserting queue stores method/body and replay uses them
- Catch AttributeError/KeyError/TypeError and generic Exception in PEPPOL block;
log with exc_info and show generic warning to user so view still renders
- Avoid silent pass that hid configuration or data errors
- Add test for exception path (mock get_custom_field to raise)
- Reports accept ?days=1-365 (default 30) for configurable date range
- ?format=csv returns CSV download (summary, hours by project, time by date)
with same access control as reports page
- Subtitle shows 'Last N days' when date range is applied
- Add tests for days param and CSV export
- New scopes read:inventory and write:inventory; existing read/write:projects
still grant same inventory access for backward compatibility
- require_api_token() accepts tuple of scopes (any one required); inventory
endpoints accept (read:inventory | read:projects) and (write:inventory | write:projects)
- ApiTokenService: add new scopes to allowed list; document in API_TOKEN_SCOPES.md
- Add tests for inventory report endpoints with scope checks
- /api/activity: return 400 with clear message when start_date/end_date
are not valid ISO 8601; avoid silent pass on parse errors
- Web route /activity: catch ValueError, log and skip filter instead of 500
- Add tests for invalid date formats on API and web routes
- When webhook_secret is set in Jira integration, verify incoming webhooks
via X-Hub-Signature-256, X-Atlassian-Webhook-Signature, or X-Hub-Signature
- Reject requests with missing or invalid signature; no secret = accept all (unchanged)
- Add webhook_secret password field to Connection Settings in Jira config
- Add tests for verification success, missing sig, and invalid sig
Remove from index (keep on disk): .cursor/plans, logs/.gitkeep, logs/app.jsonl,
mobile lib files, tests/__pycache__/*.pyc. These are already in .gitignore;
stopping tracking so future changes are ignored.
- test_installation_config: assert install_id UUID format and config persistence
- test_telemetry_consent_and_base: analytics not sent when opt-out, sent when opt-in
- test_telemetry_consent_and_base: base first_seen idempotent, heartbeat payload schema
- test_telemetry_consent_and_base: install_id stable across calls
- 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
- TestOvertimeYTD: get_overtime_ytd / get_overtime_last_12_months structure and values
- test_overtime_leave: request within YTD succeeds, exceeding YTD fails with validation
- Smoke test: assert get_overtime_ytd is available on overtime module
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
Admin menu:
- PDF Templates is now a top-level submenu under Admin (same level as System Settings) so it opens without opening System Settings first.
- Remove PDF routes from admin_settings_open so only PDF Templates expands on invoice/quote PDF pages.
- Set pdfDropdown parent to adminDropdown in nested dropdown click handler.
PDF layout (invoice & quote):
- Preserve table groups (Items, Expenses) on save by skipping Groups in cleanup and ensuring table names are set and restored from design_json.
- Add test for saving and reloading layout with tables.
Docs:
- Update PDF layout access path to Admin → PDF Templates → Invoice PDF (and Quote PDF) in PDF_LAYOUT_CUSTOMIZATION.md, PDF_EDITOR_ENHANCED_FEATURES.md, PDF_EDITOR_QUICK_START.md, INVOICE_EXTRA_GOODS_PDF_EXPORT.md.
- 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).
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>
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 a new User Report export that outputs one row per time entry with selectable columns (date/user/project/task/duration/notes by default), while keeping the existing summary/overtime export.
Refs: #483
Co-authored-by: Cursor <cursoragent@cursor.com>
When the IdP issues a large id_token (e.g. with groups claim), storing it in Flask's cookie session can exceed ~4KB and cause the browser to drop the cookie, leading to redirect loops between /auth/oidc/callback and /login.
Store id_token in Redis/in-memory cache; keep only a small reference key (oidc_id_token_key) in the session cookie. On logout, resolve id_token from cache for RP-initiated logout; support legacy session for backwards compatibility. Add regression test for oversized id_token and update OIDC logout tests.
Fixes#486
Co-authored-by: Cursor <cursoragent@cursor.com>
- 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>
When a single-client team has only one active client, pre-fill and disable
the client selection across manual time logging, project creation, and
similar forms to reduce friction.
- Add reusable client_select macro for single vs multi-client rendering
- Pass only_one_client and single_client to all relevant forms
- Invalidate dashboard cache on client create/archive/activate/delete
- Restore single client when project selection is cleared (timer forms)
- Add tests for single-client manual entry form
- Add '+ Add task' button in Kanban and Gantt headers linking to create
with project (and status for Kanban) pre-filled
- Support 'next' redirect after task create; validate to allowed paths
(/kanban, /gantt, /tasks, /projects) to avoid open redirects
- Honor Initial Status on create: TaskService and form now pass status
through; create form pre-fills status from query and hidden 'next'
- Per-column '+' on Kanban to add task into that column (status pre-set)
- Return user to same Kanban/Gantt view after creating a task
Stock devaluation for return and waste movements was already implemented via valuation layers (stock lots). This change hardens and documents it:
- Add route tests: return with devaluation creates devalued lot; waste with devaluation consumes from devalued lot.
- Add API tests for POST /api/v1/inventory/movements with devalue_enabled (return and waste).
- Add discoverability hint on Record Movement form when type is Return or Waste.
- Document stock devaluation in INVENTORY_MANAGEMENT_PLAN.md (subsection 5.3) and list devaluation in movement_type.
- TimeTrackingService.update_entry() now calls calculate_duration() when
start_time or end_time is changed so duration_seconds stays correct.
- Add test_update_entry_recalculates_duration_when_start_end_edited (fixes#451).
- 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.
- 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'.
- test_client_portal_dashboard_requires_access: expect 302 redirect to
client portal login instead of 403. The client portal 403 handler
redirects authenticated non-portal users to login by design.
- Run routes unit group with -n 0 in CI to avoid SQLite 'database is
locked' errors from audit logging under pytest-xdist parallel workers;
fixes client_portal and admin client-portal test failures.
The logout route uses current_app.config for AUTH_METHOD and Config
for OIDC_POST_LOGOUT_REDIRECT_URI. Two tests only patched Config, so
auth_method stayed local and the handler never hit the IdP redirect
branch, causing redirects to /login and assertion failures.
Set app.config AUTH_METHOD to oidc in
test_logout_with_post_logout_uri_config and
test_logout_oidc_provider_has_revocation_endpoint_only so the route
enters the OIDC branch and the tests pass.
- custom_field_definitions: capture definition_id before delete and use it in
safe_commit to avoid touching detached definition; in tests re-query Client
by id after request instead of db.session.refresh() to avoid 'not persistent'
- permissions: test_delete_role_flow asserts by name (deletable_role) instead
of Role.query.get(role_id) so redirect-triggered sync_permissions_and_roles
id reuse does not cause false failure
- uploads: add password to authenticated_admin_client login_data in
test_uploads_persistence so login succeeds and logo upload tests see
company_logo_filename
- currency_display: run PRAGMAs only for file-based SQLite and use text();
skip for in-memory sqlite:// to avoid ResourceClosedError in admin_user
fixture during commit
- Skip dashboard cache when app.testing so cached ORM objects are never
served in a later request, avoiding 'Instance not bound to a Session'
in test_base_layout_has_sidebar_toggle and test_non_admin_cannot_access_roles.
- In test_admin_can_reset_user_password: ensure Role 'user' exists before
POST (edit_user requires it), use role='user' in form data, and accept
'Manage Users' or success flash as success.