import { MigrationInterface, QueryRunner } from 'typeorm'; const surveyPermissions = [ 'surveys.read', 'surveys.manage', 'surveys.assign', 'surveys.execute', 'surveys.read_reports', 'surveys.capture', 'surveys.review', ] as const; const rolePermissionValues = ` ('admin', 'surveys.read'), ('admin', 'surveys.manage'), ('admin', 'surveys.assign'), ('admin', 'surveys.execute'), ('admin', 'surveys.read_reports'), ('admin', 'surveys.capture'), ('admin', 'surveys.review'), ('director', 'surveys.read'), ('director', 'surveys.read_reports'), ('director', 'surveys.review'), ('supervisor', 'surveys.read'), ('supervisor', 'surveys.manage'), ('supervisor', 'surveys.assign'), ('supervisor', 'surveys.execute'), ('supervisor', 'surveys.read_reports'), ('supervisor', 'surveys.capture'), ('supervisor', 'surveys.review'), ('inspector', 'surveys.read'), ('inspector', 'surveys.execute'), ('inspector', 'surveys.read_reports'), ('inspector', 'surveys.capture'), ('auditor', 'surveys.read'), ('auditor', 'surveys.read_reports') `; function quoteIdentifier(identifier: string): string { return `"${identifier.replaceAll('"', '""')}"`; } export class F4RemoveSurveySubsystem1790000300000 implements MigrationInterface { name = 'F4RemoveSurveySubsystem1790000300000'; public async up(queryRunner: QueryRunner): Promise { await queryRunner.query(` DELETE FROM role_permissions WHERE permission_id IN ( SELECT id FROM permissions WHERE code = ANY($1::varchar[]) ) `, [surveyPermissions]); await queryRunner.query( `DELETE FROM permissions WHERE code = ANY($1::varchar[])`, [surveyPermissions], ); // Deliberadamente sin CASCADE: una dependencia externa debe detener la migración. await queryRunner.query(`DROP TABLE IF EXISTS survey_target_report_versions`); await queryRunner.query(`DROP TABLE IF EXISTS survey_target_report_media`); await queryRunner.query(`DROP TABLE IF EXISTS survey_target_reports`); await queryRunner.query(`DROP TABLE IF EXISTS survey_campaign_targets`); await queryRunner.query(`DROP TABLE IF EXISTS survey_campaigns`); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(` CREATE TABLE IF NOT EXISTS survey_campaigns ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), code varchar(80) NOT NULL UNIQUE, name varchar(200) NOT NULL, description text, status varchar(24) NOT NULL DEFAULT 'DRAFT', planned_start_at timestamptz, planned_end_at timestamptz, scope_asset_id uuid REFERENCES assets(id) ON DELETE RESTRICT, coordinator_user_id uuid REFERENCES users(id) ON DELETE SET NULL, created_by uuid REFERENCES users(id) ON DELETE SET NULL, updated_by uuid REFERENCES users(id) ON DELETE SET NULL, created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT chk_survey_campaigns_status CHECK ( status IN ('DRAFT','PLANNED','IN_PROGRESS','COMPLETED','CANCELLED') ), CONSTRAINT chk_survey_campaigns_dates CHECK ( planned_start_at IS NULL OR planned_end_at IS NULL OR planned_end_at >= planned_start_at ) ) `); await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_survey_campaigns_status ON survey_campaigns(status)`); await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_survey_campaigns_scope_asset_id ON survey_campaigns(scope_asset_id)`); await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_survey_campaigns_coordinator_user_id ON survey_campaigns(coordinator_user_id)`); await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_survey_campaigns_planned_dates ON survey_campaigns(planned_start_at,planned_end_at)`); await queryRunner.query(` CREATE TABLE IF NOT EXISTS survey_campaign_targets ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), campaign_id uuid NOT NULL REFERENCES survey_campaigns(id) ON DELETE RESTRICT, asset_id uuid NOT NULL REFERENCES assets(id) ON DELETE RESTRICT, assigned_user_id uuid REFERENCES users(id) ON DELETE SET NULL, status varchar(24) NOT NULL DEFAULT 'PENDING', due_at timestamptz, instructions text, created_by uuid REFERENCES users(id) ON DELETE SET NULL, updated_by uuid REFERENCES users(id) ON DELETE SET NULL, created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT uq_survey_campaign_targets_campaign_asset UNIQUE(campaign_id,asset_id), CONSTRAINT chk_survey_campaign_targets_status CHECK ( status IN ('PENDING','IN_PROGRESS','SUBMITTED','COMPLETED','SKIPPED') ) ) `); await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_survey_campaign_targets_campaign_status ON survey_campaign_targets(campaign_id,status)`); await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_survey_campaign_targets_asset_id ON survey_campaign_targets(asset_id)`); await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_survey_campaign_targets_assigned_user_id ON survey_campaign_targets(assigned_user_id)`); await queryRunner.query(` CREATE TABLE IF NOT EXISTS survey_target_reports ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), target_id uuid NOT NULL UNIQUE REFERENCES survey_campaign_targets(id) ON DELETE RESTRICT, outcome varchar(32), status varchar(24) NOT NULL DEFAULT 'DRAFT', observed_at timestamptz, latitude numeric(9,6), longitude numeric(9,6), accuracy_m numeric(12,3), notes text, asset_version_at_submission integer, submitted_at timestamptz, submitted_by uuid REFERENCES users(id) ON DELETE SET NULL, reviewed_at timestamptz, reviewed_by uuid REFERENCES users(id) ON DELETE SET NULL, review_notes text, created_by uuid REFERENCES users(id) ON DELETE SET NULL, updated_by uuid REFERENCES users(id) ON DELETE SET NULL, created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT chk_survey_target_reports_outcome CHECK ( outcome IS NULL OR outcome IN ('CONFIRMED','CHANGES_RECORDED','NOT_LOCATED') ), CONSTRAINT chk_survey_target_reports_status CHECK ( status IN ('DRAFT','SUBMITTED','APPROVED','REJECTED') ), CONSTRAINT chk_survey_target_reports_coordinates CHECK ( (latitude IS NULL AND longitude IS NULL) OR (latitude IS NOT NULL AND longitude IS NOT NULL AND latitude BETWEEN -90 AND 90 AND longitude BETWEEN -180 AND 180) ), CONSTRAINT chk_survey_target_reports_accuracy CHECK ( accuracy_m IS NULL OR (accuracy_m >= 0 AND latitude IS NOT NULL AND longitude IS NOT NULL) ) ) `); await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_survey_target_reports_status ON survey_target_reports(status)`); await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_survey_target_reports_submitted_by ON survey_target_reports(submitted_by)`); await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_survey_target_reports_reviewed_by ON survey_target_reports(reviewed_by)`); await queryRunner.query(` CREATE TABLE IF NOT EXISTS survey_target_report_media ( report_id uuid NOT NULL REFERENCES survey_target_reports(id) ON DELETE RESTRICT, media_id uuid NOT NULL REFERENCES asset_media(id) ON DELETE RESTRICT, included boolean NOT NULL DEFAULT true, added_by uuid REFERENCES users(id) ON DELETE SET NULL, created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY(report_id,media_id) ) `); await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_survey_target_report_media_media_id ON survey_target_report_media(media_id)`); await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_survey_target_report_media_included ON survey_target_report_media(report_id,included)`); await queryRunner.query(` CREATE TABLE IF NOT EXISTS survey_target_report_versions ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), report_id uuid NOT NULL REFERENCES survey_target_reports(id) ON DELETE RESTRICT, version_number integer NOT NULL, event varchar(24) NOT NULL, snapshot jsonb NOT NULL, actor_user_id uuid REFERENCES users(id) ON DELETE SET NULL, actor_username varchar(80), created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT uq_survey_target_report_versions_number UNIQUE(report_id,version_number), CONSTRAINT chk_survey_target_report_versions_event CHECK ( event IN ('SUBMITTED','APPROVED','REJECTED') ) ) `); await queryRunner.query(`CREATE INDEX IF NOT EXISTS idx_survey_target_report_versions_created_at ON survey_target_report_versions(created_at)`); await queryRunner.query(` INSERT INTO permissions(code,description) VALUES ('surveys.read','Consultar planificación de relevamientos'), ('surveys.manage','Crear y actualizar campañas de relevamiento'), ('surveys.assign','Asignar objetivos de relevamiento'), ('surveys.execute','Actualizar el avance de objetivos asignados'), ('surveys.read_reports','Consultar capturas y versiones de relevamientos'), ('surveys.capture','Capturar y enviar relevamientos de campo'), ('surveys.review','Aprobar o rechazar relevamientos enviados') ON CONFLICT(code) DO UPDATE SET description=EXCLUDED.description `); await queryRunner.query(` WITH mapping(role_code,permission_code) AS (VALUES ${rolePermissionValues}) INSERT INTO role_permissions(role_id,permission_id) SELECT role.id,permission.id FROM mapping JOIN roles role ON role.code=mapping.role_code JOIN permissions permission ON permission.code=mapping.permission_code ON CONFLICT(role_id,permission_id) DO NOTHING `); const appRole = process.env.DB_APP_USER; if (!appRole) throw new Error('Missing required environment variable: DB_APP_USER'); const applicationRole = quoteIdentifier(appRole); await queryRunner.query(`GRANT SELECT,INSERT,UPDATE ON TABLE survey_campaigns,survey_campaign_targets,survey_target_reports,survey_target_report_media TO ${applicationRole}`); await queryRunner.query(`GRANT SELECT,INSERT ON TABLE survey_target_report_versions TO ${applicationRole}`); await queryRunner.query(`REVOKE DELETE ON TABLE survey_campaigns,survey_campaign_targets,survey_target_reports,survey_target_report_media,survey_target_report_versions FROM ${applicationRole}`); await queryRunner.query(`REVOKE UPDATE ON TABLE survey_target_report_versions FROM ${applicationRole}`); } }