Skip to content

Add V97 migration: per-item markup_percentage override on phoenix_kit_cat_items - #493

Merged
ddon merged 5 commits into
BeamLabEU:devfrom
mdon:feat/v97-item-markup-override
Apr 15, 2026
Merged

Add V97 migration: per-item markup_percentage override on phoenix_kit_cat_items#493
ddon merged 5 commits into
BeamLabEU:devfrom
mdon:feat/v97-item-markup-override

Conversation

@mdon

@mdonmdon commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a nullable markup_percentage DECIMAL(7, 2) column on
phoenix_kit_cat_items. When NULL, pricing falls back to the parent
catalogue's markup_percentage (existing behavior); when set
(including 0), the item uses its own value.

NULL vs. 0 is load-bearing:

  • NULL = "inherit whatever the catalogue currently uses"
  • 0 = "explicitly sell at base price, even if the catalogue has a markup"

The column is nullable with no default, matching that distinction.
Existing rows stay NULL and continue to inherit — no backfill needed.

Migration safety

  • up/1 is idempotentIF NOT EXISTS guard inside a DO $$ block
  • down/1 uses DROP COLUMN IF EXISTS, also idempotent
  • Lossy rollback note included in the down/1 docstring: any per-item
    overrides set after V97 are lost on rollback (items revert to the
    catalogue's markup)
  • PG ALTER TABLE ADD COLUMN with no default + nullable = metadata-only
    operation, no full-table rewrite, AccessExclusive lock held briefly —
    safe for production-size tables
  • No new index — markup_percentage isn't queried as a filter, only
    SELECTed and computed in app code

Other changes

  • Bumps @current_version to 97
  • Adds the ### V97 docblock entry above V96 with the ⚡ LATEST marker moved

Used by

phoenix_kit_catalogue
uses this column to back a per-item markup override field on the item
form and import wizard. See its companion PR.

Dependencies

⚠️Please merge #491 (Maintenance mode: layout override + scheduled
windows + PubSub) first
— this PR is branched off dev on top of
that work. Once #491 is merged, this can be rebased onto the new
dev cleanly (no overlapping files).

