Document https://crowdin.com/project/drytrix-timetracker in CONTRIBUTING.md, docs/TRANSLATION_SYSTEM.md, and contributor guides. Update CONTRIBUTING_TRANSLATIONS.md with the public project URL, clearer translator vs maintainer onboarding, and an optional "Further Crowdin integration" section (GitHub app, automation, QA, glossary, notifications).
Refresh crowdin.yml header comments (project URL, secrets pointers), normalize preserve_hierarchy to a boolean, and keep the nb→no languages_mapping quoted for YAML 1.1 compatibility.
Add crowdin.yml mapping the English gettext catalog to per-locale messages.po paths, with an explicit nb→no mapping so Norwegian matches app/config.py.
Add a manual GitHub Action (Crowdin sync) to upload sources and download translations when CROWDIN_PROJECT_ID and CROWDIN_PERSONAL_TOKEN are configured.
Extend CONTRIBUTING_TRANSLATIONS with maintainer steps, cross-link TRANSLATION_SYSTEM and contributor guides, refresh TRANSLATION_SYSTEM metadata, and note the integration under [Unreleased] in CHANGELOG.md.
Add Move up / Move down controls and an Order column for quote line items, Costs, and Extra goods on create and edit pages (Issue #584). DOM row order matches POST field order so existing QuoteItem.position handling stays correct.
Fix the quote detail Valid until row by using quote.is_expired instead of an undefined Jinja now() (Issue #583). Submit a past valid_until in the web regression test so the view path is exercised.
Document translation contributions without Git: add docs/CONTRIBUTING_TRANSLATIONS.md, a Translation improvement issue template, links from CONTRIBUTING.md and TRANSLATION_SYSTEM.md, and a Translations subsection in docs/development/CONTRIBUTING.md. Refresh CHANGELOG [Unreleased] for these items.
The will-navigate guard compared file URL origins to the literal string
file://, but the URL API reports an opaque origin for file pages, so
legitimate file navigations were blocked and the window could fail to
load reliably on Windows.
Import utils/helpers from the renderer entry so esbuild includes
window.Helpers in the bundle, restoring formatters and isValidUrl after
build:renderer.
Documentation: desktop README explains the renderer bundle workflow;
Windows desktop troubleshooting covers stuck loading; frontend quality
gates table notes the app.js entry and rebuild step.
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.
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
- Generate and send Idempotency-Key for queued POST /api/v1/time-entries creates.
- Wire sync queue and API client so retries do not duplicate entries after reconnect.
- Adjust settings and sync use case for the new behavior.
- 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.
- 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.
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.
exchange_code_for_tokens called logger.debug without a defined logger, which triggered flake8 F821 in CI. Import logging and use getLogger(__name__) at module scope.
Flutter rejects release mode with --simulator; use the default debug
simulator build so CI can compile and zip Runner.app. Document the
constraint in workflow comments.
Screens referenced dateFormat and timeFormat, which do not exist on
UserPrefs. Align with the model and date_format_utils key-based API so
flutter analyze and tests compile.
Implement the missing Flutter data layer so release builds compile: Dio ApiClient for /api/v1 (timer, time entries, projects, tasks, finance, time-off, users/me), JSON models, Hive LocalStorage, and offline SyncService queue.
Add OpenTelemetry (opentelemetry package) with initMobileOpenTelemetry() reading OTEL_EXPORTER_OTLP_ENDPOINT and OTEL_EXPORTER_OTLP_TOKEN via --dart-define, matching server OTLP base URL and Basic auth behavior. Instrument login token validation, timer start/stop, and sync pending.
Fix SyncUseCase to import storage SyncService, use trusted insecure hosts, and call syncAll().
GitHub Actions (build-mobile.yml, cd-release.yml): run flutter test; pass OTLP secrets into flutter build apk/appbundle/ios; switch iOS CI to release simulator builds and package build/ios/iphonesimulator/Runner.app to avoid requiring an Apple Development Team for generic device builds.
.gitignore: allow tracking mobile/lib/data/ despite the repo-wide data/ ignore rule.
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.
Remove default form-input shadow and top margin inside tinted invoice and
quote line rows. The row already has a border and hover shadow; inputs
were doubling that edge so the top of each row looked overly heavy.
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.
- Allow schedule edits (project, task, start/end, break) for users with
edit_own_time_entries on their own entries in API update_entry and
timer edit; scope project lists for subcontractors; admin-only source
dropdown on edit timer form.
- App shell: min-width/overflow fixes, header layout, compact bottom nav
on very narrow viewports (#573), dashboard timer block responsive layout.
- Invoice and quote edit: min-w-0 on grids/cells; scoped stronger neutral
borders for .form-input on #editInvoiceForm and #quote-form (#574).
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.
Sync editor content before submit validation and replace hidden required-field blocking with explicit inline checks. Preserve posted form values and enforce non-empty HTML on backend validation to avoid data loss and confusing no-op 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.
Increase quantity column spans for Invoice Items and Extra Goods on the invoice edit page so values are easier to read, and keep static and JS-added rows aligned.
- 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