777 lines
22 KiB
Go
777 lines
22 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"github.com/go-pdf/fpdf"
|
|
"golang.org/x/text/language"
|
|
"golang.org/x/text/message"
|
|
"log"
|
|
"math"
|
|
"os"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"text/template"
|
|
"time"
|
|
)
|
|
|
|
/* own types */
|
|
type TransactionItem struct {
|
|
Description string
|
|
Debit string
|
|
Credit string
|
|
Amount float64
|
|
Vat_code string
|
|
Amount_Vat float64
|
|
}
|
|
|
|
type Transaction struct {
|
|
Date time.Time
|
|
Items []TransactionItem
|
|
}
|
|
|
|
type Balance struct {
|
|
balance_start float64
|
|
balance_end float64
|
|
}
|
|
|
|
type Data struct {
|
|
GenerationTime string
|
|
ReportingPeriodFrom string
|
|
ReportingPeriodTill string
|
|
BusinessReferenceId string
|
|
TotalConsideration string
|
|
DataTaxRates map[string]string
|
|
InputTaxInvestments string
|
|
PayableTax string
|
|
}
|
|
|
|
/* global variable declaration */
|
|
var pdf *fpdf.Fpdf
|
|
var yPos float64
|
|
var accounts = make(map[string]string)
|
|
var transactions = make(map[uint16]Transaction)
|
|
var account_balance = make(map[string]Balance)
|
|
var balanceYear = "UNDEFINED"
|
|
var profit float64 = 0.0
|
|
|
|
const reportTitle = "Jahresrechnung - nbit Informatik GmbH"
|
|
const journalTitle = "Journal - nbit Informatik GmbH"
|
|
const MWST_ACCOUNT = "2201"
|
|
const PROFIT_ACCOUNT = "2970"
|
|
const defaultFontSize = 12
|
|
const smallFontSize = 8
|
|
const marginTop = 10
|
|
const smallLineSpacing = 5
|
|
const lineSpacing = 6
|
|
const tabstopLeft = 35
|
|
const tabstopRight = 160
|
|
const widthAmount = 28
|
|
const dashCorrectionXleft = 1.2
|
|
const dashCorrectionXright = -1.3
|
|
const dashCorrectionY = 3.5
|
|
const avoidMinusZero = 0.00001
|
|
|
|
func roundRappen(f float64) float64 {
|
|
return (math.Round(f*100) / 100)
|
|
}
|
|
|
|
func accountExists(s string) bool {
|
|
_, ok := accounts[s]
|
|
return ok
|
|
}
|
|
|
|
func accountType(s string) string {
|
|
return accounts[s][0:1]
|
|
}
|
|
|
|
func accountDescription(s string) string {
|
|
return accounts[s][2:]
|
|
}
|
|
|
|
func accountBalanceExists(s string) bool {
|
|
_, ok := account_balance[s]
|
|
return ok
|
|
}
|
|
|
|
func calculateVAT(vat_code string, amount float64) float64 {
|
|
if vat_code == "" {
|
|
return 0.0
|
|
}
|
|
vat_type := string(vat_code[0])
|
|
vat_perc, err := strconv.Atoi(vat_code[1:])
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
|
|
return 0.0
|
|
}
|
|
vat_perc_f64 := float64(vat_perc)
|
|
if vat_type == "V" {
|
|
return roundRappen(0 - (amount / (1000.0 + vat_perc_f64) * vat_perc_f64))
|
|
} else if vat_type == "I" {
|
|
return roundRappen(amount / (1000.0 + vat_perc_f64) * vat_perc_f64)
|
|
} else {
|
|
fmt.Fprintf(os.Stderr, "WARNING: Invalid Vat Type: %s\n", vat_type)
|
|
return 0.0
|
|
}
|
|
}
|
|
|
|
func addTransaction(document_number string, date string, text string, account_number_debit string, account_number_credit string, amount string, vat_code string) {
|
|
dateString := "02.01.2006"
|
|
mydate, error := time.Parse(dateString, date)
|
|
|
|
if error != nil {
|
|
fmt.Fprintf(os.Stderr, "ERROR: %v\n", error)
|
|
return
|
|
}
|
|
|
|
// we use the first transaction to set the year, all transactions not in this year will be invalid
|
|
if balanceYear == "UNDEFINED" {
|
|
balanceYear = date[6:10]
|
|
} else {
|
|
if date[6:10] != balanceYear {
|
|
fmt.Fprintf(os.Stderr, "WARNING: transaction with Document Number %s is not in same year as first transaction: %s and will be ignored\n", document_number, balanceYear)
|
|
return
|
|
}
|
|
}
|
|
|
|
var myItem TransactionItem
|
|
|
|
document_number_i, err := strconv.ParseInt(document_number, 0, 16)
|
|
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
myItem.Description = text
|
|
myItem.Debit = account_number_debit
|
|
myItem.Credit = account_number_credit
|
|
if s, err := strconv.ParseFloat(amount, 64); err == nil {
|
|
myItem.Amount = roundRappen(s)
|
|
} else {
|
|
fmt.Fprintf(os.Stderr, "WARNING: Document %s, cannot convert Amount to Float64: %s and will be ignored\n", document_number, amount)
|
|
return
|
|
}
|
|
myItem.Vat_code = vat_code
|
|
myItem.Amount_Vat = calculateVAT(vat_code, myItem.Amount)
|
|
|
|
// adjust balance values
|
|
var myBalance Balance
|
|
myBalance = account_balance[account_number_debit]
|
|
account_type := accountType(account_number_debit)
|
|
|
|
if (account_type == "A") || (account_type == "E") || (account_type == "L") {
|
|
myBalance.balance_end = myBalance.balance_end + myItem.Amount
|
|
} else {
|
|
myBalance.balance_end = myBalance.balance_end - myItem.Amount
|
|
}
|
|
if myItem.Amount_Vat > 0 {
|
|
myBalance.balance_end = myBalance.balance_end - myItem.Amount_Vat
|
|
}
|
|
account_balance[account_number_debit] = myBalance
|
|
|
|
myBalance = account_balance[account_number_credit]
|
|
account_type2 := accountType(account_number_credit)
|
|
if (account_type2 == "A") || (account_type2 == "E") || (account_type2 == "L") {
|
|
myBalance.balance_end = myBalance.balance_end - myItem.Amount
|
|
} else {
|
|
myBalance.balance_end = myBalance.balance_end + myItem.Amount
|
|
}
|
|
if myItem.Amount_Vat < 0 {
|
|
myBalance.balance_end = myBalance.balance_end + myItem.Amount_Vat
|
|
}
|
|
account_balance[account_number_credit] = myBalance
|
|
|
|
myBalance = account_balance[MWST_ACCOUNT]
|
|
myBalance.balance_end = myBalance.balance_end + myItem.Amount_Vat
|
|
account_balance[MWST_ACCOUNT] = myBalance
|
|
|
|
_, ok := transactions[uint16(document_number_i)]
|
|
if ok {
|
|
newtransactions := transactions[uint16(document_number_i)]
|
|
newtransactions.Items = append(newtransactions.Items, myItem)
|
|
transactions[uint16(document_number_i)] = newtransactions
|
|
} else {
|
|
var t Transaction
|
|
t.Date = mydate
|
|
t.Items = append(t.Items, myItem)
|
|
transactions[uint16(document_number_i)] = t
|
|
}
|
|
}
|
|
|
|
func readAccountData(filename string) {
|
|
file, err := os.Open(filename)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer file.Close()
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
line_number := 1
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
matched, _ := regexp.MatchString(`^[0-9][0-9][0-9][0-9],[ALEI]:.*`, line)
|
|
if !matched {
|
|
fmt.Fprintf(os.Stderr, "WARNING: Line %v in Accountfile %s: line not four digits followed by a comma followed by ALEI (one of those), Colon and description: %s and will be ignored.\n", line_number, filename, line)
|
|
continue
|
|
}
|
|
|
|
token := strings.SplitN(line, ",", 2)
|
|
account_number := token[0]
|
|
account_type_and_description := token[1]
|
|
|
|
if accountExists(account_number) {
|
|
fmt.Fprintf(os.Stderr, "WARNING: Line %v in Accountfile %s: Account Number %s already exists and will be ignored\n", line_number, filename, account_number)
|
|
continue
|
|
|
|
}
|
|
|
|
accounts[account_number] = account_type_and_description
|
|
|
|
line_number++
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func floatToString(f float64, sep string) string {
|
|
var s string
|
|
p := message.NewPrinter(language.English)
|
|
if roundRappen(f) == 0.00 {
|
|
s = "-.-"
|
|
} else {
|
|
s = strings.ReplaceAll(p.Sprintf("%.2f", f), ",", sep)
|
|
//fmt.Fprintf(os.Stderr,"--- s: @%s@\n", s)
|
|
}
|
|
return s
|
|
}
|
|
|
|
func str2float64(f string) float64 {
|
|
if s, err := strconv.ParseFloat(f, 64); err == nil {
|
|
return roundRappen(s)
|
|
} else {
|
|
fmt.Fprintf(os.Stderr, "WARNING: cannot convert Amount to Float64: %s and will be ignored\n", f)
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func readTransactionData(filename string) {
|
|
file, err := os.Open(filename)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer file.Close()
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
line_number := 1
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
matched, _ := regexp.MatchString(`^[0-9]+,[0-9][0-9]\.[0-9][0-9]\.[0-9][0-9][0-9][0-9],.*,[0-9][0-9][0-9][0-9],[0-9][0-9][0-9][0-9],-?[0-9]+\.[0-9][0-9],.*`, line)
|
|
if matched {
|
|
token := strings.Split(line, ",")
|
|
document_number := token[0]
|
|
date := token[1]
|
|
text := strings.Replace(token[2], "@", ",", -1)
|
|
account_number_debit := token[3]
|
|
account_number_credit := token[4]
|
|
amount := token[5]
|
|
vat_code := token[6]
|
|
|
|
if !accountExists(account_number_debit) {
|
|
fmt.Fprintf(os.Stderr, "WARNING: Line %v in Transactionfile %s: Account Number Debit %s does not exist, and will be ignored\n", line_number, filename, account_number_debit)
|
|
continue
|
|
}
|
|
if !accountExists(account_number_credit) {
|
|
fmt.Fprintf(os.Stderr, "WARNING: Line %v in Transactionfile %s: Account Number Credit %s does not exist and will be ignored\n", line_number, filename, account_number_credit)
|
|
continue
|
|
}
|
|
|
|
addTransaction(document_number, date, text, account_number_debit, account_number_credit, amount, vat_code)
|
|
|
|
} else {
|
|
matched2, _ := regexp.MatchString(`^[0-9][0-9][0-9][0-9]:-?[0-9]+\.[0-9][0-9]$`, line)
|
|
if matched2 {
|
|
token := strings.Split(line, ":")
|
|
account_number := token[0]
|
|
balance := token[1]
|
|
if !accountExists(account_number) {
|
|
fmt.Fprintf(os.Stderr, "WARNING: Line %v in Transactionfile %s: Account Number %s does not exist and will be ignored\n", line_number, filename, account_number)
|
|
continue
|
|
}
|
|
|
|
f := str2float64(balance)
|
|
|
|
var myBalance Balance
|
|
myBalance.balance_start = f
|
|
myBalance.balance_end = f
|
|
account_balance[account_number] = myBalance
|
|
|
|
} else {
|
|
fmt.Fprintf(os.Stderr, "WARNING: Line %v in Transactionfile %s is not of the form <document number>,<date>,<text>,<account number debit>,<account number credit>,<amount>,<vat code> and will be ignored.\n", line_number, filename, line)
|
|
continue
|
|
}
|
|
line_number++
|
|
}
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func writeText(x float64, y float64, w float64, text string, alignStr ...string) {
|
|
tr := pdf.UnicodeTranslatorFromDescriptor("")
|
|
align := "LT"
|
|
if len(alignStr) > 0 {
|
|
align = alignStr[0]
|
|
}
|
|
pdf.SetXY(x, y)
|
|
pdf.CellFormat(w, 11, tr(text), "", 0, align, false, 0, "")
|
|
}
|
|
|
|
func setupBalanceSheet() {
|
|
pdf = fpdf.New("P", "mm", "A4", "")
|
|
pdf.SetMargins(0, 0, 0)
|
|
pdf.SetAutoPageBreak(false, 0)
|
|
pdf.SetFontLocation("fonts")
|
|
pdf.AddFont("Dejavusans", "", "DejaVuSans.json")
|
|
pdf.AddFont("Dejavusans-Bold", "", "DejaVuSans-Bold.json")
|
|
pdf.SetFont("Dejavusans", "", defaultFontSize)
|
|
}
|
|
|
|
func printPageHeader() {
|
|
pdf.AddPage()
|
|
yPos = marginTop
|
|
pdf.SetFont("Dejavusans-Bold", "", defaultFontSize)
|
|
writeText(tabstopLeft, yPos, 0, reportTitle)
|
|
yPos = yPos + lineSpacing
|
|
pdf.SetFont("Dejavusans", "", smallFontSize)
|
|
writeText(tabstopLeft, yPos, 0, "per 31.12."+balanceYear)
|
|
yPos = yPos + lineSpacing + smallLineSpacing
|
|
}
|
|
|
|
func printSection(section string) {
|
|
var title string
|
|
var change_sign bool = false
|
|
switch section {
|
|
// Assets
|
|
case "A":
|
|
title = "AKTIVEN"
|
|
// Liabilities
|
|
case "L":
|
|
title = "PASSIVEN"
|
|
change_sign = true
|
|
// Expense
|
|
case "E":
|
|
title = "AUFWAND"
|
|
// Income
|
|
case "I":
|
|
title = "ERTRAG"
|
|
default:
|
|
fmt.Fprintf(os.Stderr, "WARNING: invalid section: %s\n", section)
|
|
return
|
|
}
|
|
|
|
var total float64 = 0.0
|
|
pdf.SetFont("Dejavusans-Bold", "", smallFontSize)
|
|
writeText(tabstopLeft, yPos, 0, title)
|
|
writeText(tabstopRight, yPos, widthAmount, "31.12."+balanceYear, "TR")
|
|
pdf.SetDashPattern([]float64{}, 0)
|
|
pdf.Line(tabstopLeft+dashCorrectionXleft, yPos+dashCorrectionY, tabstopRight+widthAmount+dashCorrectionXright, yPos+dashCorrectionY)
|
|
yPos = yPos + smallLineSpacing
|
|
pdf.SetFont("Dejavusans", "", smallFontSize)
|
|
pdf.SetDashPattern([]float64{0.2, 0.2}, 0)
|
|
|
|
// Extract keys from map
|
|
keys := make([]string, 0, len(account_balance))
|
|
for k := range account_balance {
|
|
keys = append(keys, k)
|
|
}
|
|
|
|
// Sort keys
|
|
sort.Strings(keys)
|
|
|
|
for _, key := range keys {
|
|
if accountType(key) == section {
|
|
writeText(tabstopLeft, yPos, 0, key+": "+accountDescription(key))
|
|
balance_end := account_balance[key].balance_end
|
|
if change_sign {
|
|
balance_end = 0 - balance_end
|
|
}
|
|
writeText(tabstopRight, yPos, widthAmount, floatToString(balance_end, "'"), "TR")
|
|
pdf.Line(tabstopLeft+dashCorrectionXleft, yPos+dashCorrectionY, tabstopRight+widthAmount+dashCorrectionXright, yPos+dashCorrectionY)
|
|
total = total + balance_end
|
|
yPos = yPos + smallLineSpacing
|
|
}
|
|
}
|
|
|
|
switch section {
|
|
case "A":
|
|
if profit < 0 {
|
|
writeText(tabstopLeft, yPos, 0, "Verlust")
|
|
writeText(tabstopRight, yPos, widthAmount, floatToString(0-profit, "'"), "TR")
|
|
pdf.Line(tabstopLeft+dashCorrectionXleft, yPos+dashCorrectionY, tabstopRight+widthAmount+dashCorrectionXright, yPos+dashCorrectionY)
|
|
total = total - profit
|
|
yPos = yPos + smallLineSpacing
|
|
}
|
|
case "L":
|
|
if profit >= 0 {
|
|
writeText(tabstopLeft, yPos, 0, "Gewinn")
|
|
writeText(tabstopRight, yPos, widthAmount, floatToString(profit, "'"), "TR")
|
|
pdf.Line(tabstopLeft+dashCorrectionXleft, yPos+dashCorrectionY, tabstopRight+widthAmount+dashCorrectionXright, yPos+dashCorrectionY)
|
|
total = total + profit
|
|
yPos = yPos + smallLineSpacing
|
|
}
|
|
}
|
|
pdf.SetFont("Dejavusans-Bold", "", smallFontSize)
|
|
writeText(tabstopLeft, yPos, 0, "TOTAL "+title)
|
|
writeText(tabstopRight, yPos, widthAmount, floatToString(total, "'"), "TR")
|
|
pdf.SetDashPattern([]float64{}, 0)
|
|
pdf.Line(tabstopLeft+dashCorrectionXleft, yPos+dashCorrectionY, tabstopRight+widthAmount+dashCorrectionXright, yPos+dashCorrectionY)
|
|
yPos = yPos + smallLineSpacing
|
|
pdf.SetDashPattern([]float64{0.2, 0.2}, 0)
|
|
pdf.SetFont("Dejavusans", "", smallFontSize)
|
|
switch section {
|
|
case "E":
|
|
if profit < 0 {
|
|
writeText(tabstopLeft, yPos, 0, "Verlust")
|
|
writeText(tabstopRight, yPos, widthAmount, floatToString(0-profit, "'"), "TR")
|
|
pdf.Line(tabstopLeft+dashCorrectionXleft, yPos+dashCorrectionY, tabstopRight+widthAmount+dashCorrectionXright, yPos+dashCorrectionY)
|
|
total = total - profit
|
|
yPos = yPos + smallLineSpacing
|
|
}
|
|
case "I":
|
|
if profit >= 0 {
|
|
writeText(tabstopLeft, yPos, 0, "Gewinn")
|
|
writeText(tabstopRight, yPos, widthAmount, floatToString(profit, "'"), "TR")
|
|
pdf.Line(tabstopLeft+dashCorrectionXleft, yPos+dashCorrectionY, tabstopRight+widthAmount+dashCorrectionXright, yPos+dashCorrectionY)
|
|
total = total + profit
|
|
yPos = yPos + smallLineSpacing
|
|
}
|
|
}
|
|
yPos = yPos + smallLineSpacing
|
|
|
|
}
|
|
|
|
func createBalanceSheet() {
|
|
setupBalanceSheet()
|
|
printPageHeader()
|
|
printSection("A")
|
|
printSection("L")
|
|
printPageHeader()
|
|
printSection("E")
|
|
printSection("I")
|
|
err := pdf.OutputFileAndClose("output.pdf")
|
|
if err == nil {
|
|
fmt.Fprintf(os.Stderr, "INFO: Successfully created Balance Sheet in file output.pdf\n")
|
|
} else {
|
|
fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
|
|
}
|
|
}
|
|
|
|
func calculateProfit() float64 {
|
|
var res float64 = 0.0
|
|
|
|
for key := range account_balance {
|
|
if accountType(key) == "I" {
|
|
res = res + account_balance[key].balance_end
|
|
}
|
|
if accountType(key) == "E" {
|
|
res = res - account_balance[key].balance_end
|
|
}
|
|
|
|
}
|
|
|
|
res = roundRappen(res)
|
|
fmt.Fprintf(os.Stderr, "INFO: Calculated Profit: %v\n", res)
|
|
return res
|
|
|
|
}
|
|
|
|
func transactionInQuarter(date time.Time, whatquarter string) bool {
|
|
month := date.Month()
|
|
switch month {
|
|
case 1, 2, 3:
|
|
return whatquarter == "mwst1"
|
|
case 4, 5, 6:
|
|
return whatquarter == "mwst2"
|
|
case 7, 8, 9:
|
|
return whatquarter == "mwst3"
|
|
case 10, 11, 12:
|
|
return whatquarter == "mwst4"
|
|
}
|
|
return false
|
|
}
|
|
|
|
func getTotalConsideration(whatquarter string) float64 {
|
|
//"43517.04"
|
|
var res float64 = 0.0
|
|
|
|
for key := range transactions {
|
|
if transactionInQuarter(transactions[key].Date, whatquarter) {
|
|
for _, item := range transactions[key].Items {
|
|
if strings.HasPrefix(item.Vat_code, "V") {
|
|
res = res + item.Amount + item.Amount_Vat
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
res = roundRappen(res)
|
|
fmt.Fprintf(os.Stderr, "INFO: Calculated Total Consideration: %v\n", res)
|
|
return res
|
|
|
|
}
|
|
|
|
func getVat(vat_code string) string {
|
|
// strip first letter, divide by 10
|
|
// ie.: V77 => 7.7, V80 => 8.0
|
|
if s, err := strconv.ParseFloat(vat_code[1:], 64); err == nil {
|
|
return fmt.Sprintf("%.2f", s/10.0)
|
|
} else {
|
|
fmt.Fprintf(os.Stderr, "WARNING: Invalid VAT Code %s\n", vat_code)
|
|
return vat_code
|
|
}
|
|
}
|
|
|
|
func getDataTaxRates(whatquarter string) map[string]string {
|
|
//map[string]string{"7.70": "43517.04", "8.0": "Test only"}
|
|
//var m = map[string]string{
|
|
// "7.70": "43517.04",
|
|
// "8.0": "Test only",
|
|
// }
|
|
var m = map[string]string{}
|
|
var mn = map[string]float64{}
|
|
|
|
for key := range transactions {
|
|
if transactionInQuarter(transactions[key].Date, whatquarter) {
|
|
for _, item := range transactions[key].Items {
|
|
if strings.HasPrefix(item.Vat_code, "V") {
|
|
vat := getVat(item.Vat_code)
|
|
if val, found := mn[vat]; found {
|
|
mn[vat] = val + item.Amount + item.Amount_Vat
|
|
} else {
|
|
mn[vat] = item.Amount + item.Amount_Vat
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
for key, value := range mn {
|
|
m[key] = floatToString(roundRappen(value), "")
|
|
}
|
|
|
|
fmt.Fprintf(os.Stderr, "INFO: getDataTaxRates: %v\n", m)
|
|
return m
|
|
|
|
}
|
|
|
|
func getTaxInvestments(whatquarter string) float64 {
|
|
// "174.09"
|
|
var res float64 = 0.0
|
|
|
|
for key := range transactions {
|
|
if transactionInQuarter(transactions[key].Date, whatquarter) {
|
|
for _, item := range transactions[key].Items {
|
|
if strings.HasPrefix(item.Vat_code, "I") {
|
|
res = res + item.Amount_Vat
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
res = roundRappen(res)
|
|
fmt.Fprintf(os.Stderr, "INFO: Calculated Tax Investments: %v\n", res)
|
|
return res
|
|
}
|
|
|
|
func getPayableTax(whatquarter string) float64 {
|
|
// "3176.72"
|
|
var res float64 = 0.0
|
|
|
|
for key := range transactions {
|
|
if transactionInQuarter(transactions[key].Date, whatquarter) {
|
|
for _, item := range transactions[key].Items {
|
|
res = res + item.Amount_Vat
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
res = roundRappen(res)
|
|
fmt.Fprintf(os.Stderr, "INFO: Calculated Payable Tax: %v\n", res)
|
|
return 0 - res
|
|
}
|
|
|
|
func outputMwst(whatquarter string) {
|
|
fmt.Fprintf(os.Stderr, "INFO: Create Mwst Report for %s\n", whatquarter)
|
|
|
|
tm := time.Now()
|
|
gmt_location := time.FixedZone("GMT", 0)
|
|
const refidPrefix = "ef1q2024_"
|
|
var refid string
|
|
|
|
var data Data
|
|
data.GenerationTime = fmt.Sprintf("%s", tm.In(gmt_location).Format("2006-01-02T15:04:05Z"))
|
|
switch whatquarter {
|
|
case "mwst1":
|
|
data.ReportingPeriodFrom = balanceYear + "-01-01"
|
|
data.ReportingPeriodTill = balanceYear + "-03-31"
|
|
refid = refidPrefix + balanceYear + "0101_" + balanceYear + "0331_1"
|
|
case "mwst2":
|
|
data.ReportingPeriodFrom = balanceYear + "-04-01"
|
|
data.ReportingPeriodTill = balanceYear + "-06-30"
|
|
refid = refidPrefix + balanceYear + "0401_" + balanceYear + "0630_1"
|
|
case "mwst3":
|
|
data.ReportingPeriodFrom = balanceYear + "-07-01"
|
|
data.ReportingPeriodTill = balanceYear + "-09-30"
|
|
refid = refidPrefix + balanceYear + "0701_" + balanceYear + "0930_1"
|
|
case "mwst4":
|
|
data.ReportingPeriodFrom = balanceYear + "-10-01"
|
|
data.ReportingPeriodTill = balanceYear + "-12-31"
|
|
refid = refidPrefix + balanceYear + "1001_" + balanceYear + "1231_1"
|
|
}
|
|
data.BusinessReferenceId = refid
|
|
data.TotalConsideration = floatToString(getTotalConsideration(whatquarter), "")
|
|
data.DataTaxRates = getDataTaxRates(whatquarter)
|
|
data.InputTaxInvestments = floatToString(getTaxInvestments(whatquarter), "")
|
|
data.PayableTax = floatToString(getPayableTax(whatquarter), "")
|
|
|
|
tmp, err := template.ParseFiles("vat_xml.tmpl")
|
|
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
err2 := tmp.Execute(os.Stdout, data)
|
|
|
|
if err2 != nil {
|
|
log.Fatal(err2)
|
|
}
|
|
}
|
|
|
|
func doAdd(isDebit bool, account string) bool {
|
|
account_type := accountType(account)
|
|
|
|
if (account_type == "A") || (account_type == "E") || (account_type == "L") {
|
|
return isDebit
|
|
} else {
|
|
return !isDebit
|
|
}
|
|
}
|
|
|
|
func outputAccount(account string) {
|
|
fmt.Printf("START ACCOUNT %s (%s), Balance: %.2f\n", account, accounts[account], account_balance[account].balance_start+avoidMinusZero)
|
|
var current_balance float64 = account_balance[account].balance_start
|
|
var document_numbers []int
|
|
for k := range transactions {
|
|
document_numbers = append(document_numbers, int(k))
|
|
}
|
|
sort.Ints(document_numbers)
|
|
for _, k := range document_numbers {
|
|
for _, item := range transactions[uint16(k)].Items {
|
|
if item.Debit == account || item.Credit == account {
|
|
if doAdd(item.Debit == account, account) {
|
|
current_balance = current_balance + item.Amount
|
|
} else {
|
|
current_balance = current_balance - item.Amount
|
|
}
|
|
fmt.Printf("%-5d %s %-4v %-4v %-80v %11.2f %-5v %11.2f %11.2f\n", k, transactions[uint16(k)].Date.Format("02.01.2006"), item.Debit, item.Credit, item.Description, item.Amount, item.Vat_code, item.Amount_Vat, current_balance+avoidMinusZero)
|
|
}
|
|
}
|
|
|
|
}
|
|
fmt.Printf("END ACCOUNT %s (%s), Balance: %.2f\n\n", account, accounts[account], account_balance[account].balance_end+avoidMinusZero)
|
|
}
|
|
|
|
func outputJournal() {
|
|
fmt.Printf("%s - YEAR: %s\n", journalTitle, balanceYear)
|
|
fmt.Printf("Created at: %s\n\n", time.Now().Format("02.01.2006 15:04:05"))
|
|
var active_accounts []string
|
|
for k := range account_balance {
|
|
active_accounts = append(active_accounts, k)
|
|
}
|
|
sort.Strings(active_accounts)
|
|
for _, k := range active_accounts {
|
|
outputAccount(k)
|
|
}
|
|
}
|
|
|
|
func outputNewYear() {
|
|
var active_accounts []string
|
|
var amountString string
|
|
for k := range account_balance {
|
|
active_accounts = append(active_accounts, k)
|
|
}
|
|
sort.Strings(active_accounts)
|
|
for _, myaccount := range active_accounts {
|
|
// only Assets or Liabilities
|
|
at := accountType(myaccount)
|
|
if myaccount == PROFIT_ACCOUNT {
|
|
amountString = fmt.Sprintf("%.2f", account_balance[myaccount].balance_end+avoidMinusZero-profit)
|
|
} else {
|
|
amountString = fmt.Sprintf("%.2f", account_balance[myaccount].balance_end+avoidMinusZero)
|
|
}
|
|
if at == "A" || at == "L" {
|
|
if amountString != "0.00" {
|
|
fmt.Printf("%s: %s\n", myaccount, amountString)
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
func usage() {
|
|
fmt.Fprintf(os.Stderr, "usage: bookkeeper <action> <accounts file> <transactions file>\n")
|
|
fmt.Fprintf(os.Stderr, "\n")
|
|
fmt.Fprintf(os.Stderr, "Valid actions: check, balance, journal, mwst1, mwst2, mwst3, mwst4, new_year\n")
|
|
os.Exit(1)
|
|
}
|
|
|
|
func readData(accountdatafile string, transactiondatafile string) {
|
|
readAccountData(accountdatafile)
|
|
readTransactionData(transactiondatafile)
|
|
profit = calculateProfit()
|
|
}
|
|
|
|
func main() {
|
|
if len(os.Args) != 4 {
|
|
usage()
|
|
}
|
|
|
|
//fmt.Fprintf(os.Stderr,"accounts: %#v\n", accounts)
|
|
//fmt.Fprintf(os.Stderr,"transactions: %#v\n", transactions)
|
|
//fmt.Fprintf(os.Stderr,"account_balance: %#v\n", account_balance)
|
|
|
|
switch action := os.Args[1]; action {
|
|
case "check":
|
|
fmt.Fprintln(os.Stderr, "INFO: Data is checked by reading it...")
|
|
readData(os.Args[2], os.Args[3])
|
|
case "balance":
|
|
fmt.Fprintln(os.Stderr, "INFO: Create Balance Sheet")
|
|
readData(os.Args[2], os.Args[3])
|
|
createBalanceSheet()
|
|
case "journal":
|
|
fmt.Fprintln(os.Stderr, "INFO: Create Journal")
|
|
readData(os.Args[2], os.Args[3])
|
|
outputJournal()
|
|
case "mwst1", "mwst2", "mwst3", "mwst4":
|
|
fmt.Fprintln(os.Stderr, "INFO: Create Mwst Quarterly Report")
|
|
readData(os.Args[2], os.Args[3])
|
|
outputMwst(action)
|
|
case "new_year":
|
|
fmt.Fprintln(os.Stderr, "INFO: Create New Year")
|
|
readData(os.Args[2], os.Args[3])
|
|
outputNewYear()
|
|
default:
|
|
usage()
|
|
}
|
|
|
|
}
|