--liquibase formatted sql -- PANELS · panels_product.core (Postgres) · baseline -- -- Negocio: empresas, personal, obras, expedientes, presupuesto, nómina, -- gafetes. Sin FK hacia iam (documents.uploaded_by / badge_jobs.created_by -- son snapshot desnormalizado, no referencia viva) ni hacia -- panels_platform.tenants (bases distintas -- tenant_id se valida en la -- capa de aplicación, igual que hoy entre app.db/platform.db). -- -- Requiere que la extensión citext ya esté instalada en la base de datos -- (ver db/provision/04-product-database.sql, corre como superuser). --changeset panel:core-001a-companies endDelimiter:; splitStatements:true --preconditions onFail:MARK_RAN --precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='core' AND table_name='companies' CREATE TABLE core.companies ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, code TEXT NOT NULL UNIQUE, name TEXT NOT NULL, parent_id INTEGER REFERENCES core.companies(id), kind TEXT NOT NULL DEFAULT 'sub' CHECK (kind IN ('principal', 'sub')), status TEXT NOT NULL DEFAULT 'activo' CHECK (status IN ('activo', 'inactivo')), tenant_id INTEGER, registro_patronal TEXT NOT NULL DEFAULT '', razon_social TEXT NOT NULL DEFAULT '', nombre_comercial TEXT NOT NULL DEFAULT '', rfc TEXT NOT NULL DEFAULT '', regimen_fiscal TEXT NOT NULL DEFAULT '', clase_riesgo TEXT NOT NULL DEFAULT '', domicilio_fiscal TEXT NOT NULL DEFAULT '', codigo_postal TEXT NOT NULL DEFAULT '', ciudad TEXT NOT NULL DEFAULT '', estado TEXT NOT NULL DEFAULT '', telefono TEXT NOT NULL DEFAULT '', email TEXT NOT NULL DEFAULT '', representante_legal TEXT NOT NULL DEFAULT '', giro TEXT NOT NULL DEFAULT '', created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX idx_core_companies_tenant ON core.companies(tenant_id); --changeset panel:core-001b-risk-levels endDelimiter:; splitStatements:true --preconditions onFail:MARK_RAN --precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='core' AND table_name='risk_levels' CREATE TABLE core.risk_levels ( code TEXT PRIMARY KEY, label TEXT NOT NULL, color TEXT NOT NULL, text_color TEXT NOT NULL DEFAULT '#FFFFFF' ); --changeset panel:core-001c-badge-themes endDelimiter:; splitStatements:true --preconditions onFail:MARK_RAN --precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='core' AND table_name='badge_themes' CREATE TABLE core.badge_themes ( id TEXT PRIMARY KEY, name TEXT NOT NULL, layout TEXT NOT NULL ); --changeset panel:core-001d-projects endDelimiter:; splitStatements:true --preconditions onFail:MARK_RAN --precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='core' AND table_name='projects' CREATE TABLE core.projects ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, code TEXT NOT NULL UNIQUE, name TEXT NOT NULL, address TEXT NOT NULL DEFAULT '', stage TEXT NOT NULL DEFAULT '', status TEXT NOT NULL DEFAULT 'activo' CHECK (status IN ('activo', 'pausado', 'concluido', 'cancelado')), theme_id TEXT NOT NULL REFERENCES core.badge_themes(id), logo_left_path TEXT, logo_right_path TEXT, company_id INTEGER REFERENCES core.companies(id), tenant_id INTEGER, contract_amount NUMERIC(14,2), start_date DATE, end_date DATE, resident_name TEXT NOT NULL DEFAULT '', siroc TEXT NOT NULL DEFAULT '', payroll_tax_pct NUMERIC(5,2) NOT NULL DEFAULT 4, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX idx_core_projects_tenant ON core.projects(tenant_id); --changeset panel:core-001e-workers endDelimiter:; splitStatements:true --preconditions onFail:MARK_RAN --precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='core' AND table_name='workers' CREATE TABLE core.workers ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, first_name TEXT NOT NULL, middle_name TEXT, last_name_p TEXT NOT NULL, last_name_m TEXT NOT NULL, curp CITEXT NOT NULL UNIQUE, rfc CITEXT NOT NULL UNIQUE, nss TEXT NOT NULL UNIQUE, phone TEXT NOT NULL, email TEXT NOT NULL, address TEXT NOT NULL, blood_type TEXT, hire_type TEXT NOT NULL, company_id INTEGER REFERENCES core.companies(id), tenant_id INTEGER, position TEXT NOT NULL, risk_code TEXT NOT NULL REFERENCES core.risk_levels(code), work_type TEXT NOT NULL CHECK (work_type IN ('N', 'D')), daily_wage NUMERIC(12,2) NOT NULL DEFAULT 0, needs_badge BOOLEAN NOT NULL DEFAULT true, status TEXT NOT NULL DEFAULT 'activo' CHECK (status IN ('activo', 'baja')), pipeline_status TEXT NOT NULL DEFAULT 'incompleto', imss_status TEXT NOT NULL DEFAULT 'sin_alta' CHECK (imss_status IN ('sin_alta', 'alta', 'baja_imss')), imss_company_id INTEGER REFERENCES core.companies(id), imss_alta_at DATE, imss_baja_at DATE, last_rehire_at DATE, vcard_password_enc TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX idx_core_workers_tenant ON core.workers(tenant_id); --changeset panel:core-001f-assignments endDelimiter:; splitStatements:true --preconditions onFail:MARK_RAN --precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='core' AND table_name='assignments' CREATE TABLE core.assignments ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, worker_id INTEGER NOT NULL REFERENCES core.workers(id) ON DELETE CASCADE, project_id INTEGER NOT NULL REFERENCES core.projects(id) ON DELETE CASCADE, active BOOLEAN NOT NULL DEFAULT true, start_date DATE NOT NULL, end_date DATE, UNIQUE (worker_id, project_id) ); --changeset panel:core-001g-document-types endDelimiter:; splitStatements:true --preconditions onFail:MARK_RAN --precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='core' AND table_name='document_types' CREATE TABLE core.document_types ( code TEXT PRIMARY KEY, label TEXT NOT NULL, required BOOLEAN NOT NULL DEFAULT true, validity_mode TEXT NOT NULL DEFAULT 'none' CHECK (validity_mode IN ('none', 'freshness', 'expiry')), freshness_days INTEGER, requires_issued_at BOOLEAN NOT NULL DEFAULT false, requires_expires_at BOOLEAN NOT NULL DEFAULT false, category TEXT NOT NULL DEFAULT 'identidad' ); --changeset panel:core-001h-documents endDelimiter:; splitStatements:true --preconditions onFail:MARK_RAN --precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='core' AND table_name='documents' --comment: uploaded_by_id/uploaded_by_name son snapshot -- sin FK hacia -- iam.users (esquemas aislados a propósito). CREATE TABLE core.documents ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, worker_id INTEGER NOT NULL REFERENCES core.workers(id) ON DELETE CASCADE, type_code TEXT NOT NULL REFERENCES core.document_types(code), original_name TEXT NOT NULL, mime TEXT NOT NULL, size_bytes BIGINT NOT NULL, sha256 TEXT NOT NULL, iv TEXT NOT NULL, storage_name TEXT NOT NULL, is_current BOOLEAN NOT NULL DEFAULT true, parse_status TEXT NOT NULL DEFAULT 'manual', issued_at DATE, expires_at DATE, imss_company_id INTEGER REFERENCES core.companies(id), imss_alta_at DATE, uploaded_by_id INTEGER, uploaded_by_name TEXT NOT NULL DEFAULT '', uploaded_at TIMESTAMPTZ NOT NULL DEFAULT now() ); --changeset panel:core-001i-project-document-types endDelimiter:; splitStatements:true --preconditions onFail:MARK_RAN --precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='core' AND table_name='project_document_types' CREATE TABLE core.project_document_types ( code TEXT PRIMARY KEY, label TEXT NOT NULL, required BOOLEAN NOT NULL DEFAULT true, category TEXT NOT NULL DEFAULT 'contrato' ); --changeset panel:core-001j-project-documents endDelimiter:; splitStatements:true --preconditions onFail:MARK_RAN --precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='core' AND table_name='project_documents' CREATE TABLE core.project_documents ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, project_id INTEGER NOT NULL REFERENCES core.projects(id) ON DELETE CASCADE, type_code TEXT NOT NULL REFERENCES core.project_document_types(code), original_name TEXT NOT NULL, mime TEXT NOT NULL, size_bytes BIGINT NOT NULL, sha256 TEXT NOT NULL, iv TEXT NOT NULL, storage_name TEXT NOT NULL, is_current BOOLEAN NOT NULL DEFAULT true, parse_status TEXT NOT NULL DEFAULT 'manual', uploaded_by_id INTEGER, uploaded_by_name TEXT NOT NULL DEFAULT '', uploaded_at TIMESTAMPTZ NOT NULL DEFAULT now() ); --changeset panel:core-001k-company-document-types endDelimiter:; splitStatements:true --preconditions onFail:MARK_RAN --precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='core' AND table_name='company_document_types' CREATE TABLE core.company_document_types ( code TEXT PRIMARY KEY, label TEXT NOT NULL, required BOOLEAN NOT NULL DEFAULT false, category TEXT NOT NULL DEFAULT 'otro' ); --changeset panel:core-001l-company-documents endDelimiter:; splitStatements:true --preconditions onFail:MARK_RAN --precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='core' AND table_name='company_documents' CREATE TABLE core.company_documents ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, company_id INTEGER NOT NULL REFERENCES core.companies(id) ON DELETE CASCADE, type_code TEXT NOT NULL REFERENCES core.company_document_types(code), original_name TEXT NOT NULL, mime TEXT NOT NULL, size_bytes BIGINT NOT NULL, sha256 TEXT NOT NULL, iv TEXT NOT NULL, storage_name TEXT NOT NULL, is_current BOOLEAN NOT NULL DEFAULT true, parse_status TEXT NOT NULL DEFAULT 'manual', uploaded_by_id INTEGER, uploaded_by_name TEXT NOT NULL DEFAULT '', uploaded_at TIMESTAMPTZ NOT NULL DEFAULT now() ); --changeset panel:core-001m-badge-jobs endDelimiter:; splitStatements:true --preconditions onFail:MARK_RAN --precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='core' AND table_name='badge_jobs' CREATE TABLE core.badge_jobs ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, project_id INTEGER NOT NULL REFERENCES core.projects(id), status TEXT NOT NULL DEFAULT 'done', pdf_path TEXT, created_by_id INTEGER, created_by_name TEXT NOT NULL DEFAULT '', created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); --changeset panel:core-001n-badge-job-people endDelimiter:; splitStatements:true --preconditions onFail:MARK_RAN --precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='core' AND table_name='badge_job_people' CREATE TABLE core.badge_job_people ( job_id INTEGER NOT NULL REFERENCES core.badge_jobs(id) ON DELETE CASCADE, worker_id INTEGER NOT NULL REFERENCES core.workers(id), delivered BOOLEAN NOT NULL DEFAULT false, delivered_at TIMESTAMPTZ, PRIMARY KEY (job_id, worker_id) ); --changeset panel:core-001o-loans endDelimiter:; splitStatements:true --preconditions onFail:MARK_RAN --precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='core' AND table_name='loans' CREATE TABLE core.loans ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, worker_id INTEGER NOT NULL REFERENCES core.workers(id) ON DELETE CASCADE, amount NUMERIC(12,2) NOT NULL, delivered NUMERIC(12,2) NOT NULL DEFAULT 0, balance NUMERIC(12,2) NOT NULL, weekly_payment NUMERIC(12,2) NOT NULL, note TEXT, commission_pct NUMERIC(5,2) NOT NULL DEFAULT 0, commission_amount NUMERIC(12,2) NOT NULL DEFAULT 0, plan TEXT NOT NULL DEFAULT 'single', installments_n INTEGER NOT NULL DEFAULT 1, first_due DATE, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); --changeset panel:core-001p-attendance endDelimiter:; splitStatements:true --preconditions onFail:MARK_RAN --precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='core' AND table_name='attendance' CREATE TABLE core.attendance ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, worker_id INTEGER NOT NULL REFERENCES core.workers(id) ON DELETE CASCADE, project_id INTEGER NOT NULL REFERENCES core.projects(id) ON DELETE CASCADE, work_date DATE NOT NULL, present BOOLEAN NOT NULL DEFAULT true, UNIQUE (worker_id, project_id, work_date) ); --changeset panel:core-001q-payroll-periods endDelimiter:; splitStatements:true --preconditions onFail:MARK_RAN --precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='core' AND table_name='payroll_periods' --comment: Nómina simple por proyecto (legacy, sigue en uso via payroll_http.ts). CREATE TABLE core.payroll_periods ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, project_id INTEGER NOT NULL REFERENCES core.projects(id), week_start DATE NOT NULL, week_end DATE NOT NULL, status TEXT NOT NULL DEFAULT 'draft', created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); --changeset panel:core-001r-payroll-lines endDelimiter:; splitStatements:true --preconditions onFail:MARK_RAN --precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='core' AND table_name='payroll_lines' CREATE TABLE core.payroll_lines ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, period_id INTEGER NOT NULL REFERENCES core.payroll_periods(id) ON DELETE CASCADE, worker_id INTEGER NOT NULL REFERENCES core.workers(id), days NUMERIC(5,2) NOT NULL DEFAULT 0, daily_wage NUMERIC(12,2) NOT NULL, gross NUMERIC(12,2) NOT NULL, discounts NUMERIC(12,2) NOT NULL DEFAULT 0, loan_payment NUMERIC(12,2) NOT NULL DEFAULT 0, net NUMERIC(12,2) NOT NULL ); --changeset panel:core-001s-payroll-settings endDelimiter:; splitStatements:true --preconditions onFail:MARK_RAN --precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='core' AND table_name='payroll_settings' CREATE TABLE core.payroll_settings ( tenant_id INTEGER PRIMARY KEY, loan_commission_enabled BOOLEAN NOT NULL DEFAULT true, loan_commission_pct NUMERIC(5,2) NOT NULL DEFAULT 10, loan_small_max NUMERIC(12,2) NOT NULL DEFAULT 500 ); --changeset panel:core-001t-payroll-weeks endDelimiter:; splitStatements:true --preconditions onFail:MARK_RAN --precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='core' AND table_name='payroll_weeks' CREATE TABLE core.payroll_weeks ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, week_start DATE NOT NULL, week_end DATE NOT NULL, status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'assembled', 'paid')), required_net NUMERIC(14,2) NOT NULL DEFAULT 0, payable_net NUMERIC(14,2) NOT NULL DEFAULT 0, assembled_at TIMESTAMPTZ, paid_at TIMESTAMPTZ, tenant_id INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE (tenant_id, week_start) ); CREATE INDEX idx_core_payroll_weeks_tenant ON core.payroll_weeks(tenant_id); --changeset panel:core-001u-payroll-sheets endDelimiter:; splitStatements:true --preconditions onFail:MARK_RAN --precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='core' AND table_name='payroll_sheets' CREATE TABLE core.payroll_sheets ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, week_id INTEGER NOT NULL REFERENCES core.payroll_weeks(id) ON DELETE CASCADE, kind TEXT NOT NULL CHECK (kind IN ('obra', 'destajo', 'admin')), project_id INTEGER REFERENCES core.projects(id) ); CREATE UNIQUE INDEX idx_core_payroll_sheets_week_kind_project ON core.payroll_sheets(week_id, kind, COALESCE(project_id, 0)); --changeset panel:core-001v-payroll-week-lines endDelimiter:; splitStatements:true --preconditions onFail:MARK_RAN --precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='core' AND table_name='payroll_week_lines' CREATE TABLE core.payroll_week_lines ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, sheet_id INTEGER NOT NULL REFERENCES core.payroll_sheets(id) ON DELETE CASCADE, worker_id INTEGER NOT NULL REFERENCES core.workers(id), destajo_cut_line_id INTEGER, project_id INTEGER REFERENCES core.projects(id), days NUMERIC(5,2) NOT NULL DEFAULT 0, daily_wage NUMERIC(12,2) NOT NULL DEFAULT 0, qty_planned NUMERIC(14,4) NOT NULL DEFAULT 0, qty_actual NUMERIC(14,4) NOT NULL DEFAULT 0, qty_extra NUMERIC(14,4) NOT NULL DEFAULT 0, unit_price NUMERIC(14,4) NOT NULL DEFAULT 0, unit_code TEXT, concepto TEXT, amount NUMERIC(14,2) NOT NULL DEFAULT 0, gross NUMERIC(14,2) NOT NULL DEFAULT 0, discounts NUMERIC(14,2) NOT NULL DEFAULT 0, loan_id INTEGER REFERENCES core.loans(id), loan_discount NUMERIC(14,2) NOT NULL DEFAULT 0, loan_label TEXT, required_net NUMERIC(14,2) NOT NULL DEFAULT 0, payable_net NUMERIC(14,2) NOT NULL DEFAULT 0 ); --changeset panel:core-001w-destajo-units endDelimiter:; splitStatements:true --preconditions onFail:MARK_RAN --precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='core' AND table_name='destajo_units' CREATE TABLE core.destajo_units ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, tenant_id INTEGER NOT NULL DEFAULT 0, code TEXT NOT NULL, label TEXT NOT NULL, UNIQUE (tenant_id, code) ); CREATE INDEX idx_core_destajo_units_tenant ON core.destajo_units(tenant_id); --changeset panel:core-001x-destajo-periods endDelimiter:; splitStatements:true --preconditions onFail:MARK_RAN --precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='core' AND table_name='destajo_periods' CREATE TABLE core.destajo_periods ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, period_start DATE NOT NULL, period_end DATE NOT NULL, week_id INTEGER NOT NULL REFERENCES core.payroll_weeks(id) ON DELETE CASCADE, tenant_id INTEGER NOT NULL DEFAULT 0, UNIQUE (tenant_id, period_end) ); CREATE INDEX idx_core_destajo_periods_tenant ON core.destajo_periods(tenant_id); --changeset panel:core-001y-destajo-jobs endDelimiter:; splitStatements:true --preconditions onFail:MARK_RAN --precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='core' AND table_name='destajo_jobs' CREATE TABLE core.destajo_jobs ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, project_id INTEGER NOT NULL REFERENCES core.projects(id), worker_id INTEGER NOT NULL REFERENCES core.workers(id), concepto TEXT NOT NULL, unit_code TEXT NOT NULL, qty_total_estimated NUMERIC(14,4) NOT NULL, unit_price NUMERIC(14,4) NOT NULL, status TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open', 'done')), tenant_id INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX idx_core_destajo_jobs_tenant ON core.destajo_jobs(tenant_id); --changeset panel:core-001z-destajo-cut-lines endDelimiter:; splitStatements:true --preconditions onFail:MARK_RAN --precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='core' AND table_name='destajo_cut_lines' CREATE TABLE core.destajo_cut_lines ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, period_id INTEGER NOT NULL REFERENCES core.destajo_periods(id) ON DELETE CASCADE, job_id INTEGER NOT NULL REFERENCES core.destajo_jobs(id) ON DELETE CASCADE, qty_planned NUMERIC(14,4) NOT NULL DEFAULT 0, qty_actual NUMERIC(14,4) NOT NULL DEFAULT 0, qty_extra NUMERIC(14,4) NOT NULL DEFAULT 0, UNIQUE (period_id, job_id) ); --changeset panel:core-001aa-loan-payments endDelimiter:; splitStatements:true --preconditions onFail:MARK_RAN --precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='core' AND table_name='loan_payments' CREATE TABLE core.loan_payments ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, loan_id INTEGER NOT NULL REFERENCES core.loans(id) ON DELETE CASCADE, week_id INTEGER NOT NULL REFERENCES core.payroll_weeks(id), amount NUMERIC(12,2) NOT NULL, installment_n INTEGER NOT NULL DEFAULT 1, label TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); --changeset panel:core-001ab-budget-chapters endDelimiter:; splitStatements:true --preconditions onFail:MARK_RAN --precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='core' AND table_name='budget_chapters' CREATE TABLE core.budget_chapters ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, project_id INTEGER NOT NULL REFERENCES core.projects(id) ON DELETE CASCADE, parent_id INTEGER REFERENCES core.budget_chapters(id) ON DELETE SET NULL, code TEXT NOT NULL DEFAULT '', name TEXT NOT NULL, wbs TEXT NOT NULL DEFAULT '', sort_order INTEGER NOT NULL DEFAULT 0 ); --changeset panel:core-001ac-budget-items endDelimiter:; splitStatements:true --preconditions onFail:MARK_RAN --precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='core' AND table_name='budget_items' CREATE TABLE core.budget_items ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, project_id INTEGER NOT NULL REFERENCES core.projects(id) ON DELETE CASCADE, chapter_id INTEGER REFERENCES core.budget_chapters(id) ON DELETE SET NULL, code TEXT NOT NULL DEFAULT '', description TEXT NOT NULL, unit TEXT NOT NULL DEFAULT '', quantity NUMERIC(14,4) NOT NULL DEFAULT 0, unit_price NUMERIC(14,4) NOT NULL DEFAULT 0, amount NUMERIC(14,2) NOT NULL DEFAULT 0, wbs TEXT NOT NULL DEFAULT '', sort_order INTEGER NOT NULL DEFAULT 0 );