panels-origin/db/core/changesets/018-schema-expenses-cost.sql
Cursor Agent ab89727522
feat: gastos, almacén central/obra y control presupuestal
Implementa el módulo integral de costos según el plan acordado:

- Esquema Liquibase: expense_entries, almacén (central + obra), IVA,
  tenant_cost_settings, permisos IAM y RLS
- Funciones RPC core: gastos, almacén, control presupuestal, sync nómina,
  cierre de almacén al concluir proyecto
- API HTTP: expenses_http, warehouse_http, cost_control_http, iam_http
- Middleware requirePermission con matriz IAM
- UI web-panel: /gastos, /almacen, /control-presupuesto, /usuarios
- Componentes BudgetItemPicker, CostSemaphore y semáforos CSS
- Smoke tests extendidos para gastos/almacén/control

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-04 04:04:17 +00:00

103 lines
5.1 KiB
SQL

--liquibase formatted sql
-- PANELS · core · gastos, IVA y configuración de control de costos
--changeset panel:core-018a-tenant-cost-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='tenant_cost_settings'
CREATE TABLE core.tenant_cost_settings (
tenant_id INTEGER PRIMARY KEY,
budget_warn_pct NUMERIC(5,2) NOT NULL DEFAULT 85,
budget_critical_pct NUMERIC(5,2) NOT NULL DEFAULT 100,
default_iva_rate NUMERIC(5,4) NOT NULL DEFAULT 0.16,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
--changeset panel:core-018b-expense-entries 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='expense_entries'
CREATE TABLE core.expense_entries (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
tenant_id INTEGER NOT NULL,
company_id BIGINT NOT NULL REFERENCES core.companies(id),
project_id BIGINT REFERENCES core.projects(id) ON DELETE SET NULL,
expense_date DATE NOT NULL,
source TEXT NOT NULL DEFAULT 'manual' CHECK (source IN ('manual', 'payroll', 'destajo')),
source_ref_id BIGINT,
description TEXT NOT NULL DEFAULT '',
supplier_name TEXT NOT NULL DEFAULT '',
supplier_rfc TEXT NOT NULL DEFAULT '',
budget_chapter_id BIGINT REFERENCES core.budget_chapters(id) ON DELETE SET NULL,
budget_item_id BIGINT REFERENCES core.budget_items(id) ON DELETE SET NULL,
quantity NUMERIC(14,4) NOT NULL DEFAULT 0,
unit TEXT NOT NULL DEFAULT '',
unit_price NUMERIC(14,4) NOT NULL DEFAULT 0,
subtotal NUMERIC(14,2) NOT NULL DEFAULT 0,
iva_rate NUMERIC(5,4) NOT NULL DEFAULT 0.16,
iva_amount NUMERIC(14,2) NOT NULL DEFAULT 0,
total NUMERIC(14,2) NOT NULL DEFAULT 0,
tax_included BOOLEAN NOT NULL DEFAULT false,
is_deductible BOOLEAN NOT NULL DEFAULT true,
counts_toward_budget BOOLEAN NOT NULL DEFAULT true,
is_locked BOOLEAN NOT NULL DEFAULT false,
status TEXT NOT NULL DEFAULT 'confirmed' CHECK (status IN ('draft', 'confirmed', 'void')),
created_by_id INTEGER,
created_by_name TEXT NOT NULL DEFAULT '',
notes TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_core_expense_entries_tenant ON core.expense_entries(tenant_id);
CREATE INDEX idx_core_expense_entries_project_date ON core.expense_entries(project_id, expense_date);
CREATE INDEX idx_core_expense_entries_budget_item ON core.expense_entries(budget_item_id);
CREATE UNIQUE INDEX idx_core_expense_entries_source_ref
ON core.expense_entries(source, source_ref_id)
WHERE source_ref_id IS NOT NULL AND status <> 'void';
--changeset panel:core-018c-expense-attachments 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='expense_attachments'
CREATE TABLE core.expense_attachments (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
expense_id BIGINT NOT NULL REFERENCES core.expense_entries(id) ON DELETE CASCADE,
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,
uploaded_by_id INTEGER,
uploaded_by_name TEXT NOT NULL DEFAULT '',
uploaded_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
--changeset panel:core-018d-iva-period-manual 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='iva_period_entries'
CREATE TABLE core.iva_period_entries (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
tenant_id INTEGER NOT NULL,
company_id BIGINT NOT NULL REFERENCES core.companies(id),
period_year INTEGER NOT NULL,
period_month INTEGER NOT NULL CHECK (period_month BETWEEN 1 AND 12),
iva_trasladado NUMERIC(14,2) NOT NULL DEFAULT 0,
notes TEXT NOT NULL DEFAULT '',
updated_by_name TEXT NOT NULL DEFAULT '',
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (tenant_id, company_id, period_year, period_month)
);
CREATE INDEX idx_core_iva_period_tenant ON core.iva_period_entries(tenant_id);
--changeset panel:core-018e-destajo-budget-item endDelimiter:; splitStatements:true
ALTER TABLE core.destajo_jobs
ADD COLUMN IF NOT EXISTS budget_item_id BIGINT REFERENCES core.budget_items(id) ON DELETE SET NULL;
--changeset panel:core-018f-warehouse-material-budget-links 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='warehouse_material_budget_links'
CREATE TABLE core.warehouse_material_budget_links (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
material_id BIGINT NOT NULL,
budget_item_id BIGINT NOT NULL REFERENCES core.budget_items(id) ON DELETE CASCADE,
conversion_factor NUMERIC(14,6) NOT NULL DEFAULT 1,
UNIQUE (material_id, budget_item_id)
);