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