Skip to content

Commit 5cf0b1e

Browse files
author
Eric Forgy
authored
First working (kind of) version with new design (#7)
* [DO NOT MERGE] It compiles... * Example is working * Add tests * Add tree display
1 parent b84f7fd commit 5cf0b1e

File tree

2 files changed

+115
-66
lines changed

2 files changed

+115
-66
lines changed

src/Ledgers.jl

Lines changed: 99 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -12,94 +12,119 @@ Licensed under MIT License, see LICENSE.md
1212
module Ledgers
1313

1414
using UUIDs, StructArrays, AbstractTrees
15-
using Instruments, Assets
15+
using Instruments
1616
import Instruments: instrument, symbol, amount, name, currency
17+
using Assets: USD
1718

18-
export Credit, Debit, Account, Ledger, Entry, AccountId, AccountInfo
19-
export id, balance, credit!, debit!, post!, instrument, symbol, amount, name, currency
19+
export Account, Ledger, Entry, AccountId, AccountCode, AccountInfo, AccountGroup
20+
export id, balance, credit!, debit!, post!, instrument, symbol, amount, code, name, currency
21+
export parent, subaccounts, subgroups
2022

2123
abstract type Identifier end
24+
2225
struct AccountId <: Identifier
2326
value::UUID
2427
end
28+
2529
AccountId() = AccountId(uuid4())
2630

2731
struct AccountCode
2832
value::String
2933
end
3034

31-
abstract type AccountType end
32-
struct Credit <: AccountType end
33-
struct Debit <: AccountType end
35+
abstract type AccountType{B <: Position} end
3436

35-
mutable struct Account{P<:Position}
37+
mutable struct Account{B <: Position} <: AccountType{B}
3638
id::AccountId
37-
balance::P
39+
balance::B
3840
end
39-
Account(balance::Position) = Account(AccountId(), balance)
4041

41-
abstract type AccountNode end
42-
struct AccountInfo{AT<:AccountType,A<:Account} <: AccountNode
43-
account::A
42+
Account(balance::Position) = Account{typeof(balance)}(AccountId(), balance)
43+
44+
abstract type AccountNode{B <: Position} <: AccountType{B} end
45+
46+
struct AccountInfo{B <: Position} <: AccountNode{B}
47+
account::Account{B}
4448
code::AccountCode
4549
name::String
50+
isdebit::Bool
4651

47-
function AccountInfo(::Type{T}, account, code, name) where {T<:AccountType}
48-
return new{T}(account, code, name)
52+
function AccountInfo{B}(account::Account{B}, code, name, isdebit=true, parent=nothing) where {B <: Position}
53+
acc = new{B}(account, code, name, isdebit)
54+
parent === nothing || push!(parent.subaccounts, acc)
55+
acc
4956
end
5057
end
5158

52-
struct AccountGroup{AT<:AccountType} <: AccountNode
59+
AccountInfo(account::Account{B}, code, name, isdebit=true, parent=nothing) where {B <: Position} =
60+
AccountInfo{B}(account, code, name, isdebit, parent)
61+
62+
struct AccountGroup{B <: Position} <: AccountNode{B}
63+
id::AccountId
5364
code::AccountCode
5465
name::String
55-
parent::Union{Nothing,AccountGroup{<:AccountType}}
56-
subaccounts::Vector{AccountInfo}
57-
subgroups::Vector{AccountGroup}
58-
59-
function AccountGroup(::Type{T}, account, name, parent=nothing) where {T<:AccountType}
60-
new{T}(account, code, name, parent, Vector{AccountInfo}(), Vector{AccountGroup}())
66+
isdebit::Bool
67+
parent::Union{Nothing,AccountGroup{B}}
68+
subaccounts::StructArray{AccountInfo{B}}
69+
subgroups::StructArray{AccountGroup{B}}
70+
71+
function AccountGroup{B}(
72+
id,
73+
code,
74+
name,
75+
isdebit=true,
76+
parent=nothing,
77+
subaccounts=StructArray(Vector{AccountInfo{B}}()),
78+
subgroups=StructArray(Vector{AccountGroup{B}}())) where {B <: Position}
79+
acc = new{B}(id, code, name, isdebit, parent, subaccounts, subgroups)
80+
parent === nothing || push!(parent.subgroups, acc)
81+
acc
6182
end
6283
end
6384

85+
AccountGroup(
86+
::B,
87+
code,
88+
name,
89+
isdebit=true,
90+
parent=nothing,
91+
subaccounts=StructArray(Vector{AccountInfo{B}}()),
92+
subgroups=StructArray(Vector{AccountGroup{B}}())) where {B <: Position} =
93+
AccountGroup{B}(AccountId(), code, name, isdebit, parent, subaccounts, subgroups)
94+
6495
# Identity function (to make code more generic)
65-
account(acc::Account) = acc
96+
account(acc::AccountType) = acc
6697
account(info::AccountInfo) = info.account
6798

68-
account_type(::Union{AccountInfo{AT},AccountGroup{AT}}) where {AT} = AT
69-
code(acc::Union{<:AccountInfo,<:AccountGroup}) = acc.code
70-
name(acc::Union{<:AccountInfo,<:AccountGroup}) = acc.name
99+
code(acc::AccountNode) = acc.code
100+
name(acc::AccountNode) = acc.name
101+
isdebit(acc::AccountNode) = acc.isdebit
71102

72103
parent(group::AccountGroup) = group.parent
73104
subaccounts(group::AccountGroup) = group.subaccounts
74105
subgroups(group::AccountGroup) = group.subgroups
75106

76-
id(acc::Account) = acc.id
77-
id(info::AccountInfo) = id(account(info))
107+
id(acc::AccountType) = account(acc).id
78108

79-
balance(acc::Account) = acc.balance
80-
balance(info::AccountInfo) = balance(account(info))
81-
balance(group::AccountGroup) =
82-
sum(map(info->balance(info), subaccounts(group))) +
83-
sum(map(grp->balance(grp), subgroups(group)))
109+
balance(acc) = account(acc).balance
110+
balance(group::AccountGroup) = isempty(subgroups(group)) ?
111+
sum(balance.(subaccounts(group))) :
112+
sum(balance.(subaccounts(group))) + sum(balance.(subgroups(group)))
84113

85-
instrument(::Account{P}) where {P<:Position} = instrument(P)
86-
instrument(info::AccountInfo) = instrument(account(info))
114+
instrument(::AccountType{B}) where {B <: Position} = instrument(B)
87115

88-
symbol(::Account{P}) where {P<:Position} = symbol(P)
89-
symbol(info::AccountInfo) = symbol(account(info))
116+
symbol(::AccountType{B}) where {B <: Position} = symbol(B)
90117

91-
currency(::Account{P}) where {P<:Position} = currency(P)
92-
currency(info::AccountInfo) = currency(account(info))
118+
currency(::AccountType{B}) where {B <: Position} = currency(B)
93119

94-
amount(acc::Account) = amount(balance(acc))
95-
amount(info::AccountInfo) = amount(balance(account(info)))
120+
amount(acc::AccountType) = amount(balance(acc))
96121

97122
debit!(acc::Account, amt::Position) = (acc.balance += amt)
98123
credit!(acc::Account, amt::Position) = (acc.balance -= amt)
99124

100-
struct Entry{D<:Account,C<:Account}
101-
debit::D
102-
credit::C
125+
struct Entry{B <: Position}
126+
debit::AccountInfo{B}
127+
credit::AccountInfo{B}
103128
end
104129

105130
function post!(entry::Entry, amt::Position)
@@ -108,16 +133,16 @@ function post!(entry::Entry, amt::Position)
108133
entry
109134
end
110135

111-
struct Ledger{P<:Position,I<:AccountId}
112-
indexes::Dict{I,Int}
113-
accounts::StructArray{Account{P,I}}
136+
struct Ledger{P <: Position}
137+
indexes::Dict{AccountId,Int}
138+
accounts::StructArray{Account{P}}
114139

115-
function Ledger(accounts::Vector{Account{P,I}}) where {P<:Position,I<:AccountId}
116-
indexes = Dict{I,Int}()
140+
function Ledger(accounts::Vector{Account{P}}) where {P <: Position}
141+
indexes = Dict{AccountId,Int}()
117142
for (index, account) in enumerate(accounts)
118143
indexes[id(account)] = index
119144
end
120-
new{P,I}(indexes, StructArray(accounts))
145+
new{P}(indexes, StructArray(accounts))
121146
end
122147
end
123148

@@ -132,6 +157,18 @@ function add_account!(ledger::Ledger, acc::Account)
132157
ledger.indexes[id(acc)] = length(ledger.accounts)
133158
end
134159

160+
function example()
161+
group = AccountGroup(USD(0), AccountCode("0000000"), "Account Group", false)
162+
assets = AccountGroup(USD(0), AccountCode("1000000"), "Assets", true, group)
163+
liabilities = AccountGroup(USD(0), AccountCode("2000000"), "Liabilities", false, group)
164+
cash = AccountInfo(Account(USD(0)), AccountCode("1010000"), "Cash", true, assets)
165+
payable = AccountInfo(Account(USD(0)), AccountCode("2010000"), "Accounts Payable", false, liabilities)
166+
167+
entry = Entry(cash, payable)
168+
group, assets, liabilities, cash, payable, entry
169+
end
170+
171+
135172
# const chartofaccounts = Dict{String,AccountGroup{<:Cash}}()
136173

137174
# function getledger(a::AccountGroup)
@@ -174,15 +211,23 @@ end
174211
# end
175212

176213
Base.show(io::IO, id::Identifier) = print(io, id.value)
214+
177215
Base.show(io::IO, code::AccountCode) = print(io, code.value)
178216

179-
Base.show(io::IO, ::Type{Debit}) = print(io, "Debit")
180-
Base.show(io::IO, ::Type{Credit}) = print(io, "Credit")
217+
Base.show(io::IO, acc::Account) = print(io, "$(string(id(acc))): $(balance(acc))")
218+
219+
Base.show(io::IO, acc::AccountNode) = print_tree(io, acc)
220+
221+
Base.show(io::IO, entry::Entry) = print_tree(io, entry)
222+
223+
AbstractTrees.children(acc::AccountGroup) = vcat(Vector(subgroups(acc)),Vector(subaccounts(acc)))
224+
225+
AbstractTrees.printnode(io::IO, acc::AccountNode) =
226+
print(io, "[$(code(acc))] $(name(acc)): $(isdebit(acc) ? balance(acc) : -balance(acc))")
181227

182-
Base.show(io::IO, acc::Account) = print(io, "$(string(id(acc))): $(balance(acc)).")
183-
Base.show(io::IO, info::AccountInfo{Debit}) = print(io, "$(code(info)) - $(name(info)): $(balance(info)).")
184-
Base.show(io::IO, info::AccountInfo{Credit}) = print(io, "$(code(info)) - $(name(info)): $(-balance(info)).")
228+
AbstractTrees.children(entry::Entry) = [entry.debit, entry.credit]
185229

230+
AbstractTrees.printnode(io::IO, ::Entry) = print(io, "Entry:")
186231

187232
# AbstractTrees.children(info::AccountInfo) =
188233
# isempty(subaccounts(info)) ? Vector{AccountInfo}() : subaccounts(info)

test/runtests.jl

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
using Ledgers, Test
22
using Assets: USD
33

4-
group = AccountInfo(Credit, Account(AccountId("0000000"), USD(0)), "Account Group")
5-
assets = AccountInfo(Debit, Account(AccountId("1000000"), USD(0)),
6-
"Assets", group)
7-
liabilities = AccountInfo(Credit, Account(AccountId("2000000"), USD(0)),
8-
"Liabilities", group)
9-
cash = AccountInfo(Debit, Account(AccountId("1010000"), USD(0)),
10-
"Cash", assets)
11-
payable = AccountInfo(Credit, Account(AccountId("2010000"), USD(0)),
12-
"Accounts Payable", liabilities)
13-
14-
entry = Entry(cash, payable)
4+
group, assets, liabilities, cash, payable, entry = Ledgers.example()
155

166
@testset "Account creation" begin
177
@test id(group) isa AccountId
188
@test id(assets) isa AccountId
19-
@test id(cash).value == "1010000"
9+
@test code(cash).value === "1010000"
10+
@test name(payable) === "Accounts Payable"
11+
@test balance(group) === 0USD
12+
@test balance(assets) === 0USD
13+
@test balance(liabilities) === 0USD
2014
end
15+
16+
@testset "Post entry" begin
17+
amt = 10USD
18+
post!(entry, amt)
19+
@test balance(group) === 0USD
20+
@test balance(assets) === amt
21+
@test balance(liabilities) === -amt
22+
@test balance(cash) === amt
23+
@test balance(payable) === -amt
24+
end

0 commit comments

Comments
 (0)