mdonand others added 5 commits April 14, 2026 21:32
Replaces the old @show_maintenance assign approach with a dynamic layout
swap via socket.private[:live_layout] — the underlying LiveView keeps
running so form state and scroll position are preserved when maintenance
toggles on or off. URL never changes.
Core changes:
- Layout override in on_mount hook instead of redirect. When maintenance
turns on, put_in socket.private[:live_layout] swaps the layout live;
when it ends, PubSub triggers restoration of the original layout
- New PhoenixKitWeb.Layouts :maintenance template with countdown timer
- HTTP plug renders inline 503 HTML (with Retry-After header) for
controller routes, with proper Phoenix.HTML escaping to prevent XSS
- Scheduled maintenance windows with start/end UTC datetimes, 1-year
upper bound, and 60-second tolerance for datetime-local minute precision
- cleanup_expired_schedule auto-disables stale state on every page access
- Process.send_after timer unblocks users when scheduled end arrives
(clamped to Erlang's 32-bit timeout limit)
- PubSub broadcasts on every state change so all connected LiveViews
react instantly; admin tabs stay put, user tabs swap layouts
- Manual toggle clears expired schedule on enable to avoid stale locks
- Activity logging for all admin actions (toggle, content, schedule)
- All user-facing strings wrapped in gettext
Schedule validation rejects: empty, past start/end, end before start,
dates >1 year in the future. Datetime inputs use the system time_zone
setting for display and convert to UTC for storage.
Extracts timezone helpers (offset_to_seconds, shift_to_offset,
parse_datetime_local, format_datetime_local) to PhoenixKit.Utils.Date
so they can be tested in isolation.
Adds 93 new tests: unit tests for validate_schedule and PubSub,
integration tests for Maintenance context and the plug (including
XSS regression test), and doctested timezone helpers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move phx-change from individual inputs to the form element so the
live preview updates on every keystroke (input-level phx-change on
text inputs only fires on blur, making the preview appear broken)
- Remove the "Preview" link that navigated to /maintenance. The path
went through locale-prefixed routes and got caught by the publishing
module's /:language/:group catch-all. The settings page already has
an inline Live Preview card rendering the same content, so the link
was redundant
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
HIGH (merge blockers):
- Register MaintenanceCountdown hook in priv/static/assets/phoenix_kit.js
alongside the other phoenix_kit hooks. Parent apps already include this
file, so the hook is now reliably available (was previously injected by
a plug script that wasn't guaranteed to run before LiveSocket init).
Remove the fragile inline injection from the Integration plug.
- Plug's 503 HTML now uses inline CSS instead of linking to
/assets/css/app.css (which wasn't actually served — the real digested
path is /assets/app.css). The page is now self-contained with light +
dark mode support via prefers-color-scheme, so it works on any route
regardless of the parent app's asset pipeline.
- Replace String.contains?/2 with String.starts_with?/2 in the plug's
auth_route?/1 and static_asset?/1. A parent-app path like
/blog/users/log-in-to-us would have bypassed maintenance mode. Add a
regression test covering parent-app look-alike paths.
MEDIUM:
- disable_system/0 now clears maintenance_scheduled_end in addition to
maintenance_scheduled_start so a stale end time doesn't surprise-disable
the next re-enable. Update the test to assert both fields are cleared.
- Track the Process.send_after timer ref in socket assigns and cancel it
on reschedule (via new reschedule_maintenance_end_timer/1) so schedule
changes don't leave a stale "auto-off" signal in flight.
- Settings LiveView's PubSub handler now re-reads header and subtext so
multi-admin editing stays in sync. The save handler broadcasts status
change to trigger this sync.
- schedule_error_message/1 catch-all now logs a warning with the unknown
atom so future validation additions surface instead of being silently
swallowed.
- Document check_maintenance_mode/1's required call sites (all 6 on_mount
hooks listed in the @doc) so new live_sessions don't forget it.
- Add a HACK comment near put_in socket.private[:live_layout] noting it
relies on Phoenix LiveView internals (same pattern as
maybe_apply_plugin_layout) and should be revisited on major LV upgrades.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Addresses the final MEDIUM improvement from PR review: instead of each
of the 6 on_mount hooks explicitly calling check_maintenance_mode/1,
fold it into mount_phoenix_kit_current_scope/3 which all 6 already use.
New live_sessions that use a scope-mounting on_mount hook now inherit
maintenance mode enforcement automatically — no way to forget it.
Removes 6 redundant call sites and updates the @doc comment.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…_cat_items
Adds a nullable `markup_percentage DECIMAL(7, 2)` column on
`phoenix_kit_cat_items`. When `NULL`, pricing falls back to the
parent catalogue's `markup_percentage` (existing behavior); when set
(including `0`), the item uses its own value.
`NULL` vs. `0` is load-bearing: `0` means "explicitly sell at base
price", `NULL` means "inherit whatever the catalogue currently uses".
The column is nullable with no default, matching that distinction.
Existing rows stay `NULL` and continue to inherit — no backfill is
needed.
The `up/1` operation is idempotent (`IF NOT EXISTS` guard inside a
`DO $$` block), and the `down/1` rollback uses `DROP COLUMN IF
EXISTS` so it's also idempotent. Lossy rollback note included in
the down/1 docstring: any per-item overrides set after V97 are lost
on rollback (items revert to the catalogue's markup).
PG `ALTER TABLE ADD COLUMN` with no default and nullable = metadata-
only operation, no full-table rewrite, AccessExclusive lock held
briefly — safe for production-size tables.
No new index — `markup_percentage` isn't queried as a filter, only
SELECTed and computed in app code.
Bumps `@current_version` to 97 and adds the `### V97` docblock entry
above V96 (with the `⚡ LATEST` marker moved).
Used by `phoenix_kit_catalogue` to support a per-item markup override
field on the item form and import wizard.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ddon
ddon merged commit 8742e5c into BeamLabEU:devApr 15, 2026
ddon pushed a commit that referenced this pull request Apr 15, 2026
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@mdon
mdon deleted the feat/v97-item-markup-override branch April 15, 2026 16:22
@ddonddon mentioned this pull request Apr 20, 2026
3 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@mdon@ddon