diff options
| author | Rutger Broekhoff | 2026-01-09 16:17:23 +0100 |
|---|---|---|
| committer | Rutger Broekhoff | 2026-01-09 16:17:25 +0100 |
| commit | d6c9993c2eb51650d44507ec601151cba4159039 (patch) | |
| tree | 4b5e6ed9ee1249c3bf4fa10015b0fe16bf0e41f1 | |
| parent | 59e2510a9824ac247538db9b0e42cf6fd928ab27 (diff) | |
| download | rdcapsis-d6c9993c2eb51650d44507ec601151cba4159039.tar.gz rdcapsis-d6c9993c2eb51650d44507ec601151cba4159039.zip | |
Confusion endgame for ocamlformat
| -rw-r--r-- | lib/ledger.ml | 742 |
1 files changed, 377 insertions, 365 deletions
diff --git a/lib/ledger.ml b/lib/ledger.ml index 115588a..87f3ead 100644 --- a/lib/ledger.ml +++ b/lib/ledger.ml | |||
| @@ -237,50 +237,34 @@ module Account_structure0 = struct | |||
| 237 | end | 237 | end |
| 238 | end | 238 | end |
| 239 | 239 | ||
| 240 | module Accounts_payable : | 240 | module Accounts_payable = struct |
| 241 | S with type outer = Categories.liability and type inner = Categories.final = | ||
| 242 | struct | ||
| 243 | type outer = Categories.liability | 241 | type outer = Categories.liability |
| 244 | type inner = Categories.final | 242 | type inner = Categories.final |
| 245 | 243 | ||
| 246 | module Specialize (F : Scaffold) (G : module type of Make (F)) = struct | 244 | module Specialize (F : Scaffold) (G : module type of Make (F)) = struct |
| 247 | let cons inner = G.Accounts_payable inner | 245 | let cons v = G.Accounts_payable v |
| 248 | end | 246 | end |
| 249 | end | 247 | end |
| 250 | end | 248 | end |
| 251 | end | ||
| 252 | |||
| 253 | module Account_type = struct | ||
| 254 | type 'a elem = Leaf | Node of 'a [@@deriving sexp_of] | ||
| 255 | |||
| 256 | module Scaffold = struct | ||
| 257 | type 'a t = 'a elem [@@deriving sexp_of] | ||
| 258 | end | ||
| 259 | |||
| 260 | include Account_structure0.Make (Scaffold) | ||
| 261 | end | ||
| 262 | |||
| 263 | module Account_structure (F : Account_structure0.Scaffold) = struct | ||
| 264 | module Structure = Account_structure0.Make (F) | ||
| 265 | include Structure | ||
| 266 | 249 | ||
| 267 | module Folder3 (Acc : sig | 250 | module Visitor |
| 268 | type 'a t | 251 | (F : Scaffold) |
| 269 | end) = | 252 | (G : |
| 253 | module type of Make (F)) | ||
| 254 | (Acc : sig | ||
| 255 | type 'a t | ||
| 256 | end) = | ||
| 270 | struct | 257 | struct |
| 271 | type nonrec 'outer t = { | 258 | type nonrec 'outer t = { |
| 272 | car : | 259 | car : |
| 273 | 'inner. | 260 | 'inner. |
| 274 | 'inner f F.t -> | 261 | 'inner G.f F.t -> |
| 275 | (module Account_structure0.Gen_f_cons.S | 262 | (module Gen_f_cons.S with type inner = 'inner and type outer = 'outer) -> |
| 276 | with type inner = 'inner | ||
| 277 | and type outer = 'outer) -> | ||
| 278 | 'outer Acc.t; | 263 | 'outer Acc.t; |
| 279 | } | 264 | } |
| 280 | 265 | ||
| 281 | let fold (type a) (f : a t) : a f -> a Acc.t = function | 266 | let visit (type a) (f : a t) : a G.f -> a Acc.t = function |
| 282 | | Accounts_payable v -> | 267 | | G.Accounts_payable v -> f.car v (module Gen_f_cons.Accounts_payable) |
| 283 | f.car v (module Account_structure0.Gen_f_cons.Accounts_payable) | ||
| 284 | | _ -> failwith "kaas" | 268 | | _ -> failwith "kaas" |
| 285 | (* | 269 | (* |
| 286 | | Accounts_receivable v -> | 270 | | Accounts_receivable v -> |
| @@ -294,221 +278,249 @@ module Account_structure (F : Account_structure0.Scaffold) = struct | |||
| 294 | | Checking v -> f.car v (fun inner -> H.Checking inner) *) | 278 | | Checking v -> f.car v (fun inner -> H.Checking inner) *) |
| 295 | end | 279 | end |
| 296 | 280 | ||
| 297 | module Folder = struct | 281 | module Basic_visitor (F : Scaffold) (G : module type of Make (F)) = struct |
| 298 | type nonrec 'b t = { car : 'a. 'a f F.t -> 'b } | 282 | type nonrec 'b t = { car : 'a. 'a G.f F.t -> 'b } |
| 299 | 283 | ||
| 300 | let fold (type b c) (f : c t) : b f -> c = | 284 | let visit (type b c) (f : c t) : b G.f -> c = |
| 301 | let module Inst = Folder3 (struct | 285 | let module Inst = |
| 302 | type 'a t = c | 286 | Visitor (F) (G) |
| 303 | end) in | 287 | (struct |
| 304 | Inst.fold { car = (fun v _cons -> f.car v) } | 288 | type 'a t = c |
| 289 | end) | ||
| 290 | in | ||
| 291 | Inst.visit { car = (fun v _cons -> f.car v) } | ||
| 292 | end | ||
| 305 | end | 293 | end |
| 306 | 294 | ||
| 307 | module Mapper = struct | 295 | module Account_type = struct |
| 308 | type nonrec 'b t = { | 296 | type 'a elem = Leaf | Node of 'a [@@deriving sexp_of] |
| 309 | car : | ||
| 310 | 'a. | ||
| 311 | 'a f F.t -> | ||
| 312 | ('a Account_type.f Account_type.elem -> Account_type.t0) -> | ||
| 313 | ('b * 'a f F.t) option; | ||
| 314 | } | ||
| 315 | 297 | ||
| 316 | let map (type b c) (f : c t) (mkt : b Account_type.f -> Account_type.t0) : | 298 | module Scaffold = struct |
| 317 | b f -> (c * b f) option = | 299 | type 'a t = 'a elem [@@deriving sexp_of] |
| 318 | let module Inst = Folder3 (struct | 300 | end |
| 319 | type 'b t = (c * 'b f) option | 301 | |
| 320 | end) in | 302 | include Account_structure0.Make (Scaffold) |
| 321 | Inst.fold | ||
| 322 | { | ||
| 323 | car = | ||
| 324 | (fun (type inner) | ||
| 325 | v | ||
| 326 | (module Gen_cons : Account_structure0.Gen_f_cons.S | ||
| 327 | with type inner = inner | ||
| 328 | and type outer = b) | ||
| 329 | -> | ||
| 330 | let open Option.Let_syntax in | ||
| 331 | let module Type_cons = | ||
| 332 | Gen_cons.Specialize (Account_type.Scaffold) (Account_type) | ||
| 333 | in | ||
| 334 | let module Own_cons = Gen_cons.Specialize (F) (Structure) in | ||
| 335 | let%map c, v' = f.car v (fun el -> mkt (Type_cons.cons el)) in | ||
| 336 | (c, Own_cons.cons v')); | ||
| 337 | } | ||
| 338 | end | 303 | end |
| 339 | end | ||
| 340 | 304 | ||
| 341 | module Typed_account_path = struct | 305 | module Account_structure (F : Account_structure0.Scaffold) = struct |
| 342 | type 'a elem = Leaf | Node of string * 'a | Ind of string * 'a elem | 306 | module Structure = Account_structure0.Make (F) |
| 343 | [@@deriving sexp_of] | 307 | include Structure |
| 308 | |||
| 309 | module Mapper = struct | ||
| 310 | type nonrec 'b t = { | ||
| 311 | car : | ||
| 312 | 'a. | ||
| 313 | 'a f F.t -> | ||
| 314 | ('a Account_type.f Account_type.elem -> Account_type.t0) -> | ||
| 315 | ('b * 'a f F.t) option; | ||
| 316 | } | ||
| 317 | |||
| 318 | let map (type b c) (f : c t) (mkt : b Account_type.f -> Account_type.t0) : | ||
| 319 | b f -> (c * b f) option = | ||
| 320 | let module Inst = | ||
| 321 | Account_structure0.Visitor (F) (Structure) | ||
| 322 | (struct | ||
| 323 | type 'b t = (c * 'b f) option | ||
| 324 | end) | ||
| 325 | in | ||
| 326 | Inst.visit | ||
| 327 | { | ||
| 328 | car = | ||
| 329 | (fun (type inner) | ||
| 330 | v | ||
| 331 | (module Gen_cons : Account_structure0.Gen_f_cons.S | ||
| 332 | with type inner = inner | ||
| 333 | and type outer = b) | ||
| 334 | -> | ||
| 335 | let open Option.Let_syntax in | ||
| 336 | let module Type_cons = | ||
| 337 | Gen_cons.Specialize (Account_type.Scaffold) (Account_type) | ||
| 338 | in | ||
| 339 | let module Own_cons = Gen_cons.Specialize (F) (Structure) in | ||
| 340 | let%map c, v' = f.car v (fun el -> mkt (Type_cons.cons el)) in | ||
| 341 | (c, Own_cons.cons v')); | ||
| 342 | } | ||
| 343 | end | ||
| 344 | end | ||
| 344 | 345 | ||
| 345 | include Account_structure (struct | 346 | module Typed_account_path = struct |
| 346 | type 'a t = 'a elem [@@deriving sexp_of] | 347 | type 'a elem = Leaf | Node of string * 'a | Ind of string * 'a elem |
| 347 | end) | 348 | [@@deriving sexp_of] |
| 348 | end | ||
| 349 | 349 | ||
| 350 | module Account_path = struct | 350 | include Account_structure (struct |
| 351 | type t = string list [@@deriving compare, sexp] | 351 | type 'a t = 'a elem [@@deriving sexp_of] |
| 352 | end) | ||
| 353 | end | ||
| 352 | 354 | ||
| 353 | module Map = Map.Make (struct | 355 | module Account_path = struct |
| 354 | type nonrec t = t [@@deriving compare, sexp] | 356 | type t = string list [@@deriving compare, sexp] |
| 355 | end) | ||
| 356 | end | ||
| 357 | 357 | ||
| 358 | module Account_hierarchy = struct | 358 | module Map = Map.Make (struct |
| 359 | (* The contents of an account of category 'a *) | 359 | type nonrec t = t [@@deriving compare, sexp] |
| 360 | type 'a core = | 360 | end) |
| 361 | (* Comprises of subaccounts of its subcategories *) | 361 | end |
| 362 | | Node of 'a String.Map.t | ||
| 363 | (* Comprises of subaccounts of its own category *) | ||
| 364 | | Ind of 'a account String.Map.t | ||
| 365 | (* Has no subaccounts, has a balance in a certain commodity *) | ||
| 366 | | Leaf of Commodity_id.t * Money.Diff.t | ||
| 367 | [@@deriving sexp_of] | ||
| 368 | 362 | ||
| 369 | and extra = { description : String.t } [@@deriving sexp_of] | 363 | module Account_hierarchy = struct |
| 370 | and 'a account = extra * 'a core | 364 | (* The contents of an account of category 'a *) |
| 365 | type 'a core = | ||
| 366 | (* Comprises of subaccounts of its subcategories *) | ||
| 367 | | Node of 'a String.Map.t | ||
| 368 | (* Comprises of subaccounts of its own category *) | ||
| 369 | | Ind of 'a account String.Map.t | ||
| 370 | (* Has no subaccounts, has a balance in a certain commodity *) | ||
| 371 | | Leaf of Commodity_id.t * Money.Diff.t | ||
| 372 | [@@deriving sexp_of] | ||
| 371 | 373 | ||
| 372 | include Account_structure (struct | 374 | and extra = { description : String.t } [@@deriving sexp_of] |
| 373 | type 'a t = 'a account [@@deriving sexp_of] | 375 | and 'a account = extra * 'a core |
| 374 | end) | ||
| 375 | 376 | ||
| 376 | (* All accounts *) | 377 | module Scaffold = struct |
| 377 | type world = t0 String.Map.t | 378 | type 'a t = 'a account [@@deriving sexp_of] |
| 378 | 379 | end | |
| 379 | let rec unsafe_alter_aux (subaid : Account_path.t) | 380 | |
| 380 | (f : | 381 | module Structure = Account_structure (Scaffold) |
| 381 | Account_type.t0 -> | 382 | |
| 382 | extra -> | 383 | (* All accounts *) |
| 383 | Commodity_id.t -> | 384 | type world = Structure.t0 String.Map.t |
| 384 | Money.Diff.t -> | 385 | |
| 385 | 'a * extra * Money.Diff.t) : 'a Mapper.t = | 386 | let rec unsafe_alter_aux (subaid : Account_path.t) |
| 386 | { | 387 | (f : |
| 387 | car = | 388 | Account_type.t0 -> |
| 388 | (fun in_acc mkt -> | 389 | extra -> |
| 390 | Commodity_id.t -> | ||
| 391 | Money.Diff.t -> | ||
| 392 | 'a * extra * Money.Diff.t) : 'a Structure.Mapper.t = | ||
| 393 | { | ||
| 394 | car = | ||
| 395 | (fun in_acc mkt -> | ||
| 396 | let open Option.Let_syntax in | ||
| 397 | match (subaid, in_acc) with | ||
| 398 | | [], (extra, Leaf (acc_comm, acc_bal)) -> | ||
| 399 | let x, extra', acc_bal' = | ||
| 400 | f (mkt Account_type.Leaf) extra acc_comm acc_bal | ||
| 401 | in | ||
| 402 | Some (x, (extra', Leaf (acc_comm, acc_bal'))) | ||
| 403 | | [], _ -> None | ||
| 404 | | subaid0 :: subaid, (extra, Node subaccs) -> | ||
| 405 | let%bind subacc = Map.find subaccs subaid0 in | ||
| 406 | let%map x, subacc' = | ||
| 407 | Structure.Mapper.map | ||
| 408 | (unsafe_alter_aux subaid f) | ||
| 409 | (fun k -> mkt (Node k)) | ||
| 410 | subacc | ||
| 411 | in | ||
| 412 | (x, (extra, Node (Map.set subaccs ~key:subaid0 ~data:subacc'))) | ||
| 413 | | subaid0 :: subaid, (extra, Ind subaccs) -> | ||
| 414 | let%bind subacc = Map.find subaccs subaid0 in | ||
| 415 | let%map x, subacc' = | ||
| 416 | (unsafe_alter_aux subaid f).car subacc mkt | ||
| 417 | in | ||
| 418 | (x, (extra, Ind (Map.set subaccs ~key:subaid0 ~data:subacc'))) | ||
| 419 | | _ :: _, (_, Leaf _) -> None); | ||
| 420 | } | ||
| 421 | |||
| 422 | let unsafe_alter (aid : Account_path.t) | ||
| 423 | (f : | ||
| 424 | Account_type.t0 -> | ||
| 425 | extra -> | ||
| 426 | Commodity_id.t -> | ||
| 427 | Money.Diff.t -> | ||
| 428 | 'a * extra * Money.Diff.t) (w : world) : ('a * world) option = | ||
| 429 | match aid with | ||
| 430 | | [] -> None | ||
| 431 | | aid0 :: subaid -> ( | ||
| 389 | let open Option.Let_syntax in | 432 | let open Option.Let_syntax in |
| 390 | match (subaid, in_acc) with | 433 | match%bind Map.find w aid0 with |
| 391 | | [], (extra, Leaf (acc_comm, acc_bal)) -> | 434 | | Asset acc -> |
| 392 | let x, extra', acc_bal' = | 435 | let%map x, acc' = |
| 393 | f (mkt Account_type.Leaf) extra acc_comm acc_bal | 436 | (unsafe_alter_aux subaid f).car acc (fun k -> Asset k) |
| 394 | in | 437 | in |
| 395 | Some (x, (extra', Leaf (acc_comm, acc_bal'))) | 438 | (x, Map.set w ~key:aid0 ~data:(Asset acc')) |
| 396 | | [], _ -> None | 439 | | Expense acc -> |
| 397 | | subaid0 :: subaid, (extra, Node subaccs) -> | 440 | let%map x, acc' = |
| 398 | let%bind subacc = Map.find subaccs subaid0 in | 441 | (unsafe_alter_aux subaid f).car acc (fun k -> Expense k) |
| 399 | let%map x, subacc' = | ||
| 400 | Mapper.map | ||
| 401 | (unsafe_alter_aux subaid f) | ||
| 402 | (fun k -> mkt (Node k)) | ||
| 403 | subacc | ||
| 404 | in | 442 | in |
| 405 | (x, (extra, Node (Map.set subaccs ~key:subaid0 ~data:subacc'))) | 443 | (x, Map.set w ~key:aid0 ~data:(Expense acc')) |
| 406 | | subaid0 :: subaid, (extra, Ind subaccs) -> | 444 | | Income acc -> |
| 407 | let%bind subacc = Map.find subaccs subaid0 in | 445 | let%map x, acc' = |
| 408 | let%map x, subacc' = (unsafe_alter_aux subaid f).car subacc mkt in | 446 | (unsafe_alter_aux subaid f).car acc (fun k -> Income k) |
| 409 | (x, (extra, Ind (Map.set subaccs ~key:subaid0 ~data:subacc'))) | 447 | in |
| 410 | | _ :: _, (_, Leaf _) -> None); | 448 | (x, Map.set w ~key:aid0 ~data:(Income acc')) |
| 411 | } | 449 | | Liability acc -> |
| 450 | let%map x, acc' = | ||
| 451 | (unsafe_alter_aux subaid f).car acc (fun k -> Liability k) | ||
| 452 | in | ||
| 453 | (x, Map.set w ~key:aid0 ~data:(Liability acc')) | ||
| 454 | | Equity acc -> | ||
| 455 | let%map x, acc' = | ||
| 456 | (unsafe_alter_aux subaid f).car acc (fun k -> Equity k) | ||
| 457 | in | ||
| 458 | (x, Map.set w ~key:aid0 ~data:(Equity acc'))) | ||
| 459 | |||
| 460 | (** Update the balance (debit/credit ([dc])) of account [aid] [by_amount] | ||
| 461 | (commodity: [in_comm]) in [world], giving the updated world and the pre | ||
| 462 | and post balances for [aid] iff the account exists in [world]. *) | ||
| 463 | let update_bal aid dc by_amount in_comm (w : world) : | ||
| 464 | (Money.Diff.t * Money.Diff.t * world) option = | ||
| 465 | let open Option.Let_syntax in | ||
| 466 | let%bind mres, w' = | ||
| 467 | unsafe_alter aid | ||
| 468 | (fun acc_type acc_extra acc_comm acc_bal -> | ||
| 469 | if not ([%equal: Commodity_id.t] acc_comm in_comm) then | ||
| 470 | (None, acc_extra, acc_bal) | ||
| 471 | else | ||
| 472 | let on_debit = | ||
| 473 | match acc_type with | ||
| 474 | | Asset _ -> `Incr | ||
| 475 | | Expense _ -> `Incr | ||
| 476 | | Income _ -> `Decr | ||
| 477 | | Liability _ -> `Decr | ||
| 478 | | Equity _ -> `Decr | ||
| 479 | in | ||
| 480 | let acc_bal' = | ||
| 481 | Money.Diff.(acc_bal + of_amount by_amount dc ~on_debit) | ||
| 482 | in | ||
| 483 | (Some (acc_bal, acc_bal'), acc_extra, acc_bal')) | ||
| 484 | w | ||
| 485 | in | ||
| 486 | let%map pre_bal, post_bal = mres in | ||
| 487 | (pre_bal, post_bal, w') | ||
| 488 | |||
| 489 | let get_bal aid (w : world) : (Commodity_id.t * Money.Diff.t) option = | ||
| 490 | let open Option.Let_syntax in | ||
| 491 | let%map cb, _world' = | ||
| 492 | unsafe_alter aid | ||
| 493 | (fun _acc_type acc_extra acc_comm acc_bal -> | ||
| 494 | ((acc_comm, acc_bal), acc_extra, acc_bal)) | ||
| 495 | w | ||
| 496 | in | ||
| 497 | cb | ||
| 498 | |||
| 499 | let add_balance_maps m1 m2 : Money.Diff.t Commodity_id.Map.t = | ||
| 500 | Map.merge m1 m2 ~f:(fun ~key:_comm -> function | ||
| 501 | | `Both (b1, b2) -> Some Money.Diff.(b1 + b2) | ||
| 502 | | `Left b | `Right b -> Some b) | ||
| 503 | |||
| 504 | let rec collect_balances : type a. | ||
| 505 | a Structure.f account -> Money.Diff.t Commodity_id.Map.t = function | ||
| 506 | | _extra, Leaf (acc_comm, acc_bal) -> | ||
| 507 | Commodity_id.Map.singleton acc_comm acc_bal | ||
| 508 | | _extra, Ind subaccs -> | ||
| 509 | Map.fold subaccs ~init:Commodity_id.Map.empty | ||
| 510 | ~f:(fun ~key:_ ~data:subacc comm_bal_sums -> | ||
| 511 | add_balance_maps comm_bal_sums (collect_balances subacc)) | ||
| 512 | | _extra, Node subaccs -> | ||
| 513 | Map.fold subaccs ~init:Commodity_id.Map.empty | ||
| 514 | ~f:(fun ~key:_ ~data:subacc comm_bal_sums -> | ||
| 515 | let module Visitor = | ||
| 516 | Account_structure0.Basic_visitor (Scaffold) (Structure) | ||
| 517 | in | ||
| 518 | add_balance_maps comm_bal_sums | ||
| 519 | (Visitor.visit { car = collect_balances } subacc)) | ||
| 520 | |||
| 521 | type delete_error = Not_found | Nonzero_balance | ||
| 412 | 522 | ||
| 413 | let unsafe_alter (aid : Account_path.t) | 523 | (* |
| 414 | (f : | ||
| 415 | Account_type.t0 -> | ||
| 416 | extra -> | ||
| 417 | Commodity_id.t -> | ||
| 418 | Money.Diff.t -> | ||
| 419 | 'a * extra * Money.Diff.t) (w : world) : ('a * world) option = | ||
| 420 | match aid with | ||
| 421 | | [] -> None | ||
| 422 | | aid0 :: subaid -> ( | ||
| 423 | let open Option.Let_syntax in | ||
| 424 | match%bind Map.find w aid0 with | ||
| 425 | | Asset acc -> | ||
| 426 | let%map x, acc' = | ||
| 427 | (unsafe_alter_aux subaid f).car acc (fun k -> Asset k) | ||
| 428 | in | ||
| 429 | (x, Map.set w ~key:aid0 ~data:(Asset acc')) | ||
| 430 | | Expense acc -> | ||
| 431 | let%map x, acc' = | ||
| 432 | (unsafe_alter_aux subaid f).car acc (fun k -> Expense k) | ||
| 433 | in | ||
| 434 | (x, Map.set w ~key:aid0 ~data:(Expense acc')) | ||
| 435 | | Income acc -> | ||
| 436 | let%map x, acc' = | ||
| 437 | (unsafe_alter_aux subaid f).car acc (fun k -> Income k) | ||
| 438 | in | ||
| 439 | (x, Map.set w ~key:aid0 ~data:(Income acc')) | ||
| 440 | | Liability acc -> | ||
| 441 | let%map x, acc' = | ||
| 442 | (unsafe_alter_aux subaid f).car acc (fun k -> Liability k) | ||
| 443 | in | ||
| 444 | (x, Map.set w ~key:aid0 ~data:(Liability acc')) | ||
| 445 | | Equity acc -> | ||
| 446 | let%map x, acc' = | ||
| 447 | (unsafe_alter_aux subaid f).car acc (fun k -> Equity k) | ||
| 448 | in | ||
| 449 | (x, Map.set w ~key:aid0 ~data:(Equity acc'))) | ||
| 450 | |||
| 451 | (** Update the balance (debit/credit ([dc])) of account [aid] [by_amount] | ||
| 452 | (commodity: [in_comm]) in [world], giving the updated world and the pre | ||
| 453 | and post balances for [aid] iff the account exists in [world]. *) | ||
| 454 | let update_bal aid dc by_amount in_comm (w : world) : | ||
| 455 | (Money.Diff.t * Money.Diff.t * world) option = | ||
| 456 | let open Option.Let_syntax in | ||
| 457 | let%bind mres, w' = | ||
| 458 | unsafe_alter aid | ||
| 459 | (fun acc_type acc_extra acc_comm acc_bal -> | ||
| 460 | if not ([%equal: Commodity_id.t] acc_comm in_comm) then | ||
| 461 | (None, acc_extra, acc_bal) | ||
| 462 | else | ||
| 463 | let on_debit = | ||
| 464 | match acc_type with | ||
| 465 | | Asset _ -> `Incr | ||
| 466 | | Expense _ -> `Incr | ||
| 467 | | Income _ -> `Decr | ||
| 468 | | Liability _ -> `Decr | ||
| 469 | | Equity _ -> `Decr | ||
| 470 | in | ||
| 471 | let acc_bal' = | ||
| 472 | Money.Diff.(acc_bal + of_amount by_amount dc ~on_debit) | ||
| 473 | in | ||
| 474 | (Some (acc_bal, acc_bal'), acc_extra, acc_bal')) | ||
| 475 | w | ||
| 476 | in | ||
| 477 | let%map pre_bal, post_bal = mres in | ||
| 478 | (pre_bal, post_bal, w') | ||
| 479 | |||
| 480 | let get_bal aid (w : world) : (Commodity_id.t * Money.Diff.t) option = | ||
| 481 | let open Option.Let_syntax in | ||
| 482 | let%map cb, _world' = | ||
| 483 | unsafe_alter aid | ||
| 484 | (fun _acc_type acc_extra acc_comm acc_bal -> | ||
| 485 | ((acc_comm, acc_bal), acc_extra, acc_bal)) | ||
| 486 | w | ||
| 487 | in | ||
| 488 | cb | ||
| 489 | |||
| 490 | let add_balance_maps m1 m2 : Money.Diff.t Commodity_id.Map.t = | ||
| 491 | Map.merge m1 m2 ~f:(fun ~key:_comm -> function | ||
| 492 | | `Both (b1, b2) -> Some Money.Diff.(b1 + b2) | ||
| 493 | | `Left b | `Right b -> Some b) | ||
| 494 | |||
| 495 | let rec collect_balances : type a. | ||
| 496 | a f account -> Money.Diff.t Commodity_id.Map.t = function | ||
| 497 | | _extra, Leaf (acc_comm, acc_bal) -> | ||
| 498 | Commodity_id.Map.singleton acc_comm acc_bal | ||
| 499 | | _extra, Ind subaccs -> | ||
| 500 | Map.fold subaccs ~init:Commodity_id.Map.empty | ||
| 501 | ~f:(fun ~key:_ ~data:subacc comm_bal_sums -> | ||
| 502 | add_balance_maps comm_bal_sums (collect_balances subacc)) | ||
| 503 | | _extra, Node subaccs -> | ||
| 504 | Map.fold subaccs ~init:Commodity_id.Map.empty | ||
| 505 | ~f:(fun ~key:_ ~data:subacc comm_bal_sums -> | ||
| 506 | add_balance_maps comm_bal_sums | ||
| 507 | (Folder.fold { car = collect_balances } subacc)) | ||
| 508 | |||
| 509 | type delete_error = Not_found | Nonzero_balance | ||
| 510 | |||
| 511 | (* | ||
| 512 | let rec delete_aux : type a. (Account_path.t * a f account) -> (a f account, delete_error) result = function | 524 | let rec delete_aux : type a. (Account_path.t * a f account) -> (a f account, delete_error) result = function |
| 513 | | [], (extra, Leaf (_acc_comm, acc_bal)) -> | 525 | | [], (extra, Leaf (_acc_comm, acc_bal)) -> |
| 514 | if Money.Diff.(acc_bal = ~$0) then | 526 | if Money.Diff.(acc_bal = ~$0) then |
| @@ -516,152 +528,152 @@ module Account_hierarchy = struct | |||
| 516 | let delete (aid : Account_path.t) (w : world) = | 528 | let delete (aid : Account_path.t) (w : world) = |
| 517 | *) | 529 | *) |
| 518 | 530 | ||
| 519 | let world_inst : world = | 531 | let world_inst : world = |
| 520 | String.Map.of_alist_exn | 532 | String.Map.of_alist_exn |
| 521 | [ | 533 | [ |
| 522 | ( "Assets", | 534 | ( "Assets", |
| 523 | Asset | 535 | Structure.Asset |
| 524 | ( { description = "assets" }, | 536 | ( { description = "assets" }, |
| 525 | Ind | 537 | Ind |
| 526 | (String.Map.of_alist_exn | 538 | (String.Map.of_alist_exn |
| 527 | [ | 539 | [ |
| 528 | ( "Current", | 540 | ( "Current", |
| 529 | ( { description = "current" }, | 541 | ( { description = "current" }, |
| 530 | Node | 542 | Node |
| 531 | (String.Map.of_alist_exn | 543 | (String.Map.of_alist_exn |
| 532 | [ | 544 | [ |
| 533 | ( "Checking", | 545 | ( "Checking", |
| 534 | Bank | 546 | Structure.Bank |
| 535 | ( { description = "bnak accounts" }, | 547 | ( { description = "bnak accounts" }, |
| 536 | Ind | 548 | Ind |
| 537 | (String.Map.of_alist_exn | 549 | (String.Map.of_alist_exn |
| 538 | [ | 550 | [ |
| 539 | ( "ING", | 551 | ( "ING", |
| 540 | ( { description = "ING bank" }, | 552 | ( { description = "ING bank" }, |
| 541 | Leaf ("EUC", Money.Diff.(~$0)) | 553 | Leaf ("EUC", Money.Diff.(~$0)) |
| 542 | ) ); | 554 | ) ); |
| 543 | ( "N26", | 555 | ( "N26", |
| 544 | ( { description = "ING bank" }, | 556 | ( { description = "ING bank" }, |
| 545 | Leaf ("EUC", Money.Diff.(~$0)) | 557 | Leaf ("EUC", Money.Diff.(~$0)) |
| 546 | ) ); | 558 | ) ); |
| 547 | ]) ) ); | 559 | ]) ) ); |
| 548 | ]) ) ); | 560 | ]) ) ); |
| 549 | ]) ) ); | 561 | ]) ) ); |
| 550 | ] | 562 | ] |
| 551 | end | 563 | end |
| 552 | 564 | ||
| 553 | module Bal_assert = struct | 565 | module Bal_assert = struct |
| 554 | type t = { account : Account_path.t; labels : Labels.t; bal : Money.Diff.t } | 566 | type t = { account : Account_path.t; labels : Labels.t; bal : Money.Diff.t } |
| 555 | [@@deriving sexp_of] | 567 | [@@deriving sexp_of] |
| 556 | end | 568 | end |
| 557 | 569 | ||
| 558 | module Account_decl = struct | 570 | module Account_decl = struct |
| 559 | type t = { | 571 | type t = { |
| 560 | type_ : Account_type.t0; | 572 | type_ : Account_type.t0; |
| 561 | parent : Account_path.t; | 573 | parent : Account_path.t; |
| 562 | name : string; | 574 | name : string; |
| 563 | commodity : Commodity_id.t; | 575 | commodity : Commodity_id.t; |
| 564 | extra : Account_hierarchy.extra; | 576 | extra : Account_hierarchy.extra; |
| 565 | } | 577 | } |
| 566 | [@@deriving sexp_of] | 578 | [@@deriving sexp_of] |
| 567 | end | 579 | end |
| 568 | 580 | ||
| 569 | module Tx : sig | 581 | module Tx : sig |
| 570 | type entry = { | 582 | type entry = { |
| 571 | dc : Debit_credit.t; | 583 | dc : Debit_credit.t; |
| 572 | commodity : Commodity_id.t; | 584 | commodity : Commodity_id.t; |
| 573 | amount : Money.Amount.t; | 585 | amount : Money.Amount.t; |
| 574 | assertion : Money.Diff.t option; | 586 | assertion : Money.Diff.t option; |
| 575 | } | 587 | } |
| 576 | |||
| 577 | (* Private because we only want to allow constructing balanced transactions. *) | ||
| 578 | type t = private { | ||
| 579 | cleared : Date.t option; | ||
| 580 | entries : entry Account_path.Map.t; | ||
| 581 | labels : Labels.t; | ||
| 582 | } | ||
| 583 | |||
| 584 | type error = Unbalanced | ||
| 585 | |||
| 586 | val make : | ||
| 587 | cleared:Date.t option -> | ||
| 588 | entries:entry Account_path.Map.t -> | ||
| 589 | labels:Labels.t -> | ||
| 590 | (t, error) result | ||
| 591 | |||
| 592 | val sexp_of_t : t -> Sexp.t | ||
| 593 | end = struct | ||
| 594 | type entry = { | ||
| 595 | dc : Debit_credit.t; | ||
| 596 | commodity : Commodity_id.t; | ||
| 597 | amount : Money.Amount.t; | ||
| 598 | assertion : Money.Diff.t option; | ||
| 599 | } | ||
| 600 | [@@deriving sexp_of] | ||
| 601 | 588 | ||
| 602 | type t = { | 589 | (* Private because we only want to allow constructing balanced transactions. *) |
| 603 | cleared : Date.t option; | 590 | type t = private { |
| 604 | entries : entry Account_path.Map.t; | 591 | cleared : Date.t option; |
| 605 | labels : Labels.t; | 592 | entries : entry Account_path.Map.t; |
| 606 | } | 593 | labels : Labels.t; |
| 607 | [@@deriving sexp_of] | 594 | } |
| 608 | 595 | ||
| 609 | type error = Unbalanced | 596 | type error = Unbalanced |
| 610 | 597 | ||
| 611 | let is_balanced entries = | 598 | val make : |
| 612 | Map.fold entries ~init:Commodity_id.Map.empty | 599 | cleared:Date.t option -> |
| 613 | ~f:(fun ~key:_ ~data comm_balances -> | 600 | entries:entry Account_path.Map.t -> |
| 614 | Map.update comm_balances data.commodity ~f:(fun ocomm_bal -> | 601 | labels:Labels.t -> |
| 615 | let comm_bal = Option.value ocomm_bal ~default:Money.Diff.(~$0) in | 602 | (t, error) result |
| 616 | match data.dc with | 603 | |
| 617 | | Debit_credit.Debit -> Money.Diff.(comm_bal +% data.amount) | 604 | val sexp_of_t : t -> Sexp.t |
| 618 | | Debit_credit.Credit -> Money.Diff.(comm_bal -% data.amount))) | 605 | end = struct |
| 619 | |> Map.for_all ~f:(fun comm_bal -> Money.Diff.(comm_bal = ~$0)) | 606 | type entry = { |
| 620 | 607 | dc : Debit_credit.t; | |
| 621 | let make ~cleared ~entries ~labels = | 608 | commodity : Commodity_id.t; |
| 622 | if not (is_balanced entries) then Error Unbalanced | 609 | amount : Money.Amount.t; |
| 623 | else Ok { cleared; entries; labels } | 610 | assertion : Money.Diff.t option; |
| 624 | end | 611 | } |
| 612 | [@@deriving sexp_of] | ||
| 613 | |||
| 614 | type t = { | ||
| 615 | cleared : Date.t option; | ||
| 616 | entries : entry Account_path.Map.t; | ||
| 617 | labels : Labels.t; | ||
| 618 | } | ||
| 619 | [@@deriving sexp_of] | ||
| 625 | 620 | ||
| 626 | type item = | 621 | type error = Unbalanced |
| 627 | | Tx_item of Tx.t | 622 | |
| 628 | | Bal_assert_item of Bal_assert.t | 623 | let is_balanced entries = |
| 629 | | Account_decl_item of Account_decl.t | 624 | Map.fold entries ~init:Commodity_id.Map.empty |
| 630 | [@@deriving sexp_of] | 625 | ~f:(fun ~key:_ ~data comm_balances -> |
| 626 | Map.update comm_balances data.commodity ~f:(fun ocomm_bal -> | ||
| 627 | let comm_bal = Option.value ocomm_bal ~default:Money.Diff.(~$0) in | ||
| 628 | match data.dc with | ||
| 629 | | Debit_credit.Debit -> Money.Diff.(comm_bal +% data.amount) | ||
| 630 | | Debit_credit.Credit -> Money.Diff.(comm_bal -% data.amount))) | ||
| 631 | |> Map.for_all ~f:(fun comm_bal -> Money.Diff.(comm_bal = ~$0)) | ||
| 632 | |||
| 633 | let make ~cleared ~entries ~labels = | ||
| 634 | if not (is_balanced entries) then Error Unbalanced | ||
| 635 | else Ok { cleared; entries; labels } | ||
| 636 | end | ||
| 631 | 637 | ||
| 632 | type t = item list [@@deriving sexp_of] | 638 | type item = |
| 639 | | Tx_item of Tx.t | ||
| 640 | | Bal_assert_item of Bal_assert.t | ||
| 641 | | Account_decl_item of Account_decl.t | ||
| 642 | [@@deriving sexp_of] | ||
| 633 | 643 | ||
| 634 | module World = struct | 644 | type t = item list [@@deriving sexp_of] |
| 635 | type t = Account_hierarchy.world | ||
| 636 | 645 | ||
| 637 | let empty : t = String.Map.empty | 646 | module World = struct |
| 647 | type t = Account_hierarchy.world | ||
| 638 | 648 | ||
| 639 | let apply_tx (tx : Tx.t) world : t option = | 649 | let empty : t = String.Map.empty |
| 640 | Map.fold_option tx.entries ~init:world | 650 | |
| 641 | ~f:(fun ~key:aid ~(data : Tx.entry) world -> | 651 | let apply_tx (tx : Tx.t) world : t option = |
| 642 | let open Option.Let_syntax in | 652 | Map.fold_option tx.entries ~init:world |
| 643 | let%bind _old_bal, new_bal, world = | 653 | ~f:(fun ~key:aid ~(data : Tx.entry) world -> |
| 644 | Account_hierarchy.update_bal aid data.dc data.amount data.commodity | 654 | let open Option.Let_syntax in |
| 645 | world | 655 | let%bind _old_bal, new_bal, world = |
| 646 | in | 656 | Account_hierarchy.update_bal aid data.dc data.amount data.commodity |
| 647 | match data.assertion with | 657 | world |
| 648 | | None -> Some world | 658 | in |
| 649 | | Some bal_ass -> | 659 | match data.assertion with |
| 650 | if Money.Diff.(bal_ass = new_bal) then Some world else None) | 660 | | None -> Some world |
| 651 | 661 | | Some bal_ass -> | |
| 652 | let apply_ba (ba : Bal_assert.t) world : t option = | 662 | if Money.Diff.(bal_ass = new_bal) then Some world else None) |
| 653 | let open Option.Let_syntax in | 663 | |
| 654 | let%bind _comm, bal = Account_hierarchy.get_bal ba.account world in | 664 | let apply_ba (ba : Bal_assert.t) world : t option = |
| 655 | if not Money.Diff.(bal = ba.bal) then None else Some world | 665 | let open Option.Let_syntax in |
| 656 | 666 | let%bind _comm, bal = Account_hierarchy.get_bal ba.account world in | |
| 657 | let apply_ad (_ad : Account_decl.t) _world : t option = None | 667 | if not Money.Diff.(bal = ba.bal) then None else Some world |
| 658 | 668 | ||
| 659 | let apply : item -> t -> t option = function | 669 | let apply_ad (_ad : Account_decl.t) _world : t option = None |
| 660 | | Tx_item tx -> apply_tx tx | 670 | |
| 661 | | Bal_assert_item ba -> apply_ba ba | 671 | let apply : item -> t -> t option = function |
| 662 | | Account_decl_item ad -> apply_ad ad | 672 | | Tx_item tx -> apply_tx tx |
| 663 | end | 673 | | Bal_assert_item ba -> apply_ba ba |
| 674 | | Account_decl_item ad -> apply_ad ad | ||
| 675 | end | ||
| 664 | 676 | ||
| 665 | module Ctxd_item = struct end | 677 | module Ctxd_item = struct end |
| 666 | 678 | ||
| 667 | let make = Fn.id | 679 | let make = Fn.id |