feat: importação de fatura de cartão — strip R$, skip negativos, all_expenses
Parser limpa prefixo R$ e símbolos de moeda antes de parsear o valor. Flags SkipNegative e AllExpenses para CSV de fatura de cartão. UI: checkboxes "Fatura de cartão" e "Ignorar valores negativos". Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
@@ -41,4 +41,6 @@ type CSVMapping struct {
|
||||
HasHeader bool `json:"has_header"`
|
||||
DecimalSeparator string `json:"decimal_separator"` // "." or ","
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -57,24 +57,42 @@ func ParseCSV(r io.Reader, m model.CSVMapping) ([]model.ImportRow, []string) {
|
||||
amtStr := strings.TrimSpace(record[m.AmountColumn])
|
||||
desc := cleanDescription(record[m.DescriptionColumn])
|
||||
|
||||
// Skip blank rows (e.g. empty line after header in some bank exports)
|
||||
if dateStr == "" && amtStr == "" && desc == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
t, err := time.Parse(m.DateFormat, dateStr)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Sprintf("linha %d: data inválida %q", lineNum, dateStr))
|
||||
continue
|
||||
}
|
||||
|
||||
// Strip currency symbols (R$, $, €, etc.) keeping digits, comma, dot, minus
|
||||
cleaned := strings.Map(func(r rune) rune {
|
||||
if r >= '0' && r <= '9' || r == ',' || r == '.' || r == '-' {
|
||||
return r
|
||||
}
|
||||
return -1
|
||||
}, amtStr)
|
||||
cleaned = strings.TrimSpace(cleaned)
|
||||
|
||||
if m.DecimalSeparator == "," {
|
||||
amtStr = strings.ReplaceAll(amtStr, ".", "")
|
||||
amtStr = strings.ReplaceAll(amtStr, ",", ".")
|
||||
cleaned = strings.ReplaceAll(cleaned, ".", "")
|
||||
cleaned = strings.ReplaceAll(cleaned, ",", ".")
|
||||
}
|
||||
amt, err := strconv.ParseFloat(amtStr, 64)
|
||||
if err != nil {
|
||||
amt, err := strconv.ParseFloat(cleaned, 64)
|
||||
if err != nil || cleaned == "" {
|
||||
errs = append(errs, fmt.Sprintf("linha %d: valor inválido %q", lineNum, amtStr))
|
||||
continue
|
||||
}
|
||||
|
||||
if m.SkipNegative && amt < 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
txType := "expense"
|
||||
if amt > 0 {
|
||||
if !m.AllExpenses && amt > 0 {
|
||||
txType = "income"
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user