From a8e4b3400e49f35b4f437b4ac3ffce5ced873519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=A0=E7=9A=84=E7=94=A8=E6=88=B7=E5=90=8D?= <你的邮箱> Date: Thu, 6 Nov 2025 23:16:58 +0800 Subject: [PATCH] fix: create schema before postgres import --- apps/backend/scripts/import-finance-data.js | 179 +++++++++++++++++++- 1 file changed, 172 insertions(+), 7 deletions(-) diff --git a/apps/backend/scripts/import-finance-data.js b/apps/backend/scripts/import-finance-data.js index df2e3299..575a6a51 100644 --- a/apps/backend/scripts/import-finance-data.js +++ b/apps/backend/scripts/import-finance-data.js @@ -231,6 +231,7 @@ function normalizeDate(rawValue, monthTracker, baseYear) { } async function resetFinanceTables(client) { + await ensureSchema(client); await client.query(` TRUNCATE TABLE finance_transactions, @@ -258,6 +259,165 @@ async function ensureCurrency(client, cache, code, name = code, symbol = code) { cache.currencies.add(code); } +async function ensureSchema(client) { + await client.query(` + CREATE TABLE IF NOT EXISTS finance_currencies ( + code TEXT PRIMARY KEY, + name TEXT NOT NULL, + symbol TEXT NOT NULL, + is_base BOOLEAN NOT NULL DEFAULT FALSE, + is_active BOOLEAN NOT NULL DEFAULT TRUE + ); + `); + + await client.query(` + CREATE TABLE IF NOT EXISTS finance_exchange_rates ( + id SERIAL PRIMARY KEY, + from_currency TEXT NOT NULL REFERENCES finance_currencies(code), + to_currency TEXT NOT NULL REFERENCES finance_currencies(code), + rate NUMERIC NOT NULL, + date DATE NOT NULL, + source TEXT DEFAULT 'manual' + ); + `); + + await client.query(` + CREATE TABLE IF NOT EXISTS finance_accounts ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + currency TEXT NOT NULL REFERENCES finance_currencies(code), + type TEXT DEFAULT 'cash', + icon TEXT, + color TEXT, + user_id INTEGER DEFAULT 1, + is_active BOOLEAN DEFAULT TRUE + ); + `); + + await client.query(` + CREATE TABLE IF NOT EXISTS finance_categories ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + type TEXT NOT NULL, + icon TEXT, + color TEXT, + user_id INTEGER, + is_active BOOLEAN DEFAULT TRUE + ); + `); + + await client.query(` + CREATE TABLE IF NOT EXISTS finance_transactions ( + id SERIAL PRIMARY KEY, + type TEXT NOT NULL, + amount NUMERIC NOT NULL, + currency TEXT NOT NULL REFERENCES finance_currencies(code), + exchange_rate_to_base NUMERIC NOT NULL, + amount_in_base NUMERIC NOT NULL, + category_id INTEGER REFERENCES finance_categories(id), + account_id INTEGER REFERENCES finance_accounts(id), + transaction_date DATE NOT NULL, + description TEXT, + project TEXT, + memo TEXT, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + status TEXT NOT NULL DEFAULT 'approved', + status_updated_at TIMESTAMP WITH TIME ZONE, + reimbursement_batch TEXT, + review_notes TEXT, + submitted_by TEXT, + approved_by TEXT, + approved_at TIMESTAMP WITH TIME ZONE, + is_deleted BOOLEAN NOT NULL DEFAULT FALSE, + deleted_at TIMESTAMP WITH TIME ZONE + ); + `); + + await client.query(` + CREATE TABLE IF NOT EXISTS finance_media_messages ( + id SERIAL PRIMARY KEY, + chat_id BIGINT NOT NULL, + message_id BIGINT NOT NULL, + user_id INTEGER NOT NULL, + username TEXT, + display_name TEXT, + file_type TEXT NOT NULL, + file_id TEXT NOT NULL, + file_unique_id TEXT, + caption TEXT, + file_name TEXT, + file_path TEXT NOT NULL, + file_size INTEGER, + mime_type TEXT, + duration INTEGER, + width INTEGER, + height INTEGER, + forwarded_to INTEGER, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + UNIQUE(chat_id, message_id) + ); + `); + + await client.query(` + CREATE TABLE IF NOT EXISTS telegram_notification_configs ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + bot_token TEXT NOT NULL, + chat_id TEXT NOT NULL, + notification_types TEXT NOT NULL, + is_enabled BOOLEAN NOT NULL DEFAULT TRUE, + priority TEXT DEFAULT 'normal', + rate_limit_seconds INTEGER DEFAULT 0, + batch_enabled BOOLEAN DEFAULT FALSE, + batch_interval_minutes INTEGER DEFAULT 60, + retry_enabled BOOLEAN DEFAULT TRUE, + retry_max_attempts INTEGER DEFAULT 3, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() + ); + `); + + await client.query(` + CREATE TABLE IF NOT EXISTS telegram_notification_history ( + id SERIAL PRIMARY KEY, + config_id INTEGER NOT NULL REFERENCES telegram_notification_configs(id), + notification_type TEXT NOT NULL, + content_hash TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'pending', + retry_count INTEGER DEFAULT 0, + sent_at TIMESTAMP WITH TIME ZONE, + error_message TEXT, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() + ); + `); + + await client.query(` + CREATE INDEX IF NOT EXISTS idx_finance_media_messages_created_at + ON finance_media_messages (created_at DESC); + `); + await client.query(` + CREATE INDEX IF NOT EXISTS idx_finance_media_messages_user_id + ON finance_media_messages (user_id); + `); + await client.query(` + CREATE INDEX IF NOT EXISTS idx_telegram_notification_configs_enabled + ON telegram_notification_configs (is_enabled); + `); + await client.query(` + CREATE INDEX IF NOT EXISTS idx_telegram_notification_history_config + ON telegram_notification_history (config_id, created_at DESC); + `); + await client.query(` + CREATE INDEX IF NOT EXISTS idx_telegram_notification_history_hash + ON telegram_notification_history (content_hash, created_at DESC); + `); + await client.query(` + CREATE INDEX IF NOT EXISTS idx_telegram_notification_history_status + ON telegram_notification_history (status, retry_count); + `); +} + async function ensureExchangeRate( client, cache, @@ -463,6 +623,17 @@ async function importNewFormat(client, header, rows, cache) { throw new Error('CSV 表头缺少必需字段,无法导入新版格式数据'); } + await ensureCurrency(client, cache, 'CNY', '人民币', '¥'); + await ensureExchangeRate( + client, + cache, + 'CNY', + 'CNY', + 1, + '1970-01-01', + 'system', + ); + const transactions = []; for (const columns of rows) { @@ -487,13 +658,7 @@ async function importNewFormat(client, header, rows, cache) { const accountIcon = resolveCurrencyIcon(currency); const accountColor = resolveCurrencyColor(currency); - await ensureCurrency( - client, - cache, - currency, - currencyName, - currencySymbol, - ); + await ensureCurrency(client, cache, currency, currencyName, currencySymbol); await ensureExchangeRate(client, cache, currency, 'CNY', 1, date); const accountName = columns[indexMap.account]?.trim() || '默认账户';