summaryrefslogtreecommitdiffstats
path: root/lib/ledger.ml
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ledger.ml')
-rw-r--r--lib/ledger.ml192
1 files changed, 137 insertions, 55 deletions
diff --git a/lib/ledger.ml b/lib/ledger.ml
index 84a0146..7805179 100644
--- a/lib/ledger.ml
+++ b/lib/ledger.ml
@@ -1,7 +1,5 @@
1open Prelude 1open Prelude
2 2
3type account_type = Asset | Equity | Liability | Expense | Income
4
5type tx_type = 3type tx_type =
6 | Interest_tx 4 | Interest_tx
7 | Online_banking_tx 5 | Online_banking_tx
@@ -14,11 +12,10 @@ type tx_type =
14 | Direct_debit_tx 12 | Direct_debit_tx
15 | Periodic_tx 13 | Periodic_tx
16 14
17type iban_tag = Account_tag | Counterparty_iban_tag 15type iban_tag = Account_tag | Counterparty_iban_tag [@@deriving compare, sexp]
18[@@deriving compare, sexp_of]
19 16
20type unit_tag = Filed_tag | Google_pay_tag | Auto_round_savings_tag 17type unit_tag = Filed_tag | Google_pay_tag | Auto_round_savings_tag
21[@@deriving compare, sexp_of] 18[@@deriving compare, sexp]
22 19
23type string_tag = 20type string_tag =
24 | Desc_tag 21 | Desc_tag
@@ -32,7 +29,7 @@ type string_tag =
32 | Terminal_tag 29 | Terminal_tag
33 | Card_seq_no_tag 30 | Card_seq_no_tag
34 | Savings_account_tag 31 | Savings_account_tag
35[@@deriving compare, sexp_of] 32[@@deriving compare, sexp]
36 33
37module Label = struct 34module Label = struct
38 type 'a t = 35 type 'a t =
@@ -62,29 +59,41 @@ end
62module Labels = struct 59module Labels = struct
63 include Dmap.Make (Label) 60 include Dmap.Make (Label)
64 61
65 let sexp_of_t m = 62 let sexp_of_binding = function
66 Sexp.List 63 | Binding (Iban_label tag, iban) ->
67 (bindings m 64 Sexp.List
68 |> List.map ~f:(function 65 [
69 | Binding (Iban_label tag, iban) -> 66 Sexp.Atom "iban"; [%sexp_of: iban_tag] tag; [%sexp_of: Iban.t] iban;
70 Sexp.List 67 ]
71 [ 68 | Binding (String_label tag, s) ->
72 Sexp.Atom "Iban_label"; 69 Sexp.List
73 [%sexp_of: iban_tag] tag; 70 [ Sexp.Atom "string"; [%sexp_of: string_tag] tag; Sexp.Atom s ]
74 [%sexp_of: Iban.t] iban; 71 | Binding (Timestamp_label, ts) ->
75 ] 72 Sexp.List [ Sexp.Atom "timestamp"; [%sexp_of: Time_ns_unix.t] ts ]
76 | Binding (String_label tag, s) -> 73 | Binding (Unit_label tag, ()) ->
77 Sexp.List 74 Sexp.List [ Sexp.Atom "unit"; [%sexp_of: unit_tag] tag ]
78 [ 75
79 Sexp.Atom "String_label"; 76 let binding_of_sexp sexp =
80 [%sexp_of: string_tag] tag; 77 match sexp with
81 Sexp.Atom s; 78 | Sexp.List [ Sexp.Atom "iban"; tag_sexp; iban_sexp ] ->
82 ] 79 Binding
83 | Binding (Timestamp_label, ts) -> 80 ( Iban_label ([%of_sexp: iban_tag] tag_sexp),
84 Sexp.List 81 [%of_sexp: Iban.t] iban_sexp )
85 [ Sexp.Atom "Timestamp_label"; [%sexp_of: Time_ns_unix.t] ts ] 82 | Sexp.List [ Sexp.Atom "string"; tag_sexp; Sexp.Atom s ] ->
86 | Binding (Unit_label tag, ()) -> 83 Binding (String_label ([%of_sexp: string_tag] tag_sexp), s)
87 Sexp.List [ Sexp.Atom "Unit_label"; [%sexp_of: unit_tag] tag ])) 84 | Sexp.List [ Sexp.Atom "timestamp"; ts_sexp ] ->
85 Binding (Timestamp_label, [%of_sexp: Time_ns_unix.t] ts_sexp)
86 | Sexp.List [ Sexp.Atom "unit"; tag_sexp ] ->
87 Binding (Unit_label ([%of_sexp: unit_tag] tag_sexp), ())
88 | _ -> of_sexp_error "Labels.binding_of_sexp: invalid binding" sexp
89
90 let sexp_of_t m = Sexp.List (bindings m |> List.map ~f:sexp_of_binding)
91
92 let t_of_sexp sexp =
93 match sexp with
94 | Sexp.List labels ->
95 Sequence.(of_list labels >>| binding_of_sexp |> to_seq) |> of_seq
96 | Sexp.Atom _ -> of_sexp_error "Labels.t_of_sexp: list needed" sexp
88end 97end
89 98
90module Money : sig 99module Money : sig
@@ -92,27 +101,33 @@ module Money : sig
92 101
93 val equal : t -> t -> bool 102 val equal : t -> t -> bool
94 val compare : t -> t -> int 103 val compare : t -> t -> int
95 val of_z : Z.t -> t 104 val of_bigint : Bigint.t -> t
96 val to_z : t -> Z.t 105 val to_bigint : t -> Bigint.t
97 val ( + ) : t -> t -> t 106 val ( + ) : t -> t -> t
98 val ( - ) : t -> t -> t 107 val ( - ) : t -> t -> t
108 val ( = ) : t -> t -> bool
109 val ( ~$ ) : int -> t
99 val sexp_of_t : t -> Sexp.t 110 val sexp_of_t : t -> Sexp.t
100end = struct 111end = struct
101 type t = Z.t [@@deriving sexp_of] 112 type t = Bigint.t [@@deriving sexp_of]
102
103 let equal = Z.equal
104 let compare = Z.compare
105 let of_z = Fn.id
106 let to_z = Fn.id
107 let ( + ) x y = Z.(x + y)
108 let ( - ) x y = Z.(x - y)
109end
110 113
111type scalar = Amount of Money.t | Rate of Z.t 114 let equal = Bigint.equal
112[@@deriving equal, compare, sexp_of] 115 let compare = Bigint.compare
116 let of_bigint = Fn.id
117 let to_bigint = Fn.id
118 let ( + ) x y = Bigint.(x + y)
119 let ( - ) x y = Bigint.(x - y)
120 let ( = ) = equal
121 let ( ~$ ) = Fn.compose of_bigint Bigint.of_int
122end
113 123
114type commodity_id = string 124type commodity_id = string
115(* TODO: consider making this UUID *) [@@deriving sexp] 125(* TODO: consider making this UUID *) [@@deriving equal, compare, sexp]
126
127type scalar =
128 | Amount of Money.t
129 | Rate of { in_primary_commodity : Money.t; rate : Bigdecimal.t }
130[@@deriving equal, compare, sexp_of]
116 131
117module Account_id = struct 132module Account_id = struct
118 type t = string list [@@deriving sexp, compare] 133 type t = string list [@@deriving sexp, compare]
@@ -135,13 +150,18 @@ type bal_assert = {
135 150
136module Account_id_map = Map.Make (Account_id) 151module Account_id_map = Map.Make (Account_id)
137 152
153module Debit_credit = struct
154 type t = Debit | Credit [@@deriving string, sexp_of]
155
156 let opposite = function Debit -> Credit | Credit -> Debit
157end
158
138module Tx : sig 159module Tx : sig
139 (* Private because we only want to allow constructing balanced transactions. *) 160 (* Private because we only want to allow constructing balanced transactions. *)
140 type t = private { 161 type t = private {
141 cleared : Date.t option; 162 cleared : Date.t option;
142 commodity_id : commodity_id; 163 commodity_id : commodity_id;
143 debit : scalar Account_id_map.t; 164 entries : (Debit_credit.t * scalar * Money.t option) Account_id_map.t;
144 credit : scalar Account_id_map.t;
145 labels : Labels.t; 165 labels : Labels.t;
146 } 166 }
147 167
@@ -150,8 +170,7 @@ module Tx : sig
150 val make : 170 val make :
151 cleared:Date.t option -> 171 cleared:Date.t option ->
152 commodity_id:commodity_id -> 172 commodity_id:commodity_id ->
153 debit:scalar Account_id_map.t -> 173 entries:(Debit_credit.t * scalar * Money.t option) Account_id_map.t ->
154 credit:scalar Account_id_map.t ->
155 labels:Labels.t -> 174 labels:Labels.t ->
156 (t, error) result 175 (t, error) result
157 176
@@ -160,23 +179,86 @@ end = struct
160 type t = { 179 type t = {
161 cleared : Date.t option; 180 cleared : Date.t option;
162 commodity_id : commodity_id; 181 commodity_id : commodity_id;
163 debit : scalar Account_id_map.t; 182 entries : (Debit_credit.t * scalar * Money.t option) Account_id_map.t;
164 credit : scalar Account_id_map.t;
165 labels : Labels.t; 183 labels : Labels.t;
166 } 184 }
167 [@@deriving sexp_of] 185 [@@deriving sexp_of]
168 186
169 type error = Unbalanced 187 type error = Unbalanced
170 188
171 (* TODO: check if debits and credits are balanced *) 189 let is_balanced entries =
172 let is_balanced _debits _credits = true 190 Map.fold entries
191 ~init:Money.(~$0, ~$0)
192 ~f:(fun ~key:_ ~data:(type_, scalar, _oassert) (ds, cs) ->
193 let m =
194 match scalar with
195 | Amount m -> m
196 | Rate { in_primary_commodity = m; _ } -> m
197 in
198 match type_ with
199 | Debit_credit.Debit -> Money.(ds + m, cs)
200 | Debit_credit.Credit -> Money.(ds, cs + m))
201 |> fun (ds, cs) -> Money.(ds = cs)
173 202
174 let make ~cleared ~commodity_id ~debit ~credit ~labels = 203 let make ~cleared ~commodity_id ~entries ~labels =
175 if not (is_balanced debit credit) then Error Unbalanced 204 if not (is_balanced entries) then Error Unbalanced
176 else Ok { cleared; commodity_id; debit; credit; labels } 205 else Ok { cleared; commodity_id; entries; labels }
177end 206end
178 207
179type item = Tx_item of Tx.t | Bal_assert_item of bal_assert 208type item =
209 | Tx_item of Tx.t
210 | Bal_assert_item of bal_assert (*| Account_decl_item of account_decl*)
180[@@deriving sexp_of] 211[@@deriving sexp_of]
181 212
182type t = item list [@@deriving sexp_of] 213type t = item list [@@deriving sexp_of]
214
215module Account = struct
216 type global_type = Asset | Equity | Liability | Expense | Income
217 [@@deriving compare, sexp]
218
219 type asset
220 type global
221
222 type 'a subcategory =
223 | Asset : asset subcategory option -> global subcategory
224 | Checking : asset subcategory
225
226 type 'a t = Sub of ('a, 'a t) category String.Map.t
227
228 let world : global t =
229 Sub
230 (String.Map.of_alist_exn [ ("Assets", Asset (Some (
231 String.Map.of_alist_exn [
232 ("Checking", Checking)
233 ]
234 ))) ])
235end
236
237(*
238module World = struct
239 type t = (commodity_id * Money.t) Account_id_map.t
240
241 let empty : t = Account_id_map.empty
242
243 let apply_tx_entry_base aid primary_commodity debit_credit scalar =
244 let amount = Scalar.to_amount ~commodity:primary_commodity scalar in
245 Map.update aid ~f:(function
246 | None ->
247
248 (*
249 let assert_bal aid sc world =
250
251 let apply_tx_entry aid (dc, sc, oassert) world = *)
252
253 let apply_tx (tx : Tx.t) world =
254 Map.fold tx.entries ~init:world ~f:(fun ~key:account_id ~data:(type_, scalar, _oassert) world ->
255
256
257 )
258
259 let apply : item -> t -> t = function
260 | Tx_item tx -> apply_tx tx
261 | Bal_assert_item ba -> apply_ba ba
262end *)
263
264let make = Fn.id