Commit Graph

1469 Commits

Author SHA1 Message Date
MacJediWizard 89a0897708 fix(api): repair joinedload calls referencing non-existent relationships
Five integration tests fail because three API v1 route groups call
`joinedload()` on relationships the underlying models never define,
producing AttributeError -> 500 on the first query.

The three bad references were all introduced in commit 579fc7af
("extract business logic to service layer", Nov 2025) and have been
quietly broken since. They surfaced only now because the v5.5.6
fixture refresh started exercising these endpoints more thoroughly.
FK enforcement (also added in v5.5.6) is a red herring here.

### Repairs

- `KanbanColumn` has no `project` relationship (only the `project_id`
  FK column), and `KanbanColumn.to_dict()` never reads `self.project`.
  The eager load is dead code in both the PUT/PATCH and DELETE
  handlers — drop it.
- `ClientNote` has an `author` relationship, not `created_by_user`,
  and `ClientNote.to_dict()` reads `self.author.username` /
  `self.author.full_name`. The four call sites still need eager
  loading; rename the attribute to the real one (`author`).
- `SavedFilter` has no `user` relationship (only the `user_id` FK
  column), and `SavedFilter.to_dict()` never reads `self.user`. Like
  KanbanColumn, the eager load is dead code in all four call sites —
  drop it.

### Tests fixed

- tests/test_api_kanban_v1.py::test_kanban_columns
- tests/test_api_client_notes_v1.py::test_client_notes_crud
- tests/test_api_saved_filters_v1.py::test_saved_filters_crud
- tests/test_routes/test_api_v1_invoices_tasks_expenses_refactored.py::TestAPITasksRefactored::test_get_task_uses_eager_loading (transitively, via the same route module)

### Notes

The unused `from sqlalchemy.orm import joinedload` import in the
kanban/saved-filter handlers is left in place to keep the diff
surgical. It can be removed in a follow-up cleanup.

### Test plan

- [ ] pytest tests/test_api_kanban_v1.py::test_kanban_columns
- [ ] pytest tests/test_api_client_notes_v1.py::test_client_notes_crud
- [ ] pytest tests/test_api_saved_filters_v1.py::test_saved_filters_crud
- [ ] All three return 200 from CRUD operations; no AttributeError logged
- [ ] No regression on routes that already used joinedload correctly
2026-05-14 16:54:14 -04:00
Dries Peeters 8f18d3b624 Merge pull request #624 from DRYTRIX/rc/v5.5.7
Rc/v5.5.7
v5.5.7
2026-05-14 21:11:34 +02:00
Dries Peeters 96e0acbbea chore(release): version 5.5.7 and changelog
Bump setup.py to 5.5.7 and document the invoice PDF designer layout fix,
designer-to-ReportLab template parity, and preview vs export alignment
for issue #622 in CHANGELOG.md.
2026-05-14 21:10:43 +02:00
Dries Peeters 19f6636c0b Fix invoice PDF designer layout and table export parity
Close the missing canvas-area wrapper in the invoice PDF designer so the properties panel sits in the third grid column beside the canvas instead of below it.

When saving template JSON from the designer, read items-table and expenses-table width, colors, and separator line settings from the Konva group children so exports match user edits. Scale column widths to the chosen table width and emit a style block for ReportLab.

In the ReportLab renderer, scale column widths to element.width, wrap tables in a two-column outer table to honor horizontal x offset relative to the left margin, and apply borderColor and borderWidth from template style.

