nordabiz/database/migrations/050_user_companies.sql
Maciej Pienczyn 2e6eca55e7
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
feat(multi-company): Allow users to be associated with multiple companies
Adds user_companies table with BEFORE/AFTER triggers to sync primary
company to users.company_id. Dashboard shows all user's companies with
edit buttons. Company edit routes accept optional company_id parameter.
Admin API endpoints for managing user-company associations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 19:13:10 +01:00

109 lines
3.6 KiB
PL/PgSQL

-- Migration 050: User-Company associations (multi-company support)
-- Allows one user to be associated with multiple companies
-- Maintains backward compatibility via trigger syncing users.company_id
BEGIN;
-- 1. Create user_companies table
CREATE TABLE IF NOT EXISTS user_companies (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
company_id INTEGER NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
role VARCHAR(20) NOT NULL DEFAULT 'MANAGER',
is_primary BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
UNIQUE(user_id, company_id)
);
-- 2. Indexes
CREATE INDEX IF NOT EXISTS idx_user_companies_user_id ON user_companies(user_id);
CREATE INDEX IF NOT EXISTS idx_user_companies_company_id ON user_companies(company_id);
-- No partial unique index on is_primary - enforced by BEFORE trigger instead
-- (AFTER trigger can't clear old primary before unique constraint check)
-- 3. Migrate existing data from users table
INSERT INTO user_companies (user_id, company_id, role, is_primary, created_at)
SELECT id, company_id,
COALESCE(NULLIF(company_role, 'NONE'), 'MANAGER'),
TRUE,
COALESCE(created_at, NOW())
FROM users
WHERE company_id IS NOT NULL
ON CONFLICT (user_id, company_id) DO NOTHING;
-- 4. BEFORE trigger: enforce single primary + clear others
CREATE OR REPLACE FUNCTION sync_user_primary_company()
RETURNS TRIGGER AS $$
BEGIN
-- On INSERT or UPDATE with is_primary = TRUE
IF TG_OP IN ('INSERT', 'UPDATE') AND NEW.is_primary = TRUE THEN
-- Clear other primary flags for this user BEFORE the row is written
UPDATE user_companies
SET is_primary = FALSE, updated_at = NOW()
WHERE user_id = NEW.user_id
AND id != NEW.id
AND is_primary = TRUE;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS trg_sync_user_primary_company ON user_companies;
CREATE TRIGGER trg_sync_user_primary_company
BEFORE INSERT OR UPDATE ON user_companies
FOR EACH ROW
EXECUTE FUNCTION sync_user_primary_company();
-- 5. AFTER trigger: sync to users table
CREATE OR REPLACE FUNCTION sync_user_primary_to_users()
RETURNS TRIGGER AS $$
BEGIN
-- On INSERT or UPDATE with is_primary = TRUE -> sync to users
IF TG_OP IN ('INSERT', 'UPDATE') AND NEW.is_primary = TRUE THEN
UPDATE users
SET company_id = NEW.company_id,
company_role = NEW.role
WHERE id = NEW.user_id;
END IF;
-- On DELETE of primary association
IF TG_OP = 'DELETE' AND OLD.is_primary = TRUE THEN
-- Try to promote another association to primary
UPDATE user_companies
SET is_primary = TRUE, updated_at = NOW()
WHERE id = (
SELECT id FROM user_companies
WHERE user_id = OLD.user_id AND id != OLD.id
ORDER BY created_at ASC
LIMIT 1
);
-- If no other association exists, clear users table
IF NOT FOUND THEN
UPDATE users
SET company_id = NULL, company_role = 'NONE'
WHERE id = OLD.user_id;
END IF;
END IF;
IF TG_OP = 'DELETE' THEN
RETURN OLD;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS trg_sync_user_primary_to_users ON user_companies;
CREATE TRIGGER trg_sync_user_primary_to_users
AFTER INSERT OR UPDATE OR DELETE ON user_companies
FOR EACH ROW
EXECUTE FUNCTION sync_user_primary_to_users();
-- 6. Permissions
GRANT ALL ON TABLE user_companies TO nordabiz_app;
GRANT USAGE, SELECT ON SEQUENCE user_companies_id_seq TO nordabiz_app;
COMMIT;