summaryrefslogtreecommitdiffstats
path: root/lib/ingcsv.ml
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ingcsv.ml')
-rw-r--r--lib/ingcsv.ml71
1 files changed, 63 insertions, 8 deletions
diff --git a/lib/ingcsv.ml b/lib/ingcsv.ml
index 203a353..f3536bf 100644
--- a/lib/ingcsv.ml
+++ b/lib/ingcsv.ml
@@ -68,6 +68,8 @@ module Transaction_type = struct
68end 68end
69 69
70module Primitive_tx = struct 70module Primitive_tx = struct
71 exception Inconsistent_transaction_code
72
71 type t = { 73 type t = {
72 date : Date.t; 74 date : Date.t;
73 description : string; 75 description : string;
@@ -85,6 +87,21 @@ module Primitive_tx = struct
85 let opt_field (f : string -> 'a) (v : string) : 'a option = 87 let opt_field (f : string -> 'a) (v : string) : 'a option =
86 if String.is_empty (String.strip v) then None else Some (f v) 88 if String.is_empty (String.strip v) then None else Some (f v)
87 89
90 let headers =
91 [
92 "Date";
93 "Name / Description";
94 "Account";
95 "Counterparty";
96 "Code";
97 "Debit/credit";
98 "Amount (EUR)";
99 "Transaction type";
100 "Notifications";
101 "Resulting balance";
102 "Tag";
103 ]
104
88 let parse : t Delimited.Read.t = 105 let parse : t Delimited.Read.t =
89 let open Delimited.Read.Let_syntax in 106 let open Delimited.Read.Let_syntax in
90 let%map_open date = at_header "Date" ~f:Date.of_string 107 let%map_open date = at_header "Date" ~f:Date.of_string
@@ -99,12 +116,7 @@ module Primitive_tx = struct
99 and resulting_balance = at_header "Resulting balance" ~f:Cents.of_string 116 and resulting_balance = at_header "Resulting balance" ~f:Cents.of_string
100 and tag = at_header "Tag" ~f:Fn.id in 117 and tag = at_header "Tag" ~f:Fn.id in
101 if not ([%equal: Transaction_type.t] code type_) then 118 if not ([%equal: Transaction_type.t] code type_) then
102 Printf.failwithf 119 raise Inconsistent_transaction_code;
103 "Primitive_tx.parse: parsed transaction code (%S) and type (%S) do not \
104 match"
105 (Transaction_type.to_string code)
106 (Transaction_type.to_string type_)
107 ();
108 { 120 {
109 date; 121 date;
110 description; 122 description;
@@ -123,7 +135,7 @@ type tx_base = {
123 date : Date.t; 135 date : Date.t;
124 account : Iban.t; 136 account : Iban.t;
125 amount : Cents.t; 137 amount : Cents.t;
126 res_bal : Cents.t; 138 resulting_balance : Cents.t;
127 tag : string; 139 tag : string;
128} 140}
129 141
@@ -196,6 +208,7 @@ type parse_err =
196 | Inconsistent_value_date 208 | Inconsistent_value_date
197 | Inconsistent_counterparty_name 209 | Inconsistent_counterparty_name
198 | Inconsistent_counterparty_iban 210 | Inconsistent_counterparty_iban
211 | Inconsistent_transaction_code
199 212
200let assert_value_date (ptx : Primitive_tx.t) d = 213let assert_value_date (ptx : Primitive_tx.t) d =
201 if Date.(d = ptx.date) then Ok () else Error Inconsistent_value_date 214 if Date.(d = ptx.date) then Ok () else Error Inconsistent_value_date
@@ -383,7 +396,7 @@ let parse_batch_payment_credit_notifs notifs =
383 (name, desc, iban, ref_, val_date) 396 (name, desc, iban, ref_, val_date)
384 | _ | (exception _) -> Error No_notifs_match 397 | _ | (exception _) -> Error No_notifs_match
385 398
386let specifics_from_prim_exn (ams_tz : Time_ns.Zone.t) : 399let tx_specifics_from_prim (ams_tz : Time_ns.Zone.t) :
387 Primitive_tx.t -> (tx_specifics, parse_err) result = function 400 Primitive_tx.t -> (tx_specifics, parse_err) result = function
388 | { type_ = Payment_terminal; debit_credit = Debit; _ } as ptx -> 401 | { type_ = Payment_terminal; debit_credit = Debit; _ } as ptx ->
389 let%bind 402 let%bind
@@ -531,3 +544,45 @@ let specifics_from_prim_exn (ams_tz : Time_ns.Zone.t) :
531 reference = ref_; 544 reference = ref_;
532 } 545 }
533 | _ -> Error Unknown_type_combination 546 | _ -> Error Unknown_type_combination
547
548let tx_base_from_prim (ptx : Primitive_tx.t) : tx_base =
549 {
550 date = ptx.date;
551 account = ptx.account;
552 amount = ptx.amount;
553 resulting_balance = ptx.resulting_balance;
554 tag = ptx.tag;
555 }
556
557let tx_from_prim ptx ~ams_tz : (tx, parse_err) result =
558 let base = tx_base_from_prim ptx in
559 let%map specifics = tx_specifics_from_prim ams_tz ptx in
560 Tx (base, specifics)
561
562type csv_err = Parse_err of parse_err | Exn of exn
563
564module List = struct
565 include List
566
567 let map_result ~(f : 'a -> ('b, 'c) result) : 'a list -> ('b list, 'c) result
568 =
569 let open Result.Let_syntax in
570 let rec go = function
571 | [] -> return []
572 | x :: xs ->
573 let%map x' = f x and xs' = go xs in
574 x' :: xs'
575 in
576 go
577end
578
579let read_channel (c : In_channel.t) ~ams_tz : (tx list, csv_err) result =
580 let%bind ptxs =
581 try
582 Ok
583 (Delimited.Read.read_lines ~header:(`Require Primitive_tx.headers)
584 Primitive_tx.parse c)
585 with e -> Error (Exn e)
586 in
587 List.map_result ptxs ~f:(tx_from_prim ~ams_tz)
588 |> Result.map_error ~f:(fun e -> Parse_err e)