117 lines
3.2 KiB
Go

package process
import (
"cmp"
"slices"
"strings"
"kryddan.xyz/bookkeep/types"
)
func PerOrgUnit(account types.BookkeepingAccount) map[string]types.BookkeepingAccount {
out := map[string]types.BookkeepingAccount{}
for _, row := range account.Rows {
unit := strings.ToLower(*cmp.Or(row.OrganizationUnit, new("")))
if _, ok := out[unit]; !ok {
out[unit] = types.BookkeepingAccount{
Name: account.Name,
AccountNumber: account.AccountNumber,
Rows: []types.BookkeepingAccountRow{},
}
}
v := out[unit]
v.Rows = append(v.Rows, row)
out[unit] = v
}
return out
}
type FaultyOutlay struct {
Row types.BookkeepingAccountRow
ContributingVerifications []types.Verification
}
func ValidateOutlays(account types.BookkeepingAccount) ([]FaultyOutlay, bool) {
rows := sortRows(account.Rows)
slices.Reverse(rows)
talliedVerifications := map[string]types.BookkeepingAccountRow{}
originalVerifications := map[string]types.BookkeepingAccountRow{}
for _, row := range rows {
if r, ok := talliedVerifications[row.Verification.String()]; ok {
r.Debet += row.Debet
r.Kredit += row.Kredit
talliedVerifications[row.Verification.String()] = r
originalVerifications[row.Verification.String()] = r
continue
}
talliedVerifications[row.Verification.String()] = row
originalVerifications[row.Verification.String()] = row
}
backlinks := map[string][]string{}
for rowVerf, row := range originalVerifications {
if len(row.LinkedVerifications) == 0 {
continue
}
if row.Kredit == 0 && row.Debet == 0 {
continue
}
alteredRow := talliedVerifications[rowVerf]
if alteredRow.Kredit == 0 && alteredRow.Debet == 0 {
continue
}
alterValue := func(old int, adjustment int) (int, int) {
v := old + adjustment
if v > 0 {
return v, 0
} else {
return 0, -v
}
}
for _, linked := range row.LinkedVerifications {
linkedRow := talliedVerifications[linked.String()]
if linkedRow.Debet != 0 && row.Kredit != 0 {
linkedRow.Debet, row.Kredit = alterValue(linkedRow.Debet, -row.Kredit)
} else if linkedRow.Kredit != 0 && row.Debet != 0 {
linkedRow.Kredit, row.Debet = alterValue(linkedRow.Kredit, -row.Debet)
}
backlinks[linked.String()] = append(backlinks[linked.String()], row.Verification.String())
talliedVerifications[linked.String()] = linkedRow
}
talliedVerifications[row.Verification.String()] = row
}
faults := []FaultyOutlay{}
for verf, row := range talliedVerifications {
if row.Debet != 0 || row.Kredit != 0 {
contributingVerfs := []types.Verification{}
bls := backlinks[verf]
for _, v := range bls {
contributingVerfs = append(contributingVerfs, talliedVerifications[v].Verification)
}
faults = append(faults, FaultyOutlay{
Row: row,
ContributingVerifications: contributingVerfs,
})
}
}
return faults, len(faults) == 0
}
func sortRows(rows []types.BookkeepingAccountRow) []types.BookkeepingAccountRow {
slices.SortFunc(rows, func(a, b types.BookkeepingAccountRow) int {
if a.Verification.Series != b.Verification.Series {
return 0
}
return a.Verification.Number - b.Verification.Number
})
return rows
}