diff --git a/drizzle/0026_bored_eternity.sql b/drizzle/0026_bored_eternity.sql new file mode 100644 index 0000000..a49440f --- /dev/null +++ b/drizzle/0026_bored_eternity.sql @@ -0,0 +1,2 @@ +ALTER TABLE "lancamentos" ADD COLUMN "split_group_id" uuid;--> statement-breakpoint +CREATE INDEX "lancamentos_user_id_split_group_id_idx" ON "lancamentos" USING btree ("user_id","split_group_id"); \ No newline at end of file diff --git a/drizzle/meta/0026_snapshot.json b/drizzle/meta/0026_snapshot.json new file mode 100644 index 0000000..fdc922c --- /dev/null +++ b/drizzle/meta/0026_snapshot.json @@ -0,0 +1,2916 @@ +{ + "id": "6018cc6c-b5fc-4509-944e-bb7cec92ff06", + "prevId": "282a4bfb-26bb-4435-99c3-12c9fdd79e4e", + "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": { + "account_user_id_idx": { + "name": "account_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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.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_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.anexos": { + "name": "anexos", + "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 + }, + "chave_arquivo": { + "name": "chave_arquivo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nome_arquivo": { + "name": "nome_arquivo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tamanho_bytes": { + "name": "tamanho_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "anexos_user_id_idx": { + "name": "anexos_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "anexos_user_id_user_id_fk": { + "name": "anexos_user_id_user_id_fk", + "tableFrom": "anexos", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "anexos_chave_arquivo_unique": { + "name": "anexos_chave_arquivo_unique", + "nullsNotDistinct": false, + "columns": ["chave_arquivo"] + } + }, + "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": {} + }, + "orcamentos_user_id_categoria_id_periodo_key": { + "name": "orcamentos_user_id_categoria_id_periodo_key", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "categoria_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "periodo", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "orcamentos_categoria_id_idx": { + "name": "orcamentos_categoria_id_idx", + "columns": [ + { + "expression": "categoria_id", + "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.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_conta_id_idx": { + "name": "cartoes_conta_id_idx", + "columns": [ + { + "expression": "conta_id", + "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.dashboard_notification_states": { + "name": "dashboard_notification_states", + "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 + }, + "notification_key": { + "name": "notification_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "read_at": { + "name": "read_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_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": { + "dashboard_notification_states_user_id_key_unique": { + "name": "dashboard_notification_states_user_id_key_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "notification_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dashboard_notification_states_user_id_user_id_fk": { + "name": "dashboard_notification_states_user_id_user_id_fk", + "tableFrom": "dashboard_notification_states", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.establishment_logos": { + "name": "establishment_logos", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name_key": { + "name": "name_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "establishment_logos_user_id_user_id_fk": { + "name": "establishment_logos_user_id_user_id_fk", + "tableFrom": "establishment_logos", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "establishment_logos_user_id_name_key_pk": { + "name": "establishment_logos_user_id_name_key_pk", + "columns": ["user_id", "name_key"] + } + }, + "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": {}, + "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.import_category_mappings": { + "name": "import_category_mappings", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description_key": { + "name": "description_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "category_id": { + "name": "category_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "import_category_mappings_category_id_idx": { + "name": "import_category_mappings_category_id_idx", + "columns": [ + { + "expression": "category_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "import_category_mappings_user_id_user_id_fk": { + "name": "import_category_mappings_user_id_user_id_fk", + "tableFrom": "import_category_mappings", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "import_category_mappings_category_id_categorias_id_fk": { + "name": "import_category_mappings_category_id_categorias_id_fk", + "tableFrom": "import_category_mappings", + "tableTo": "categorias", + "columnsFrom": ["category_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "import_category_mappings_user_id_description_key_pk": { + "name": "import_category_mappings_user_id_description_key_pk", + "columns": ["user_id", "description_key"] + } + }, + "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": {} + }, + "pre_lancamentos_lancamento_id_idx": { + "name": "pre_lancamentos_lancamento_id_idx", + "columns": [ + { + "expression": "lancamento_id", + "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.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_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": {} + }, + "antecipacoes_parcelas_lancamento_id_idx": { + "name": "antecipacoes_parcelas_lancamento_id_idx", + "columns": [ + { + "expression": "lancamento_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "antecipacoes_parcelas_pagador_id_idx": { + "name": "antecipacoes_parcelas_pagador_id_idx", + "columns": [ + { + "expression": "pagador_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "antecipacoes_parcelas_categoria_id_idx": { + "name": "antecipacoes_parcelas_categoria_id_idx", + "columns": [ + { + "expression": "categoria_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.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": {} + }, + "faturas_user_id_cartao_id_periodo_key": { + "name": "faturas_user_id_cartao_id_periodo_key", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cartao_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "periodo", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "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.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": { + "anotacoes_user_id_idx": { + "name": "anotacoes_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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.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": { + "passkey_user_id_idx": { + "name": "passkey_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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.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": {} + }, + "compartilhamentos_pagador_shared_with_user_id_idx": { + "name": "compartilhamentos_pagador_shared_with_user_id_idx", + "columns": [ + { + "expression": "shared_with_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "compartilhamentos_pagador_created_by_user_id_idx": { + "name": "compartilhamentos_pagador_created_by_user_id_idx", + "columns": [ + { + "expression": "created_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "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.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": {} + } + }, + "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.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.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": { + "session_user_id_idx": { + "name": "session_user_id_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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.lancamento_anexos": { + "name": "lancamento_anexos", + "schema": "", + "columns": { + "lancamento_id": { + "name": "lancamento_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "anexo_id": { + "name": "anexo_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "lancamento_anexos_anexo_id_idx": { + "name": "lancamento_anexos_anexo_id_idx", + "columns": [ + { + "expression": "anexo_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "lancamento_anexos_lancamento_id_lancamentos_id_fk": { + "name": "lancamento_anexos_lancamento_id_lancamentos_id_fk", + "tableFrom": "lancamento_anexos", + "tableTo": "lancamentos", + "columnsFrom": ["lancamento_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "lancamento_anexos_anexo_id_anexos_id_fk": { + "name": "lancamento_anexos_anexo_id_anexos_id_fk", + "tableFrom": "lancamento_anexos", + "tableTo": "anexos", + "columnsFrom": ["anexo_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "lancamento_anexos_lancamento_id_anexo_id_pk": { + "name": "lancamento_anexos_lancamento_id_anexo_id_pk", + "columns": ["lancamento_id", "anexo_id"] + } + }, + "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 + }, + "split_group_id": { + "name": "split_group_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "transfer_id": { + "name": "transfer_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "ofx_fit_id": { + "name": "ofx_fit_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "import_batch_id": { + "name": "import_batch_id", + "type": "text", + "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_pagador_id_period_idx": { + "name": "lancamentos_user_id_pagador_id_period_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "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_user_id_split_group_id_idx": { + "name": "lancamentos_user_id_split_group_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "split_group_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": {} + }, + "lancamentos_conta_id_idx": { + "name": "lancamentos_conta_id_idx", + "columns": [ + { + "expression": "conta_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "lancamentos_categoria_id_idx": { + "name": "lancamentos_categoria_id_idx", + "columns": [ + { + "expression": "categoria_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "lancamentos_antecipacao_id_idx": { + "name": "lancamentos_antecipacao_id_idx", + "columns": [ + { + "expression": "antecipacao_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "lancamentos_ofx_fit_id_user_id_idx": { + "name": "lancamentos_ofx_fit_id_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "ofx_fit_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "ofx_fit_id IS NOT NULL", + "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" + } + }, + "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.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 + }, + "lancamentos_column_order": { + "name": "lancamentos_column_order", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "attachment_max_size_mb": { + "name": "attachment_max_size_mb", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 50 + }, + "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.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 c48dbd3..f31c2a2 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -183,6 +183,13 @@ "when": 1776351838548, "tag": "0025_burly_colonel_america", "breakpoints": true + }, + { + "idx": 26, + "version": "7", + "when": 1777042423451, + "tag": "0026_bored_eternity", + "breakpoints": true } ] } diff --git a/src/db/schema.ts b/src/db/schema.ts index 034c218..f9a063e 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -670,6 +670,7 @@ export const transactions = pgTable( onUpdate: "cascade", }), seriesId: uuid("series_id"), + splitGroupId: uuid("split_group_id"), transferId: uuid("transfer_id"), ofxFitId: text("ofx_fit_id"), importBatchId: text("import_batch_id"), @@ -702,6 +703,11 @@ export const transactions = pgTable( ), // Índice para buscar parcelas de uma série seriesIdIdx: index("lancamentos_series_id_idx").on(table.seriesId), + // Índice para buscar shares de um split (userId + splitGroupId) + userIdSplitGroupIdIdx: index("lancamentos_user_id_split_group_id_idx").on( + table.userId, + table.splitGroupId, + ), // Índice para buscar transferências relacionadas transferIdIdx: index("lancamentos_transfer_id_idx").on(table.transferId), // Índice para filtrar por condição (aberto, realizado, cancelado) diff --git a/src/features/payers/detail-queries.ts b/src/features/payers/detail-queries.ts index 76d94ea..60e7377 100644 --- a/src/features/payers/detail-queries.ts +++ b/src/features/payers/detail-queries.ts @@ -1,10 +1,11 @@ -import { and, desc, eq, type SQL } from "drizzle-orm"; +import { and, desc, eq, type SQL, sql } from "drizzle-orm"; import { cards, categories, financialAccounts, payerShares, payers, + transactionAttachments, transactions, user as usersTable, } from "@/db/schema"; @@ -73,6 +74,10 @@ export async function fetchPagadorLancamentos(filters: SQL[]) { financialAccount: financialAccounts, card: cards, category: categories, + hasAttachments: sql`EXISTS ( + SELECT 1 FROM ${transactionAttachments} + WHERE ${transactionAttachments.transactionId} = ${transactions.id} + )`, }) .from(transactions) .leftJoin(payers, eq(transactions.payerId, payers.id)) @@ -85,12 +90,12 @@ export async function fetchPagadorLancamentos(filters: SQL[]) { .where(and(...filters)) .orderBy(desc(transactions.purchaseDate), desc(transactions.createdAt)); - // Transformar resultado para o formato esperado return transactionRows.map((row) => ({ ...row.transaction, payer: row.payer, financialAccount: row.financialAccount, card: row.card, category: row.category, + hasAttachments: row.hasAttachments, })); } diff --git a/src/features/transactions/actions.ts b/src/features/transactions/actions.ts index d14d8e5..26169e1 100644 --- a/src/features/transactions/actions.ts +++ b/src/features/transactions/actions.ts @@ -12,6 +12,7 @@ import { deleteTransactionAction as deleteTransactionActionImpl, toggleTransactionSettlementAction as toggleTransactionSettlementActionImpl, updateTransactionAction as updateTransactionActionImpl, + updateTransactionSplitPairAction as updateTransactionSplitPairActionImpl, } from "./actions/single-actions"; export async function createTransactionAction( @@ -62,6 +63,12 @@ export async function deleteMultipleTransactionsAction( return deleteMultipleTransactionsActionImpl(...args); } +export async function updateTransactionSplitPairAction( + ...args: Parameters +): ReturnType { + return updateTransactionSplitPairActionImpl(...args); +} + export async function exportTransactionsDataAction( ...args: Parameters ): ReturnType { diff --git a/src/features/transactions/actions/attachments.ts b/src/features/transactions/actions/attachments.ts index 513b9af..9bcee6a 100644 --- a/src/features/transactions/actions/attachments.ts +++ b/src/features/transactions/actions/attachments.ts @@ -1,7 +1,7 @@ "use server"; import crypto, { randomUUID } from "node:crypto"; -import { and, count, eq, inArray } from "drizzle-orm"; +import { and, count, eq, inArray, isNotNull } from "drizzle-orm"; import { z } from "zod/v4"; import { attachments, transactionAttachments, transactions } from "@/db/schema"; import { @@ -15,7 +15,6 @@ import { import { getUser } from "@/shared/lib/auth/server"; import { db } from "@/shared/lib/db"; import { - createPresignedGetUrl, createPresignedPutUrl, deleteS3Object, headS3Object, @@ -98,6 +97,46 @@ function signUploadToken(payload: UploadTokenPayload): string { return `${encodedPayload}.${signature}`; } +async function expandSplitSiblings( + transactionIds: string[], + userId: string, +): Promise { + if (transactionIds.length === 0) return transactionIds; + + const groupRows = await db + .select({ splitGroupId: transactions.splitGroupId }) + .from(transactions) + .where( + and( + inArray(transactions.id, transactionIds), + eq(transactions.userId, userId), + isNotNull(transactions.splitGroupId), + ), + ); + + const splitGroupIds = [ + ...new Set( + groupRows + .map((r) => r.splitGroupId) + .filter((v): v is string => v !== null), + ), + ]; + + if (splitGroupIds.length === 0) return transactionIds; + + const siblingRows = await db + .select({ id: transactions.id }) + .from(transactions) + .where( + and( + inArray(transactions.splitGroupId, splitGroupIds), + eq(transactions.userId, userId), + ), + ); + + return [...new Set([...transactionIds, ...siblingRows.map((r) => r.id)])]; +} + function verifyUploadToken(token: string): UploadTokenPayload | null { try { const [encodedPayload, signature] = token.split("."); @@ -281,6 +320,8 @@ export async function confirmAttachmentUploadAction(input: { } } + transactionIds = await expandSplitSiblings(transactionIds, user.id); + await db.insert(transactionAttachments).values( transactionIds.map((tid) => ({ transactionId: tid, @@ -359,69 +400,6 @@ export async function detachTransactionAttachmentAction(input: { } } -export async function fetchTransactionAttachmentsAction( - transactionId: string, -): Promise< - Array<{ - attachmentId: string; - fileName: string; - fileSize: number; - mimeType: string; - createdAt: Date; - url: string; - }> -> { - const user = await getUser(); - - const [transaction] = await db - .select({ id: transactions.id }) - .from(transactions) - .where( - and(eq(transactions.id, transactionId), eq(transactions.userId, user.id)), - ); - - if (!transaction) { - return []; - } - - const rows = await db - .select({ - attachmentId: transactionAttachments.attachmentId, - fileName: attachments.fileName, - fileSize: attachments.fileSize, - mimeType: attachments.mimeType, - fileKey: attachments.fileKey, - createdAt: attachments.createdAt, - }) - .from(transactionAttachments) - .innerJoin( - transactions, - and( - eq(transactionAttachments.transactionId, transactions.id), - eq(transactions.userId, user.id), - ), - ) - .innerJoin( - attachments, - and( - eq(transactionAttachments.attachmentId, attachments.id), - eq(attachments.userId, user.id), - ), - ) - .where(eq(transactionAttachments.transactionId, transactionId)); - - return Promise.all( - rows.map(async (row) => ({ - attachmentId: row.attachmentId, - fileName: row.fileName, - fileSize: row.fileSize, - mimeType: row.mimeType, - createdAt: row.createdAt, - url: await createPresignedGetUrl(row.fileKey), - })), - ); -} - const detachBulkSchema = z.object({ attachmentId: z.string().uuid(), transactionId: z.string().uuid(), @@ -497,6 +475,11 @@ export async function detachAttachmentBulkAction(input: { } } + targetTransactionIds = await expandSplitSiblings( + targetTransactionIds, + user.id, + ); + if (targetTransactionIds.length > 0) { await db .delete(transactionAttachments) diff --git a/src/features/transactions/actions/core.ts b/src/features/transactions/actions/core.ts index bcfecf7..3573dcc 100644 --- a/src/features/transactions/actions/core.ts +++ b/src/features/transactions/actions/core.ts @@ -1,3 +1,4 @@ +import { randomUUID } from "node:crypto"; import { and, eq, inArray } from "drizzle-orm"; import { z } from "zod"; import { @@ -394,7 +395,11 @@ const refineLancamento = ( } }; -export const createSchema = baseFields.superRefine(refineLancamento); +export const createSchema = baseFields + .extend({ + importFromTransactionId: uuidSchema("Lançamento fonte").optional(), + }) + .superRefine(refineLancamento); export const updateSchema = baseFields .extend({ id: uuidSchema("Lançamento"), @@ -544,6 +549,7 @@ export const buildLancamentoRecords = ({ seriesId, }: BuildTransactionRecordsParams): TransactionInsert[] => { const records: TransactionInsert[] = []; + const isSplit = (data.isSplit ?? false) && shares.length > 1; const basePayload = { name: data.name, @@ -562,6 +568,8 @@ export const buildLancamentoRecords = ({ seriesId, }; + const cycleSplitGroupId = () => (isSplit ? randomUUID() : null); + const resolveSettledValue = (cycleIndex: number) => { if (shouldNullifySettled) { return null; @@ -588,6 +596,7 @@ export const buildLancamentoRecords = ({ const installmentDueDate = dueDate ? addMonthsToDate(dueDate, installment) : null; + const splitGroupId = cycleSplitGroupId(); shares.forEach((share, shareIndex) => { const amountCents = amountsByShare[shareIndex]?.[installment] ?? 0; @@ -603,6 +612,7 @@ export const buildLancamentoRecords = ({ currentInstallment: installment + 1, recurrenceCount: null, dueDate: installmentDueDate, + splitGroupId, boletoPaymentDate: data.paymentMethod === "Boleto" && settled ? boletoPaymentDate @@ -623,6 +633,7 @@ export const buildLancamentoRecords = ({ const recurrenceDueDate = dueDate ? addMonthsToDate(dueDate, index) : null; + const splitGroupId = cycleSplitGroupId(); shares.forEach((share) => { const settled = resolveSettledValue(index); @@ -635,6 +646,7 @@ export const buildLancamentoRecords = ({ isSettled: settled, recurrenceCount: recurrenceTotal, dueDate: recurrenceDueDate, + splitGroupId, boletoPaymentDate: data.paymentMethod === "Boleto" && settled ? boletoPaymentDate @@ -646,6 +658,8 @@ export const buildLancamentoRecords = ({ return records; } + const splitGroupId = cycleSplitGroupId(); + shares.forEach((share) => { const settled = resolveSettledValue(0); records.push({ @@ -656,6 +670,7 @@ export const buildLancamentoRecords = ({ period, isSettled: settled, dueDate, + splitGroupId, boletoPaymentDate: data.paymentMethod === "Boleto" && settled ? boletoPaymentDate : null, }); diff --git a/src/features/transactions/actions/export-actions.ts b/src/features/transactions/actions/export-actions.ts index 4926f8c..37bdad4 100644 --- a/src/features/transactions/actions/export-actions.ts +++ b/src/features/transactions/actions/export-actions.ts @@ -33,6 +33,7 @@ const exportTransactionsSchema: z.ZodType = z.object( searchFilter: z.string().nullable(), settledFilter: z.string().nullable(), attachmentFilter: z.string().nullable(), + dividedFilter: z.string().nullable(), }), accountId: z.string().min(1).nullable().optional(), cardId: z.string().min(1).nullable().optional(), diff --git a/src/features/transactions/actions/single-actions.ts b/src/features/transactions/actions/single-actions.ts index 67ca014..abd0286 100644 --- a/src/features/transactions/actions/single-actions.ts +++ b/src/features/transactions/actions/single-actions.ts @@ -1,7 +1,7 @@ "use server"; import { randomUUID } from "node:crypto"; -import { and, eq } from "drizzle-orm"; +import { and, eq, ne } from "drizzle-orm"; import { attachments, financialAccounts, @@ -21,6 +21,7 @@ import { getBusinessTodayDate, parseLocalDateString, } from "@/shared/utils/date"; +import { copyAttachmentsForImport } from "../attachment-copy"; import { cleanupAttachmentsAfterTransactionDelete } from "./attachments"; import { buildLancamentoRecords, @@ -138,6 +139,14 @@ export async function createTransactionAction( .values(records) .returning({ id: transactions.id }); + if (data.importFromTransactionId && inserted.length > 0) { + await copyAttachmentsForImport({ + sourceTransactionId: data.importFromTransactionId, + targetTransactionIds: inserted.map((r) => r.id), + targetUserId: user.id, + }); + } + const notificationEntries = buildEntriesByPayer( records.map((record) => ({ payerId: record.payerId ?? null, @@ -437,6 +446,134 @@ export async function deleteTransactionAction( } } +export async function updateTransactionSplitPairAction( + input: UpdateInput, +): Promise { + try { + const user = await getUser(); + const data = updateSchema.parse(input); + + const ownershipError = await validateAllOwnership(user.id, { + payerId: data.payerId, + categoryId: data.categoryId, + accountId: data.accountId, + cardId: data.cardId, + }); + if (ownershipError) { + return { success: false, error: ownershipError }; + } + + const existing = await db.query.transactions.findFirst({ + columns: { + id: true, + period: true, + transactionType: true, + condition: true, + paymentMethod: true, + accountId: true, + cardId: true, + categoryId: true, + splitGroupId: true, + }, + where: and( + eq(transactions.id, data.id), + eq(transactions.userId, user.id), + ), + }); + + if (!existing) { + return { success: false, error: "Lançamento não encontrado." }; + } + + const period = resolvePeriod(data.purchaseDate, data.period); + const amountSign: 1 | -1 = data.transactionType === "Despesa" ? -1 : 1; + const amountCents = Math.round(Math.abs(data.amount) * 100); + const normalizedAmount = centsToDecimalString(amountCents * amountSign); + const normalizedSettled = + data.paymentMethod === "Cartão de crédito" + ? null + : (data.isSettled ?? false); + const shouldSetBoletoPaymentDate = + data.paymentMethod === "Boleto" && Boolean(normalizedSettled); + const boletoPaymentDateValue = shouldSetBoletoPaymentDate + ? data.boletoPaymentDate + ? parseLocalDateString(data.boletoPaymentDate) + : getBusinessTodayDate() + : null; + const targetCardId = data.cardId ?? existing.cardId; + const movedInvoice = + data.paymentMethod === "Cartão de crédito" && + targetCardId && + (targetCardId !== existing.cardId || period !== existing.period); + + if (movedInvoice) { + const paidPeriods = await getPaidInvoicePeriods(user.id, targetCardId, [ + period, + ]); + if (paidPeriods.length > 0) { + return { + success: false, + error: `As faturas dos meses ${formatPaidInvoicePeriods( + paidPeriods, + )} já estão pagas. Desfaça o pagamento antes de mover este lançamento.`, + }; + } + } + + const purchaseDate = parseLocalDateString(data.purchaseDate); + const dueDate = data.dueDate ? parseLocalDateString(data.dueDate) : null; + + const sharedPayload = { + name: data.name, + purchaseDate, + transactionType: data.transactionType, + condition: data.condition, + paymentMethod: data.paymentMethod, + accountId: data.accountId ?? null, + cardId: data.cardId ?? null, + categoryId: data.categoryId ?? null, + note: data.note ?? null, + dueDate, + period, + isSettled: normalizedSettled, + boletoPaymentDate: boletoPaymentDateValue, + }; + + await db.transaction(async (tx: typeof db) => { + await tx + .update(transactions) + .set({ + ...sharedPayload, + amount: normalizedAmount, + payerId: data.payerId ?? null, + installmentCount: data.installmentCount ?? null, + recurrenceCount: data.recurrenceCount ?? null, + }) + .where( + and(eq(transactions.id, data.id), eq(transactions.userId, user.id)), + ); + + if (existing.splitGroupId) { + await tx + .update(transactions) + .set(sharedPayload) + .where( + and( + eq(transactions.splitGroupId, existing.splitGroupId), + eq(transactions.userId, user.id), + ne(transactions.id, data.id), + ), + ); + } + }); + + revalidate(user.id); + return { success: true, message: "Lançamentos atualizados com sucesso." }; + } catch (error) { + return handleActionError(error); + } +} + export async function toggleTransactionSettlementAction( input: ToggleSettlementInput, ): Promise { diff --git a/src/features/transactions/attachment-copy.ts b/src/features/transactions/attachment-copy.ts new file mode 100644 index 0000000..aef48cb --- /dev/null +++ b/src/features/transactions/attachment-copy.ts @@ -0,0 +1,107 @@ +import { randomUUID } from "node:crypto"; +import { CopyObjectCommand } from "@aws-sdk/client-s3"; +import { eq } from "drizzle-orm"; +import { attachments, transactionAttachments, transactions } from "@/db/schema"; +import { db } from "@/shared/lib/db"; +import { getPayerAccess } from "@/shared/lib/payers/access"; +import { deleteS3Object } from "@/shared/lib/storage/presign"; +import { S3_BUCKET, s3 } from "@/shared/lib/storage/s3-client"; + +const SAFE_EXTENSION = /^[a-z0-9]{1,10}$/i; + +function sanitizeExtension(fileKey: string): string { + const ext = fileKey.split(".").pop() ?? ""; + return SAFE_EXTENSION.test(ext) ? ext.toLowerCase() : "bin"; +} + +export async function copyAttachmentsForImport({ + sourceTransactionId, + targetTransactionIds, + targetUserId, +}: { + sourceTransactionId: string; + targetTransactionIds: string[]; + targetUserId: string; +}): Promise { + if (targetTransactionIds.length === 0) return; + + const [source] = await db + .select({ + id: transactions.id, + userId: transactions.userId, + payerId: transactions.payerId, + }) + .from(transactions) + .where(eq(transactions.id, sourceTransactionId)); + + if (!source) return; + + if (source.userId !== targetUserId) { + if (!source.payerId) return; + const access = await getPayerAccess(targetUserId, source.payerId); + if (!access) return; + } + + const sourceAttachments = await db + .select({ + fileKey: attachments.fileKey, + fileName: attachments.fileName, + fileSize: attachments.fileSize, + mimeType: attachments.mimeType, + }) + .from(transactionAttachments) + .innerJoin( + attachments, + eq(transactionAttachments.attachmentId, attachments.id), + ) + .where(eq(transactionAttachments.transactionId, sourceTransactionId)); + + if (sourceAttachments.length === 0) return; + + for (const src of sourceAttachments) { + const newFileKey = `${targetUserId}/${randomUUID()}.${sanitizeExtension(src.fileKey)}`; + + try { + await s3.send( + new CopyObjectCommand({ + Bucket: S3_BUCKET, + CopySource: `${S3_BUCKET}/${src.fileKey}`, + Key: newFileKey, + ContentType: src.mimeType, + MetadataDirective: "COPY", + }), + ); + } catch (error) { + console.error("Falha ao copiar anexo no S3:", error); + continue; + } + + try { + const [newAttachment] = await db + .insert(attachments) + .values({ + userId: targetUserId, + fileKey: newFileKey, + fileName: src.fileName, + fileSize: src.fileSize, + mimeType: src.mimeType, + }) + .returning({ id: attachments.id }); + + if (!newAttachment) { + await deleteS3Object(newFileKey); + continue; + } + + await db.insert(transactionAttachments).values( + targetTransactionIds.map((tid) => ({ + transactionId: tid, + attachmentId: newAttachment.id, + })), + ); + } catch (error) { + console.error("Falha ao registrar anexo copiado:", error); + await deleteS3Object(newFileKey).catch(() => {}); + } + } +} diff --git a/src/features/transactions/attachment-queries.ts b/src/features/transactions/attachment-queries.ts index d21a09a..829ca6e 100644 --- a/src/features/transactions/attachment-queries.ts +++ b/src/features/transactions/attachment-queries.ts @@ -1,6 +1,7 @@ -import { and, eq } from "drizzle-orm"; +import { eq } from "drizzle-orm"; import { attachments, transactionAttachments, transactions } from "@/db/schema"; import { db } from "@/shared/lib/db"; +import { getPayerAccess } from "@/shared/lib/payers/access"; import { createPresignedGetUrl } from "@/shared/lib/storage/presign"; export type TransactionAttachmentListItem = { @@ -17,16 +18,24 @@ export async function fetchTransactionAttachments( transactionId: string, ): Promise { const [transaction] = await db - .select({ id: transactions.id }) + .select({ + id: transactions.id, + userId: transactions.userId, + payerId: transactions.payerId, + }) .from(transactions) - .where( - and(eq(transactions.id, transactionId), eq(transactions.userId, userId)), - ); + .where(eq(transactions.id, transactionId)); if (!transaction) { return []; } + if (transaction.userId !== userId) { + if (!transaction.payerId) return []; + const access = await getPayerAccess(userId, transaction.payerId); + if (!access) return []; + } + const rows = await db .select({ attachmentId: transactionAttachments.attachmentId, @@ -37,19 +46,9 @@ export async function fetchTransactionAttachments( createdAt: attachments.createdAt, }) .from(transactionAttachments) - .innerJoin( - transactions, - and( - eq(transactionAttachments.transactionId, transactions.id), - eq(transactions.userId, userId), - ), - ) .innerJoin( attachments, - and( - eq(transactionAttachments.attachmentId, attachments.id), - eq(attachments.userId, userId), - ), + eq(transactionAttachments.attachmentId, attachments.id), ) .where(eq(transactionAttachments.transactionId, transactionId)); diff --git a/src/features/transactions/components/dialogs/split-pair-dialog.tsx b/src/features/transactions/components/dialogs/split-pair-dialog.tsx new file mode 100644 index 0000000..b73d0f1 --- /dev/null +++ b/src/features/transactions/components/dialogs/split-pair-dialog.tsx @@ -0,0 +1,104 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "@/shared/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/shared/components/ui/dialog"; +import { Label } from "@/shared/components/ui/label"; +import { RadioGroup, RadioGroupItem } from "@/shared/components/ui/radio-group"; + +export type SplitPairScope = "current" | "both"; + +type SplitPairDialogProps = { + open: boolean; + onOpenChange: (open: boolean) => void; + onConfirm: (scope: SplitPairScope) => void; +}; + +export function SplitPairDialog({ + open, + onOpenChange, + onConfirm, +}: SplitPairDialogProps) { + const [scope, setScope] = useState("current"); + + const handleConfirm = () => { + onConfirm(scope); + onOpenChange(false); + }; + + return ( + + + + Editar lançamento dividido + + Este lançamento está dividido com outra pessoa. Escolha o que deseja + editar: + + + + setScope(v as SplitPairScope)} + > +
+
+ +
+ +

+ Aplica a alteração somente neste lado da divisão +

+
+
+ +
+ +
+ +

+ Aplica nome, data, categoria e outros campos compartilhados + nos dois lados da divisão +

+
+
+
+
+ + + + + +
+
+ ); +} diff --git a/src/features/transactions/components/dialogs/transaction-dialog/transaction-dialog-types.ts b/src/features/transactions/components/dialogs/transaction-dialog/transaction-dialog-types.ts index 82397d0..fddcff4 100644 --- a/src/features/transactions/components/dialogs/transaction-dialog/transaction-dialog-types.ts +++ b/src/features/transactions/components/dialogs/transaction-dialog/transaction-dialog-types.ts @@ -49,6 +49,26 @@ export interface TransactionDialogProps { pendingDetachIds: string[]; pendingUploadFiles: File[]; }) => void; + onSplitEditRequest?: (data: { + id: string; + purchaseDate: string; + period: string; + name: string; + transactionType: string; + amount: number; + condition: string; + paymentMethod: string; + categoryId: string | undefined; + note: string; + payerId: string | undefined; + accountId: string | undefined; + cardId: string | undefined; + isSettled: boolean | null; + dueDate: string | null; + boletoPaymentDate: string | null; + pendingDetachIds: string[]; + pendingUploadFiles: File[]; + }) => void; } export interface BaseFieldSectionProps { 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 20edb42..adc31ab 100644 --- a/src/features/transactions/components/dialogs/transaction-dialog/transaction-dialog.tsx +++ b/src/features/transactions/components/dialogs/transaction-dialog/transaction-dialog.tsx @@ -78,6 +78,7 @@ export function TransactionDialog({ onSuccess, maxSizeMb, onBulkEditRequest, + onSplitEditRequest, }: TransactionDialogProps) { const [dialogOpen, setDialogOpen] = useControlledState( open, @@ -321,6 +322,10 @@ export function TransactionDialog({ formState.boletoPaymentDate ? formState.boletoPaymentDate : undefined, + importFromTransactionId: + mode === "create" && isImporting && transaction?.id + ? transaction.id + : undefined, }; startTransition(async () => { @@ -365,6 +370,11 @@ export function TransactionDialog({ } const hasSeriesId = Boolean(transaction?.seriesId); + const hasSplitPair = Boolean( + transaction?.isDivided && + transaction?.splitGroupId && + !transaction?.seriesId, + ); if (hasSeriesId && onBulkEditRequest) { // Para lançamentos em série, passa os arquivos para a página confirmar @@ -398,6 +408,39 @@ export function TransactionDialog({ return; } + if (hasSplitPair && onSplitEditRequest) { + onSplitEditRequest({ + id: transaction?.id ?? "", + purchaseDate: formState.purchaseDate, + period: formState.period, + name: formState.name.trim(), + transactionType: formState.transactionType, + amount: sanitizedAmount, + condition: formState.condition, + paymentMethod: formState.paymentMethod, + categoryId: formState.categoryId, + note: formState.note.trim() || "", + payerId: formState.payerId, + accountId: formState.accountId, + cardId: formState.cardId, + isSettled: + formState.paymentMethod === "Cartão de crédito" + ? null + : Boolean(formState.isSettled), + dueDate: + formState.paymentMethod === "Boleto" + ? formState.dueDate || null + : null, + boletoPaymentDate: + mode === "update" && formState.paymentMethod === "Boleto" + ? formState.boletoPaymentDate || null + : null, + pendingDetachIds, + pendingUploadFiles, + }); + return; + } + // Atualização normal para lançamentos únicos const updatePayload: UpdateTransactionInput = { id: transaction?.id ?? "", @@ -609,6 +652,17 @@ export function TransactionDialog({ formState={formState} onFieldChange={handleFieldChange} /> + {isImportMode && transaction?.id && ( +
+ + +
+ )} setPendingFiles((prev) => [...prev, file])} diff --git a/src/features/transactions/components/page/transactions-page.tsx b/src/features/transactions/components/page/transactions-page.tsx index 469638f..81a195f 100644 --- a/src/features/transactions/components/page/transactions-page.tsx +++ b/src/features/transactions/components/page/transactions-page.tsx @@ -8,7 +8,9 @@ import { deleteTransactionAction, deleteTransactionBulkAction, toggleTransactionSettlementAction, + updateTransactionAction, updateTransactionBulkAction, + updateTransactionSplitPairAction, } from "@/features/transactions/actions"; import { confirmAttachmentUploadAction, @@ -31,6 +33,10 @@ import { MassAddDialog, type MassAddFormData, } from "../dialogs/mass-add-dialog"; +import { + SplitPairDialog, + type SplitPairScope, +} from "../dialogs/split-pair-dialog"; import { TransactionDetailsDialog } from "../dialogs/transaction-details-dialog"; import { TransactionDialog } from "../dialogs/transaction-dialog/transaction-dialog"; import { TransactionsTable } from "../table/transactions-table"; @@ -125,6 +131,26 @@ export function TransactionsPage({ ); const [bulkEditOpen, setBulkEditOpen] = useState(false); const [bulkDeleteOpen, setBulkDeleteOpen] = useState(false); + const [pendingSplitEditData, setPendingSplitEditData] = useState<{ + id: string; + name: string; + purchaseDate: string; + period: string; + transactionType: string; + amount: number; + condition: string; + paymentMethod: string; + payerId: string | undefined; + accountId: string | undefined; + cardId: string | undefined; + categoryId: string | undefined; + note: string; + isSettled: boolean | null; + dueDate: string | null; + boletoPaymentDate: string | null; + pendingDetachIds: string[]; + pendingUploadFiles: File[]; + } | null>(null); const [pendingEditData, setPendingEditData] = useState<{ id: string; purchaseDate: string; @@ -394,6 +420,90 @@ export function TransactionsPage({ setMassAddOpen(true); }; + const handleSplitEditRequest = ( + data: NonNullable, + ) => { + setPendingSplitEditData(data); + setEditOpen(false); + }; + + const handleSplitEdit = async (scope: SplitPairScope) => { + if (!pendingSplitEditData) { + return; + } + + const payload = { + id: pendingSplitEditData.id, + name: pendingSplitEditData.name, + purchaseDate: pendingSplitEditData.purchaseDate, + period: pendingSplitEditData.period, + transactionType: pendingSplitEditData.transactionType as Parameters< + typeof updateTransactionAction + >[0]["transactionType"], + amount: pendingSplitEditData.amount, + condition: pendingSplitEditData.condition as Parameters< + typeof updateTransactionAction + >[0]["condition"], + paymentMethod: pendingSplitEditData.paymentMethod as Parameters< + typeof updateTransactionAction + >[0]["paymentMethod"], + payerId: pendingSplitEditData.payerId ?? null, + accountId: pendingSplitEditData.accountId ?? null, + cardId: pendingSplitEditData.cardId ?? null, + categoryId: pendingSplitEditData.categoryId ?? null, + note: pendingSplitEditData.note, + isSettled: pendingSplitEditData.isSettled, + dueDate: pendingSplitEditData.dueDate ?? undefined, + boletoPaymentDate: pendingSplitEditData.boletoPaymentDate ?? undefined, + isSplit: false, + }; + + const action = + scope === "both" + ? updateTransactionSplitPairAction + : updateTransactionAction; + const result = await action(payload); + + if (!result.success) { + toast.error(result.error); + throw new Error(result.error); + } + + await Promise.all( + pendingSplitEditData.pendingDetachIds.map((attachmentId) => + detachAttachmentBulkAction({ + attachmentId, + transactionId: pendingSplitEditData.id, + scope: "current", + }), + ), + ); + + await Promise.all( + pendingSplitEditData.pendingUploadFiles.map(async (file) => { + const presign = await getPresignedUploadUrlAction({ + fileName: file.name, + mimeType: file.type, + fileSize: file.size, + transactionId: pendingSplitEditData.id, + }); + if (!presign.success) return; + await fetch(presign.presignedUrl, { + method: "PUT", + body: file, + headers: { "Content-Type": file.type }, + }); + await confirmAttachmentUploadAction({ + uploadToken: presign.uploadToken, + scope: "current", + }); + }), + ); + + toast.success(result.message); + setPendingSplitEditData(null); + }; + const handleEdit = (item: TransactionItem) => { setSelectedTransaction(item); setEditOpen(true); @@ -557,6 +667,7 @@ export function TransactionsPage({ transaction={selectedTransaction ?? undefined} defaultPeriod={selectedPeriod} onBulkEditRequest={handleBulkEditRequest} + onSplitEditRequest={handleSplitEditRequest} maxSizeMb={attachmentMaxSizeMb} /> @@ -626,6 +737,14 @@ export function TransactionsPage({ onConfirm={handleBulkEdit} /> + { + if (!open) setPendingSplitEditData(null); + }} + onConfirm={handleSplitEdit} + /> + {allowCreate && massAddOpen ? ( { handleReset(); @@ -628,6 +629,23 @@ export function TransactionsFilters({ }} /> + +
+ + { + handleFilterChange("isDivided", checked ? "true" : null); + }} + /> +
diff --git a/src/features/transactions/components/types.ts b/src/features/transactions/components/types.ts index 18371f1..dc69963 100644 --- a/src/features/transactions/components/types.ts +++ b/src/features/transactions/components/types.ts @@ -33,6 +33,7 @@ export type TransactionItem = { isAnticipated: boolean; anticipationId: string | null; seriesId: string | null; + splitGroupId: string | null; hasAttachments: boolean; readonly?: boolean; }; diff --git a/src/features/transactions/export-types.ts b/src/features/transactions/export-types.ts index e02f4da..c1fd607 100644 --- a/src/features/transactions/export-types.ts +++ b/src/features/transactions/export-types.ts @@ -8,6 +8,7 @@ export type TransactionExportFilters = { searchFilter: string | null; settledFilter: string | null; attachmentFilter: string | null; + dividedFilter: string | null; }; export type TransactionsExportContext = { diff --git a/src/features/transactions/page-helpers.ts b/src/features/transactions/page-helpers.ts index 3db0ebd..cd468fc 100644 --- a/src/features/transactions/page-helpers.ts +++ b/src/features/transactions/page-helpers.ts @@ -45,6 +45,7 @@ export type TransactionSearchFilters = { searchFilter: string | null; settledFilter: string | null; attachmentFilter: string | null; + dividedFilter: string | null; }; type BaseSluggedOption = { @@ -134,6 +135,7 @@ export const extractTransactionSearchFilters = ( searchFilter: getSingleParam(params, "q"), settledFilter: getSingleParam(params, "settled"), attachmentFilter: getSingleParam(params, "hasAttachment"), + dividedFilter: getSingleParam(params, "isDivided"), }); export const resolveTransactionPagination = ( @@ -402,6 +404,10 @@ export const buildTransactionWhere = ({ ); } + if (filters.dividedFilter === "true") { + where.push(eq(transactions.isDivided, true)); + } + const searchPattern = buildSearchPattern(filters.searchFilter); if (searchPattern) { where.push( @@ -468,6 +474,7 @@ export const mapTransactionsData = (rows: TransactionRowWithRelations[]) => isAnticipated: item.isAnticipated ?? false, anticipationId: item.anticipationId ?? null, seriesId: item.seriesId ?? null, + splitGroupId: item.splitGroupId ?? null, hasAttachments: item.hasAttachments ?? false, readonly: Boolean(item.note?.startsWith(ACCOUNT_AUTO_INVOICE_NOTE_PREFIX)) ||