From 2b0aa352d5e0e8b1d1af788fed8aa4598a102ce3 Mon Sep 17 00:00:00 2001 From: Mlcarvalho1 Date: Wed, 27 May 2026 16:25:52 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20salvar=20data=20original=20da=20transa?= =?UTF-8?q?=C3=A7=C3=A3o=20e=20data=20de=20vencimento=20no=20cart=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ao importar fatura de cartão com PaymentDate configurado, a data efetiva da transação passa a ser o vencimento e a data de compra é preservada em original_date. Migration 013 adiciona a coluna original_date em transactions. Co-Authored-By: Claude Sonnet 4.6 --- apps/api/internal/migration/migration.go | 5 ++++- .../sql/013_transaction_original_date.sql | 1 + apps/api/internal/model/transaction.go | 16 +++++++++------- apps/api/internal/parser/csv.go | 17 +++++++++++++---- apps/api/internal/repository/transaction.go | 10 +++++++--- apps/web/src/stores/import.ts | 1 + apps/web/src/views/ImportView.vue | 5 +++++ 7 files changed, 40 insertions(+), 15 deletions(-) create mode 100644 apps/api/internal/migration/sql/013_transaction_original_date.sql diff --git a/apps/api/internal/migration/migration.go b/apps/api/internal/migration/migration.go index 3f22e6d..2122fc0 100644 --- a/apps/api/internal/migration/migration.go +++ b/apps/api/internal/migration/migration.go @@ -44,6 +44,9 @@ var m011 string //go:embed sql/012_category_is_tax.sql var m012 string +//go:embed sql/013_transaction_original_date.sql +var m013 string + func Run(ctx context.Context, pool *pgxpool.Pool) error { // Bootstrap: ensure schema_migrations table exists before checking versions. if _, err := pool.Exec(ctx, ` @@ -55,7 +58,7 @@ func Run(ctx context.Context, pool *pgxpool.Pool) error { return fmt.Errorf("bootstrap schema_migrations: %w", err) } - migrations := []string{m001, m002, m003, m004, m005, m006, m007, m008, m009, m010, m011, m012} + migrations := []string{m001, m002, m003, m004, m005, m006, m007, m008, m009, m010, m011, m012, m013} for i, sql := range migrations { version := i + 1 var applied bool diff --git a/apps/api/internal/migration/sql/013_transaction_original_date.sql b/apps/api/internal/migration/sql/013_transaction_original_date.sql new file mode 100644 index 0000000..4f5532f --- /dev/null +++ b/apps/api/internal/migration/sql/013_transaction_original_date.sql @@ -0,0 +1 @@ +ALTER TABLE transactions ADD COLUMN IF NOT EXISTS original_date DATE; diff --git a/apps/api/internal/model/transaction.go b/apps/api/internal/model/transaction.go index 1b76a25..3618ec0 100644 --- a/apps/api/internal/model/transaction.go +++ b/apps/api/internal/model/transaction.go @@ -17,13 +17,14 @@ type Transaction struct { // ImportRow is a parsed-but-not-yet-saved transaction from a file. type ImportRow struct { - Date string `json:"date"` - Amount float64 `json:"amount"` - Description string `json:"description"` - Type string `json:"type"` - CategoryID *int `json:"category_id,omitempty"` - ExternalID string `json:"external_id,omitempty"` // FITID from OFX - IsDuplicate bool `json:"is_duplicate"` + Date string `json:"date"` // effective date (payment date for credit cards) + OriginalDate string `json:"original_date"` // purchase date from the CSV (empty for non-credit imports) + Amount float64 `json:"amount"` + Description string `json:"description"` + Type string `json:"type"` + CategoryID *int `json:"category_id,omitempty"` + ExternalID string `json:"external_id,omitempty"` // FITID from OFX + IsDuplicate bool `json:"is_duplicate"` } // ImportResult is the response after confirming an import. @@ -44,4 +45,5 @@ type CSVMapping struct { FieldSeparator string `json:"field_separator"` // "," or ";" SkipNegative bool `json:"skip_negative"` // skip rows with negative amount (e.g. credit card bill payments) AllExpenses bool `json:"all_expenses"` // treat all rows as expense (credit card statements) + PaymentDate string `json:"payment_date"` // YYYY-MM-DD; when set, overrides date for all rows (credit card due date) } diff --git a/apps/api/internal/parser/csv.go b/apps/api/internal/parser/csv.go index 6650c98..11fcaf5 100644 --- a/apps/api/internal/parser/csv.go +++ b/apps/api/internal/parser/csv.go @@ -96,11 +96,20 @@ func ParseCSV(r io.Reader, m model.CSVMapping) ([]model.ImportRow, []string) { txType = "income" } + purchaseDate := t.Format("2006-01-02") + effectiveDate := purchaseDate + originalDate := "" + if m.PaymentDate != "" { + effectiveDate = m.PaymentDate + originalDate = purchaseDate + } + rows = append(rows, model.ImportRow{ - Date: t.Format("2006-01-02"), - Amount: math.Abs(amt), - Description: desc, - Type: txType, + Date: effectiveDate, + OriginalDate: originalDate, + Amount: math.Abs(amt), + Description: desc, + Type: txType, }) } return rows, errs diff --git a/apps/api/internal/repository/transaction.go b/apps/api/internal/repository/transaction.go index 5db8e4c..0386462 100644 --- a/apps/api/internal/repository/transaction.go +++ b/apps/api/internal/repository/transaction.go @@ -62,10 +62,14 @@ func (r *transactionRepo) BulkInsert(ctx context.Context, rows []model.ImportRow if row.ExternalID != "" { extID = &row.ExternalID } + var origDate *string + if row.OriginalDate != "" { + origDate = &row.OriginalDate + } _, err := tx.Exec(ctx, ` - INSERT INTO transactions (date, amount, description, type, source, external_id, category_id) - VALUES ($1, $2, $3, $4, 'import', $5, $6)`, - row.Date, row.Amount, row.Description, row.Type, extID, row.CategoryID) + INSERT INTO transactions (date, original_date, amount, description, type, source, external_id, category_id) + VALUES ($1, $2, $3, $4, $5, 'import', $6, $7)`, + row.Date, origDate, row.Amount, row.Description, row.Type, extID, row.CategoryID) if err != nil { return count, err } diff --git a/apps/web/src/stores/import.ts b/apps/web/src/stores/import.ts index 7116a0e..8f752e6 100644 --- a/apps/web/src/stores/import.ts +++ b/apps/web/src/stores/import.ts @@ -27,6 +27,7 @@ export interface CSVMapping { field_separator: string skip_negative: boolean all_expenses: boolean + payment_date: string } export const useImportStore = defineStore('import', () => { diff --git a/apps/web/src/views/ImportView.vue b/apps/web/src/views/ImportView.vue index fb311b9..1f2d5f6 100644 --- a/apps/web/src/views/ImportView.vue +++ b/apps/web/src/views/ImportView.vue @@ -22,6 +22,7 @@ const csvMapping = ref({ field_separator: ';', skip_negative: false, all_expenses: false, + payment_date: '', }) // Quando "Fatura de cartão" é marcado, configura automaticamente as colunas corretas @@ -160,6 +161,10 @@ function fmt(amount: number) { Ignorar valores negativos +