import { pgTable, text, varchar, uuid, boolean, timestamp, jsonb, uniqueIndex, index, } from "drizzle-orm/pg-core"; import { sql } from "drizzle-orm"; // --------------------------------------------------------------------------- // 基础时间戳辅助 // --------------------------------------------------------------------------- export const timestamps = { createdAt: timestamp("created_at", { withTimezone: true }) .default(sql`CURRENT_TIMESTAMP`) .notNull(), updatedAt: timestamp("updated_at", { withTimezone: true }) .default(sql`CURRENT_TIMESTAMP`) .notNull(), deletedAt: timestamp("deleted_at", { withTimezone: true }), }; // --------------------------------------------------------------------------- // 1. 用户与认证表 // --------------------------------------------------------------------------- /** * 用户表:本地密码用户与 OIDC 用户统一归属于此表 */ export const users = pgTable( "users", { id: uuid("id").defaultRandom().primaryKey(), name: varchar("name", { length: 100 }).notNull(), email: varchar("email", { length: 255 }).notNull(), passwordHash: text("password_hash"), // OIDC 注册的用户可无密码 avatar: text("avatar"), isActive: boolean("is_active").default(true).notNull(), ...timestamps, }, (t) => [ uniqueIndex("users_email_unique").on(t.email), ] ); export type User = typeof users.$inferSelect; export type NewUser = typeof users.$inferInsert; /** * 用户认证授权表 (支持 OIDC / OAuth 账号关联) */ export const userAccounts = pgTable( "user_accounts", { id: uuid("id").defaultRandom().primaryKey(), userId: uuid("user_id") .references(() => users.id, { onDelete: "cascade" }) .notNull(), provider: varchar("provider", { length: 64 }).notNull(), // 如 'oidc' providerAccountId: varchar("provider_account_id", { length: 255 }).notNull(), refreshToken: text("refresh_token"), accessToken: text("access_token"), expiresAt: timestamp("expires_at", { withTimezone: true }), tokenType: varchar("token_type", { length: 64 }), scope: text("scope"), idToken: text("id_token"), ...timestamps, }, (t) => [ uniqueIndex("user_accounts_provider_account_id_unique").on( t.provider, t.providerAccountId ), index("user_accounts_user_id_idx").on(t.userId), ] ); // --------------------------------------------------------------------------- // 2. 资金账户表 (Accounts) // --------------------------------------------------------------------------- export type AccountType = "BANK" | "E_WALLET" | "CASH"; export type BalanceType = "ASSET" | "LIABILITY" | "EQUITY"; export const accounts = pgTable( "accounts", { id: uuid("id").defaultRandom().primaryKey(), userId: uuid("user_id") .references(() => users.id, { onDelete: "cascade" }) .notNull(), name: varchar("name", { length: 150 }).notNull(), accountType: varchar("account_type", { length: 32 }).$type().notNull(), balanceType: varchar("balance_type", { length: 32 }) .$type() .default("ASSET") .notNull(), primaryCurrency: varchar("primary_currency", { length: 10 }), supportedCurrencies: jsonb("supported_currencies").$type(), isActive: boolean("is_active").default(true).notNull(), remark: text("remark"), ext: text("ext"), // 银行账户特定字段 issuerName: varchar("issuer_name", { length: 100 }), accountNumber: varchar("account_number", { length: 100 }), subAccounts: jsonb("sub_accounts").$type(), // 电子钱包特定字段 platform: varchar("platform", { length: 64 }), accountId: varchar("account_id", { length: 100 }), // 现金账户特定字段 location: varchar("location", { length: 150 }), ...timestamps, }, (t) => [ index("accounts_user_id_idx").on(t.userId), ] ); export type Account = typeof accounts.$inferSelect; export type NewAccount = typeof accounts.$inferInsert; // --------------------------------------------------------------------------- // 3. 渠道表 (Channels) // --------------------------------------------------------------------------- export type ChannelType = "PAYMENT_CARD" | "E_WALLET" | "CASH" | "TRANSFER"; export type PaymentInstrumentType = "CREDIT" | "DEBIT"; export const channels = pgTable( "channels", { id: uuid("id").defaultRandom().primaryKey(), userId: uuid("user_id") .references(() => users.id, { onDelete: "cascade" }) .notNull(), channelType: varchar("channel_type", { length: 32 }).$type().notNull(), refAccounts: jsonb("ref_accounts").$type().notNull(), // 关联的账户 UUID 列表 isActive: boolean("is_active").default(true).notNull(), desc: text("desc"), ext: text("ext"), // 支付卡渠道字段 region: varchar("region", { length: 10 }), // 发卡地如 HK, CN issuerName: varchar("issuer_name", { length: 100 }), cardType: varchar("card_type", { length: 32 }).$type(), cardNumberFull: varchar("card_number_full", { length: 100 }), cardNumberSuffix: varchar("card_number_suffix", { length: 10 }), cardBrand: varchar("card_brand", { length: 32 }), // 电子钱包渠道字段 platform: varchar("platform", { length: 64 }), platformAccountId: varchar("platform_account_id", { length: 100 }), subChannel: varchar("sub_channel", { length: 64 }), subChannelType: varchar("sub_channel_type", { length: 32 }).$type(), ...timestamps, }, (t) => [ index("channels_user_id_idx").on(t.userId), ] ); export type Channel = typeof channels.$inferSelect; export type NewChannel = typeof channels.$inferInsert; // --------------------------------------------------------------------------- // 4. 交易记账表 (Transactions) // --------------------------------------------------------------------------- export type TransactionStatus = "PENDING" | "COMPLETED" | "FAILED" | "REFUNDED"; export type DcFlag = "DEBIT" | "CREDIT"; export interface FxRateItem { fromCcy: string; toCcy: string; rate: string; } export const transactions = pgTable( "transactions", { id: uuid("id").defaultRandom().primaryKey(), userId: uuid("user_id") .references(() => users.id, { onDelete: "cascade" }) .notNull(), version: varchar("version", { length: 16 }).default("0").notNull(), refTransactions: jsonb("ref_transactions").$type(), // 关联交易 UUID 列表 txnDate: timestamp("txn_date", { withTimezone: true }).notNull(), clearingDate: timestamp("clearing_date", { withTimezone: true }), postingDate: timestamp("posting_date", { withTimezone: true }), // 金额与币种 txnAmt: varchar("txn_amt", { length: 32 }).notNull(), txnCcy: varchar("txn_ccy", { length: 10 }).notNull(), postingAmt: varchar("posting_amt", { length: 32 }), postingCcy: varchar("posting_ccy", { length: 10 }), commAmt: varchar("comm_amt", { length: 32 }), commCcy: varchar("comm_ccy", { length: 10 }), surchargeAmt: varchar("surcharge_amt", { length: 32 }), surchargeCcy: varchar("surcharge_ccy", { length: 10 }), discAmt: varchar("disc_amt", { length: 32 }), discCcy: varchar("disc_ccy", { length: 10 }), fxRates: jsonb("fx_rates").$type(), dcFlag: varchar("dc_flag", { length: 16 }).$type().notNull(), refChannels: jsonb("ref_channels").$type().notNull(), // 渠道 UUID 列表 cp: varchar("cp", { length: 255 }), // 对手方 acqInst: varchar("acq_inst", { length: 150 }), clearingNetwork: varchar("clearing_network", { length: 100 }), txnSts: varchar("txn_sts", { length: 32 }) .$type() .default("COMPLETED") .notNull(), description: text("description"), memo: text("memo"), ext: text("ext"), rawDescription: text("raw_description"), rawData: jsonb("raw_data"), // 场景类型 txnScene: varchar("txn_scene", { length: 64 }).notNull(), // MISC_IN, PAYMENT, ECOM_PAYMENT, POS_PAYMENT, ATM, TRANSFER 等 merchantName: varchar("merchant_name", { length: 255 }), orderId: varchar("order_id", { length: 150 }), geo: varchar("geo", { length: 64 }), ...timestamps, }, (t) => [ index("transactions_user_id_idx").on(t.userId), index("transactions_txn_date_idx").on(t.txnDate), ] ); export type Transaction = typeof transactions.$inferSelect; export type NewTransaction = typeof transactions.$inferInsert;