Extend the JSON-to-HTML preview converter so table header text, row text, row background, and border width reflect the same style keys used on export (fixes preview vs PDF mismatch for issue #622).
2026-05-14 21:07:50 +02:00
Dries Peeters 90a8480e4b Merge pull request #621 from DRYTRIX/rc/v5.5.6
Rc/v5.5.6
v5.5.6
2026-05-14 06:54:16 +02:00
Dries Peeters 52c7e9f02a feat(ai): gate helper by default; add uninstall docs and release 5.5.6
- Honor AI_ENABLED across session AI, REST v1, LLM service, templates, and
  context; add regression tests for the AI helper gate.
- Docker Compose: optional Ollama behind the ai profile; align env.example
  and example compose with safe defaults.
- Add UNINSTALL.md with a dedicated AI teardown section; cross-link from
  README, INSTALLATION, Getting Started, docs index, and Docker setup guide.
- Record 5.5.6 in CHANGELOG and sync version examples in BUILD_CONFIGURATION;
  bump setup.py to 5.5.6.
2026-05-14 06:46:59 +02:00
Dries Peeters 3153f0d800 test(dashboard): assert against split stats/chart cache keys
The dashboard route now writes two cache entries per user
(dashboard:stats:<id> and dashboard💹<id>) instead of a single
dashboard:<id> blob, and today_hours moved into the nested
time_tracking payload. Update the caching tests to mirror this shape
and centralise the key tuple in a small helper.
2026-05-14 06:23:29 +02:00
Dries Peeters 973e6a285c test(project_costs): set updated_at explicitly instead of patching datetime
The ProjectCost.updated_at column default captures datetime.utcnow at
class definition time, so patching app.models.project_cost.datetime
does not change the initial value written on insert. Assign
updated_at directly on the instance to get the deterministic
"before" timestamp the test relies on.
2026-05-14 06:23:23 +02:00
Dries Peeters befb2d7b5f test(keyboard): update asset assertions for advanced shortcuts script
base.html now loads keyboard-shortcuts-advanced.js globally instead of
keyboard-shortcuts-enhanced.js, and the cheat-sheet CSS is only pulled
in on the dedicated settings page. Update the integration assertion
accordingly.
2026-05-14 06:23:18 +02:00
Dries Peeters 93dafdbc31 test(currency): detach ORM objects across app_contexts and harden Settings test
- Return lightweight SimpleNamespace snapshots from the sample_client
  and sample_project fixtures so downstream tests can access .id and
  .name without re-attaching the SQLAlchemy instance to a new session.
- In test_settings_currency_can_be_changed, persist the Settings row
  when get_settings() returns a transient instance, and re-query for
  verification instead of refresh() (which fails on transient objects).
2026-05-14 06:23:14 +02:00
Dries Peeters 92a081d37c test(webhook): use naive datetime for next_retry_at assertion
WebhookDelivery.next_retry_at is a naive DateTime column. Strip the
tzinfo before passing it to mark_retrying() so the round-trip
comparison after commit/reload does not fail with a naive/aware
mismatch.
2026-05-14 06:23:00 +02:00
Dries Peeters bda4bcec3e test(supplier): use supplier_items relationship API
Supplier.stock_items is no longer a list-like relationship; assert
against the dynamic supplier_items relationship using .count() and
.first() instead.
2026-05-14 06:22:56 +02:00
Dries Peeters f7becfa946 test(models): create Client/User rows to satisfy FK enforcement
With foreign keys enforced in tests, hard-coded client_id=1 and
user_id=1 references no longer work because no such rows exist in the
isolated test database. Create the required parent rows (Client for
inventory project tests, Client + User for the burndown/SavedFilter
smoke tests) before inserting the children.
2026-05-14 06:22:53 +02:00
Dries Peeters 8f8cb3d7b1 test(client_portal): centralise portal access setup and drop retry shims
Introduce a small set_client_portal_access helper that updates the
user row via a single UPDATE and expires the session, replacing the
scattered no_autoflush + safe_commit_with_retry + safe_get_user dance
in every test. Also auto-disable audit logging for this module to
avoid SQLite write-lock contention during portal tests, and fix a few
indentation bugs where assertions sat outside the app_context block.
2026-05-14 06:22:35 +02:00
Dries Peeters 373a21f323 test(fixtures): enable SQLite FK enforcement and seed baseline roles
- Turn on PRAGMA foreign_keys=ON for every SQLite connection so
  ondelete="CASCADE" and other FK constraints are exercised by tests.
- Disable FK enforcement only for DROP TABLE statements, since the
  schema has cyclic references (deals/leads/projects/quotes) and
  drop_all() cannot order them cleanly.
- Seed admin/user/manager/subcontractor roles in the app fixture so
  route tests that validate against the role table no longer need to
  run the full permission seed command.
- Make TimeEntryFactory.end_time deterministic relative to start_time
  so created entries always represent a valid 2h window.
2026-05-14 06:22:30 +02:00
Dries Peeters e7e0376dce chore(api): re-export _get_client_id_from_session helper
The legacy api.py module exposes _get_client_id_from_session, which is
imported by client-portal tests via app.routes.api. Surface it through
the package __init__ so the import works regardless of which loader
path is taken.
2026-05-14 06:22:24 +02:00
Dries Peeters 4469ea8720 fix(client_portal): tolerate non-JSON dashboard preferences body
Use request.get_json(silent=True) so a request with the wrong
Content-Type or a malformed body falls through to the existing
validation path and returns 400 instead of raising a 415/parse error.
2026-05-14 06:22:19 +02:00
Dries Peeters 7446b86a36 fix(audit): reuse session connection when checking audit_logs table
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.
2026-05-14 06:22:15 +02:00
Dries Peeters 0718da2585 fix(inventory): soft-delete suppliers via HTML delete route
Match the behaviour of the API v1 endpoint by marking the supplier as
inactive instead of hard-deleting it. This preserves referential
integrity with purchase orders, stock items and audit data and removes
the "cannot delete supplier with associated stock items" failure mode.
2026-05-14 06:22:11 +02:00
Dries Peeters 2ed3bd6a88 feat(pdf-designer): multi-select, text alignment, guides, layers (#619)
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.
2026-05-13 19:49:38 +02:00
Dries Peeters 768b0b5b6d fix(ci): harden security pytest lane and Safety report output
Security pytest:
- Run an explicit node list instead of -m security over the whole tests
  tree, so collection stays small and nothing is spuriously deselected.
- Use a writable pytest cache under INSTALLATION_CONFIG_DIR and filter
  the known Flask-SQLAlchemy SAWarning on metadata DROP ordering.
- Add scripts/ci/security-pytest.sh and wire Makefile, run-tests.sh/.bat,
  and ci-comprehensive to call it for a single source of truth.

Safety:
- Write JSON to .test_installation_config/safety-report.json (with the
  rest of local CI artifacts) instead of the repo root.
- Run scripts/ci/sanitize_safety_report.py after each scan so paths in
  the report are workspace-relative for artifacts and reviews.
- Capture Safety exit codes so failures still print where the report was
  written; use python -m safety in workflows where appropriate.

Release and legacy workflows pick up the new report path, sanitizer,
and a pinned Safety install where the CLI is invoked.
2026-05-13 12:46:35 +02:00
Dries Peeters 91bb9417fd chore(deps-test): pin safety==3.7.0 for Typer compatibility
Safety 3.0.1 crashes on import with Typer >= 0.17 (AttributeError:
typer.rich_utils). Pin the test dependency to 3.7.0 so local CI and
workflows can run the Safety CLI reliably.
2026-05-13 12:46:28 +02:00
Dries Peeters 1836cb3c2d chore(typing): resolve mypy errors and harden type checking
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.
2026-05-13 10:32:06 +02:00
Dries Peeters 786d88bdba style: apply black 24.8.0 and isort across app/
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.
2026-05-13 10:31:39 +02:00
Dries Peeters 66698cd16c chore(ci): add local comprehensive CI script mirroring GitHub Actions
Introduce scripts/run-ci-local.sh and scripts/ci/lib.sh to run the same
checks as ci-comprehensive.yml. Ignore .test_installation_config/ where
the script writes INSTALLATION_CONFIG_DIR by default.
2026-05-13 09:12:16 +02:00
Dries Peeters 8b24059b3d test: re-enable skipped suites and align auth/fixtures with CI
Remove obsolete skips across admin, calendar, invoices, payments, PDF,
uploads, and related tests. Use /login and passwords consistent with
conftest; fix team chat setup in silent-exception tests; assert expense
currency with locale-tolerant amount matching; align admin logo tests
with admin_authenticated_client credentials.
2026-05-13 09:12:14 +02:00
Dries Peeters 343dc90220 fix(nginx): use http2 on; and lazy-resolve app upstream in Docker
Switch to the listen + http2 on; form for modern nginx. Add Docker DNS
resolver and a variable-based proxy_pass so nginx can start before the
app container registers, and apply the same upstream to /socket.io/.
2026-05-13 09:12:10 +02:00
Dries Peeters 657874f6c1 refactor(quotes): structure quote create POST with stepped validation
Wrap create flow in a single try/except with explicit step labels for
logging and diagnostics. Harden logging around current_user and keep
validation/flash behavior while making failures easier to trace.
2026-05-13 09:12:08 +02:00
Dries Peeters 98f173c5f7 fix(telemetry): quiet OpenTelemetry during pytest unless opted in
Skip network OTLP export when TESTING is set unless OTEL_ENABLE_IN_TESTS=1.
Register shutdown once at exit, reset providers cleanly in tests, and
briefly silence OTEL loggers during shutdown to avoid noisy CI output.
2026-05-13 09:12:05 +02:00
Dries Peeters 83dbfb51df fix(settings): unwrap scoped session when detecting flush state
Flask-SQLAlchemy exposes a scoped_session proxy; reading _flushing on the
proxy did not reflect the underlying session, so Settings.get_settings()
could run add/commit mid-flush and break tests. Unwrap to the real session
before checking _flushing and _warn_on_events.
2026-05-13 09:12:03 +02:00
Dries Peeters f9096af735 Merge pull request #618 from DRYTRIX/rc/v5.5.5
Rc/v5.5.5
v5.5.5
2026-05-12 20:22:03 +02:00
Dries Peeters 0d9e588597 chore(release): bump version to 5.5.5
Move shell layout fixes from [Unreleased] to CHANGELOG [5.5.5].
Sync BUILD_CONFIGURATION.md version code example and setup.py snippet
with setup.py (single source of truth).
2026-05-12 20:20:23 +02:00
Dries Peeters 4514e1883e fix(ui): repair main shell DOM and use full column width
Remove stray closing </div> tags from admin backups, admin API
tokens, and quote detail templates so content and modals stay
inside #mainContent and the footer aligns with the main column.

Drop max-w-7xl from the primary shell; wrap main + attribution in
a flex column so the scroll area fills width beside the sidebar.
Align the support banner inner row to full width.

Documented in CHANGELOG.md under [Unreleased].
2026-05-12 20:18:46 +02:00
Dries Peeters e389342d3a Merge pull request #617 from DRYTRIX/rc/v5.5.4
Rc/v5.5.4
v5.5.4
2026-05-11 07:32:51 +02:00
Dries Peeters 1dff4e7b05 Release 5.5.4
Bump package version in setup.py to 5.5.4.

CHANGELOG: document 5.5.4 (full-database restore hardening and backup/restore documentation).

docs/BUILD_CONFIGURATION.md: refresh version-code example and setup.py snippet to 5.5.4 (50504).
2026-05-11 07:27:13 +02:00
Dries Peeters 1ddea89d40 Harden full-database restore and document operational behaviour
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.
2026-05-11 07:13:04 +02:00
Dries Peeters 2f838adeee Merge pull request #616 from MacJediWizard/upstream-fix/markdown-filter-normalize-toastui
fix(markdown): unescape Toast UI Editor output and render strikethrough
2026-05-08 15:18:08 +02:00
Dries Peeters 2196379036 Merge pull request #615 from MacJediWizard/upstream-fix/tasks-edit-create-orphan-div
fix(ui): close orphan div tags breaking task edit/create layout
2026-05-08 15:17:56 +02:00
Dries Peeters 8e44a37ec4 Merge pull request #614 from MacJediWizard/upstream-fix/kanban-fallback-include-on-hold
fix(kanban): include on_hold in last-ditch validator fallback
2026-05-08 15:17:42 +02:00
MacJediWizard d7a1eac247 style(template_filters): apply black 24.8.0 formatting
Match upstream's pinned black version so the file lands clean in
CI. All four hunks are line-joins under the 88-char limit, no
behavioural change.
2026-05-07 14:51:26 -04:00
MacJediWizard 6dd74a861a fix(markdown): render ~~strikethrough~~ in task and note descriptions
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.
2026-05-07 11:41:36 -04:00
MacJediWizard 7394af7940 fix(markdown): unescape Toast UI Editor over-escaping in markdown filter
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.
2026-05-07 11:36:15 -04:00
MacJediWizard a77461a5c8 fix(ui): close orphan </div> tags breaking task edit/create layout
Same class as the projects/list.html and weekly_goals/index.html fix
shipped earlier. Both task templates had a stray </div> immediately
before the <style> block with no matching opener:

  app/templates/tasks/edit.html:339
  app/templates/tasks/create.html:192

The orphan close pushed the rest of the page outside the outer
.grid lg:grid-cols-3 wrapper, so the form and sidebar rendered
left-aligned and the 'Built by an independent developer' footer
floated up to the top right.

Verified by div-balance trace: both files now reach final balance 0
with no negative excursions.
2026-05-06 15:40:44 -04:00
MacJediWizard 76c8235355 fix(kanban): include on_hold in last-ditch validator fallback
The validator's last-ditch fallback (used when both project-specific
and global columns are missing) returned a hardcoded list that omitted
"on_hold". The function's own docstring on the same code path even
calls this out explicitly:

    drops to globally-defined columns like "on_hold" come back as 400
    "Invalid status".

The broader fix from PR #605 made the validator fall back to global
columns first, which fixes the common case. But the very last fallback
list — used during fresh migrations before the kanban_columns table is
seeded — still rejects "on_hold" tasks the user has already created.
Real installs that ship with on_hold columns enabled hit this on the
first request after a clean migration.

Add on_hold to the hardcoded list so it stays consistent with what
on_hold-enabled installs expect to validate.

Auto-lint reformatted the surrounding column declarations; the only
behavioral change is the addition of on_hold to the fallback list.
2026-05-06 12:19:30 -04:00
Dries Peeters 93aad64b2b Merge pull request #613 from DRYTRIX/rc/v5.5.3
Rc/v5.5.3
v5.5.3
2026-05-06 08:16:52 +02:00
Dries Peeters 381e8b9e84 chore(release): 5.5.3
Document 5.5.3 fixes in CHANGELOG and bump package version.
2026-05-06 08:15:43 +02:00
Dries Peeters 0ba5dca51a Merge remote-tracking branch 'Origin/main' into develop 2026-05-06 08:12:06 +02:00
Dries Peeters 2b26b9a224 Merge pull request #612 from MacJediWizard/upstream-fix/client-approval-enum-values
fix(approvals): bind ClientApprovalStatus enum values to PG, not names
2026-05-06 08:11:28 +02:00
Dries Peeters 25192f5d65 Merge pull request #611 from MacJediWizard/upstream-fix/clients-view-nested-script-tag
fix(clients): remove nested script tag that orphaned confirmDeleteNote
2026-05-06 08:11:16 +02:00
MacJediWizard 0a8fbd8329 fix(approvals): bind ClientApprovalStatus enum values to PG, not names
ClientApprovalStatus is defined with uppercase Python names and lowercase
string values (PENDING = "pending", etc.), but the Postgres enum type
clientapprovalstatus is defined with the lowercase values. SQLAlchemy
defaults to binding the enum *name*, so every query against the column
sent "PENDING" and Postgres rejected it with InvalidTextRepresentation.

This made get_pending_approvals_for_client raise on every client portal
request — the navbar context processor catches the exception and returns
0, but the stack trace was logged on every page load.

Pass values_callable to SQLEnum so SQLAlchemy uses the enum *value* (the
lowercase string PG actually stores).

The auto-lint hook reformatted the rest of the file; the only behavioral
change is the values_callable parameter on the status column.
2026-05-06 01:14:53 -04:00