summaryrefslogtreecommitdiffstats
path: root/lib/preledger.ml
diff options
context:
space:
mode:
Diffstat (limited to 'lib/preledger.ml')
-rw-r--r--lib/preledger.ml217
1 files changed, 217 insertions, 0 deletions
diff --git a/lib/preledger.ml b/lib/preledger.ml
new file mode 100644
index 0000000..05f9e36
--- /dev/null
+++ b/lib/preledger.ml
@@ -0,0 +1,217 @@
1open Prelude
2module Debit_credit = Ledger.Debit_credit
3
4type tx_type =
5 | Interest_tx
6 | Online_banking_tx
7 | Recurrent_direct_tx
8 | Payment_terminal_tx
9 | Cash_payment_tx
10 | Atm_tx
11 | Auto_save_rounding_tx
12 | Batch_tx
13 | Direct_debit_tx
14 | Periodic_tx
15[@@deriving compare, sexp]
16
17type iban_tag = Account_tag | Counterparty_iban_tag [@@deriving compare, sexp]
18
19type unit_tag = Filed_tag | Google_pay_tag | Auto_round_savings_tag
20[@@deriving compare, sexp]
21
22type string_tag =
23 | Desc_tag
24 | User_tag
25 | Counterparty_name_tag
26 | Reference_tag
27 | Mandate_id_tag
28 | Creditor_id_tag
29 | Other_party_tag
30 | Transaction_tag
31 | Terminal_tag
32 | Card_seq_no_tag
33 | Savings_account_tag
34[@@deriving compare, sexp]
35
36module Label = struct
37 type 'a t =
38 | Iban_label : iban_tag -> Iban.t t
39 | String_label : string_tag -> string t
40 | Timestamp_label : Time_ns.t t
41 | Tx_type_label : tx_type t
42 | Unit_label : unit_tag -> unit t
43
44 let int_to_cmp x : ('a, 'a) Dmap.cmp =
45 if x < 0 then Lt else if x > 0 then Gt else Eq
46
47 let compare (type a1 a2) (v1 : a1 t) (v2 : a2 t) : (a1, a2) Dmap.cmp =
48 match (v1, v2) with
49 | Iban_label t1, Iban_label t2 -> int_to_cmp @@ [%compare: iban_tag] t1 t2
50 | String_label t1, String_label t2 ->
51 int_to_cmp @@ [%compare: string_tag] t1 t2
52 | Timestamp_label, Timestamp_label -> Eq
53 | Tx_type_label, Tx_type_label -> Eq
54 | Unit_label t1, Unit_label t2 -> int_to_cmp @@ [%compare: unit_tag] t1 t2
55 | Iban_label _, _ -> Lt
56 | String_label _, Iban_label _ -> Gt
57 | String_label _, _ -> Lt
58 | Timestamp_label, Unit_label _ -> Lt
59 | Timestamp_label, Tx_type_label -> Lt
60 | Timestamp_label, _ -> Gt
61 | Tx_type_label, Unit_label _ -> Lt
62 | Tx_type_label, _ -> Gt
63 | Unit_label _, _ -> Gt
64end
65
66module Labels = struct
67 include Dmap.Make (Label)
68
69 let sexp_of_binding = function
70 | Binding (Iban_label tag, iban) ->
71 Sexp.List
72 [
73 Sexp.Atom "iban"; [%sexp_of: iban_tag] tag; [%sexp_of: Iban.t] iban;
74 ]
75 | Binding (String_label tag, s) ->
76 Sexp.List
77 [ Sexp.Atom "string"; [%sexp_of: string_tag] tag; Sexp.Atom s ]
78 | Binding (Timestamp_label, ts) ->
79 Sexp.List [ Sexp.Atom "timestamp"; [%sexp_of: Time_ns_unix.t] ts ]
80 | Binding (Tx_type_label, type_) ->
81 Sexp.List [ Sexp.Atom "tx_type"; [%sexp_of: tx_type] type_ ]
82 | Binding (Unit_label tag, ()) ->
83 Sexp.List [ Sexp.Atom "unit"; [%sexp_of: unit_tag] tag ]
84
85 let binding_of_sexp sexp =
86 match sexp with
87 | Sexp.List [ Sexp.Atom "iban"; tag_sexp; iban_sexp ] ->
88 Binding
89 ( Iban_label ([%of_sexp: iban_tag] tag_sexp),
90 [%of_sexp: Iban.t] iban_sexp )
91 | Sexp.List [ Sexp.Atom "string"; tag_sexp; Sexp.Atom s ] ->
92 Binding (String_label ([%of_sexp: string_tag] tag_sexp), s)
93 | Sexp.List [ Sexp.Atom "timestamp"; ts_sexp ] ->
94 Binding (Timestamp_label, [%of_sexp: Time_ns_unix.t] ts_sexp)
95 | Sexp.List [ Sexp.Atom "tx_type"; type_sexp ] ->
96 Binding (Tx_type_label, [%of_sexp: tx_type] type_sexp)
97 | Sexp.List [ Sexp.Atom "unit"; tag_sexp ] ->
98 Binding (Unit_label ([%of_sexp: unit_tag] tag_sexp), ())
99 | _ -> of_sexp_error "Labels.binding_of_sexp: invalid binding" sexp
100
101 let sexp_of_t m = Sexp.List (bindings m |> List.map ~f:sexp_of_binding)
102
103 let t_of_sexp sexp =
104 match sexp with
105 | Sexp.List labels ->
106 Sequence.(of_list labels >>| binding_of_sexp |> to_seq) |> of_seq
107 | Sexp.Atom _ -> of_sexp_error "Labels.t_of_sexp: list needed" sexp
108end
109
110module Money : sig
111 type t
112
113 val equal : t -> t -> bool
114 val compare : t -> t -> int
115 val of_z : Z.t -> t
116 val to_z : t -> Z.t
117 val ( + ) : t -> t -> t
118 val ( - ) : t -> t -> t
119 val ( = ) : t -> t -> bool
120 val ( ~$ ) : int -> t
121 val sexp_of_t : t -> Sexp.t
122end = struct
123 type t = Z.t [@@deriving sexp_of]
124
125 let equal = Z.equal
126 let compare = Z.compare
127 let of_z = Fn.id
128 let to_z = Fn.id
129 let ( + ) x y = Z.(x + y)
130 let ( - ) x y = Z.(x - y)
131 let ( = ) = equal
132 let ( ~$ ) = Fn.compose of_z Z.of_int
133end
134
135(* TODO: make rate a decimal *)
136type scalar =
137 | Amount of Money.t
138 | Rate of { in_primary_commodity : Money.t; rate : Z.t }
139[@@deriving equal, compare, sexp_of]
140
141type commodity_id = string
142(* TODO: consider making this UUID *) [@@deriving sexp]
143
144module Account_id = struct
145 type t = string list [@@deriving sexp, compare]
146end
147
148type account = {
149 id : Account_id.t;
150 description : string list;
151 commodity_id : commodity_id;
152 balance : Money.t;
153}
154[@@deriving sexp_of]
155
156type bal_assert = {
157 account : Account_id.t;
158 amount : Money.t;
159 labels : Labels.t;
160}
161[@@deriving sexp_of]
162
163module Account_id_map = Map.Make (Account_id)
164
165module Tx : sig
166 (* Private because we only want to allow constructing balanced transactions. *)
167 type t = private {
168 cleared : Date.t option;
169 commodity_id : commodity_id;
170 entries : (Debit_credit.t * scalar * Money.t option) Account_id_map.t;
171 labels : Labels.t;
172 }
173
174 type error = Unbalanced
175
176 val make :
177 cleared:Date.t option ->
178 commodity_id:commodity_id ->
179 entries:(Debit_credit.t * scalar * Money.t option) Account_id_map.t ->
180 labels:Labels.t ->
181 (t, error) result
182
183 val sexp_of_t : t -> Sexp.t
184end = struct
185 type t = {
186 cleared : Date.t option;
187 commodity_id : commodity_id;
188 entries : (Debit_credit.t * scalar * Money.t option) Account_id_map.t;
189 labels : Labels.t;
190 }
191 [@@deriving sexp_of]
192
193 type error = Unbalanced
194
195 let is_balanced entries =
196 Map.fold entries
197 ~init:Money.(~$0, ~$0)
198 ~f:(fun ~key:_ ~data:(type_, scalar, _oassert) (ds, cs) ->
199 let m =
200 match scalar with
201 | Amount m -> m
202 | Rate { in_primary_commodity = m; _ } -> m
203 in
204 match type_ with
205 | Debit_credit.Debit -> Money.(ds + m, cs)
206 | Debit_credit.Credit -> Money.(ds, cs + m))
207 |> fun (ds, cs) -> Money.(ds = cs)
208
209 let make ~cleared ~commodity_id ~entries ~labels =
210 if not (is_balanced entries) then Error Unbalanced
211 else Ok { cleared; commodity_id; entries; labels }
212end
213
214type item = Tx_item of Tx.t | Bal_assert_item of bal_assert
215[@@deriving sexp_of]
216
217type t = item list [@@deriving sexp_of]