Files
iistwin/server/finance/views.ts
Ильяс Султанов 5ff2aaa023 fix: RAG embedding_queue ON CONFLICT + пересоздание finance views
- enqueueEmbedding: onConflictDoNothing без предиката не находил
  частичный уникальный индекс embedding_queue_pending_entity_idx
  (WHERE processed_at IS NULL) — каждая постановка в очередь индексации
  падала. Добавлен where: isNull(processedAt).
- finance ensureViews: CREATE OR REPLACE VIEW не умеет менять тип столбца
  (stavkands text↔varchar во внешней БД omegaprom) — заменено на
  DROP VIEW IF EXISTS + CREATE VIEW (зависимых объектов нет).
2026-08-06 15:10:38 +03:00

94 lines
2.8 KiB
TypeScript

import { query } from "./db-client";
import { getTableConfig } from "./table-config";
export async function ensureFinanceTables(): Promise<void> {
try {
// Overrides tables
await query(`
CREATE TABLE IF NOT EXISTS income_category_overrides (
record_id TEXT PRIMARY KEY,
category_id TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)
`);
await query(`
CREATE TABLE IF NOT EXISTS expense_category_overrides (
record_id TEXT PRIMARY KEY,
category_id TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)
`);
// Tags tables
await query(`
CREATE TABLE IF NOT EXISTS node_tags (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
color TEXT NOT NULL DEFAULT '#2563eb'
)
`);
await query(`
CREATE TABLE IF NOT EXISTS node_tag_assignments (
id SERIAL PRIMARY KEY,
node_id TEXT NOT NULL,
tag_id INTEGER NOT NULL REFERENCES node_tags(id) ON DELETE CASCADE,
context TEXT NOT NULL DEFAULT 'income',
UNIQUE(node_id, tag_id, context)
)
`);
} catch (err) {
console.error("[FINANCE] Failed to ensure metadata tables:", err);
}
}
async function tableExists(tableName: string): Promise<boolean> {
try {
const result = await query(
`SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public' AND table_name = $1
) as exists`,
[tableName]
);
return result.rows[0]?.exists === true;
} catch {
return false;
}
}
export async function ensureViews(): Promise<void> {
const config = getTableConfig();
try {
const incomeExists = await tableExists(config.incomeTable);
const expenseExists = await tableExists(config.expenseTable);
if (!incomeExists && !expenseExists) {
return;
}
if (incomeExists) {
// CREATE OR REPLACE VIEW не умеет менять типы столбцов (тип stavkands в источнике
// изменился text ↔ varchar) — пересоздаём view целиком. Зависимых объектов нет.
await query(`DROP VIEW IF EXISTS vw_income_effective`);
await query(`
CREATE VIEW vw_income_effective AS
SELECT t.*, o.category_id as _override_category_id
FROM "${config.incomeTable}" t
LEFT JOIN income_category_overrides o ON o.record_id = t.id::text
`);
}
if (expenseExists) {
await query(`DROP VIEW IF EXISTS vw_expense_effective`);
await query(`
CREATE VIEW vw_expense_effective AS
SELECT t.*, o.category_id as _override_category_id
FROM "${config.expenseTable}" t
LEFT JOIN expense_category_overrides o ON o.record_id = t.id::text
`);
}
} catch (err) {
console.error("[FINANCE] Failed to create views:", err);
}
}