31 lines
1.8 KiB
SQL
31 lines
1.8 KiB
SQL
-- +goose Up
|
|
|
|
-- Append-only ledger, same doctrine as cash_transactions (006_cash.sql):
|
|
-- balance is never a stored column, always SUM(points) on read — a
|
|
-- mis-entered correction gets a compensating row of the same type, not an
|
|
-- edit/delete. Unlike cash, points really does have one meaningful
|
|
-- "at most once" fact: an order/batch is only ever accrued once, enforced
|
|
-- below as a real constraint (dedupe_key in internal/clientnotify is an
|
|
-- app-computed analog of the same idea; here the natural key already is
|
|
-- just "this order").
|
|
CREATE TABLE loyalty_transactions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
client_id UUID NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
|
|
type TEXT NOT NULL CHECK (type IN ('accrual', 'redemption', 'adjustment')),
|
|
points INT NOT NULL CHECK (points <> 0),
|
|
order_id UUID REFERENCES orders(id) ON DELETE SET NULL,
|
|
cartridge_batch_id UUID REFERENCES cartridge_batches(id) ON DELETE SET NULL,
|
|
note TEXT,
|
|
created_by_staff_id UUID NOT NULL,
|
|
created_by_staff_name TEXT NOT NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
CREATE INDEX loyalty_transactions_client_id_idx ON loyalty_transactions (client_id, created_at DESC);
|
|
CREATE UNIQUE INDEX loyalty_transactions_order_accrual_idx
|
|
ON loyalty_transactions (order_id) WHERE type = 'accrual' AND order_id IS NOT NULL;
|
|
CREATE UNIQUE INDEX loyalty_transactions_batch_accrual_idx
|
|
ON loyalty_transactions (cartridge_batch_id) WHERE type = 'accrual' AND cartridge_batch_id IS NOT NULL;
|
|
|
|
ALTER TABLE settings ADD COLUMN loyalty_enabled BOOLEAN NOT NULL DEFAULT FALSE;
|
|
ALTER TABLE settings ADD COLUMN loyalty_accrual_percent NUMERIC(5, 2) NOT NULL DEFAULT 0;
|