1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
package mapping
import (
"errors"
"testing"
"time"
"simenandre.no/accounted-multicase-intgr/internal/accounted"
"simenandre.no/accounted-multicase-intgr/internal/multicase"
)
func config() *Config {
return &Config{
Accounts: map[string]string{"3000": "3001", "1460": "1460", "4301": "4000"},
CustomerLedgerAccount: "1510",
VATCodes: map[string]VATCode{
"3": {Rate: 25, Account: "2611"},
"6": {Rate: 0},
},
RoundingAccount: "3740",
MaxRoundingOre: 100,
SourceType: "manual",
}
}
func voucher(lines ...multicase.Line) multicase.Voucher {
return multicase.Voucher{
Type: "11", TypeText: "Faktura", Number: "100", Text: "Demo kunde",
Date: time.Date(2026, 10, 1, 0, 0, 0, 0, time.UTC), Lines: lines,
}
}
func TestSplitNet(t *testing.T) {
tests := []struct {
gross, want int64
rate float64
}{
{270019, 216015, 25},
{-270019, -216015, 25},
{12500, 10000, 25},
{1, 1, 25},
{11200, 10000, 12},
{5000, 5000, 0},
}
for _, tt := range tests {
if got := SplitNet(tt.gross, tt.rate); got != tt.want {
t.Errorf("SplitNet(%d, %v) = %d, want %d", tt.gross, tt.rate, got, tt.want)
}
}
}
func TestBuildSplitsVATAndMapsCustomerLedger(t *testing.T) {
e, err := config().Build(voucher(
multicase.Line{Account: "3000", Amount: -270019, VATCode: "3", CustomerNo: "20001"},
multicase.Line{Account: "20001", Amount: 270019, CustomerNo: "20001"},
))
if err != nil {
t.Fatal(err)
}
want := []accounted.Line{
{Account: "3001", Credit: 216015},
{Account: "2611", Credit: 54004},
{Account: "1510", Debit: 270019},
}
if len(e.Lines) != len(want) {
t.Fatalf("lines = %+v", e.Lines)
}
for i, w := range want {
g := e.Lines[i]
if g.Account != w.Account || g.Debit != w.Debit || g.Credit != w.Credit {
t.Errorf("line %d = %+v, want %+v", i, g, w)
}
}
if e.SourceID != "multicase:11:100" || e.EntryDate != "2026-10-01" || e.Description != "Faktura 100: Demo kunde" {
t.Errorf("header = %+v", e)
}
}
func TestBuildReportsUnmapped(t *testing.T) {
_, err := config().Build(voucher(
multicase.Line{Account: "9999", Amount: -100},
multicase.Line{Account: "1460", Amount: 100, VATCode: "42"},
))
var u *UnmappedError
if !errors.As(err, &u) || len(u.Accounts) != 1 || u.Accounts[0] != "9999" || len(u.VATCodes) != 1 || u.VATCodes[0] != "42" {
t.Fatalf("err = %v", err)
}
}
func TestBuildRounding(t *testing.T) {
e, err := config().Build(voucher(
multicase.Line{Account: "1460", Amount: 1978734},
multicase.Line{Account: "4301", Amount: -1832162},
multicase.Line{Account: "4301", Amount: -146573},
))
if err != nil {
t.Fatal(err)
}
last := e.Lines[len(e.Lines)-1]
if last.Account != "3740" || last.Debit != 1 {
t.Errorf("rounding line = %+v", last)
}
_, err = config().Build(voucher(
multicase.Line{Account: "1460", Amount: 1000},
multicase.Line{Account: "4301", Amount: -500},
))
if err == nil {
t.Error("expected an error for a voucher off by more than max_rounding_ore")
}
}
func TestBuildSkipsZeroLines(t *testing.T) {
_, err := config().Build(voucher(multicase.Line{Account: "20001", Amount: 0, CustomerNo: "20001"}))
if !errors.Is(err, ErrEmpty) {
t.Fatalf("err = %v, want ErrEmpty", err)
}
}
|