feat!: port to gp247/core 3.0, with an atomic sale quota (2.0.0)
Version 1.0.0 could not be installed on core 3.0 at all: requireCore ["1.1"]
fails ExtensionInstaller's compatibility range, and the admin screen extended
the removed gp247-core::layout while paginating through the removed
gp247-core::component.pagination, driven by pjax/select2/SweetAlert that the
TailAdmin shell no longer loads.
Admin
- Two-panel Livewire screen on the core ResourcePanel base, replacing the
AdminLTE controller and its views. Route names are unchanged because the
AdminMenu row written by install() points at admin_product_flash_sale.index. - Searchable product picker, flatpickr window WITH the hour (time-boxed selling
needs it), price in the base currency, sold/quantity progress, store column on
multi-store installs. - Validation the old screen never really had: its
uniquerule passed a quoted
class name, which Laravel reads as a table name, so creating a sale hit a SQL
error. Rules now cover one-sale-per-product, quantity >= sold, end after start,
and a server-side check that the product belongs to the acting admin's store.
Quota integrity
- Units are taken with a conditional UPDATE inside the order transaction. The old
read-modify-write let two simultaneous checkouts both pass the advisory check
and oversell the sale. - Units come back when the goods do: cancel, delete, and removing an order line
now release them (wired from gp247/shop). Re-opening a cancelled order takes
them again and is refused when the sale is spent. Before this, one cancelled
order burned a slot for ever.
Visibility
- The listing delegates to ShopProduct::buildQuery instead of querying the tables
directly, so status, approval, store scope and the out-of-stock setting apply.
On a marketplace the old query published unapproved and other stores' products.
Storefront
- Own listing page (the template's product-list screen ignores $products and
rebuilds its own query, so the old page listed the whole catalogue), registered
as a LayoutBlock page-type and contributed to sitemap.xml. - The strip is Tailwind + the template's Alpine countdown; the jQuery countdown
from a CDN is gone, as is jQuery itself. - The block is offered to templates by registering this plugin's directory as one
more GP247TemplatePath source root — nothing is copied into app/GP247/Templates,
so it works on read-only deployments and leaves nothing behind when removed.
Registered from booted() so the site's own published files still win.
Install lifecycle
- install() and update() share a re-entrant converge(); the table is created only
when missing (installing used to drop it first, destroying every scheduled sale)
and the menu row only when absent. - A fresh install also places the strip at the bottom of the home page for stores
that have no such block. Deliberately not in converge(): where a block appears
is the admin's decision, and an update must not overrule it.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com