open Core type account_type = Asset | Equity | Liability | Expense | Income type tx_type = | Interest_tx | Online_banking_tx | Recurrent_direct_tx | Payment_terminal_tx | Cash_payment_tx | Atm_tx | Auto_save_rounding_tx | Batch_tx | Direct_debit_tx | Periodic_tx type iban_tag = Account_tag | Counterparty_iban_tag [@@deriving compare] type unit_tag = Filed_tag | Google_pay_tag | Auto_round_savings_tag [@@deriving compare] type string_tag = | Desc_tag | User_tag | Counterparty_name_tag | Reference_tag | Mandate_id_tag | Creditor_id_tag | Other_party_tag | Transaction_tag | Terminal_tag | Card_seq_no_tag | Savings_account_tag [@@deriving compare] module Label = struct type 'a t = | Iban_label : iban_tag -> Iban.t t | String_label : string_tag -> string t | Timestamp_label : Time_ns.t t | Unit_label : unit_tag -> unit t let int_to_cmp x : ('a, 'a) Dmap.cmp = if x < 0 then Lt else if x > 0 then Gt else Eq let compare (type a1 a2) (v1 : a1 t) (v2 : a2 t) : (a1, a2) Dmap.cmp = match (v1, v2) with | Iban_label t1, Iban_label t2 -> int_to_cmp @@ [%compare: iban_tag] t1 t2 | String_label t1, String_label t2 -> int_to_cmp @@ [%compare: string_tag] t1 t2 | Timestamp_label, Timestamp_label -> Eq | Unit_label t1, Unit_label t2 -> int_to_cmp @@ [%compare: unit_tag] t1 t2 | Iban_label _, _ -> Lt | String_label _, Iban_label _ -> Gt | String_label _, _ -> Lt | Timestamp_label, Unit_label _ -> Lt | Timestamp_label, _ -> Gt | Unit_label _, _ -> Gt end module Labels = Dmap.Make (Label) module Money : sig type t val equal : t -> t -> bool val compare : t -> t -> int val of_z : Z.t -> t val to_z : t -> Z.t val ( + ) : t -> t -> t val ( - ) : t -> t -> t end = struct type t = Z.t let equal = Z.equal let compare = Z.compare let of_z = Fn.id let to_z = Fn.id let ( + ) x y = Z.(x + y) let ( - ) x y = Z.(x - y) end type scalar = Amount of Money.t | Rate of Z.t [@@deriving equal, compare] type commodity_id = string (* TODO: consider making this UUID *) module Account_id = struct type t = string list [@@deriving sexp, compare] end type account = { id : Account_id.t; description : string list; commodity_id : commodity_id; balance : Money.t; } type bal_assert = { account : Account_id.t; amount : Money.t; labels : Labels.t; } module Account_id_map = Map.Make (Account_id) module Tx : sig type t type error = Unbalanced val make : cleared:Date.t option -> commodity_id:commodity_id -> debit:scalar Account_id_map.t -> credit:scalar Account_id_map.t -> labels:Labels.t -> (t, error) result val cleared : t -> Date.t option val commodity_id : t -> commodity_id val debit : t -> scalar Account_id_map.t val credit : t -> scalar Account_id_map.t val labels : t -> Labels.t end = struct (* We hide this because we only want to allow constructing balanced transactions *) type t = { cleared : Date.t option; commodity_id : commodity_id; debit : scalar Account_id_map.t; credit : scalar Account_id_map.t; labels : Labels.t; } [@@deriving fields] type error = Unbalanced (* TODO: check if debits and credits are balanced *) let is_balanced _debits _credits = true let make ~cleared ~commodity_id ~debit ~credit ~labels = if not (is_balanced debit credit) then Error Unbalanced else Ok { cleared; commodity_id; debit; credit; labels } end type item = Tx_item of Tx.t | Bal_assert_item of bal_assert type ledger = Ledger of item list