- number-поля теперь рендерятся как text + inputMode=numeric, чтобы браузер не округлял значения через input type=number - пробелы при вставке в number-поля удаляются - бэкенд нормализует значения number-полей в строку перед сохранением - добавлен хелпер normalizeFieldValueForStorage Closes: искажение расчётного счёта и других длинных числовых полей
90 lines
2.5 KiB
TypeScript
90 lines
2.5 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) {
|
|
await query(`
|
|
CREATE OR REPLACE 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(`
|
|
CREATE OR REPLACE 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);
|
|
}
|
|
}
|