From 375482e654a01615683867e732ac9e458c7cbe74 Mon Sep 17 00:00:00 2001 From: Lucas Faria Mendes Date: Sun, 16 Aug 2026 20:17:42 -0300 Subject: feat: upload yaba src Signed-off-by: Lucas Faria Mendes --- api/src/db/schema/incomes.ts | 51 ++++++++++++++++++++++++ api/src/db/schema/index.ts | 11 ++++++ api/src/db/schema/one-time-expenses.ts | 53 +++++++++++++++++++++++++ api/src/db/schema/recurring-expenses.ts | 69 +++++++++++++++++++++++++++++++++ api/src/db/schema/users.ts | 37 ++++++++++++++++++ 5 files changed, 221 insertions(+) create mode 100644 api/src/db/schema/incomes.ts create mode 100644 api/src/db/schema/index.ts create mode 100644 api/src/db/schema/one-time-expenses.ts create mode 100644 api/src/db/schema/recurring-expenses.ts create mode 100644 api/src/db/schema/users.ts (limited to 'api/src/db/schema') diff --git a/api/src/db/schema/incomes.ts b/api/src/db/schema/incomes.ts new file mode 100644 index 0000000..f502cb9 --- /dev/null +++ b/api/src/db/schema/incomes.ts @@ -0,0 +1,51 @@ +import { sql } from 'drizzle-orm'; +import { integer, real, sqliteTable, text } from 'drizzle-orm/sqlite-core'; +import { users } from './users'; + +/** + * Tabela de rendas/salários. + * Armazena o histórico de salários por mês/ano, permitindo que o usuário + * tenha registros diferentes para cada período (ex: aumento de salário em julho). + */ +export const incomes = sqliteTable('incomes', { + id: integer('id').primaryKey({ autoIncrement: true }), + + user_id: integer('user_id') + .notNull() + .references(() => users.id, { onDelete: 'cascade' }), + + /** Salário bruto do período (em R$) */ + gross_salary: real('gross_salary').notNull(), + + /** + * Salário líquido calculado automaticamente pelo app (INSS + IRRF). + * Armazenado para fins de histórico e auditoria. + */ + calculated_net_salary: real('calculated_net_salary').notNull(), + + /** + * Salário líquido efetivo usado no cálculo do saldo. + * Pode diferir do calculado caso o usuário tenha feito um override manual. + */ + effective_net_salary: real('effective_net_salary').notNull(), + + /** Mês de referência (1-12) */ + reference_month: integer('reference_month').notNull(), + + /** Ano de referência (ex: 2025) */ + reference_year: integer('reference_year').notNull(), + + /** Observações opcionais (ex: "13º salário", "PLR") */ + description: text('description'), + + created_at: text('created_at') + .notNull() + .default(sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`), + + updated_at: text('updated_at') + .notNull() + .default(sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`), +}); + +export type Income = typeof incomes.$inferSelect; +export type NewIncome = typeof incomes.$inferInsert; diff --git a/api/src/db/schema/index.ts b/api/src/db/schema/index.ts new file mode 100644 index 0000000..72cf261 --- /dev/null +++ b/api/src/db/schema/index.ts @@ -0,0 +1,11 @@ +/** + * Ponto central de exportação de todos os schemas do Drizzle. + * Importe sempre a partir deste arquivo para evitar imports circulares. + * + * Uso: + * import { users, incomes, recurringExpenses, oneTimeExpenses } from '@/db/schema'; + */ +export * from './users'; +export * from './incomes'; +export * from './recurring-expenses'; +export * from './one-time-expenses'; diff --git a/api/src/db/schema/one-time-expenses.ts b/api/src/db/schema/one-time-expenses.ts new file mode 100644 index 0000000..10c9c01 --- /dev/null +++ b/api/src/db/schema/one-time-expenses.ts @@ -0,0 +1,53 @@ +import { sql } from 'drizzle-orm'; +import { integer, real, sqliteTable, text } from 'drizzle-orm/sqlite-core'; +import { users } from './users'; + +/** + * Tabela de gastos avulsos (despesas pontuais). + * Ex: Cinema, iFood, Uber, Farmácia. + * Cada registro representa um gasto único em uma data específica. + * O valor é subtraído do saldo disponível do mês em que a despesa ocorreu. + */ +export const oneTimeExpenses = sqliteTable('one_time_expenses', { + id: integer('id').primaryKey({ autoIncrement: true }), + + user_id: integer('user_id') + .notNull() + .references(() => users.id, { onDelete: 'cascade' }), + + /** Nome/descrição do gasto (ex: "Cinema com a família", "iFood jantar") */ + name: text('name').notNull(), + + /** Valor do gasto (em R$) */ + amount: real('amount').notNull(), + + /** + * Categoria do gasto para organização e futuros relatórios. + * Ex: "alimentação", "lazer", "saúde", "transporte", "educação", "outros" + */ + category: text('category').notNull().default('outros'), + + /** Tipo do lançamento: 'expense' (despesa) ou 'income' (receita) */ + type: text('type').notNull().default('expense'), + + /** + * Data em que o gasto ocorreu. + * Formato ISO 8601 (YYYY-MM-DD). Usado para atribuir o gasto ao mês correto. + * Default: data atual no momento da inserção. + */ + expense_date: text('expense_date').notNull().default(sql`(date('now'))`), + + /** Observações opcionais (ex: "Split 50/50 com João") */ + notes: text('notes'), + + created_at: text('created_at') + .notNull() + .default(sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`), + + updated_at: text('updated_at') + .notNull() + .default(sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`), +}); + +export type OneTimeExpense = typeof oneTimeExpenses.$inferSelect; +export type NewOneTimeExpense = typeof oneTimeExpenses.$inferInsert; diff --git a/api/src/db/schema/recurring-expenses.ts b/api/src/db/schema/recurring-expenses.ts new file mode 100644 index 0000000..8a0a1df --- /dev/null +++ b/api/src/db/schema/recurring-expenses.ts @@ -0,0 +1,69 @@ +import { sql } from 'drizzle-orm'; +import { integer, real, sqliteTable, text } from 'drizzle-orm/sqlite-core'; +import { users } from './users'; + +/** + * Tabela de gastos recorrentes (despesas fixas mensais). + * Ex: Aluguel, Internet, Streaming, Academia. + * Estes gastos são deduzidos automaticamente do saldo todo mês, + * sem precisar recadastrar. + */ +export const recurringExpenses = sqliteTable('recurring_expenses', { + id: integer('id').primaryKey({ autoIncrement: true }), + + user_id: integer('user_id') + .notNull() + .references(() => users.id, { onDelete: 'cascade' }), + + /** Nome/descrição do gasto (ex: "Aluguel", "Netflix") */ + name: text('name').notNull(), + + /** Valor mensal do gasto (em R$) */ + amount: real('amount').notNull(), + + /** + * Categoria do gasto para organização e futuros relatórios. + * Ex: "moradia", "alimentação", "lazer", "saúde", "transporte", "educação", "outros" + */ + category: text('category').notNull().default('outros'), + + /** Tipo do lançamento: 'expense' (despesa) ou 'income' (receita) */ + type: text('type').notNull().default('expense'), + + /** + * Dia do mês em que a despesa costuma vencer (1-31). + * Opcional — usado apenas para informação ao usuário. + */ + due_day: integer('due_day'), + + /** + * Indica se o gasto está ativo. + * Quando false, não é deduzido do saldo (útil para pausar sem deletar). + * Armazenado como integer (0 = false, 1 = true) por limitação do SQLite. + */ + is_active: integer('is_active', { mode: 'boolean' }).notNull().default(true), + + /** + * Data a partir da qual este gasto passa a ser contabilizado. + * Formato ISO 8601 (YYYY-MM-DD). + */ + starts_at: text('starts_at').notNull().default(sql`(date('now'))`), + + /** + * Data em que este gasto deixa de ser contabilizado (opcional). + * Útil para despesas com prazo definido (ex: parcelamento de 12x). + * Formato ISO 8601 (YYYY-MM-DD). + */ + ends_at: text('ends_at'), + + created_at: text('created_at') + .notNull() + .default(sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`), + + updated_at: text('updated_at') + .notNull() + .default(sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`), +}); + +export type RecurringExpense = typeof recurringExpenses.$inferSelect; +export type NewRecurringExpense = typeof recurringExpenses.$inferInsert; diff --git a/api/src/db/schema/users.ts b/api/src/db/schema/users.ts new file mode 100644 index 0000000..636ad2d --- /dev/null +++ b/api/src/db/schema/users.ts @@ -0,0 +1,37 @@ +import { sql } from 'drizzle-orm'; +import { integer, real, sqliteTable, text } from 'drizzle-orm/sqlite-core'; + +/** + * Tabela de usuários. + * Por ora, suporta um único usuário local (single-user app). + * O campo `net_salary_override` permite que o usuário edite manualmente + * o salário líquido calculado, para cobrir descontos extras (plano de saúde, VT, etc.). + */ +export const users = sqliteTable('users', { + id: integer('id').primaryKey({ autoIncrement: true }), + + name: text('name').notNull(), + email: text('email').unique().notNull(), + password_hash: text('password_hash').notNull(), + + /** Salário bruto informado pelo usuário (em R$) */ + gross_salary: real('gross_salary').notNull().default(0), + + /** + * Override manual do salário líquido. + * Quando NULL, o app usa o valor calculado automaticamente (INSS + IRRF). + * Quando preenchido, este valor é usado como base para o saldo disponível. + */ + net_salary_override: real('net_salary_override'), + + created_at: text('created_at') + .notNull() + .default(sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`), + + updated_at: text('updated_at') + .notNull() + .default(sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`), +}); + +export type User = typeof users.$inferSelect; +export type NewUser = typeof users.$inferInsert; -- cgit v1.2.3