Files
dh-inspeccion-v2/api-v3/src/database/migrations/1788631200000-phase-d5-3-22-director-report-review.ts
T

144 lines
7.4 KiB
TypeScript

import { MigrationInterface, QueryRunner } from 'typeorm';
const permissions = [
['inspection_reports.revise', 'Cargar versiones corregidas del informe para revisión directiva'],
['inspection_reports.review', 'Aprobar la versión vigente del informe'],
['inspection_reports.sign_final', 'Firmar electrónicamente el informe final aprobado'],
] as const;
export class PhaseD5322DirectorReportReview1788631200000 implements MigrationInterface {
name = 'PhaseD5322DirectorReportReview1788631200000';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE inspection_reports
ADD COLUMN review_status varchar(32) NOT NULL DEFAULT 'PENDING_REVIEW',
ADD COLUMN current_revision_number integer NOT NULL DEFAULT 0,
ADD COLUMN approved_revision_id uuid,
ADD COLUMN approved_by uuid,
ADD COLUMN approved_at timestamptz,
ADD COLUMN review_note varchar(1000),
ADD COLUMN signed_at timestamptz,
ADD CONSTRAINT chk_inspection_reports_review_status CHECK (review_status IN ('PENDING_REVIEW','APPROVED','SIGNED')),
ADD CONSTRAINT chk_inspection_reports_revision_number CHECK (current_revision_number >= 0),
ADD CONSTRAINT fk_inspection_reports_approved_by FOREIGN KEY (approved_by) REFERENCES users(id) ON DELETE SET NULL
`);
await queryRunner.query(`
CREATE TABLE inspection_report_revisions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
report_id uuid NOT NULL,
revision_number integer NOT NULL,
source varchar(24) NOT NULL,
original_name varchar(255) NOT NULL,
stored_name varchar(255) NOT NULL,
mime_type varchar(120) NOT NULL,
size_bytes integer NOT NULL,
sha256 char(64) NOT NULL,
change_summary varchar(1000),
created_by uuid NOT NULL,
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_inspection_report_revisions_report FOREIGN KEY (report_id) REFERENCES inspection_reports(id) ON DELETE CASCADE,
CONSTRAINT fk_inspection_report_revisions_created_by FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE RESTRICT,
CONSTRAINT uq_inspection_report_revision_number UNIQUE (report_id, revision_number),
CONSTRAINT chk_inspection_report_revision_number CHECK (revision_number > 0),
CONSTRAINT chk_inspection_report_revision_source CHECK (source IN ('AUTO','DIRECTOR_UPLOAD')),
CONSTRAINT chk_inspection_report_revision_file CHECK (
mime_type = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
AND size_bytes > 0
AND sha256 ~ '^[0-9a-f]{64}$'
)
)
`);
await queryRunner.query(`CREATE INDEX idx_inspection_report_revisions_report_created ON inspection_report_revisions (report_id, revision_number DESC)`);
await queryRunner.query(`
INSERT INTO inspection_report_revisions (
report_id, revision_number, source, original_name, stored_name, mime_type,
size_bytes, sha256, change_summary, created_by, created_at
)
SELECT
id, 1, 'AUTO', word_original_name, word_stored_name, word_mime_type,
word_size_bytes, word_sha256, 'Versión automática inicial', generated_by,
COALESCE(word_generated_at, generated_at)
FROM inspection_reports
WHERE word_status = 'READY'
AND word_original_name IS NOT NULL
AND word_stored_name IS NOT NULL
AND word_mime_type = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
AND word_size_bytes > 0
AND word_sha256 ~ '^[0-9a-f]{64}$'
ON CONFLICT (report_id, revision_number) DO NOTHING
`);
await queryRunner.query(`
UPDATE inspection_reports report
SET current_revision_number = 1
WHERE EXISTS (
SELECT 1 FROM inspection_report_revisions revision
WHERE revision.report_id = report.id AND revision.revision_number = 1
)
`);
await queryRunner.query(`
ALTER TABLE inspection_reports
ADD CONSTRAINT fk_inspection_reports_approved_revision
FOREIGN KEY (approved_revision_id) REFERENCES inspection_report_revisions(id) ON DELETE RESTRICT
`);
await queryRunner.query(`
CREATE TABLE inspection_report_signatures (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
report_id uuid NOT NULL UNIQUE,
revision_id uuid NOT NULL UNIQUE,
signed_by uuid NOT NULL,
signed_at timestamptz NOT NULL,
confirmation_text varchar(500) NOT NULL,
signature_payload jsonb NOT NULL,
signature_sha256 char(64) NOT NULL,
created_at timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_inspection_report_signatures_report FOREIGN KEY (report_id) REFERENCES inspection_reports(id) ON DELETE CASCADE,
CONSTRAINT fk_inspection_report_signatures_revision FOREIGN KEY (revision_id) REFERENCES inspection_report_revisions(id) ON DELETE RESTRICT,
CONSTRAINT fk_inspection_report_signatures_user FOREIGN KEY (signed_by) REFERENCES users(id) ON DELETE RESTRICT,
CONSTRAINT chk_inspection_report_signature_sha CHECK (signature_sha256 ~ '^[0-9a-f]{64}$')
)
`);
await queryRunner.query(`CREATE INDEX idx_inspection_report_signatures_signed_at ON inspection_report_signatures (signed_at DESC)`);
for (const [code, description] of permissions) {
await queryRunner.query(
`INSERT INTO permissions (code, description) VALUES ($1, $2) ON CONFLICT (code) DO UPDATE SET description = EXCLUDED.description`,
[code, description],
);
}
await queryRunner.query(`
WITH mapping(role_code, permission_code) AS (VALUES
('director', 'inspection_reports.revise'),
('director', 'inspection_reports.review'),
('director', 'inspection_reports.sign_final')
)
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
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DELETE FROM role_permissions rp USING permissions p WHERE rp.permission_id = p.id AND p.code IN ('inspection_reports.revise','inspection_reports.review','inspection_reports.sign_final')`);
await queryRunner.query(`DELETE FROM permissions WHERE code IN ('inspection_reports.revise','inspection_reports.review','inspection_reports.sign_final')`);
await queryRunner.query(`DROP TABLE IF EXISTS inspection_report_signatures`);
await queryRunner.query(`ALTER TABLE inspection_reports DROP CONSTRAINT IF EXISTS fk_inspection_reports_approved_revision`);
await queryRunner.query(`DROP TABLE IF EXISTS inspection_report_revisions`);
await queryRunner.query(`
ALTER TABLE inspection_reports
DROP CONSTRAINT IF EXISTS fk_inspection_reports_approved_by,
DROP CONSTRAINT IF EXISTS chk_inspection_reports_revision_number,
DROP CONSTRAINT IF EXISTS chk_inspection_reports_review_status,
DROP COLUMN IF EXISTS signed_at,
DROP COLUMN IF EXISTS review_note,
DROP COLUMN IF EXISTS approved_at,
DROP COLUMN IF EXISTS approved_by,
DROP COLUMN IF EXISTS approved_revision_id,
DROP COLUMN IF EXISTS current_revision_number,
DROP COLUMN IF EXISTS review_status
`);
}
}