more rules, need to add exceptions
[outofuni/gocash.git] / gocash.go
1 package main
2
3 import (
4         "encoding/xml"
5         "fmt"
6         "io/ioutil"
7         "os"
8 )
9
10 //
11 // hardcoded account ids we have to look at
12 //
13 // --- buy
14 // wareneingang 19% and 7%
15 const pid_buy_n = string("8e3b7c42e3173ed85f3d4736e82afb4d")
16 const pid_buy_s = string("0cfd2ceb45fff89b9d1b7ce3af66cdf3")
17 const pid_misc  = string("e3acc2865dbf931e41cf2b90240de5c2")
18 const pid_rep   = string("b1d04ad157cac569f4299d4ddf94ed6f")
19 const pid_room  = string("4394ed4ffa7266f8f8731080926a7a61")
20 const pid_cap   = string("4196ee026d1bdb785df2c975fca91ae0")
21 // abziehbare vst 19% and 7%
22 const aid_vst_n = string("7c449e13125d6b93043f963628106db2")
23 const aid_vst_s = string("006643c1c0a91f2b40614c75a49c6295")
24 // --- sales
25 // receipts
26 const aid_rec_n = string("f3e905732b729ba096a50dab60559ce7")
27 const aid_rec_s = string("66c1b04bd897766cb2be538094e1db6a")
28 const aid_tip   = string("1d20024badc11a99a8e1cf3a9a64a501")
29 const aid_dep   = string("9772f4e231f6f5e3100132cc53eb3447")
30 // ust
31 const aid_ust_n = string("e4bd6ff52408be8076f24aeb105893d9")
32 const aid_ust_s = string("38bf40d16529f2a1e611c073c6c1dc9c")
33
34 // account maps
35 type amap struct {
36         pid string // parent id
37         num int // account number
38         taxval int // 7 or 19
39         buy bool // buy or sales
40         tax bool // tax or non-tax(=goods) account
41         rid []string // required transaction account(s)
42 }
43
44 // xml
45 type Account struct {
46         XMLName xml.Name `xml:"account"`
47         Name string `xml:"name"`
48         AccountId string `xml:"id"`
49         ParentId string `xml:"parent"`
50 }
51 type Split struct {
52         XMLName xml.Name `xml:"split"`
53         Id string `xml:"id"`
54         Value string `xml:"value"`
55         Quantity string `xml:"quantity"`
56         AccountId string `xml:"account"`
57 }
58 type Transaction struct {
59         XMLName xml.Name `xml:"transaction"`
60         Id string `xml:"id"`
61         Date string `xml:"date-posted>date"`
62         Description string `xml:"description"`
63         Spl []Split `xml:"splits>split"`
64 }
65 type ParsedData struct {
66         XMLName xml.Name `xml:"gnc-v2"`
67         DataCnt []string `xml:"count-data"`
68         Accnt []Account `xml:"book>account"`
69         Trn []Transaction `xml:"book>transaction"`
70 }
71
72 // 'global' data
73 var data ParsedData
74
75 func main() {
76
77         // open xml file
78         file, err := os.Open("c13_skr03.gnucash")
79         if err != nil {
80                 fmt.Println("Error opening file:", err)
81                 return
82         }
83         defer file.Close()
84
85         // read xml file
86         xmldata, err := ioutil.ReadAll(file)
87         if err != nil {
88                 fmt.Println("Error reading file:", err)
89                 return
90         }
91
92         // unmarshal xml data
93         err = xml.Unmarshal(xmldata,&data)
94         if err != nil {
95                 fmt.Println("Error unmarshaling xml data:", err)
96                 return
97         }
98
99         // whooha, this is our data!
100         fmt.Println("Parsed accounts:",len(data.Accnt))
101         fmt.Println("Parsed transactions:",len(data.Trn))
102
103         accnt := make(map[string]amap)
104
105         for ac := range data.Accnt {
106                 aid := data.Accnt[ac].AccountId
107                 pid := data.Accnt[ac].ParentId
108                 // general map
109                 rid := make ([]string,10,10)
110                 accnt[aid]=amap{
111                         pid,ac,0,false,false,rid,
112                 }
113                 rid[0]="NONE"
114                 tmp := accnt[aid]
115                 switch {
116                 // ---- buy
117                 //   -- goods
118                 case pid == pid_buy_n || pid == pid_misc || pid == pid_rep || pid == pid_room || pid == pid_cap:
119                         tmp.taxval=19
120                         tmp.buy=true
121                         rid[0]=aid_vst_n
122                         accnt[aid]=tmp
123                 case pid == pid_buy_s:
124                         tmp.taxval=7
125                         tmp.buy=true
126                         rid[0]=aid_vst_s
127                         accnt[aid]=tmp
128                 //   -- tax
129                 case aid == aid_vst_n:
130                         tmp.taxval=19
131                         tmp.buy=true
132                         tmp.tax=true
133                         rid=[]string{pid_buy_n,pid_misc,pid_rep,pid_room,pid_cap,}
134                         accnt[aid]=tmp
135                 case aid == aid_vst_s:
136                         tmp.taxval=7
137                         tmp.buy=true
138                         tmp.tax=true
139                         rid[0]=pid_buy_s
140                         accnt[aid]=tmp
141                 // ---- sales ----
142                 //   -- receipts
143                 case aid == aid_rec_n || aid == aid_tip || aid == aid_dep:
144                         tmp.taxval=19
145                         rid[0]=aid_ust_n
146                         accnt[aid]=tmp
147                 case aid == aid_rec_s:
148                         tmp.taxval=7
149                         rid[0]=aid_ust_s
150                         accnt[aid]=tmp
151                 //   -- tax
152                 case aid == aid_ust_n:
153                         tmp.taxval=19
154                         tmp.tax=true
155                         rid=[]string{aid_rec_n,aid_tip,aid_dep,}
156                         accnt[aid]=tmp
157                 case aid == aid_ust_s:
158                         tmp.taxval=7
159                         tmp.tax=true
160                         rid[0]=aid_rec_s
161                         accnt[aid]=tmp
162                 }
163         }
164
165         // check transactions ...
166         for tc := range data.Trn {
167                 // ... and all the accounts involved
168                 for tsc := range data.Trn[tc].Spl {
169                         aid := data.Trn[tc].Spl[tsc].AccountId
170                         if check_trn(&data.Trn[tc],accnt,aid) == false {
171                                 ac := accnt[aid].num
172                                 fmt.Println("P:",data.Accnt[ac].Name,"at",data.Trn[tc].Date)
173
174                         }
175                 }
176         }
177
178 }
179
180 func check_trn(ta *Transaction,accnt map[string]amap,aid string) bool {
181         //ac := accnt[aid].num
182         if accnt[aid].rid[0]=="NONE" {
183                 return true
184         }
185         for ra := range accnt[aid].rid {
186                 for ea := range ta.Spl {
187                         oaid := ta.Spl[ea].AccountId
188                         switch {
189                         case accnt[aid].tax && accnt[aid].buy:
190                                 // check pids
191                                 if accnt[oaid].pid == accnt[aid].rid[ra] {
192                                         return true
193                                 }
194                         default:
195                                 // check aids
196                                 if ta.Spl[ea].AccountId == accnt[aid].rid[ra] {
197                                         return true
198                                 }
199                         }
200                         //fmt.Println(data.Accnt[accnt[oaid].num].Name)
201                 }
202         }
203         // exceptions
204         //if !ok {
205         //}
206         return false
207 }
208