diff --git a/drizzle.config.ts b/drizzle.config.ts index d01fb23..b0a6a3d 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,7 +1,7 @@ import type { Config } from "drizzle-kit"; export default { - schema: "./db/schema.ts", + schema: "./src/db/schema.ts", out: "./drizzle", dialect: "postgresql", dbCredentials: { diff --git a/drizzle/0019_parched_mephistopheles.sql b/drizzle/0019_parched_mephistopheles.sql deleted file mode 100644 index 2987dc0..0000000 --- a/drizzle/0019_parched_mephistopheles.sql +++ /dev/null @@ -1,15 +0,0 @@ -CREATE TABLE "recurring_series" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "user_id" text NOT NULL, - "status" text DEFAULT 'active' NOT NULL, - "day_of_month" smallint NOT NULL, - "last_generated_period" text NOT NULL, - "template_data" jsonb NOT NULL, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone DEFAULT now() NOT NULL -); ---> statement-breakpoint -ALTER TABLE "lancamentos" ADD COLUMN "recurring_series_id" uuid;--> statement-breakpoint -ALTER TABLE "recurring_series" ADD CONSTRAINT "recurring_series_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -CREATE INDEX "recurring_series_user_id_status_idx" ON "recurring_series" USING btree ("user_id","status");--> statement-breakpoint -ALTER TABLE "lancamentos" ADD CONSTRAINT "lancamentos_recurring_series_id_recurring_series_id_fk" FOREIGN KEY ("recurring_series_id") REFERENCES "public"."recurring_series"("id") ON DELETE set null ON UPDATE no action; \ No newline at end of file diff --git a/drizzle/meta/0019_snapshot.json b/drizzle/meta/0019_snapshot.json deleted file mode 100644 index e49e0c1..0000000 --- a/drizzle/meta/0019_snapshot.json +++ /dev/null @@ -1,2398 +0,0 @@ -{ - "id": "0d4a9f38-7438-418e-956f-a8d1cf8e8993", - "prevId": "853b7f42-7b0e-43a6-8665-918ec5ec6608", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.account": { - "name": "account", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "accountId": { - "name": "accountId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "providerId": { - "name": "providerId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "accessToken": { - "name": "accessToken", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "refreshToken": { - "name": "refreshToken", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "idToken": { - "name": "idToken", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "accessTokenExpiresAt": { - "name": "accessTokenExpiresAt", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "refreshTokenExpiresAt": { - "name": "refreshTokenExpiresAt", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "password": { - "name": "password", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "account_userId_user_id_fk": { - "name": "account_userId_user_id_fk", - "tableFrom": "account", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.anotacoes": { - "name": "anotacoes", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "titulo": { - "name": "titulo", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "descricao": { - "name": "descricao", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tipo": { - "name": "tipo", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'nota'" - }, - "tasks": { - "name": "tasks", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "arquivada": { - "name": "arquivada", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "anotacoes_user_id_user_id_fk": { - "name": "anotacoes_user_id_user_id_fk", - "tableFrom": "anotacoes", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.antecipacoes_parcelas": { - "name": "antecipacoes_parcelas", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "series_id": { - "name": "series_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "periodo_antecipacao": { - "name": "periodo_antecipacao", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "data_antecipacao": { - "name": "data_antecipacao", - "type": "date", - "primaryKey": false, - "notNull": true - }, - "parcelas_antecipadas": { - "name": "parcelas_antecipadas", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "valor_total": { - "name": "valor_total", - "type": "numeric(12, 2)", - "primaryKey": false, - "notNull": true - }, - "qtde_parcelas": { - "name": "qtde_parcelas", - "type": "smallint", - "primaryKey": false, - "notNull": true - }, - "desconto": { - "name": "desconto", - "type": "numeric(12, 2)", - "primaryKey": false, - "notNull": true, - "default": "'0'" - }, - "lancamento_id": { - "name": "lancamento_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "pagador_id": { - "name": "pagador_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "categoria_id": { - "name": "categoria_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "anotacao": { - "name": "anotacao", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "antecipacoes_parcelas_series_id_idx": { - "name": "antecipacoes_parcelas_series_id_idx", - "columns": [ - { - "expression": "series_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "antecipacoes_parcelas_user_id_idx": { - "name": "antecipacoes_parcelas_user_id_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "antecipacoes_parcelas_lancamento_id_lancamentos_id_fk": { - "name": "antecipacoes_parcelas_lancamento_id_lancamentos_id_fk", - "tableFrom": "antecipacoes_parcelas", - "tableTo": "lancamentos", - "columnsFrom": ["lancamento_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "antecipacoes_parcelas_pagador_id_pagadores_id_fk": { - "name": "antecipacoes_parcelas_pagador_id_pagadores_id_fk", - "tableFrom": "antecipacoes_parcelas", - "tableTo": "pagadores", - "columnsFrom": ["pagador_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "antecipacoes_parcelas_categoria_id_categorias_id_fk": { - "name": "antecipacoes_parcelas_categoria_id_categorias_id_fk", - "tableFrom": "antecipacoes_parcelas", - "tableTo": "categorias", - "columnsFrom": ["categoria_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "antecipacoes_parcelas_user_id_user_id_fk": { - "name": "antecipacoes_parcelas_user_id_user_id_fk", - "tableFrom": "antecipacoes_parcelas", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.cartoes": { - "name": "cartoes", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "nome": { - "name": "nome", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "dt_fechamento": { - "name": "dt_fechamento", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "dt_vencimento": { - "name": "dt_vencimento", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "anotacao": { - "name": "anotacao", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "limite": { - "name": "limite", - "type": "numeric(10, 2)", - "primaryKey": false, - "notNull": false - }, - "bandeira": { - "name": "bandeira", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "logo": { - "name": "logo", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "conta_id": { - "name": "conta_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "cartoes_user_id_status_idx": { - "name": "cartoes_user_id_status_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "cartoes_user_id_user_id_fk": { - "name": "cartoes_user_id_user_id_fk", - "tableFrom": "cartoes", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "cartoes_conta_id_contas_id_fk": { - "name": "cartoes_conta_id_contas_id_fk", - "tableFrom": "cartoes", - "tableTo": "contas", - "columnsFrom": ["conta_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.categorias": { - "name": "categorias", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "nome": { - "name": "nome", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tipo": { - "name": "tipo", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "icone": { - "name": "icone", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "categorias_user_id_type_idx": { - "name": "categorias_user_id_type_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "tipo", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "categorias_user_id_user_id_fk": { - "name": "categorias_user_id_user_id_fk", - "tableFrom": "categorias", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.compartilhamentos_pagador": { - "name": "compartilhamentos_pagador", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "pagador_id": { - "name": "pagador_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "shared_with_user_id": { - "name": "shared_with_user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "permission": { - "name": "permission", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'read'" - }, - "created_by_user_id": { - "name": "created_by_user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "compartilhamentos_pagador_unique": { - "name": "compartilhamentos_pagador_unique", - "columns": [ - { - "expression": "pagador_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "shared_with_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "compartilhamentos_pagador_pagador_id_pagadores_id_fk": { - "name": "compartilhamentos_pagador_pagador_id_pagadores_id_fk", - "tableFrom": "compartilhamentos_pagador", - "tableTo": "pagadores", - "columnsFrom": ["pagador_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "compartilhamentos_pagador_shared_with_user_id_user_id_fk": { - "name": "compartilhamentos_pagador_shared_with_user_id_user_id_fk", - "tableFrom": "compartilhamentos_pagador", - "tableTo": "user", - "columnsFrom": ["shared_with_user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "compartilhamentos_pagador_created_by_user_id_user_id_fk": { - "name": "compartilhamentos_pagador_created_by_user_id_user_id_fk", - "tableFrom": "compartilhamentos_pagador", - "tableTo": "user", - "columnsFrom": ["created_by_user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.contas": { - "name": "contas", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "nome": { - "name": "nome", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tipo_conta": { - "name": "tipo_conta", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "anotacao": { - "name": "anotacao", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "logo": { - "name": "logo", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "saldo_inicial": { - "name": "saldo_inicial", - "type": "numeric(12, 2)", - "primaryKey": false, - "notNull": true, - "default": "'0'" - }, - "excluir_do_saldo": { - "name": "excluir_do_saldo", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "excluir_saldo_inicial_receitas": { - "name": "excluir_saldo_inicial_receitas", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "contas_user_id_status_idx": { - "name": "contas_user_id_status_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "contas_user_id_user_id_fk": { - "name": "contas_user_id_user_id_fk", - "tableFrom": "contas", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.faturas": { - "name": "faturas", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "status_pagamento": { - "name": "status_pagamento", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "periodo": { - "name": "periodo", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "cartao_id": { - "name": "cartao_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "faturas_user_id_period_idx": { - "name": "faturas_user_id_period_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "periodo", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "faturas_cartao_id_period_idx": { - "name": "faturas_cartao_id_period_idx", - "columns": [ - { - "expression": "cartao_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "periodo", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "faturas_user_id_user_id_fk": { - "name": "faturas_user_id_user_id_fk", - "tableFrom": "faturas", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "faturas_cartao_id_cartoes_id_fk": { - "name": "faturas_cartao_id_cartoes_id_fk", - "tableFrom": "faturas", - "tableTo": "cartoes", - "columnsFrom": ["cartao_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.insights_salvos": { - "name": "insights_salvos", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "period": { - "name": "period", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "model_id": { - "name": "model_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "data": { - "name": "data", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "insights_salvos_user_period_idx": { - "name": "insights_salvos_user_period_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "period", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "insights_salvos_user_id_user_id_fk": { - "name": "insights_salvos_user_id_user_id_fk", - "tableFrom": "insights_salvos", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.lancamentos": { - "name": "lancamentos", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "condicao": { - "name": "condicao", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "nome": { - "name": "nome", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "forma_pagamento": { - "name": "forma_pagamento", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "anotacao": { - "name": "anotacao", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "valor": { - "name": "valor", - "type": "numeric(12, 2)", - "primaryKey": false, - "notNull": true - }, - "data_compra": { - "name": "data_compra", - "type": "date", - "primaryKey": false, - "notNull": true - }, - "tipo_transacao": { - "name": "tipo_transacao", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "qtde_parcela": { - "name": "qtde_parcela", - "type": "smallint", - "primaryKey": false, - "notNull": false - }, - "periodo": { - "name": "periodo", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "parcela_atual": { - "name": "parcela_atual", - "type": "smallint", - "primaryKey": false, - "notNull": false - }, - "qtde_recorrencia": { - "name": "qtde_recorrencia", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "data_vencimento": { - "name": "data_vencimento", - "type": "date", - "primaryKey": false, - "notNull": false - }, - "dt_pagamento_boleto": { - "name": "dt_pagamento_boleto", - "type": "date", - "primaryKey": false, - "notNull": false - }, - "realizado": { - "name": "realizado", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "dividido": { - "name": "dividido", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "antecipado": { - "name": "antecipado", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "antecipacao_id": { - "name": "antecipacao_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "cartao_id": { - "name": "cartao_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "conta_id": { - "name": "conta_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "categoria_id": { - "name": "categoria_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "pagador_id": { - "name": "pagador_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "series_id": { - "name": "series_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "transfer_id": { - "name": "transfer_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "recurring_series_id": { - "name": "recurring_series_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "lancamentos_user_id_period_idx": { - "name": "lancamentos_user_id_period_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "periodo", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "lancamentos_user_id_period_type_idx": { - "name": "lancamentos_user_id_period_type_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "periodo", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "tipo_transacao", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "lancamentos_pagador_id_period_idx": { - "name": "lancamentos_pagador_id_period_idx", - "columns": [ - { - "expression": "pagador_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "periodo", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "lancamentos_user_id_purchase_date_idx": { - "name": "lancamentos_user_id_purchase_date_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "data_compra", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "lancamentos_series_id_idx": { - "name": "lancamentos_series_id_idx", - "columns": [ - { - "expression": "series_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "lancamentos_transfer_id_idx": { - "name": "lancamentos_transfer_id_idx", - "columns": [ - { - "expression": "transfer_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "lancamentos_user_id_condition_idx": { - "name": "lancamentos_user_id_condition_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "condicao", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "lancamentos_cartao_id_period_idx": { - "name": "lancamentos_cartao_id_period_idx", - "columns": [ - { - "expression": "cartao_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "periodo", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "lancamentos_antecipacao_id_antecipacoes_parcelas_id_fk": { - "name": "lancamentos_antecipacao_id_antecipacoes_parcelas_id_fk", - "tableFrom": "lancamentos", - "tableTo": "antecipacoes_parcelas", - "columnsFrom": ["antecipacao_id"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - }, - "lancamentos_user_id_user_id_fk": { - "name": "lancamentos_user_id_user_id_fk", - "tableFrom": "lancamentos", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "lancamentos_cartao_id_cartoes_id_fk": { - "name": "lancamentos_cartao_id_cartoes_id_fk", - "tableFrom": "lancamentos", - "tableTo": "cartoes", - "columnsFrom": ["cartao_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "cascade" - }, - "lancamentos_conta_id_contas_id_fk": { - "name": "lancamentos_conta_id_contas_id_fk", - "tableFrom": "lancamentos", - "tableTo": "contas", - "columnsFrom": ["conta_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "cascade" - }, - "lancamentos_categoria_id_categorias_id_fk": { - "name": "lancamentos_categoria_id_categorias_id_fk", - "tableFrom": "lancamentos", - "tableTo": "categorias", - "columnsFrom": ["categoria_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "cascade" - }, - "lancamentos_pagador_id_pagadores_id_fk": { - "name": "lancamentos_pagador_id_pagadores_id_fk", - "tableFrom": "lancamentos", - "tableTo": "pagadores", - "columnsFrom": ["pagador_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "cascade" - }, - "lancamentos_recurring_series_id_recurring_series_id_fk": { - "name": "lancamentos_recurring_series_id_recurring_series_id_fk", - "tableFrom": "lancamentos", - "tableTo": "recurring_series", - "columnsFrom": ["recurring_series_id"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.orcamentos": { - "name": "orcamentos", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "valor": { - "name": "valor", - "type": "numeric(10, 2)", - "primaryKey": false, - "notNull": true - }, - "periodo": { - "name": "periodo", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "categoria_id": { - "name": "categoria_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "orcamentos_user_id_period_idx": { - "name": "orcamentos_user_id_period_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "periodo", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "orcamentos_user_id_user_id_fk": { - "name": "orcamentos_user_id_user_id_fk", - "tableFrom": "orcamentos", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "orcamentos_categoria_id_categorias_id_fk": { - "name": "orcamentos_categoria_id_categorias_id_fk", - "tableFrom": "orcamentos", - "tableTo": "categorias", - "columnsFrom": ["categoria_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.pagadores": { - "name": "pagadores", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "nome": { - "name": "nome", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "avatar_url": { - "name": "avatar_url", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "anotacao": { - "name": "anotacao", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "role": { - "name": "role", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "is_auto_send": { - "name": "is_auto_send", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "share_code": { - "name": "share_code", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "substr(encode(gen_random_bytes(24), 'base64'), 1, 24)" - }, - "last_mail": { - "name": "last_mail", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "pagadores_share_code_key": { - "name": "pagadores_share_code_key", - "columns": [ - { - "expression": "share_code", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "pagadores_user_id_status_idx": { - "name": "pagadores_user_id_status_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "pagadores_user_id_role_idx": { - "name": "pagadores_user_id_role_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "role", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "pagadores_user_id_user_id_fk": { - "name": "pagadores_user_id_user_id_fk", - "tableFrom": "pagadores", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.passkey": { - "name": "passkey", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "publicKey": { - "name": "publicKey", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "credentialID": { - "name": "credentialID", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "counter": { - "name": "counter", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "deviceType": { - "name": "deviceType", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "backedUp": { - "name": "backedUp", - "type": "boolean", - "primaryKey": false, - "notNull": true - }, - "transports": { - "name": "transports", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "aaguid": { - "name": "aaguid", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "passkey_userId_user_id_fk": { - "name": "passkey_userId_user_id_fk", - "tableFrom": "passkey", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.pre_lancamentos": { - "name": "pre_lancamentos", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source_app": { - "name": "source_app", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source_app_name": { - "name": "source_app_name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "original_title": { - "name": "original_title", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "original_text": { - "name": "original_text", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "notification_timestamp": { - "name": "notification_timestamp", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "parsed_name": { - "name": "parsed_name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "parsed_amount": { - "name": "parsed_amount", - "type": "numeric(12, 2)", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "lancamento_id": { - "name": "lancamento_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "processed_at": { - "name": "processed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "discarded_at": { - "name": "discarded_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "pre_lancamentos_user_id_status_idx": { - "name": "pre_lancamentos_user_id_status_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "pre_lancamentos_user_id_created_at_idx": { - "name": "pre_lancamentos_user_id_created_at_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "created_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "pre_lancamentos_user_id_user_id_fk": { - "name": "pre_lancamentos_user_id_user_id_fk", - "tableFrom": "pre_lancamentos", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "pre_lancamentos_lancamento_id_lancamentos_id_fk": { - "name": "pre_lancamentos_lancamento_id_lancamentos_id_fk", - "tableFrom": "pre_lancamentos", - "tableTo": "lancamentos", - "columnsFrom": ["lancamento_id"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.preferencias_usuario": { - "name": "preferencias_usuario", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "extrato_note_as_column": { - "name": "extrato_note_as_column", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "system_font": { - "name": "system_font", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'ai-sans'" - }, - "money_font": { - "name": "money_font", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'ai-sans'" - }, - "lancamentos_column_order": { - "name": "lancamentos_column_order", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "dashboard_widgets": { - "name": "dashboard_widgets", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "preferencias_usuario_user_id_user_id_fk": { - "name": "preferencias_usuario_user_id_user_id_fk", - "tableFrom": "preferencias_usuario", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "preferencias_usuario_user_id_unique": { - "name": "preferencias_usuario_user_id_unique", - "nullsNotDistinct": false, - "columns": ["user_id"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.recurring_series": { - "name": "recurring_series", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'active'" - }, - "day_of_month": { - "name": "day_of_month", - "type": "smallint", - "primaryKey": false, - "notNull": true - }, - "last_generated_period": { - "name": "last_generated_period", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "template_data": { - "name": "template_data", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "recurring_series_user_id_status_idx": { - "name": "recurring_series_user_id_status_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "recurring_series_user_id_user_id_fk": { - "name": "recurring_series_user_id_user_id_fk", - "tableFrom": "recurring_series", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.session": { - "name": "session", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "expiresAt": { - "name": "expiresAt", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "token": { - "name": "token", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "ipAddress": { - "name": "ipAddress", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "userAgent": { - "name": "userAgent", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "session_userId_user_id_fk": { - "name": "session_userId_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": ["userId"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "session_token_unique": { - "name": "session_token_unique", - "nullsNotDistinct": false, - "columns": ["token"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.tokens_api": { - "name": "tokens_api", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "token_hash": { - "name": "token_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "token_prefix": { - "name": "token_prefix", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "last_used_at": { - "name": "last_used_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "last_used_ip": { - "name": "last_used_ip", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "revoked_at": { - "name": "revoked_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "tokens_api_user_id_idx": { - "name": "tokens_api_user_id_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "tokens_api_token_hash_idx": { - "name": "tokens_api_token_hash_idx", - "columns": [ - { - "expression": "token_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "tokens_api_user_id_user_id_fk": { - "name": "tokens_api_user_id_user_id_fk", - "tableFrom": "tokens_api", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user": { - "name": "user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "emailVerified": { - "name": "emailVerified", - "type": "boolean", - "primaryKey": false, - "notNull": true - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "user_email_unique": { - "name": "user_email_unique", - "nullsNotDistinct": false, - "columns": ["email"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.verification": { - "name": "verification", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "identifier": { - "name": "identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "value": { - "name": "value", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expiresAt": { - "name": "expiresAt", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": {}, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 35983b6..2f900e9 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -134,13 +134,6 @@ "when": 1773020417482, "tag": "0018_rainy_epoch", "breakpoints": true - }, - { - "idx": 19, - "version": "7", - "when": 1773265586360, - "tag": "0019_parched_mephistopheles", - "breakpoints": true } ] } diff --git a/src/app/(dashboard)/dashboard/page.tsx b/src/app/(dashboard)/dashboard/page.tsx index cc9b408..9335cef 100644 --- a/src/app/(dashboard)/dashboard/page.tsx +++ b/src/app/(dashboard)/dashboard/page.tsx @@ -3,7 +3,6 @@ import { DashboardMetricsCards } from "@/features/dashboard/components/dashboard import { DashboardWelcome } from "@/features/dashboard/components/dashboard-welcome"; import { fetchDashboardData } from "@/features/dashboard/fetch-dashboard-data"; import { fetchUserDashboardPreferences } from "@/features/dashboard/preferences-queries"; -import { triggerRecurringGeneration } from "@/features/recurring/trigger-recurring-generation"; import { buildOptionSets, buildSluggedFilters, @@ -25,7 +24,6 @@ type PageProps = { export default async function Page({ searchParams }: PageProps) { const user = await getUser(); - await triggerRecurringGeneration(user.id); const resolvedSearchParams = searchParams ? await searchParams : undefined; const periodoParam = getSingleParam(resolvedSearchParams, "periodo"); const { period: selectedPeriod } = parsePeriodParam(periodoParam); diff --git a/src/app/(dashboard)/transactions/page.tsx b/src/app/(dashboard)/transactions/page.tsx index c268af0..fcc788b 100644 --- a/src/app/(dashboard)/transactions/page.tsx +++ b/src/app/(dashboard)/transactions/page.tsx @@ -1,11 +1,10 @@ -import { triggerRecurringGeneration } from "@/features/recurring/trigger-recurring-generation"; import { fetchUserPreferences } from "@/features/settings/queries"; import { TransactionsPage } from "@/features/transactions/components/page/transactions-page"; import { - buildTransactionWhere, buildOptionSets, buildSluggedFilters, buildSlugMaps, + buildTransactionWhere, extractTransactionSearchFilters, getSingleParam, mapTransactionsData, @@ -28,7 +27,6 @@ type PageProps = { export default async function Page({ searchParams }: PageProps) { const userId = await getUserId(); - await triggerRecurringGeneration(userId); const resolvedSearchParams = searchParams ? await searchParams : undefined; const periodoParamRaw = getSingleParam(resolvedSearchParams, "periodo"); diff --git a/src/db/schema.ts b/src/db/schema.ts index 447799f..b0c2f4e 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -562,55 +562,6 @@ export const installmentAnticipations = pgTable( }), ); -// ===================== RECURRING SERIES ===================== - -export type RecurringSeriesTemplate = { - name: string; - amount: string; - transactionType: string; - paymentMethod: string; - categoryId: string | null; - accountId: string | null; - cardId: string | null; - payerId: string | null; - note: string | null; - condition: string; -}; - -export const recurringSeries = pgTable( - "recurring_series", - { - id: uuid("id").primaryKey().default(sql`gen_random_uuid()`), - userId: text("user_id") - .notNull() - .references(() => user.id, { onDelete: "cascade" }), - status: text("status").notNull().default("active"), // "active" | "paused" | "cancelled" - dayOfMonth: smallint("day_of_month").notNull(), - lastGeneratedPeriod: text("last_generated_period").notNull(), // YYYY-MM - templateData: jsonb("template_data") - .notNull() - .$type(), - createdAt: timestamp("created_at", { - mode: "date", - withTimezone: true, - }) - .notNull() - .defaultNow(), - updatedAt: timestamp("updated_at", { - mode: "date", - withTimezone: true, - }) - .notNull() - .defaultNow(), - }, - (table) => ({ - userIdStatusIdx: index("recurring_series_user_id_status_idx").on( - table.userId, - table.status, - ), - }), -); - // ===================== TRANSACTIONS ===================== export const transactions = pgTable( @@ -664,10 +615,6 @@ export const transactions = pgTable( }), seriesId: uuid("series_id"), transferId: uuid("transfer_id"), - recurringSeriesId: uuid("recurring_series_id").references( - () => recurringSeries.id, - { onDelete: "set null" }, - ), }, (table) => ({ // Índice composto mais importante: userId + period (usado em quase todas as queries do dashboard) @@ -722,7 +669,6 @@ export const userRelations = relations(user, ({ many, one }) => ({ installmentAnticipations: many(installmentAnticipations), apiTokens: many(apiTokens), inboxItems: many(inboxItems), - recurringSeries: many(recurringSeries), })); export const accountRelations = relations(account, ({ one }) => ({ @@ -876,23 +822,8 @@ export const transactionsRelations = relations(transactions, ({ one }) => ({ fields: [transactions.anticipationId], references: [installmentAnticipations.id], }), - recurringSeries: one(recurringSeries, { - fields: [transactions.recurringSeriesId], - references: [recurringSeries.id], - }), })); -export const recurringSeriesRelations = relations( - recurringSeries, - ({ one, many }) => ({ - user: one(user, { - fields: [recurringSeries.userId], - references: [user.id], - }), - transactions: many(transactions), - }), -); - export const installmentAnticipationsRelations = relations( installmentAnticipations, ({ one, many }) => ({ @@ -938,5 +869,3 @@ export type ApiToken = typeof apiTokens.$inferSelect; export type NewApiToken = typeof apiTokens.$inferInsert; export type InboxItem = typeof inboxItems.$inferSelect; export type NewInboxItem = typeof inboxItems.$inferInsert; -export type RecurringSeries = typeof recurringSeries.$inferSelect; -export type NewRecurringSeries = typeof recurringSeries.$inferInsert; diff --git a/src/features/dashboard/components/recurring-series-widget.tsx b/src/features/dashboard/components/recurring-series-widget.tsx deleted file mode 100644 index e092091..0000000 --- a/src/features/dashboard/components/recurring-series-widget.tsx +++ /dev/null @@ -1,153 +0,0 @@ -"use client"; - -import { - RiPauseCircleLine, - RiPlayCircleLine, - RiRefreshLine, - RiStopCircleLine, -} from "@remixicon/react"; -import { useTransition } from "react"; -import { toast } from "sonner"; -import type { RecurringSeriesData } from "@/features/dashboard/recurring/recurring-series-queries"; -import { - cancelRecurringSeriesAction, - pauseRecurringSeriesAction, - resumeRecurringSeriesAction, -} from "@/features/recurring/actions"; -import MoneyValues from "@/shared/components/money-values"; -import { Badge } from "@/shared/components/ui/badge"; -import { Button } from "@/shared/components/ui/button"; -import { WidgetEmptyState } from "@/shared/components/widget-empty-state"; -import { formatMonthYearLabel } from "@/shared/utils/period"; - -type RecurringSeriesWidgetProps = { - data: RecurringSeriesData; -}; - -export function RecurringSeriesWidget({ data }: RecurringSeriesWidgetProps) { - const [isPending, startTransition] = useTransition(); - - if (data.series.length === 0) { - return ( - } - title="Nenhuma série recorrente" - description="Séries recorrentes aparecerão aqui quando forem criadas." - /> - ); - } - - const handlePause = (seriesId: string) => { - startTransition(async () => { - const result = await pauseRecurringSeriesAction({ seriesId }); - if (result.success) { - toast.success(result.message); - } else { - toast.error(result.error); - } - }); - }; - - const handleResume = (seriesId: string) => { - startTransition(async () => { - const result = await resumeRecurringSeriesAction({ seriesId }); - if (result.success) { - toast.success(result.message); - } else { - toast.error(result.error); - } - }); - }; - - const handleCancel = (seriesId: string) => { - if ( - !confirm( - "Tem certeza que deseja cancelar esta série recorrente? Lançamentos passados serão mantidos.", - ) - ) { - return; - } - startTransition(async () => { - const result = await cancelRecurringSeriesAction({ seriesId }); - if (result.success) { - toast.success(result.message); - } else { - toast.error(result.error); - } - }); - }; - - return ( -
-
    - {data.series.map((item) => ( -
  • -
    -
    -
    -

    - {item.name} -

    - - {item.status === "active" ? "Ativo" : "Pausado"} - -
    - -
    - -
    - - Dia {item.dayOfMonth} · {item.paymentMethod} - {item.categoryName ? ` · ${item.categoryName}` : ""} - - Próx: {formatMonthYearLabel(item.nextPeriod)} -
    - -
    - {item.status === "active" ? ( - - ) : ( - - )} - -
    -
    -
  • - ))} -
-
- ); -} diff --git a/src/features/dashboard/fetch-dashboard-data.ts b/src/features/dashboard/fetch-dashboard-data.ts index b9be2b0..bd3696b 100644 --- a/src/features/dashboard/fetch-dashboard-data.ts +++ b/src/features/dashboard/fetch-dashboard-data.ts @@ -16,7 +16,6 @@ import { fetchPaymentConditions } from "./payments/payment-conditions-queries"; import { fetchPaymentMethods } from "./payments/payment-methods-queries"; import { fetchPaymentStatus } from "./payments/payment-status-queries"; import { fetchPurchasesByCategory } from "./purchases-by-category-queries"; -import { fetchRecurringSeries } from "./recurring/recurring-series-queries"; import { fetchTopEstablishments } from "./top-establishments-queries"; async function fetchDashboardDataInternal(userId: string, period: string) { @@ -40,7 +39,6 @@ async function fetchDashboardDataInternal(userId: string, period: string) { purchasesByCategoryData, incomeByCategoryData, expensesByCategoryData, - recurringSeriesData, ] = await Promise.all([ fetchDashboardCardMetrics(userId, period), fetchDashboardAccounts(userId), @@ -61,7 +59,6 @@ async function fetchDashboardDataInternal(userId: string, period: string) { fetchPurchasesByCategory(userId, period), fetchIncomeByCategory(userId, period), fetchExpensesByCategory(userId, period), - fetchRecurringSeries(userId), ]); return { @@ -84,7 +81,6 @@ async function fetchDashboardDataInternal(userId: string, period: string) { purchasesByCategoryData, incomeByCategoryData, expensesByCategoryData, - recurringSeriesData, }; } diff --git a/src/features/dashboard/recurring/recurring-series-queries.ts b/src/features/dashboard/recurring/recurring-series-queries.ts deleted file mode 100644 index ea97571..0000000 --- a/src/features/dashboard/recurring/recurring-series-queries.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { and, eq, inArray } from "drizzle-orm"; -import type { RecurringSeriesTemplate } from "@/db/schema"; -import { categories, recurringSeries } from "@/db/schema"; -import { db } from "@/shared/lib/db"; -import { getAdminPayerId } from "@/shared/lib/payers/get-admin-id"; -import { safeToNumber as toNumber } from "@/shared/utils/number"; -import { addMonthsToPeriod } from "@/shared/utils/period"; - -export type RecurringSeriesItem = { - id: string; - name: string; - amount: number; - categoryName: string | null; - categoryIcon: string | null; - paymentMethod: string; - dayOfMonth: number; - status: "active" | "paused" | "cancelled"; - nextPeriod: string; - lastGeneratedPeriod: string; -}; - -export type RecurringSeriesData = { - series: RecurringSeriesItem[]; -}; - -export async function fetchRecurringSeries( - userId: string, -): Promise { - const adminPayerId = await getAdminPayerId(userId); - - const rows = await db - .select({ - id: recurringSeries.id, - status: recurringSeries.status, - dayOfMonth: recurringSeries.dayOfMonth, - lastGeneratedPeriod: recurringSeries.lastGeneratedPeriod, - templateData: recurringSeries.templateData, - }) - .from(recurringSeries) - .where( - and( - eq(recurringSeries.userId, userId), - inArray(recurringSeries.status, ["active", "paused"]), - ), - ); - - if (rows.length === 0) { - return { series: [] }; - } - - // Fetch category names for all series in one query - const categoryIds = rows - .map((r) => (r.templateData as RecurringSeriesTemplate).categoryId) - .filter((id): id is string => id !== null); - - const categoryMap = new Map(); - if (categoryIds.length > 0) { - const cats = await db - .select({ - id: categories.id, - name: categories.name, - icon: categories.icon, - }) - .from(categories) - .where(inArray(categories.id, categoryIds)); - for (const cat of cats) { - categoryMap.set(cat.id, { name: cat.name, icon: cat.icon }); - } - } - - const series = rows - .filter((row) => { - // If admin pagador exists, only show series belonging to admin - if (!adminPayerId) return true; - const template = row.templateData as RecurringSeriesTemplate; - return template.payerId === adminPayerId || template.payerId === null; - }) - .map((row): RecurringSeriesItem => { - const template = row.templateData as RecurringSeriesTemplate; - const category = template.categoryId - ? categoryMap.get(template.categoryId) - : null; - return { - id: row.id, - name: template.name, - amount: Math.abs(toNumber(template.amount)), - categoryName: category?.name ?? null, - categoryIcon: category?.icon ?? null, - paymentMethod: template.paymentMethod, - dayOfMonth: row.dayOfMonth, - status: row.status as "active" | "paused", - nextPeriod: addMonthsToPeriod(row.lastGeneratedPeriod, 1), - lastGeneratedPeriod: row.lastGeneratedPeriod, - }; - }); - - return { series }; -} diff --git a/src/features/dashboard/widgets/widgets-config.tsx b/src/features/dashboard/widgets/widgets-config.tsx index 38fbf72..60c7200 100644 --- a/src/features/dashboard/widgets/widgets-config.tsx +++ b/src/features/dashboard/widgets/widgets-config.tsx @@ -30,7 +30,6 @@ import { PaymentOverviewWidget } from "@/features/dashboard/components/payment-o import { PaymentStatusWidget } from "@/features/dashboard/components/payment-status-widget"; import { PurchasesByCategoryWidget } from "@/features/dashboard/components/purchases-by-category-widget"; import { RecurringExpensesWidget } from "@/features/dashboard/components/recurring-expenses-widget"; -import { RecurringSeriesWidget } from "@/features/dashboard/components/recurring-series-widget"; import { SpendingOverviewWidget } from "@/features/dashboard/components/spending-overview-widget"; import type { DashboardData } from "../fetch-dashboard-data"; @@ -164,15 +163,6 @@ export const widgetsConfig: WidgetConfig[] = [ ), }, - { - id: "recurring-series", - title: "Séries Recorrentes", - subtitle: "Gerencie seus lançamentos recorrentes", - icon: , - component: ({ data }) => ( - - ), - }, { id: "installment-expenses", title: "Lançamentos Parcelados", diff --git a/src/features/recurring/actions.ts b/src/features/recurring/actions.ts deleted file mode 100644 index b38ec2f..0000000 --- a/src/features/recurring/actions.ts +++ /dev/null @@ -1,144 +0,0 @@ -"use server"; - -import { and, eq } from "drizzle-orm"; -import { recurringSeries } from "@/db/schema"; -import { generateRecurringTransactions } from "@/features/recurring/generate-recurring"; -import { - handleActionError, - revalidateForEntity, -} from "@/shared/lib/actions/helpers"; -import { getUser } from "@/shared/lib/auth/server"; -import { db } from "@/shared/lib/db"; -import { recurringSeriesActionSchema } from "@/shared/lib/schemas/recurring-series"; -import type { ActionResult } from "@/shared/lib/types/actions"; - -const revalidate = () => revalidateForEntity("recurring"); - -async function findRecurringSeriesForUser(userId: string, seriesId: string) { - const [series] = await db - .select({ - id: recurringSeries.id, - status: recurringSeries.status, - }) - .from(recurringSeries) - .where( - and(eq(recurringSeries.id, seriesId), eq(recurringSeries.userId, userId)), - ) - .limit(1); - - return series ?? null; -} - -export async function pauseRecurringSeriesAction(input: { - seriesId: string; -}): Promise { - try { - const user = await getUser(); - const data = recurringSeriesActionSchema.parse(input); - - const existing = await findRecurringSeriesForUser(user.id, data.seriesId); - - if (!existing) { - return { success: false, error: "Série recorrente não encontrada." }; - } - - if (existing.status !== "active") { - return { - success: false, - error: "Apenas séries ativas podem ser pausadas.", - }; - } - - await db - .update(recurringSeries) - .set({ status: "paused", updatedAt: new Date() }) - .where( - and( - eq(recurringSeries.id, data.seriesId), - eq(recurringSeries.userId, user.id), - ), - ); - - revalidate(); - return { success: true, message: "Série recorrente pausada." }; - } catch (error) { - return handleActionError(error); - } -} - -export async function resumeRecurringSeriesAction(input: { - seriesId: string; -}): Promise { - try { - const user = await getUser(); - const data = recurringSeriesActionSchema.parse(input); - - const existing = await findRecurringSeriesForUser(user.id, data.seriesId); - - if (!existing) { - return { success: false, error: "Série recorrente não encontrada." }; - } - - if (existing.status !== "paused") { - return { - success: false, - error: "Apenas séries pausadas podem ser retomadas.", - }; - } - - await db - .update(recurringSeries) - .set({ status: "active", updatedAt: new Date() }) - .where( - and( - eq(recurringSeries.id, data.seriesId), - eq(recurringSeries.userId, user.id), - ), - ); - - // Trigger catch-up generation for missed months - await generateRecurringTransactions(user.id); - - revalidate(); - return { success: true, message: "Série recorrente retomada." }; - } catch (error) { - return handleActionError(error); - } -} - -export async function cancelRecurringSeriesAction(input: { - seriesId: string; -}): Promise { - try { - const user = await getUser(); - const data = recurringSeriesActionSchema.parse(input); - - const existing = await findRecurringSeriesForUser(user.id, data.seriesId); - - if (!existing) { - return { success: false, error: "Série recorrente não encontrada." }; - } - - if (existing.status === "cancelled") { - return { - success: false, - error: "Esta série já está cancelada.", - }; - } - - await db - .update(recurringSeries) - .set({ status: "cancelled", updatedAt: new Date() }) - .where( - and( - eq(recurringSeries.id, data.seriesId), - eq(recurringSeries.userId, user.id), - ), - ); - - revalidate(); - return { success: true, message: "Série recorrente cancelada." }; - } catch (error) { - return handleActionError(error); - } -} diff --git a/src/features/recurring/generate-recurring.ts b/src/features/recurring/generate-recurring.ts deleted file mode 100644 index 3dc6a86..0000000 --- a/src/features/recurring/generate-recurring.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { and, eq } from "drizzle-orm"; -import { recurringSeries, transactions } from "@/db/schema"; -import { db } from "@/shared/lib/db"; -import { - addMonthsToPeriod, - comparePeriods, - getCurrentPeriod, - getNextPeriod, - parsePeriod, -} from "@/shared/utils/period"; - -/** - * Computes the purchase date for a given period and day of month. - * Clamps to last day of month for short months (e.g., Feb 30 → Feb 28). - */ -function computePurchaseDate(period: string, dayOfMonth: number): Date { - const { year, month } = parsePeriod(period); - // month is 1-indexed, Date constructor expects 0-indexed - const lastDayOfMonth = new Date(year, month, 0).getDate(); - const clampedDay = Math.min(dayOfMonth, lastDayOfMonth); - return new Date(year, month - 1, clampedDay); -} - -/** - * Generates missing recurring transactions for a single user. - * - * For each active recurring series: - * 1. Determines which months are missing between lastGeneratedPeriod and current month - * 2. Creates lancamento rows for each missing month using the template data - * 3. Updates lastGeneratedPeriod on the series - * - * Uses a DB transaction for atomicity. - */ -export async function generateRecurringTransactions( - userId: string, -): Promise<{ generated: number }> { - const currentPeriod = getCurrentPeriod(); - - // Fetch all active recurring series for this user - const activeSeries = await db - .select() - .from(recurringSeries) - .where( - and( - eq(recurringSeries.userId, userId), - eq(recurringSeries.status, "active"), - ), - ); - - if (activeSeries.length === 0) { - return { generated: 0 }; - } - - let totalGenerated = 0; - - for (const series of activeSeries) { - // Determine missing periods: from lastGeneratedPeriod + 1 to currentPeriod - const startPeriod = getNextPeriod(series.lastGeneratedPeriod); - - // If startPeriod is already past the current period, nothing to generate - if (comparePeriods(startPeriod, currentPeriod) > 0) { - continue; - } - - // Build list of periods to generate - const periodsToGenerate: string[] = []; - let iterPeriod = startPeriod; - while (comparePeriods(iterPeriod, currentPeriod) <= 0) { - periodsToGenerate.push(iterPeriod); - iterPeriod = addMonthsToPeriod(iterPeriod, 1); - } - - if (periodsToGenerate.length === 0) { - continue; - } - - const template = series.templateData; - - // Create all transactions for missing periods in a transaction - await db.transaction(async (tx: typeof db) => { - const records = periodsToGenerate.map((period) => { - const purchaseDate = computePurchaseDate(period, series.dayOfMonth); - return { - name: template.name, - amount: template.amount, - transactionType: template.transactionType, - paymentMethod: template.paymentMethod, - condition: "Recorrente" as const, - categoryId: template.categoryId, - accountId: template.accountId, - cardId: template.cardId, - payerId: template.payerId, - note: template.note, - purchaseDate, - period, - isSettled: false, - recurrenceCount: null, - installmentCount: null, - currentInstallment: null, - isDivided: false, - userId, - seriesId: series.id, - recurringSeriesId: series.id, - }; - }); - - await tx.insert(transactions).values(records); - - // Update lastGeneratedPeriod to the last period we generated - const lastPeriod = - periodsToGenerate[periodsToGenerate.length - 1] ?? - series.lastGeneratedPeriod; - await tx - .update(recurringSeries) - .set({ - lastGeneratedPeriod: lastPeriod, - updatedAt: new Date(), - }) - .where(eq(recurringSeries.id, series.id)); - }); - - totalGenerated += periodsToGenerate.length; - } - - return { generated: totalGenerated }; -} diff --git a/src/features/recurring/trigger-recurring-generation.ts b/src/features/recurring/trigger-recurring-generation.ts deleted file mode 100644 index a2240a8..0000000 --- a/src/features/recurring/trigger-recurring-generation.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { cache } from "react"; -import { generateRecurringTransactions } from "./generate-recurring"; - -/** - * Triggers recurring transaction generation for a user. - * Deduped per-request via React.cache to avoid multiple calls - * during the same server render (layout + page). - * - * Call this at the top of dashboard and lancamentos page server components. - */ -export const triggerRecurringGeneration = cache( - async (userId: string): Promise => { - try { - await generateRecurringTransactions(userId); - } catch (error) { - // Log but don't throw — generation failure should not block page render - console.error( - "[RecurringGeneration] Failed to generate recurring transactions:", - error, - ); - } - }, -); diff --git a/src/features/recurring/types.ts b/src/features/recurring/types.ts deleted file mode 100644 index 3626973..0000000 --- a/src/features/recurring/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -// Re-export from schema for convenience -export type { RecurringSeriesTemplate } from "@/db/schema"; - -export type RecurringSeriesStatus = "active" | "paused" | "cancelled"; diff --git a/src/features/transactions/actions.ts b/src/features/transactions/actions.ts index 962b608..d0e3a93 100644 --- a/src/features/transactions/actions.ts +++ b/src/features/transactions/actions.ts @@ -3,13 +3,11 @@ import { randomUUID } from "node:crypto"; import { and, asc, eq, inArray, sql } from "drizzle-orm"; import { z } from "zod"; -import type { RecurringSeriesTemplate } from "@/db/schema"; import { cards, categories, financialAccounts, payers, - recurringSeries, transactions, } from "@/db/schema"; import { @@ -221,6 +219,22 @@ const refineLancamento = ( }); } + if (data.condition === "Recorrente") { + if (!data.recurrenceCount) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["recurrenceCount"], + message: "Informe por quantos meses a recorrência acontecerá.", + }); + } else if (data.recurrenceCount < 2) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["recurrenceCount"], + message: "A recorrência deve ter ao menos dois meses.", + }); + } + } + if (data.condition === "Parcelado") { if (!data.installmentCount) { ctx.addIssue({ @@ -518,23 +532,33 @@ const buildLancamentoRecords = ({ } if (data.condition === "Recorrente") { - // For the new recurring model, only create 1 row (the current month) - // Future rows will be generated lazily by generateRecurringTransactions - shares.forEach((share) => { - const settled = resolveSettledValue(0); - records.push({ - ...basePayload, - amount: centsToDecimalString(share.amountCents * amountSign), - payerId: share.payerId, - purchaseDate, - period, - isSettled: settled, - recurrenceCount: null, - dueDate, - boletoPaymentDate: - data.paymentMethod === "Boleto" && settled ? boletoPaymentDate : null, + const recurrenceTotal = data.recurrenceCount ?? 0; + + for (let index = 0; index < recurrenceTotal; index += 1) { + const recurrencePeriod = addMonthsToPeriod(period, index); + const recurrencePurchaseDate = addMonthsToDate(purchaseDate, index); + const recurrenceDueDate = dueDate + ? addMonthsToDate(dueDate, index) + : null; + + shares.forEach((share) => { + const settled = resolveSettledValue(index); + records.push({ + ...basePayload, + amount: centsToDecimalString(share.amountCents * amountSign), + payerId: share.payerId, + purchaseDate: recurrencePurchaseDate, + period: recurrencePeriod, + isSettled: settled, + recurrenceCount: recurrenceTotal, + dueDate: recurrenceDueDate, + boletoPaymentDate: + data.paymentMethod === "Boleto" && settled + ? boletoPaymentDate + : null, + }); }); - }); + } return records; } @@ -661,42 +685,7 @@ export async function createTransactionAction( throw new Error("Não foi possível criar os lançamentos solicitados."); } - await db.transaction(async (tx: typeof db) => { - // If creating a recurring series, insert the series row first - if (data.condition === "Recorrente" && seriesId) { - const templateData: RecurringSeriesTemplate = { - name: data.name, - amount: centsToDecimalString( - Math.round(Math.abs(data.amount) * 100) * - (data.transactionType === "Despesa" ? -1 : 1), - ), - transactionType: data.transactionType, - paymentMethod: data.paymentMethod, - categoryId: data.categoryId ?? null, - accountId: data.accountId ?? null, - cardId: data.cardId ?? null, - payerId: data.payerId ?? null, - note: data.note ?? null, - condition: "Recorrente", - }; - - await tx.insert(recurringSeries).values({ - id: seriesId, - userId: user.id, - status: "active", - dayOfMonth: purchaseDate.getDate(), - lastGeneratedPeriod: period, - templateData, - }); - - // Link lancamento records to the recurring series - for (const record of records) { - record.recurringSeriesId = seriesId; - } - } - - await tx.insert(transactions).values(records); - }); + await db.insert(transactions).values(records); const notificationEntries = buildEntriesByPayer( records.map((record) => ({ diff --git a/src/features/transactions/components/dialogs/transaction-dialog/condition-section.tsx b/src/features/transactions/components/dialogs/transaction-dialog/condition-section.tsx index 7db6f0e..58ca628 100644 --- a/src/features/transactions/components/dialogs/transaction-dialog/condition-section.tsx +++ b/src/features/transactions/components/dialogs/transaction-dialog/condition-section.tsx @@ -101,14 +101,26 @@ export function ConditionSection({ {showRecurrence ? (
- -

Repetirá por +

) : null} diff --git a/src/features/transactions/components/dialogs/transaction-dialog/transaction-dialog.tsx b/src/features/transactions/components/dialogs/transaction-dialog/transaction-dialog.tsx index ffdbbc8..9b5c37d 100644 --- a/src/features/transactions/components/dialogs/transaction-dialog/transaction-dialog.tsx +++ b/src/features/transactions/components/dialogs/transaction-dialog/transaction-dialog.tsx @@ -1,5 +1,5 @@ "use client"; -import { RiAddLine } from "@remixicon/react"; +import { RiArrowDropDownLine } from "@remixicon/react"; import { useEffect, useMemo, useState, useTransition } from "react"; import { toast } from "sonner"; import { @@ -292,7 +292,10 @@ export function TransactionDialog({ formState.condition === "Parcelado" && formState.installmentCount ? Number(formState.installmentCount) : undefined, - recurrenceCount: undefined, + recurrenceCount: + formState.condition === "Recorrente" && formState.recurrenceCount + ? Number(formState.recurrenceCount) + : undefined, dueDate: formState.paymentMethod === "Boleto" && formState.dueDate ? formState.dueDate @@ -375,7 +378,7 @@ export function TransactionDialog({ const title = mode === "create" ? isImportMode - ? "Importar para Minha FinancialAccount" + ? "Importar para Minha Conta" : isCopyMode ? "Copiar lançamento" : isNewWithType @@ -476,7 +479,7 @@ export function TransactionDialog({ } > - + Condições e anotações diff --git a/src/shared/lib/actions/helpers.ts b/src/shared/lib/actions/helpers.ts index dee6d02..2f3ea5c 100644 --- a/src/shared/lib/actions/helpers.ts +++ b/src/shared/lib/actions/helpers.ts @@ -33,7 +33,6 @@ export const revalidateConfig = { notes: ["/notes", "/notes/archived", "/dashboard"], transactions: ["/transactions", "/accounts"], inbox: ["/inbox", "/transactions", "/dashboard"], - recurring: ["/transactions", "/dashboard"], } as const; /** Entities whose mutations should invalidate the dashboard cache */ diff --git a/src/shared/lib/schemas/recurring-series.ts b/src/shared/lib/schemas/recurring-series.ts deleted file mode 100644 index bc480bd..0000000 --- a/src/shared/lib/schemas/recurring-series.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { z } from "zod"; -import { uuidSchema } from "./common"; - -/** - * Schema for pause/resume/cancel recurring series actions - */ -export const recurringSeriesActionSchema = z.object({ - seriesId: uuidSchema("Série recorrente"), -}); - -export type RecurringSeriesActionInput = z.infer< - typeof recurringSeriesActionSchema ->;