- 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 (зависимых объектов нет).
94 lines
2.8 KiB
TypeScript
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);
|
|
}
|
|
}
|