Compare commits
4 Commits
4a2884a0df
...
066b5b76a8
| Author | SHA1 | Date | |
|---|---|---|---|
| 066b5b76a8 | |||
| 2a3f13e91a | |||
| 0b6b35e736 | |||
| 5060fca7be |
13
README.md
13
README.md
@@ -3,16 +3,15 @@
|
||||
[](https://goreportcard.com/report/github.com/nmoniz/any2anexoj)
|
||||
[](https://coveralls.io/github/nmoniz/any2anexoj?branch=coveralls-badge)
|
||||
|
||||

|
||||
<p align="center">
|
||||
<img src="https://i.ibb.co/0yRtwq2C/0-FBA40-FD-D97-A-4-AFB-8618-49582-DB98-F3-C.png" alt="Screenshot" border="0">
|
||||
</p>
|
||||
|
||||
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,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)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user