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