aboutsummaryrefslogtreecommitdiffstats
path: root/internal/mapping/mapping_test.go
blob: d9190cda02613ea0ba3c29721a53f39a005e688a (plain)
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)
	}
}