-
Notifications
You must be signed in to change notification settings - Fork 5
/
MoneyMonkey.lua
351 lines (262 loc) · 11.5 KB
/
MoneyMonkey.lua
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
--
-- Export von MoneyMoney Umsätzen zu direkt importierbaren Steuer-Buchungssätzen.
--
-- Dieses Skript ist getestet mit MonKey Office, sollte aber im Prinzip auch mit allen
-- anderen Buchhaltungsprogrammen funktionieren, die Buchungssätze als CSV-Datei
-- direkt importieren können (was vermutlich nahezu alle sind).
--
-- Das Skript liest eine zweite Lua-Datei ein, in der sich die eigentliche Konfiguration
-- befindet. Diese muss an die eigenen Bedürfnisse und Verhältnisse angepasst werden.
--
-- Erforderliche MoneyMoney-Version: 2.3.25
-- CSV Dateieinstellungen
local encoding = "UTF-8"
local utf_bom = false
local linebreak = "\n"
-- Exportformat bei MoneyMoney anmelden
Exporter{version = 1.5,
options = {
{ label="Umsätze müssen als erledigt markiert sein", name="checkedOnly", default=true }
},
format = MM.localizeText("Buchungssätze"),
fileExtension = "csv",
reverseOrder = true,
description = MM.localizeText("Export von MoneyMoney Umsätzen zu direkt importierbaren Steuer-Buchungssätzen.")}
-- Definition der Reihenfolge und Titel der zu exportierenden Buchungsfelder
-- Format: Key (Internes Feld), Titel (in der ersten Zeile der CSV-Datei)
Exportdatei = {
{ "Datum", "Datum" },
{ "BelegNr", "BelegNr" },
{ "Referenz", "Referenz" },
{ "Betrag", "Betrag" },
{ "Waehrung", "Währung" },
{ "Text", "Text" },
{ "Finanzkonto", "KontoSoll" },
{ "Gegenkonto", "KontoHaben" },
{ "Steuersatz", "Steuersatz" },
{ "Kostenstelle1", "Kostenstelle1" },
{ "Kostenstelle2", "Kostenstelle2" },
{ "Notiz", "Notiz" }
}
--
-- Hilfsfunktionen zur String-Behandlung
--
local DELIM = "," -- Delimiter
local function csvField (str)
if str == nil or str == "" then
return ""
end
return '"' .. string.gsub(str, '"', '""') .. '"'
end
local function concatenate (...)
local catstring = ""
for _, str in pairs({...}) do
catstring = catstring .. ( str or "")
end
return catstring
end
--
-- WriteHeader: Erste Zeile der Exportdatei schreiben
--
function WriteHeader (account, startDate, endDate, transactionCount, options)
-- Write CSV header.
local line = ""
for Position, Eintrag in ipairs(Exportdatei) do
if Position ~= 1 then
line = line .. DELIM
end
line = line .. csvField(Eintrag[2])
end
assert(io.write(MM.toEncoding(encoding, line .. linebreak, utf_bom)))
print ("--------------- START EXPORT ----------------")
end
--
-- WriteHeader: Export abschließen
--
function WriteTail (account, options)
print ("--------------- END EXPORT ----------------")
end
function DruckeUmsatz(Grund, Umsatz)
print (string.format( "%s: %s / %s %s / %s / %s / %s\n",
Grund, Umsatz.Datum, Umsatz.Betrag, Umsatz.Waehrung,
Umsatz.Kategorie, Umsatz.Verwendungszweck, Umsatz.Notiz) )
end
-- Extrahiere Metadaten aus dem Kategorie-Titel
--
-- Übergeben wird ein KategoriePfad, der die Kategorie-Hierarchie
-- in MoneyMoney wiedergibt. Jede Kategorie in diesem Pfad kann
-- ein Gegenkonto, einen Steuersatz oder eine Kostenstelle spezifizieren.
--
-- Wird ein Gegenkonto oder Steuersatz mehrfach spezifiziert, überschreibt
-- jeweils das jeweils rechte Feld den Wert seines Vorgängers. Kostenstellen
-- ergänzen sich, es dürfen aber nur maximal zwei unterschiedliche Kostenstellen
-- angegeben werden.
function UmsatzMetadaten (KategoriePfad, Kommentar)
local KategoriePfadNeu
local Gegenkonto, Steuersatz, KS1, KS2
local AnzahlKostenstellen = 1
local Kostenstellen = {}
for Kategorie in string.gmatch(KategoriePfad, "([^\\]+)") do
-- Ist dem Titel eine Konfiguration angehängt worden?
local i, _, Metadaten = string.find ( Kategorie, "([%[{#].*)$")
-- Dann Metadaten extrahieren
if Metadaten then
-- Metadaten aus dem Kategorie-Titel entfernen
Kategorie = string.sub (Kategorie, 1, i - 1)
-- Konto in eckigen Klammern ("[6851]")
_, _, Konto = string.find (Metadaten, "%[(%d+)%]")
if Konto then
Gegenkonto = Konto
end
-- Steuersatz in geschweiften Klammern ("{VSt7}")
_, _, Text = string.find (Metadaten, "{(.+)}")
if Text then
Steuersatz = Text
end
-- Kostenstelle 1 und 2 mit Hashzeichen ("#1000")
for Nummer in string.gmatch(Metadaten, "#(%w+)%s*") do
if AnzahlKostenstellen > 2 then
error(string.format("Der Export wurde abgebrochen, da mehr als zwei Kostenstellen über die Kategorie angegeben wurde.\n\nKategorie:\t%s\n", Kategorie), 0)
end
Kostenstellen[AnzahlKostenstellen] = Nummer
AnzahlKostenstellen = AnzahlKostenstellen + 1
end
end
-- Leading/Trailing Whitespace aus dem verbliebenen Kategorie-Titel entfernen
_, _, Kategorie = string.find (Kategorie, "%s*(.-)%s*$")
-- Neuen Kategoriepfad aufbauen
if KategoriePfadNeu then
KategoriePfadNeu = KategoriePfadNeu .. " - " .. Kategorie
else
KategoriePfadNeu = Kategorie
end
end
-- Umsatz-Kommentar nach Kostenstellen oder Steuersätzen durchsuchen
KommentarNeu = Kommentar
for KS in string.gmatch(Kommentar, "#(%w+)%s*") do
if AnzahlKostenstellen > 2 then
error(string.format("Der Export wurde abgebrochen, da zu viele weitere Kostenstellen in den Notizen angegeben wurden.\n\nKategorie:\t%s\nNotiz:\t%s\nKostenstelle 1:\t%s\nKostenstelle 2:\t%s", Kategorie, Notiz, Kostenstellen[1], Kostenstellen[2]), 0)
end
Kostenstellen[AnzahlKostenstellen] = KS
AnzahlKostenstellen = AnzahlKostenstellen + 1
KommentarNeu = string.gsub(KommentarNeu, "#" .. KS .. "%s*", "")
end
Begin, End, Text = string.find (Kommentar, "{(.+)}")
if Text then
Steuersatz = Text
KommentarNeu = string.sub(KommentarNeu, 1, Begin) .. string.sub(KommentarNeu, End)
KommentarNeu = string.gsub(KommentarNeu, "{" .. Text .. "}%s*", "")
end
KommentarNeu = string.gsub(KommentarNeu, "%s*$", "")
-- Alle extrahierten Werte zurückliefern
return KategoriePfadNeu, KommentarNeu, Gegenkonto, Steuersatz, Kostenstellen[1], Kostenstellen[2]
end
--
-- WriteTransactions: Jede Buchung in eine Zeile der Exportdatei schreiben
--
function WriteTransactions (account, transactions, options)
for _,transaction in ipairs(transactions) do
-- Trage Umsatzdaten aus der Transaktion in der später zu exportierenden Form zusammen
local Exportieren = true
-- Zu übertragende Umsatzinformationen in eigener Struktur zwischenspeichern
-- und einfache Feldinhalte aus Transaktion übernehmen
local Umsatz = {
Typ = transaction.bookingText,
Name = transaction.name or "",
Kontonummer = transaction.accountNumber or "",
Bankcode = transaction.bankcode or "",
Datum = MM.localizeDate(transaction.bookingDate),
Betrag = transaction.amount,
Kommentar = transaction.comment or "",
Verwendungszweck = transaction.purpose or "",
Waehrung = transaction.currency or ""
}
-- Daten für den zu schreibenden Buchungsdatensatz
local Buchung = {
Umsatzart = Umsatz.Typ,
Datum = Umsatz.Datum,
Text = nil,
Finanzkonto = nil,
Gegenkonto = nil,
Betrag = nil,
Steuersatz = nil,
Kostenstelle1 = nil,
Kostenstelle2 = nil,
BelegNr = string.gsub(io.filename, ".*/", ""), -- Dateiname des Exports ist die Belegnummer
Referenz = Umsatz.Referenz,
Waehrung = Umsatz.Waehrung,
Notiz = ""
}
-- Einlesen der Konto-spezifischen Konfiguration aus den Konto-Attributen bzw. dem Kommentarfeld
local Bankkonto = {}
for Kennzeichen, Wert in pairs(account.attributes) do
Bankkonto[Kennzeichen] = Wert
end
for Kennzeichen, Wert in string.gmatch(account.comment, "(%g+)=(%g+)") do
Bankkonto[Kennzeichen] = Wert
end
-- Finanzkonto für verwendetes Bankkonto ermitteln
if ( Bankkonto.Finanzkonto == "" ) then
error ( string.format("Kein Finanzkonto für Konto %s gesetzt.\n\nBitte Feld 'Finanzkonto' in den benutzerdefinierten Feldern in den Einstellungen zum Konto setzen.", account.name ), 0)
end
Buchung.Finanzkonto = Bankkonto.Finanzkonto
-- Extrahiere Buchungsinformationen aus dem Kategorie-Text und Kommentar
Umsatz.Kategorie, Umsatz.Kommentar, Buchung.Gegenkonto, Buchung.Steuersatz,
Buchung.Kostenstelle1, Buchung.Kostenstelle2 = UmsatzMetadaten (transaction.category, Umsatz.Kommentar)
Buchung.Text = Umsatz.Name .. ": " .. Umsatz.Verwendungszweck .. ((Umsatz.Kommentar ~= "") and ( " (" .. Umsatz.Kommentar .. ")") or "")
Buchung.Notiz = ((Umsatz.Kontonummer ~= "") and ( "(" .. Umsatz.Kontonummer .. ") ") or "") -- .. "[" .. Umsatz.Kategorie .. "] {" .. Umsatz.Typ .. "}"
-- Vorgemerkte Buchungen nicht exportieren
if ( transaction.booked == false) then
Exportieren = false
end
-- Buchungen mit Betrag 0,00 nicht exportieren
if ( transaction.amount == 0) then
Exportieren = false
end
-- Buchungen mit Gegenkonto 0000 nicht exportieren
if ( tonumber(Buchung.Gegenkonto) == 0) then
Exportieren = false
end
-- Wenn für das Bankkonto eine Währung spezifiziert ist muss der Umsatz in dieser Währung vorliegen
if Bankkonto.Waehrung and (Bankkonto.Waehrung ~= Umsatz.Waehrung) then
Exportieren = false
end
-- Export der Buchung vorbereiten
if (transaction.amount > 0) then
Buchung.Betrag = MM.localizeNumber("0.00", transaction.amount)
else
Buchung.Betrag = MM.localizeNumber("0.00", - transaction.amount)
Buchung.Finanzkonto, Buchung.Gegenkonto = Buchung.Gegenkonto, Buchung.Finanzkonto
end
-- Buchung exportieren
if Exportieren then
if options ~= nil then
if options.checkedOnly and transaction.checkmark == false then
error(string.format("Der Export wurde abgebrochen, da ein Umsatz nicht als erledigt markiert wurde.\n\nBetroffener Umsatz:\nKonto:\t%s\nDatum:\t%s\nName:\t%s\nBetrag:\t%.2f\t%s\nKategorie:\t%s\nZweck:\t%s\nNotiz:\t%s", account.name, Umsatz.Datum, Umsatz.Name, Umsatz.Betrag, Umsatz.Waehrung, Umsatz.Kategorie, Umsatz.Verwendungszweck, Umsatz.Notiz), 0)
end
end
if Buchung.Finanzkonto and Buchung.Gegenkonto then
local line = ""
for Position, Eintrag in ipairs(Exportdatei) do
if Position ~= 1 then
line = line .. DELIM
end
line = line .. csvField(Buchung[Eintrag[1]])
end
assert(io.write(MM.toEncoding(encoding, line .. linebreak, utf_bom)))
else
DruckeUmsatz ("UNVOLLSTÄNDIG", Umsatz)
if (Umsatz.Kategorie == nil) then
error(string.format("Der Export wurde abgebrochen, da einem Umsatz keine Kategorie zugewiesen wurde.\n\nBetroffener Umsatz:\nKonto:\t%s\nDatum:\t%s\nName:\t%s\nBetrag:\t%s %s\nZweck:\t%s\nNotiz:\t%s", account.name, Umsatz.Datum, Umsatz.Name, Umsatz.Betrag, Umsatz.Waehrung, Umsatz.Verwendungszweck, Umsatz.Notiz), 0)
else
error(string.format("Der Export wurde abgebrochen, da die Kontenzuordnung für die Buchhaltung unvollständig ist (Finanzkonto: %s Gegenkonto: %s).\n\nBetroffener Umsatz:\nKonto:\t%s\nDatum:\t%s\nName:\t%s\nBetrag:\t%s\t%s\nKategorie:\t%s\nZweck:\t%s\nNotiz:\t%s", Buchung.Finanzkonto, Buchung.Gegenkonto, account.name, Umsatz.Datum, Umsatz.Name, Umsatz.Betrag, Umsatz.Waehrung, Umsatz.Kategorie, Umsatz.Verwendungszweck, Umsatz.Notiz), 0)
end
end
else
DruckeUmsatz ("ÜBERSPRUNGEN", Umsatz)
end
end
end
function WriteTail (account)
-- Nothing to do.
end