schema / migration / 002-multi-branch

002 - Multi-branch (venues)

  • Tanggal: 2026-08-08
  • DB: mpcdb (shared Postgres — mpc-be / mpc-be-pos / mpc-admin)
  • Ringkas: Tabel branches + branch_stocks; FK branch_id pada courts, pos_outlets, time_groups, special_days, coaches, events; seed branch Main (is_default); backfill data existing.

ALTER (jalankan di DB server)

-- Idempotent; safe to re-run.

DO $$ BEGIN
  CREATE TYPE "BranchStatus" AS ENUM ('Active', 'Inactive');
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;

CREATE TABLE IF NOT EXISTS branches (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  code text NOT NULL UNIQUE,
  name text NOT NULL,
  address text,
  timezone text NOT NULL DEFAULT 'Asia/Jakarta',
  open_time text NOT NULL DEFAULT '06:00',
  close_time text NOT NULL DEFAULT '23:00',
  is_default boolean NOT NULL DEFAULT false,
  status "BranchStatus" NOT NULL DEFAULT 'Active',
  created_at timestamptz NOT NULL DEFAULT now(),
  updated_at timestamptz NOT NULL DEFAULT now(),
  deleted_at timestamptz,
  created_by uuid,
  updated_by uuid,
  deleted_by uuid
);

CREATE UNIQUE INDEX IF NOT EXISTS branches_one_default_idx
  ON branches (is_default) WHERE is_default = true AND deleted_at IS NULL;

INSERT INTO branches (code, name, address, timezone, open_time, close_time, is_default, status)
SELECT 'MAIN', 'Main', ss.address, 'Asia/Jakarta', '06:00', '23:00', true, 'Active'
FROM (SELECT address FROM store_settings WHERE deleted_at IS NULL LIMIT 1) ss
WHERE NOT EXISTS (SELECT 1 FROM branches WHERE code = 'MAIN');

INSERT INTO branches (code, name, timezone, open_time, close_time, is_default, status)
SELECT 'MAIN', 'Main', 'Asia/Jakarta', '06:00', '23:00', true, 'Active'
WHERE NOT EXISTS (SELECT 1 FROM branches WHERE code = 'MAIN');

-- courts
ALTER TABLE courts ADD COLUMN IF NOT EXISTS branch_id uuid REFERENCES branches(id);
UPDATE courts c SET branch_id = b.id FROM branches b WHERE b.code = 'MAIN' AND c.branch_id IS NULL;
CREATE INDEX IF NOT EXISTS courts_branch_id_idx ON courts (branch_id);

-- pos_outlets
ALTER TABLE pos_outlets ADD COLUMN IF NOT EXISTS branch_id uuid REFERENCES branches(id);
UPDATE pos_outlets o SET branch_id = b.id FROM branches b WHERE b.code = 'MAIN' AND o.branch_id IS NULL;
CREATE INDEX IF NOT EXISTS pos_outlets_branch_id_idx ON pos_outlets (branch_id);

-- time_groups
ALTER TABLE time_groups ADD COLUMN IF NOT EXISTS branch_id uuid REFERENCES branches(id);
UPDATE time_groups t SET branch_id = b.id FROM branches b WHERE b.code = 'MAIN' AND t.branch_id IS NULL;
CREATE INDEX IF NOT EXISTS time_groups_branch_id_idx ON time_groups (branch_id);

-- special_days
ALTER TABLE special_days ADD COLUMN IF NOT EXISTS branch_id uuid REFERENCES branches(id);
UPDATE special_days s SET branch_id = b.id FROM branches b WHERE b.code = 'MAIN' AND s.branch_id IS NULL;
CREATE INDEX IF NOT EXISTS special_days_branch_id_idx ON special_days (branch_id);

-- coaches
ALTER TABLE coaches ADD COLUMN IF NOT EXISTS branch_id uuid REFERENCES branches(id);
UPDATE coaches c SET branch_id = b.id FROM branches b WHERE b.code = 'MAIN' AND c.branch_id IS NULL;
CREATE INDEX IF NOT EXISTS coaches_branch_id_idx ON coaches (branch_id);

-- events (if table exists)
DO $$ BEGIN
  IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'events') THEN
    ALTER TABLE events ADD COLUMN IF NOT EXISTS branch_id uuid REFERENCES branches(id);
    UPDATE events e SET branch_id = b.id FROM branches b WHERE b.code = 'MAIN' AND e.branch_id IS NULL;
    CREATE INDEX IF NOT EXISTS events_branch_id_idx ON events (branch_id);
  END IF;
END $$;

-- branch_stocks (catalog tetap di products; stok per branch)
CREATE TABLE IF NOT EXISTS branch_stocks (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  branch_id uuid NOT NULL REFERENCES branches(id),
  product_id uuid NOT NULL REFERENCES products(id),
  stock_quantity int NOT NULL DEFAULT 0,
  reorder_level int NOT NULL DEFAULT 10,
  created_at timestamptz NOT NULL DEFAULT now(),
  updated_at timestamptz NOT NULL DEFAULT now(),
  deleted_at timestamptz,
  UNIQUE (branch_id, product_id)
);

INSERT INTO branch_stocks (branch_id, product_id, stock_quantity, reorder_level)
SELECT b.id, p.id, COALESCE(p.stock_quantity, 0), COALESCE(p.reorder_level, 10)
FROM products p
CROSS JOIN branches b
WHERE b.code = 'MAIN' AND p.deleted_at IS NULL
ON CONFLICT (branch_id, product_id) DO NOTHING;

Rollback (opsional)

DROP TABLE IF EXISTS branch_stocks;
ALTER TABLE courts DROP COLUMN IF EXISTS branch_id;
ALTER TABLE pos_outlets DROP COLUMN IF EXISTS branch_id;
ALTER TABLE time_groups DROP COLUMN IF EXISTS branch_id;
ALTER TABLE special_days DROP COLUMN IF EXISTS branch_id;
ALTER TABLE coaches DROP COLUMN IF EXISTS branch_id;
ALTER TABLE events DROP COLUMN IF EXISTS branch_id;
DROP TABLE IF EXISTS branches;
DROP TYPE IF EXISTS "BranchStatus";

Catatan

  • Dampak: mpc-be (default branch resolve), mpc-be-pos (outlet→branch schedule), mpc-admin (CRUD + switcher).
  • Member apps: omit branch_id → Main; tidak wajib diubah.
  • Setelah jalankan: pastikan tepat satu row is_default = true.