Compare commits

..

3 Commits

Author SHA1 Message Date
8b2c4aa6fe Merge pull request 'Several improvements and bug fixes for 2025 tax return' (#25) from fix-bugs-for-2025-report into main
All checks were successful
Badges / coveralls (push) Successful in 14s
Reviewed-on: #25
2026-05-17 09:47:28 +01:00
22a0204fa2 fix trading212 record test
All checks were successful
Generate check / check-changes (pull_request) Successful in 4s
Quality / check-changes (pull_request) Successful in 3s
Generate check / verify-generate (pull_request) Successful in 11s
Quality / run-tests (pull_request) Successful in 13s
2026-05-17 09:45:53 +01:00
a4237aa00c add a csv printer
Some checks failed
Generate check / check-changes (pull_request) Successful in 4s
Quality / check-changes (pull_request) Successful in 3s
Generate check / verify-generate (pull_request) Successful in 11s
Quality / run-tests (pull_request) Failing after 10s
2026-05-16 15:38:19 +01:00
3 changed files with 72 additions and 11 deletions

View File

@@ -0,0 +1,57 @@
package main
import (
"encoding/csv"
"fmt"
"io"
"github.com/nmoniz/any2anexoj/internal"
)
type CSVWriter struct {
w *csv.Writer
}
func NewCSVWriter(w io.Writer) *CSVWriter {
return &CSVWriter{w: csv.NewWriter(w)}
}
func (cw *CSVWriter) Render(aw *internal.AggregatorWriter) error {
err := cw.w.Write([]string{
"source_country", "code",
"realization_year", "realization_month", "realization_day", "realization_value",
"acquisition_year", "acquisition_month", "acquisition_day", "acquisition_value",
"expenses", "foreign_tax_paid", "counter_country",
})
if err != nil {
return fmt.Errorf("write csv header: %w", err)
}
for ri := range aw.Iter() {
err := cw.w.Write(reportItemToRow(ri))
if err != nil {
return fmt.Errorf("write csv row: %w", err)
}
}
cw.w.Flush()
return cw.w.Error()
}
func reportItemToRow(ri internal.ReportItem) []string {
return []string{
fmt.Sprintf("%d", ri.AssetCountry),
string(ri.Nature),
fmt.Sprintf("%d", ri.SellTimestamp.Year()),
fmt.Sprintf("%d", int(ri.SellTimestamp.Month())),
fmt.Sprintf("%d", ri.SellTimestamp.Day()),
ri.SellValue.StringFixed(2),
fmt.Sprintf("%d", ri.BuyTimestamp.Year()),
fmt.Sprintf("%d", int(ri.BuyTimestamp.Month())),
fmt.Sprintf("%d", ri.BuyTimestamp.Day()),
ri.BuyValue.StringFixed(2),
ri.Fees.StringFixed(2),
ri.Taxes.StringFixed(2),
fmt.Sprintf("%d", ri.BrokerCountry),
}
}

View File

@@ -22,6 +22,7 @@ var (
platform = pflag.StringP("platform", "p", "trading212", "One of the supported platforms")
lang = pflag.StringP("language", "l", language.Portuguese.String(), "The 2 letter language code")
debug = pflag.BoolP("debug", "d", false, "Activate to log debug messages")
format = pflag.StringP("format", "f", "table", "Output format: table or csv")
ofAPIKey = pflag.String("open-figi-api-key", "", "An OpenFIGI API key for faster report generation (better rate api rate limits)")
// TODO: improve documentation on selectors
selectors = pflag.StringSlice("selectors", nil, "Only process entries that conform to all the selectors:")
@@ -80,16 +81,19 @@ func run(ctx context.Context) error {
return err
}
loc, err := NewLocalizer(*lang)
if err != nil {
return fmt.Errorf("create localizer: %w", err)
switch *format {
case "csv":
return NewCSVWriter(os.Stdout).Render(writer)
case "table":
loc, err := NewLocalizer(*lang)
if err != nil {
return fmt.Errorf("create localizer: %w", err)
}
NewPrettyPrinter(os.Stdout, loc).Render(writer)
return nil
default:
return fmt.Errorf("unsupported format %q: must be table or csv", *format)
}
printer := NewPrettyPrinter(os.Stdout, loc)
printer.Render(writer)
return nil
}
func getReader(platform string, ofAPIKey string) (internal.RecordReader, error) {

View File

@@ -158,7 +158,7 @@ func TestRecordReader_ReadRecord_Split(t *testing.T) {
// open row has the NEW (post-split) position: more shares at lower price
// close row has the OLD (pre-split) position: fewer shares at higher price
// ratio = openQty / closeQty = 0.5 / 0.1 = 5 (a 5:1 split)
splitOpen := `Stock split open,2025-06-03 05:34:16,XX1234567890,ABXY,"Aspargus Broccoli",EOF111111111,0.5000000000,20.0000000000,EUR,1.00000000,,,10.00,"EUR",,,,,,`
splitOpen := `Stock split open,2025-06-03 05:34:16,XX1234567890,ABXY,"Aspargus Broccoli",EOF111111111,0.5000000000,20.0000000000,EUR,1.00000000,,,10.00,"EUR",,,,,,`
splitClose := `Stock split close,2025-06-03 05:34:16,XX1234567890,ABXY,"Aspargus Broccoli",EOF222222222,0.1000000000,100.0000000000,EUR,1.00000000,0.00,"EUR",10.00,"EUR",,,,,,`
t.Run("well-formed split pair returns split record with correct ratio", func(t *testing.T) {
@@ -175,7 +175,7 @@ func TestRecordReader_ReadRecord_Split(t *testing.T) {
if got.Kind() != internal.KindSplit {
t.Errorf("want kind %v but got %v", internal.KindSplit, got.Kind())
}
if got.Symbol() != "NO0013536151" {
if got.Symbol() != "XX1234567890" {
t.Errorf("want symbol NO0013536151 but got %v", got.Symbol())
}