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 }