From 5060fca7becba46309759a5c753f98f4c29eb842 Mon Sep 17 00:00:00 2001 From: Natercio Moniz Date: Tue, 25 Nov 2025 15:14:49 +0000 Subject: [PATCH 1/3] round to decimals and improve presentation --- internal/table_writer.go | 74 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 6 deletions(-) diff --git a/internal/table_writer.go b/internal/table_writer.go index 95c181a..07834c0 100644 --- a/internal/table_writer.go +++ b/internal/table_writer.go @@ -2,8 +2,10 @@ package internal import ( "context" + "fmt" "io" + "github.com/biter777/countries" "github.com/jedib0t/go-pretty/v6/table" "github.com/jedib0t/go-pretty/v6/text" "github.com/shopspring/decimal" @@ -26,6 +28,21 @@ func NewTableWriter(w io.Writer) *TableWriter { t.SetOutputMirror(w) t.SetAutoIndex(true) t.SetStyle(table.StyleLight) + t.SetColumnConfigs([]table.ColumnConfig{ + colCountry(1), + colOther(2), + colOther(3), + colOther(4), + colOther(5), + colEuros(6), + colOther(7), + colOther(8), + colOther(9), + colEuros(10), + colEuros(11), + colEuros(12), + colCountry(13), + }) t.AppendHeader(table.Row{"", "", "Realisation", "Realisation", "Realisation", "Realisation", "Acquisition", "Acquisition", "Acquisition", "Acquisition", "", "", ""}, table.RowConfig{AutoMerge: true}) t.AppendHeader(table.Row{"Source Country", "Code", "Year", "Month", "Day", "Value", "Year", "Month", "Day", "Value", "Expenses", "Paid Taxes", "Counter Country"}) @@ -37,17 +54,62 @@ func NewTableWriter(w io.Writer) *TableWriter { } func (tw *TableWriter) Write(_ context.Context, ri ReportItem) error { - tw.totalEarned = tw.totalEarned.Add(ri.SellValue) - tw.totalSpent = tw.totalSpent.Add(ri.BuyValue) - tw.totalFees = tw.totalFees.Add(ri.Fees) - tw.totalTaxes = tw.totalTaxes.Add(ri.Taxes) + tw.totalEarned = tw.totalEarned.Add(ri.SellValue.Round(2)) + tw.totalSpent = tw.totalSpent.Add(ri.BuyValue.Round(2)) + tw.totalFees = tw.totalFees.Add(ri.Fees.Round(2)) + tw.totalTaxes = tw.totalTaxes.Add(ri.Taxes.Round(2)) - tw.table.AppendRow(table.Row{ri.AssetCountry, ri.Nature, ri.SellTimestamp.Year(), int(ri.SellTimestamp.Month()), ri.SellTimestamp.Day(), ri.SellValue, ri.BuyTimestamp.Year(), ri.BuyTimestamp.Month(), ri.BuyTimestamp.Day(), ri.BuyValue, ri.Fees, ri.Taxes, ri.BrokerCountry}) + tw.table.AppendRow(table.Row{ + ri.AssetCountry, ri.Nature, + ri.SellTimestamp.Year(), int(ri.SellTimestamp.Month()), ri.SellTimestamp.Day(), ri.SellValue.StringFixed(2), + ri.BuyTimestamp.Year(), int(ri.BuyTimestamp.Month()), ri.BuyTimestamp.Day(), ri.BuyValue.StringFixed(2), + ri.Fees.StringFixed(2), ri.Taxes.StringFixed(2), ri.BrokerCountry, + }) return nil } func (tw *TableWriter) Render() { - tw.table.AppendFooter(table.Row{"SUM", "SUM", "SUM", "SUM", "SUM", tw.totalEarned, "", "", "", tw.totalSpent, tw.totalFees, tw.totalTaxes}, table.RowConfig{AutoMerge: true, AutoMergeAlign: text.AlignRight}) + tw.table.AppendFooter(table.Row{"SUM", "SUM", "SUM", "SUM", "SUM", tw.totalEarned.StringFixed(2), "", "", "", tw.totalSpent.StringFixed(2), tw.totalFees.StringFixed(2), tw.totalTaxes.StringFixed(2)}, table.RowConfig{AutoMerge: true, AutoMergeAlign: text.AlignRight}) tw.table.Render() } + +func colEuros(n int) table.ColumnConfig { + return table.ColumnConfig{ + Number: n, + Align: text.AlignRight, + AlignFooter: text.AlignRight, + AlignHeader: text.AlignRight, + WidthMin: 12, + Transformer: func(val any) string { + return fmt.Sprintf("%v €", val) + }, + TransformerFooter: func(val any) string { + return fmt.Sprintf("%v €", val) + }, + } +} + +func colOther(n int) table.ColumnConfig { + return table.ColumnConfig{ + Number: n, + Align: text.AlignLeft, + AlignFooter: text.AlignLeft, + AlignHeader: text.AlignLeft, + } +} + +func colCountry(n int) table.ColumnConfig { + return table.ColumnConfig{ + Number: n, + Align: text.AlignLeft, + AlignFooter: text.AlignLeft, + AlignHeader: text.AlignLeft, + WidthMax: 24, + WidthMaxEnforcer: text.Trim, + Transformer: func(val any) string { + countryCode := val.(int64) + return fmt.Sprintf("%v - %s", val, countries.ByNumeric(int(countryCode)).Info().Name) + }, + } +} -- 2.49.1 From 0b6b35e73630431177862cb60c406fc790fcc0a3 Mon Sep 17 00:00:00 2001 From: Natercio Moniz Date: Wed, 26 Nov 2025 09:56:49 +0000 Subject: [PATCH 2/3] update readme screenshot and add section about rounding --- README.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ec556c8..19ca8a5 100644 --- a/README.md +++ b/README.md @@ -3,16 +3,15 @@ [![Go Report Card](https://goreportcard.com/badge/github.com/nmoniz/any2anexoj)](https://goreportcard.com/report/github.com/nmoniz/any2anexoj) [![Coverage Status](https://coveralls.io/repos/github/nmoniz/any2anexoj/badge.svg?branch=coveralls-badge)](https://coveralls.io/github/nmoniz/any2anexoj?branch=coveralls-badge) -![Screenshot](https://i.ibb.co/TDDypq8X/Screenshot-2025-11-18-at-16-17-16.png) +

+ Screenshot +

This tool converts the statements from known brokers and exchanges into a format compatible with section 9 from the Portuguese IRS form: [Mod_3_anexo_j](https://info.portaldasfinancas.gov.pt/pt/apoio_contribuinte/modelos_formularios/irs/Documents/Mod_3_anexo_J.pdf) > [!WARNING] > Although I made significant efforts to ensure the correctness of the calculations you should verify any outputs produced by this tool on your own or with a certified accountant. -> [!NOTE] -> This tool is in early stages of development. Use at your own risk! - ## Install ```bash @@ -24,3 +23,9 @@ go install github.com/nmoniz/any2anexoj/cmd/any2anexoj-cli@latest ```bash cat statement.csv | any2anexoj-cli --platform=tranding212 ``` + +## Rounding + +All Euro values are rounded to cents (2 decimal places) but internal calculations use the statement values with full precision. +There are no explicit rules or details about how to round Euro values in Anexo J. +This application rounds according to `Portaria n.º 1180/2001, art. 2.º, alínea c) e d)` (Ministerial Order / Government Order) examples, which imply we should round to the 2nd decimal place by rounding up (ceiling) or down (floor) depending on whether the third decimal place is ≥ 5 or < 5, respectively. -- 2.49.1 From 2a3f13e91a66c774367b630cb01a28a63aab35d0 Mon Sep 17 00:00:00 2001 From: Natercio Moniz Date: Wed, 26 Nov 2025 10:03:35 +0000 Subject: [PATCH 3/3] remove readme image width constraint --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 19ca8a5..72bb7d5 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Coverage Status](https://coveralls.io/repos/github/nmoniz/any2anexoj/badge.svg?branch=coveralls-badge)](https://coveralls.io/github/nmoniz/any2anexoj?branch=coveralls-badge)

- Screenshot + Screenshot

This tool converts the statements from known brokers and exchanges into a format compatible with section 9 from the Portuguese IRS form: [Mod_3_anexo_j](https://info.portaldasfinancas.gov.pt/pt/apoio_contribuinte/modelos_formularios/irs/Documents/Mod_3_anexo_J.pdf) -- 2.49.1