You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by me...@apache.org on 2026/07/13 16:55:04 UTC
(fineract-site) 01/02: add raw 1.15.0-SNAPSHOT docs
This is an automated email from the ASF dual-hosted git repository.
meonkeys pushed a commit to branch asf-site
in repository https://gitbox.apache.org/repos/asf/fineract-site.git
commit 3c4dd4e052ea2b96b66816247696ab027be9fb0c
Author: Adam Monsen <ha...@gmail.com>
AuthorDate: Mon Jul 13 09:51:43 2026 -0700
add raw 1.15.0-SNAPSHOT docs
will need to change a couple lines to reference local assets
built with `./gradlew asciidoctor` from https://github.com/apache/fineract/commit/45d8e24f82c9c42c46a6762b24e102ad2c723824
---
docs/1.15.0-SNAPSHOT/index.html | 32180 ++++++++++++++++++++++++++++++++++++++
1 file changed, 32180 insertions(+)
diff --git a/docs/1.15.0-SNAPSHOT/index.html b/docs/1.15.0-SNAPSHOT/index.html
new file mode 100644
index 0000000..9306a40
--- /dev/null
+++ b/docs/1.15.0-SNAPSHOT/index.html
@@ -0,0 +1,32180 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta http-equiv="X-UA-Compatible" content="IE=edge">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<meta name="generator" content="Asciidoctor 2.0.18">
+<title>Fineract Platform Documentation</title>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700">
+<style>
+/*! Asciidoctor default stylesheet | MIT License | https://asciidoctor.org */
+/* Uncomment the following line when using as a custom stylesheet */
+/* @import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700"; */
+html{font-family:sans-serif;-webkit-text-size-adjust:100%}
+a{background:none}
+a:focus{outline:thin dotted}
+a:active,a:hover{outline:0}
+h1{font-size:2em;margin:.67em 0}
+b,strong{font-weight:bold}
+abbr{font-size:.9em}
+abbr[title]{cursor:help;border-bottom:1px dotted #dddddf;text-decoration:none}
+dfn{font-style:italic}
+hr{height:0}
+mark{background:#ff0;color:#000}
+code,kbd,pre,samp{font-family:monospace;font-size:1em}
+pre{white-space:pre-wrap}
+q{quotes:"\201C" "\201D" "\2018" "\2019"}
+small{font-size:80%}
+sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
+sup{top:-.5em}
+sub{bottom:-.25em}
+img{border:0}
+svg:not(:root){overflow:hidden}
+figure{margin:0}
+audio,video{display:inline-block}
+audio:not([controls]){display:none;height:0}
+fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
+legend{border:0;padding:0}
+button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
+button,input{line-height:normal}
+button,select{text-transform:none}
+button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}
+button[disabled],html input[disabled]{cursor:default}
+input[type=checkbox],input[type=radio]{padding:0}
+button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
+textarea{overflow:auto;vertical-align:top}
+table{border-collapse:collapse;border-spacing:0}
+*,::before,::after{box-sizing:border-box}
+html,body{font-size:100%}
+body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;line-height:1;position:relative;cursor:auto;-moz-tab-size:4;-o-tab-size:4;tab-size:4;word-wrap:anywhere;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}
+a:hover{cursor:pointer}
+img,object,embed{max-width:100%;height:auto}
+object,embed{height:100%}
+img{-ms-interpolation-mode:bicubic}
+.left{float:left!important}
+.right{float:right!important}
+.text-left{text-align:left!important}
+.text-right{text-align:right!important}
+.text-center{text-align:center!important}
+.text-justify{text-align:justify!important}
+.hide{display:none}
+img,object,svg{display:inline-block;vertical-align:middle}
+textarea{height:auto;min-height:50px}
+select{width:100%}
+.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
+div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0}
+a{color:#2156a5;text-decoration:underline;line-height:inherit}
+a:hover,a:focus{color:#1d4b8f}
+a img{border:0}
+p{line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
+p aside{font-size:.875em;line-height:1.35;font-style:italic}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
+h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
+h1{font-size:2.125em}
+h2{font-size:1.6875em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
+h4,h5{font-size:1.125em}
+h6{font-size:1em}
+hr{border:solid #dddddf;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em}
+em,i{font-style:italic;line-height:inherit}
+strong,b{font-weight:bold;line-height:inherit}
+small{font-size:60%;line-height:inherit}
+code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
+ul,ol,dl{line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
+ul,ol{margin-left:1.5em}
+ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0}
+ul.circle{list-style-type:circle}
+ul.disc{list-style-type:disc}
+ul.square{list-style-type:square}
+ul.circle ul:not([class]),ul.disc ul:not([class]),ul.square ul:not([class]){list-style:inherit}
+ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
+dl dt{margin-bottom:.3125em;font-weight:bold}
+dl dd{margin-bottom:1.25em}
+blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
+blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
+@media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
+h1{font-size:2.75em}
+h2{font-size:2.3125em}
+h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
+h4{font-size:1.4375em}}
+table{background:#fff;margin-bottom:1.25em;border:1px solid #dedede;word-wrap:normal}
+table thead,table tfoot{background:#f7f8f7}
+table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
+table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
+table tr.even,table tr.alt{background:#f8f8f7}
+table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{line-height:1.6}
+h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
+h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
+.center{margin-left:auto;margin-right:auto}
+.stretch{width:100%}
+.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table}
+.clearfix::after,.float-group::after{clear:both}
+:not(pre).nobreak{word-wrap:normal}
+:not(pre).nowrap{white-space:nowrap}
+:not(pre).pre-wrap{white-space:pre-wrap}
+:not(pre):not([class^=L])>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background:#f7f7f8;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed}
+pre{color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;line-height:1.45;text-rendering:optimizeSpeed}
+pre code,pre pre{color:inherit;font-size:inherit;line-height:inherit}
+pre>code{display:block}
+pre.nowrap,pre.nowrap pre{white-space:pre;word-wrap:normal}
+em em{font-style:normal}
+strong strong{font-weight:400}
+.keyseq{color:rgba(51,51,51,.8)}
+kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background:#f7f7f7;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 0 rgba(0,0,0,.2),inset 0 0 0 .1em #fff;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}
+.keyseq kbd:first-child{margin-left:0}
+.keyseq kbd:last-child{margin-right:0}
+.menuseq,.menuref{color:#000}
+.menuseq b:not(.caret),.menuref{font-weight:inherit}
+.menuseq{word-spacing:-.02em}
+.menuseq b.caret{font-size:1.25em;line-height:.8}
+.menuseq i.caret{font-weight:bold;text-align:center;width:.45em}
+b.button::before,b.button::after{position:relative;top:-1px;font-weight:400}
+b.button::before{content:"[";padding:0 3px 0 2px}
+b.button::after{content:"]";padding:0 2px 0 3px}
+p a>code:hover{color:rgba(0,0,0,.9)}
+#header,#content,#footnotes,#footer{width:100%;margin:0 auto;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
+#header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table}
+#header::after,#content::after,#footnotes::after,#footer::after{clear:both}
+#content{margin-top:1.25em}
+#content::before{content:none}
+#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
+#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf}
+#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px}
+#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:flex;flex-flow:row wrap}
+#header .details span:first-child{margin-left:-.125em}
+#header .details span.email a{color:rgba(0,0,0,.85)}
+#header .details br{display:none}
+#header .details br+span::before{content:"\00a0\2013\00a0"}
+#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
+#header .details br+span#revremark::before{content:"\00a0|\00a0"}
+#header #revnumber{text-transform:capitalize}
+#header #revnumber::after{content:"\00a0"}
+#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
+#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em}
+#toc>ul{margin-left:.125em}
+#toc ul.sectlevel0>li>a{font-style:italic}
+#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
+#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
+#toc li{line-height:1.3334;margin-top:.3334em}
+#toc a{text-decoration:none}
+#toc a:active{text-decoration:underline}
+#toctitle{color:#7a2518;font-size:1.2em}
+@media screen and (min-width:768px){#toctitle{font-size:1.375em}
+body.toc2{padding-left:15em;padding-right:0}
+#toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
+#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
+#toc.toc2>ul{font-size:.9em;margin-bottom:0}
+#toc.toc2 ul ul{margin-left:0;padding-left:1em}
+#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
+body.toc2.toc-right{padding-left:0;padding-right:15em}
+body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}}
+@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
+#toc.toc2{width:20em}
+#toc.toc2 #toctitle{font-size:1.375em}
+#toc.toc2>ul{font-size:.95em}
+#toc.toc2 ul ul{padding-left:1.25em}
+body.toc2.toc-right{padding-left:0;padding-right:20em}}
+#content #toc{border:1px solid #e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;border-radius:4px}
+#content #toc>:first-child{margin-top:0}
+#content #toc>:last-child{margin-bottom:0}
+#footer{max-width:none;background:rgba(0,0,0,.8);padding:1.25em}
+#footer-text{color:hsla(0,0%,100%,.8);line-height:1.44}
+#content{margin-bottom:.625em}
+.sect1{padding-bottom:.625em}
+@media screen and (min-width:768px){#content{margin-bottom:1.25em}
+.sect1{padding-bottom:1.25em}}
+.sect1:last-child{padding-bottom:0}
+.sect1+.sect1{border-top:1px solid #e7e7e9}
+#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
+#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
+#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
+#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
+#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
+details,.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
+details{margin-left:1.25rem}
+details>summary{cursor:pointer;display:block;position:relative;line-height:1.6;margin-bottom:.625rem;outline:none;-webkit-tap-highlight-color:transparent}
+details>summary::-webkit-details-marker{display:none}
+details>summary::before{content:"";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;left:-1.25rem;transform:translateX(15%)}
+details[open]>summary::before{border:solid transparent;border-top:solid;border-width:.5em .3em 0;transform:translateY(15%)}
+details>summary::after{content:"";width:1.25rem;height:1em;position:absolute;top:.3em;left:-1.25rem}
+.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
+table.tableblock.fit-content>caption.title{white-space:nowrap;width:0}
+.paragraph.lead>p,#preamble>.sectionbody>[class=paragraph]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)}
+.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
+.admonitionblock>table td.icon{text-align:center;width:80px}
+.admonitionblock>table td.icon img{max-width:none}
+.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
+.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6);word-wrap:anywhere}
+.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
+.exampleblock>.content{border:1px solid #e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;border-radius:4px}
+.exampleblock>.content>:first-child{margin-top:0}
+.exampleblock>.content>:last-child{margin-bottom:0}
+.sidebarblock{border:1px solid #dbdbd6;margin-bottom:1.25em;padding:1.25em;background:#f3f3f2;border-radius:4px}
+.sidebarblock>:first-child{margin-top:0}
+.sidebarblock>:last-child{margin-bottom:0}
+.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
+.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
+.literalblock pre,.listingblock>.content>pre{border-radius:4px;overflow-x:auto;padding:1em;font-size:.8125em}
+@media screen and (min-width:768px){.literalblock pre,.listingblock>.content>pre{font-size:.90625em}}
+@media screen and (min-width:1280px){.literalblock pre,.listingblock>.content>pre{font-size:1em}}
+.literalblock pre,.listingblock>.content>pre:not(.highlight),.listingblock>.content>pre[class=highlight],.listingblock>.content>pre[class^="highlight "]{background:#f7f7f8}
+.literalblock.output pre{color:#f7f7f8;background:rgba(0,0,0,.9)}
+.listingblock>.content{position:relative}
+.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:inherit;opacity:.5}
+.listingblock:hover code[data-lang]::before{display:block}
+.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:inherit;opacity:.5}
+.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"}
+.listingblock pre.highlightjs{padding:0}
+.listingblock pre.highlightjs>code{padding:1em;border-radius:4px}
+.listingblock pre.prettyprint{border-width:0}
+.prettyprint{background:#f7f7f8}
+pre.prettyprint .linenums{line-height:1.45;margin-left:2em}
+pre.prettyprint li{background:none;list-style-type:inherit;padding-left:0}
+pre.prettyprint li code[data-lang]::before{opacity:1}
+pre.prettyprint li:not(:first-child) code[data-lang]::before{display:none}
+table.linenotable{border-collapse:separate;border:0;margin-bottom:0;background:none}
+table.linenotable td[class]{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal}
+table.linenotable td.code{padding-left:.75em}
+table.linenotable td.linenos,pre.pygments .linenos{border-right:1px solid;opacity:.35;padding-right:.5em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
+pre.pygments span.linenos{display:inline-block;margin-right:.75em}
+.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
+.quoteblock:not(.excerpt)>.title{margin-left:-1.5em;margin-bottom:.75em}
+.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
+.quoteblock blockquote{margin:0;padding:0;border:0}
+.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
+.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
+.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right}
+.verseblock{margin:0 1em 1.25em}
+.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans-serif;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
+.verseblock pre strong{font-weight:400}
+.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
+.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
+.quoteblock .attribution br,.verseblock .attribution br{display:none}
+.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}
+.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none}
+.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0}
+.quoteblock.abstract{margin:0 1em 1.25em;display:block}
+.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center}
+.quoteblock.excerpt>blockquote,.quoteblock .quoteblock{padding:0 0 .25em 1em;border-left:.25em solid #dddddf}
+.quoteblock.excerpt,.quoteblock .quoteblock{margin-left:0}
+.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem}
+.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;font-size:.85rem;text-align:left;margin-right:0}
+p.tableblock:last-child{margin-bottom:0}
+td.tableblock>.content{margin-bottom:1.25em;word-wrap:anywhere}
+td.tableblock>.content>:last-child{margin-bottom:-1.25em}
+table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
+table.grid-all>*>tr>*{border-width:1px}
+table.grid-cols>*>tr>*{border-width:0 1px}
+table.grid-rows>*>tr>*{border-width:1px 0}
+table.frame-all{border-width:1px}
+table.frame-ends{border-width:1px 0}
+table.frame-sides{border-width:0 1px}
+table.frame-none>colgroup+*>:first-child>*,table.frame-sides>colgroup+*>:first-child>*{border-top-width:0}
+table.frame-none>:last-child>:last-child>*,table.frame-sides>:last-child>:last-child>*{border-bottom-width:0}
+table.frame-none>*>tr>:first-child,table.frame-ends>*>tr>:first-child{border-left-width:0}
+table.frame-none>*>tr>:last-child,table.frame-ends>*>tr>:last-child{border-right-width:0}
+table.stripes-all>*>tr,table.stripes-odd>*>tr:nth-of-type(odd),table.stripes-even>*>tr:nth-of-type(even),table.stripes-hover>*>tr:hover{background:#f8f8f7}
+th.halign-left,td.halign-left{text-align:left}
+th.halign-right,td.halign-right{text-align:right}
+th.halign-center,td.halign-center{text-align:center}
+th.valign-top,td.valign-top{vertical-align:top}
+th.valign-bottom,td.valign-bottom{vertical-align:bottom}
+th.valign-middle,td.valign-middle{vertical-align:middle}
+table thead th,table tfoot th{font-weight:bold}
+tbody tr th{background:#f7f8f7}
+tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
+p.tableblock>code:only-child{background:none;padding:0}
+p.tableblock{font-size:1em}
+ol{margin-left:1.75em}
+ul li ol{margin-left:1.5em}
+dl dd{margin-left:1.125em}
+dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
+li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
+ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none}
+ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em}
+ul.unstyled,ol.unstyled{margin-left:0}
+li>p:empty:only-child::before{content:"";display:inline-block}
+ul.checklist>li>p:first-child{margin-left:-1em}
+ul.checklist>li>p:first-child>.fa-square-o:first-child,ul.checklist>li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em}
+ul.checklist>li>p:first-child>input[type=checkbox]:first-child{margin-right:.25em}
+ul.inline{display:flex;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em}
+ul.inline>li{margin-left:1.25em}
+.unstyled dl dt{font-weight:400;font-style:normal}
+ol.arabic{list-style-type:decimal}
+ol.decimal{list-style-type:decimal-leading-zero}
+ol.loweralpha{list-style-type:lower-alpha}
+ol.upperalpha{list-style-type:upper-alpha}
+ol.lowerroman{list-style-type:lower-roman}
+ol.upperroman{list-style-type:upper-roman}
+ol.lowergreek{list-style-type:lower-greek}
+.hdlist>table,.colist>table{border:0;background:none}
+.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
+td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}
+td.hdlist1{font-weight:bold;padding-bottom:1.25em}
+td.hdlist2{word-wrap:anywhere}
+.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
+.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top}
+.colist td:not([class]):first-child img{max-width:none}
+.colist td:not([class]):last-child{padding:.25em 0}
+.thumb,.th{line-height:0;display:inline-block;border:4px solid #fff;box-shadow:0 0 0 1px #ddd}
+.imageblock.left{margin:.25em .625em 1.25em 0}
+.imageblock.right{margin:.25em 0 1.25em .625em}
+.imageblock>.title{margin-bottom:0}
+.imageblock.thumb,.imageblock.th{border-width:6px}
+.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
+.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
+.image.left{margin-right:.625em}
+.image.right{margin-left:.625em}
+a.image{text-decoration:none;display:inline-block}
+a.image object{pointer-events:none}
+sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}
+sup.footnote a,sup.footnoteref a{text-decoration:none}
+sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline}
+#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
+#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0}
+#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em}
+#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em}
+#footnotes .footnote:last-of-type{margin-bottom:0}
+#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
+div.unbreakable{page-break-inside:avoid}
+.big{font-size:larger}
+.small{font-size:smaller}
+.underline{text-decoration:underline}
+.overline{text-decoration:overline}
+.line-through{text-decoration:line-through}
+.aqua{color:#00bfbf}
+.aqua-background{background:#00fafa}
+.black{color:#000}
+.black-background{background:#000}
+.blue{color:#0000bf}
+.blue-background{background:#0000fa}
+.fuchsia{color:#bf00bf}
+.fuchsia-background{background:#fa00fa}
+.gray{color:#606060}
+.gray-background{background:#7d7d7d}
+.green{color:#006000}
+.green-background{background:#007d00}
+.lime{color:#00bf00}
+.lime-background{background:#00fa00}
+.maroon{color:#600000}
+.maroon-background{background:#7d0000}
+.navy{color:#000060}
+.navy-background{background:#00007d}
+.olive{color:#606000}
+.olive-background{background:#7d7d00}
+.purple{color:#600060}
+.purple-background{background:#7d007d}
+.red{color:#bf0000}
+.red-background{background:#fa0000}
+.silver{color:#909090}
+.silver-background{background:#bcbcbc}
+.teal{color:#006060}
+.teal-background{background:#007d7d}
+.white{color:#bfbfbf}
+.white-background{background:#fafafa}
+.yellow{color:#bfbf00}
+.yellow-background{background:#fafa00}
+span.icon>.fa{cursor:default}
+a span.icon>.fa{cursor:inherit}
+.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
+.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c}
+.admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
+.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900}
+.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400}
+.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000}
+.conum[data-value]{display:inline-block;color:#fff!important;background:rgba(0,0,0,.8);border-radius:50%;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
+.conum[data-value] *{color:#fff!important}
+.conum[data-value]+b{display:none}
+.conum[data-value]::after{content:attr(data-value)}
+pre .conum[data-value]{position:relative;top:-.125em}
+b.conum *{color:inherit!important}
+.conum:not([data-value]):empty{display:none}
+dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}
+h1,h2,p,td.content,span.alt,summary{letter-spacing:-.01em}
+p strong,td.content strong,div.footnote strong{letter-spacing:-.005em}
+p,blockquote,dt,td.content,span.alt,summary{font-size:1.0625rem}
+p{margin-bottom:1.25rem}
+.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
+.exampleblock>.content{background:#fffef7;border-color:#e0e0dc;box-shadow:0 1px 4px #e0e0dc}
+.print-only{display:none!important}
+@page{margin:1.25cm .75cm}
+@media print{*{box-shadow:none!important;text-shadow:none!important}
+html{font-size:80%}
+a{color:inherit!important;text-decoration:underline!important}
+a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
+a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
+abbr[title]{border-bottom:1px dotted}
+abbr[title]::after{content:" (" attr(title) ")"}
+pre,blockquote,tr,img,object,svg{page-break-inside:avoid}
+thead{display:table-header-group}
+svg{max-width:100%}
+p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
+h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
+#header,#content,#footnotes,#footer{max-width:none}
+#toc,.sidebarblock,.exampleblock>.content{background:none!important}
+#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important}
+body.book #header{text-align:center}
+body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em}
+body.book #header .details{border:0!important;display:block;padding:0!important}
+body.book #header .details span:first-child{margin-left:0!important}
+body.book #header .details br{display:block}
+body.book #header .details br+span::before{content:none!important}
+body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
+body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
+.listingblock code[data-lang]::before{display:block}
+#footer{padding:0 .9375em}
+.hide-on-print{display:none!important}
+.print-only{display:block!important}
+.hide-for-print{display:none!important}
+.show-for-print{display:inherit!important}}
+@media amzn-kf8,print{#header>h1:first-child{margin-top:1.25rem}
+.sect1{padding:0!important}
+.sect1+.sect1{border:0}
+#footer{background:none}
+#footer-text{color:rgba(0,0,0,.6);font-size:.9em}}
+@media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}}
+</style>
+<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
+<style>
+pre.rouge table td { padding: 5px; }
+pre.rouge table pre { margin: 0; }
+pre.rouge .cm {
+ color: #999988;
+ font-style: italic;
+}
+pre.rouge .cp {
+ color: #999999;
+ font-weight: bold;
+}
+pre.rouge .c1 {
+ color: #999988;
+ font-style: italic;
+}
+pre.rouge .cs {
+ color: #999999;
+ font-weight: bold;
+ font-style: italic;
+}
+pre.rouge .c, pre.rouge .ch, pre.rouge .cd, pre.rouge .cpf {
+ color: #999988;
+ font-style: italic;
+}
+pre.rouge .err {
+ color: #a61717;
+ background-color: #e3d2d2;
+}
+pre.rouge .gd {
+ color: #000000;
+ background-color: #ffdddd;
+}
+pre.rouge .ge {
+ color: #000000;
+ font-style: italic;
+}
+pre.rouge .gr {
+ color: #aa0000;
+}
+pre.rouge .gh {
+ color: #999999;
+}
+pre.rouge .gi {
+ color: #000000;
+ background-color: #ddffdd;
+}
+pre.rouge .go {
+ color: #888888;
+}
+pre.rouge .gp {
+ color: #555555;
+}
+pre.rouge .gs {
+ font-weight: bold;
+}
+pre.rouge .gu {
+ color: #aaaaaa;
+}
+pre.rouge .gt {
+ color: #aa0000;
+}
+pre.rouge .kc {
+ color: #000000;
+ font-weight: bold;
+}
+pre.rouge .kd {
+ color: #000000;
+ font-weight: bold;
+}
+pre.rouge .kn {
+ color: #000000;
+ font-weight: bold;
+}
+pre.rouge .kp {
+ color: #000000;
+ font-weight: bold;
+}
+pre.rouge .kr {
+ color: #000000;
+ font-weight: bold;
+}
+pre.rouge .kt {
+ color: #445588;
+ font-weight: bold;
+}
+pre.rouge .k, pre.rouge .kv {
+ color: #000000;
+ font-weight: bold;
+}
+pre.rouge .mf {
+ color: #009999;
+}
+pre.rouge .mh {
+ color: #009999;
+}
+pre.rouge .il {
+ color: #009999;
+}
+pre.rouge .mi {
+ color: #009999;
+}
+pre.rouge .mo {
+ color: #009999;
+}
+pre.rouge .m, pre.rouge .mb, pre.rouge .mx {
+ color: #009999;
+}
+pre.rouge .sa {
+ color: #000000;
+ font-weight: bold;
+}
+pre.rouge .sb {
+ color: #d14;
+}
+pre.rouge .sc {
+ color: #d14;
+}
+pre.rouge .sd {
+ color: #d14;
+}
+pre.rouge .s2 {
+ color: #d14;
+}
+pre.rouge .se {
+ color: #d14;
+}
+pre.rouge .sh {
+ color: #d14;
+}
+pre.rouge .si {
+ color: #d14;
+}
+pre.rouge .sx {
+ color: #d14;
+}
+pre.rouge .sr {
+ color: #009926;
+}
+pre.rouge .s1 {
+ color: #d14;
+}
+pre.rouge .ss {
+ color: #990073;
+}
+pre.rouge .s, pre.rouge .dl {
+ color: #d14;
+}
+pre.rouge .na {
+ color: #008080;
+}
+pre.rouge .bp {
+ color: #999999;
+}
+pre.rouge .nb {
+ color: #0086B3;
+}
+pre.rouge .nc {
+ color: #445588;
+ font-weight: bold;
+}
+pre.rouge .no {
+ color: #008080;
+}
+pre.rouge .nd {
+ color: #3c5d5d;
+ font-weight: bold;
+}
+pre.rouge .ni {
+ color: #800080;
+}
+pre.rouge .ne {
+ color: #990000;
+ font-weight: bold;
+}
+pre.rouge .nf, pre.rouge .fm {
+ color: #990000;
+ font-weight: bold;
+}
+pre.rouge .nl {
+ color: #990000;
+ font-weight: bold;
+}
+pre.rouge .nn {
+ color: #555555;
+}
+pre.rouge .nt {
+ color: #000080;
+}
+pre.rouge .vc {
+ color: #008080;
+}
+pre.rouge .vg {
+ color: #008080;
+}
+pre.rouge .vi {
+ color: #008080;
+}
+pre.rouge .nv, pre.rouge .vm {
+ color: #008080;
+}
+pre.rouge .ow {
+ color: #000000;
+ font-weight: bold;
+}
+pre.rouge .o {
+ color: #000000;
+ font-weight: bold;
+}
+pre.rouge .w {
+ color: #bbbbbb;
+}
+pre.rouge {
+ background-color: #f8f8f8;
+}
+</style>
+</head>
+<body class="book toc2 toc-left">
+<div id="header">
+<h1>Fineract Platform Documentation</h1>
+<div class="details">
+<span id="revnumber">version 1.15.0-SNAPSHOT</span>
+</div>
+<div id="toc" class="toc2">
+<div id="toctitle">Table of Contents</div>
+<ul class="sectlevel1">
+<li><a href="#_license">License</a></li>
+<li><a href="#_preface">Preface</a></li>
+<li><a href="#_introduction">Introduction</a>
+<ul class="sectlevel2">
+<li><a href="#_platform_for_digital_financial_services">Platform for Digital Financial Services</a></li>
+<li><a href="#_about">About</a></li>
+<li><a href="#_contribute">Contribute</a></li>
+<li><a href="#_downloads">Downloads</a></li>
+<li><a href="#_resources">Resources</a></li>
+</ul>
+</li>
+<li><a href="#_deployment">Deployment</a>
+<ul class="sectlevel2">
+<li><a href="#_plugins">Plugins</a></li>
+<li><a href="#_https">HTTPS</a></li>
+<li><a href="#_docker_compose">Docker Compose</a></li>
+<li><a href="#_application_server">Application Server</a></li>
+<li><a href="#_fineract_instance_types">Fineract Instance types</a></li>
+<li><a href="#_kubernetes">Kubernetes</a></li>
+<li><a href="#_aws">AWS</a></li>
+<li><a href="#_google_cloud">Google Cloud</a></li>
+<li><a href="#_apache_software_foundation_infrastructure">Apache Software Foundation Infrastructure</a></li>
+</ul>
+</li>
+<li><a href="#_architecture">Architecture</a>
+<ul class="sectlevel2">
+<li><a href="#_history">History</a></li>
+<li><a href="#_resources_2">Resources</a></li>
+<li><a href="#_system_overview">System Overview</a></li>
+<li><a href="#_functional_overview">Functional Overview</a></li>
+<li><a href="#_principles">Principles</a></li>
+<li><a href="#_design_overview">Design Overview</a></li>
+<li><a href="#_persistence">Persistence</a></li>
+<li><a href="#_idempotency">Idempotency</a></li>
+<li><a href="#_validation">Validation</a></li>
+<li><a href="#_batch_execution_and_jobs">Batch execution and jobs</a></li>
+<li><a href="#_loan_account_locking">Loan account locking</a></li>
+<li><a href="#_technology">Technology</a></li>
+<li><a href="#_modules">Modules</a></li>
+<li><a href="#_introducing_business_date_into_fineract_community_version">Introducing Business Date into Fineract - Community version</a></li>
+<li><a href="#_reliable_event_framework">Reliable event framework</a></li>
+<li><a href="#_introducing_advanced_payment_allocation">Introducing Advanced payment allocation</a></li>
+<li><a href="#_api_backward_compatibility">API Backward Compatibility</a></li>
+</ul>
+</li>
+<li><a href="#_command_processing">Command Processing</a>
+<ul class="sectlevel2">
+<li><a href="#_introduction_2">Introduction</a></li>
+<li><a href="#_architecture_3">Architecture</a></li>
+<li><a href="#_commands">Commands</a></li>
+<li><a href="#_command_handler">Command Handler</a></li>
+<li><a href="#_command_hooks">Command Hooks</a></li>
+<li><a href="#_command_store">Command Store</a></li>
+<li><a href="#_command_audit">Command Audit</a></li>
+<li><a href="#_command_idempotency">Command Idempotency</a></li>
+<li><a href="#_command_observability">Command Observability</a></li>
+<li><a href="#_refactoring_instructions">Refactoring Instructions</a></li>
+</ul>
+</li>
+<li><a href="#_features">Features</a>
+<ul class="sectlevel2">
+<li><a href="#_capitalized_income">Capitalized Income</a></li>
+<li><a href="#_buy_down_fee">Buy Down Fee</a></li>
+<li><a href="#_approved_amount_modification_on_loans">Approved amount modification on loans</a></li>
+<li><a href="#_backdated_interest_modification">Backdated interest modification</a></li>
+<li><a href="#_interest_rate_modification_for_progressive_loan">Interest Rate Modification For Progressive Loan</a></li>
+<li><a href="#_contract_termination">Contract Termination</a></li>
+<li><a href="#_loan_charges">Loan Charges</a></li>
+<li><a href="#_loan_product_templates">Loan Product Templates</a></li>
+<li><a href="#_journal_entry_aggregation">Journal Entry Aggregation</a></li>
+<li><a href="#_asset_externalization">Asset Externalization</a></li>
+<li><a href="#_pause_delinquency">Pause Delinquency</a></li>
+<li><a href="#_loan_re_amortization">Loan Re-Amortization</a></li>
+<li><a href="#_loan_re_aging">Loan Re-Aging</a></li>
+<li><a href="#_delayed_schedule_captures_full_term_tranches">Delayed Schedule Captures / Full Term Tranches</a></li>
+<li><a href="#_loan_origination_details">Loan Origination Details</a></li>
+<li><a href="#_taxes_on_loan_charges">Taxes on Loan Charges</a></li>
+</ul>
+</li>
+<li><a href="#_working_capital_loan_projected_amortization_schedule">Working Capital Loan — Projected Amortization Schedule</a>
+<ul class="sectlevel2">
+<li><a href="#_overview_17">Overview</a></li>
+<li><a href="#_goal">Goal</a></li>
+<li><a href="#_configuration_at_loan_product_level_3">Configuration at Loan Product Level</a></li>
+<li><a href="#_origination_fee_discount_treatment">Origination Fee (Discount) Treatment</a></li>
+<li><a href="#_lifecycle">Lifecycle</a></li>
+<li><a href="#_input_parameters">Input Parameters</a></li>
+<li><a href="#_formulas">Formulas</a></li>
+<li><a href="#_schedule_fields">Schedule Fields</a></li>
+<li><a href="#_eir_based_income_recognition">EIR-Based Income Recognition</a></li>
+<li><a href="#_calculation_algorithm">Calculation Algorithm</a></li>
+<li><a href="#_loan_balance">Loan Balance</a></li>
+<li><a href="#_cob_close_of_business_processing">COB (Close of Business) Processing</a></li>
+<li><a href="#_special_handling_4">Special Handling</a></li>
+<li><a href="#_accounting_entries_11">Accounting Entries</a></li>
+<li><a href="#_api_endpoints_9">API Endpoints</a></li>
+<li><a href="#_business_events_7">Business Events</a></li>
+<li><a href="#_database">Database</a></li>
+<li><a href="#_transaction_types_2">Transaction Types</a></li>
+<li><a href="#_constraints_and_limitations">Constraints and Limitations</a></li>
+<li><a href="#_appendix_tvm_functions">Appendix: TVM Functions</a></li>
+<li><a href="#_working_capital_loan_discount_fee_transactions">Working Capital Loan — Discount Fee Transactions</a></li>
+<li><a href="#_working_capital_loan_charges">Working Capital Loan Charges</a></li>
+<li><a href="#_working_capital_loan_credit_balance_refund_cbr">Working Capital Loan — Credit Balance Refund (CBR)</a></li>
+<li><a href="#_working_capital_loan_goodwill_credit_transaction">Working Capital Loan — Goodwill Credit Transaction</a></li>
+<li><a href="#_working_capital_product_delinquency_management">Working Capital Product Delinquency Management</a></li>
+<li><a href="#_working_capital_loan_eir_calculation">Working Capital Loan EIR Calculation</a></li>
+<li><a href="#_working_capital_loan_planned_and_projected_balances">Working Capital Loan — Planned and Projected Balances</a></li>
+<li><a href="#_working_capital_loan_discount_configuration">Working Capital Loan Discount Configuration</a></li>
+<li><a href="#_savings_interest_posting">Savings Interest Posting</a></li>
+<li><a href="#_working_capital_breach_management_and_near_breach">Working Capital Breach Management and Near Breach</a></li>
+<li><a href="#_working_capital_breach_grace_days">Working Capital Breach Grace Days</a></li>
+<li><a href="#_working_capital_loan_accrual_with_deferred_revenue_amortization_accounting">Working Capital Loan — Accrual with Deferred Revenue Amortization Accounting</a></li>
+<li><a href="#_working_capital_loan_eir_calculation_accounting">Working Capital Loan — EIR Calculation Accounting</a></li>
+<li><a href="#_working_capital_loan_start_dates_breach_and_delinquency">Working Capital Loan Start Dates (Breach and Delinquency)</a></li>
+</ul>
+</li>
+<li><a href="#_fineract_development_environment">Fineract Development Environment</a>
+<ul class="sectlevel2">
+<li><a href="#_git">Git</a></li>
+<li><a href="#_gpg">GPG</a></li>
+<li><a href="#_docker">Docker</a></li>
+<li><a href="#_gradle">Gradle</a></li>
+<li><a href="#_ide">IDE</a></li>
+<li><a href="#_kubernetes_2">Kubernetes</a></li>
+<li><a href="#_tools">Tools</a></li>
+</ul>
+</li>
+<li><a href="#_custom_modules">Custom Modules</a>
+<ul class="sectlevel2">
+<li><a href="#_introduction_5">Introduction</a></li>
+<li><a href="#_custom_services">Custom Services</a></li>
+<li><a href="#_custom_business_steps">Custom Business Steps</a></li>
+<li><a href="#_custom_loan_transaction_processors">Custom Loan Transaction Processors</a></li>
+<li><a href="#_custom_batch_jobs">Custom Batch Jobs</a></li>
+<li><a href="#_custom_database_migration">Custom Database Migration</a></li>
+<li><a href="#_deploying_custom_modules">Deploying Custom Modules</a></li>
+<li><a href="#_outlook">Outlook</a></li>
+</ul>
+</li>
+<li><a href="#_resilience">Resilience</a>
+<ul class="sectlevel2">
+<li><a href="#_introduction_resilience">Introduction Resilience</a></li>
+<li><a href="#_command">Command</a></li>
+<li><a href="#_jobs">Jobs</a></li>
+<li><a href="#_loan">Loan</a></li>
+<li><a href="#_savings">Savings</a></li>
+</ul>
+</li>
+<li><a href="#_security">Security</a>
+<ul class="sectlevel2">
+<li><a href="#_http_basic_authentication">HTTP Basic Authentication</a></li>
+<li><a href="#_oauth">OAuth</a></li>
+<li><a href="#_two_factor_authentication">Two-factor authentication</a></li>
+<li><a href="#_securing_fineract">Securing Fineract</a></li>
+</ul>
+</li>
+<li><a href="#_testing">Testing</a>
+<ul class="sectlevel2">
+<li><a href="#testing-cucumber">Cucumber E2E Tests</a></li>
+<li><a href="#_unit_testing">Unit Testing</a></li>
+<li><a href="#_integration_testing">Integration Testing</a></li>
+</ul>
+</li>
+<li><a href="#_fineract_documentation_guide">Fineract Documentation Guide</a>
+<ul class="sectlevel2">
+<li><a href="#_file_and_folder_layout">File and Folder Layout</a></li>
+<li><a href="#_asciidoc">AsciiDoc</a></li>
+<li><a href="#_diagrams">Diagrams</a></li>
+<li><a href="#_editor">Editor</a></li>
+<li><a href="#_antora">Antora</a></li>
+</ul>
+</li>
+<li><a href="#_releases">Releases</a>
+<ul class="sectlevel2">
+<li><a href="#_configuration_21">Configuration</a></li>
+<li><a href="#_release_process">Release Process</a></li>
+<li><a href="#_maintenance_release_process">Maintenance Release Process</a></li>
+<li><a href="#_publish_release_artifacts">Publish Release Artifacts</a></li>
+</ul>
+</li>
+<li><a href="#_fineract_sdks">Fineract SDKs</a>
+<ul class="sectlevel2">
+<li><a href="#_generate_apache_fineract_api_client">Generate Apache Fineract API Client</a></li>
+</ul>
+</li>
+<li><a href="#_frequently_asked_questions">Frequently Asked Questions</a></li>
+<li><a href="#_glossary_4">Glossary</a></li>
+<li><a href="#_fineract_application_properties">Appendix A: Fineract Application Properties</a>
+<ul class="sectlevel2">
+<li><a href="#_tenant_database_properties">Tenant Database Properties</a></li>
+<li><a href="#_hikari_connection_pool_properties">Hikari Connection Pool Properties</a></li>
+<li><a href="#_ssl_properties">SSL Properties</a></li>
+<li><a href="#_authentication_properties">Authentication Properties</a></li>
+<li><a href="#_tomcat_properties">Tomcat Properties</a></li>
+<li><a href="#_kafka_properties">Kafka Properties</a></li>
+<li><a href="#_metrics_properties">Metrics Properties</a></li>
+<li><a href="#_aws_configuration_properties">AWS Configuration Properties</a></li>
+<li><a href="#_resilience4j_properties">Resilience4j Properties</a></li>
+</ul>
+</li>
+<li><a href="#_third_party_software">Appendix B: Third Party Software</a></li>
+</ul>
+</div>
+</div>
+<div id="content">
+<div class="sect1">
+<h2 id="_license"><a class="link" href="#_license">License</a></h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>Copyright © 2015-2026 Apache Foundation</p>
+</div>
+<div class="paragraph">
+<p>Licensed under the Apache License, Version 2.0 (the "License");<br>
+you may not use this file except in compliance with the License.<br>
+You may obtain a copy of the License at</p>
+</div>
+<div class="paragraph">
+<p><a href="https://www.apache.org/licenses/LICENSE-2.0" class="bare">www.apache.org/licenses/LICENSE-2.0</a></p>
+</div>
+<div class="paragraph">
+<p>Unless required by applicable law or agreed to in writing, software<br>
+distributed under the License is distributed on an "AS IS" BASIS,<br>
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.<br>
+See the License for the specific language governing permissions and<br>
+limitations under the License.</p>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_preface"><a class="link" href="#_preface">Preface</a></h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p><strong>Apache Fineract</strong><br>
+Website: <a href="https://fineract.apache.org" class="bare">fineract.apache.org</a><br>
+Email: <a href="mailto:dev@fineract.apache.org">dev@fineract.apache.org</a></p>
+</div>
+<div class="paragraph">
+<p><strong>Version</strong>: 1.15.0-SNAPSHOT</p>
+</div>
+<div class="paragraph">
+<p><strong>Built on</strong>: Mon Jul 13 09:50:30 PDT 2026</p>
+</div>
+<div class="paragraph">
+<p>For document authors and changelog, view code history for the <code>fineract-doc</code> directory in <a href="https://github.com/apache/fineract/" class="bare">github.com/apache/fineract/</a>.</p>
+</div>
+<!-- toc disabled -->
+</div>
+</div>
+<div class="sect1">
+<h2 id="_introduction"><a class="link" href="#_introduction">Introduction</a></h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="_platform_for_digital_financial_services"><a class="link" href="#_platform_for_digital_financial_services">Platform for Digital Financial Services</a></h3>
+<div class="paragraph">
+<p>See <a href="https://fineract.apache.org" class="bare">fineract.apache.org</a></p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_about"><a class="link" href="#_about">About</a></h3>
+<div class="paragraph">
+<p>See <a href="https://fineract.apache.org/#about" class="bare">fineract.apache.org/#about</a></p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_contribute"><a class="link" href="#_contribute">Contribute</a></h3>
+<div class="paragraph">
+<p>See <a href="https://fineract.apache.org/#contribute" class="bare">fineract.apache.org/#contribute</a></p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_downloads"><a class="link" href="#_downloads">Downloads</a></h3>
+<div class="paragraph">
+<p>See <a href="https://fineract.apache.org/#downloads" class="bare">fineract.apache.org/#downloads</a></p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_resources"><a class="link" href="#_resources">Resources</a></h3>
+<div class="paragraph">
+<p>See <a href="https://fineract.apache.org/#resources" class="bare">fineract.apache.org/#resources</a></p>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_deployment"><a class="link" href="#_deployment">Deployment</a></h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>The recommended <strong>starting point</strong> for deployment is the <a href="https://fineract.apache.org/#downloads">latest stable binary release</a>. Obtain one from an Apache mirror, <a href="https://www.apache.org/info/verification.html">verify it</a>, and try running the JAR file within.</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="bash">java <span class="nt">-jar</span> fineract-provider-1.15.0-SNAPSHOT.jar</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Start with the command above then read and follow tips and tricks in <a href="#_https">HTTPS</a> and <a href="#_security"><em>Security</em></a> as you work towards becoming production-ready.</p>
+</div>
+<div class="sect2">
+<h3 id="_plugins"><a class="link" href="#_plugins">Plugins</a></h3>
+<div class="paragraph">
+<p>Apache Fineract is extensible through plugin JARs (<a href="https://issues.apache.org/jira/browse/FINERACT-1177">FINERACT-1177</a>; based on<br>
+<a href="https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-executable-jar-format.html">Spring Boot’s support</a>). To launch Fineract with plugin JARs in <code>libs/*.jar</code>, use:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="bash">java <span class="nt">-Dloader</span>.path<span class="o">=</span>libs/ <span class="nt">-jar</span> fineract-provider-1.15.0-SNAPSHOT.jar</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>The Fineract "Docker" container image’s <code>ENTRYPOINT</code> uses this, see our <code>Dockerfile</code>. You could therefore build your customized Fineract distribution container image with your own <code>Dockerfile</code> using e.g. <code>FROM apache/fineract:latest</code> and then drop some plugin JARs into <code>/app/libs/</code>.</p>
+</div>
+<div class="paragraph">
+<p>The WAR distribution does not directly support such plugins, but one could "explode" the WAR and drop JARs into <code>WEB-INF/lib</code>; if you know what you are doing, and feel nostalgic of the 1990s still using WARs, instead of the recommended modern Spring Boot distribution.</p>
+</div>
+<div class="paragraph">
+<p>Here is a list of known 3rd-party plugin projects which can be dropped into <code>libs/</code>:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><a href="https://github.com/vorburger/fineract-pentaho" class="bare">github.com/vorburger/fineract-pentaho</a></p>
+</li>
+</ul>
+</div>
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-note" title="Note"></i>
+</td>
+<td class="content">
+The reporting module became our first module experiment out of necessity. We are currently developing a strategy to split up even more internals of Fineract into proper modules. Those that have an incompatible license will be hosted in a separate Git repository (probably on Github under the Mifos organisation). We’ll send out an announcement as soon as we have more to say on this topic.
+</td>
+</tr>
+</table>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_https"><a class="link" href="#_https">HTTPS</a></h3>
+<div class="paragraph">
+<p>Because Apache Fineract deals with customer sensitive personally identifiable information (PII), it very strongly encourages all developers, implementors and end-users to always only use HTTPS. This is why it does not run on HTTP even for local development and enforces use of HTTPS by default.</p>
+</div>
+<div class="paragraph">
+<p>For this purpose, Fineract includes a built-in default SSL certificate. This cert is intended for development on <code>localhost</code>, only. It is not trusted by your browser (because it’s self signed).</p>
+</div>
+<div class="paragraph">
+<p>For production deployments, we recommend running Fineract behind a modern managed cloud native web proxy which includes SSL termination with automatically rotating SSL certificates, either using your favourite cloud provider’s respective solution, or locally setting up the equivalent using e.g. something like NGINX combined with <a href="https://letsencrypt.org">Let’s Encrypt</a>.</p>
+</div>
+<div class="paragraph">
+<p>Such products, when correctly configured, add the conventional <code>X-Forwarded-For</code> and <code>X-Forwarded-Proto</code> HTTP headers, which Fineract (or rather the Spring Framework really) correctly respects since <a href="https://issues.apache.org/jira/browse/FINERACT-914">FINERACT-914 was fixed</a>.</p>
+</div>
+<div class="paragraph">
+<p>Alternatively, you could replace the built-in default SSL certificate with one you obtained from a Certificate Authority. We currently do not document how to do this, because we do not recommend this approach, as it’s cumbersome to configure and support and less secure than a managed auto rotating solution.</p>
+</div>
+<div class="paragraph">
+<p>The Fineract API client supports an insecure mode (<code>FineractClient.Builder#insecure()</code>), and API users such as mobile apps may expose Settings to let end-users accept the self signed certificate. This should always be used for testing only, never in production.</p>
+</div>
+<div class="paragraph">
+<p>All SSL-related properties are tunable. SSL can be turned off by setting the environment variable <code>FINERACT_SERVER_SSL_ENABLED</code> to false. If you do that then please make sure to also change the server port to <code>8080</code> via the variable <code>FINERACT_SERVER_PORT</code>, just for the sake of keeping the conventions.</p>
+</div>
+<div class="paragraph">
+<p>To use a different SSL keystore, set <code>FINERACT_SERVER_SSL_KEY_STORE</code> to a path to a different (not embedded) keystore. The password can be set via <code>FINERACT_SERVER_SSL_KEY_STORE_PASSWORD</code>. See the <code>application.properties</code> file and the <a href="https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html">latest Spring Boot documentation</a> for details.</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_docker_compose"><a class="link" href="#_docker_compose">Docker Compose</a></h3>
+<div class="paragraph">
+<p>TBD</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_application_server"><a class="link" href="#_application_server">Application Server</a></h3>
+<div class="sect3">
+<h4 id="_tomcat"><a class="link" href="#_tomcat">Tomcat</a></h4>
+<div class="paragraph">
+<p>TBD</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_undertow"><a class="link" href="#_undertow">Undertow</a></h4>
+<div class="paragraph">
+<p>TBD</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_jetty"><a class="link" href="#_jetty">Jetty</a></h4>
+<div class="paragraph">
+<p>TBD</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_jboss"><a class="link" href="#_jboss">JBoss</a></h4>
+<div class="paragraph">
+<p>TBD</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_weblogic"><a class="link" href="#_weblogic">Weblogic</a></h4>
+<div class="paragraph">
+<p>TBD</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_payara"><a class="link" href="#_payara">Payara</a></h4>
+<div class="paragraph">
+<p>TBD</p>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_fineract_instance_types"><a class="link" href="#_fineract_instance_types">Fineract Instance types</a></h3>
+<div class="paragraph">
+<p>In cases where Fineract has to deal with high load, it can cause a performance problem for a single Fineract instance.<br>
+To overcome this problem, Fineract instances can be started in different instance types for better scalability and performance in a multi-instance environment:</p>
+</div>
+<div class="ulist">
+<div class="title">Fineract instance types</div>
+<ul>
+<li>
+<p>Read instance</p>
+</li>
+<li>
+<p>Write instance</p>
+</li>
+<li>
+<p>Batch instance</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>Each instance type comes with different restrictions. The specifics can be found in the table below.</p>
+</div>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<caption class="title">Table 1. Instance types</caption>
+<colgroup>
+<col style="width: 25%;">
+<col style="width: 25%;">
+<col style="width: 25%;">
+<col style="width: 25%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-center valign-top"></th>
+<th class="tableblock halign-center valign-top">Read instance</th>
+<th class="tableblock halign-center valign-top">Write instance</th>
+<th class="tableblock halign-center valign-top">Batch instance</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Using only read-only DB connection</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Yes</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">No</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">No</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Batch jobs are automatically scheduled or startable via API</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">No</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">No</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Yes</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Can receive events (business events, hook template events)</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">No</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Yes</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">No</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Can send events (business events, hook template events)</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">No</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Yes</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Yes</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Read APIs supported</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Yes</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Yes</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">No</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Write APIs supported</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">No</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Yes</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">No</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Batch job APIs supported</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">No</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">No</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Yes</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Liquibase migration initiated upon startup</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">No</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Yes</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">No</p></td>
+</tr>
+</tbody>
+</table>
+<div class="sect3">
+<h4 id="_configuring_instance_types_in_single_instance_setup"><a class="link" href="#_configuring_instance_types_in_single_instance_setup">Configuring instance types in single instance setup</a></h4>
+<div class="paragraph">
+<p>If Fineract is running as a single instance, then all of the 3 instance types should be enabled. In this case, there is no need to worry about the configuration, because this is the default behavior.</p>
+</div>
+<div class="imageblock">
+<div class="content">
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOYAAAD8CAIAAADsRU8RAAAdR0lEQVR4Xu2deXRV1dmHLwEDREYTBqUC1iIWKFBqZagFKwtCyQqEoYgUNYupBQErIp8MMrlqkQY+pCAQJikYFFOLtAkQBMkAkSE2EaKEOSEDGZgSyEAS8/163nX3vewMHC73XPbO9z5/ZJ1hn3P3ft/n7L1P7smJrYJhtMImb2AYtWFlGc1wKPsDwygMK8toBivLaAYry2gGK8toBivLaAYry2gGK8toBivLaAYry2gGK8toBivLaAYry2gGK8toBivLaAYry2gGK8toBivLaAYry2gGK8toBivLaAYry2gGK8toBivLaAYry2gGK8toBivLaAYry2gGK8toBivLaAYry2gGK8toRq1S1tfX18aYA7GSw6cJtUpZZEK0gqkZxOr69esFBQWFhYUlJSVlZWVyNFXF [...]
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_configuring_instance_types_in_multi_instance_setup"><a class="link" href="#_configuring_instance_types_in_multi_instance_setup">Configuring instance types in multi-instance setup</a></h4>
+<div class="paragraph">
+<p>A common solution to dealing with the high load is to deploy 1 write and 1 batch instances and deploy multiple read instances with read replicas of the Fineract database.<br>
+In this case, the write instance and the database will be freed from part of the load, because read request will use the separated read instance and its read replica database.</p>
+</div>
+<div class="imageblock">
+<div class="content">
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAuQAAAFCCAIAAAA2a19cAAA+5ElEQVR4Xu3deZhUxbk/8AZUFtk3cQMS4xJcIMarogZErmLgAREIwZ0LSsQAxiWoYGSJMaiIRlERwRURFRPlXkQ2o8gSVAQEVFCQQfZ9HZZB+X1//T5T51A909ZwuqZOdX8/f/Cc7q4+c7reb/V5u6eZThwiIiIiirGEfgURERFRnLBZISIiolgLmpUfiYiIiGKDzQoRERHFGpsVIiIiijU2K0RERBRrbFaIiIgo1tisEBERUayxWSEiIqJYY7NCREREscZmhYiIiGKNzQoRERHFGpsVIiIiijU2K0RERBRrbFaIiIgo1tisEBERUayxWSEiIqJYY7NCREREscZmhYiIiGKNzQoRERHFGpsVIiIiijU2K0RERBRrbFaIiIgo1tisEBERUayxWSEiIqJYY7NCRERE [...]
+</div>
+</div>
+<div class="paragraph">
+<p>Also a common scenario when Close of Business jobs are running and Fineract has to deal with a high amount of processes.<br>
+(In a future release) Fineract (will be) is able to run this CoB jobs in batches.<br>
+In multi-instances environment these CoB jobs can run on multiple batch instances and they don’t have any impact on the performance of the read and write processes.<br>
+The best practice is to deploy 1 master batch instance and multiple worker batch instances.</p>
+</div>
+<div class="imageblock">
+<div class="content">
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA4QAAAEYCAIAAACLF699AAA3lklEQVR4Xu3deZhU1Z3/8QsiAjabLBJNBHU0CTDAMEbBUYkyAgMDIhhExYQHhERFNG4/QUTQGUcd9GccVARRcQFDSOISQHCJNJsIKMgiIIusLfvS0gs06flOnafPLQ/dnUOdKqpO3ffrD55bVadud5/vpy+f3oNSAAAAIE0C8w4AAADgZKGMAgAAIG3CMvo3AAAA4KSgjAIAACBtKKMAAABIG8ooAAAA0oYyCgAAgLShjAIAACBtKKMAAABIG8ooAAAA0oYyCgAAgLShjAIAACBtKKMAAABIG8ooAAAA0oYyCgAAgLShjAIAACBtKKMAAABIG8ooAAAA0oYyCgAAgLShjAIAACBtKKMAAABIG8ooAAAA0oYyCgAAgLShjAIAACBtKKMAAABIG8ooAAAA0oYyCgAA [...]
+</div>
+</div>
+<div class="paragraph">
+<p>These solutions can be mixed with each other, based on the load of the Fineract deployment.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_configuring_instance_type_via_environment_variables"><a class="link" href="#_configuring_instance_type_via_environment_variables">Configuring instance type via environment variables</a></h4>
+<div class="paragraph">
+<p>The Fineract instance type is configurable via environment variables for the following 3 values:</p>
+</div>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<caption class="title">Table 2. Environment variables</caption>
+<colgroup>
+<col style="width: 50%;">
+<col style="width: 50%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-center valign-top">Instance type</th>
+<th class="tableblock halign-center valign-top">Environment variable</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Read instance</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">FINERACT_READ_MODE_ENABLED</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Write instance</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">FINERACT_WRITE_MODE_ENABLED</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Batch instance</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">FINERACT_BATCH_MODE_ENABLED</p></td>
+</tr>
+</tbody>
+</table>
+<div class="paragraph">
+<p>The environment variable values are booleans (true/false). The Fineract instance can be configured in any combination of these instance types, although if all 3 configurations are false, startup will fail. The default value for all 3 values is true.</p>
+</div>
+<div class="paragraph">
+<p>The configured Fineract instance types are easily accessible via a single Spring bean, named <code>FineractProperties.FineractModeProperties</code> that has 4 methods: <code>isReadMode()</code>, <code>isWriteMode()</code>, <code>isBatchMode()</code>, <code>isReadOnlyMode()</code></p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_liquibase_database_migration"><a class="link" href="#_liquibase_database_migration">Liquibase Database Migration</a></h4>
+<div class="paragraph">
+<p>Liquibase data migration is allowed only for write instances</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_apis"><a class="link" href="#_apis">APIs</a></h4>
+<div class="sect4">
+<h5 id="_read_apis_are_allowed_only_for_read_and_write_instances"><a class="link" href="#_read_apis_are_allowed_only_for_read_and_write_instances">Read APIs are allowed only for read and write instances</a></h5>
+<div class="paragraph">
+<p>A Fineract instance is ONLY able to serve read API calls when it’s configured as a read or write instance. In batch instance mode, it won’t serve read API calls.<br>
+If it’s a read or write instance, the read APIs will be served.<br>
+If it’s a batch instance, the read APIs won’t be served and a proper HTTP status code will be returned.<br>
+The distinction whether something is a read API can be decided based on the HTTP request method. If it’s a GET, we can assume it’s a read call.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_write_apis_are_allowed_only_for_write_instances"><a class="link" href="#_write_apis_are_allowed_only_for_write_instances">Write APIs are allowed only for write instances</a></h5>
+<div class="paragraph">
+<p>A Fineract instance is ONLY able to serve write API calls when it’s configured as a write instance. In read or batch instance mode, it won’t serve write API calls.<br>
+If the write APIs won’t be served and a proper HTTP status code will be returned.<br>
+If it’s a write instance, the write APIs will be served except the ones related to batch jobs.<br>
+The distinction whether something is a write API can be decided based on the HTTP request method. If it’s non-GET, we can assume it’s a write call. Also, the write APIs related to batch jobs (starting/stopping jobs) will not be served either.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_batch_job_apis_are_allowed_only_for_batch_instances"><a class="link" href="#_batch_job_apis_are_allowed_only_for_batch_instances">Batch job APIs are allowed only for batch instances</a></h5>
+<div class="paragraph">
+<p>A Fineract instance is ONLY able to serve batch API calls when it’s configured as a batch instance. In read or write instance mode, it won’t serve batch API calls.<br>
+If the batch APIs won’t be served and a proper HTTP status code will be returned.<br>
+If it’s a batch instance, the batch APIs will be served.</p>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_batch_jobs"><a class="link" href="#_batch_jobs">Batch jobs</a></h4>
+<div class="sect4">
+<h5 id="_batch_job_scheduling_is_allowed_only_for_batch_instances"><a class="link" href="#_batch_job_scheduling_is_allowed_only_for_batch_instances">Batch job scheduling is allowed only for batch instances</a></h5>
+<div class="paragraph">
+<p>Batch jobs are scheduled only if the Fineract instance running as a batch instance</p>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_read_only_instance_type_restrictions"><a class="link" href="#_read_only_instance_type_restrictions">Read-only instance type restrictions</a></h4>
+<div class="paragraph">
+<p>If the read mode is enabled, but the write mode and batch mode are disabled, Fineract instance runs in read-only mode.</p>
+</div>
+<div class="sect4">
+<h5 id="_events_are_disabled_for_read_only_instances"><a class="link" href="#_events_are_disabled_for_read_only_instances">Events are disabled for read-only instances</a></h5>
+<div class="paragraph">
+<p>When a Fineract instance is running in read-only mode, all event receiving/sending will be disabled.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_read_only_tenant_connection_support"><a class="link" href="#_read_only_tenant_connection_support">Read-only tenant connection support</a></h5>
+<div class="paragraph">
+<p>With read separation, there’s a possibility to use read-only database connections for read-only instances.<br>
+If the instance is read-only , the DataSource connection used for the tenant will be read-only.<br>
+If the instance is read-only and the configuration for the read-only datasource is not set, the application startup will fail.</p>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_batch_only_instance_type_restrictions"><a class="link" href="#_batch_only_instance_type_restrictions">Batch-only instance type restrictions</a></h4>
+<div class="paragraph">
+<p>If the batch mode is enabled, but the read mode and write mode are disabled, Fineract instance runs in batch-only mode.</p>
+</div>
+<div class="sect4">
+<h5 id="_receiving_events_is_disabled_for_batch_only_instances"><a class="link" href="#_receiving_events_is_disabled_for_batch_only_instances">Receiving events is disabled for batch-only instances</a></h5>
+<div class="paragraph">
+<p>When a Fineract instance is running as batch, event receiving will be disabled while sending events will be still possible since the batch jobs are potentially generating business events.</p>
+</div>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_kubernetes"><a class="link" href="#_kubernetes">Kubernetes</a></h3>
+<div class="paragraph">
+<p>In a scaled Kubernetes environment where multiple Fineract instances are deployed, doing the database migrations properly is essential.</p>
+</div>
+<div class="paragraph">
+<p>Fineract provides a way to run only the Liquibase migrations instead of starting up the whole application server so that you can easily do the migrations before actually upgrading a Fineract instance.</p>
+</div>
+<div class="paragraph">
+<p>The <code>FINERACT_LIQUIBASE_ENABLED</code> flag controls whether Liquibase is enabled or not. For regular read/write/batch manager/batch worker instances this should be disabled.</p>
+</div>
+<div class="paragraph">
+<p>There’s a special Spring profile that should be enabled for running Liquibase only. In can be done via <code>SPRING_PROFILES_ACTIVE</code> environment variable. The profile name is <code>liquibase-only</code>. At the end of the migration process, the application will exit.</p>
+</div>
+<div class="paragraph">
+<p>For the instance running the Liquibase migrations, the profile should be activated.</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_aws"><a class="link" href="#_aws">AWS</a></h3>
+<div class="paragraph">
+<p>TBD</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_google_cloud"><a class="link" href="#_google_cloud">Google Cloud</a></h3>
+<div class="paragraph">
+<p>The <a href="https://www.fineract.dev" class="bare">www.fineract.dev</a> demo server runs on Google Cloud.</p>
+</div>
+<div class="paragraph">
+<p><a href="https://docs.google.com/presentation/d/1-VP4bNkc5kZ3B0yme_vYLiY1qpswnfz8ainnX5fp3l8/">The Running Fineract.dev, SRE style presentation</a> given at <em>ApacheCon 2020</em> has some related background.</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_apache_software_foundation_infrastructure"><a class="link" href="#_apache_software_foundation_infrastructure">Apache Software Foundation Infrastructure</a></h3>
+<div class="paragraph">
+<p>We can order a server from Apache’s infrastructure team and deploy a demo instance…​</p>
+</div>
+<div class="paragraph">
+<p>TBD</p>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_architecture"><a class="link" href="#_architecture">Architecture</a></h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>This document captures the major architectural decisions in platform. The purpose of the document is to provide a guide to the overall structure of the platform; where it fits in the overall context of an MIS solution and its internals so that contributors can more effectively understand how changes that they are considering can be made, and the consequences of those changes.</p>
+</div>
+<div class="paragraph">
+<p>The target audience for this report is both system integrators (who will use the document to gain an understanding of the structure of the platform and its design rationale) and platform contributors who will use the document to reason about future changes and who will update the document as the system evolves.</p>
+</div>
+<div class="sect2">
+<h3 id="_history"><a class="link" href="#_history">History</a></h3>
+<div class="sect3">
+<h4 id="_the_idea"><a class="link" href="#_the_idea">The Idea</a></h4>
+<div class="paragraph">
+<p>Fineract was an idea born out of a wish to create and deploy technology that allows the microfinance industry to scale. The goal is to:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Produce a gold standard management information system suitable for microfinance operations</p>
+</li>
+<li>
+<p>Acts as the basis of a platform for microfinance</p>
+</li>
+<li>
+<p>Open source, owned and driven by member organisations in the community</p>
+</li>
+<li>
+<p>Enabling potential for eco-system of providers located near to MFIs</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_timeline"><a class="link" href="#_timeline">Timeline</a></h4>
+<div class="ulist">
+<ul>
+<li>
+<p>2006: Project initiated by Grameen Foundation</p>
+</li>
+<li>
+<p>Late 2011: Grameen Foundation handed over full responsibility to open source community.</p>
+</li>
+<li>
+<p>2012: Mifos X platform started. Previous members of project come together under the name of Community for Open Source Microfinance (COSM / OpenMF)</p>
+</li>
+<li>
+<p>2013: COSM / OpenMF officially rebranded to Mifos Initiative and receive US 501c3 status.</p>
+</li>
+<li>
+<p>2016: Fineract 1.x began incubation at Apache</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_resources_2"><a class="link" href="#_resources_2">Resources</a></h3>
+<div class="ulist">
+<ul>
+<li>
+<p>Project URL is <a href="https://github.com/apache/fineract" class="bare">github.com/apache/fineract</a></p>
+</li>
+<li>
+<p>Issue tracker is <a href="https://issues.apache.org/jira/projects/FINERACT/summary" class="bare">issues.apache.org/jira/projects/FINERACT/summary</a></p>
+</li>
+<li>
+<p>Download from <a href="http://fineract.apache.org/" class="bare">fineract.apache.org/</a></p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_system_overview"><a class="link" href="#_system_overview">System Overview</a></h3>
+<div class="imageblock">
+<div class="content">
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyIAAAI8CAYAAAAN5h8BAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEwAACxMBAJqcGAAAIABJREFUeJzs3Xd8FVX+//FXJBCkCyhFCEhgFSSCBRRrwBUVFbBRFHWx4a4Cwurqrqu4rq7iflkL6tobqKiga8GCP4qoUVRERUFcitIEFlR6SzK/P84NuQm5ya1zpryfj8d9JLfO5565c8585pw5AyIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIimZZlOwARSbvsOnXq9G7ZsmU/x3EOBFb+9NNPr23btu09oMh2cCIiIiFXrp3OyspatXr16v+EsZ1WIiISIJ06deq1e/fuJ3Nzc1sWFBTULCgoYNasWcycOXP3ihUrVmdnZ1+8 [...]
+</div>
+<div class="title">Figure 1. Platform System Overview</div>
+</div>
+<div class="paragraph">
+<p>Financial institutions deliver their services to customers through a variety of means today.</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Customers can call direct into branches (teller model)</p>
+</li>
+<li>
+<p>Customers can organise into groups (or centers) and agree to meetup at a location and time with FI staff (traditional microfinance).</p>
+</li>
+<li>
+<p>An FI might have a public facing information portal that customers can use for variety of reasons including account management (online banking).</p>
+</li>
+<li>
+<p>An FI might be integrated into a ATM/POS/Card services network that the customer can use.</p>
+</li>
+<li>
+<p>An FI might be integrated with a mobile money operator and support mobile money services for customer (present/future microfinance).</p>
+</li>
+<li>
+<p>An FI might use third party agents to sell on products/services from other banks/FIs.</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>As illustrated in the above diagram, the various stakeholders leverage business apps to perform specific customer or FI related actions. The functionality contained in these business apps can be bundled up and packaged in any way. In the diagram, several of the apps may be combined into one app or any one of the blocks representing an app could be further broken up as needed.</p>
+</div>
+<div class="paragraph">
+<p>The platform is the core engine of the MIS. It hides a lot of the complexity that exists in the business and technical domains needed for an MIS in FIs behind a relatively simple API. It is this API that frees up app developers to innovate and produce apps that can be as general or as bespoke as FIs need them to be.</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_functional_overview"><a class="link" href="#_functional_overview">Functional Overview</a></h3>
+<div class="paragraph">
+<p>As ALL capabilities of the platform are exposed through an API, The API docs are the best place to view a detailed breakdown of what the platform does. See online API Documentation.</p>
+</div>
+<div class="imageblock">
+<div class="content">
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAxgAAAE2CAYAAADiRDQLAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEwAACxMBAJqcGAAAIABJREFUeJzt3XlYVPXix/HPAIobuJSImIkh5r7kGJorWpqFC6bd0jK6KZmaaVqZZrvd9He17WouqdmipoVrejNNcwXF3VzRUlxySRYXFmH4/cFlYhmGAQ6bvF/Pw/PAzDnf5Zzv+Z75zDkzSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwA5TUTfAlpo1a05NSkp6zN3d3RIbG+vk5OS09OLFi68UdbuKi3TbJyU2Ntbk4uLy/YULF8YWdbuKk9K4jUpjn5EV46D4Yx+hNKlZs+bU5OTkfm5ubhbGe96xHfOhVatWZSpWrHh93rx5yeHh4SkpKSkp [...]
+</div>
+<div class="title">Figure 2. Platform Functional Overview</div>
+</div>
+<div class="paragraph">
+<p>At a higher level though we see the capabilities fall into the following categories:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Infrastructure</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Codes</p>
+</li>
+<li>
+<p>Extensible Data Tables</p>
+</li>
+<li>
+<p>Reporting</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>User Administration</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Users</p>
+</li>
+<li>
+<p>Roles</p>
+</li>
+<li>
+<p>Permissions</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Organisation Modelling</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Offices</p>
+</li>
+<li>
+<p>Staff</p>
+</li>
+<li>
+<p>Currency</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Product Configuration</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Charges</p>
+</li>
+<li>
+<p>Loan Products</p>
+</li>
+<li>
+<p>Deposit Products</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Client Data</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Know Your Client (KYC)</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Portfolio Management</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Loan Accounts</p>
+</li>
+<li>
+<p>Deposit Accounts</p>
+</li>
+<li>
+<p>Client/Groups</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>GL Account Management</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Chart of Accounts</p>
+</li>
+<li>
+<p>General Ledger</p>
+</li>
+</ul>
+</div>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_principles"><a class="link" href="#_principles">Principles</a></h3>
+<div class="sect3">
+<h4 id="_restful_api"><a class="link" href="#_restful_api">RESTful API</a></h4>
+<div class="paragraph">
+<p>The platform exposes all its functionality via a <strong>practically-RESTful API</strong>, that communicates using JSON.</p>
+</div>
+<div class="paragraph">
+<p>We use the term <strong>practically-RESTful</strong> in order to make it clear we are not trying to be fully REST compliant but still maintain important RESTful attributes like:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Stateless: platform maintains no conversational or session-based state. The result of this is ability to scale horizontally with ease.</p>
+</li>
+<li>
+<p>Resource-oriented: API is focussed around set of resources using HTTP vocabulary and conventions e.g GET, PUT, POST, DELETE, HTTP status codes. This results in a simple and consistent API for clients.</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>See online API Documentation for more detail.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_multi_tenanted"><a class="link" href="#_multi_tenanted">Multi-tenanted</a></h4>
+<div class="paragraph">
+<p>The Fineract platform has been developed with support for multi-tenancy at the core of its design. This means that it is just as easy to use the platform for Software-as-a-Service (SaaS) type offerings as it is for local installations.</p>
+</div>
+<div class="paragraph">
+<p>The platform uses an approach that isolates an FIs data per database/schema (See Separate Databases and Shared Database, Separate Schemas).</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_extensible"><a class="link" href="#_extensible">Extensible</a></h4>
+<div class="paragraph">
+<p>Whilst each tenant will have a set of core tables, the platform tables can be extended in different ways for each tenant through the use of Data tables functionality.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_command_query_separation"><a class="link" href="#_command_query_separation">Command Query Separation</a></h4>
+<div class="paragraph">
+<p>We separate <strong>commands</strong> (that change data) from <strong>queries</strong> (that read data).</p>
+</div>
+<div class="paragraph">
+<p>Why? There are numerous reasons for choosing this approach which at present is not an attempt at full blown CQRS. The main advantages at present are:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>State changing commands are persisted providing an audit of all state changes.</p>
+</li>
+<li>
+<p>Used to support a general approach to <strong>maker-checker</strong>.</p>
+</li>
+<li>
+<p>State changing commands use the Object-Oriented paradigm (and hence ORM) whilst querys can stay in the data paradigm.</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_maker_checker"><a class="link" href="#_maker_checker">Maker-Checker</a></h4>
+<div class="paragraph">
+<p>Also known as <strong>four-eyes principal</strong>. Enables apps to support a maker-checker style workflow process. Commands that pass validation will be persisted. Maker-checker can be enabled/disabled at fine-grained level for any state changing API.<br>
+Fine grained access control</p>
+</div>
+<div class="paragraph">
+<p>A fine grained permission is associated with each API. Administrators have fine grained control over what roles or users have access to.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_package_structure"><a class="link" href="#_package_structure">Package Structure</a></h4>
+<div class="paragraph">
+<p>The intention is for platform code to be packaged in a vertical slice way (as opposed to layers).<br>
+Source code starts from <a href="https://github.com/apache/fineract/tree/develop/fineract-provider/src/main/java/org/apache/fineract" class="bare">github.com/apache/fineract/tree/develop/fineract-provider/src/main/java/org/apache/fineract</a></p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>accounting</p>
+</li>
+<li>
+<p>useradministration</p>
+</li>
+<li>
+<p>infrastructure</p>
+</li>
+<li>
+<p>portfolio</p>
+<div class="ulist">
+<ul>
+<li>
+<p>charge</p>
+</li>
+<li>
+<p>client</p>
+</li>
+<li>
+<p>fund</p>
+</li>
+<li>
+<p>loanaccount</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>accounting</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>Within each vertical slice is some common packaging structure:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>api - XXXApiResource.java - REST api implementation files</p>
+</li>
+<li>
+<p>handler - XXXCommandHandler.java - specific handlers invoked</p>
+</li>
+<li>
+<p>service - contains read + write services for functional area</p>
+</li>
+<li>
+<p>domain - OO concepts for the functional area</p>
+</li>
+<li>
+<p>data - Data concepts for the area</p>
+</li>
+<li>
+<p>serialization - ability to convert from/to API JSON for functional area</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_design_overview"><a class="link" href="#_design_overview">Design Overview</a></h3>
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-note" title="Note"></i>
+</td>
+<td class="content">
+The implementation of the platform code to process commands through handlers whilst supporting maker-checker and authorisation checks is a little bit convoluted at present and is an area pin-pointed for clean up to make it easier to on board new platform developers. In the mean time below content is used to explain its workings at present.
+</td>
+</tr>
+</table>
+</div>
+<div class="imageblock">
+<div class="content">
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAw4AAAHXCAYAAAAV7A2DAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEwAACxMBAJqcGAAAIABJREFUeJzs3Xlc1NX+x/HXsCMIyi7I6gbuJS6ZJi5pLml5XXLNUq+WqS1allpZ3bzZYqZW9jPLNPfSm2k3NbW0zELLpdxIERUTUBTZEeb3BzFXQphBgQF9Px+Pech3Oed8zozi9zPne84XREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREpGwYrB2AiIiIiIhcU3UgEKj9158uQBpwCjj915+XKyoYJQ43OR8fn0MJCQnh1o5DRERExFocHBz+zM7OrmXtOCzgVK1ateHVq1cfdvHixZYGg8EmICAgNyAgwBASEmLTtGlTu/3791+J [...]
+</div>
+<div class="title">Figure 3. CQRS</div>
+</div>
+<div class="paragraph">
+<p>Taking into account example shown above for the <strong>users</strong> resource.</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Query: GET /users</p>
+</li>
+<li>
+<p>HTTPS API: retrieveAll method on <strong>org.apache.fineract.useradministration.api.UsersApiResource</strong> invoked</p>
+</li>
+<li>
+<p>UsersApiResource.retrieveAll: Check user has permission to access this resources data.</p>
+</li>
+<li>
+<p>UsersApiResource.retrieveAll: Use 'read service' to fetch all users data ('read services' execute simple SQL queries against Database using JDBC)</p>
+</li>
+<li>
+<p>UsersApiResource.retrieveAll: Data returned to converted into JSON response</p>
+</li>
+<li>
+<p>Command: POST /users (Note: data passed in request body)</p>
+</li>
+<li>
+<p>HTTPS API: create method on org.apache.fineract.useradministration.api.UsersApiResource invoked</p>
+</li>
+</ul>
+</div>
+<div class="listingblock">
+<div class="title">UsersApiResource.create</div>
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"> <span class="o">.</span><span class="na">withJson</span><span class="o">(</span><span class="n">apiRequestBodyAsJson</span><span class="o">)</span> <span class="c1">//</span>
+ <span class="o">.</span><span class="na">build</span><span class="o">();</span>
+
+ <span class="kd">final</span> <span class="nc">CommandProcessingResult</span> <span class="n">result</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">commandsSourceWritePlatformService</span><span class="o">.</span><span class="na">logCommandSource</span><span class="o">(</span><span class="n">commandRequest</span><span class="o">);</span>
+
+ <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">toApiJsonSerializer</span><span class="o">.</span><span class="na">serialize</span><span class="o">(</span><span class="n">result</span><span class="o">);</span>
+ <span class="o">}</span>
+
+ <span class="nd">@PUT</span>
+ <span class="nd">@Path</span><span class="o">(</span><span class="s">"{userId}"</span><span class="o">)</span>
+ <span class="nd">@Operation</span><span class="o">(</span><span class="n">summary</span> <span class="o">=</span> <span class="s">"Update a User"</span><span class="o">,</span> <span class="n">operationId</span> <span class="o">=</span> <span class="s">"updateUser"</span><span class="o">,</span> <span class="n">tags</span> <span class="o">=</span> <span class="o">{</span> <span class="s">"Users"</span> <span class="o">},</span> <span class="n">description</span> <span class="o">=</sp [...]
+ <span class="nd">@AlternativeOperationId</span><span class="o">(</span><span class="s">"update_26"</span><span class="o">)</span>
+ <span class="nd">@RequestBody</span><span class="o">(</span><span class="n">required</span> <span class="o">=</span> <span class="kc">true</span><span class="o">,</span> <span class="n">content</span> <span class="o">=</span> <span class="nd">@Content</span><span class="o">(</span><span class="n">schema</span> <span class="o">=</span> <span class="nd">@Schema</span><span class="o">(</span><span class="n">implementation</span> <span class="o">=</span> <span class="nc">UsersApiResource [...]
+ <span class="nd">@ApiResponses</span><span class="o">({</span>
+ <span class="nd">@ApiResponse</span><span class="o">(</span><span class="n">responseCode</span> <span class="o">=</span> <span class="s">"200"</span><span class="o">,</span> <span class="n">description</span> <span class="o">=</span> <span class="s">"OK"</span><span class="o">,</span> <span class="n">content</span> <span class="o">=</span> <span class="nd">@Content</span><span class="o">(</span><span class="n">schema</span> <span class="o">=</span> <span class="nd">@Schema</s [...]
+ <span class="nd">@Consumes</span><span class="o">({</span> <span class="nc">MediaType</span><span class="o">.</span><span class="na">APPLICATION_JSON</span> <span class="o">})</span>
+ <span class="nd">@Produces</span><span class="o">({</span> <span class="nc">MediaType</span><span class="o">.</span><span class="na">APPLICATION_JSON</span> <span class="o">})</span>
+ <span class="kd">public</span> <span class="nc">String</span> <span class="nf">update</span><span class="o">(</span><span class="nd">@PathParam</span><span class="o">(</span><span class="s">"userId"</span><span class="o">)</span> <span class="nd">@Parameter</span><span class="o">(</span><span class="n">description</span> <span class="o">=</span> <span class="s">"userId"</span><span class="o">)</span> <span class="kd">final</span> <span class="nc">Long</span> <span class="n">userId</s [...]
+ <span class="nd">@Parameter</span><span class="o">(</span><span class="n">hidden</span> <span class="o">=</span> <span class="kc">true</span><span class="o">)</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">apiRequestBodyAsJson</span><span class="o">)</span> <span class="o">{</span>
+
+ <span class="kd">final</span> <span class="nc">CommandWrapper</span> <span class="n">commandRequest</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CommandWrapperBuilder</span><span class="o">()</span> <span class="c1">//</span></code></pre>
+</div>
+</div>
+<div class="listingblock">
+<div class="title">Create a CommandWrapper object that represents this create user command and JSON request body. Pass off responsibility for processing to PortfolioCommandSourceWritePlatformService.logCommandSource</div>
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"> <span class="c1">// permission to perform specific task.</span>
+ <span class="k">this</span><span class="o">.</span><span class="na">context</span><span class="o">.</span><span class="na">authenticatedUser</span><span class="o">(</span><span class="n">wrapper</span><span class="o">).</span><span class="na">validateHasPermissionTo</span><span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">getTaskPermissionName</span><span class="o">());</span>
+ <span class="o">}</span>
+ <span class="n">validateIsUpdateAllowed</span><span class="o">();</span>
+
+ <span class="kd">final</span> <span class="nc">String</span> <span class="n">json</span> <span class="o">=</span> <span class="n">wrapper</span><span class="o">.</span><span class="na">getJson</span><span class="o">();</span>
+ <span class="kd">final</span> <span class="nc">JsonElement</span> <span class="n">parsedCommand</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">fromApiJsonHelper</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="n">json</span><span class="o">);</span>
+ <span class="nc">JsonCommand</span> <span class="n">command</span> <span class="o">=</span> <span class="nc">JsonCommand</span><span class="o">.</span><span class="na">from</span><span class="o">(</span><span class="n">json</span><span class="o">,</span> <span class="n">parsedCommand</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">fromApiJsonHelper</span><span class="o">,</span> <span class="n">wrapper</span><span class="o">.</sp [...]
+ <span class="n">wrapper</span><span class="o">.</span><span class="na">getSubentityId</span><span class="o">(),</span> <span class="n">wrapper</span><span class="o">.</span><span class="na">getGroupId</span><span class="o">(),</span> <span class="n">wrapper</span><span class="o">.</span><span class="na">getClientId</span><span class="o">(),</span> <span class="n">wrapper</span><span class="o">.</span><span class="na">getLoanId</span><span class="o">(),</span> <span class= [...]
+ <span class="n">wrapper</span><span class="o">.</span><span class="na">getTransactionId</span><span class="o">(),</span> <span class="n">wrapper</span><span class="o">.</span><span class="na">getHref</span><span class="o">(),</span> <span class="n">wrapper</span><span class="o">.</span><span class="na">getProductId</span><span class="o">(),</span> <span class="n">wrapper</span><span class="o">.</span><span class="na">getCreditBureauId</span><span class="o">(),</span>
+ <span class="n">wrapper</span><span class="o">.</span><span class="na">getOrganisationCreditBureauId</span><span class="o">(),</span> <span class="n">wrapper</span><span class="o">.</span><span class="na">getJobName</span><span class="o">(),</span> <span class="n">wrapper</span><span class="o">.</span><span class="na">getLoanExternalId</span><span class="o">());</span>
+
+ <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">processAndLogCommandService</span><span class="o">.</span><span class="na">executeCommand</span><span class="o">(</span><span class="n">wrapper</span><span class="o">,</span> <span class="n">command</span><span class="o">,</span> <span class="n">isApprovedByChecker</span><span class="o">);</span>
+ <span class="o">}</span>
+
+ <span class="nd">@Override</span>
+ <span class="kd">public</span> <span class="nc">CommandProcessingResult</span> <span class="nf">approveEntry</span><span class="o">(</span><span class="kd">final</span> <span class="nc">Long</span> <span class="n">makerCheckerId</span><span class="o">)</span> <span class="o">{</span>
+ <span class="kd">final</span> <span class="nc">CommandSource</span> <span class="n">commandSourceInput</span> <span class="o">=</span> <span class="n">validateMakerCheckerTransaction</span><span class="o">(</span><span class="n">makerCheckerId</span><span class="o">);</span>
+ <span class="n">validateIsUpdateAllowed</span><span class="o">();</span>
+
+ <span class="kd">final</span> <span class="nc">CommandWrapper</span> <span class="n">wrapper</span> <span class="o">=</span> <span class="nc">CommandWrapper</span><span class="o">.</span><span class="na">fromExistingCommand</span><span class="o">(</span><span class="n">makerCheckerId</span><span class="o">,</span> <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getActionName</span><span class="o">(),</span>
+ <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getEntityName</span><span class="o">(),</span> <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getResourceId</span><span class="o">(),</span> <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getSubResourceId</span><span class="o">(),</span>
+ <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getResourceGetUrl</span><span class="o">(),</span> <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getProductId</span><span class="o">(),</span> <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getOfficeId</span><span class="o">(),</span>
+ <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getGroupId</span><span class="o">(),</span> <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getClientId</span><span class="o">(),</span> <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getLoanId</span><span class="o">(),</span>
+ <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getSavingsId</span><span class="o">(),</span> <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getTransactionId</span><span class="o">(),</span> <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getCreditBureauId</span><span class="o">(),</span>
+ <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getOrganisationCreditBureauId</span><span class="o">(),</span> <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getIdempotencyKey</span><span class="o">(),</span>
+ <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getLoanExternalId</span><span class="o">());</span>
+ <span class="kd">final</span> <span class="nc">JsonElement</span> <span class="n">parsedCommand</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">fromApiJsonHelper</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getCommandAsJson</span><span class="o">());</span>
+ <span class="kd">final</span> <span class="nc">JsonCommand</span> <span class="n">command</span> <span class="o">=</span> <span class="nc">JsonCommand</span><span class="o">.</span><span class="na">fromExistingCommand</span><span class="o">(</span><span class="n">makerCheckerId</span><span class="o">,</span> <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getCommandAsJson</span><span class="o">(),</span> <span class="n">parsedCommand</span><span [...]
+ <span class="k">this</span><span class="o">.</span><span class="na">fromApiJsonHelper</span><span class="o">,</span> <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getEntityName</span><span class="o">(),</span> <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getResourceId</span><span class="o">(),</span>
+ <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getSubResourceId</span><span class="o">(),</span> <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getGroupId</span><span class="o">(),</span> <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getClientId</span><span class="o">(),</span>
+ <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getLoanId</span><span class="o">(),</span> <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getSavingsId</span><span class="o">(),</span> <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getTransactionId</span><span class="o">(),</span>
+ <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getResourceGetUrl</span><span class="o">(),</span> <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getProductId</span><span class="o">(),</span> <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getCreditBureauId</span><span class="o">(),</span>
+ <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getOrganisationCreditBureauId</span><span class="o">(),</span> <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getJobName</span><span class="o">(),</span>
+ <span class="n">commandSourceInput</span><span class="o">.</span><span class="na">getLoanExternalId</span><span class="o">());</span>
+
+ <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">processAndLogCommandService</span><span class="o">.</span><span class="na">executeCommand</span><span class="o">(</span><span class="n">wrapper</span><span class="o">,</span> <span class="n">command</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span>
+ <span class="o">}</span>
+
+ <span class="nd">@Transactional</span>
+ <span class="nd">@Override</span>
+ <span class="kd">public</span> <span class="nc">Long</span> <span class="nf">deleteEntry</span><span class="o">(</span><span class="kd">final</span> <span class="nc">Long</span> <span class="n">makerCheckerId</span><span class="o">)</span> <span class="o">{</span>
+
+ <span class="n">validateMakerCheckerTransaction</span><span class="o">(</span><span class="n">makerCheckerId</span><span class="o">);</span>
+ <span class="n">validateIsUpdateAllowed</span><span class="o">();</span>
+
+ <span class="k">this</span><span class="o">.</span><span class="na">commandSourceRepository</span><span class="o">.</span><span class="na">deleteById</span><span class="o">(</span><span class="n">makerCheckerId</span><span class="o">);</span>
+
+ <span class="k">return</span> <span class="n">makerCheckerId</span><span class="o">;</span>
+ <span class="o">}</span>
+
+ <span class="kd">private</span> <span class="nc">CommandSource</span> <span class="nf">validateMakerCheckerTransaction</span><span class="o">(</span><span class="kd">final</span> <span class="nc">Long</span> <span class="n">makerCheckerId</span><span class="o">)</span> <span class="o">{</span>
+ <span class="kd">final</span> <span class="nc">CommandSource</span> <span class="n">commandSource</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">commandSourceRepository</span><span class="o">.</span><span class="na">findById</span><span class="o">(</span><span class="n">makerCheckerId</span><span class="o">)</span>
+ <span class="o">.</span><span class="na">orElseThrow</span><span class="o">(()</span> <span class="o">-></span> <span class="k">new</span> <span class="nc">CommandNotFoundException</span><span class="o">(</span><span class="n">makerCheckerId</span><span class="o">));</span>
+ <span class="k">if</span> <span class="o">(!</span><span class="n">commandSource</span><span class="o">.</span><span class="na">isAwaitingApproval</span><span class="o">())</span> <span class="o">{</span>
+ <span class="k">throw</span> <span class="k">new</span> <span class="nf">CommandNotAwaitingApprovalException</span><span class="o">(</span><span class="n">makerCheckerId</span><span class="o">);</span>
+ <span class="o">}</span>
+ <span class="nc">AppUser</span> <span class="n">appUser</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">context</span><span class="o">.</span><span class="na">authenticatedUser</span><span class="o">();</span>
+ <span class="nc">String</span> <span class="n">permissionCode</span> <span class="o">=</span> <span class="n">commandSource</span><span class="o">.</span><span class="na">getPermissionCode</span><span class="o">();</span>
+ <span class="n">appUser</span><span class="o">.</span><span class="na">validateHasCheckerPermissionTo</span><span class="o">(</span><span class="n">permissionCode</span><span class="o">);</span>
+ <span class="k">if</span> <span class="o">(!</span><span class="n">configurationService</span><span class="o">.</span><span class="na">isSameMakerCheckerEnabled</span><span class="o">()</span> <span class="o">&&</span> <span class="o">!</span><span class="n">appUser</span><span class="o">.</span><span class="na">isCheckerSuperUser</span><span class="o">())</span> <span class="o">{</span></code></pre>
+</div>
+</div>
+<div class="listingblock">
+<div class="title">Check user has permission for this action. if ok, a) parse the json request body, b) create a JsonCommand object to wrap the command details, c) use CommandProcessingService to handle command</div>
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"> <span class="kd">private</span> <span class="kd">final</span> <span class="nc">RetryConfigurationAssembler</span> <span class="n">retryConfigurationAssembler</span><span class="o">;</span>
+
+ <span class="kd">private</span> <span class="kd">final</span> <span class="nc">FineractRequestContextHolder</span> <span class="n">fineractRequestContextHolder</span><span class="o">;</span>
+ <span class="kd">private</span> <span class="kd">final</span> <span class="nc">Gson</span> <span class="n">gson</span> <span class="o">=</span> <span class="nc">GoogleGsonSerializerHelper</span><span class="o">.</span><span class="na">createSimpleGson</span><span class="o">();</span>
+
+ <span class="kd">private</span> <span class="nc">CommandProcessingResult</span> <span class="nf">executeWithRetry</span><span class="o">(</span><span class="nc">CommandExecutionContext</span> <span class="n">executionContext</span><span class="o">)</span> <span class="o">{</span>
+ <span class="k">try</span> <span class="o">{</span>
+ <span class="k">if</span> <span class="o">(!</span><span class="nc">BatchRequestContextHolder</span><span class="o">.</span><span class="na">isEnclosingTransaction</span><span class="o">())</span> <span class="o">{</span>
+ <span class="k">return</span> <span class="n">retryConfigurationAssembler</span><span class="o">.</span><span class="na">getRetryConfigurationForExecuteCommand</span><span class="o">()</span>
+ <span class="o">.</span><span class="na">executeSupplier</span><span class="o">(()</span> <span class="o">-></span> <span class="n">executeCommandAttempt</span><span class="o">(</span><span class="n">executionContext</span><span class="o">));</span>
+ <span class="o">}</span>
+ <span class="k">return</span> <span class="nf">executeCommandAttempt</span><span class="o">(</span><span class="n">executionContext</span><span class="o">);</span>
+ <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">RollbackTransactionNotApprovedException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
+ <span class="k">throw</span> <span class="n">e</span><span class="o">;</span>
+ <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">RuntimeException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
+ <span class="nc">RuntimeException</span> <span class="n">mappable</span> <span class="o">=</span> <span class="nc">ErrorHandler</span><span class="o">.</span><span class="na">getMappable</span><span class="o">(</span><span class="n">e</span><span class="o">);</span>
+ <span class="n">persistFinalErrorResult</span><span class="o">(</span><span class="n">executionContext</span><span class="o">,</span> <span class="n">mappable</span><span class="o">);</span>
+ <span class="k">throw</span> <span class="n">mappable</span><span class="o">;</span>
+ <span class="o">}</span>
+ <span class="o">}</span>
+
+ <span class="nd">@Override</span>
+ <span class="kd">public</span> <span class="nc">CommandProcessingResult</span> <span class="nf">executeCommand</span><span class="o">(</span><span class="kd">final</span> <span class="nc">CommandWrapper</span> <span class="n">wrapper</span><span class="o">,</span> <span class="kd">final</span> <span class="nc">JsonCommand</span> <span class="n">command</span><span class="o">,</span>
+ <span class="kd">final</span> <span class="kt">boolean</span> <span class="n">isApprovedByChecker</span><span class="o">)</span> <span class="o">{</span>
+ <span class="k">return</span> <span class="nf">executeWithRetry</span><span class="o">(</span><span class="k">new</span> <span class="nc">CommandExecutionContext</span><span class="o">(</span><span class="n">wrapper</span><span class="o">,</span> <span class="n">command</span><span class="o">,</span> <span class="n">isApprovedByChecker</span><span class="o">));</span>
+ <span class="o">}</span>
+
+ <span class="kd">private</span> <span class="nc">CommandProcessingResult</span> <span class="nf">executeCommandAttempt</span><span class="o">(</span><span class="nc">CommandExecutionContext</span> <span class="n">executionContext</span><span class="o">)</span> <span class="o">{</span>
+ <span class="c1">// Do not store the idempotency key because of the exception handling</span>
+ <span class="n">setIdempotencyKeyStoreFlag</span><span class="o">(</span><span class="kc">false</span><span class="o">);</span>
+
+ <span class="nc">CommandWrapper</span> <span class="n">wrapper</span> <span class="o">=</span> <span class="n">executionContext</span><span class="o">.</span><span class="na">wrapper</span><span class="o">();</span>
+ <span class="nc">JsonCommand</span> <span class="n">command</span> <span class="o">=</span> <span class="n">executionContext</span><span class="o">.</span><span class="na">command</span><span class="o">();</span>
+ <span class="nc">Long</span> <span class="n">commandId</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Long</span><span class="o">)</span> <span class="n">fineractRequestContextHolder</span><span class="o">.</span><span class="na">getAttribute</span><span class="o">(</span><span class="no">COMMAND_SOURCE_ID</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
+ <span class="kt">boolean</span> <span class="n">isRetry</span> <span class="o">=</span> <span class="n">commandId</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">;</span>
+ <span class="kt">boolean</span> <span class="n">isEnclosingTransaction</span> <span class="o">=</span> <span class="nc">BatchRequestContextHolder</span><span class="o">.</span><span class="na">isEnclosingTransaction</span><span class="o">();</span>
+ <span class="n">executionContext</span><span class="o">.</span><span class="na">setEnclosingTransaction</span><span class="o">(</span><span class="n">isEnclosingTransaction</span><span class="o">);</span>
+
+ <span class="nc">CommandSource</span> <span class="n">commandSource</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
+ <span class="nc">String</span> <span class="n">idempotencyKey</span><span class="o">;</span>
+ <span class="k">if</span> <span class="o">(</span><span class="n">isRetry</span><span class="o">)</span> <span class="o">{</span>
+ <span class="n">commandSource</span> <span class="o">=</span> <span class="n">commandSourceService</span><span class="o">.</span><span class="na">getCommandSource</span><span class="o">(</span><span class="n">commandId</span><span class="o">);</span>
+ <span class="n">idempotencyKey</span> <span class="o">=</span> <span class="n">commandSource</span><span class="o">.</span><span class="na">getIdempotencyKey</span><span class="o">();</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">((</span><span class="n">commandId</span> <span class="o">=</span> <span class="n">command</span><span class="o">.</span><span class="na">commandId</span><span class="o">())</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// action on the command itself</span>
+ <span class="n">commandSource</span> <span class="o">=</span> <span class="n">commandSourceService</span><span class="o">.</span><span class="na">getCommandSource</span><span class="o">(</span><span class="n">commandId</span><span class="o">);</span>
+ <span class="n">idempotencyKey</span> <span class="o">=</span> <span class="n">commandSource</span><span class="o">.</span><span class="na">getIdempotencyKey</span><span class="o">();</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
+ <span class="n">idempotencyKey</span> <span class="o">=</span> <span class="n">idempotencyKeyResolver</span><span class="o">.</span><span class="na">resolve</span><span class="o">(</span><span class="n">wrapper</span><span class="o">);</span>
+ <span class="o">}</span>
+ <span class="n">exceptionWhenTheRequestAlreadyProcessed</span><span class="o">(</span><span class="n">wrapper</span><span class="o">,</span> <span class="n">idempotencyKey</span><span class="o">,</span> <span class="n">isRetry</span><span class="o">);</span>
+
+ <span class="nc">AppUser</span> <span class="n">user</span> <span class="o">=</span> <span class="n">context</span><span class="o">.</span><span class="na">authenticatedUser</span><span class="o">(</span><span class="n">wrapper</span><span class="o">);</span>
+ <span class="k">if</span> <span class="o">(</span><span class="n">commandSource</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
+ <span class="k">if</span> <span class="o">(</span><span class="n">isEnclosingTransaction</span><span class="o">)</span> <span class="o">{</span>
+ <span class="n">commandSource</span> <span class="o">=</span> <span class="n">commandSourceService</span><span class="o">.</span><span class="na">getInitialCommandSource</span><span class="o">(</span><span class="n">wrapper</span><span class="o">,</span> <span class="n">command</span><span class="o">,</span> <span class="n">user</span><span class="o">,</span> <span class="n">idempotencyKey</span><span class="o">);</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
+ <span class="n">commandSource</span> <span class="o">=</span> <span class="n">commandSourceService</span><span class="o">.</span><span class="na">saveInitial</span><span class="o">(</span><span class="n">wrapper</span><span class="o">,</span> <span class="n">command</span><span class="o">,</span> <span class="n">user</span><span class="o">,</span> <span class="n">idempotencyKey</span><span class="o">);</span>
+ <span class="n">commandId</span> <span class="o">=</span> <span class="n">commandSource</span><span class="o">.</span><span class="na">getId</span><span class="o">();</span>
+ <span class="o">}</span>
+ <span class="o">}</span>
+ <span class="n">executionContext</span><span class="o">.</span><span class="na">setCommandSource</span><span class="o">(</span><span class="n">commandSource</span><span class="o">);</span>
+ <span class="k">if</span> <span class="o">(</span><span class="n">commandId</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
+ <span class="n">storeCommandIdInContext</span><span class="o">(</span><span class="n">commandSource</span><span class="o">);</span> <span class="c1">// Store command id as a request attribute</span>
+ <span class="o">}</span>
+
+ <span class="n">setIdempotencyKeyStoreFlag</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span></code></pre>
+</div>
+</div>
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-note" title="Note"></i>
+</td>
+<td class="content">
+if a RollbackTransactionAsCommandIsNotApprovedByCheckerException occurs at this point. The original transaction will of been aborted and we only log an entry for the command in the audit table setting its status as 'Pending'.
+</td>
+</tr>
+</table>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Check that if maker-checker configuration enabled for this action. If yes and this is not a 'checker' approving the command - rollback at the end. We rollback at the end in order to test if the command will pass 'domain validation' which requires commit to database for full check.</p>
+</li>
+<li>
+<p>findCommandHandler - Find the correct Handler to process this command.</p>
+</li>
+<li>
+<p>Process command using handler (In transactional scope).</p>
+</li>
+<li>
+<p>CommandSource object created/updated with all details for logging to 'm_portfolio_command_source' table.</p>
+</li>
+<li>
+<p>In update scenario, we check to see if there where really any changes/updates. If so only JSON for changes is stored in audit log.</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_persistence"><a class="link" href="#_persistence">Persistence</a></h3>
+<div class="paragraph">
+<p>TBD</p>
+</div>
+<div class="sect3">
+<h4 id="_database_support"><a class="link" href="#_database_support">Database support</a></h4>
+<div class="paragraph">
+<p>Fineract officially supports PostgreSQL.</p>
+</div>
+<div class="paragraph">
+<p><a href="https://cwiki.apache.org/confluence/display/FINERACT/FSIP-9%3A+Standardize+on+PostgreSQL">MySQL and MariaDB support exists and is deprecated — it will eventually be removed entirely</a>.</p>
+</div>
+<div class="paragraph">
+<p>The platform differentiates between these database types in certain cases when there’s a need to use some database specific tooling. To do so, the platform examines the JDBC driver used for running the platform and tries to determine which database is being used.</p>
+</div>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 50%;">
+<col style="width: 50%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>JDBC driver class name</strong></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Resolved database type</strong></p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>org.mariadb.jdbc.Driver</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">MySQL</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>com.mysql.jdbc.Driver</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">MySQL</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>org.postgresql.Driver</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">PostgreSQL</p></td>
+</tr>
+</tbody>
+</table>
+<div class="paragraph">
+<p>The actual code can be found in the <code>DatabaseTypeResolver</code> class.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_tenant_database_security"><a class="link" href="#_tenant_database_security">Tenant database security</a></h4>
+<div class="paragraph">
+<p>The tenant database schema password is stored in the <code>tenant_server_connections</code> table in the tenant database. The password and the read only schema password are encrypted using the <code>fineract.tenant.master-password</code> property. By default, the database property will be encrypted in the first start from a plain text.</p>
+</div>
+<div class="paragraph">
+<p>When you want to generate a new encrypted password, you can use the <code>org.apache.fineract.infrastructure.core.service.database.DatabasePasswordEncryptor</code> class.</p>
+</div>
+<div class="sect4">
+<h5 id="_database_password_encryption_usage"><a class="link" href="#_database_password_encryption_usage">Database password encryption usage</a></h5>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight nowrap"><code data-lang="bash">java <span class="nt">-cp</span> fineract-provider.jar <span class="se">\</span>
+ <span class="nt">-Dloader</span>.main<span class="o">=</span>org.apache.fineract.infrastructure.core.service.database.DatabasePasswordEncryptor <span class="se">\</span>
+ org.springframework.boot.loader.PropertiesLauncher <span class="se">\</span>
+ <masterPassword> <span class="se">\</span>
+ <plainPassword></code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>For example:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight nowrap"><code data-lang="text">java -cp fineract-provider-0.0.0-48f7e315.jar \
+ -Dloader.main=org.apache.fineract.infrastructure.core.service.database.DatabasePasswordEncryptor \
+ org.springframework.boot.loader.PropertiesLauncher \
+ fineract-master-password \
+ fineract-tenant-password
+The encrypted password: VLwGl7vOP/q275ZTku+PNGWnGwW4mzzNHSNaO9Pr67WT5/NZMpBr9tGYYiYsqwL1eRew2jl7O3/N1EFbLlXhSA==</code></pre>
+</div>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_data_access_layer"><a class="link" href="#_data_access_layer">Data-access layer</a></h4>
+<div class="paragraph">
+<p>The data-access layer of Fineract is implemented by using JPA (Java Persistence API) with the EclipseLink provider.<br>
+Despite the fact that JPA is used quite extensively in the system, there are cases where the performance is a key element for an operation therefore you can easily find native SQLs as well.</p>
+</div>
+<div class="paragraph">
+<p>The data-access layer of Fineract is compatible with different databases. Since a lot of the native queries are using specific database functions, a wrapper class - <code>DatabaseSpecificSQLGenerator</code> - has been introduced to handle these database specifics. Whenever there’s a need to rely on new database level functions, make sure to extend this class and implement the specific functions provided by the database.</p>
+</div>
+<div class="paragraph">
+<p>Fineract has been developed for 10+ years by the community and unfortunately there are places where entity relationships are configured with <code>EAGER</code> fetching strategy. This must not confuse anybody. The long-term goal is to use the <code>LAZY</code> fetching strategy for every single relationship. If you’re about to introduce a new one, make sure to use <code>LAZY</code> as a fetching strategy, otherwise your PR will be rejected.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_database_schema_migration"><a class="link" href="#_database_schema_migration">Database schema migration</a></h4>
+<div class="paragraph">
+<p>As for every system, the database structure will and need to evolve over time. Fineract is no different. Originally for Fineract, Flyway was used until Fineract 1.6.x.</p>
+</div>
+<div class="paragraph">
+<p>After 1.6.x, PostgreSQL support was added to the platform hence there was a need to make the data-access layer and the schema migration as database independent as possible. Because of that, from Fineract 1.7.0, Flyway is not used anymore but Liquibase is.</p>
+</div>
+<div class="paragraph">
+<p>Some of the changesets in the Liquibase changelogs have database specifics into it but they only run for the relevant databases. This is controller by Liquibase contexts.</p>
+</div>
+<div class="paragraph">
+<p>The currently available Liquibase contexts are:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><code>mysql</code> - only set when the database is a MySQL compatible database (e.g. MariaDB)</p>
+</li>
+<li>
+<p><code>postgresql</code> - only set when the database is a PostgreSQL database</p>
+</li>
+<li>
+<p>configured Spring active profiles</p>
+</li>
+<li>
+<p><code>tenant_store_db</code> - only set when the database migration runs the Tenant Store upgrade</p>
+</li>
+<li>
+<p><code>tenant_db</code> - only set when the database migration runs the Tenant upgrade</p>
+</li>
+<li>
+<p><code>initial_switch</code> - this is a technical context and should <strong>NOT</strong> be used</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>The switch from Flyway (1.6.x) to Liquibase (1.7.x) was planned to be as smooth as possible so there’s no need for manual work hence the behavior is described as following:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>If the database is empty, Liquibase will create the database schema from scratch</p>
+</li>
+<li>
+<p>If the database contains the latest Fineract 1.6.x database structure which was previously migrated with Flyway. Liquibase will seamlessly upgrade it to the latest version. Note: the Flyway related 2 database tables are left as they are and are not deleted.</p>
+</li>
+<li>
+<p>If the database contains an earlier version of the database structure than Fineract 1.6.x. Liquibase will <strong>NOT</strong> do anything and <strong>will fail the application during startup</strong>. The proper approach in this case is to first upgrade your application version to the latest Fineract 1.6.x so that the latest Flyway changes are executed and then upgrade to the newer Fineract version where Liquibase will seamlessly take over the database upgrades.</p>
+</li>
+</ul>
+</div>
+<div class="sect4">
+<h5 id="_troubleshooting"><a class="link" href="#_troubleshooting">Troubleshooting</a></h5>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>During upgrade from Fineract 1.5.0 to 1.6.0, Liquibase fails</p>
+<div class="paragraph">
+<p>After dropping the flyway migrations table (schema_version), Liquibase runs its<br>
+own migrations which fails (in recreating tables which already exist) because<br>
+we are aiming to re-use DB with existing data from Fineract 1.5.0.</p>
+</div>
+<div class="paragraph">
+<p>Solution: The latest release version (1.6.0) doesn’t have Liquibase at all, it<br>
+still runs Flyway migrations. Only the develop branch (later to be 1.7.0) got<br>
+switched to Liquibase. Do not pull the develop before upgrading your instance.</p>
+</div>
+<div class="paragraph">
+<p>Make sure first you upgrade your instance (aka database schema with Fineract 1.6.0).<br>
+Then upgrade with the current develop branch. Check if some migration scripts<br>
+did not run which led to some operations failing due to slight differences in<br>
+schema. Try with running the missing migrations manually.</p>
+</div>
+<div class="paragraph">
+<p>Note: develop is considered unstable until released.</p>
+</div>
+</li>
+<li>
+<p>Upgrading database from MySQL 5.7 as advised to Maria DB 10.6, fails. If we<br>
+use data from version 18.03.01 it fails to migrate the data. If we use databases<br>
+running on 1.5.0 release it completes the startup but the system login fails.</p>
+<div class="paragraph">
+<p>Solution: A database upgrade is separate thing to take care of.</p>
+</div>
+</li>
+<li>
+<p>We are getting <code>ScehmaUpgradeNeededException: Make sure to upgrade to Fineract<br>
+1.6 first and then to a newer version</code> error while upgrading to <code>tag 1.6</code>.</p>
+<div class="paragraph">
+<p>1.6 version shouldn’t include Liquibase. It will only be released after 1.6.<br>
+Make sure Liquibase is dropping <code>schema_version</code> table, as there is no Flyway<br>
+it is not required. Drop Flyway and use Liquibase for both migrations and<br>
+database independence. In case, if you still get errors, you can use git SHA<br>
+<code>746c589a6e809b33d68c0596930fcaa7338d5270</code> and Flyway migration will be done to<br>
+the latest.</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="no">TENANT_LATEST_FLYWAY_VERSION</span> <span class="o">=</span> <span class="mi">392</span><span class="o">;</span>
+<span class="no">TENANT_LATEST_FLYWAY_SCRIPT_NAME</span> <span class="o">=</span>
+<span class="s">"V392__interest_recovery_conf_for_rescedule.sql"</span><span class="o">;</span>
+<span class="no">TENANT_LATEST_FLYWAY_SCRIPT_CHECKSUM</span> <span class="o">=</span> <span class="mi">1102395052</span><span class="o">;</span></code></pre>
+</div>
+</div>
+</li>
+</ol>
+</div>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_idempotency"><a class="link" href="#_idempotency">Idempotency</a></h3>
+<div class="paragraph">
+<p>Idempotency is the way to make sure your specific action is only executed once.<br>
+For example, if you have a button that is supposed to send a repayment, you don’t want to repayment twice if the user clicks the button twice. Idempotency is a way to make sure that the action is only executed once.</p>
+</div>
+<div class="paragraph">
+<p>There are two ways to use idempotency:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>HTTP Request with idempotency key header</p>
+</li>
+<li>
+<p>Batch request with batch item header</p>
+</li>
+</ul>
+</div>
+<div class="sect3">
+<h4 id="_how_it_works"><a class="link" href="#_how_it_works">How it works</a></h4>
+<div class="paragraph">
+<p>The <code>idempotency key</code> with <code>action name</code> and <code>entity name</code> is unique, and identify a specific command in the system.<br>
+If no idempotency key is assigned to the request, the system will generate one for you.</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>User send a request</p>
+</li>
+<li>
+<p>The system checks there are already executed commands with the same <code>idempotency key</code> and <code>action name</code> and <code>entity name</code></p>
+</li>
+<li>
+<p>The action based on the result of the check</p>
+<div class="ulist">
+<ul>
+<li>
+<p>If the request is completed the system return with the already generated result</p>
+</li>
+<li>
+<p>If not completed, return HTTP 409 response</p>
+</li>
+<li>
+<p>If the request is not completed, we process the requests and store the results in the database</p>
+</li>
+</ul>
+</div>
+</li>
+</ol>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_idempotency_in_http_requests"><a class="link" href="#_idempotency_in_http_requests">Idempotency in HTTP requests</a></h4>
+<div class="paragraph">
+<p>To achieve idempotency in HTTP requests, you can use the HTTP header from <code>fineract.idempotency-key-header-name</code> configuration variables (default <code>Idemptency-Key</code>). This header is a unique identifier for the request. If you send the same request twice, the second request will be ignored and the response from the first request will be returned.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_idempotency_in_batch_requests"><a class="link" href="#_idempotency_in_batch_requests">Idempotency in Batch requests</a></h4>
+<div class="paragraph">
+<p>In batch requests, you can set the idempotency key for every batch item, in the batch item <code>header</code> fields. The header key is from <code>fineract.idempotency-key-header-name</code> configuration variables (default <code>Idemptency-Key</code>).</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_result_of_the_request"><a class="link" href="#_result_of_the_request">Result of the request</a></h4>
+<div class="ulist">
+<ul>
+<li>
+<p>When the request is already executed and completed, the system will return a <code>x-served-from-cache</code> header with the value <code>true</code> in the response and return the original request body.</p>
+</li>
+<li>
+<p>When the request is already executed but still not completed, the system will return to HTTP 409 error code</p>
+</li>
+<li>
+<p>When the request is not executed, the system runs it normally and stores the result in the date</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_validation"><a class="link" href="#_validation">Validation</a></h3>
+<div class="sect3">
+<h4 id="_programmatic"><a class="link" href="#_programmatic">Programmatic</a></h4>
+<div class="paragraph">
+<p>Use the <a href="https://github.com/apache/fineract/search?q=%22class+DataValidatorBuilder%22">DataValidatorBuilder</a>, e.g. like so:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="k">new</span> <span class="nc">DataValidatorBuilder</span><span class="o">().</span><span class="na">resource</span><span class="o">(</span><span class="s">"fileUpload"</span><span class="o">)</span>
+ <span class="o">.</span><span class="na">reset</span><span class="o">().</span><span class="na">parameter</span><span class="o">(</span><span class="s">"Content-Length"</span><span class="o">).</span><span class="na">value</span><span class="o">(</span><span class="n">contentLength</span><span class="o">).</span><span class="na">notBlank</span><span class="o">().</span><span class="na">integerGreaterThanNumber</span><span class="o">(</span><span class="mi">0</span><span class="o">)</span>
+ <span class="o">.</span><span class="na">reset</span><span class="o">().</span><span class="na">parameter</span><span class="o">(</span><span class="s">"FormDataContentDisposition"</span><span class="o">).</span><span class="na">value</span><span class="o">(</span><span class="n">fileDetails</span><span class="o">).</span><span class="na">notNull</span><span class="o">()</span>
+ <span class="o">.</span><span class="na">throwValidationErrors</span><span class="o">();</span></code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Such code is often encapsulated in <a href="https://github.com/apache/fineract/search?q=Validator"><code>*Validator</code></a> classes (if more than a few lines, and/or reused from several places; avoid copy/paste), like so:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">YourThingValidator</span> <span class="o">{</span>
+
+ <span class="kd">public</span> <span class="kt">void</span> <span class="nf">validate</span><span class="o">(</span><span class="nc">YourThing</span> <span class="n">thing</span><span class="o">)</span> <span class="o">{</span>
+ <span class="k">new</span> <span class="nf">DataValidatorBuilder</span><span class="o">().</span><span class="na">resource</span><span class="o">(</span><span class="s">"yourThing"</span><span class="o">)</span>
+ <span class="o">...</span>
+ <span class="o">.</span><span class="na">throwValidationErrors</span><span class="o">();</span>
+ <span class="o">}</span>
+<span class="o">}</span></code></pre>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_declarative"><a class="link" href="#_declarative">Declarative</a></h4>
+<div class="paragraph">
+<p><a href="https://issues.apache.org/jira/browse/FINERACT-1229">FINERACT-1229</a> is an open issue about adopting Bean Validation for <em>declarative</em> instead of <em>programmatic</em> (as above) validation. Contributions welcome!</p>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_batch_execution_and_jobs"><a class="link" href="#_batch_execution_and_jobs">Batch execution and jobs</a></h3>
+<div class="paragraph">
+<p>Just like any financial system, Fineract also has batch jobs to achieve some processing on the data that’s stored in the system.</p>
+</div>
+<div class="paragraph">
+<p>The batch jobs in Fineract are implemented using <a href="https://docs.spring.io/spring-batch/docs/current/reference/html/">Spring Batch</a>. In addition to the Spring Batch ecosystem, the automatic scheduling is done by the <a href="http://www.quartz-scheduler.org/">Quartz Scheduler</a> but it’s also possible to trigger batch jobs via regular APIs.</p>
+</div>
+<div class="sect3">
+<h4 id="_glossary"><a class="link" href="#_glossary">Glossary</a></h4>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 50%;">
+<col style="width: 50%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Job</strong></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A Job is an object that encapsulates an entire batch process.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Step</strong></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A Step is an object that encapsulates an independent phase of a Job.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Chunk oriented processing</strong></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Chunk oriented processing refers to reading the data one at a time and creating 'chunks' that are written out within a transaction boundary.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Partitioning</strong></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Partitioning refers to the high-level idea of dividing your data into so called partitions and distributing the individual partitions among Workers. The splitting of data and pushing work to Workers is done by a Manager.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Remote partitioning</strong></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Remote partitioning is a specialized partitioning concept. It refers to the idea of distributing the partitions among multiple JVMs mainly by using a messaging middleware.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Manager node</strong></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">The Manager node is one of the objects taking a huge part when using partitioning. The Manager node is responsible for dividing the dataset into partitions and keeping track of all the divided partitions' Worker execution. When all Workers nodes are done with their partitions, the Manager will mark the corresponding Job as completed.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Worker node</strong></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A Worker node is the other important party in the context of partitioning. The Worker node is the one executing the work needed for a single partition.</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect3">
+<h4 id="_batch_jobs_in_fineract"><a class="link" href="#_batch_jobs_in_fineract">Batch jobs in Fineract</a></h4>
+<div class="sect4">
+<h5 id="_types_of_jobs"><a class="link" href="#_types_of_jobs">Types of jobs</a></h5>
+<div class="paragraph">
+<p>The jobs in Fineract can be divided into 2 categories:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Normal batch jobs</p>
+</li>
+<li>
+<p>Partitionable batch jobs</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>Most of the jobs are normal batch jobs with limited scalability because Fineract is still passing through the evolution on making most of them capable to process a high-volume of data.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_list_of_jobs"><a class="link" href="#_list_of_jobs">List of jobs</a></h5>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 25%;">
+<col style="width: 25%;">
+<col style="width: 25%;">
+<col style="width: 25%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Job name</strong></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Active by default</strong></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Partitionable</strong></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Description</strong></p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">LOAN_CLOSE_OF_BUSINESS</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">No</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Yes</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">TBD</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_batch_job_execution"><a class="link" href="#_batch_job_execution">Batch job execution</a></h4>
+<div class="sect4">
+<h5 id="_state_management"><a class="link" href="#_state_management">State management</a></h5>
+<div class="paragraph">
+<p>State management for the batch jobs is done by the Spring Batch provided state management. The data model consists of the following database structure:</p>
+</div>
+<div class="imageblock">
+<div class="content">
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAz8AAAKnCAIAAAAndWSOAAAgAElEQVR4nOzdd1wU1/ow8Gd2l13YhgJLLxYQEFREFFFRUVCjgl2D2I1R9MYWNTfBiJobc82rib1Ek2hu/JFEo2gMImuLBaJo7KAo0tSlw1J22TIz7x+Tu9m7jcXYfb5/+Nk5c+Y5BZCHmTMzBE3TgBBCCCGEXhGsF90BhBBCCCHUApi9IYQQQgi9SjB7QwghhBB6lWD2hhBCCCH0KsHsDSGEEELoVYLZG0IIIYTQqwSzN4QQQgihVwlmbwghhBBCrxLM3hBCCCGEXiUcK+spFIrs7OxqeT0BNIdFPNM+IfTS0lI0TYNDK/vu3cP4fP6L7g5CCKE3EWHNm7IuXrxUWl6u5ggV3FYali1JsAAwgUNvIJpNUzZUE18j56rrXF1cwsN7vOguIYQQeuM0n72dOHmqQamq [...]
+</div>
+</div>
+<div class="paragraph">
+<p>The corresponding database migration scripts are shipped with the Spring Batch core module under the <code>org.springframework.batch.core</code> package. They are only available as native scripts and are named as <code>schema-<strong>.sql</code> where <code></strong></code> is the short name of the database platform. For MySQL it’s called <code>schema-mysql.sql</code> and for PostgreSQL it’s called <code>schema-postgresql.sql</code>.<br>
+When Fineract is started, the database dependent schema SQL script will be picked up according to the datasource configurations.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_chunk_oriented_processing"><a class="link" href="#_chunk_oriented_processing">Chunk oriented processing</a></h5>
+<div class="paragraph">
+<p>Chunking data has not been easier. Spring Batch does a really good job at providing this capability.</p>
+</div>
+<div class="paragraph">
+<p>In order to save resources when starting/committing/rollbacking transactions for every single processed item, chunking shall be used. That way, it’s possible to mark the transaction boundaries for a single processed chunk instead of a single item processing. The image below describes the flow with a very simplistic example.</p>
+</div>
+<div class="imageblock">
+<div class="content">
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABrcAAAOqCAYAAAA2ew4EAACAAElEQVR42uzdX8hu130ndl2cO9/G181tIBKyBYnlYZzjm2NLoUMJpUIxpgyeG5Hgi4a5OaC0Q5hC4uIxCaXBnZSGjlWBg2HMUHeUQijqEDqUym7IlRJSCpZ9U9m0RWaQ4PT8Xr3reJ31/tbea+9nP++7n+f5fGAjnXOeZz9rr/3sP8/67rXWM88AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA [...]
+</div>
+</div>
+<div class="paragraph">
+<p>In addition to not opening a lot of transactions, the processing could also benefit from JDBC batching. The last step - writing the result into the database - collects all the processed items and then writes it to the database; both for MySQL and PostgreSQL (the databases supported by Fineract) are capable of grouping multiple DML (INSERT/UPDATE/DELETE) statements and sending them in one round-trip, optimizing the data being sent over the network and granting the possibility to the un [...]
+</div>
+</div>
+<div class="sect4">
+<h5 id="_remote_partitioning"><a class="link" href="#_remote_partitioning">Remote partitioning</a></h5>
+<div class="paragraph">
+<p>Spring Batch provides a really nice way to do remote partitioning. The 2 type of objects in this setup is a manager node - who splits and distributes the work - and a number of worker nodes - who picks up the work.</p>
+</div>
+<div class="paragraph">
+<p>In remote partitioning, the worker instances are receiving the work via a messaging system as soon as the manager splits up the work into smaller pieces.</p>
+</div>
+<div class="paragraph">
+<p>Remote partitioning could be done 2 ways in terms of keeping the job state up-to-date. The main difference between the two is how the manager is notified about partition completions.</p>
+</div>
+<div class="paragraph">
+<p>One way is that they share the same database. When the worker does something to a partition - for example picks it up for processing - it updates the state of that partition in the database. In the meantime, the manager regularly polls the database until all partitions are processed. This is visualized in the below diagram.</p>
+</div>
+<div class="imageblock">
+<div class="content">
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABeUAAARnCAYAAAB6qQ2UAACAAElEQVR42uzdP4wd150veAY3UyrGT+EacBOUCVjd0jPVTCh2K3iAXjCgvQ4GHuyDYD3tzvot1hCmNTNw5MQgHDmZYIK5iQAFykwFjgwDjpoQlNlwRgvYXduRHFhAL3/ULb7i4TlVp+r+q67+fIADUeS9davOrb/fOvdX164BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA [...]
+</div>
+</div>
+<div class="paragraph">
+<p>An alternative approach to this - when the database is not intended to be shared between manager and workers - is to use a messaging system (could be the same as for distributing the work) and the workers could send back a message to the manager instance, therefore notifying it about failure/completion. Then the manager can simply keep the database state up-to-date.</p>
+</div>
+<div class="paragraph">
+<p>Even though the alternative solution decouples the workers even better, we thought it’s not necessary to add the complexity of handling reply message channel to the manager.</p>
+</div>
+<div class="paragraph">
+<p>Also, please note that the partitioned job execution is multitenant meaning that the workers will receive which tenant it should do the processing for.</p>
+</div>
+<div class="sect5">
+<h6 id="_supported_message_channels"><a class="link" href="#_supported_message_channels">Supported message channels</a></h6>
+<div class="paragraph">
+<p>For remote partitioning, the following message channels are supported by Fineract:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Any JMS compatible message channels (ActiveMQ, Amazon MQ, etc)</p>
+</li>
+<li>
+<p>Apache Kafka</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_fault_tolerance_scenarios"><a class="link" href="#_fault_tolerance_scenarios">Fault-tolerance scenarios</a></h6>
+<div class="paragraph">
+<p>There are multiple fault tolerance use-cases that this solution must and will support:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>If the manager fails during partitioning</p>
+</li>
+<li>
+<p>If the manager completes the partitioning and the partition messages are sent to the broker but while the manager is waiting for the workers to finish, the manager fails</p>
+</li>
+<li>
+<p>If the manager runs properly and during a partition processing a worker instance fails</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>In case of scenario 1), the simple solution is to re-trigger the job via API or via the Quartz scheduler.</p>
+</div>
+<div class="paragraph">
+<p>In case of scenario 2), there’s no out-of-the-box solution by Spring Batch. Although there’s a custom mechanism in place that’ll resume the job upon restarting the manager. There are 2 cases in the context of this scenario:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>If all the partitions have been successfully processed by workers</p>
+</li>
+<li>
+<p>If not all the partitions have been processed by the workers</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>In the first case, we’ll simply mark the stuck job as <code>FAILED</code> along with it’s partitioning step and instruct Spring Batch to restart the job. The behavior in this case will be that Spring Batch will spawn a new job execution but will notice that the partitions have all been completed so it’s not going to execute them once more.</p>
+</div>
+<div class="paragraph">
+<p>In the latter case, the same will happen as for the first one but before marking the job execution as <code>FAILED</code>, we’ll wait until all partitions have been completed.</p>
+</div>
+<div class="imageblock">
+<div class="content">
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAjIAAAHwCAIAAAAzZhIsAAAAKXRFWHRjb3B5bGVmdABHZW5lcmF0ZWQgYnkgaHR0cDovL3BsYW50dW1sLmNvbREwORwAAAGtaVRYdHBsYW50dW1sAAEAAAB4nI1SwW4aMRC9W9p/GO0JKhFRUqJqD202QNRQtkEBUlVND4YdWLdmvLJnkfL3HS9U0EtVX2z5vXnvjce3gbXnZm8TFX4ZqrXXe7CGkF9rBOe5colKVOdNF3q9D5AWmvQOPbRlIU2U2UL6EIAr9AgafENkaAc/3Rr0QRur1xY/phGnRAFEeh6Z1oKYsWHjKEClDwhrRIKN29cWGcuLorjE/fs3DD8gXdWlZmwdJIWc2MF9/jCbjNMLMqRP2IaMKpH8N7iofYx5p3lTvRB7TeEU5UR/obP66LGYzybLSwNvdhWLkDzM8Q5twMusX5xE [...]
+</div>
+</div>
+<div class="paragraph">
+<p>In case of scenario 3), another worker instance will take over the partition since it hasn’t been finished.</p>
+</div>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_configurable_batch_jobs"><a class="link" href="#_configurable_batch_jobs">Configurable batch jobs</a></h4>
+<div class="paragraph">
+<p>There’s another type of distinction on the batch jobs. Some of them are configurable in terms of their behavior.</p>
+</div>
+<div class="paragraph">
+<p>The currently supported configurable batch jobs are the following:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>LOAN_CLOSE_OF_BUSINESS</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>The behavior of these batch jobs are configurable. There’s a new terminology we’re introducing called <strong>business steps</strong>.</p>
+</div>
+<div class="sect4">
+<h5 id="_business_steps"><a class="link" href="#_business_steps">Business steps</a></h5>
+<div class="paragraph">
+<p>Business steps are a smaller unit of work than regular Spring Batch Steps and the two are not meant to be mixed up because there’s a large difference between them.</p>
+</div>
+<div class="paragraph">
+<p>A Spring Batch Step’s main purpose is to decompose a bigger work into smaller ones and making sure that these smaller Steps are properly handled within a single database transaction.</p>
+</div>
+<div class="paragraph">
+<p>In case of a business step, it’s a smaller unit of work. Business steps live <strong>within</strong> a Spring Batch Step. Fundamentally, they are simple classes that are implementing an interface with a single method that contains the business logic.</p>
+</div>
+<div class="paragraph">
+<p>Here’s a very simple example:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">MyCustomBusinessStep</span> <span class="kd">implements</span> <span class="nc">BusinessStep</span><span class="o"><</span><span class="nc">Loan</span><span class="o">></span> <span class="o">{</span>
+ <span class="nd">@Override</span>
+ <span class="kd">public</span> <span class="nc">Loan</span> <span class="nf">process</span><span class="o">(</span><span class="nc">Loan</span> <span class="n">loan</span><span class="o">)</span> <span class="o">{</span>
+ <span class="c1">// do something</span>
+ <span class="o">}</span>
+<span class="o">}</span></code></pre>
+</div>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">LoanCOBItemProcessor</span> <span class="kd">implements</span> <span class="nc">ItemProcessor</span><span class="o"><</span><span class="nc">Loan</span><span class="o">,</span> <span class="nc">Loan</span><span class="o">></span> <span class="o">{</span>
+ <span class="nd">@Override</span>
+ <span class="kd">public</span> <span class="nc">Loan</span> <span class="nf">process</span><span class="o">(</span><span class="nc">Loan</span> <span class="n">loan</span><span class="o">)</span> <span class="o">{</span>
+ <span class="nc">List</span><span class="o"><</span><span class="nc">BusinessStep</span><span class="o"><</span><span class="nc">Loan</span><span class="o">>></span> <span class="n">bSteps</span> <span class="o">=</span> <span class="n">getBusinessSteps</span><span class="o">();</span>
+ <span class="nc">Loan</span> <span class="n">result</span> <span class="o">=</span> <span class="n">loan</span><span class="o">;</span>
+ <span class="k">for</span> <span class="o">(</span><span class="nc">BusinessStep</span><span class="o"><</span><span class="nc">Loan</span><span class="o">></span> <span class="n">bStep</span> <span class="o">:</span> <span class="n">bSteps</span><span class="o">)</span> <span class="o">{</span>
+ <span class="n">result</span> <span class="o">=</span> <span class="n">bStep</span><span class="o">.</span><span class="na">process</span><span class="o">(</span><span class="n">result</span><span class="o">);</span>
+ <span class="o">}</span>
+ <span class="k">return</span> <span class="n">result</span><span class="o">;</span>
+ <span class="o">}</span>
+<span class="o">}</span></code></pre>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_business_step_configuration"><a class="link" href="#_business_step_configuration">Business step configuration</a></h5>
+<div class="paragraph">
+<p>The business steps are configurable for certain jobs. The reason for that is because we want to allow the possibility for Fineract users to configure their very own business logic for generic jobs, like the Loan Close Of Business job where we want to do a formal "closing" of the loans at the end of the day.</p>
+</div>
+<div class="paragraph">
+<p>All countries are different with a different set of regulations. However in terms of behavior, there’s no all size fits all for loan closing.</p>
+</div>
+<div class="paragraph">
+<p>For example in the United States of America, you might need the following logic for a day closing:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Close fully repaid loan accounts</p>
+</li>
+<li>
+<p>Apply penalties</p>
+</li>
+<li>
+<p>Invoke IRS API for regulatory purposes</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>While in Germany it should be:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Close fully repaid loan accounts</p>
+</li>
+<li>
+<p>Apply penalties</p>
+</li>
+<li>
+<p>Do some fraud detection on the account using an external service</p>
+</li>
+<li>
+<p>Invoke local tax authority API for regulatory purposes</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>These are just examples, but you get the idea.</p>
+</div>
+<div class="paragraph">
+<p>The business steps are configurable through APIs:</p>
+</div>
+<div class="paragraph">
+<p>Retrieving the configuration for a job:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="text">GET /fineract-provider/api/v1/jobs/{jobName}/steps?tenantIdentifier={tenantId}
+HTTP 200
+
+{
+ "jobName": "LOAN_CLOSE_OF_BUSINESS",
+ "businessSteps": [
+ {
+ "stepName": "APPLY_PENALTY_FOR_OVERDUE_LOANS",
+ "order": 1
+ },
+ {
+ "stepName": "LOAN_TAGGING",
+ "order": 2
+ }
+ ]
+}</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Updating the business step configuration for a job:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="text">PUT /fineract-provider/api/v1/jobs/{jobName}/steps?tenantIdentifier={tenantId}
+
+{
+ "businessSteps": [
+ {
+ "stepName": "LOAN_TAGGING",
+ "order": 1
+ },
+ {
+ "stepName": "APPLY_PENALTY_FOR_OVERDUE_LOANS",
+ "order": 2
+ }
+ ]
+}</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>The business step configuration for jobs are tracked within the database in the <code>m_batch_business_steps</code> table.</p>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_inline_jobs"><a class="link" href="#_inline_jobs">Inline Jobs</a></h4>
+<div class="paragraph">
+<p>Some jobs that work with business entities have a corresponding job that can trigger the job with a list of specified entities.<br>
+When the Inline job gets triggered then the corresponding existing job will run in real time with the given entities as a dataset.</p>
+</div>
+<div class="sect4">
+<h5 id="_list_of_inline_jobs"><a class="link" href="#_list_of_inline_jobs">List of Inline jobs</a></h5>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 50%;">
+<col style="width: 50%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Inline Job name</strong></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Corresponding Job</strong></p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">LOAN_COB</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">LOAN_CLOSE_OF_BUSINESS</p></td>
+</tr>
+</tbody>
+</table>
+<div class="paragraph">
+<p>Triggering the Inline Loan COB Job:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="text">POST /fineract-provider/api/v1/jobs/LOAN_COB/inline?tenantIdentifier={tenantId}
+
+{
+ "loanIds": [
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14
+ ]
+}</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>In this case the Loan COB job will work only with the given loan IDs.</p>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_global_configuration_for_enablingdisabling_jobs"><a class="link" href="#_global_configuration_for_enablingdisabling_jobs">Global Configuration for enabling/disabling jobs</a></h4>
+<div class="paragraph">
+<p>Some jobs can be enabled/disabled with global configuration.<br>
+If the job is disabled with the global configuration then it cannot be scheduled and cannot be triggered via API.</p>
+</div>
+<div class="sect4">
+<h5 id="_list_of_jobs_with_global_configuration"><a class="link" href="#_list_of_jobs_with_global_configuration">List of jobs with global configuration</a></h5>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 25%;">
+<col style="width: 25%;">
+<col style="width: 25%;">
+<col style="width: 25%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Job name</strong></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Application property</strong></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Environment variable</strong></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Default value</strong></p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">LOAN_CLOSE_OF_BUSINESS</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">fineract.job.loan-cob-enabled</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">FINERACT_JOB_LOAN_COB_ENABLED</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_loan_account_locking"><a class="link" href="#_loan_account_locking">Loan account locking</a></h3>
+<div class="paragraph">
+<p>Keeping a consistent state of loan accounts become quite important when we start talking about doing a business day closing each day for loans.</p>
+</div>
+<div class="paragraph">
+<p>There are 2 concepts for loan account locking:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Soft-locking loan accounts</p>
+</li>
+<li>
+<p>Hard-locking loan accounts</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>Soft-locking simply means that when the Loan COB has been kicked off but workers not yet processing the chunk of loan accounts (i.e. the partition is waiting in the queue to be picked up) and during this time a real-time write request (e.g. a repayment/disbursement) comes in through the API, we simply do an "inlined" version of the Loan COB for that loan account. From a practical standpoint this will mean that before doing the actual repayment/disbursement on the loan account on the A [...]
+</div>
+<div class="paragraph">
+<p>Hard-locking means that when a worker picks up the loan account in the chunk, real-time write requests on those loan accounts will be simply rejected with an <code>HTTP 409</code>.</p>
+</div>
+<div class="paragraph">
+<p>The locking is strictly tied to the Loan COB job’s execution but there could be other processes in the future which might want to introduce new type of locks for loans.</p>
+</div>
+<div class="paragraph">
+<p>The loan account locking is solved by maintaining a database table which stores the locked accounts, it’s called <code>m_loan_account_locks</code>.</p>
+</div>
+<div class="paragraph">
+<p>When a loan account is present in the table above, it simply means there’s a lock applied to it and whether it’s a soft or hard lock can be determined by the <code>lock_owner</code> column.</p>
+</div>
+<div class="paragraph">
+<p>And when a loan account is locked, loan related write API calls will be either rejected or will trigger an inline Loan COB execution. There could be a corner case here when the Loan COB fails to process some loan accounts (due to a bug, inconsistency, etc) and the loan accounts stay locked. This is an intended behavior to mark loans which are not supposed to be used until they are "fixed".</p>
+</div>
+<div class="paragraph">
+<p>Since the fixing might involve making changes to the loan account via API (for example doing a repayment to fix the loan account’s inconsistent state), we need to allow those API calls. Hence, the lock table includes a <code>bypass_enabled</code> column which disables the lock checks on the loan write APIs.</p>
+</div>
+<div class="sect3">
+<h4 id="_orphaned_cob_lock_cleanup"><a class="link" href="#_orphaned_cob_lock_cleanup">Orphaned COB lock cleanup</a></h4>
+<div class="paragraph">
+<p>A separate failure mode exists where a loan account is fully processed by COB but its lock row stays in <code>m_loan_account_locks</code> (no <code>error</code> was recorded, yet the lock was never removed). Such locks are orphaned: the loan is in a consistent state, but real-time write APIs are still rejected.</p>
+</div>
+<div class="paragraph">
+<p>To remove these, the Loan COB job runs a final step <code>unlockProcessedLoansStep</code> (after <code>stayedLockedStep</code>) which executes <code>UnlockProcessedLoansTasklet</code>. The tasklet delegates to <code>AccountLockService#removeOrphanedLocksForProcessedAccounts</code>, which deletes lock rows that meet all of the following:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><code>lock_owner</code> is <code>LOAN_COB_CHUNK_PROCESSING</code> or <code>LOAN_INLINE_COB_PROCESSING</code></p>
+</li>
+<li>
+<p><code>error IS NULL</code></p>
+</li>
+<li>
+<p><code>lock_placed_on_cob_business_date IS NOT NULL</code></p>
+</li>
+<li>
+<p>the referenced loan’s <code>last_closed_business_date</code> equals the lock’s <code>lock_placed_on_cob_business_date</code> (i.e. all business steps for that COB date completed)</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>The same mechanism is wired into the Working Capital Loan COB job via <code>UnlockProcessedWorkingCapitalLoansTasklet</code>. Locks that carry an <code>error</code> are intentionally left in place – they still represent a loan that needs manual fixing and <code>bypass_enabled</code> handling continues to apply.</p>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_technology"><a class="link" href="#_technology">Technology</a></h3>
+<div class="ulist">
+<ul>
+<li>
+<p>Java: <a href="http://www.oracle.com/technetwork/java/javase/downloads/index.html" class="bare">www.oracle.com/technetwork/java/javase/downloads/index.html</a></p>
+</li>
+<li>
+<p>JAX-RS using Jersey</p>
+</li>
+<li>
+<p>JSON using Google GSON</p>
+</li>
+<li>
+<p>Spring I/O Platform: <a href="http://spring.io/platform" class="bare">spring.io/platform</a></p>
+<div class="ulist">
+<ul>
+<li>
+<p>Spring Framework</p>
+</li>
+<li>
+<p>Spring Boot</p>
+</li>
+<li>
+<p>Spring Security</p>
+</li>
+<li>
+<p>Spring Data (JPA) backed by EclipseLink</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>MySQL: <a href="http://www.oracle.com/us/products/mysql/overview/index.html" class="bare">www.oracle.com/us/products/mysql/overview/index.html</a></p>
+</li>
+<li>
+<p>PostgreSQL</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>TBD</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_modules"><a class="link" href="#_modules">Modules</a></h3>
+<div class="paragraph">
+<p>We are currently working towards a fully modular codebase and will publish more here when we are ready.</p>
+</div>
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-note" title="Note"></i>
+</td>
+<td class="content">
+Even if we are not quite there yet with full modularity you can already create your own custom modules to extend Fineract. Please see chapter <a href="#_custom_modules"><em>Custom Modules</em></a>.
+</td>
+</tr>
+</table>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_introducing_business_date_into_fineract_community_version"><a class="link" href="#_introducing_business_date_into_fineract_community_version">Introducing Business Date into Fineract - Community version</a></h3>
+<div class="paragraph">
+<p>Business date as a concept does not exist as of now in Fineract. It would be business critical to add such a functionality to support various banking functionalities like “Closing of Business day”, “Having Closing of Business day relevant jobs”, “Supporting logical date management”.</p>
+</div>
+<div class="sect3">
+<h4 id="_glossary_2"><a class="link" href="#_glossary_2">Glossary</a></h4>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 50%;">
+<col style="width: 50%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">*COB</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Close of Business; concept of closing a business day</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">*Business day</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Timeframe that logically group together actions on a particular business date</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">*Business date</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Logical date; its value is not tied to the physical calendar. Represents a business day</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">*Cob date</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Logical date; Represents the business date for actions during COB job execution</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">*Created date</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">When the transaction was created (audit purposes). Date + time</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">*Last modified date</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">When the transaction was last modified (audit purposes). Date + time</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">*Submitted on date / Posting date</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">When the transaction was posted. Tenant date or business date (depends on whether the logical date concept was introduced or not)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">*Transaction date / Value date</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">The date on which the transaction occurred or to be accounted for</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect3">
+<h4 id="_current_behaviour"><a class="link" href="#_current_behaviour">Current behaviour</a></h4>
+<div class="ulist">
+<ul>
+<li>
+<p>Fineract support 3 types of dates:</p>
+<div class="ulist">
+<ul>
+<li>
+<p>System date</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Physical/System date of the running environment</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Tenant date</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Timezoned version of the above system date</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>User-provided date</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Based on the provided date (as string) and the provided date format</p>
+</li>
+</ul>
+</div>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>There is no support of logical date concept</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Independent from the system / tenant date</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Jobs are scheduled against system date (CRON), but aligned with the tenant timezone.</p>
+</li>
+<li>
+<p>During the job execution all the data and transactions are using the actual tenant date</p>
+<div class="ulist">
+<ul>
+<li>
+<p>It could happen some transactions are written for 17th of May and other for 18th of May, if the job was executed around midnight</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>There is no support of COB</p>
+<div class="ulist">
+<ul>
+<li>
+<p>No backdated transactions by jobs</p>
+</li>
+<li>
+<p>There is no support to logically group together transactions and store them with the same transaction date which is independent of the physical calendar of the tenant</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>All the transactions and business logic are tied to a physical calendar</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_business_date"><a class="link" href="#_business_date">Business date</a></h4>
+<div class="imageblock">
+<div class="content">
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAvYAAANCCAYAAAANzYj3AAAMa2lDQ1BJQ0MgUHJvZmlsZQAASImVVwdYU8kWnluSkJDQAhGQEnoTRHqREkKLICBVsBGSQEIJMSGo2FFRwbWLKFZ0VUTRtQCyqIi9LIqKXRcLCsq6qIuiqLwJCei6r3zv5Js7/z1z5j/lzuTeAUCzlyuRZKNaAOSI86SxYUHM8ckpTNJzQABU+NMEdC5PJmHFxEQCKIP93+X9LYAo+huOCq5/jv9X0eELZDwAkIkQp/FlvByIGwHAN/Ek0jwAiAq9xbQ8iQLPg1hXCgOEeK0CZyjxHgVOU+KGAZv4WDbE1wBQo3K50gwANB5APTOflwF5ND5D7Czmi8QAaI6A2J8n5PIhVsQ+IicnV4HLILaF9hKIYTzAK+07zoy/8acN8XO5GUNYmdeAqAWLZJJs7oz/szT/W3Ky [...]
+</div>
+</div>
+<div class="sect4">
+<h5 id="_design"><a class="link" href="#_design">Design</a></h5>
+<div class="paragraph">
+<p>By introducing the business day concept we are not tied anymore to the physical calendar of the system or the tenant. We got the ability to define our own business day boundaries which might end 15 minutes before midnight and any incoming transactions after the cutoff will be accounted for the following business day.</p>
+</div>
+<div class="paragraph">
+<p>It is a logical date which makes it possible to separate the business day from the physical calendar:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Close a business day before midnight</p>
+</li>
+<li>
+<p>Close a business day at midnight</p>
+</li>
+<li>
+<p>Close a business day after midnight</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>Closing a Business Day could be a longer process (see COB jobs) meanwhile some processes shall still be able to create transactions for that business day (COB jobs), but others are meant to create the transactions for the next (incoming transactions): Business date concept is there to sort that out.</p>
+</div>
+<div class="paragraph">
+<p>Business date concept is essential when:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Having COB jobs:</p>
+<div class="ulist">
+<ul>
+<li>
+<p>When the COB was triggered:</p>
+<div class="ulist">
+<ul>
+<li>
+<p>All the jobs which processing the data must still accounted for actual business day</p>
+</li>
+<li>
+<p>All the incoming transactions must be accounted to the next business day</p>
+</li>
+</ul>
+</div>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Business day is ending before / after midnight (tenant date / system date)</p>
+</li>
+<li>
+<p>Testing purposes:</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Since the transactions and job execution is not tied anymore to a physical calendar, we can easily test a whole loan lifecycle by altering the business date</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Handling disruption of service: For any unseen reason the system goes down or there are any disruption in the workflow, the “missed days” can easily be processed one by one as nothing happened</p>
+<div class="ulist">
+<ul>
+<li>
+<p>There is a disruption at 2022-06-02</p>
+</li>
+<li>
+<p>The issue is fixed by 2022-06-05</p>
+</li>
+<li>
+<p>The COB flow can be executed for 2022-06-03 and when it is finished for 2022-06-04 and after when the time arrives for 2022-06-05</p>
+</li>
+</ul>
+</div>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>This logical date is manageable via:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Job</p>
+</li>
+<li>
+<p>API</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>To maintain such separation from physical calendar we need to introduce the following new dates:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Business date</p>
+</li>
+<li>
+<p>COB date</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Can be calculated based on the actual business date</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Depend on COB date strategy (see below)</p>
+</li>
+</ul>
+</div>
+</li>
+</ul>
+</div>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_business_date_2"><a class="link" href="#_business_date_2">Business date</a></h5>
+<div class="paragraph">
+<p>The - logical - date of the actual business day, eg: 2022-05-06</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>It does not support time parts</p>
+</li>
+<li>
+<p>It can be managed manually (via API call) or automatically (via scheduled job)</p>
+</li>
+<li>
+<p>All business actions during the business day shall use this date:</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Posting / submitted on date of transactions</p>
+</li>
+<li>
+<p>Submitted on date of actions</p>
+</li>
+<li>
+<p>(Regular) jobs</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>It will be used in every situation where the transaction date / value date is not provided by the user or the user provided date shall be validated.</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Opening date</p>
+</li>
+<li>
+<p>Closing date</p>
+</li>
+<li>
+<p>Disbursal date</p>
+</li>
+<li>
+<p>Transaction/Value date</p>
+</li>
+<li>
+<p>Posting/Submitted date</p>
+</li>
+<li>
+<p>Reversal date</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Will not be use for audit purposes:</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Created on date</p>
+</li>
+<li>
+<p>Updated on date</p>
+</li>
+</ul>
+</div>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_cob_date"><a class="link" href="#_cob_date">COB date</a></h5>
+<div class="paragraph">
+<p>The - logical - date of the business day for job execution, eg: 2022-05-05</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>It can be calculated based on the business date</p>
+<div class="ulist">
+<ul>
+<li>
+<p>COB date = business date - 1 day</p>
+</li>
+<li>
+<p>Automatically modified alongside with the business date change</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>It does not support time parts</p>
+</li>
+<li>
+<p>It is automatically managed by business date change</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Configurable</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>It is used only via COB job execution</p>
+<div class="ulist">
+<ul>
+<li>
+<p>When we create / modify any business data during the COB job execution, the COB date is to be used:</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Posting date of transactions</p>
+</li>
+<li>
+<p>Submitted on date of actions</p>
+</li>
+<li>
+<p>Transaction / value date of any actions</p>
+</li>
+</ul>
+</div>
+</li>
+</ul>
+</div>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_some_basic_example"><a class="link" href="#_some_basic_example">Some basic example</a></h5>
+<div class="sect5">
+<h6 id="_apply_for_a_loan"><a class="link" href="#_apply_for_a_loan">Apply for a loan</a></h6>
+<div class="sect6">
+<h7 id="_1"><a class="link" href="#_1">#1</a></h7>
+<div class="paragraph">
+<p>Tenant date: 2022-05-23 14:22:12</p>
+</div>
+<div class="paragraph">
+<p>Business date: 2022-05-22</p>
+</div>
+<div class="paragraph">
+<p>Submitted on date: 2022-05-23</p>
+</div>
+<div class="paragraph">
+<p>Outcome: <strong>FAIL</strong></p>
+</div>
+<div class="paragraph">
+<p>Message: <strong>The date on which a loan is submitted cannot be in the future.</strong></p>
+</div>
+<div class="paragraph">
+<p>Reason: Even the tenant date is 2022-05-23, but the business date was 2022-05-22 which means anything further that date must be considered as a future date.</p>
+</div>
+</div>
+<div class="sect6">
+<h7 id="_2"><a class="link" href="#_2">#2</a></h7>
+<div class="paragraph">
+<p>Tenant date: 2022-05-23 14:22:12</p>
+</div>
+<div class="paragraph">
+<p>Business date: 2022-05-22</p>
+</div>
+<div class="paragraph">
+<p>Submitted on date: 2022-05-22</p>
+</div>
+<div class="paragraph">
+<p>Outcome: <strong>SUCCESS</strong></p>
+</div>
+<div class="paragraph">
+<p>Loan application details:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Submitted on date: 2022-05-22</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_repayment_for_a_loan"><a class="link" href="#_repayment_for_a_loan">Repayment for a loan</a></h6>
+<div class="sect6">
+<h7 id="_1_2"><a class="link" href="#_1_2">#1</a></h7>
+<div class="paragraph">
+<p>Tenant date: 2022-05-25 11:22:12</p>
+</div>
+<div class="paragraph">
+<p>Business date: 2022-05-24</p>
+</div>
+<div class="paragraph">
+<p>Transaction date: 2022-05-25</p>
+</div>
+<div class="paragraph">
+<p>Outcome: <strong>FAIL</strong></p>
+</div>
+<div class="paragraph">
+<p>Message: <strong>The transaction date cannot be in the future.</strong></p>
+</div>
+<div class="paragraph">
+<p>Reason: Even the physical date is 2022-05-25, but the business date was 2022-05-24 which means anything further that date must be considered as a future date.</p>
+</div>
+</div>
+<div class="sect6">
+<h7 id="_2_2"><a class="link" href="#_2_2">#2</a></h7>
+<div class="paragraph">
+<p>Tenant date: 2022-05-25 11:22:12</p>
+</div>
+<div class="paragraph">
+<p>Business date: 2022-05-24</p>
+</div>
+<div class="paragraph">
+<p>Transaction date: 2022-05-23</p>
+</div>
+<div class="paragraph">
+<p>Outcome: <strong>SUCCESS</strong></p>
+</div>
+<div class="paragraph">
+<p>Loan transaction details:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Submitted on date: 2022-05-24</p>
+</li>
+<li>
+<p>Transaction date: 2022-05-23</p>
+</li>
+<li>
+<p>Created on date: 2022-05-25 11:22:12</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_changes_in_fineract"><a class="link" href="#_changes_in_fineract">Changes in Fineract</a></h4>
+<div class="paragraph">
+<p>We shall modify at all the relevant places where the tenant date was used:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>With very limited exceptions all places where the tenant date is used we need to modify to use the business date.</p>
+</li>
+<li>
+<p>Replace system date with tenant date or business date (exceptions may apply)</p>
+</li>
+<li>
+<p>Add missing Value dates and Posting dates to entities</p>
+</li>
+<li>
+<p>Having a generic naming conventions for JPA fields and DB fields</p>
+</li>
+<li>
+<p>Renaming the fields accordingly</p>
+</li>
+<li>
+<p>Evaluate value date (transaction date) and posting date (submitted on date), created on date usages</p>
+</li>
+<li>
+<p>Jobs to be checked and modified accordingly</p>
+</li>
+<li>
+<p>Native queries to be checked and modified accordingly</p>
+</li>
+<li>
+<p>Reports to be checked and modified accordingly</p>
+</li>
+<li>
+<p>Every table where update is supported the AbstractAuditableCustom should be implemented</p>
+</li>
+<li>
+<p>Amend Transactions and Journal entries date handling to fit for business date concept</p>
+</li>
+<li>
+<p>For audit fields we shall introduce timezoned datetimes and store them in database accordingly</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Storing DATETIME fields without Timezone is potential problem due to the daylight savings</p>
+</li>
+<li>
+<p>Also, some external libs (like Quartz) are using system timezone and Fineract will using Tenant timezone for audit fields. To be able to distinct them in DB we shall use DATETIME with TIMESTAMP column types and use timezoned java time objects in the application</p>
+</li>
+</ul>
+</div>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_reliable_event_framework"><a class="link" href="#_reliable_event_framework">Reliable event framework</a></h3>
+<div class="paragraph">
+<p>Fineract is capable of generating and raising events for external consumers in a reliable way. This section is going to describe all the details on that front with examples.</p>
+</div>
+<div class="sect3">
+<h4 id="_framework_capabilities"><a class="link" href="#_framework_capabilities">Framework capabilities</a></h4>
+<div class="sect4">
+<h5 id="_acid_transactional_guarantee"><a class="link" href="#_acid_transactional_guarantee">ACID (transactional) guarantee</a></h5>
+<div class="paragraph">
+<p>The event framework must support ACID guarantees on the business operation level.</p>
+</div>
+<div class="paragraph">
+<p>Let’s see a simple use-case:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>A client applies to a loan on the UI</p>
+</li>
+<li>
+<p>The loan is created on the server</p>
+</li>
+<li>
+<p>A loan creation event is raised</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>What happens if step 3 fails? Shall it fail the original loan creation process?</p>
+</div>
+<div class="paragraph">
+<p>What happens if step 2 fails but step 3 still gets executed? We’re raising an event for a loan that hasn’t been created in reality.</p>
+</div>
+<div class="paragraph">
+<p>Therefore, raising an event is tied to the original business transaction to ensure the data that’s getting written into the database along with the respective events are saved in an all-or-nothing fashion.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_messaging_integration"><a class="link" href="#_messaging_integration">Messaging integration</a></h5>
+<div class="paragraph">
+<p>The system is able to send the raised events to downstream message channels. The current implementation supports the following message channels:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>ActiveMQ</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_ordering_guarantee"><a class="link" href="#_ordering_guarantee">Ordering guarantee</a></h5>
+<div class="paragraph">
+<p>The events that are raised will be sent to the downstream message channels in the same order as they were raised.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_delivery_guarantee"><a class="link" href="#_delivery_guarantee">Delivery guarantee</a></h5>
+<div class="paragraph">
+<p>The framework supports the at-least-once delivery guarantee for the raised events.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_reliability_and_fault_tolerance"><a class="link" href="#_reliability_and_fault_tolerance">Reliability and fault-tolerance</a></h5>
+<div class="paragraph">
+<p>In terms of reliability and fault-tolerance, the event framework is able to handle the cases when the downstream message channel is not able to accept events. As soon as the message channel is back to operational, the events will be sent again.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_selective_event_producing"><a class="link" href="#_selective_event_producing">Selective event producing</a></h5>
+<div class="paragraph">
+<p>Whether or not an event must be sent to downstream message channels for a particular Fineract instance is configurable through the UI and API.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_standardized_format"><a class="link" href="#_standardized_format">Standardized format</a></h5>
+<div class="paragraph">
+<p>All the events sent to downstream message channels are conforming a standardized format using Avro schemas.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_extendability_and_customizations"><a class="link" href="#_extendability_and_customizations">Extendability and customizations</a></h5>
+<div class="paragraph">
+<p>The event framework is capable of being easily extended with new events for additional business operations or customizing existing events.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_ability_to_send_events_in_bulk"><a class="link" href="#_ability_to_send_events_in_bulk">Ability to send events in bulk</a></h5>
+<div class="paragraph">
+<p>The event framework makes it possible to sort of queue events until they are ready to be sent and send them as a single message instead of sending each event as a separate, individual one.</p>
+</div>
+<div class="paragraph">
+<p>For example during the COB process, there might be events raised in separate business steps which needs to be sent out but they only need to be sent out at the end of the COB execution process instead of one-by-one.</p>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_architecture_2"><a class="link" href="#_architecture_2">Architecture</a></h4>
+<div class="sect4">
+<h5 id="_intro"><a class="link" href="#_intro">Intro</a></h5>
+<div class="paragraph">
+<p>On a high-level, the concept looks the following. An event gets raised in a business operation. The event data gets saved to the database - to ensure ACID guarantees. An asynchronous process takes the saved events from the database and puts them onto a message channel.</p>
+</div>
+<div class="paragraph">
+<p>The flow can be seen in the following diagram:</p>
+</div>
+<div class="imageblock">
+<div class="content">
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAtMAAAGyCAYAAAAmtEhTAAAAAXNSR0IArs4c6QAAIABJREFUeF7svQu4FtV5/n2DCoIKKEQRKirtFzCtoUFjNMBHo0ZIJIlCKuYD/okxkSiEiAEPyEkOxooB5UMNRmIaoBUbjIkYhRgtBeKhSIv2UjAtKh5C0o0ixI2H6P5fz7ifN2svZt6Zeec8c891bWXvmbXWs37Peue955lnrdUOPEiABEiABEiABEiABEiABBoi0K6hUixEAiRAAiRAAiRAAiRAAiQAimkOAhIgARIgARIgARIgARJokADFdIPgWIwESIAESIAESIAESIAEKKY5BkiABEiABEiABEiABEigQQIU0w2CYzESIAESIAESIAESIAESoJjmGCABEiABEiABEiABEiCBBglQTDcIjsVIgARIgARIgARIgARIgGKaY4AESIAESIAE [...]
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_foundational_business_events"><a class="link" href="#_foundational_business_events">Foundational business events</a></h5>
+<div class="paragraph">
+<p>The whole framework is built upon an existing infrastructure in Fineract; the Business Events.</p>
+</div>
+<div class="paragraph">
+<p>As a quick recap, Business Events are Fineract events that can be raised at any place in a business operation using the <code>BusinessEventNotifierService</code>. Callbacks can be registered when a certain type of Business Event is raised and other business operations can be done. For example when a Loan gets disbursed, there’s an interested party doing the Loan Arrears Aging recalculation using the Business Event communication.</p>
+</div>
+<div class="paragraph">
+<p>The nice thing about the Business Events is that they are tied to the original transaction which means if any of the processing on the subscriber’s side fail, the entire original transaction will be rolled back. This was one of the requirements for the Reliable event framework.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_event_database_integration"><a class="link" href="#_event_database_integration">Event database integration</a></h5>
+<div class="paragraph">
+<p>The database plays a crucial part in the framework since to ensure transactionality, - without doing proper transaction synchronization between different message channels and the database - the framework is going to save all the raised events into the same relational database that Fineract is using.</p>
+</div>
+<div class="sect5">
+<h6 id="_database_structure"><a class="link" href="#_database_structure">Database structure</a></h6>
+<div class="paragraph">
+<p>The database structure looks the following</p>
+</div>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 25%;">
+<col style="width: 25%;">
+<col style="width: 25%;">
+<col style="width: 25%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Name</strong></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Type</strong></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Description</strong></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Example</strong></p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>id</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">number</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Auto incremented ID.<br>
+<br>
+Not null.</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>1</code></p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>type</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">text</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">The event type as a string.<br>
+<br>
+Not null.</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>LoanApprovedBusinessEvent</code></p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>schema</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">text</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">The fully qualified name of the schema that was used for the data serialization, as a string.<br>
+<br>
+Not null.</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>org.apache.fineract.avro.loan.v1.LoanAccountDataV1</code></p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>data</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">BLOB (MySQL/MariaDB), BYTEA (PostgreSQL)</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">The event payload as Avro binary.<br>
+<br>
+Not null.</p></td>
+<td class="tableblock halign-left valign-top"></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>created_at</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">timestamp</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">UTC timestamp when the event was raised.<br>
+<br>
+Not null.</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>2022-09-06 14:20:10.148627 +00:00</code></p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>status</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">text</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Enum text representing the status of the external event.<br>
+<br>
+Not null, indexed.</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>TO_BE_SENT</code>, <code>SENT</code></p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>sent_at</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">timestamp</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">UTC timestamp when the event was sent.</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>2022-09-06 14:30:10.148627 +00:00</code></p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>idempotency_key</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">text</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Randomly generated UUID upon inserting a row into the table for idempotency purposes.<br>
+<br>
+Not null.</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>68aed085-8235-4722-b27d-b38674c19445</code></p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>business_date</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">date</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">The business date to when the event was generated.<br>
+<br>
+Not null, indexed.</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>2022-09-05</code></p></td>
+</tr>
+</tbody>
+</table>
+<div class="paragraph">
+<p>The above database table contains the unsent events which later on will be sent by an asynchronous event processor.</p>
+</div>
+<div class="paragraph">
+<p>Upon successfully sending an event, the corresponding statuses will be updated.</p>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_avro_schemas"><a class="link" href="#_avro_schemas">Avro schemas</a></h5>
+<div class="paragraph">
+<p>For serializing events, Fineract is using Apache Avro. There are 2 reasons for that:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>More compact storage since Avro is a binary format</p>
+</li>
+<li>
+<p>The Avro schemas are published with Fineract as a separate JAR so event consumers can directly map the events into POJOs</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>There are 3 different levels of Avro schemas used in Fineract for the Reliable event framework which are described below.</p>
+</div>
+<div class="sect5">
+<h6 id="_standard_event_schema"><a class="link" href="#_standard_event_schema">Standard event schema</a></h6>
+<div class="paragraph">
+<p>The standard event schema is for the regular events. These schemas are used when saving a raised event into the database and using the Avro schema to serialize the event data into a binary format.</p>
+</div>
+<div class="paragraph">
+<p>For example the OfficeDataV1 Avro schema looks the following:</p>
+</div>
+<details>
+<summary class="title"><code>OfficeDataV1.avsc</code></summary>
+<div class="content">
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"OfficeDataV1"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"namespace"</span><span class="p">:</span><span class="w"> </span><span class="s2">"org.apache.fineract.avro.office.v1"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"record"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"fields"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
+ </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"default"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"id"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
+ </span><span class="s2">"null"</span><span class="p">,</span><span class="w">
+ </span><span class="s2">"long"</span><span class="w">
+ </span><span class="p">]</span><span class="w">
+ </span><span class="p">},</span><span class="w">
+ </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"default"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"name"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
+ </span><span class="s2">"null"</span><span class="p">,</span><span class="w">
+ </span><span class="s2">"string"</span><span class="w">
+ </span><span class="p">]</span><span class="w">
+ </span><span class="p">},</span><span class="w">
+ </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"default"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"nameDecorated"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
+ </span><span class="s2">"null"</span><span class="p">,</span><span class="w">
+ </span><span class="s2">"string"</span><span class="w">
+ </span><span class="p">]</span><span class="w">
+ </span><span class="p">},</span><span class="w">
+ </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"default"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"externalId"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
+ </span><span class="s2">"null"</span><span class="p">,</span><span class="w">
+ </span><span class="s2">"string"</span><span class="w">
+ </span><span class="p">]</span><span class="w">
+ </span><span class="p">},</span><span class="w">
+ </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"default"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"openingDate"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
+ </span><span class="s2">"null"</span><span class="p">,</span><span class="w">
+ </span><span class="s2">"string"</span><span class="w">
+ </span><span class="p">]</span><span class="w">
+ </span><span class="p">},</span><span class="w">
+ </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"default"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"hierarchy"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
+ </span><span class="s2">"null"</span><span class="p">,</span><span class="w">
+ </span><span class="s2">"string"</span><span class="w">
+ </span><span class="p">]</span><span class="w">
+ </span><span class="p">},</span><span class="w">
+ </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"default"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"parentId"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
+ </span><span class="s2">"null"</span><span class="p">,</span><span class="w">
+ </span><span class="s2">"long"</span><span class="w">
+ </span><span class="p">]</span><span class="w">
+ </span><span class="p">},</span><span class="w">
+ </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"default"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"parentName"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
+ </span><span class="s2">"null"</span><span class="p">,</span><span class="w">
+ </span><span class="s2">"string"</span><span class="w">
+ </span><span class="p">]</span><span class="w">
+ </span><span class="p">},</span><span class="w">
+ </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"default"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"allowedParents"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
+ </span><span class="s2">"null"</span><span class="p">,</span><span class="w">
+ </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"array"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"items"</span><span class="p">:</span><span class="w"> </span><span class="s2">"org.apache.fineract.avro.office.v1.OfficeDataV1"</span><span class="w">
+ </span><span class="p">}</span><span class="w">
+ </span><span class="p">]</span><span class="w">
+ </span><span class="p">}</span><span class="w">
+ </span><span class="p">]</span><span class="w">
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+</div>
+</details>
+</div>
+<div class="sect5">
+<h6 id="_event_message_schema"><a class="link" href="#_event_message_schema">Event message schema</a></h6>
+<div class="paragraph">
+<p>The event message schema is just a wrapper around the standard event schema with extra metadata for the event consumers.</p>
+</div>
+<div class="paragraph">
+<p>Since Avro is strongly typed, the event content needs to be first serialized into a byte sequence and that needs to be wrapped around.</p>
+</div>
+<div class="paragraph">
+<p>This implies that for putting a single event message onto a message queue for external consumption, data needs to be serialized 2 times; this is the 2-level serialization.</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Serializing the event</p>
+</li>
+<li>
+<p>Serializing the already serialized event into an event message using the message wrapper</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>The message schema looks the following:</p>
+</div>
+<div class="listingblock">
+<div class="title"><code>MessageV1.avsc</code></div>
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"MessageV1"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"namespace"</span><span class="p">:</span><span class="w"> </span><span class="s2">"org.apache.fineract.avro"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"record"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"fields"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
+ </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"id"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"doc"</span><span class="p">:</span><span class="w"> </span><span class="s2">"The ID of the message to be sent"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"long"</span><span class="w">
+ </span><span class="p">},</span><span class="w">
+ </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"source"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"doc"</span><span class="p">:</span><span class="w"> </span><span class="s2">"A unique identifier of the source service"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"string"</span><span class="w">
+ </span><span class="p">},</span><span class="w">
+ </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"type"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"doc"</span><span class="p">:</span><span class="w"> </span><span class="s2">"The type of event the payload refers to. For example LoanApprovedBusinessEvent"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"string"</span><span class="w">
+ </span><span class="p">},</span><span class="w">
+ </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"category"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"doc"</span><span class="p">:</span><span class="w"> </span><span class="s2">"The category of event the payload refers to. For example LOAN"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"string"</span><span class="w">
+ </span><span class="p">},</span><span class="w">
+ </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"createdAt"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"doc"</span><span class="p">:</span><span class="w"> </span><span class="s2">"The UTC time of when the event has been raised; in ISO_LOCAL_DATE_TIME format. For example 2011-12-03T10:15:30"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"string"</span><span class="w">
+ </span><span class="p">},</span><span class="w">
+ </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"businessDate"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"doc"</span><span class="p">:</span><span class="w"> </span><span class="s2">"The business date when the event has been raised; in ISO_LOCAL_DATE format. For example 2011-12-03"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"string"</span><span class="w">
+ </span><span class="p">},</span><span class="w">
+ </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"tenantId"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"doc"</span><span class="p">:</span><span class="w"> </span><span class="s2">"The tenantId that the event has been sent from. For example default"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"string"</span><span class="w">
+ </span><span class="p">},</span><span class="w">
+ </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"idempotencyKey"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"doc"</span><span class="p">:</span><span class="w"> </span><span class="s2">"The idempotency key for this particular event for consumer de-duplication"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"string"</span><span class="w">
+ </span><span class="p">},</span><span class="w">
+ </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"dataschema"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"doc"</span><span class="p">:</span><span class="w"> </span><span class="s2">"The fully qualified name of the schema of the event payload. For example org.apache.fineract.avro.loan.v1.LoanAccountDataV1"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"string"</span><span class="w">
+ </span><span class="p">},</span><span class="w">
+ </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"data"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"doc"</span><span class="p">:</span><span class="w"> </span><span class="s2">"The payload data serialized into Avro bytes"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"bytes"</span><span class="w">
+ </span><span class="p">}</span><span class="w">
+ </span><span class="p">]</span><span class="w">
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_bulk_event_schema"><a class="link" href="#_bulk_event_schema">Bulk event schema</a></h6>
+<div class="paragraph">
+<p>The bulk event schema is used when multiple events are supposed to be sent together. This schema is used also when serializing the data for the database storing but the idea is quite simple. Have an array of other event schemas embedded into it.</p>
+</div>
+<div class="paragraph">
+<p>Since Avro is strongly typed, the array within the bulk event schema is an array of <code>MessageV1</code> schemas. That way the consumers can decide which events they want to deserialize and which don’t.</p>
+</div>
+<div class="paragraph">
+<p>This elevates the regular 2-level serialization/deserialization concept up to a 3-level one:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Serializing the standard events</p>
+</li>
+<li>
+<p>Serializing the standard events into a bulk event</p>
+</li>
+<li>
+<p>Serializing the bulk event into an event message</p>
+</li>
+</ol>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_versioning"><a class="link" href="#_versioning">Versioning</a></h6>
+<div class="paragraph">
+<p>Avro is quite strict with changes to an existing schema and there are a number of compatibility modes available.</p>
+</div>
+<div class="paragraph">
+<p>Fineract keeps it simple though. Version numbers - in the package names and in the schema names - are increased with each published modification; meaning that if the OfficeDataV1 schema needs a new field and the <code>OfficeDataV1</code> schema has been published officially with Fineract, a new <code>OfficeDataV2</code> has to be created with the new field instead of modifying the existing schema.</p>
+</div>
+<div class="paragraph">
+<p>This pattern ensures that a certain event is always deserialized with the appropriate schema definition, otherwise the deserialization could fail.</p>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_code_generation"><a class="link" href="#_code_generation">Code generation</a></h6>
+<div class="paragraph">
+<p>The Avro schemas are described as JSON documents. That’s hardly usable directly with Java hence Fineract generates Java POJOs from the Avro schemas. The good thing about these POJOs is the fact that they can be serialized/deserialized in themselves without any magic since they have a <code>toByteBuffer</code> and <code>fromByteBuffer</code> method.</p>
+</div>
+<div class="paragraph">
+<p>From POJO to ByteBuffer:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="nc">LoanAccountDataV1</span> <span class="n">avroDto</span> <span class="o">=</span> <span class="o">...</span>
+<span class="nc">ByteBuffer</span> <span class="n">buffer</span> <span class="o">=</span> <span class="n">avroDto</span><span class="o">.</span><span class="na">toByteBuffer</span><span class="o">();</span></code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>From ByteBuffer to POJO:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="nc">ByteBuffer</span> <span class="n">buffer</span> <span class="o">=</span> <span class="o">...</span>
+<span class="nc">LoanAccountDataV1</span> <span class="n">avroDto</span> <span class="o">=</span> <span class="nc">LoanAccountDataV1</span><span class="o">.</span><span class="na">fromByteBuffer</span><span class="o">(</span><span class="n">buffer</span><span class="o">);</span></code></pre>
+</div>
+</div>
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-note" title="Note"></i>
+</td>
+<td class="content">
+The ByteBuffer is a stateful container and needs to be handled carefully. Therefore Fineract has a built-in ByteBuffer to byte array converter; <code>ByteBufferConverter</code>.
+</td>
+</tr>
+</table>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_downstream_event_consumption"><a class="link" href="#_downstream_event_consumption">Downstream event consumption</a></h6>
+<div class="paragraph">
+<p>When consuming events on the other side of the message channel, it’s critical to know which events the system is interested in. With the multi-level serialization, it’s possible to deserialize only parts of the message and decide based on that whether it makes sense for a particular system to deserialize the event payload more.</p>
+</div>
+<div class="paragraph">
+<p>Whether events are important can be decided based on:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>the <code>type</code> attribute in the message</p>
+</li>
+<li>
+<p>the <code>category</code> attribute in the message</p>
+</li>
+<li>
+<p>the <code>dataschema</code> attribute in the message</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>These are the main attributes in the message wrapper one can use to decide whether an event message is useful.</p>
+</div>
+<div class="paragraph">
+<p>If the event needs to be deserialized, the next step is to find the corresponding schema definition. That’s going to be sent in the <code>dataschema</code> attribute within the message wrapper. Since the attribute contains the fully-qualified name of the respective schema, it can be easily resolved to a Class object. Based on that class, the payload data can be easily deserialized using the <code>fromByteBuffer</code> method on every generated schema POJO.</p>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_message_ordering"><a class="link" href="#_message_ordering">Message ordering</a></h5>
+<div class="paragraph">
+<p>One of the requirements for the framework is to provide ordering guarantees. All the events have to conform a happens-before relation.</p>
+</div>
+<div class="paragraph">
+<p>For the downstream consumers, this can be verified by the <code>id</code> attribute within the messages. Since it’s going to be a strictly-monotonic numeric sequence, it can be used for ordering purposes.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_event_categorization"><a class="link" href="#_event_categorization">Event categorization</a></h5>
+<div class="paragraph">
+<p>For easier consumption, the terminology event category is introduced. This is nothing else but the bounded context an event is related to.</p>
+</div>
+<div class="paragraph">
+<p>For example the LoanApprovedBusinessEvent and the LoanWaiveInterestBusinessEvent are both related to the Loan bounded contexts.</p>
+</div>
+<div class="paragraph">
+<p>The category in which an event resides in is included in the message under the <code>category</code> attribute.</p>
+</div>
+<div class="paragraph">
+<p>The existing event categories can be found under the <a href="#event-categories">Event categories</a> section.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_asynchronous_event_processor"><a class="link" href="#_asynchronous_event_processor">Asynchronous event processor</a></h5>
+<div class="paragraph">
+<p>The events stored in the database will be picked up and sent by a regularly executed job.</p>
+</div>
+<div class="paragraph">
+<p>This job is a Fineract job, scheduled to run for every minute and will pick a number of events in order. Those events will be put onto the downstream message channel in the same order as they were raised.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_purging_events"><a class="link" href="#_purging_events">Purging events</a></h5>
+<div class="paragraph">
+<p>The events database table is going to grow continuously. That’s why Fineract has a purging functionality in place that’s gonna delete old and already sent events.</p>
+</div>
+<div class="paragraph">
+<p>It’s implemented as a Fineract job and is disabled by default. It’s called TBD.</p>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_usage"><a class="link" href="#_usage">Usage</a></h4>
+<div class="paragraph">
+<p>Using the event framework is quite simple. First, it has to be enabled through properties or environment variable.</p>
+</div>
+<div class="paragraph">
+<p>The respective options are the following:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>the <code>fineract.events.external.enabled</code> property</p>
+</li>
+<li>
+<p>the <code>FINERACT_EXTERNAL_EVENTS_ENABLED</code> environment variable</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>These configurations accept a boolean value; <code>true</code> or <code>false</code>.</p>
+</div>
+<div class="paragraph">
+<p>The key component to interact with is the <code>BusinessEventNotifierService#notifyPostBusinessEvent</code> method.</p>
+</div>
+<div class="sect4">
+<h5 id="_raising_events"><a class="link" href="#_raising_events">Raising events</a></h5>
+<div class="paragraph">
+<p>Raising events is really easy. An instance of a BusinessEvent interface is needed, that’s going to be the event. There are plenty of them available already in the Fineract codebase.</p>
+</div>
+<div class="paragraph">
+<p>And that’s pretty much it. Everything else is taken care of in terms of event data persisting and later on putting it onto a message channel.</p>
+</div>
+<div class="paragraph">
+<p>An example of event raising:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="nd">@Override</span>
+<span class="kd">public</span> <span class="nc">CommandProcessingResult</span> <span class="nf">createClient</span><span class="o">(</span><span class="kd">final</span> <span class="nc">JsonCommand</span> <span class="n">command</span><span class="o">)</span> <span class="o">{</span>
+ <span class="o">...</span>
+ <span class="n">businessEventNotifierService</span><span class="o">.</span><span class="na">notifyPostBusinessEvent</span><span class="o">(</span><span class="k">new</span> <span class="nc">ClientCreateBusinessEvent</span><span class="o">(</span><span class="n">newClient</span><span class="o">));</span>
+ <span class="o">...</span>
+ <span class="k">return</span> <span class="o">...;</span>
+<span class="o">}</span></code></pre>
+</div>
+</div>
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-note" title="Note"></i>
+</td>
+<td class="content">
+The above code is copied from the <code>ClientWritePlatformServiceJpaRepositoryImpl</code> class.
+</td>
+</tr>
+</table>
+</div>
+<div class="sect5">
+<h6 id="_example_event_message_content"><a class="link" href="#_example_event_message_content">Example event message content</a></h6>
+<div class="paragraph">
+<p>Since the message is serialized into binary format, it’s hard to represent in the documentation therefore here’s a JSON representation of the data, just as an example.</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">121</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"source"</span><span class="p">:</span><span class="w"> </span><span class="s2">"a65d759d-04f9-4ddf-ac52-34fa5d1f5a25"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"LoanApprovedBusinessEvent"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"category"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Loan"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"createdAt"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2022-09-05T10:15:30"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"tenantId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"default"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"idempotencyKey"</span><span class="p">:</span><span class="w"> </span><span class="s2">"abda146d-68b5-48ca-b527-16d2b7c5daef"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"dataschema"</span><span class="p">:</span><span class="w"> </span><span class="s2">"org.apache.fineract.avro.loan.v1.LoanAccountDataV1"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"data"</span><span class="p">:</span><span class="w"> </span><span class="s2">"..."</span><span class="w">
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-note" title="Note"></i>
+</td>
+<td class="content">
+The source attribute refers to an ID that’s identifying the producer service. Fineract will regenerate this ID upon each application startup.
+</td>
+</tr>
+</table>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_raising_bulk_events"><a class="link" href="#_raising_bulk_events">Raising bulk events</a></h5>
+<div class="paragraph">
+<p>Raising bulk events is really easy as well. The 2 key methods are:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><code>BusinessEventNotifierService#startExternalEventRecording</code></p>
+</li>
+<li>
+<p><code>BusinessEventNotifierService#stopExternalEventRecording</code></p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>First, you have to start recording your events. This recording will be applied for the current thread. And then you can raise as many events as you want with the regular <code>BusinessEventNotifierService#notifyPostBusinessEvent</code> method, but they won’t get saved to the database immediately. They’ll get "recorded" into an internal buffer.</p>
+</div>
+<div class="paragraph">
+<p>When you stop recording using the method above, all the recorded events will be saved as a bulk event to the database; and serialized appropriately.</p>
+</div>
+<div class="paragraph">
+<p>From then on, the bulk event works just like any of the event. It’ll be picked up by the processor to send it to a message channel.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="event-categories"><a class="link" href="#event-categories">Event categories</a></h5>
+<div class="paragraph">
+<p>TBD</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_selective_event_producing_2"><a class="link" href="#_selective_event_producing_2">Selective event producing</a></h5>
+<div class="paragraph">
+<p>TBD</p>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_customizations"><a class="link" href="#_customizations">Customizations</a></h4>
+<div class="paragraph">
+<p>The framework provides a number of customization options:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Creating new events (that’s already given by the Business Events)</p>
+</li>
+<li>
+<p>Creating new Avro schemas</p>
+</li>
+<li>
+<p>Customizing what data gets serialized for existing events</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>In the upcoming sections, that’s what going to be discussed.</p>
+</div>
+<div class="sect4">
+<h5 id="_creating_new_events"><a class="link" href="#_creating_new_events">Creating new events</a></h5>
+<div class="paragraph">
+<p>Creating new events is super easy. Just create an implementation of the <code>BusinessEvent</code> interface and that’s it.</p>
+</div>
+<div class="paragraph">
+<p>From then on, you can raise those events in the system, although you can’t publish them to an external message channel. If you have the event framework enabled, it’s going to fail with not finding the appropriate serializer for your business event.</p>
+</div>
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-note" title="Note"></i>
+</td>
+<td class="content">
+There are existing serializers which might be able to handle your new event. For example the <code>LoanBusinessEventSerializer</code> is capable of handling all <code>LoanBusinessEvent</code> subclasses so there’s no need to create a brand new serializer.
+</td>
+</tr>
+</table>
+</div>
+<div class="paragraph">
+<p>The interface looks the following:</p>
+</div>
+<div class="listingblock">
+<div class="title"><code>BusinessEvent.java</code></div>
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">BusinessEvent</span><span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="o">{</span>
+
+ <span class="no">T</span> <span class="nf">get</span><span class="o">();</span>
+
+ <span class="nc">String</span> <span class="nf">getType</span><span class="o">();</span>
+
+ <span class="nc">String</span> <span class="nf">getCategory</span><span class="o">();</span>
+
+ <span class="nc">Long</span> <span class="nf">getAggregateRootId</span><span class="o">();</span>
+<span class="o">}</span></code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Quite simple. The <code>get</code> method should return the data you want to pass within the event instance. The <code>getType</code> method returns the name of the business event that’s gonna be saved as the <code>type</code> into the database.</p>
+</div>
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-note" title="Note"></i>
+</td>
+<td class="content">
+Creating a new business event only means that it can be used for raising an event. To make it compatible with the event framework and to be sent to a message channel, some extra work is needed which are described below.
+</td>
+</tr>
+</table>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_creating_new_avro_schemas_and_serializers"><a class="link" href="#_creating_new_avro_schemas_and_serializers">Creating new Avro schemas and serializers</a></h5>
+<div class="paragraph">
+<p>First let’s talk about the event serializers because that’s what’s needed to make a new event compatible with the framework.</p>
+</div>
+<div class="paragraph">
+<p>The serializer has a special interface, <code>BusinessEventSerializer</code>.</p>
+</div>
+<div class="listingblock">
+<div class="title"><code>BusinessEventSerializer.java</code></div>
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">BusinessEventSerializer</span> <span class="o">{</span>
+
+ <span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="kt">boolean</span> <span class="nf">canSerialize</span><span class="o">(</span><span class="nc">BusinessEvent</span><span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="n">event</span><span class="o">);</span>
+
+ <span class="nc">Class</span><span class="o"><?</span> <span class="kd">extends</span> <span class="nc">GenericContainer</span><span class="o">></span> <span class="nf">getSupportedSchema</span><span class="o">();</span>
+
+ <span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="nc">ByteBufferSerializable</span> <span class="nf">toAvroDTO</span><span class="o">(</span><span class="nc">BusinessEvent</span><span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="n">rawEvent</span><span class="o">);</span>
+
+<span class="o">}</span></code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>An implementation of this interface shall be registered as a Spring bean, and it’ll be picked up automatically by the framework.</p>
+</div>
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-note" title="Note"></i>
+</td>
+<td class="content">
+You can look at the existing serializers for implementation ideas.
+</td>
+</tr>
+</table>
+</div>
+<div class="paragraph">
+<p>New Avro schemas can be easily created. Just create a new Avro schema file in the <code>fineract-avro-schemas</code> project under the respective bounded context folder, and it will be picked up automatically by the code generator.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_bigdecimal_support_in_avro_schemas"><a class="link" href="#_bigdecimal_support_in_avro_schemas">BigDecimal support in Avro schemas</a></h5>
+<div class="paragraph">
+<p>Apache Avro by default doesn’t support complex types like a BigDecimal. It has to be implemented using a custom snippet like this:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="nl">"logicalType"</span><span class="p">:</span><span class="w"> </span><span class="s2">"decimal"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"precision"</span><span class="p">:</span><span class="w"> </span><span class="mi">27</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"scale"</span><span class="p">:</span><span class="w"> </span><span class="mi">8</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"bytes"</span><span class="w">
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>It’s a 20 precision and 8 scale BigDecimal.</p>
+</div>
+<div class="paragraph">
+<p>Obviously it’s quite challenging to copy-paste this snippet to every single BigDecimal field, so there’s a customization in place for Fineract.<br>
+The type <code>bigdecimal</code> is supported natively, and you’re free to use it like this:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="nl">"default"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"principal"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
+ </span><span class="s2">"null"</span><span class="p">,</span><span class="w">
+ </span><span class="s2">"bigdecimal"</span><span class="w">
+ </span><span class="p">]</span><span class="w">
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-note" title="Note"></i>
+</td>
+<td class="content">
+This <code>bigdecimal</code> type will be simple replaced with the BigDecimal snippet showed above during the compilation process.
+</td>
+</tr>
+</table>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_custom_data_serialization_for_existing_events"><a class="link" href="#_custom_data_serialization_for_existing_events">Custom data serialization for existing events</a></h5>
+<div class="paragraph">
+<p>In case there’s a need some extra bit of information within the event message that the default serializers are not providing, you can override this behavior by registering a brand-new custom serializer (as shown above).</p>
+</div>
+<div class="paragraph">
+<p>Since there’s a priority order of serializers, the only thing the custom serializer need to do is to be annotated by the <code>@Order</code> annotation or to implement the <code>Ordered</code> interface.</p>
+</div>
+<div class="paragraph">
+<p>An example custom serializer with priority looks the following:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="nd">@Component</span>
+<span class="nd">@RequiredArgsConstructor</span>
+<span class="nd">@Order</span><span class="o">(</span><span class="nc">Ordered</span><span class="o">.</span><span class="na">HIGHEST_PRECEDENCE</span><span class="o">)</span>
+<span class="kd">public</span> <span class="kd">class</span> <span class="nc">CustomLoanBusinessEventSerializer</span> <span class="kd">implements</span> <span class="nc">BusinessEventSerializer</span> <span class="o">{</span>
+ <span class="o">...</span>
+
+ <span class="nd">@Override</span>
+ <span class="kd">public</span> <span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="kt">boolean</span> <span class="nf">canSerialize</span><span class="o">(</span><span class="nc">BusinessEvent</span><span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="n">event</span><span class="o">)</span> <span class="o">{</span>
+ <span class="k">return</span> <span class="o">...;</span>
+ <span class="o">}</span>
+
+ <span class="nd">@Override</span>
+ <span class="kd">public</span> <span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="kt">byte</span><span class="o">[]</span> <span class="nf">serialize</span><span class="o">(</span><span class="nc">BusinessEvent</span><span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="n">rawEvent</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
+ <span class="o">...</span>
+ <span class="nc">ByteBuffer</span> <span class="n">buffer</span> <span class="o">=</span> <span class="n">avroDto</span><span class="o">.</span><span class="na">toByteBuffer</span><span class="o">();</span>
+ <span class="k">return</span> <span class="n">byteBufferConverter</span><span class="o">.</span><span class="na">convert</span><span class="o">(</span><span class="n">buffer</span><span class="o">);</span>
+ <span class="o">}</span>
+
+ <span class="nd">@Override</span>
+ <span class="kd">public</span> <span class="nc">Class</span><span class="o"><?</span> <span class="kd">extends</span> <span class="nc">GenericContainer</span><span class="o">></span> <span class="nf">getSupportedSchema</span><span class="o">()</span> <span class="o">{</span>
+ <span class="k">return</span> <span class="o">...;</span>
+ <span class="o">}</span>
+<span class="o">}</span></code></pre>
+</div>
+</div>
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-note" title="Note"></i>
+</td>
+<td class="content">
+All the default serializers are having <code>Ordered.LOWEST_PRECEDENCE</code>.
+</td>
+</tr>
+</table>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_appendix_a_properties_and_environment_variables"><a class="link" href="#_appendix_a_properties_and_environment_variables">Appendix A: Properties and environment variables</a></h4>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 25%;">
+<col style="width: 25%;">
+<col style="width: 25%;">
+<col style="width: 25%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Property name</th>
+<th class="tableblock halign-left valign-top">Environment variable</th>
+<th class="tableblock halign-left valign-top">Default value</th>
+<th class="tableblock halign-left valign-top">Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>fineract.events.external.enabled</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>FINERACT_EXTERNAL_EVENTS_ENABLED</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>false</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Whether the external event sending is enabled or disabled.</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_introducing_advanced_payment_allocation"><a class="link" href="#_introducing_advanced_payment_allocation">Introducing Advanced payment allocation</a></h3>
+<div class="paragraph">
+<p>Since the first repayment strategy got introduced, many followed, but there was one thing common in them:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>They were hard coding the allocation rules for each transaction type.</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>By introducing the "Advanced payment allocation" the idea was to have a repayment strategy which was:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>supporting dynamic configuration of the allocation rules for transaction types</p>
+</li>
+<li>
+<p>supporting configuration of more fine-grained allocation rules for future installments</p>
+</li>
+<li>
+<p>supporting reprocessing of transactions and charges in chronological order</p>
+</li>
+</ul>
+</div>
+<div class="sect3">
+<h4 id="_glossary_3"><a class="link" href="#_glossary_3">Glossary</a></h4>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 50%;">
+<col style="width: 50%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">*Advanced payment allocation</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Ability to configure allocation rules dynamically for transactions</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">*Payment allocation</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Rule that defines which outstanding balance to be paid of first on which installment</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">*Re-amortization</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Transaction amount to be divided into equal portions by the number of future installments and those installments to be paid by these portions.</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect3">
+<h4 id="_capabilities"><a class="link" href="#_capabilities">Capabilities</a></h4>
+<div class="ulist">
+<ul>
+<li>
+<p>Payment allocation should be configurable for transactions:</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Repayment</p>
+</li>
+<li>
+<p>Goodwill credit</p>
+</li>
+<li>
+<p>Payout refund</p>
+</li>
+<li>
+<p>Merchant refund</p>
+</li>
+<li>
+<p>Charge adjustments</p>
+</li>
+<li>
+<p>etc.</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Can be configured for Loan products</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Payment allocation rule changes on the loan product will affect only the newly created Loan accounts.</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Chronological reprocess order</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Transactions (including disbursements) and charges are (re)processed and allocated in chronological order</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Support re-amortization between future installments</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Transaction amount to be divided into equal portions (based on the number of future installments) and to repay each future installment by the calculated portion.</p>
+<div class="ulist">
+<ul>
+<li>
+<p>It’s not hard coded, but usually the principal portion needs to be allocated first, but if there are still unprocessed amounts, the rest of the outstanding balances are to be allocated based on the rest of the rules</p>
+</li>
+</ul>
+</div>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Main allocation rules (installment level)</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Past Due Installment(s):</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Oldest first</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Due Installment(s):</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Normal installment takes priority over Down-payment installment (if applicable)</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Future Installment(s):</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Available allocation orders:</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Next installment first</p>
+</li>
+<li>
+<p>Last installment first</p>
+</li>
+<li>
+<p>Re-amortization*</p>
+</li>
+</ul>
+</div>
+</li>
+</ul>
+</div>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Secondary allocation rules</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Penalty</p>
+</li>
+<li>
+<p>Fee</p>
+</li>
+<li>
+<p>Interest</p>
+</li>
+<li>
+<p>Principal</p>
+</li>
+</ul>
+</div>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_configuration"><a class="link" href="#_configuration">Configuration</a></h4>
+<div class="paragraph">
+<p>Advanced repayment allocation rules can be configured for the Loan product if "Advanced payment allocation" got selected as repayment strategy.</p>
+</div>
+<div class="paragraph">
+<p>There will be a (always required) “DEFAULT” transaction type configuration which acts as fallback ruleset, if the there are no configured rules for a specific transaction type.</p>
+</div>
+<div class="sect4">
+<h5 id="_new_repayment_strategy"><a class="link" href="#_new_repayment_strategy">New repayment strategy</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p>Name: Advanced payment allocation</p>
+</li>
+<li>
+<p>Code: advanced-payment-allocation-strategy</p>
+</li>
+<li>
+<p>Order: 8</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_allocation_rules"><a class="link" href="#_allocation_rules">Allocation rules</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p>Past due penalty</p>
+</li>
+<li>
+<p>Past due fee</p>
+</li>
+<li>
+<p>Past due principal</p>
+</li>
+<li>
+<p>Past due interest</p>
+</li>
+<li>
+<p>Due penalty</p>
+</li>
+<li>
+<p>Due fee</p>
+</li>
+<li>
+<p>Due principal</p>
+</li>
+<li>
+<p>Due interest</p>
+</li>
+<li>
+<p>In advance penalty</p>
+</li>
+<li>
+<p>In advance fee</p>
+</li>
+<li>
+<p>In advance principal</p>
+</li>
+<li>
+<p>In advance interest</p>
+</li>
+</ul>
+</div>
+<div class="sect5">
+<h6 id="_future_installment_allocation_rules"><a class="link" href="#_future_installment_allocation_rules">Future installment allocation rules:</a></h6>
+<div class="ulist">
+<ul>
+<li>
+<p>Next installment</p>
+</li>
+<li>
+<p>Last installment</p>
+</li>
+<li>
+<p>Re-amortization</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_example_request"><a class="link" href="#_example_request">Example Request</a></h5>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="o">{</span>
+ <span class="o">...</span>
+ <span class="s">"paymentAllocation"</span><span class="o">:</span> <span class="o">[</span>
+ <span class="o">{</span>
+ <span class="s">"transactionType"</span><span class="o">:</span> <span class="s">"DEFAULT"</span><span class="o">,</span>
+ <span class="s">"paymentAllocationOrder"</span><span class="o">:</span> <span class="o">[</span>
+ <span class="o">{</span>
+ <span class="s">"paymentAllocationRule"</span><span class="o">:</span> <span class="s">"DUE_PAST_PENALTY"</span><span class="o">,</span>
+ <span class="s">"order"</span><span class="o">:</span> <span class="mi">1</span>
+ <span class="o">},</span>
+ <span class="o">{</span>
+ <span class="s">"paymentAllocationRule"</span><span class="o">:</span> <span class="s">"DUE_PAST_FEE"</span><span class="o">,</span>
+ <span class="s">"order"</span><span class="o">:</span> <span class="mi">2</span>
+ <span class="o">},</span>
+ <span class="o">{</span>
+ <span class="s">"paymentAllocationRule"</span><span class="o">:</span> <span class="s">"DUE_PAST_INTEREST"</span><span class="o">,</span>
+ <span class="s">"order"</span><span class="o">:</span> <span class="mi">3</span>
+ <span class="o">},</span>
+ <span class="o">...</span>
+ <span class="o">{</span>
+ <span class="s">"paymentAllocationRule"</span><span class="o">:</span> <span class="s">"IN_ADVANCE_INTEREST"</span><span class="o">,</span>
+ <span class="s">"order"</span><span class="o">:</span> <span class="mi">14</span>
+ <span class="o">}</span>
+ <span class="o">],</span>
+ <span class="s">"futureInstallmentAllocationRule"</span><span class="o">:</span> <span class="s">"NEXT_INSTALLMENT"</span>
+ <span class="o">}</span>
+ <span class="o">],</span>
+ <span class="o">...</span>
+<span class="o">}</span></code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>The above request configures the "DEFAULT" allocation rules:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>First the already due penalties to be paid</p>
+</li>
+<li>
+<p>Second the already due fees to be paid</p>
+</li>
+<li>
+<p>Last the future interests to be paid</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>Also for future installments set the allocation rules as</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>First future installment by due date to be paid first</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_high_level_design"><a class="link" href="#_high_level_design">High level design</a></h5>
+<div class="paragraph">
+<p>Flow of advanced payment allocation processing</p>
+</div>
+<div class="imageblock">
+<div class="content">
+<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABHcAAAbICAYAAAABrDiSAAAAAXNSR0IArs4c6QAA1v50RVh0bXhmaWxlACUzQ214ZmlsZSUyMGhvc3QlM0QlMjJhYy5kcmF3LmlvJTIyJTIwbW9kaWZpZWQlM0QlMjIyMDIzLTA4LTIzVDA5JTNBMzQlM0E1MC45NDBaJTIyJTIwYWdlbnQlM0QlMjJNb3ppbGxhJTJGNS4wJTIwKE1hY2ludG9zaCUzQiUyMEludGVsJTIwTWFjJTIwT1MlMjBYJTIwMTBfMTVfNyklMjBBcHBsZVdlYktpdCUyRjUzNy4zNiUyMChLSFRNTCUyQyUyMGxpa2UlMjBHZWNrbyklMjBDaHJvbWUlMkYxMTYuMC4wLjAlMjBTYWZhcmklMkY1MzcuMzYlMjIlMjBldGFnJTNEJTIyUVlpWVQ0Mk9HQmpZbGRZMk1J [...]
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_api_backward_compatibility"><a class="link" href="#_api_backward_compatibility">API Backward Compatibility</a></h3>
+<div class="sect3">
+<h4 id="_overview"><a class="link" href="#_overview">Overview</a></h4>
+<div class="paragraph">
+<p>Apache Fineract enforces API backward compatibility using <a href="https://github.com/docktape/swagger-brake">swagger-brake</a>, an automated tool that compares OpenAPI specifications between the base branch and a pull request to detect breaking changes. This ensures that existing API consumers are not broken when new changes are deployed.</p>
+</div>
+<div class="paragraph">
+<p>The check runs automatically on every pull request via the <code>verify-api-backward-compatibility.yml</code> GitHub Actions workflow.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_how_it_works_2"><a class="link" href="#_how_it_works_2">How It Works</a></h4>
+<div class="paragraph">
+<p>The workflow follows these steps:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p><strong>Generate baseline spec</strong> — Checks out the base branch (e.g. <code>develop</code>) and runs <code>./gradlew :fineract-provider:resolve</code> to generate the current OpenAPI specification.</p>
+</li>
+<li>
+<p><strong>Generate PR spec</strong> — Checks out the PR branch and generates its OpenAPI specification.</p>
+</li>
+<li>
+<p><strong>Sanitize specs</strong> — Patches known issues in the generated specs (e.g. missing <code>schema</code> entries in <code>requestBody</code> content) to prevent false positives.</p>
+</li>
+<li>
+<p><strong>Compare</strong> — Runs <code>checkBreakingChanges</code> via the swagger-brake Gradle plugin to compare old vs new specs.</p>
+</li>
+<li>
+<p><strong>Report</strong> — If breaking changes are found:</p>
+<div class="ulist">
+<ul>
+<li>
+<p>A deduplicated summary table is written to the GitHub Actions Step Summary (visible on the workflow run page).</p>
+</li>
+<li>
+<p>A comment is posted on the PR (when token permissions allow).</p>
+</li>
+<li>
+<p>The full JSON report is archived as a build artifact.</p>
+</li>
+<li>
+<p>The workflow <strong>fails</strong>, blocking the PR.</p>
+</li>
+</ul>
+</div>
+</li>
+</ol>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_breaking_change_rules"><a class="link" href="#_breaking_change_rules">Breaking Change Rules</a></h4>
+<div class="paragraph">
+<p>swagger-brake detects the following categories of breaking changes:</p>
+</div>
+<div class="sect4">
+<h5 id="_endpoint_rules"><a class="link" href="#_endpoint_rules">Endpoint Rules</a></h5>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 25%;">
+<col style="width: 75%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Rule</th>
+<th class="tableblock halign-left valign-top">Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">R001</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A stable (non-beta) API was changed to beta</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">R002</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">An API path was deleted</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect4">
+<h5 id="_request_rules"><a class="link" href="#_request_rules">Request Rules</a></h5>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 25%;">
+<col style="width: 75%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Rule</th>
+<th class="tableblock halign-left valign-top">Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">R003</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A request media type (content type) was removed</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">R004</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A request parameter was deleted</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">R005</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">An enum value was removed from a request parameter</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">R006</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A parameter location changed (e.g. <code>query</code> to <code>header</code>)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">R007</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A parameter was made required</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">R008</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A parameter type was changed</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">R009</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">An attribute was removed from a request body schema</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">R010</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A property type was changed in a request schema</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">R011</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">An enum value was removed from a request body schema</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect4">
+<h5 id="_response_rules"><a class="link" href="#_response_rules">Response Rules</a></h5>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 25%;">
+<col style="width: 75%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Rule</th>
+<th class="tableblock halign-left valign-top">Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">R012</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A response code was deleted</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">R013</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A response media type was removed</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">R014</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">An attribute was removed from a response schema</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">R015</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A property type was changed in a response schema</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">R016</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">An enum value was removed from a response schema</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect4">
+<h5 id="_constraint_rules"><a class="link" href="#_constraint_rules">Constraint Rules</a></h5>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 25%;">
+<col style="width: 75%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Rule</th>
+<th class="tableblock halign-left valign-top">Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">R017</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A request parameter constraint was tightened (covers <code>maxLength</code>, <code>minLength</code>, <code>maximum</code>, <code>minimum</code>, <code>maxItems</code>, <code>minItems</code>, <code>uniqueItems</code>)</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_gradle_configuration"><a class="link" href="#_gradle_configuration">Gradle Configuration</a></h4>
+<div class="paragraph">
+<p>The swagger-brake plugin is configured in <code>fineract-provider/build.gradle</code>:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="groovy"><span class="n">apply</span> <span class="nl">plugin:</span> <span class="s1">'com.docktape.swagger-brake'</span>
+
+<span class="n">swaggerBrake</span> <span class="o">{</span>
+ <span class="n">newApi</span> <span class="o">=</span> <span class="s2">"${project.buildDir}/resources/main/static/fineract.json"</span>
+ <span class="n">oldApi</span> <span class="o">=</span> <span class="n">findProperty</span><span class="o">(</span><span class="s1">'apiBaseline'</span><span class="o">)</span> <span class="o">?:</span> <span class="s2">"${projectDir}/config/swagger/fineract-baseline.json"</span>
+ <span class="n">outputFormats</span> <span class="o">=</span> <span class="o">[</span><span class="s1">'JSON'</span><span class="o">]</span>
+ <span class="n">outputFilePath</span> <span class="o">=</span> <span class="s2">"${project.buildDir}/swagger-brake"</span>
+ <span class="n">deprecatedApiDeletionAllowed</span> <span class="o">=</span> <span class="kc">true</span>
+ <span class="n">strictValidation</span> <span class="o">=</span> <span class="kc">false</span>
+<span class="o">}</span></code></pre>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_configuration_options"><a class="link" href="#_configuration_options">Configuration Options</a></h5>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 28.5714%;">
+<col style="width: 14.2857%;">
+<col style="width: 57.1429%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Option</th>
+<th class="tableblock halign-left valign-top">Default</th>
+<th class="tableblock halign-left valign-top">Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>newApi</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">—</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Path to the new (PR branch) OpenAPI spec. Generated by the <code>resolve</code> task.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>oldApi</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">—</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Path to the baseline OpenAPI spec. Provided via <code>-PapiBaseline</code> in CI, or falls back to a local file.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>outputFormats</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>['STDOUT', 'HTML']</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Report formats. We use <code>['JSON']</code> to avoid STDOUT spam and parse the report programmatically.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>outputFilePath</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>build/swagger-brake</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Directory for generated reports.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>deprecatedApiDeletionAllowed</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>true</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">When <code>true</code>, removing a deprecated endpoint is NOT a breaking change.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>strictValidation</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>true</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">When <code>false</code>, schemas without an explicit <code>type</code> field log a warning instead of failing. Set to <code>false</code> for Fineract because the generated spec has many type-less schemas.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>excludedPaths</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>[]</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">List of path prefixes to skip (e.g. <code>['/v1/smscampaigns', '/v1/internal']</code>). Useful for excluding endpoints undergoing cleanup.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>ignoredBreakingChangeRules</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>[]</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">List of rule codes to suppress entirely (e.g. <code>['R001']</code>).</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>betaApiExtensionName</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>x-beta-api</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Vendor extension name for marking beta APIs. Beta endpoints can be freely modified without triggering violations.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>maxLogSerializationDepth</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>3</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Controls nested object serialization depth in logs (range 1-20). Increase if you see <code>StackOverflowError</code> from circular schema references.</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_running_locally"><a class="link" href="#_running_locally">Running Locally</a></h4>
+<div class="paragraph">
+<p>To run the check locally, you need a baseline spec to compare against:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="bash"><span class="c"># 1. Generate the baseline from develop</span>
+git stash
+git checkout develop
+./gradlew :fineract-provider:resolve <span class="nt">--no-daemon</span>
+<span class="nb">cp </span>fineract-provider/build/resources/main/static/fineract.json /tmp/baseline.json
+git checkout -
+git stash pop
+
+<span class="c"># 2. Generate your current spec and compare</span>
+./gradlew :fineract-provider:checkBreakingChanges <span class="se">\</span>
+ <span class="nt">-PapiBaseline</span><span class="o">=</span><span class="s2">"/tmp/baseline.json"</span> <span class="se">\</span>
+ <span class="nt">--no-daemon</span></code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>The JSON report is written to <code>fineract-provider/build/swagger-brake/</code>.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_handling_breaking_changes"><a class="link" href="#_handling_breaking_changes">Handling Breaking Changes</a></h4>
+<div class="sect4">
+<h5 id="_intentional_breaking_changes"><a class="link" href="#_intentional_breaking_changes">Intentional Breaking Changes</a></h5>
+<div class="paragraph">
+<p>If your PR intentionally introduces a breaking API change (e.g. removing a deprecated field):</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>The workflow will fail and report the violations.</p>
+</li>
+<li>
+<p>Document the breaking change in the PR description with justification.</p>
+</li>
+<li>
+<p>A committer will review and approve the PR with the understanding that the API contract is changing.</p>
+</li>
+</ol>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_excluding_paths_under_cleanup"><a class="link" href="#_excluding_paths_under_cleanup">Excluding Paths Under Cleanup</a></h5>
+<div class="paragraph">
+<p>If you need to fix incorrect API annotations on endpoints that are not yet stable, use <code>excludedPaths</code> to temporarily exclude them from checking:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="groovy"><span class="n">swaggerBrake</span> <span class="o">{</span>
+ <span class="n">excludedPaths</span> <span class="o">=</span> <span class="o">[</span>
+ <span class="s1">'/v1/smscampaigns'</span><span class="o">,</span>
+ <span class="s1">'/v1/email'</span><span class="o">,</span>
+ <span class="o">]</span>
+<span class="o">}</span></code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Path exclusion is <strong>prefix-based</strong> — excluding <code>/v1/smscampaigns</code> will skip all paths starting with that prefix.</p>
+</div>
+<div class="paragraph">
+<p>Remove the exclusion once the cleanup is complete.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_marking_endpoints_as_beta"><a class="link" href="#_marking_endpoints_as_beta">Marking Endpoints as Beta</a></h5>
+<div class="paragraph">
+<p>For endpoints that are experimental or under active development, mark them as beta in the Java code:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="nd">@Operation</span><span class="o">(</span>
+ <span class="n">summary</span> <span class="o">=</span> <span class="s">"..."</span><span class="o">,</span>
+ <span class="n">extensions</span> <span class="o">=</span> <span class="nd">@Extension</span><span class="o">(</span>
+ <span class="n">properties</span> <span class="o">=</span> <span class="nd">@ExtensionProperty</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"x-beta-api"</span><span class="o">,</span> <span class="n">value</span> <span class="o">=</span> <span class="s">"true"</span><span class="o">)</span>
+ <span class="o">)</span>
+<span class="o">)</span></code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Beta endpoints can be freely modified, created, or removed without triggering violations. Promoting a beta endpoint to stable (removing the extension) is also non-breaking. However, demoting a stable endpoint to beta <strong>is</strong> a breaking change (R001).</p>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_report_format"><a class="link" href="#_report_format">Report Format</a></h4>
+<div class="paragraph">
+<p>When breaking changes are detected, the workflow produces a deduplicated summary table:</p>
+</div>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 12.5%;">
+<col style="width: 25%;">
+<col style="width: 12.5%;">
+<col style="width: 37.5%;">
+<col style="width: 12.5%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Rule</th>
+<th class="tableblock halign-left valign-top">Description</th>
+<th class="tableblock halign-left valign-top">Detail</th>
+<th class="tableblock halign-left valign-top">Affected endpoints</th>
+<th class="tableblock halign-left valign-top">Count</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">R014</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Response attribute removed</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>totalOverpaid</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>GET /v1/loans</code>, <code>GET /v1/loans/{loanId}</code>, <code>GET /v1/loans/external-id/{loanExternalId}</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">3</p></td>
+</tr>
+</tbody>
+</table>
+<div class="paragraph">
+<p>The deduplication groups violations by rule code and affected attribute, collapsing multiple endpoint occurrences into a single row. This is important because a single schema change (e.g. removing a field from a shared response type) can generate dozens of raw violations — one per endpoint that uses that schema.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_tool_reference"><a class="link" href="#_tool_reference">Tool Reference</a></h4>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Tool</strong>: <a href="https://github.com/docktape/swagger-brake">swagger-brake</a> v2.7.0</p>
+</li>
+<li>
+<p><strong>Gradle plugin</strong>: <code>com.docktape.swagger-brake</code></p>
+</li>
+<li>
+<p><strong>Documentation</strong>: <a href="https://docktape.github.io/swagger-brake/" class="bare">docktape.github.io/swagger-brake/</a></p>
+</li>
+<li>
+<p><strong>License</strong>: Apache 2.0</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_command_processing"><a class="link" href="#_command_processing">Command Processing</a></h2>
+<div class="sectionbody">
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-note" title="Note"></i>
+</td>
+<td class="content">
+This is an ongoing effort to migrate to an improved <a href="https://martinfowler.com/bliki/CQRS.html[CQRS]">CQRS</a> sub-system. Please read Jira ticket <a href="https://issues.apache.org/jira/browse/FINERACT-2169">FINERACT-2169</a> to see the current progress.
+</td>
+</tr>
+</table>
+</div>
+<div class="sect2">
+<h3 id="_introduction_2"><a class="link" href="#_introduction_2">Introduction</a></h3>
+<div class="paragraph">
+<p>Fineract accumulated some technical debt over the years. One area that is implicated is type-safety of internal and external facing APIs, the most prominent of which is Fineract’s REST API. In general the package layout of the project reflects a more or less classic layered architecture (REST API, data transfer/value objects, business logic services, storage/repositories). The project predates some of the more modern frameworks and best practices that are available today and on [...]
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>the lowest common denominator is the string type (aka JSON blob); this is where we lose the type information</p>
+</li>
+<li>
+<p>the strings are transformed into JSONObjects; a little bit better than raw strings, but barely more than a hash map</p>
+</li>
+<li>
+<p>"magic" strings are needed to get/set values</p>
+</li>
+<li>
+<p>this approach makes refactoring more difficult</p>
+</li>
+<li>
+<p>to be able to serve an OpenAPI descriptor (as JSON and YAML) we had to re-introduce the type information at the REST API level with dummy classes that contain only the specified attributes; those classes are only used with the Swagger annotations and no were else</p>
+</li>
+<li>
+<p>some developers skipped the layered architecture and found it too tedious to maintain DTOs and JSON helper classes, and as a result just passed JSONObjects right to the business logic layer</p>
+</li>
+<li>
+<p>now the business logic is unnecessarily aware of how Fineract communicates to the outside world and makes replacing/enhancing the communication protocol (e.g. with GRPC) pretty much impossible</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>The list doesn’t end here, but in the end things boil down to two main points:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>developer experience: boilerplate code and missing type safety cost more time</p>
+</li>
+<li>
+<p>bugs: the more code the more likely errors get introduced, especially when type safety is missing and we have to rely on runtime errors (vs. compile time).</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>There has been already some preparatory work done concerning type safety, but until now we avoided dealing with the real source of this issue. Fineract’s architectures devises read from write requests (<a href="https://martinfowler.com/bliki/CQRS.html">CQRS</a>) for improved scalability.</p>
+</div>
+<div class="paragraph">
+<p>The read requests are not that problematic and not (yet) covered, but all write requests pass through a component/service that is called <code>SynchronousCommandProcessingService</code>.</p>
+</div>
+<div class="listingblock">
+<div class="title">Synchronous Command Processing Service</div>
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">SynchronousCommandProcessingService</span> <span class="kd">implements</span> <span class="nc">CommandProcessingService</span> <span class="o">{</span>
+
+ <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">IDEMPOTENCY_KEY_STORE_FLAG</span> <span class="o">=</span> <span class="s">"idempotencyKeyStoreFlag"</span><span class="o">;</span>
+
+ <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">IDEMPOTENCY_KEY_ATTRIBUTE</span> <span class="o">=</span> <span class="s">"IdempotencyKeyAttribute"</span><span class="o">;</span>
+ <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">COMMAND_SOURCE_ID</span> <span class="o">=</span> <span class="s">"commandSourceId"</span><span class="o">;</span>
+ <span class="kd">private</span> <span class="kd">final</span> <span class="nc">PlatformSecurityContext</span> <span class="n">context</span><span class="o">;</span>
+ <span class="kd">private</span> <span class="kd">final</span> <span class="nc">ApplicationContext</span> <span class="n">applicationContext</span><span class="o">;</span>
+ <span class="kd">private</span> <span class="kd">final</span> <span class="nc">TransactionBoundApplicationEventPublisher</span> <span class="n">eventPublisher</span><span class="o">;</span>
+ <span class="kd">private</span> <span class="kd">final</span> <span class="nc">ToApiJsonSerializer</span><span class="o"><</span><span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">>></span> <span class="n">toApiJsonSerializer</span><span class="o">;</span>
+ <span class="kd">private</span> <span class="kd">final</span> <span class="nc">ToApiJsonSerializer</span><span class="o"><</span><span class="nc">CommandProcessingResult</span><span class="o">></span> <span class="n">toApiResultJsonSerializer</span><span class="o">;</span>
+ <span class="kd">private</span> <span class="kd">final</span> <span class="nc">ConfigurationDomainService</span> <span class="n">configurationDomainService</span><span class="o">;</span>
+ <span class="kd">private</span> <span class="kd">final</span> <span class="nc">CommandHandlerProvider</span> <span class="n">commandHandlerProvider</span><span class="o">;</span>
+ <span class="kd">private</span> <span class="kd">final</span> <span class="nc">IdempotencyKeyResolver</span> <span class="n">idempotencyKeyResolver</span><span class="o">;</span>
+ <span class="kd">private</span> <span class="kd">final</span> <span class="nc">CommandSourceService</span> <span class="n">commandSourceService</span><span class="o">;</span>
+ <span class="kd">private</span> <span class="kd">final</span> <span class="nc">RetryConfigurationAssembler</span> <span class="n">retryConfigurationAssembler</span><span class="o">;</span>
+
+ <span class="kd">private</span> <span class="kd">final</span> <span class="nc">FineractRequestContextHolder</span> <span class="n">fineractRequestContextHolder</span><span class="o">;</span>
+ <span class="kd">private</span> <span class="kd">final</span> <span class="nc">Gson</span> <span class="n">gson</span> <span class="o">=</span> <span class="nc">GoogleGsonSerializerHelper</span><span class="o">.</span><span class="na">createSimpleGson</span><span class="o">();</span>
+
+ <span class="kd">private</span> <span class="nc">CommandProcessingResult</span> <span class="nf">executeWithRetry</span><span class="o">(</span><span class="nc">CommandExecutionContext</span> <span class="n">executionContext</span><span class="o">)</span> <span class="o">{</span>
+ <span class="k">try</span> <span class="o">{</span>
+ <span class="k">if</span> <span class="o">(!</span><span class="nc">BatchRequestContextHolder</span><span class="o">.</span><span class="na">isEnclosingTransaction</span><span class="o">())</span> <span class="o">{</span>
+ <span class="k">return</span> <span class="n">retryConfigurationAssembler</span><span class="o">.</span><span class="na">getRetryConfigurationForExecuteCommand</span><span class="o">()</span>
+ <span class="o">.</span><span class="na">executeSupplier</span><span class="o">(()</span> <span class="o">-></span> <span class="n">executeCommandAttempt</span><span class="o">(</span><span class="n">executionContext</span><span class="o">));</span>
+ <span class="o">}</span>
+ <span class="k">return</span> <span class="nf">executeCommandAttempt</span><span class="o">(</span><span class="n">executionContext</span><span class="o">);</span>
+ <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">RollbackTransactionNotApprovedException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
+ <span class="k">throw</span> <span class="n">e</span><span class="o">;</span>
+ <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">RuntimeException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
+ <span class="nc">RuntimeException</span> <span class="n">mappable</span> <span class="o">=</span> <span class="nc">ErrorHandler</span><span class="o">.</span><span class="na">getMappable</span><span class="o">(</span><span class="n">e</span><span class="o">);</span>
+ <span class="n">persistFinalErrorResult</span><span class="o">(</span><span class="n">executionContext</span><span class="o">,</span> <span class="n">mappable</span><span class="o">);</span>
+ <span class="k">throw</span> <span class="n">mappable</span><span class="o">;</span>
+ <span class="o">}</span>
+ <span class="o">}</span>
+
+ <span class="nd">@Override</span>
+ <span class="kd">public</span> <span class="nc">CommandProcessingResult</span> <span class="nf">executeCommand</span><span class="o">(</span><span class="kd">final</span> <span class="nc">CommandWrapper</span> <span class="n">wrapper</span><span class="o">,</span> <span class="kd">final</span> <span class="nc">JsonCommand</span> <span class="n">command</span><span class="o">,</span>
+ <span class="kd">final</span> <span class="kt">boolean</span> <span class="n">isApprovedByChecker</span><span class="o">)</span> <span class="o">{</span>
+ <span class="k">return</span> <span class="nf">executeWithRetry</span><span class="o">(</span><span class="k">new</span> <span class="nc">CommandExecutionContext</span><span class="o">(</span><span class="n">wrapper</span><span class="o">,</span> <span class="n">command</span><span class="o">,</span> <span class="n">isApprovedByChecker</span><span class="o">));</span>
+ <span class="o">}</span>
+
+ <span class="kd">private</span> <span class="nc">CommandProcessingResult</span> <span class="nf">executeCommandAttempt</span><span class="o">(</span><span class="nc">CommandExecutionContext</span> <span class="n">executionContext</span><span class="o">)</span> <span class="o">{</span>
+ <span class="c1">// Do not store the idempotency key because of the exception handling</span>
+ <span class="n">setIdempotencyKeyStoreFlag</span><span class="o">(</span><span class="kc">false</span><span class="o">);</span>
+
+ <span class="nc">CommandWrapper</span> <span class="n">wrapper</span> <span class="o">=</span> <span class="n">executionContext</span><span class="o">.</span><span class="na">wrapper</span><span class="o">();</span>
+ <span class="nc">JsonCommand</span> <span class="n">command</span> <span class="o">=</span> <span class="n">executionContext</span><span class="o">.</span><span class="na">command</span><span class="o">();</span>
+ <span class="nc">Long</span> <span class="n">commandId</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Long</span><span class="o">)</span> <span class="n">fineractRequestContextHolder</span><span class="o">.</span><span class="na">getAttribute</span><span class="o">(</span><span class="no">COMMAND_SOURCE_ID</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
+ <span class="kt">boolean</span> <span class="n">isRetry</span> <span class="o">=</span> <span class="n">commandId</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">;</span>
+ <span class="kt">boolean</span> <span class="n">isEnclosingTransaction</span> <span class="o">=</span> <span class="nc">BatchRequestContextHolder</span><span class="o">.</span><span class="na">isEnclosingTransaction</span><span class="o">();</span>
+ <span class="n">executionContext</span><span class="o">.</span><span class="na">setEnclosingTransaction</span><span class="o">(</span><span class="n">isEnclosingTransaction</span><span class="o">);</span>
+
+ <span class="nc">CommandSource</span> <span class="n">commandSource</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
+ <span class="nc">String</span> <span class="n">idempotencyKey</span><span class="o">;</span>
+ <span class="k">if</span> <span class="o">(</span><span class="n">isRetry</span><span class="o">)</span> <span class="o">{</span>
+ <span class="n">commandSource</span> <span class="o">=</span> <span class="n">commandSourceService</span><span class="o">.</span><span class="na">getCommandSource</span><span class="o">(</span><span class="n">commandId</span><span class="o">);</span>
+ <span class="n">idempotencyKey</span> <span class="o">=</span> <span class="n">commandSource</span><span class="o">.</span><span class="na">getIdempotencyKey</span><span class="o">();</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">((</span><span class="n">commandId</span> <span class="o">=</span> <span class="n">command</span><span class="o">.</span><span class="na">commandId</span><span class="o">())</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// action on the command itself</span>
+ <span class="n">commandSource</span> <span class="o">=</span> <span class="n">commandSourceService</span><span class="o">.</span><span class="na">getCommandSource</span><span class="o">(</span><span class="n">commandId</span><span class="o">);</span>
+ <span class="n">idempotencyKey</span> <span class="o">=</span> <span class="n">commandSource</span><span class="o">.</span><span class="na">getIdempotencyKey</span><span class="o">();</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
+ <span class="n">idempotencyKey</span> <span class="o">=</span> <span class="n">idempotencyKeyResolver</span><span class="o">.</span><span class="na">resolve</span><span class="o">(</span><span class="n">wrapper</span><span class="o">);</span>
+ <span class="o">}</span>
+ <span class="n">exceptionWhenTheRequestAlreadyProcessed</span><span class="o">(</span><span class="n">wrapper</span><span class="o">,</span> <span class="n">idempotencyKey</span><span class="o">,</span> <span class="n">isRetry</span><span class="o">);</span>
+
+ <span class="nc">AppUser</span> <span class="n">user</span> <span class="o">=</span> <span class="n">context</span><span class="o">.</span><span class="na">authenticatedUser</span><span class="o">(</span><span class="n">wrapper</span><span class="o">);</span>
+ <span class="k">if</span> <span class="o">(</span><span class="n">commandSource</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
+ <span class="k">if</span> <span class="o">(</span><span class="n">isEnclosingTransaction</span><span class="o">)</span> <span class="o">{</span>
+ <span class="n">commandSource</span> <span class="o">=</span> <span class="n">commandSourceService</span><span class="o">.</span><span class="na">getInitialCommandSource</span><span class="o">(</span><span class="n">wrapper</span><span class="o">,</span> <span class="n">command</span><span class="o">,</span> <span class="n">user</span><span class="o">,</span> <span class="n">idempotencyKey</span><span class="o">);</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
+ <span class="n">commandSource</span> <span class="o">=</span> <span class="n">commandSourceService</span><span class="o">.</span><span class="na">saveInitial</span><span class="o">(</span><span class="n">wrapper</span><span class="o">,</span> <span class="n">command</span><span class="o">,</span> <span class="n">user</span><span class="o">,</span> <span class="n">idempotencyKey</span><span class="o">);</span>
+ <span class="n">commandId</span> <span class="o">=</span> <span class="n">commandSource</span><span class="o">.</span><span class="na">getId</span><span class="o">();</span>
+ <span class="o">}</span>
+ <span class="o">}</span>
+ <span class="n">executionContext</span><span class="o">.</span><span class="na">setCommandSource</span><span class="o">(</span><span class="n">commandSource</span><span class="o">);</span>
+ <span class="k">if</span> <span class="o">(</span><span class="n">commandId</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
+ <span class="n">storeCommandIdInContext</span><span class="o">(</span><span class="n">commandSource</span><span class="o">);</span> <span class="c1">// Store command id as a request attribute</span>
+ <span class="o">}</span>
+
+ <span class="n">setIdempotencyKeyStoreFlag</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>
+
+ <span class="k">return</span> <span class="nf">executeCommandInTransaction</span><span class="o">(</span><span class="n">executionContext</span><span class="o">,</span> <span class="n">commandSource</span><span class="o">,</span> <span class="n">user</span><span class="o">,</span> <span class="n">isEnclosingTransaction</span><span class="o">);</span>
+ <span class="o">}</span>
+
+ <span class="kd">private</span> <span class="nc">CommandProcessingResult</span> <span class="nf">executeCommandInTransaction</span><span class="o">(</span><span class="nc">CommandExecutionContext</span> <span class="n">executionContext</span><span class="o">,</span> <span class="nc">CommandSource</span> <span class="n">commandSource</span><span class="o">,</span>
+ <span class="nc">AppUser</span> <span class="n">user</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">isEnclosingTransaction</span><span class="o">)</span> <span class="o">{</span>
+
+ <span class="kd">final</span> <span class="nc">CommandSourceService</span><span class="o">.</span><span class="na">CommandExecutionResult</span> <span class="n">commandExecutionResult</span><span class="o">;</span>
+ <span class="k">try</span> <span class="o">{</span>
+ <span class="n">commandExecutionResult</span> <span class="o">=</span> <span class="n">commandSourceService</span><span class="o">.</span><span class="na">processCommandAndSaveResult</span><span class="o">(</span><span class="n">findCommandHandler</span><span class="o">(</span><span class="n">executionContext</span><span class="o">.</span><span class="na">wrapper</span><span class="o">()),</span>
+ <span class="n">executionContext</span><span class="o">.</span><span class="na">command</span><span class="o">(),</span> <span class="n">commandSource</span><span class="o">,</span> <span class="n">user</span><span class="o">,</span> <span class="n">executionContext</span><span class="o">.</span><span class="na">approvedByChecker</span><span class="o">(),</span> <span class="k">this</span><span class="o">::</span><span class="n">updateSuccessfulResult</span><span clas [...]
+ <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">RollbackTransactionNotApprovedException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
+ <span class="nc">ErrorInfo</span> <span class="n">errorInfo</span> <span class="o">=</span> <span class="n">commandSourceService</span><span class="o">.</span><span class="na">generateErrorInfo</span><span class="o">(</span><span class="n">e</span><span class="o">);</span>
+ <span class="n">commandSource</span><span class="o">.</span><span class="na">setResultStatusCode</span><span class="o">(</span><span class="n">errorInfo</span><span class="o">.</span><span class="na">getStatusCode</span><span class="o">());</span>
+ <span class="n">commandSource</span><span class="o">.</span><span class="na">setResult</span><span class="o">(</span><span class="n">errorInfo</span><span class="o">.</span><span class="na">getMessage</span><span class="o">());</span>
+ <span class="k">if</span> <span class="o">(!</span><span class="n">isEnclosingTransaction</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// TODO: temporary solution</span>
+ <span class="n">commandSourceService</span><span class="o">.</span><span class="na">saveResult</span><span class="o">(</span><span class="n">commandSource</span><span class="o">);</span>
+ <span class="o">}</span>
+ <span class="n">publishHookErrorEvent</span><span class="o">(</span><span class="n">executionContext</span><span class="o">.</span><span class="na">wrapper</span><span class="o">(),</span> <span class="n">executionContext</span><span class="o">.</span><span class="na">command</span><span class="o">(),</span> <span class="n">errorInfo</span><span class="o">);</span>
+ <span class="k">throw</span> <span class="n">e</span><span class="o">;</span>
+ <span class="o">}</span>
+
+ <span class="nc">CommandProcessingResult</span> <span class="n">result</span> <span class="o">=</span> <span class="n">commandExecutionResult</span><span class="o">.</span><span class="na">result</span><span class="o">();</span>
+ <span class="n">storeCommandIdInContext</span><span class="o">(</span><span class="n">commandExecutionResult</span><span class="o">.</span><span class="na">commandSource</span><span class="o">());</span>
+
+ <span class="n">result</span><span class="o">.</span><span class="na">setRollbackTransaction</span><span class="o">(</span><span class="kc">null</span><span class="o">);</span>
+
+ <span class="c1">// When running inside an enclosing batch transaction, defer hook publication</span>
+ <span class="c1">// until after the transaction commits. This prevents webhooks from firing for</span>
+ <span class="c1">// commands that are subsequently rolled back when a later command in the batch</span>
+ <span class="c1">// fails (e.g. a withdrawal succeeds but its fee charge fails, rolling back both).</span>
+ <span class="k">if</span> <span class="o">(</span><span class="n">isEnclosingTransaction</span> <span class="o">&&</span> <span class="nc">TransactionSynchronizationManager</span><span class="o">.</span><span class="na">isSynchronizationActive</span><span class="o">())</span> <span class="o">{</span>
+ <span class="nc">TransactionSynchronizationManager</span><span class="o">.</span><span class="na">registerSynchronization</span><span class="o">(</span><span class="k">new</span> <span class="nc">TransactionSynchronization</span><span class="o">()</span> <span class="o">{</span>
+
+ <span class="nd">@Override</span>
+ <span class="kd">public</span> <span class="kt">void</span> <span class="nf">afterCommit</span><span class="o">()</span> <span class="o">{</span>
+ <span class="n">publishHookEvent</span><span class="o">(</span><span class="n">executionContext</span><span class="o">.</span><span class="na">wrapper</span><span class="o">().</span><span class="na">entityName</span><span class="o">(),</span> <span class="n">executionContext</span><span class="o">.</span><span class="na">wrapper</span><span class="o">().</span><span class="na">actionName</span><span class="o">(),</span>
+ <span class="n">executionContext</span><span class="o">.</span><span class="na">command</span><span class="o">(),</span> <span class="n">result</span><span class="o">);</span>
+ <span class="o">}</span>
+ <span class="o">});</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
+ <span class="n">publishHookEvent</span><span class="o">(</span><span class="n">executionContext</span><span class="o">.</span><span class="na">wrapper</span><span class="o">().</span><span class="na">entityName</span><span class="o">(),</span> <span class="n">executionContext</span><span class="o">.</span><span class="na">wrapper</span><span class="o">().</span><span class="na">actionName</span><span class="o">(),</span> <span class="n">executionContext</span><span class="o"> [...]
+ <span class="n">result</span><span class="o">);</span>
+ <span class="o">}</span>
+
+ <span class="k">return</span> <span class="n">result</span><span class="o">;</span>
+ <span class="o">}</span>
+
+ <span class="kd">private</span> <span class="kt">void</span> <span class="nf">updateSuccessfulResult</span><span class="o">(</span><span class="nc">CommandSource</span> <span class="n">commandSource</span><span class="o">,</span> <span class="nc">CommandProcessingResult</span> <span class="n">result</span><span class="o">)</span> <span class="o">{</span>
+ <span class="n">commandSource</span><span class="o">.</span><span class="na">setResultStatusCode</span><span class="o">(</span><span class="no">SC_OK</span><span class="o">);</span>
+ <span class="n">commandSource</span><span class="o">.</span><span class="na">updateForAudit</span><span class="o">(</span><span class="n">result</span><span class="o">);</span>
+ <span class="n">commandSource</span><span class="o">.</span><span class="na">setResult</span><span class="o">(</span><span class="n">toApiResultJsonSerializer</span><span class="o">.</span><span class="na">serializeResult</span><span class="o">(</span><span class="n">result</span><span class="o">));</span>
+ <span class="n">commandSource</span><span class="o">.</span><span class="na">setStatus</span><span class="o">(</span><span class="no">PROCESSED</span><span class="o">);</span>
+ <span class="o">}</span>
+
+ <span class="kd">private</span> <span class="kt">void</span> <span class="nf">persistFinalErrorResult</span><span class="o">(</span><span class="nc">CommandExecutionContext</span> <span class="n">executionContext</span><span class="o">,</span> <span class="nc">RuntimeException</span> <span class="n">mappable</span><span class="o">)</span> <span class="o">{</span>
+ <span class="nc">CommandSource</span> <span class="n">commandSource</span> <span class="o">=</span> <span class="n">executionContext</span><span class="o">.</span><span class="na">commandSource</span><span class="o">();</span>
+ <span class="k">if</span> <span class="o">(</span><span class="n">commandSource</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
+ <span class="k">return</span><span class="o">;</span>
+ <span class="o">}</span>
+ <span class="nc">ErrorInfo</span> <span class="n">errorInfo</span> <span class="o">=</span> <span class="n">commandSourceService</span><span class="o">.</span><span class="na">generateErrorInfo</span><span class="o">(</span><span class="n">mappable</span><span class="o">);</span>
+ <span class="n">publishHookErrorEvent</span><span class="o">(</span><span class="n">executionContext</span><span class="o">.</span><span class="na">wrapper</span><span class="o">(),</span> <span class="n">executionContext</span><span class="o">.</span><span class="na">command</span><span class="o">(),</span> <span class="n">errorInfo</span><span class="o">);</span>
+ <span class="k">if</span> <span class="o">(</span><span class="n">executionContext</span><span class="o">.</span><span class="na">enclosingTransaction</span><span class="o">())</span> <span class="o">{</span>
+ <span class="k">return</span><span class="o">;</span>
+ <span class="o">}</span>
+
+ <span class="nc">Integer</span> <span class="n">statusCode</span> <span class="o">=</span> <span class="n">errorInfo</span><span class="o">.</span><span class="na">getStatusCode</span><span class="o">();</span>
+ <span class="n">commandSource</span><span class="o">.</span><span class="na">setResultStatusCode</span><span class="o">(</span><span class="n">statusCode</span><span class="o">);</span>
+ <span class="n">commandSource</span><span class="o">.</span><span class="na">setResult</span><span class="o">(</span><span class="n">errorInfo</span><span class="o">.</span><span class="na">getMessage</span><span class="o">());</span>
+ <span class="k">if</span> <span class="o">(</span><span class="n">statusCode</span> <span class="o">!=</span> <span class="no">SC_OK</span><span class="o">)</span> <span class="o">{</span>
+ <span class="n">commandSource</span><span class="o">.</span><span class="na">setStatus</span><span class="o">(</span><span class="no">ERROR</span><span class="o">);</span>
+ <span class="o">}</span>
+ <span class="n">commandSourceService</span><span class="o">.</span><span class="na">saveResult</span><span class="o">(</span><span class="n">commandSource</span><span class="o">);</span>
+ <span class="o">}</span>
+
+ <span class="kd">private</span> <span class="kt">void</span> <span class="nf">storeCommandIdInContext</span><span class="o">(</span><span class="nc">CommandSource</span> <span class="n">savedCommandSource</span><span class="o">)</span> <span class="o">{</span>
+ <span class="k">if</span> <span class="o">(</span><span class="n">savedCommandSource</span><span class="o">.</span><span class="na">getId</span><span class="o">()</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
+ <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="s">"Command source not saved"</span><span class="o">);</span>
+ <span class="o">}</span>
+ <span class="c1">// Idempotency filters and retry need this</span>
+ <span class="n">fineractRequestContextHolder</span><span class="o">.</span><span class="na">setAttribute</span><span class="o">(</span><span class="no">COMMAND_SOURCE_ID</span><span class="o">,</span> <span class="n">savedCommandSource</span><span class="o">.</span><span class="na">getId</span><span class="o">());</span>
+ <span class="o">}</span>
+
+ <span class="kd">private</span> <span class="kt">void</span> <span class="nf">publishHookErrorEvent</span><span class="o">(</span><span class="nc">CommandWrapper</span> <span class="n">wrapper</span><span class="o">,</span> <span class="nc">JsonCommand</span> <span class="n">command</span><span class="o">,</span> <span class="nc">ErrorInfo</span> <span class="n">errorInfo</span><span class="o">)</span> <span class="o">{</span>
+ <span class="k">try</span> <span class="o">{</span>
+ <span class="n">publishHookEvent</span><span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">entityName</span><span class="o">(),</span> <span class="n">wrapper</span><span class="o">.</span><span class="na">actionName</span><span class="o">(),</span> <span class="n">command</span><span class="o">,</span> <span class="n">gson</span><span class="o">.</span><span class="na">toJson</span><span class="o">(</span><span class="n">errorInfo</ [...]
+ <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
+ <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"Failed to publish hook error event for entity: {}, action: {}"</span><span class="o">,</span> <span class="n">wrapper</span><span class="o">.</span><span class="na">entityName</span><span class="o">(),</span> <span class="n">wrapper</span><span class="o">.</span><span class="na">actionName</span><span class="o">(),</span> <span class="n">e</span><span class= [...]
+ <span class="o">}</span>
+ <span class="o">}</span>
+
+ <span class="kd">private</span> <span class="kt">void</span> <span class="nf">exceptionWhenTheRequestAlreadyProcessed</span><span class="o">(</span><span class="nc">CommandWrapper</span> <span class="n">wrapper</span><span class="o">,</span> <span class="nc">String</span> <span class="n">idempotencyKey</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">retry</span><span class="o">)</span> <span class="o">{</span>
+ <span class="nc">CommandSource</span> <span class="n">command</span> <span class="o">=</span> <span class="n">commandSourceService</span><span class="o">.</span><span class="na">findCommandSource</span><span class="o">(</span><span class="n">wrapper</span><span class="o">,</span> <span class="n">idempotencyKey</span><span class="o">);</span>
+ <span class="k">if</span> <span class="o">(</span><span class="n">command</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
+ <span class="k">return</span><span class="o">;</span>
+ <span class="o">}</span>
+ <span class="nc">CommandProcessingResultType</span> <span class="n">status</span> <span class="o">=</span> <span class="nc">CommandProcessingResultType</span><span class="o">.</span><span class="na">fromInt</span><span class="o">(</span><span class="n">command</span><span class="o">.</span><span class="na">getStatus</span><span class="o">());</span>
+ <span class="k">switch</span> <span class="o">(</span><span class="n">status</span><span class="o">)</span> <span class="o">{</span>
+ <span class="k">case</span> <span class="no">UNDER_PROCESSING</span> <span class="o">-></span> <span class="o">{</span>
+ <span class="nc">Class</span><span class="o"><?></span> <span class="n">lastExecutionExceptionClass</span> <span class="o">=</span> <span class="n">retryConfigurationAssembler</span><span class="o">.</span><span class="na">getLastException</span><span class="o">();</span>
+ <span class="k">if</span> <span class="o">(</span><span class="n">lastExecutionExceptionClass</span> <span class="o">==</span> <span class="kc">null</span>
+ <span class="o">||</span> <span class="nc">IdempotentCommandProcessUnderProcessingException</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">isAssignableFrom</span><span class="o">(</span><span class="n">lastExecutionExceptionClass</span><span class="o">))</span> <span class="o">{</span>
+ <span class="k">throw</span> <span class="k">new</span> <span class="nf">IdempotentCommandProcessUnderProcessingException</span><span class="o">(</span><span class="n">wrapper</span><span class="o">,</span> <span class="n">idempotencyKey</span><span class="o">);</span>
+ <span class="o">}</span>
+ <span class="o">}</span>
+ <span class="k">case</span> <span class="no">PROCESSED</span> <span class="o">-></span> <span class="k">throw</span> <span class="k">new</span> <span class="nc">IdempotentCommandProcessSucceedException</span><span class="o">(</span><span class="n">wrapper</span><span class="o">,</span> <span class="n">idempotencyKey</span><span class="o">,</span> <span class="n">command</span><span class="o">);</span>
+ <span class="k">case</span> <span class="no">ERROR</span> <span class="o">-></span> <span class="o">{</span>
+ <span class="k">if</span> <span class="o">(!</span><span class="n">retry</span><span class="o">)</span> <span class="o">{</span>
+ <span class="k">throw</span> <span class="k">new</span> <span class="nf">IdempotentCommandProcessFailedException</span><span class="o">(</span><span class="n">wrapper</span><span class="o">,</span> <span class="n">idempotencyKey</span><span class="o">,</span> <span class="n">command</span><span class="o">);</span>
+ <span class="o">}</span>
+ <span class="o">}</span>
+ <span class="k">default</span> <span class="o">-></span> <span class="o">{</span>
+ <span class="o">}</span>
+ <span class="o">}</span>
+ <span class="o">}</span>
+
+ <span class="kd">private</span> <span class="kt">void</span> <span class="nf">setIdempotencyKeyStoreFlag</span><span class="o">(</span><span class="kt">boolean</span> <span class="n">flag</span><span class="o">)</span> <span class="o">{</span>
+ <span class="n">fineractRequestContextHolder</span><span class="o">.</span><span class="na">setAttribute</span><span class="o">(</span><span class="no">IDEMPOTENCY_KEY_STORE_FLAG</span><span class="o">,</span> <span class="n">flag</span><span class="o">);</span>
+ <span class="o">}</span>
+
+ <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">CommandExecutionContext</span> <span class="o">{</span>
+
+ <span class="kd">private</span> <span class="kd">final</span> <span class="nc">CommandWrapper</span> <span class="n">wrapper</span><span class="o">;</span>
+ <span class="kd">private</span> <span class="kd">final</span> <span class="nc">JsonCommand</span> <span class="n">command</span><span class="o">;</span>
+ <span class="kd">private</span> <span class="kd">final</span> <span class="kt">boolean</span> <span class="n">approvedByChecker</span><span class="o">;</span>
+ <span class="kd">private</span> <span class="nc">CommandSource</span> <span class="n">commandSource</span><span class="o">;</span>
+ <span class="kd">private</span> <span class="kt">boolean</span> <span class="n">enclosingTransaction</span><span class="o">;</span>
+
+ <span class="kd">private</span> <span class="nf">CommandExecutionContext</span><span class="o">(</span><span class="nc">CommandWrapper</span> <span class="n">wrapper</span><span class="o">,</span> <span class="nc">JsonCommand</span> <span class="n">command</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">approvedByChecker</span><span class="o">)</span> <span class="o">{</span>
+ <span class="k">this</span><span class="o">.</span><span class="na">wrapper</span> <span class="o">=</span> <span class="n">wrapper</span><span class="o">;</span>
+ <span class="k">this</span><span class="o">.</span><span class="na">command</span> <span class="o">=</span> <span class="n">command</span><span class="o">;</span>
+ <span class="k">this</span><span class="o">.</span><span class="na">approvedByChecker</span> <span class="o">=</span> <span class="n">approvedByChecker</span><span class="o">;</span>
+ <span class="o">}</span>
+
+ <span class="nc">CommandWrapper</span> <span class="nf">wrapper</span><span class="o">()</span> <span class="o">{</span>
+ <span class="k">return</span> <span class="n">wrapper</span><span class="o">;</span>
+ <span class="o">}</span>
+
+ <span class="nc">JsonCommand</span> <span class="nf">command</span><span class="o">()</span> <span class="o">{</span>
+ <span class="k">return</span> <span class="n">command</span><span class="o">;</span>
+ <span class="o">}</span>
+
+ <span class="kt">boolean</span> <span class="nf">approvedByChecker</span><span class="o">()</span> <span class="o">{</span>
+ <span class="k">return</span> <span class="n">approvedByChecker</span><span class="o">;</span>
+ <span class="o">}</span>
+
+ <span class="nc">CommandSource</span> <span class="nf">commandSource</span><span class="o">()</span> <span class="o">{</span>
+ <span class="k">return</span> <span class="n">commandSource</span><span class="o">;</span>
+ <span class="o">}</span>
+
+ <span class="kt">void</span> <span class="nf">setCommandSource</span><span class="o">(</span><span class="nc">CommandSource</span> <span class="n">commandSource</span><span class="o">)</span> <span class="o">{</span>
+ <span class="k">this</span><span class="o">.</span><span class="na">commandSource</span> <span class="o">=</span> <span class="n">commandSource</span><span class="o">;</span>
+ <span class="o">}</span>
+
+ <span class="kt">boolean</span> <span class="nf">enclosingTransaction</span><span class="o">()</span> <span class="o">{</span>
+ <span class="k">return</span> <span class="n">enclosingTransaction</span><span class="o">;</span>
+ <span class="o">}</span>
+
+ <span class="kt">void</span> <span class="nf">setEnclosingTransaction</span><span class="o">(</span><span class="kt">boolean</span> <span class="n">enclosingTransaction</span><span class="o">)</span> <span class="o">{</span>
+ <span class="k">this</span><span class="o">.</span><span class="na">enclosingTransaction</span> <span class="o">=</span> <span class="n">enclosingTransaction</span><span class="o">;</span>
+ <span class="o">}</span>
+ <span class="o">}</span>
+
+ <span class="kd">private</span> <span class="nc">NewCommandSourceHandler</span> <span class="nf">findCommandHandler</span><span class="o">(</span><span class="kd">final</span> <span class="nc">CommandWrapper</span> <span class="n">wrapper</span><span class="o">)</span> <span class="o">{</span>
+ <span class="nc">NewCommandSourceHandler</span> <span class="n">handler</span><span class="o">;</span>
+
+ <span class="k">if</span> <span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">isDatatableResource</span><span class="o">())</span> <span class="o">{</span>
+ <span class="k">if</span> <span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">isCreateDatatable</span><span class="o">())</span> <span class="o">{</span>
+ <span class="n">handler</span> <span class="o">=</span> <span class="n">applicationContext</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="s">"createDatatableCommandHandler"</span><span class="o">,</span> <span class="nc">NewCommandSourceHandler</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">isDeleteDatatable</span><span class="o">())</span> <span class="o">{</span>
+ <span class="n">handler</span> <span class="o">=</span> <span class="n">applicationContext</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="s">"deleteDatatableCommandHandler"</span><span class="o">,</span> <span class="nc">NewCommandSourceHandler</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">isUpdateDatatable</span><span class="o">())</span> <span class="o">{</span>
+ <span class="n">handler</span> <span class="o">=</span> <span class="n">applicationContext</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="s">"updateDatatableCommandHandler"</span><span class="o">,</span> <span class="nc">NewCommandSourceHandler</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">isCreate</span><span class="o">())</span> <span class="o">{</span>
+ <span class="n">handler</span> <span class="o">=</span> <span class="n">applicationContext</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="s">"createDatatableEntryCommandHandler"</span><span class="o">,</span> <span class="nc">NewCommandSourceHandler</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">isUpdateMultiple</span><span class="o">())</span> <span class="o">{</span>
+ <span class="n">handler</span> <span class="o">=</span> <span class="n">applicationContext</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="s">"updateOneToManyDatatableEntryCommandHandler"</span><span class="o">,</span> <span class="nc">NewCommandSourceHandler</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">isUpdateOneToOne</span><span class="o">())</span> <span class="o">{</span>
+ <span class="n">handler</span> <span class="o">=</span> <span class="n">applicationContext</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="s">"updateOneToOneDatatableEntryCommandHandler"</span><span class="o">,</span> <span class="nc">NewCommandSourceHandler</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">isDeleteMultiple</span><span class="o">())</span> <span class="o">{</span>
+ <span class="n">handler</span> <span class="o">=</span> <span class="n">applicationContext</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="s">"deleteOneToManyDatatableEntryCommandHandler"</span><span class="o">,</span> <span class="nc">NewCommandSourceHandler</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">isDeleteOneToOne</span><span class="o">())</span> <span class="o">{</span>
+ <span class="n">handler</span> <span class="o">=</span> <span class="n">applicationContext</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="s">"deleteOneToOneDatatableEntryCommandHandler"</span><span class="o">,</span> <span class="nc">NewCommandSourceHandler</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">isRegisterDatatable</span><span class="o">())</span> <span class="o">{</span>
+ <span class="n">handler</span> <span class="o">=</span> <span class="n">applicationContext</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="s">"registerDatatableCommandHandler"</span><span class="o">,</span> <span class="nc">NewCommandSourceHandler</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
+ <span class="k">throw</span> <span class="k">new</span> <span class="nf">UnsupportedCommandException</span><span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">commandName</span><span class="o">());</span>
+ <span class="o">}</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">isNoteResource</span><span class="o">())</span> <span class="o">{</span>
+ <span class="k">if</span> <span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">isCreate</span><span class="o">())</span> <span class="o">{</span>
+ <span class="n">handler</span> <span class="o">=</span> <span class="n">applicationContext</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="s">"createNoteCommandHandler"</span><span class="o">,</span> <span class="nc">NewCommandSourceHandler</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">isUpdate</span><span class="o">())</span> <span class="o">{</span>
+ <span class="n">handler</span> <span class="o">=</span> <span class="n">applicationContext</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="s">"updateNoteCommandHandler"</span><span class="o">,</span> <span class="nc">NewCommandSourceHandler</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">isDelete</span><span class="o">())</span> <span class="o">{</span>
+ <span class="n">handler</span> <span class="o">=</span> <span class="n">applicationContext</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="s">"deleteNoteCommandHandler"</span><span class="o">,</span> <span class="nc">NewCommandSourceHandler</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
+ <span class="k">throw</span> <span class="k">new</span> <span class="nf">UnsupportedCommandException</span><span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">commandName</span><span class="o">());</span>
+ <span class="o">}</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">isSurveyResource</span><span class="o">())</span> <span class="o">{</span>
+ <span class="k">if</span> <span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">isRegisterSurvey</span><span class="o">())</span> <span class="o">{</span>
+ <span class="n">handler</span> <span class="o">=</span> <span class="n">applicationContext</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="s">"registerSurveyCommandHandler"</span><span class="o">,</span> <span class="nc">NewCommandSourceHandler</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">isFullFilSurvey</span><span class="o">())</span> <span class="o">{</span>
+ <span class="n">handler</span> <span class="o">=</span> <span class="n">applicationContext</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="s">"fullFilSurveyCommandHandler"</span><span class="o">,</span> <span class="nc">NewCommandSourceHandler</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
+ <span class="k">throw</span> <span class="k">new</span> <span class="nf">UnsupportedCommandException</span><span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">commandName</span><span class="o">());</span>
+ <span class="o">}</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">isLoanDisburseDetailResource</span><span class="o">())</span> <span class="o">{</span>
+ <span class="k">if</span> <span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">isUpdateDisbursementDate</span><span class="o">())</span> <span class="o">{</span>
+ <span class="n">handler</span> <span class="o">=</span> <span class="n">applicationContext</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="s">"updateLoanDisburseDateCommandHandler"</span><span class="o">,</span> <span class="nc">NewCommandSourceHandler</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">addAndDeleteDisbursementDetails</span><span class="o">())</span> <span class="o">{</span>
+ <span class="n">handler</span> <span class="o">=</span> <span class="n">applicationContext</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="s">"addAndDeleteLoanDisburseDetailsCommandHandler"</span><span class="o">,</span> <span class="nc">NewCommandSourceHandler</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
+ <span class="k">throw</span> <span class="k">new</span> <span class="nf">UnsupportedCommandException</span><span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">commandName</span><span class="o">());</span>
+ <span class="o">}</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">isInterestPauseResource</span><span class="o">())</span> <span class="o">{</span>
+ <span class="k">if</span> <span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">isInterestPauseCreateResource</span><span class="o">())</span> <span class="o">{</span>
+ <span class="n">handler</span> <span class="o">=</span> <span class="n">applicationContext</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="s">"createInterestPauseCommandHandler"</span><span class="o">,</span> <span class="nc">NewCommandSourceHandler</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">isInterestPauseUpdateResource</span><span class="o">())</span> <span class="o">{</span>
+ <span class="n">handler</span> <span class="o">=</span> <span class="n">applicationContext</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="s">"updateInterestPauseCommandHandler"</span><span class="o">,</span> <span class="nc">NewCommandSourceHandler</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">isInterestPauseDeleteResource</span><span class="o">())</span> <span class="o">{</span>
+ <span class="n">handler</span> <span class="o">=</span> <span class="n">applicationContext</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="s">"deleteInterestPauseCommandHandler"</span><span class="o">,</span> <span class="nc">NewCommandSourceHandler</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
+ <span class="k">throw</span> <span class="k">new</span> <span class="nf">UnsupportedCommandException</span><span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">commandName</span><span class="o">());</span>
+ <span class="o">}</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
+ <span class="n">handler</span> <span class="o">=</span> <span class="n">commandHandlerProvider</span><span class="o">.</span><span class="na">getHandler</span><span class="o">(</span><span class="n">wrapper</span><span class="o">.</span><span class="na">entityName</span><span class="o">(),</span> <span class="n">wrapper</span><span class="o">.</span><span class="na">actionName</span><span class="o">());</span>
+ <span class="o">}</span>
+
+ <span class="k">return</span> <span class="n">handler</span><span class="o">;</span>
+ <span class="o">}</span>
+
+ <span class="nd">@Override</span>
+ <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">validateRollbackCommand</span><span class="o">(</span><span class="kd">final</span> <span class="nc">CommandWrapper</span> <span class="n">commandWrapper</span><span class="o">,</span> <span class="kd">final</span> <span class="nc">AppUser</span> <span class="n">user</span><span class="o">)</span> <span class="o">{</span>
+ <span class="n">user</span><span class="o">.</span><span class="na">validateHasPermissionTo</span><span class="o">(</span><span class="n">commandWrapper</span><span class="o">.</span><span class="na">getTaskPermissionName</span><span class="o">());</span>
+ <span class="kt">boolean</span> <span class="n">isMakerChecker</span> <span class="o">=</span> <span class="n">configurationDomainService</span><span class="o">.</span><span class="na">isMakerCheckerEnabledForTask</span><span class="o">(</span><span class="n">commandWrapper</span><span class="o">.</span><span class="na">taskPermissionName</span><span class="o">());</span>
+ <span class="k">return</span> <span class="n">isMakerChecker</span> <span class="o">&&</span> <span class="o">!</span><span class="n">user</span><span class="o">.</span><span class="na">isCheckerSuperUser</span><span class="o">();</span>
+ <span class="o">}</span>
+
+ <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">publishHookEvent</span><span class="o">(</span><span class="kd">final</span> <span class="nc">String</span> <span class="n">entityName</span><span class="o">,</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">actionName</span><span class="o">,</span> <span class="nc">JsonCommand</span> <span class="n">command</span><span class="o">,</span> <span class="kd">final</span> <s [...]
+ <span class="k">try</span> <span class="o">{</span>
+ <span class="kd">final</span> <span class="nc">AppUser</span> <span class="n">appUser</span> <span class="o">=</span> <span class="n">context</span><span class="o">.</span><span class="na">authenticatedUser</span><span class="o">(</span><span class="nc">CommandWrapper</span><span class="o">.</span><span class="na">wrap</span><span class="o">(</span><span class="n">actionName</span><span class="o">,</span> <span class="n">entityName</span><span class="o">,</span> <span class=" [...]
+
+ <span class="kd">final</span> <span class="nc">HookEventSource</span> <span class="n">hookEventSource</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HookEventSource</span><span class="o">(</span><span class="n">entityName</span><span class="o">,</span> <span class="n">actionName</span><span class="o">);</span>
+
+ <span class="c1">// TODO: Add support for publishing array events</span>
+ <span class="k">if</span> <span class="o">(</span><span class="n">command</span><span class="o">.</span><span class="na">json</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
+ <span class="nc">Type</span> <span class="n">type</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">TypeToken</span><span class="o"><</span><span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">>>()</span> <span class="o">{</span>
+
+ <span class="o">}.</span><span class="na">getType</span><span class="o">();</span>
+
+ <span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">></span> <span class="n">myMap</span><span class="o">;</span>
+
+ <span class="k">try</span> <span class="o">{</span>
+ <span class="n">myMap</span> <span class="o">=</span> <span class="n">gson</span><span class="o">.</span><span class="na">fromJson</span><span class="o">(</span><span class="n">command</span><span class="o">.</span><span class="na">json</span><span class="o">(),</span> <span class="n">type</span><span class="o">);</span>
+ <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
+ <span class="k">throw</span> <span class="k">new</span> <span class="nf">PlatformApiDataValidationException</span><span class="o">(</span><span class="s">"error.msg.invalid.json"</span><span class="o">,</span> <span class="s">"The provided JSON is invalid."</span><span class="o">,</span>
+ <span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><>(),</span> <span class="n">e</span><span class="o">);</span>
+ <span class="o">}</span>
+
+ <span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">></span> <span class="n">reqmap</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o"><>();</span>
+ <span class="n">reqmap</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"entityName"</span><span class="o">,</span> <span class="n">entityName</span><span class="o">);</span>
+ <span class="n">reqmap</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"actionName"</span><span class="o">,</span> <span class="n">actionName</span><span class="o">);</span>
+ <span class="n">reqmap</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"createdBy"</span><span class="o">,</span> <span class="n">context</span><span class="o">.</span><span class="na">authenticatedUser</span><span class="o">().</span><span class="na">getId</span><span class="o">());</span>
+ <span class="n">reqmap</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"createdByName"</span><span class="o">,</span> <span class="n">context</span><span class="o">.</span><span class="na">authenticatedUser</span><span class="o">().</span><span class="na">getUsername</span><span class="o">());</span>
+ <span class="n">reqmap</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"createdByFullName"</span><span class="o">,</span> <span class="n">context</span><span class="o">.</span><span class="na">authenticatedUser</span><span class="o">().</span><span class="na">getDisplayName</span><span class="o">());</span>
+
+ <span class="n">reqmap</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"request"</span><span class="o">,</span> <span class="n">myMap</span><span class="o">);</span>
+ <span class="k">if</span> <span class="o">(</span><span class="n">result</span> <span class="k">instanceof</span> <span class="nc">CommandProcessingResult</span><span class="o">)</span> <span class="o">{</span>
+ <span class="nc">CommandProcessingResult</span> <span class="n">resultCopy</span> <span class="o">=</span> <span class="nc">CommandProcessingResult</span>
+ <span class="o">.</span><span class="na">fromCommandProcessingResult</span><span class="o">((</span><span class="nc">CommandProcessingResult</span><span class="o">)</span> <span class="n">result</span><span class="o">);</span>
+
+ <span class="n">reqmap</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"officeId"</span><span class="o">,</span> <span class="n">resultCopy</span><span class="o">.</span><span class="na">getOfficeId</span><span class="o">());</span>
+ <span class="n">reqmap</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"clientId"</span><span class="o">,</span> <span class="n">resultCopy</span><span class="o">.</span><span class="na">getClientId</span><span class="o">());</span>
+ <span class="n">resultCopy</span><span class="o">.</span><span class="na">setOfficeId</span><span class="o">(</span><span class="kc">null</span><span class="o">);</span>
+ <span class="n">reqmap</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"response"</span><span class="o">,</span> <span class="n">resultCopy</span><span class="o">);</span>
+ <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">result</span> <span class="k">instanceof</span> <span class="nc">ErrorInfo</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span>
+ <span class="n">reqmap</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"status"</span><span class="o">,</span> <span class="s">"Exception"</span><span class="o">);</span>
+
+ <span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">></span> <span class="n">errorMap</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o"><>();</span>
+
+ <span class="k">try</span> <span class="o">{</span>
+ <span class="n">errorMap</span> <span class="o">=</span> <span class="n">gson</span><span class="o">.</span><span class="na">fromJson</span><span class="o">(</span><span class="n">ex</span><span class="o">.</span><span class="na">getMessage</span><span class="o">(),</span> <span class="n">type</span><span class="o">);</span>
+ <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
+ <span class="n">errorMap</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"errorMessage"</span><span class="o">,</span> <span class="n">ex</span><span class="o">.</span><span class="na">getMessage</span><span class="o">());</span>
+ <span class="o">}</span>
+
+ <span class="n">errorMap</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"errorCode"</span><span class="o">,</span> <span class="n">ex</span><span class="o">.</span><span class="na">getErrorCode</span><span class="o">());</span>
+ <span class="n">errorMap</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"statusCode"</span><span class="o">,</span> <span class="n">ex</span><span class="o">.</span><span class="na">getStatusCode</span><span class="o">());</span>
+
+ <span class="n">reqmap</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"response"</span><span class="o">,</span> <span class="n">errorMap</span><span class="o">);</span>
+ <span class="o">}</span>
+
+ <span class="n">reqmap</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"timestamp"</span><span class="o">,</span> <span class="nc">Instant</span><span class="o">.</span><span class="na">now</span><span class="o">().</span><span class="na">toString</span><span class="o">());</span>
+
+ <span class="kd">final</span> <span class="nc">String</span> <span class="n">serializedResult</span> <span class="o">=</span> <span class="n">toApiJsonSerializer</span><span class="o">.</span><span class="na">serialize</span><span class="o">(</span><span class="n">reqmap</span><span class="o">);</span>
+
+ <span class="kd">final</span> <span class="nc">HookEvent</span> <span class="n">applicationEvent</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HookEvent</span><span class="o">(</span><span class="n">hookEventSource</span><span class="o">,</span> <span class="n">serializedResult</span><span class="o">,</span> <span class="n">appUser</span><span class="o">,</span>
+ <span class="nc">ThreadLocalContextUtil</span><span class="o">.</span><span class="na">getContext</span><span class="o">());</span>
+
+ <span class="n">eventPublisher</span><span class="o">.</span><span class="na">publishEvent</span><span class="o">(</span><span class="n">applicationEvent</span><span class="o">);</span>
+ <span class="o">}</span>
+ <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
+ <span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"Failed to publish hook event for entity: {}, action: {}"</span><span class="o">,</span> <span class="n">entityName</span><span class="o">,</span> <span class="n">actionName</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>
+ <span class="o">}</span>
+ <span class="o">}</span>
+<span class="o">}</span></code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>As the name suggests the execution of business logic is synchronous (mostly) due to this part of the architecture. This is not necessarily a problem (not immediately at least), but it’s nevertheless a central bottleneck in the system. Even more important: this service is responsible to route incoming commands to their respective handler classes which in turn execute functions on one or more business logic services. The payload of these commands are obviously not always the same& [...]
+</div>
+<div class="paragraph">
+<p>Over the years we’ve also added additional features (e.g. idempotency guarantees for incoming write requests) that make it now very hard to reason about the execution flow. Testing the performance impact of such additions to the critical execution path even can’t be properly measured. Note: the current implementation of idempotency relies on database lookups (quite often, for each incoming request) and none of those queries are cached. If we wanted to store already process [...]
+</div>
+</div>
+<div class="sect2">
+<h3 id="_architecture_3"><a class="link" href="#_architecture_3">Architecture</a></h3>
+<div class="paragraph">
+<p>This chapter outlines the new Command Query Responsibility Segregation (CQRS) implementation introduced in Apache Fineract. This architecture aims to improve the separation of concerns, testability, and maintainability of the write-side (command) operations.</p>
+</div>
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-note" title="Note"></i>
+</td>
+<td class="content">
+A proposal to cover the read-side (query) is in preparation.
+</td>
+</tr>
+</table>
+</div>
+<div class="paragraph">
+<p>By shifting from our legacy type-less, synchronous command processing to an improved and extensible command-driven flow, we establish a robust pipeline for handling business operations, cross-cutting concerns (via hooks), and error handling (retries, fallbacks).</p>
+</div>
+<div class="sect3">
+<h4 id="_core_components"><a class="link" href="#_core_components">Core Components</a></h4>
+<div class="ulist">
+<ul>
+<li>
+<p>command dispatcher: the entry point for all command operations</p>
+</li>
+<li>
+<p>command hanlder: acts as the bridge between the incoming command and the specific business logic required to handle it</p>
+</li>
+<li>
+<p>command hooks: interception points to execute cross-cutting logic without polluting the dispatcher or the handler; there are three types of hooks</p>
+<div class="ulist">
+<ul>
+<li>
+<p>before command hook</p>
+</li>
+<li>
+<p>after command hook</p>
+</li>
+<li>
+<p>error command hook</p>
+</li>
+</ul>
+</div>
+</li>
+</ul>
+</div>
+<div class="imageblock">
+<div class="content">
+<img src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnIHN0YW5kYWxvbmU9J25vJz8+PHN2ZyBjb250ZW50U3R5bGVUeXBlPSd0ZXh0L2NzcycgaGVpZ2h0PSc1NzdweCcgcHJlc2VydmVBc3BlY3RSYXRpbz0nbm9uZScgc3R5bGU9J3dpZHRoOjEzOTFweDtoZWlnaHQ6NTc3cHg7YmFja2dyb3VuZDojRkZGRkZGOycgdmVyc2lvbj0nMS4xJyB2aWV3Qm94PScwIDAgMTM5MSA1NzcnIHdpZHRoPScxMzkxcHgnIHhtbG5zOnhsaW5rPSdodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rJyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHpvb21BbmRQYW49J21hZ25pZnknPjxkZWZzLz48 [...]
+</div>
+<div class="title">Figure 4. Command Processing Architecture Level 1 - Core</div>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_commands"><a class="link" href="#_commands">Commands</a></h3>
+<div class="paragraph">
+<p>This is the write-side of the architecture…​</p>
+</div>
+<div class="sect3">
+<h4 id="_dispatcher"><a class="link" href="#_dispatcher">Dispatcher</a></h4>
+<div class="paragraph">
+<p>The synchronous command dispatcher is the default implementation that is used to dispatch and execute commands. We have already prototypes for other dispatch modes (asynchronous and non-blocking), but they need extensive testing before they can be used in production. When ready they can be dropped in without any further code changes. The default synchronous dispatcher is automatically disabled if an alternative command dispatcher implementation is provided by Spring Framework’s [...]
+</div>
+<div class="imageblock">
+<div class="content">
+<img src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnIHN0YW5kYWxvbmU9J25vJz8+PHN2ZyBjb250ZW50U3R5bGVUeXBlPSd0ZXh0L2NzcycgaGVpZ2h0PSc1NDBweCcgcHJlc2VydmVBc3BlY3RSYXRpbz0nbm9uZScgc3R5bGU9J3dpZHRoOjk5NHB4O2hlaWdodDo1NDBweDtiYWNrZ3JvdW5kOiNGRkZGRkY7JyB2ZXJzaW9uPScxLjEnIHZpZXdCb3g9JzAgMCA5OTQgNTQwJyB3aWR0aD0nOTk0cHgnIHhtbG5zOnhsaW5rPSdodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rJyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHpvb21BbmRQYW49J21hZ25pZnknPjxkZWZzLz48Zz48 [...]
+</div>
+<div class="title">Figure 5. Command Processing Architecture Level 2 - Dispatcher</div>
+</div>
+<div class="listingblock">
+<div class="title">Command Dispatcher</div>
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="kn">package</span> <span class="nn">org.apache.fineract.command.core</span><span class="o">;</span>
+
+<span class="kn">import</span> <span class="nn">java.util.function.Supplier</span><span class="o">;</span>
+
+<span class="kd">public</span> <span class="kd">interface</span> <span class="nc">CommandDispatcher</span> <span class="o">{</span>
+
+ <span class="o"><</span><span class="no">REQ</span><span class="o">,</span> <span class="no">RES</span><span class="o">></span> <span class="nc">Supplier</span><span class="o"><</span><span class="no">RES</span><span class="o">></span> <span class="nf">dispatch</span><span class="o">(</span><span class="nc">Command</span><span class="o"><</span><span class="no">REQ</span><span class="o">></span> <span class="n">command</span><span class="o">);</span>
+<span class="o">}</span></code></pre>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_synchronous"><a class="link" href="#_synchronous">Synchronous</a></h4>
+<div class="paragraph">
+<p>TBD</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_asynchronous"><a class="link" href="#_asynchronous">Asynchronous</a></h4>
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-note" title="Note"></i>
+</td>
+<td class="content">
+Early phase of development. There is a prototype available in module <code>fineract-command-async</code>, but needs extensive testing before it can be used in production.
+</td>
+</tr>
+</table>
+</div>
+<div class="paragraph">
+<p>TBD</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_lmax_disruptor"><a class="link" href="#_lmax_disruptor">LMAX Disruptor</a></h4>
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-note" title="Note"></i>
+</td>
+<td class="content">
+Early phase of development. There is a prototype available in module <code>fineract-command-disruptor</code>, but needs extensive testing before it can be used in production.
+</td>
+</tr>
+</table>
+</div>
+<div class="paragraph">
+<p>TBD</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_chronicle_queue"><a class="link" href="#_chronicle_queue">Chronicle Queue</a></h4>
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-note" title="Note"></i>
+</td>
+<td class="content">
+Early phase of development. No prototype available yet, but most likely module <code>fineract-command-chronicle</code> will be used.
+</td>
+</tr>
+</table>
+</div>
+<div class="paragraph">
+<p>TBD</p>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_command_handler"><a class="link" href="#_command_handler">Command Handler</a></h3>
+<div class="paragraph">
+<p>TBD</p>
+</div>
+<div class="imageblock">
+<div class="content">
+<img src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnIHN0YW5kYWxvbmU9J25vJz8+PHN2ZyBjb250ZW50U3R5bGVUeXBlPSd0ZXh0L2NzcycgaGVpZ2h0PScyOTBweCcgcHJlc2VydmVBc3BlY3RSYXRpbz0nbm9uZScgc3R5bGU9J3dpZHRoOjEyMTVweDtoZWlnaHQ6MjkwcHg7YmFja2dyb3VuZDojRkZGRkZGOycgdmVyc2lvbj0nMS4xJyB2aWV3Qm94PScwIDAgMTIxNSAyOTAnIHdpZHRoPScxMjE1cHgnIHhtbG5zOnhsaW5rPSdodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rJyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHpvb21BbmRQYW49J21hZ25pZnknPjxkZWZzLz48 [...]
+</div>
+<div class="title">Figure 6. Command Processing Architecture Level 2 - Handlers</div>
+</div>
+<div class="sect3">
+<h4 id="_handler_manager"><a class="link" href="#_handler_manager">Handler Manager</a></h4>
+<div class="listingblock">
+<div class="title">Command Handler Manager</div>
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="kn">package</span> <span class="nn">org.apache.fineract.command.core</span><span class="o">;</span>
+
+<span class="nd">@FunctionalInterface</span>
+<span class="kd">public</span> <span class="kd">interface</span> <span class="nc">CommandHandlerManager</span> <span class="o">{</span>
+
+ <span class="o"><</span><span class="no">REQ</span><span class="o">,</span> <span class="no">RES</span><span class="o">></span> <span class="no">RES</span> <span class="nf">handle</span><span class="o">(</span><span class="nc">Command</span><span class="o"><</span><span class="no">REQ</span><span class="o">></span> <span class="n">command</span><span class="o">);</span>
+<span class="o">}</span></code></pre>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_handler"><a class="link" href="#_handler">Handler</a></h4>
+<div class="listingblock">
+<div class="title">Command Handler</div>
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="kn">package</span> <span class="nn">org.apache.fineract.command.core</span><span class="o">;</span>
+
+<span class="kn">import</span> <span class="nn">com.google.common.reflect.TypeToken</span><span class="o">;</span>
+<span class="kn">import</span> <span class="nn">lombok.SneakyThrows</span><span class="o">;</span>
+
+<span class="kd">public</span> <span class="kd">interface</span> <span class="nc">CommandHandler</span><span class="o"><</span><span class="no">REQ</span><span class="o">,</span> <span class="no">RES</span><span class="o">></span> <span class="o">{</span>
+
+ <span class="no">RES</span> <span class="nf">handle</span><span class="o">(</span><span class="nc">Command</span><span class="o"><</span><span class="no">REQ</span><span class="o">></span> <span class="n">command</span><span class="o">);</span>
+
+ <span class="nd">@SneakyThrows</span>
+ <span class="k">default</span> <span class="no">RES</span> <span class="nf">fallback</span><span class="o">(</span><span class="nc">Command</span><span class="o"><</span><span class="no">REQ</span><span class="o">></span> <span class="n">command</span><span class="o">,</span> <span class="nc">Throwable</span> <span class="n">t</span><span class="o">)</span> <span class="o">{</span>
+ <span class="c1">// NOTE: any command handler can override this default to implement more specialized fallbacks.</span>
+ <span class="k">throw</span> <span class="n">t</span><span class="o">;</span>
+ <span class="o">}</span>
+
+ <span class="k">default</span> <span class="kt">boolean</span> <span class="nf">matches</span><span class="o">(</span><span class="nc">Command</span><span class="o"><</span><span class="no">REQ</span><span class="o">></span> <span class="n">command</span><span class="o">)</span> <span class="o">{</span>
+ <span class="nc">TypeToken</span><span class="o"><</span><span class="no">REQ</span><span class="o">></span> <span class="n">handlerType</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">TypeToken</span><span class="o"><>(</span><span class="n">getClass</span><span class="o">())</span> <span class="o">{};</span>
+
+ <span class="k">return</span> <span class="n">handlerType</span><span class="o">.</span><span class="na">getRawType</span><span class="o">().</span><span class="na">isAssignableFrom</span><span class="o">(</span><span class="n">command</span><span class="o">.</span><span class="na">getPayload</span><span class="o">().</span><span class="na">getClass</span><span class="o">());</span>
+ <span class="o">}</span>
+<span class="o">}</span></code></pre>
+</div>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_command_hooks"><a class="link" href="#_command_hooks">Command Hooks</a></h3>
+<div class="paragraph">
+<p>TBD</p>
+</div>
+<div class="imageblock">
+<div class="content">
+<img src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnIHN0YW5kYWxvbmU9J25vJz8+PHN2ZyBjb250ZW50U3R5bGVUeXBlPSd0ZXh0L2NzcycgaGVpZ2h0PSc3MDlweCcgcHJlc2VydmVBc3BlY3RSYXRpbz0nbm9uZScgc3R5bGU9J3dpZHRoOjEyODRweDtoZWlnaHQ6NzA5cHg7YmFja2dyb3VuZDojRkZGRkZGOycgdmVyc2lvbj0nMS4xJyB2aWV3Qm94PScwIDAgMTI4NCA3MDknIHdpZHRoPScxMjg0cHgnIHhtbG5zOnhsaW5rPSdodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rJyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHpvb21BbmRQYW49J21hZ25pZnknPjxkZWZzLz48 [...]
+</div>
+<div class="title">Figure 7. Command Processing Architecture Level 2 - Hooks</div>
+</div>
+<div class="sect3">
+<h4 id="_hook_manager"><a class="link" href="#_hook_manager">Hook Manager</a></h4>
+<div class="listingblock">
+<div class="title">Command Hook Manager</div>
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="kn">package</span> <span class="nn">org.apache.fineract.command.core</span><span class="o">;</span>
+
+<span class="kd">public</span> <span class="kd">interface</span> <span class="nc">CommandHookManager</span> <span class="o">{</span>
+
+ <span class="kt">void</span> <span class="nf">before</span><span class="o">(</span><span class="nc">Command</span> <span class="n">command</span><span class="o">);</span>
+
+ <span class="kt">void</span> <span class="nf">after</span><span class="o">(</span><span class="nc">Command</span> <span class="n">command</span><span class="o">,</span> <span class="nc">Object</span> <span class="n">response</span><span class="o">);</span>
+
+ <span class="kt">void</span> <span class="nf">error</span><span class="o">(</span><span class="nc">Command</span> <span class="n">command</span><span class="o">,</span> <span class="nc">Throwable</span> <span class="n">error</span><span class="o">);</span>
+<span class="o">}</span></code></pre>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_before_hooks"><a class="link" href="#_before_hooks">Before Hooks</a></h4>
+<div class="paragraph">
+<p>TBD</p>
+</div>
+<div class="listingblock">
+<div class="title">Command Hook Before</div>
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="kn">package</span> <span class="nn">org.apache.fineract.command.core</span><span class="o">;</span>
+
+<span class="nd">@FunctionalInterface</span>
+<span class="kd">public</span> <span class="kd">interface</span> <span class="nc">CommandHookBefore</span><span class="o"><</span><span class="no">REQ</span><span class="o">></span> <span class="o">{</span>
+
+ <span class="kt">void</span> <span class="nf">onBefore</span><span class="o">(</span><span class="nc">Command</span><span class="o"><</span><span class="no">REQ</span><span class="o">></span> <span class="n">command</span><span class="o">);</span>
+<span class="o">}</span></code></pre>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_after_hooks"><a class="link" href="#_after_hooks">After Hooks</a></h4>
+<div class="paragraph">
+<p>TBD</p>
+</div>
+<div class="listingblock">
+<div class="title">Command Hook After</div>
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="kn">package</span> <span class="nn">org.apache.fineract.command.core</span><span class="o">;</span>
+
+<span class="nd">@FunctionalInterface</span>
+<span class="kd">public</span> <span class="kd">interface</span> <span class="nc">CommandHookAfter</span><span class="o"><</span><span class="no">REQ</span><span class="o">,</span> <span class="no">RES</span><span class="o">></span> <span class="o">{</span>
+
+ <span class="kt">void</span> <span class="nf">onAfter</span><span class="o">(</span><span class="nc">Command</span><span class="o"><</span><span class="no">REQ</span><span class="o">></span> <span class="n">command</span><span class="o">,</span> <span class="no">RES</span> <span class="n">response</span><span class="o">);</span>
+<span class="o">}</span></code></pre>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_error_hooks"><a class="link" href="#_error_hooks">Error Hooks</a></h4>
+<div class="paragraph">
+<p>TBD</p>
+</div>
+<div class="listingblock">
+<div class="title">Command Hook Error</div>
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="kn">package</span> <span class="nn">org.apache.fineract.command.core</span><span class="o">;</span>
+
+<span class="nd">@FunctionalInterface</span>
+<span class="kd">public</span> <span class="kd">interface</span> <span class="nc">CommandHookError</span><span class="o"><</span><span class="no">REQ</span><span class="o">></span> <span class="o">{</span>
+
+ <span class="kt">void</span> <span class="nf">onError</span><span class="o">(</span><span class="nc">Command</span><span class="o"><</span><span class="no">REQ</span><span class="o">></span> <span class="n">command</span><span class="o">,</span> <span class="nc">Throwable</span> <span class="n">error</span><span class="o">);</span>
+<span class="o">}</span></code></pre>
+</div>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_command_store"><a class="link" href="#_command_store">Command Store</a></h3>
+<div class="paragraph">
+<p>TBD</p>
+</div>
+<div class="imageblock">
+<div class="content">
+<img src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnIHN0YW5kYWxvbmU9J25vJz8+PHN2ZyBjb250ZW50U3R5bGVUeXBlPSd0ZXh0L2NzcycgaGVpZ2h0PSc2MzNweCcgcHJlc2VydmVBc3BlY3RSYXRpbz0nbm9uZScgc3R5bGU9J3dpZHRoOjkxM3B4O2hlaWdodDo2MzNweDtiYWNrZ3JvdW5kOiNGRkZGRkY7JyB2ZXJzaW9uPScxLjEnIHZpZXdCb3g9JzAgMCA5MTMgNjMzJyB3aWR0aD0nOTEzcHgnIHhtbG5zOnhsaW5rPSdodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rJyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHpvb21BbmRQYW49J21hZ25pZnknPjxkZWZzLz48Zz48 [...]
+</div>
+<div class="title">Figure 8. Command Processing Architecture Level 2 - Store</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_command_audit"><a class="link" href="#_command_audit">Command Audit</a></h3>
+<div class="paragraph">
+<p>TBD</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_command_idempotency"><a class="link" href="#_command_idempotency">Command Idempotency</a></h3>
+<div class="paragraph">
+<p>TBD</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_command_observability"><a class="link" href="#_command_observability">Command Observability</a></h3>
+<div class="paragraph">
+<p>TBD</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_refactoring_instructions"><a class="link" href="#_refactoring_instructions">Refactoring Instructions</a></h3>
+<div class="admonitionblock important">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-important" title="Important"></i>
+</td>
+<td class="content">
+The content of this section will be probably distributed to the other parts of this chapter. Just to say: this is work in progress!
+</td>
+</tr>
+</table>
+</div>
+<div class="sect3">
+<h4 id="_general"><a class="link" href="#_general">General</a></h4>
+<div class="sect4">
+<h5 id="_pojosdtos"><a class="link" href="#_pojosdtos">POJOs/DTOs</a></h5>
+<div class="paragraph">
+<p>Please make sure that all POJOs (for request and response types) have a similar structure. They should:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>use Lombok to reduce boilerplate code</p>
+</li>
+<li>
+<p>make sure that all annotations are always in the same order as shown below</p>
+</li>
+<li>
+<p>avoid <code>records</code> for now (we might or might not migrate later from Lombok to <code>record</code>)</p>
+</li>
+<li>
+<p>each class should implement <code>java.io.Serializable</code></p>
+</li>
+<li>
+<p>each class should contain a serialization version set to <code>1L</code></p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>Example:</p>
+</div>
+<div class="listingblock">
+<div class="title">Anatomy of a DTO</div>
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="nd">@Builder</span>
+<span class="nd">@Data</span>
+<span class="nd">@NoArgsConstructor</span>
+<span class="nd">@AllArgsConstructor</span>
+<span class="nd">@FieldNameConstants</span>
+<span class="kd">public</span> <span class="kd">class</span> <span class="nc">DummyRequest</span> <span class="kd">implements</span> <span class="nc">Serializable</span> <span class="o">{</span>
+
+ <span class="nd">@Serial</span>
+ <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">long</span> <span class="n">serialVersionUID</span> <span class="o">=</span> <span class="mi">1L</span><span class="o">;</span>
+
+ <span class="nd">@NotEmpty</span><span class="o">(</span><span class="n">message</span> <span class="o">=</span> <span class="s">"{org.apache.fineract.dummy.content.not-empty}"</span><span class="o">)</span>
+ <span class="kd">private</span> <span class="nc">String</span> <span class="n">content</span><span class="o">;</span>
+<span class="o">}</span></code></pre>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_transformation_between_types"><a class="link" href="#_transformation_between_types">Transformation between types</a></h5>
+<div class="paragraph">
+<p>If you need to transform/convert two classes then then use <a href="https://mapstruct.org/">MapStruct</a> whenever possible. Place the <a href="https://mapstruct.org/[MapStruct]">MapStruct</a> interfaces under a sibling package <code>mapping</code> next to the <code>data</code> package (e.g. <code>org.apache.fineract.xxx.data</code> and <code>org.apache.fineract.xxx.mapping</code>).</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_package_structure_org_apache_fineract_xxx"><a class="link" href="#_package_structure_org_apache_fineract_xxx">Package structure (<code>org.apache.fineract.xxx.*</code>)</a></h5>
+<div class="paragraph">
+<p>Please make sure to always follow this package structure pattern (aka layered architecture):</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><code>api</code>: contains all REST JAX-RS resource classes (later Spring Web MVC controllers)</p>
+</li>
+<li>
+<p><code>command</code>: primarily used for command specific child class implementations of <code>org.apache.fineract.command.core.Command</code> (see section "Command Dispatcher preserving Type Information")</p>
+</li>
+<li>
+<p><code>data</code>: contains all DTOs (request and response types)</p>
+</li>
+<li>
+<p><code>domain</code>: contains all entity/table mapping classes</p>
+</li>
+<li>
+<p><code>handler</code>: contains all command handlers</p>
+</li>
+<li>
+<p><code>service</code>: contains business logic services</p>
+</li>
+<li>
+<p><code>mapping</code>: contains MapStruct interfaces</p>
+</li>
+<li>
+<p><code>security</code>: might contain later so called Spring Security "authorization managers" for more complex use cases</p>
+</li>
+<li>
+<p><code>serialization</code>: technically we should not need this package anymore after we are done with the refactorings; in theory there could be very complex data structures that are not easily digestable by Jackson; for those case we could still use this package to add de-/serialization helpers (Jackson provides a proper API for this). We should avoid complex structures!</p>
+</li>
+<li>
+<p><code>starter</code>: Spring Java configuration that allows us to make Fineract customizable (make parts of the system replaceable)</p>
+</li>
+<li>
+<p><code>validation</code>: contains custom Jakarta Validation components/annotations</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>In general avoid too many nesting levels. Ideally we would only have one additional underneath the base package (<code>org.apache.fineract.*</code>) where in turn only the above package patterns are used.</p>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_rest_api"><a class="link" href="#_rest_api">REST API</a></h4>
+<div class="sect4">
+<h5 id="_read_requests"><a class="link" href="#_read_requests">Read Requests</a></h5>
+<div class="paragraph">
+<p>The <strong>read requests</strong> (HTTP <code><strong>GET</strong></code>) usually only require to refactor (if at all) the business logic services in case they don’t return proper Java POJOs. More often than not service functions have a return type of <code>org.apache.fineract.infrastructure.core.data.CommandProcessingResult</code>. This type belongs to the legacy command processing mechanics and basically is just a thin wrapper around a JSON object that is stored in a string [...]
+</div>
+<div class="paragraph">
+<p>Please name all return types for these read requests consistently. We propose to add always a suffix <code>Response</code>; this makes it immediately clear that we are dealing with a data transfer object (vs database mapping entities) and that it’s something that we return to the clients (vs incoming requests). Historically we’ve used <code>Data</code> as a suffix, but this doesn’t make it clear if it’s used as input or output. Let’s see an example.</p>
+</div>
+<div class="paragraph">
+<p>This is how the legacy code used to look like:</p>
+</div>
+<div class="listingblock">
+<div class="title">Business Date JAX-RS Resource (<strong>legacy</strong>)</div>
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">BusinessDateApiResource</span> <span class="o">{</span>
+ <span class="c1">// ...</span>
+ <span class="nd">@GET</span>
+ <span class="nd">@Consumes</span><span class="o">({</span> <span class="nc">MediaType</span><span class="o">.</span><span class="na">TEXT_HTML</span><span class="o">,</span> <span class="nc">MediaType</span><span class="o">.</span><span class="na">APPLICATION_JSON</span> <span class="o">})</span>
+ <span class="nd">@Produces</span><span class="o">(</span><span class="nc">MediaType</span><span class="o">.</span><span class="na">APPLICATION_JSON</span><span class="o">)</span>
+ <span class="nd">@Operation</span><span class="o">(</span><span class="n">summary</span> <span class="o">=</span> <span class="s">"List all business dates"</span><span class="o">,</span> <span class="n">description</span> <span class="o">=</span> <span class="s">""</span><span class="o">)</span>
+ <span class="kd">public</span> <span class="nc">String</span> <span class="nf">getBusinessDates</span><span class="o">(</span><span class="nd">@Context</span> <span class="kd">final</span> <span class="nc">UriInfo</span> <span class="n">uriInfo</span><span class="o">)</span> <span class="o">{</span>
+ <span class="n">securityContext</span><span class="o">.</span><span class="na">authenticatedUser</span><span class="o">().</span><span class="na">validateHasReadPermission</span><span class="o">(</span><span class="s">"BUSINESS_DATE"</span><span class="o">);</span>
+ <span class="kd">final</span> <span class="nc">List</span><span class="o"><</span><span class="nc">BusinessDateData</span><span class="o">></span> <span class="n">foundBusinessDates</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">readPlatformService</span><span class="o">.</span><span class="na">findAll</span><span class="o">();</span>
+ <span class="nc">ApiRequestJsonSerializationSettings</span> <span class="n">settings</span> <span class="o">=</span> <span class="n">parameterHelper</span><span class="o">.</span><span class="na">process</span><span class="o">(</span><span class="n">uriInfo</span><span class="o">.</span><span class="na">getQueryParameters</span><span class="o">());</span>
+ <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">jsonSerializer</span><span class="o">.</span><span class="na">serialize</span><span class="o">(</span><span class="n">settings</span><span class="o">,</span> <span class="n">foundBusinessDates</span><span class="o">);</span>
+ <span class="o">}</span>
+ <span class="c1">// ...</span>
+<span class="o">}</span></code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>This is how the refactored code should look like; there is absolutely no need to manually serialize:</p>
+</div>
+<div class="listingblock">
+<div class="title">Business Date JAX-RS Resource (<strong>refactored</strong>)</div>
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">BusinessDateApiResource</span> <span class="o">{</span>
+
+ <span class="nd">@GET</span>
+ <span class="nd">@Produces</span><span class="o">(</span><span class="nc">MediaType</span><span class="o">.</span><span class="na">APPLICATION_JSON</span><span class="o">)</span>
+ <span class="nd">@Operation</span><span class="o">(</span><span class="n">summary</span> <span class="o">=</span> <span class="s">"List all business dates"</span><span class="o">,</span> <span class="n">description</span> <span class="o">=</span> <span class="s">""</span><span class="o">)</span>
+ <span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">BusinessDateResponse</span><span class="o">></span> <span class="nf">getBusinessDates</span><span class="o">()</span> <span class="o">{</span>
+ <span class="k">return</span> <span class="n">businessDateMapper</span><span class="o">.</span><span class="na">mapFetchResponse</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">readPlatformService</span><span class="o">.</span><span class="na">findAll</span><span class="o">());</span>
+ <span class="o">}</span></code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>On occasions it could be that there could be a service read function that returns a collection of some data structure. In those cases it is fine to use a DTO class with suffix <code>Data</code>. In the current code base we have all kinds of variations like <code>DTO</code>, <code>Dto</code>, <code>DATA</code> and so on. Make sure we are consistent with the naming and let’s use the <code>Data</code> suffix in these cases.</p>
+</div>
+<div class="paragraph">
+<p>Very often you’ll see the use of <code>PlatformSecurityContext</code> (which is a wrapper around Spring Security’s <code>org.springframework.security.core.context.SecurityContextHolder</code>) to validate read permissions. Avoid this please! Fortunately, the fix is relatively easy. We just need to introduce an "Ant matcher" configuration in the Spring web security configuration (<code>org.apache.fineract.infrastructure.core.config.SecurityConfig</code>). The change should [...]
+</div>
+<div class="listingblock">
+<div class="title">Security Configuration (best practice)</div>
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"> <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">PathPatternRequestMatcher</span><span class="o">.</span><span class="na">Builder</span> <span class="no">API_MATCHER</span> <span class="o">=</span> <span class="nc">PathPatternRequestMatcher</span><span class="o">.</span><span class="na">withDefaults</span><span class="o">();</span>
+ <span class="o">.</span><span class="na">requestMatchers</span><span class="o">(</span><span class="no">API_MATCHER</span><span class="o">.</span><span class="na">matcher</span><span class="o">(</span><span class="nc">HttpMethod</span><span class="o">.</span><span class="na">GET</span><span class="o">,</span> <span class="s">"/api/*/businessdate/*"</span><span class="o">))</span>
+ <span class="o">.</span><span class="na">hasAnyAuthority</span><span class="o">(</span><span class="no">ALL_FUNCTIONS</span><span class="o">,</span> <span class="no">ALL_FUNCTIONS_READ</span><span class="o">,</span> <span class="s">"READ_BUSINESS_DATE"</span><span class="o">)</span>
+ <span class="o">.</span><span class="na">requestMatchers</span><span class="o">(</span><span class="no">API_MATCHER</span><span class="o">.</span><span class="na">matcher</span><span class="o">(</span><span class="nc">HttpMethod</span><span class="o">.</span><span class="na">POST</span><span class="o">,</span> <span class="s">"/api/*/businessdate"</span><span class="o">))</span>
+ <span class="o">.</span><span class="na">hasAnyAuthority</span><span class="o">(</span><span class="no">ALL_FUNCTIONS</span><span class="o">,</span> <span class="no">ALL_FUNCTIONS_WRITE</span><span class="o">,</span> <span class="s">"UPDATE_BUSINESS_DATE"</span><span class="o">)</span>
+ <span class="c1">// external</span>
+ <span class="n">userNotificationService</span><span class="o">,</span> <span class="n">basicAuthTenantDetailsService</span><span class="o">,</span> <span class="n">businessDateReadPlatformService</span><span class="o">);</span></code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>The nice side effect is that we’ll have all security rules that we are enforcing in one place. This will enable more flexibility with customizations around security (will be handled in the modular security proposal).</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_write_requests"><a class="link" href="#_write_requests">Write Requests</a></h5>
+<div class="paragraph">
+<p>The <strong>write requests</strong> (HTTP <code><strong>PUT</strong></code> and <code><strong>POST</strong></code>) are the ones that affect the command processing infrastructure. The JSON body in pretty much all nthe legacy cases is probably represented as a simple string variable that gets passed to a command wrapper class (<code>org.apache.fineract.commands.domain.CommandWrapper</code>). All these variables need to be replaced by proper POJO classes that represent the request body. [...]
+</div>
+<div class="paragraph">
+<p>Usually you’ll have 2 classes to take care of: <strong>requests</strong> (incoming input parameters) and <strong>responses</strong> (outgoing results). These classes are technically <strong>DTOs</strong> (data transfer objects) or <strong>VOs</strong> (value objects) and to make them more recognizable as such I would start standardizing the naming. E.g. if I have a use case aka REST endpoint that creates client data we would name that DTO class <code>CreateClientRequest</code>; [...]
+</div>
+<div class="paragraph">
+<p>Only new thing that needs to be injected in those REST API resource classes (JAX-RS) or controllers (Spring Web MVC) is the <code>CommandDispatcher</code> component that allows you to send requests to the <strong>command dispatcher</strong> which in turn will be processed by <strong>handlers</strong> that eventually call one or more <strong>business logic services</strong>. Results are sent back to the dispatcher and are received in the REST API aka returned by <code>CommandDispatcher [...]
+</div>
+</div>
+<div class="sect4">
+<h5 id="_jakarta_validation"><a class="link" href="#_jakarta_validation">Jakarta Validation</a></h5>
+<div class="paragraph">
+<p>TBD</p>
+</div>
+<div class="paragraph">
+<p>Example POJO:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="nd">@Builder</span>
+<span class="nd">@Data</span>
+<span class="nd">@NoArgsConstructor</span>
+<span class="nd">@AllArgsConstructor</span>
+<span class="nd">@FieldNameConstants</span>
+<span class="kd">public</span> <span class="kd">class</span> <span class="nc">DummyRequest</span> <span class="kd">implements</span> <span class="nc">Serializable</span> <span class="o">{</span>
+
+ <span class="nd">@Serial</span>
+ <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">long</span> <span class="n">serialVersionUID</span> <span class="o">=</span> <span class="mi">1L</span><span class="o">;</span>
+
+ <span class="nd">@NotEmpty</span><span class="o">(</span><span class="n">message</span> <span class="o">=</span> <span class="s">"{org.apache.fineract.dummy.content.not-empty}"</span><span class="o">)</span>
+ <span class="kd">private</span> <span class="nc">String</span> <span class="n">content</span><span class="o">;</span>
+<span class="o">}</span></code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Add your translation keys to <code>fineract-validation/src/main/resources/ValidationMessages.properties</code></p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_future_spring_web_mvc"><a class="link" href="#_future_spring_web_mvc"><strong>Future</strong>: Spring Web MVC</a></h5>
+<div class="paragraph">
+<p>We will not do this right now, but later we might move away from JAX-RS and introduce <strong>Spring Web MVC</strong> which is mostly just changing the annotations (e.g. <code><strong>@POST</strong></code> vs <code><strong>@PostRequest</strong></code>). This process should be pretty straight forward and could be even (semi-) automated by using <strong>OpenRewrite</strong> recipes. Once we have proper POJOs for requests and response we can also use <strong>Jakarta Validation</strong> a [...]
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_command_dispatcher_preserving_type_information"><a class="link" href="#_command_dispatcher_preserving_type_information">Command Dispatcher preserving Type Information</a></h4>
+<div class="paragraph">
+<p><code>CommandDispatcher</code> needs to be injected where needed (usually only REST API controllers). By default everything is configured for <code>sync</code>(-hronous) processing. Other modes (<code>async</code>, <code>disruptor</code>) can be easily configured via application.properties, but need more testing and are out of scope for now. As you can see the command object just contains some of metadata (<code>createdAt</code>, <code>username</code> etc.) and the payload aka request [...]
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">DummyApiResource</span> <span class="o">{</span>
+ <span class="c1">// ...</span>
+ <span class="nd">@GET</span>
+ <span class="nc">DummyResponse</span> <span class="nf">dummy</span><span class="o">(</span><span class="nd">@HeaderParam</span><span class="o">(</span><span class="s">"x-fineract-request-id"</span><span class="o">)</span> <span class="no">UUID</span> <span class="n">requestId</span><span class="o">,</span> <span class="nd">@HeaderParam</span><span class="o">(</span><span class="s">"Fineract-Platform-TenantId"</span><span class="o">)</span> <span class="nc">String</span> <span class=" [...]
+ <span class="kt">var</span> <span class="n">command</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DummyCommand</span><span class="o">();</span>
+ <span class="n">command</span><span class="o">.</span><span class="na">setId</span><span class="o">(</span><span class="n">requestId</span><span class="o">);</span>
+ <span class="n">command</span><span class="o">.</span><span class="na">setPayload</span><span class="o">(</span><span class="n">request</span><span class="o">);</span>
+
+ <span class="n">tenantService</span><span class="o">.</span><span class="na">set</span><span class="o">(</span><span class="n">tenantId</span><span class="o">);</span>
+
+ <span class="nc">Supplier</span><span class="o"><</span><span class="nc">DummyResponse</span><span class="o">></span> <span class="n">result</span> <span class="o">=</span> <span class="n">pipeline</span><span class="o">.</span><span class="na">send</span><span class="o">(</span><span class="n">command</span><span class="o">);</span>
+
+ <span class="k">return</span> <span class="n">result</span><span class="o">.</span><span class="na">get</span><span class="o">();</span>
+ <span class="o">}</span>
+ <span class="c1">// ...</span>
+<span class="o">}</span></code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Make sure to create command specific child class of the generic (and abstract) <code>org.apache.fineract.command.core.Command</code> class. Example:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="nd">@Data</span>
+<span class="nd">@EqualsAndHashCode</span><span class="o">(</span><span class="n">callSuper</span> <span class="o">=</span> <span class="kc">true</span><span class="o">)</span>
+<span class="kd">public</span> <span class="kd">class</span> <span class="nc">DummyCommand</span> <span class="kd">extends</span> <span class="nc">Command</span><span class="o"><</span><span class="nc">DummyRequest</span><span class="o">></span> <span class="o">{}</span></code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>If everything is done correctly then no type information will be lost and everything can be parsed without further help by the Jackson parser. Eventually all handcrafted boilerplate JSON parsing code can be dumped. In rare cases we might need to add de-/serialization helper classes (a concept provided by Jackson) to help the parser identify the types properly.</p>
+</div>
+<div class="paragraph">
+<p>When we use <strong>Spring Web MVC unit testing</strong> (actually integration testing) gets very easy. Just a couple of annotations and you can execute them pretty much like simple unit tests in your IDE (because Spring Web MVC is a first class citizen obviously in the Spring Framework). NONE of that handcrafted client code like in our current integration tests is required; the tests should be easier to refactor and easier to understand. Writing those tests is <strong>optional</stron [...]
+</div>
+</div>
+<div class="sect3">
+<h4 id="_command_handlers"><a class="link" href="#_command_handlers">Command Handlers</a></h4>
+<div class="paragraph">
+<p>In the current CQRS implementation we have already a concept called <strong>command handlers</strong>. Those handlers are responsible to receive the command objects with their (request) payloads and transform the requests as needed an pass them to one or more business logic services. The refactoring of those handlers should not be too complicated, they just need to implement the Java interface <code>CommandHandler</code>. Look at my test samples to see how the implementation details l [...]
+</div>
+<div class="paragraph">
+<p>The old handlers look somewhat like this:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="nd">@Service</span>
+<span class="nd">@CommandType</span><span class="o">(</span><span class="n">entity</span> <span class="o">=</span> <span class="s">"PAYMENTTYPE"</span><span class="o">,</span> <span class="n">action</span> <span class="o">=</span> <span class="s">"CREATE"</span><span class="o">)</span>
+<span class="kd">public</span> <span class="kd">class</span> <span class="nc">CreatePaymentTypeCommandHandler</span> <span class="kd">implements</span> <span class="nc">NewCommandSourceHandler</span> <span class="o">{</span>
+
+ <span class="kd">private</span> <span class="kd">final</span> <span class="nc">PaymentTypeWriteService</span> <span class="n">paymentTypeWriteService</span><span class="o">;</span>
+
+ <span class="nd">@Autowired</span>
+ <span class="kd">public</span> <span class="nf">CreatePaymentTypeCommandHandler</span><span class="o">(</span><span class="kd">final</span> <span class="nc">PaymentTypeWriteService</span> <span class="n">paymentTypeWriteService</span><span class="o">)</span> <span class="o">{</span>
+ <span class="k">this</span><span class="o">.</span><span class="na">paymentTypeWriteService</span> <span class="o">=</span> <span class="n">paymentTypeWriteService</span><span class="o">;</span>
+ <span class="o">}</span>
+
+ <span class="nd">@Override</span>
+ <span class="nd">@Transactional</span>
+ <span class="kd">public</span> <span class="nc">CommandProcessingResult</span> <span class="nf">processCommand</span><span class="o">(</span><span class="nc">JsonCommand</span> <span class="n">command</span><span class="o">)</span> <span class="o">{</span>
+ <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">paymentTypeWriteService</span><span class="o">.</span><span class="na">createPaymentType</span><span class="o">(</span><span class="n">command</span><span class="o">);</span>
+ <span class="o">}</span>
+<span class="o">}</span></code></pre>
+</div>
+</div>
+<div class="olist lowerroman">
+<ol class="lowerroman" type="i">
+<li>
+<p>and this is how the refactored handler could look like:</p>
+</li>
+</ol>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="nd">@Slf4j</span>
+<span class="nd">@RequiredArgsConstructor</span>
+<span class="nd">@Component</span>
+<span class="kd">public</span> <span class="kd">class</span> <span class="nc">PaymentTypeCreateCommandHandler</span> <span class="kd">implements</span> <span class="nc">CommandHandler</span><span class="o"><</span><span class="nc">PaymentTypeCreateRequest</span><span class="o">,</span> <span class="nc">PaymentTypeCreateResponse</span><span class="o">></span> <span class="o">{</span>
+
+ <span class="kd">private</span> <span class="kd">final</span> <span class="nc">PaymentTypeWriteService</span> <span class="n">paymentTypeWriteService</span><span class="o">;</span>
+
+ <span class="nd">@Override</span>
+ <span class="kd">public</span> <span class="nc">PaymentTypeCreateResponse</span> <span class="nf">handle</span><span class="o">(</span><span class="nc">Command</span><span class="o"><</span><span class="nc">PaymentTypeCreateRequest</span><span class="o">></span> <span class="n">command</span><span class="o">)</span> <span class="o">{</span>
+ <span class="c1">// TODO: refactor business logic service to accept properly typed request objects as input</span>
+ <span class="k">return</span> <span class="n">paymentTypeWriteService</span><span class="o">.</span><span class="na">createPaymentType</span><span class="o">(</span><span class="n">command</span><span class="o">.</span><span class="na">getPayload</span><span class="o">());</span>
+ <span class="o">}</span>
+<span class="o">}</span></code></pre>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_dispatch_types"><a class="link" href="#_dispatch_types">Dispatch Types</a></h4>
+<div class="sect4">
+<h5 id="_sync_execution"><a class="link" href="#_sync_execution">Sync Execution</a></h5>
+<div class="paragraph">
+<p>This is the default execution mode. Performance is to be expected on par with the current legacy implementation all tests need to work with this mode.</p>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_skip_async_execution"><a class="link" href="#_skip_async_execution"><strong>Skip</strong>: Async Execution</a></h5>
+<div class="paragraph">
+<p>Already included in the current implementation. Just needs a proper <strong>configuration</strong> in <strong>application.properties</strong> (see unit tests). One thing that might need some additional coding: the use of <strong>thread local variables in multi threaded environments</strong> needs some special care to properly work (we use this to identify the current tenant). Also: we should upgrade to JDK 21 and make use of virtual threads (very easy in Spring Boot, simple configurat [...]
+</div>
+</div>
+<div class="sect4">
+<h5 id="_skip_non_blocking_execution"><a class="link" href="#_skip_non_blocking_execution"><strong>Skip</strong>: Non-blocking Execution</a></h5>
+<div class="paragraph">
+<p>Already included in the current implementation and configurable. I’ve tried a couple of combinations with <a href="https://lmax-exchange.github.io/disruptor/user-guide">LMAX disruptor</a>, but this needs more testing to figure out optimal an configuration. Would be worth to create more realistic JMH benchmarks. I have added a simple one to get a first idea how the mechanics are working.</p>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_maker_checker_2"><a class="link" href="#_maker_checker_2">Maker-Checker</a></h4>
+<div class="paragraph">
+<p>This will be part of a separate proposal. The only related feature I’ve added here was command persistence so that you can save commands for deferred execution. Other than that I want to keep this concept (command processing) clean and avoid mixing to many concepts/concerns in one place. This ensures better maintainability. Maker-checker is actually a security related concept and should probably be handled with the proper Spring Security APIs (<code>AuthorizationManager</code> i [...]
+</div>
+<div class="paragraph">
+<p>When we encounter the first need to take care of Maker-Checker then let’s figure out a solution that has minimal impact and do a proper cleanup when the Maker-Checker proposal is available.</p>
+</div>
+<div class="paragraph">
+<p>[!NOTE] The best guess is that Maker-Checker will probably implemented</p>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_features"><a class="link" href="#_features">Features</a></h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>This section covers specific features and functionality available in Apache Fineract.</p>
+</div>
+<div class="sect2">
+<h3 id="_capitalized_income"><a class="link" href="#_capitalized_income">Capitalized Income</a></h3>
+<div class="sect3">
+<h4 id="_overview_2"><a class="link" href="#_overview_2">Overview</a></h4>
+<div class="paragraph">
+<p>Capitalized Income (formerly referred to as Origination Fee) in Apache Fineract is a fee-based or interest-based income item that is added to the principal of a loan and amortized over time. It is designed to be applied at disbursement and is treated as part of the loan’s principal for repayment and interest calculations.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_purpose"><a class="link" href="#_purpose">Purpose</a></h4>
+<div class="paragraph">
+<p>This functionality enables financial institutions to:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Recognize deferred income (fees or interest) systematically over the loan term</p>
+</li>
+<li>
+<p>Align accounting practices with regulatory requirements</p>
+</li>
+<li>
+<p>Improve income recognition accuracy</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_supported_loan_type"><a class="link" href="#_supported_loan_type">Supported Loan Type</a></h4>
+<div class="admonitionblock important">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-important" title="Important"></i>
+</td>
+<td class="content">
+<div class="paragraph">
+<p>Capitalized Income is only supported for:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Progressive Loan Schedules</p>
+</li>
+<li>
+<p>Advanced Payment Allocation Strategy</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>Other loan schedule types and transaction processing strategies are not supported.</p>
+</div>
+</td>
+</tr>
+</table>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_configuration_at_loan_product_level"><a class="link" href="#_configuration_at_loan_product_level">Configuration at Loan Product Level</a></h4>
+<div class="paragraph">
+<p>Capitalized income must be configured on the loan product.</p>
+</div>
+<div class="paragraph">
+<p>The configuration options include:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Enable Capitalized Income</strong>: Boolean toggle (default: disabled)</p>
+</li>
+<li>
+<p><strong>Calculation Mode</strong>: Only "Flat" is currently supported</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Later "Percentage based" can be introduced</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p><strong>Amortization Strategy</strong>: Only "EQUAL_AMORTIZATION" is supported</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Daily equal portions are recognized over the life of the loan</p>
+</li>
+<li>
+<p>Later other strategies can be introduced</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p><strong>Income Type</strong>: Specifies allocation rule. Defines which "balance category" to be used.</p>
+</li>
+</ul>
+</div>
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-note" title="Note"></i>
+</td>
+<td class="content">
+<div class="paragraph">
+<p>In Fineract, balance of a transaction is either: Principal, Fee, Penalty, Interest or overpayment</p>
+</div>
+</td>
+</tr>
+</table>
+</div>
+<div class="paragraph">
+<p>Options:<br>
+* FEE (default)<br>
+* INTEREST</p>
+</div>
+<div class="sect4">
+<h5 id="_gl_mapping"><a class="link" href="#_gl_mapping">GL Mapping</a></h5>
+<div class="paragraph">
+<p>Required GL Account mappings when Capitalized Income is enabled:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Deferred Income (Liability)</strong>: <code>deferredIncomeLiabilityAccountId</code> - mandatory when enabled</p>
+</li>
+<li>
+<p><strong>Income from Capitalization (Income)</strong>: <code>incomeFromCapitalizationAccountId</code> - mandatory when enabled</p>
+</li>
+</ul>
+</div>
+<div class="admonitionblock important">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-important" title="Important"></i>
+</td>
+<td class="content">
+<div class="paragraph">
+<p>Both GL accounts become mandatory when <code>enableIncomeCapitalization</code> is set to <code>true</code>.</p>
+</div>
+</td>
+</tr>
+</table>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_configuration_dependencies"><a class="link" href="#_configuration_dependencies">Configuration Dependencies</a></h5>
+<div class="paragraph">
+<p>When <code>enableIncomeCapitalization</code> is set to <code>true</code>, all following parameters become mandatory:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><code>capitalizedIncomeCalculationType</code> - must be "FLAT"</p>
+</li>
+<li>
+<p><code>capitalizedIncomeStrategy</code> - must be "EQUAL_AMORTIZATION"</p>
+</li>
+<li>
+<p><code>capitalizedIncomeType</code> - must be "FEE" or "INTEREST"</p>
+</li>
+<li>
+<p><code>deferredIncomeLiabilityAccountId</code> - must reference a valid GL account</p>
+</li>
+<li>
+<p><code>incomeFromCapitalizationAccountId</code> - must reference a valid GL account</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_behavior_and_calculations"><a class="link" href="#_behavior_and_calculations">Behavior and Calculations</a></h4>
+<div class="ulist">
+<ul>
+<li>
+<p>Capitalized income is added via API on or after the first disbursement date</p>
+</li>
+<li>
+<p>It is treated as a principal portion, recalculating the repayment schedule accordingly</p>
+</li>
+<li>
+<p>Interest and amortization schedules are updated to include the capitalized income amount</p>
+</li>
+<li>
+<p>Validated using formula: <code>(Total Disbursed + Current Capitalized Income + New Transaction Amount) ≤ Max Amount</code>, where Max Amount depends on loan product configuration: if <code>allowApprovedDisbursedAmountsOverApplied = true</code> uses <code>getOverAppliedMax(loan)</code>, otherwise uses <code>getApprovedPrincipal()</code></p>
+</li>
+</ul>
+</div>
+<div class="sect4">
+<h5 id="_daily_amortization"><a class="link" href="#_daily_amortization">Daily Amortization</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p>Recognized daily using the configured strategy</p>
+</li>
+<li>
+<p>Recognized portions move from Deferred Income to Income from Capitalization</p>
+</li>
+</ul>
+</div>
+<div class="sect5">
+<h6 id="_special_handling"><a class="link" href="#_special_handling">Special Handling</a></h6>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Preclosure</strong>: Remaining balance recognized in full on the preclosure date</p>
+</li>
+<li>
+<p><strong>Charge-off</strong>: Amortization stops and remaining balance is charged off</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_transaction_types_introduced"><a class="link" href="#_transaction_types_introduced">Transaction Types Introduced</a></h4>
+<div class="ulist">
+<ul>
+<li>
+<p>Capitalized Income</p>
+</li>
+<li>
+<p>Capitalized Income Amortization</p>
+</li>
+<li>
+<p>Capitalized Income Adjustment</p>
+</li>
+<li>
+<p>Capitalized Income Amortization Adjustment</p>
+</li>
+</ul>
+</div>
+<div class="sect4">
+<h5 id="_capitalized_income_transaction"><a class="link" href="#_capitalized_income_transaction">Capitalized Income Transaction</a></h5>
+<div class="paragraph">
+<p>The Capitalized Income transaction in Apache Fineract performs the following actions:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Adds a specified amount to the loan principal</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Considered a deferred income item (such as a fee or interest)</p>
+</li>
+<li>
+<p>Booked as part of the loan’s principal</p>
+</li>
+<li>
+<p>Added post-disbursement and only if the loan type supports it (currently: Progressive Loans)</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Creates a distinct loan transaction</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Separately tracked with its own transaction type ("Capitalized Income")</p>
+</li>
+<li>
+<p>Not merged with disbursements or repayments</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Updates the loan schedule</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Recalculates amortization and interest schedule to include the added amount in the outstanding principal</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Triggers accounting entries</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Debits "Loan Portfolio" (Asset)</p>
+</li>
+<li>
+<p>Credits "Deferred Income" (Liability)</p>
+</li>
+<li>
+<p>Does not recognize income upfront</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Initiates daily amortization</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Source for daily income recognition through "Capitalized Income Amortization" transactions</p>
+</li>
+<li>
+<p>Progressively converts the deferred amount to recognized income</p>
+</li>
+</ul>
+</div>
+</li>
+</ul>
+</div>
+<div class="sect5">
+<h6 id="_accounting_entries"><a class="link" href="#_accounting_entries">Accounting Entries</a></h6>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 50%;">
+<col style="width: 50%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Scenario</th>
+<th class="tableblock halign-left valign-top">Debit</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Credit</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Capitalized Income</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Loan Portfolio (Asset)</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Deferred Income (Liability)</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_capitalized_income_amortization"><a class="link" href="#_capitalized_income_amortization">Capitalized Income Amortization</a></h5>
+<div class="paragraph">
+<p>A Capitalized Income Amortization transaction in Apache Fineract does the following:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Recognizes Deferred Income Over Time</strong>: Transfers a portion of the capitalized income (originally posted as a liability) into recognized income (posted as interest or fee income), based on a configured daily amortization strategy.</p>
+</li>
+<li>
+<p><strong>Daily Posting</strong>: The system automatically creates this transaction each day from the date of capitalized income until the loan maturity or until the full amount is amortized. This is handled by a background job during the COB (Close of Business) process.</p>
+</li>
+<li>
+<p><strong>Uses Equal Amortization</strong>: The default and only supported strategy is Equal Amortization, which divides the total capitalized income evenly over the remaining number of days until the loan matures.</p>
+</li>
+</ul>
+</div>
+<div class="sect5">
+<h6 id="_accounting_entries_2"><a class="link" href="#_accounting_entries_2">Accounting Entries</a></h6>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 50%;">
+<col style="width: 50%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Scenario</th>
+<th class="tableblock halign-left valign-top">Debit</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Credit</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Daily amortization</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Deferred Income (Liability)</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Income from Capitalization (Income)</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect5">
+<h6 id="_stops_on_events"><a class="link" href="#_stops_on_events">Stops on Events</a></h6>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Preclosure</strong>: Triggers final amortization for remaining unrecognized income</p>
+</li>
+<li>
+<p><strong>Charge-off</strong>: Halts further amortization; the remaining deferred income is charged off</p>
+</li>
+</ul>
+</div>
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-note" title="Note"></i>
+</td>
+<td class="content">
+<div class="paragraph">
+<p>Reversal Handling: If the original Capitalized Income transaction is reversed, all associated amortization transactions are also reversed via "Capitalized Income Amortization Adjustment" transactions.</p>
+</div>
+</td>
+</tr>
+</table>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_capitalized_income_adjustment"><a class="link" href="#_capitalized_income_adjustment">Capitalized Income Adjustment</a></h5>
+<div class="paragraph">
+<p>A Capitalized Income Adjustment transaction in Apache Fineract serves to reduce the balance of an existing capitalized income transaction.</p>
+</div>
+<div class="sect5">
+<h6 id="_purpose_2"><a class="link" href="#_purpose_2">Purpose</a></h6>
+<div class="ulist">
+<ul>
+<li>
+<p>Correct overcharged or misposted capitalized income amounts</p>
+</li>
+<li>
+<p>Reflect fee waivers or negotiated reductions</p>
+</li>
+<li>
+<p>Support backdated corrections if needed</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_transaction_behavior"><a class="link" href="#_transaction_behavior">Transaction Behavior</a></h6>
+<div class="ulist">
+<ul>
+<li>
+<p>It is a credit-type transaction, reducing the capitalized income balance</p>
+</li>
+<li>
+<p>Treated similarly to other credit transactions and follows a defined allocation strategy</p>
+</li>
+<li>
+<p>Can be backdated, but not dated before the original capitalized income transaction</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_validation_rules"><a class="link" href="#_validation_rules">Validation Rules</a></h6>
+<div class="ulist">
+<ul>
+<li>
+<p>The adjustment amount must not exceed the remaining amount (original capitalized income amount minus total previous adjustments)</p>
+</li>
+<li>
+<p>Adjustment is linked to a specific Capitalized Income transaction (by ID)</p>
+</li>
+<li>
+<p>Multiple adjustments can be made against the same original transaction</p>
+</li>
+<li>
+<p>Adjustments can be reversed if needed</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_accounting_entries_3"><a class="link" href="#_accounting_entries_3">Accounting Entries</a></h6>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 33.3333%;">
+<col style="width: 33.3334%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Scenario</th>
+<th class="tableblock halign-left valign-top">Debit</th>
+<th class="tableblock halign-left valign-top">Credit</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Adjustment ≤ unrecognized balance</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Deferred Income (Liability)</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Loan Portfolio (Asset)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Adjustment > unrecognized balance</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Deferred Income (Liability)</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Loan Portfolio (Asset)</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect5">
+<h6 id="_business_event_triggers"><a class="link" href="#_business_event_triggers">Business Event Triggers</a></h6>
+<div class="ulist">
+<ul>
+<li>
+<p>Triggers "Capitalized Income Adjustment" event</p>
+</li>
+<li>
+<p>Updates loan balance and possibly loan status depending on impact</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_impact"><a class="link" href="#_impact">Impact</a></h6>
+<div class="ulist">
+<ul>
+<li>
+<p>Reduces amortization basis</p>
+</li>
+<li>
+<p>May modify future amortization amounts</p>
+</li>
+<li>
+<p>Repayment schedule is not affected directly unless recalculated manually</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_capitalized_income_amortization_adjustment"><a class="link" href="#_capitalized_income_amortization_adjustment">Capitalized Income Amortization Adjustment</a></h5>
+<div class="paragraph">
+<p>A Capitalized Income Amortization Adjustment in Apache Fineract is a special transaction type used to reverse previously recognized income from capitalized income amortization.</p>
+</div>
+<div class="sect5">
+<h6 id="_purpose_3"><a class="link" href="#_purpose_3">Purpose</a></h6>
+<div class="ulist">
+<ul>
+<li>
+<p>Automatically generated when a Capitalized Income transaction is reversed or when backdated Capitalized Income Adjustment affects amortization balances</p>
+</li>
+<li>
+<p>Reverses all already recognized portions (amortized income) linked to the original Capitalized Income transaction</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_when_it_occurs"><a class="link" href="#_when_it_occurs">When It Occurs</a></h6>
+<div class="ulist">
+<ul>
+<li>
+<p>Created by daily amortization (COB) or final amortization (triggered on loan closure, charge-off, or by any backdated transaction that affects capitalized income balances)</p>
+</li>
+<li>
+<p>Reverses previously recognized income when amortization needs to be adjusted</p>
+</li>
+<li>
+<p>Restores Deferred Income balances and reverses income recognition</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_accounting_entries_4"><a class="link" href="#_accounting_entries_4">Accounting Entries</a></h6>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 33.3333%;">
+<col style="width: 33.3334%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Transaction Type</th>
+<th class="tableblock halign-left valign-top">Debit</th>
+<th class="tableblock halign-left valign-top">Credit</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Capitalized Income Amortization Adjustment</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Income from Capitalization (Income)</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Deferred Income (Liability)</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect5">
+<h6 id="_key_characteristics"><a class="link" href="#_key_characteristics">Key Characteristics</a></h6>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>System-Generated Only</strong>: Cannot be created manually by API or UI</p>
+</li>
+<li>
+<p><strong>Ensures Accounting Integrity</strong>: Keeps amortized and unrecognized balances aligned after reversals</p>
+</li>
+<li>
+<p><strong>Non-monetary transaction - does not trigger balance changed or status update events</strong></p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_business_events"><a class="link" href="#_business_events">Business Events</a></h6>
+<div class="ulist">
+<ul>
+<li>
+<p>Triggers a new business event: Capitalized Income Amortization Adjustment</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_api_endpoints"><a class="link" href="#_api_endpoints">API Endpoints</a></h4>
+<div class="sect4">
+<h5 id="_configure_capitalized_income_on_loan_product"><a class="link" href="#_configure_capitalized_income_on_loan_product">Configure Capitalized Income on Loan Product</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Endpoint</strong>: <code>/loanproducts</code></p>
+</li>
+<li>
+<p><strong>Method</strong>: <code>POST</code></p>
+</li>
+</ul>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="err">...</span><span class="w">
+ </span><span class="nl">"enableIncomeCapitalization"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w">
+ </span><span class="nl">"capitalizedIncomeCalculationType"</span><span class="p">:</span><span class="w"> </span><span class="s2">"FLAT"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w"> </span><span class="err">when</span><span class="w"> </span><span class="err">enabled</span><span class="w">
+ </span><span class="nl">"capitalizedIncomeStrategy"</span><span class="p">:</span><span class="w"> </span><span class="s2">"EQUAL_AMORTIZATION"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w"> </span><span class="err">when</span><span class="w"> </span><span class="err">enabled</span><span class="w">
+ </span><span class="nl">"capitalizedIncomeType"</span><span class="p">:</span><span class="w"> </span><span class="s2">"FEE"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w"> </span><span class="err">when</span><span class="w"> </span><span class="err">enabled</span><span class="w">
+ </span><span class="nl">"deferredIncomeLiabilityAccountId"</span><span class="p">:</span><span class="w"> </span><span class="mi">123</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w"> </span><span class="err">when</span><span class="w"> </span><span class="err">enabled</span><span class="w">
+ </span><span class="nl">"incomeFromCapitalizationAccountId"</span><span class="p">:</span><span class="w"> </span><span class="mi">456</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w"> </span><span class="err">when</span><span class="w"> </span><span class="err">enabled</span><span class="w">
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_add_capitalized_income"><a class="link" href="#_add_capitalized_income">Add Capitalized Income</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Endpoint</strong>: <code>/loans/{loanId}/transactions?command=capitalizedIncome</code></p>
+</li>
+<li>
+<p><strong>Alternative Endpoint</strong>: <code>/loans/external-id/{loanExternalId}/transactions?command=capitalizedIncome</code></p>
+</li>
+<li>
+<p><strong>Method</strong>: <code>POST</code></p>
+</li>
+</ul>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="nl">"transactionDate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2025-05-01"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w">
+ </span><span class="nl">"dateFormat"</span><span class="p">:</span><span class="w"> </span><span class="s2">"yyyy-MM-dd"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w">
+ </span><span class="nl">"locale"</span><span class="p">:</span><span class="w"> </span><span class="s2">"en"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w">
+ </span><span class="nl">"transactionAmount"</span><span class="p">:</span><span class="w"> </span><span class="mf">100.0</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w">
+ </span><span class="nl">"paymentTypeId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Optional</span><span class="w">
+ </span><span class="nl">"note"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Capitalized income fee"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Optional</span><span class="w">
+ </span><span class="nl">"externalId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CINCOME-001"</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Optional</span><span class="w">
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_get_capitalized_income_amortization_info"><a class="link" href="#_get_capitalized_income_amortization_info">Get Capitalized Income Amortization Info</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Endpoint</strong>: <code>/loans/{loanId}/deferredincome</code></p>
+</li>
+<li>
+<p><strong>Alternative Endpoint</strong>: <code>/loans/external-id/{loanExternalId}/deferredincome</code></p>
+</li>
+<li>
+<p><strong>Method</strong>: <code>GET</code></p>
+</li>
+</ul>
+</div>
+<div class="sect5">
+<h6 id="_response_body"><a class="link" href="#_response_body">Response Body</a></h6>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="nl">"capitalizedIncomeData"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
+ </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"amount"</span><span class="p">:</span><span class="w"> </span><span class="mf">50.0</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Total</span><span class="w"> </span><span class="err">capitalized</span><span class="w"> </span><span class="err">income</span><span class="w"> </span><span class="err">amount</span><span class="w">
+ </span><span class="nl">"amortizedAmount"</span><span class="p">:</span><span class="w"> </span><span class="mf">1.1</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Amount</span><span class="w"> </span><span class="err">already</span><span class="w"> </span><span class="err">amortized</span><span class="w">
+ </span><span class="nl">"unrecognizedAmount"</span><span class="p">:</span><span class="w"> </span><span class="mf">48.9</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Amount</span><span class="w"> </span><span class="err">not</span><span class="w"> </span><span class="err">yet</span><span class="w"> </span><span class="err">amortized</span><span class="w">
+ </span><span class="nl">"amountAdjustment"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.0</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Any</span><span class="w"> </span><span class="err">adjustments</span><span class="w"> </span><span class="err">made</span><span class="w">
+ </span><span class="nl">"chargedOffAmount"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Amount</span><span class="w"> </span><span class="err">charged</span><span class="w"> </span><span class="err">off</span><span class="w"> </span><span class="err">(if</span><span class="w"> </span><span class="err">applicable)</span><span class="w">
+ </span><span class="p">}</span><span class="w">
+ </span><span class="p">]</span><span class="w">
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_add_capitalized_income_adjustment"><a class="link" href="#_add_capitalized_income_adjustment">Add Capitalized Income Adjustment</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Endpoint</strong>: <code>/loans/{loanId}/transactions/{capitalizedIncomeTransactionId}?command=capitalizedIncomeAdjustment</code></p>
+</li>
+<li>
+<p><strong>Alternative Endpoint</strong>: <code>/loans/external-id/{loanExternalId}/transactions/{capitalizedIncomeTransactionId}?command=capitalizedIncomeAdjustment</code></p>
+</li>
+<li>
+<p><strong>Method</strong>: <code>POST</code></p>
+</li>
+</ul>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="nl">"transactionDate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2025-05-01"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w">
+ </span><span class="nl">"dateFormat"</span><span class="p">:</span><span class="w"> </span><span class="s2">"yyyy-MM-dd"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w">
+ </span><span class="nl">"locale"</span><span class="p">:</span><span class="w"> </span><span class="s2">"en"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w">
+ </span><span class="nl">"transactionAmount"</span><span class="p">:</span><span class="w"> </span><span class="mf">50.0</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w">
+ </span><span class="nl">"paymentTypeId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Optional</span><span class="w">
+ </span><span class="nl">"note"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Capitalized income fee"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Optional</span><span class="w">
+ </span><span class="nl">"externalId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CINCOMEADJ-001"</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Optional</span><span class="w">
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_response_body_2"><a class="link" href="#_response_body_2">Response Body</a></h6>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="nl">"resourceId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"resourceExternalId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CINCOMEADJ-001"</span><span class="w">
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_capitalized_income_template_api_to_retrieve_limits"><a class="link" href="#_capitalized_income_template_api_to_retrieve_limits">Capitalized Income Template API (to retrieve limits)</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Endpoint</strong>: <code>/loans/{loanId}/transactions/template?command=capitalizedIncome</code></p>
+</li>
+<li>
+<p><strong>Alternative Endpoint</strong>: <code>/loans/external-id/{loanExternalId}/transactions/template?command=capitalizedIncome</code></p>
+</li>
+<li>
+<p><strong>Method</strong>: <code>GET</code></p>
+</li>
+</ul>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="nl">"paymentTypeOptions"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">List</span><span class="w"> </span><span class="err">of</span><span class="w"> </span><span class="err">available</span><span class="w"> </span><span class="err">payment</span><span class="w"> </span><span class="err">types</span><span class="w">
+ </span><span class="nl">"currency"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="err">...</span><span class="p">},</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Currency</span><span class="w"> </span><span class="err">configuration</span><span class="w">
+ </span><span class="nl">"date"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">2025</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w"> </span><span class="mi">29</span><span class="p">],</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Return</span><span class="w"> </span><span class="err">the</span><span class="w"> [...]
+ </span><span class="nl">"amount"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Return</span><span class="w"> </span><span class="err">the</span><span class="w"> </span><span class="err">maximum</span><span class="w"> </span><span class="err">amount</span><span class="w"> </span><span class="err">that</span><span class="w"> </span><span class="er [...]
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_capitalized_income_adjustment_template_api_to_retrieve_limits"><a class="link" href="#_capitalized_income_adjustment_template_api_to_retrieve_limits">Capitalized Income Adjustment Template API (to retrieve limits)</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Endpoint</strong>: <code>/loans/{loanId}/transactions/template?command=capitalizedIncomeAdjustment&transactionId={capitalizedIncomeTransactionId}</code></p>
+</li>
+<li>
+<p><strong>Alternative Endpoint</strong>: <code>/loans/external-id/{loanExternalId}/transactions/template?command=capitalizedIncomeAdjustment&transactionId={capitalizedIncomeTransactionId}</code></p>
+</li>
+<li>
+<p><strong>Method</strong>: <code>GET</code></p>
+</li>
+</ul>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="nl">"paymentTypeOptions"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">List</span><span class="w"> </span><span class="err">of</span><span class="w"> </span><span class="err">available</span><span class="w"> </span><span class="err">payment</span><span class="w"> </span><span class="err">types</span><span class="w">
+ </span><span class="nl">"currency"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="err">...</span><span class="p">},</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Currency</span><span class="w"> </span><span class="err">configuration</span><span class="w">
+ </span><span class="nl">"date"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">2025</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w"> </span><span class="mi">29</span><span class="p">],</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Return</span><span class="w"> </span><span class="err">the</span><span class="w"> [...]
+ </span><span class="nl">"amount"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Return</span><span class="w"> </span><span class="err">the</span><span class="w"> </span><span class="err">maximum</span><span class="w"> </span><span class="err">amount</span><span class="w"> </span><span class="err">that</span><span class="w"> </span><span class="er [...]
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_accounting_entries_5"><a class="link" href="#_accounting_entries_5">Accounting Entries</a></h4>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 33.3333%;">
+<col style="width: 33.3334%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Transaction Type</th>
+<th class="tableblock halign-left valign-top">Debit</th>
+<th class="tableblock halign-left valign-top">Credit</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Capitalized Income</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Loan Portfolio (Asset)</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Deferred Income (Liability)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Capitalized Income Amortization</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Deferred Income (Liability)</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Income from Capitalization (Income)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Capitalized Income Adjustment</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Deferred Income (Liability)</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Loan Portfolio (Asset)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Capitalized Income Amortization Adjustment</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Income from Capitalization (Income)</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Deferred Income (Liability)</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect3">
+<h4 id="_business_events_2"><a class="link" href="#_business_events_2">Business Events</a></h4>
+<div class="sect4">
+<h5 id="_triggered_for_capitalized_income"><a class="link" href="#_triggered_for_capitalized_income">Triggered for Capitalized Income</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p><code>LoanCapitalizedIncomeTransactionCreatedBusinessEvent</code></p>
+</li>
+<li>
+<p><code>LoanBalanceChangedBusinessEvent</code></p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_daily_amortization_2"><a class="link" href="#_daily_amortization_2">Daily Amortization</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p><code>LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent</code></p>
+</li>
+<li>
+<p><code>LoanCapitalizedIncomeAmortizationAdjustmentTransactionCreatedBusinessEvent</code></p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_capitalized_income_adjustment_2"><a class="link" href="#_capitalized_income_adjustment_2">Capitalized Income Adjustment</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p><code>LoanCapitalizedIncomeAdjustmentTransactionCreatedBusinessEvent</code></p>
+</li>
+<li>
+<p><code>LoanBalanceChangedBusinessEvent</code></p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_reversal"><a class="link" href="#_reversal">Reversal</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p><code>LoanAdjustTransactionBusinessEvent</code></p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_database_structure_2"><a class="link" href="#_database_structure_2">Database Structure</a></h4>
+<div class="sect4">
+<h5 id="_configuration_2"><a class="link" href="#_configuration_2">Configuration</a></h5>
+<div class="sect5">
+<h6 id="_stored_on_loan_product_m_product_loan"><a class="link" href="#_stored_on_loan_product_m_product_loan">Stored on Loan Product (<code>m_product_loan</code>)</a></h6>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 33.3333%;">
+<col style="width: 33.3334%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Field</th>
+<th class="tableblock halign-left valign-top">Data Type</th>
+<th class="tableblock halign-left valign-top">Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>enable_income_capitalization</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>BOOLEAN</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Enable capitalized income feature (default: <code>false</code>)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>capitalized_income_calculation_type</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>VARCHAR</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Calculation method (ENUM: <code>FLAT</code>)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>capitalized_income_strategy</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>VARCHAR</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Amortization strategy (ENUM: <code>EQUAL_AMORTIZATION</code>)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>capitalized_income_type</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>VARCHAR</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Income type (ENUM: <code>FEE</code>, <code>INTEREST</code>)</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect5">
+<h6 id="_stored_on_loan_m_loan"><a class="link" href="#_stored_on_loan_m_loan">Stored on Loan (<code>m_loan</code>)</a></h6>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 33.3333%;">
+<col style="width: 33.3334%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Field</th>
+<th class="tableblock halign-left valign-top">Data Type</th>
+<th class="tableblock halign-left valign-top">Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>enable_income_capitalization</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>BOOLEAN</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Enable capitalized income feature (default: <code>false</code>)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>capitalized_income_calculation_type</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>VARCHAR</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Calculation method (ENUM: <code>FLAT</code>)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>capitalized_income_strategy</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>VARCHAR</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Amortization strategy (ENUM: <code>EQUAL_AMORTIZATION</code>)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>capitalized_income_type</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>VARCHAR</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Income type (ENUM: <code>FEE</code>, <code>INTEREST</code>)</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_balances"><a class="link" href="#_balances">Balances</a></h5>
+<div class="sect5">
+<h6 id="_on_loan"><a class="link" href="#_on_loan">On Loan</a></h6>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 33.3333%;">
+<col style="width: 33.3334%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Field</th>
+<th class="tableblock halign-left valign-top">Data Type</th>
+<th class="tableblock halign-left valign-top">Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>capitalized_income_derived</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>DECIMAL(19,6)</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Total capitalized income amount (nullable)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>capitalized_income_adjustment_derived</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>DECIMAL(19,6)</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Total adjustment amount (nullable)</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect5">
+<h6 id="_capitalized_income_balance_m_loan_capitalized_income_balance"><a class="link" href="#_capitalized_income_balance_m_loan_capitalized_income_balance">Capitalized Income Balance (<code>m_loan_capitalized_income_balance</code>)</a></h6>
+<div class="paragraph">
+<p>Each capitalized income has its own balance (1 row for each transaction)</p>
+</div>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 33.3333%;">
+<col style="width: 33.3334%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Field</th>
+<th class="tableblock halign-left valign-top">Data Type</th>
+<th class="tableblock halign-left valign-top">Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>id</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>BIGINT</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Unique identifier (Primary Key)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>version</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>BIGINT</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Version for optimistic locking</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>loan_id</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>BIGINT</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Associated loan ID (Foreign Key, NOT NULL)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>loan_transaction_id</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>BIGINT</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Associated loan transaction ID (Foreign Key, NOT NULL)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>amount</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>DECIMAL(19,6)</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Capitalized income transaction amount (NOT NULL)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>date</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>DATE</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Capitalized income transaction date (NOT NULL)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>unrecognized_amount</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>DECIMAL(19,6)</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Amortization - not yet recognized amount (NOT NULL)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>charged_off_amount</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>DECIMAL(19,6)</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Charged-off balance (nullable)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>amount_adjustment</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>DECIMAL(19,6)</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Total adjustment amount (nullable)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>created_by</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>BIGINT</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Audit field - user who created the record</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>created_on_utc</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>DATETIME</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Creation timestamp (UTC)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>last_modified_by</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>BIGINT</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Last modifier user ID</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>last_modified_on_utc</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>DATETIME</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Last modification timestamp (UTC)</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_constraints"><a class="link" href="#_constraints">Constraints</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Foreign Key Constraints:</strong></p>
+<div class="ulist">
+<ul>
+<li>
+<p><code>loan_id</code> references <code>m_loan(id)</code></p>
+</li>
+<li>
+<p><code>loan_transaction_id</code> references <code>m_loan_transaction(id)</code></p>
+</li>
+<li>
+<p><code>created_by</code> references <code>m_appuser(id)</code></p>
+</li>
+<li>
+<p><code>last_modified_by</code> references <code>m_appuser(id)</code></p>
+</li>
+</ul>
+</div>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_notes"><a class="link" href="#_notes">Notes</a></h4>
+<div class="admonitionblock important">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-important" title="Important"></i>
+</td>
+<td class="content">
+<div class="ulist">
+<ul>
+<li>
+<p>Capitalized income transactions support backdating</p>
+</li>
+<li>
+<p>Adjustment transactions must not predate the original capitalized income</p>
+</li>
+<li>
+<p>No automatic reversal is supported; must be handled manually via dedicated transactions</p>
+</li>
+<li>
+<p>Proper GL accounts must be set for Deferred Income and Income from Capitalization to enable this functionality</p>
+</li>
+</ul>
+</div>
+</td>
+</tr>
+</table>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_buy_down_fee"><a class="link" href="#_buy_down_fee">Buy Down Fee</a></h3>
+<div class="sect3">
+<h4 id="_overview_3"><a class="link" href="#_overview_3">Overview</a></h4>
+<div class="paragraph">
+<p>Buy Down Fee is a specialized fee mechanism in Apache Fineract that allows financial institutions to collect upfront fees from borrowers to reduce their effective interest rate over the loan term. This feature is particularly designed for 0% interest "buy down" loans where a merchant fee is collected and amortized into interest/fee income over the life of the loan.</p>
+</div>
+<div class="paragraph">
+<p>The key characteristic of Buy Down Fee is that the amortized fee is <strong>NOT visible to the customer</strong> and <strong>NOT affecting the repayment schedule</strong> - it operates as a background process for proper revenue recognition while maintaining transparency in customer-facing loan terms.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_purpose_4"><a class="link" href="#_purpose_4">Purpose</a></h4>
+<div class="paragraph">
+<p>This functionality enables financial institutions to:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Interest Rate Reduction</strong>: Borrowers can reduce their effective interest rate by paying an upfront fee</p>
+</li>
+<li>
+<p><strong>Merchant Fee Support</strong>: Enables 0% interest loan products with merchant-paid fees</p>
+</li>
+<li>
+<p><strong>Revenue Recognition</strong>: Provides controlled amortization of fee income over the loan term</p>
+</li>
+<li>
+<p><strong>Customer Transparency</strong>: Fee amortization is invisible to customers, maintaining clean loan presentation</p>
+</li>
+<li>
+<p><strong>Accounting Integration</strong>: Proper journal entries and accounting treatment for fee transactions</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_supported_loan_type_2"><a class="link" href="#_supported_loan_type_2">Supported Loan Type</a></h4>
+<div class="admonitionblock important">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-important" title="Important"></i>
+</td>
+<td class="content">
+<div class="paragraph">
+<p>Buy Down Fee is only supported for loans that have <strong>all</strong> of the following:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Advanced Payment Allocation Strategy</strong> (transaction processing strategy)</p>
+</li>
+<li>
+<p><strong>Progressive Loan Schedule</strong> (loan schedule type)</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>Other transaction processing strategies or loan schedule types are not supported.</p>
+</div>
+</td>
+</tr>
+</table>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_configuration_at_loan_product_level_2"><a class="link" href="#_configuration_at_loan_product_level_2">Configuration at Loan Product Level</a></h4>
+<div class="paragraph">
+<p>Buy Down Fee must be configured on the loan product.</p>
+</div>
+<div class="paragraph">
+<p>The configuration options include:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Enable Buy Down Fee</strong>: Boolean toggle (<code>enableBuyDownFee</code>) (default: disabled)</p>
+</li>
+<li>
+<p><strong>Calculation Mode</strong>: Only "Flat" is currently supported (<code>buyDownFeeCalculationType</code>)</p>
+</li>
+<li>
+<p><strong>Amortization Strategy</strong>: Only "EQUAL_AMORTIZATION" is supported (<code>buyDownFeeStrategy</code>)</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Daily equal portions are recognized over the life of the loan</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p><strong>Income Type</strong>: Specifies allocation rule (<code>buyDownFeeIncomeType</code>)</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>Options:<br>
+* FEE<br>
+* INTEREST</p>
+</div>
+<div class="sect4">
+<h5 id="_gl_mapping_2"><a class="link" href="#_gl_mapping_2">GL Mapping</a></h5>
+<div class="paragraph">
+<p>Required GL Account mappings when Buy Down Fee is enabled:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Buy Down Expense Account</strong>: <code>buyDownExpenseAccountId</code> - mandatory when enabled</p>
+</li>
+<li>
+<p><strong>Deferred Income Liability Account</strong>: <code>deferredIncomeLiabilityAccountId</code> - mandatory when enabled</p>
+</li>
+<li>
+<p><strong>Income from Buy Down Account</strong>: <code>incomeFromBuyDownAccountId</code> - mandatory when enabled</p>
+</li>
+</ul>
+</div>
+<div class="admonitionblock important">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-important" title="Important"></i>
+</td>
+<td class="content">
+<div class="paragraph">
+<p>All GL accounts become mandatory when <code>enableBuyDownFee</code> is set to <code>true</code>.</p>
+</div>
+</td>
+</tr>
+</table>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_configuration_dependencies_2"><a class="link" href="#_configuration_dependencies_2">Configuration Dependencies</a></h5>
+<div class="paragraph">
+<p>When <code>enableBuyDownFee</code> is set to <code>true</code>, all following parameters become mandatory:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><code>buyDownFeeCalculationType</code> - must be "FLAT"</p>
+</li>
+<li>
+<p><code>buyDownFeeStrategy</code> - must be "EQUAL_AMORTIZATION"</p>
+</li>
+<li>
+<p><code>buyDownFeeIncomeType</code> - must be "FEE" or "INTEREST"</p>
+</li>
+<li>
+<p><code>buyDownExpenseAccountId</code> - must reference a valid GL account</p>
+</li>
+<li>
+<p><code>deferredIncomeLiabilityAccountId</code> - must reference a valid GL account</p>
+</li>
+<li>
+<p><code>incomeFromBuyDownAccountId</code> - must reference a valid GL account</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_validation_rules_2"><a class="link" href="#_validation_rules_2">Validation Rules</a></h4>
+<div class="sect4">
+<h5 id="_product_level_validations"><a class="link" href="#_product_level_validations">Product Level Validations</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p>Buy Down Fee can only be enabled for Progressive Loan products</p>
+</li>
+<li>
+<p>When <code>enableBuyDownFee</code> is <code>true</code>, all related parameters become mandatory</p>
+</li>
+<li>
+<p>Calculation type must be <code>FLAT</code> (other types not yet supported)</p>
+</li>
+<li>
+<p>Strategy must be <code>EQUAL_AMORTIZATION</code> (other strategies not yet supported)</p>
+</li>
+<li>
+<p>Income type must be either <code>FEE</code> or <code>INTEREST</code></p>
+</li>
+<li>
+<p>Both expense and income GL accounts must be provided and valid</p>
+</li>
+<li>
+<p>GL accounts must have correct account types (EXPENSE and INCOME respectively)</p>
+</li>
+<li>
+<p><code>deferredIncomeLiabilityAccountId</code> mapping requirements:</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Must be a valid LIABILITY type GL account</p>
+</li>
+<li>
+<p>Represents temporary holding of not-yet-recognized income</p>
+</li>
+<li>
+<p>Used to track unamortized Buy Down Fee portion</p>
+</li>
+<li>
+<p>Cannot be zero or null when Buy Down Fee is enabled</p>
+</li>
+</ul>
+</div>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_transaction_level_validations"><a class="link" href="#_transaction_level_validations">Transaction Level Validations</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p>Buy Down Fee transactions can only be added to active loans</p>
+</li>
+<li>
+<p>Transaction amount must be positive (greater than zero)</p>
+</li>
+<li>
+<p>Transaction date cannot be before the first disbursement date</p>
+</li>
+<li>
+<p>Loan must have Buy Down Fee enabled in its product configuration</p>
+</li>
+<li>
+<p>Transaction date cannot be in the future</p>
+</li>
+<li>
+<p>Client/Group must be active</p>
+</li>
+<li>
+<p>Loan must be disbursed</p>
+</li>
+<li>
+<p>Multiple Buy Down Fee transactions per loan are supported</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_adjustment_validations"><a class="link" href="#_adjustment_validations">Adjustment Validations</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p>Original Buy Down Fee transaction must exist</p>
+</li>
+<li>
+<p>Adjustment amount cannot exceed remaining balance (amount - previous adjustments)</p>
+</li>
+<li>
+<p>Adjustment date cannot be before original transaction date</p>
+</li>
+<li>
+<p>Cannot reverse Buy Down Fee transaction if it has linked adjustments</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>Buy down fee adjustments are related to the buy down fee transaction (they have relation with type ADJUSTMENT between them), and there can be more than one adjustment to the same buy down fee transaction.</p>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_error_responses"><a class="link" href="#_error_responses">Error Responses</a></h4>
+<div class="sect4">
+<h5 id="_common_error_codes"><a class="link" href="#_common_error_codes">Common Error Codes</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p><code>buy.down.fee.not.enabled</code>: Buy Down Fee not enabled for loan product</p>
+</li>
+<li>
+<p><code>cannot.be.before.first.disbursement.date</code>: Invalid transaction date</p>
+</li>
+<li>
+<p><code>cannot.be.more.than.remaining.amount</code>: Adjustment exceeds balance</p>
+</li>
+<li>
+<p><code>loan.transaction.not.found</code>: Referenced transaction not found</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_error_response_example"><a class="link" href="#_error_response_example">Error Response Example</a></h5>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="nl">"developerMessage"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Buy down fee is not enabled for this loan product"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"httpStatusCode"</span><span class="p">:</span><span class="w"> </span><span class="s2">"400"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"defaultUserMessage"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Buy down fee is not enabled for this loan product"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"userMessageGlobalisationCode"</span><span class="p">:</span><span class="w"> </span><span class="s2">"buy.down.fee.not.enabled"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"errors"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
+ </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"developerMessage"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Buy down fee is not enabled for this loan product"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"defaultUserMessage"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Buy down fee is not enabled for this loan product"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"userMessageGlobalisationCode"</span><span class="p">:</span><span class="w"> </span><span class="s2">"buy.down.fee.not.enabled"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"parameterName"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="w">
+ </span><span class="p">}</span><span class="w">
+ </span><span class="p">]</span><span class="w">
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_configuration_error_messages"><a class="link" href="#_configuration_error_messages">Configuration Error Messages</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>"Buy Down Fee calculation type is required"</strong>: Provide <code>buyDownFeeCalculationType</code> when enabling Buy Down Fee</p>
+</li>
+<li>
+<p><strong>"Buy Down Fee strategy is required"</strong>: Provide <code>buyDownFeeStrategy</code> when enabling Buy Down Fee</p>
+</li>
+<li>
+<p><strong>"Buy Down Fee income type is required"</strong>: Provide <code>buyDownFeeIncomeType</code> when enabling Buy Down Fee</p>
+</li>
+<li>
+<p><strong>"Buy Down expense account is required"</strong>: Provide valid <code>buyDownExpenseAccountId</code></p>
+</li>
+<li>
+<p><strong>"Deferred income liability account is required"</strong>: Provide valid <code>deferredIncomeLiabilityAccountId</code></p>
+</li>
+<li>
+<p><strong>"Income from Buy Down account is required"</strong>: Provide valid <code>incomeFromBuyDownAccountId</code></p>
+</li>
+<li>
+<p><strong>"Buy Down fees can only be added to active loans"</strong>: Ensure loan status is ACTIVE before adding Buy Down Fee transactions</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_behavior_and_calculations_2"><a class="link" href="#_behavior_and_calculations_2">Behavior and Calculations</a></h4>
+<div class="ulist">
+<ul>
+<li>
+<p>Buy Down Fee transactions can only be added to active loans</p>
+</li>
+<li>
+<p>Transaction amount must be positive (greater than zero)</p>
+</li>
+<li>
+<p>Transaction date cannot be before the first disbursement date</p>
+</li>
+<li>
+<p>Loan must have Buy Down Fee enabled in its product configuration</p>
+</li>
+</ul>
+</div>
+<div class="sect4">
+<h5 id="_daily_amortization_3"><a class="link" href="#_daily_amortization_3">Daily Amortization</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p>Recognized daily using the configured strategy</p>
+</li>
+<li>
+<p>Recognized portions move from Deferred Income to Income from Buy Down</p>
+</li>
+</ul>
+</div>
+<div class="sect5">
+<h6 id="_special_handling_2"><a class="link" href="#_special_handling_2">Special Handling</a></h6>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Preclosure</strong>: Remaining balance recognized in full on the preclosure date</p>
+</li>
+<li>
+<p><strong>Charge-off</strong>: Amortization stops and remaining balance is charged off</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_transaction_types_introduced_2"><a class="link" href="#_transaction_types_introduced_2">Transaction Types Introduced</a></h4>
+<div class="ulist">
+<ul>
+<li>
+<p>Buy Down Fee</p>
+</li>
+<li>
+<p>Buy Down Fee Amortization</p>
+</li>
+<li>
+<p>Buy Down Fee Adjustment</p>
+</li>
+<li>
+<p>Buy Down Fee Amortization Adjustment</p>
+</li>
+</ul>
+</div>
+<div class="sect4">
+<h5 id="_buy_down_fee_transaction"><a class="link" href="#_buy_down_fee_transaction">Buy Down Fee Transaction</a></h5>
+<div class="paragraph">
+<p>The Buy Down Fee transaction in Apache Fineract performs the following actions:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Creates a distinct loan transaction</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Separately tracked with its own transaction type ("Buy Down Fee")</p>
+</li>
+<li>
+<p>Not merged with disbursements or repayments</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Triggers accounting entries</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Debits "Buy Down Expense Account" (Expense)</p>
+</li>
+<li>
+<p>Credits "Deferred Income Liability Account" (Liability)</p>
+</li>
+<li>
+<p>Does not recognize income upfront</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Initiates daily amortization</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Source for daily income recognition through "Buy Down Fee Amortization" transactions</p>
+</li>
+<li>
+<p>Progressively converts the deferred amount to recognized income</p>
+</li>
+</ul>
+</div>
+</li>
+</ul>
+</div>
+<div class="sect5">
+<h6 id="_accounting_entries_6"><a class="link" href="#_accounting_entries_6">Accounting Entries</a></h6>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 50%;">
+<col style="width: 50%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Scenario</th>
+<th class="tableblock halign-left valign-top">Debit</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Credit</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Buy Down Fee</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Buy Down Expense Account</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Deferred Income Liability Account</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_buy_down_fee_amortization"><a class="link" href="#_buy_down_fee_amortization">Buy Down Fee Amortization</a></h5>
+<div class="paragraph">
+<p>A Buy Down Fee Amortization transaction in Apache Fineract does the following:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Recognizes Deferred Income Over Time</strong>: Transfers a portion of the buy down fee (originally posted as a liability) into recognized income, based on a configured daily amortization strategy.</p>
+</li>
+<li>
+<p><strong>Daily Posting</strong>: The system automatically creates this transaction each day from the date of buy down fee until the loan maturity or until the full amount is amortized. This is handled by a background job during the COB (Close of Business) process.</p>
+</li>
+<li>
+<p><strong>Uses Equal Amortization</strong>: The default and only supported strategy is Equal Amortization, which divides the total buy down fee evenly over the remaining number of days until the loan matures.</p>
+</li>
+</ul>
+</div>
+<div class="sect5">
+<h6 id="_accounting_entries_7"><a class="link" href="#_accounting_entries_7">Accounting Entries</a></h6>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 50%;">
+<col style="width: 50%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Scenario</th>
+<th class="tableblock halign-left valign-top">Debit</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Credit</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Daily amortization</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Deferred Income Liability Account</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Income from Buy Down Account</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect5">
+<h6 id="_stops_on_events_2"><a class="link" href="#_stops_on_events_2">Stops on Events</a></h6>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Preclosure</strong>: Triggers final amortization for remaining unrecognized income</p>
+</li>
+<li>
+<p><strong>Charge-off</strong>: Halts further amortization; the remaining deferred income is charged off using Charge-off Expense Account</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_buy_down_fee_adjustment"><a class="link" href="#_buy_down_fee_adjustment">Buy Down Fee Adjustment</a></h5>
+<div class="paragraph">
+<p>A Buy Down Fee Adjustment transaction in Apache Fineract serves to reduce the balance of an existing buy down fee transaction.</p>
+</div>
+<div class="sect5">
+<h6 id="_purpose_5"><a class="link" href="#_purpose_5">Purpose</a></h6>
+<div class="ulist">
+<ul>
+<li>
+<p>Correct overcharged or misposted buy down fee amounts</p>
+</li>
+<li>
+<p>Reflect fee waivers or negotiated reductions</p>
+</li>
+<li>
+<p>Support backdated corrections if needed</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_transaction_behavior_2"><a class="link" href="#_transaction_behavior_2">Transaction Behavior</a></h6>
+<div class="ulist">
+<ul>
+<li>
+<p>It is a credit-type transaction, reducing the buy down fee balance</p>
+</li>
+<li>
+<p>Can be backdated, but not dated before the original buy down fee transaction</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_validation_rules_3"><a class="link" href="#_validation_rules_3">Validation Rules</a></h6>
+<div class="ulist">
+<ul>
+<li>
+<p>The adjustment amount cannot exceed remaining balance (amount - previous adjustments)</p>
+</li>
+<li>
+<p>Adjustment date cannot be before original transaction date</p>
+</li>
+<li>
+<p>Adjustment date cannot be before disbursement date</p>
+</li>
+<li>
+<p>Adjustment date cannot be in the future</p>
+</li>
+<li>
+<p>Cannot reverse Buy Down Fee transaction if it has linked adjustments</p>
+</li>
+<li>
+<p>Loan must be in Active, Closed, or Overpaid status</p>
+</li>
+<li>
+<p>Buy Down Fee must be enabled on the loan product</p>
+</li>
+<li>
+<p>Loan must use Progressive Schedule</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_accounting_entries_8"><a class="link" href="#_accounting_entries_8">Accounting Entries</a></h6>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 33.3333%;">
+<col style="width: 33.3334%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Scenario</th>
+<th class="tableblock halign-left valign-top">Debit</th>
+<th class="tableblock halign-left valign-top">Credit</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Buy Down Fee Adjustment</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Deferred Income Liability Account</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Buy Down Expense Account</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_buy_down_fee_amortization_adjustment"><a class="link" href="#_buy_down_fee_amortization_adjustment">Buy Down Fee Amortization Adjustment</a></h5>
+<div class="paragraph">
+<p>A Buy Down Fee Amortization Adjustment in Apache Fineract is a special transaction type used to reverse previously recognized income from buy down fee amortization.</p>
+</div>
+<div class="sect5">
+<h6 id="_purpose_6"><a class="link" href="#_purpose_6">Purpose</a></h6>
+<div class="ulist">
+<ul>
+<li>
+<p>Automatically generated when a Buy Down Fee transaction is reversed</p>
+</li>
+<li>
+<p>Reverses all already recognized portions (amortized income) linked to the original Buy Down Fee transaction</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_when_it_occurs_2"><a class="link" href="#_when_it_occurs_2">When It Occurs</a></h6>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Trigger</strong>: Only initiated during the reversal of a Buy Down Fee transaction</p>
+</li>
+<li>
+<p>Reverses all amortization that has occurred up to that point</p>
+</li>
+<li>
+<p>Restores Deferred Income balances and reverses income recognition</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_accounting_entries_9"><a class="link" href="#_accounting_entries_9">Accounting Entries</a></h6>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 33.3333%;">
+<col style="width: 33.3334%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Transaction Type</th>
+<th class="tableblock halign-left valign-top">Debit</th>
+<th class="tableblock halign-left valign-top">Credit</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Buy Down Fee Amortization Adjustment</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Income from Buy Down Account</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Deferred Income Liability Account</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect5">
+<h6 id="_key_characteristics_2"><a class="link" href="#_key_characteristics_2">Key Characteristics</a></h6>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>System-Generated Only</strong>: Cannot be created manually by API or UI</p>
+</li>
+<li>
+<p><strong>Ensures Accounting Integrity</strong>: Keeps amortized and unrecognized balances aligned after reversals</p>
+</li>
+<li>
+<p><strong>Links to Original Amortization</strong>: Maintains traceability by referencing the reversed Buy Down Fee transaction</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_api_endpoints_2"><a class="link" href="#_api_endpoints_2">API Endpoints</a></h4>
+<div class="sect4">
+<h5 id="_configure_buy_down_fee_on_loan_product"><a class="link" href="#_configure_buy_down_fee_on_loan_product">Configure Buy Down Fee on Loan Product</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Endpoint</strong>: <code>/loanproducts</code></p>
+</li>
+<li>
+<p><strong>Method</strong>: <code>POST</code></p>
+</li>
+</ul>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="err">...</span><span class="w">
+ </span><span class="nl">"enableBuyDownFee"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w">
+ </span><span class="nl">"buyDownFeeCalculationType"</span><span class="p">:</span><span class="w"> </span><span class="s2">"FLAT"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w"> </span><span class="err">when</span><span class="w"> </span><span class="err">enabled</span><span class="w">
+ </span><span class="nl">"buyDownFeeStrategy"</span><span class="p">:</span><span class="w"> </span><span class="s2">"EQUAL_AMORTIZATION"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w"> </span><span class="err">when</span><span class="w"> </span><span class="err">enabled</span><span class="w">
+ </span><span class="nl">"buyDownFeeIncomeType"</span><span class="p">:</span><span class="w"> </span><span class="s2">"FEE"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w"> </span><span class="err">when</span><span class="w"> </span><span class="err">enabled</span><span class="w">
+ </span><span class="nl">"buyDownExpenseAccountId"</span><span class="p">:</span><span class="w"> </span><span class="mi">123</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w"> </span><span class="err">when</span><span class="w"> </span><span class="err">enabled</span><span class="w">
+ </span><span class="nl">"deferredIncomeLiabilityAccountId"</span><span class="p">:</span><span class="w"> </span><span class="mi">456</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w"> </span><span class="err">when</span><span class="w"> </span><span class="err">enabled</span><span class="w">
+ </span><span class="nl">"incomeFromBuyDownAccountId"</span><span class="p">:</span><span class="w"> </span><span class="mi">789</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w"> </span><span class="err">when</span><span class="w"> </span><span class="err">enabled</span><span class="w">
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_add_buy_down_fee"><a class="link" href="#_add_buy_down_fee">Add Buy Down Fee</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Endpoint</strong>: <code>/loans/{loanId}/transactions?command=buyDownFee</code></p>
+</li>
+<li>
+<p><strong>Alternative Endpoint</strong>: <code>/loans/external-id/{loanExternalId}/transactions?command=buyDownFee</code></p>
+</li>
+<li>
+<p><strong>Method</strong>: <code>POST</code></p>
+</li>
+</ul>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="nl">"transactionDate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2025-05-01"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w">
+ </span><span class="nl">"dateFormat"</span><span class="p">:</span><span class="w"> </span><span class="s2">"yyyy-MM-dd"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w">
+ </span><span class="nl">"locale"</span><span class="p">:</span><span class="w"> </span><span class="s2">"en"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w">
+ </span><span class="nl">"transactionAmount"</span><span class="p">:</span><span class="w"> </span><span class="mf">100.0</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w">
+ </span><span class="nl">"paymentTypeId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Optional</span><span class="w">
+ </span><span class="nl">"note"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Buy down fee"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Optional</span><span class="w">
+ </span><span class="nl">"externalId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"BUYDOWN-001"</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Optional</span><span class="w">
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_response_body_3"><a class="link" href="#_response_body_3">Response Body</a></h6>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="nl">"resourceId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"resourceExternalId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"BUYDOWN-001"</span><span class="w">
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_get_buy_down_fee_amortization_info"><a class="link" href="#_get_buy_down_fee_amortization_info">Get Buy Down Fee Amortization Info</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Endpoint</strong>: <code>/loans/{loanId}/buydown-fees</code></p>
+</li>
+<li>
+<p><strong>Alternative Endpoint</strong>: <code>/loans/external-id/{loanExternalId}/buydown-fees</code></p>
+</li>
+<li>
+<p><strong>Method</strong>: <code>GET</code></p>
+</li>
+</ul>
+</div>
+<div class="sect5">
+<h6 id="_response_body_4"><a class="link" href="#_response_body_4">Response Body</a></h6>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">[</span><span class="w">
+ </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"loanId"</span><span class="p">:</span><span class="w"> </span><span class="mi">123</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"transactionId"</span><span class="p">:</span><span class="w"> </span><span class="mi">456</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"buyDownFeeDate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2025-05-01"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"buyDownFeeAmount"</span><span class="p">:</span><span class="w"> </span><span class="mf">100.0</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"amortizedAmount"</span><span class="p">:</span><span class="w"> </span><span class="mf">5.0</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"notYetAmortizedAmount"</span><span class="p">:</span><span class="w"> </span><span class="mf">95.0</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"adjustedAmount"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.0</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"chargedOffAmount"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.0</span><span class="w">
+ </span><span class="p">}</span><span class="w">
+</span><span class="p">]</span></code></pre>
+</div>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_add_buy_down_fee_adjustment"><a class="link" href="#_add_buy_down_fee_adjustment">Add Buy Down Fee Adjustment</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Endpoint</strong>: <code>/loans/{loanId}/transactions/{buyDownFeeTransactionId}?command=buyDownFeeAdjustment</code></p>
+</li>
+<li>
+<p><strong>Alternative Endpoint</strong>: <code>/loans/external-id/{loanExternalId}/transactions/{buyDownFeeTransactionId}?command=buyDownFeeAdjustment</code></p>
+</li>
+<li>
+<p><strong>Method</strong>: <code>POST</code></p>
+</li>
+</ul>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="nl">"transactionDate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2025-05-01"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w">
+ </span><span class="nl">"dateFormat"</span><span class="p">:</span><span class="w"> </span><span class="s2">"yyyy-MM-dd"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w">
+ </span><span class="nl">"locale"</span><span class="p">:</span><span class="w"> </span><span class="s2">"en"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w">
+ </span><span class="nl">"transactionAmount"</span><span class="p">:</span><span class="w"> </span><span class="mf">50.0</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w">
+ </span><span class="nl">"paymentTypeId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Optional</span><span class="w">
+ </span><span class="nl">"note"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Buy down fee adjustment"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Optional</span><span class="w">
+ </span><span class="nl">"externalId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"BUYDOWNADJ-001"</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Optional</span><span class="w">
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_response_body_5"><a class="link" href="#_response_body_5">Response Body</a></h6>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="nl">"resourceId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"resourceExternalId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"BUYDOWNADJ-001"</span><span class="w">
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_buy_down_fee_template_api_to_retrieve_limits"><a class="link" href="#_buy_down_fee_template_api_to_retrieve_limits">Buy Down Fee Template API (to retrieve limits)</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Endpoint</strong>: <code>/loans/{loanId}/transactions/template?command=buyDownFee</code></p>
+</li>
+<li>
+<p><strong>Alternative Endpoint</strong>: <code>/loans/external-id/{loanExternalId}/transactions/template?command=buyDownFee</code></p>
+</li>
+<li>
+<p><strong>Method</strong>: <code>GET</code></p>
+</li>
+</ul>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="nl">"paymentTypeOptions"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">List</span><span class="w"> </span><span class="err">of</span><span class="w"> </span><span class="err">available</span><span class="w"> </span><span class="err">payment</span><span class="w"> </span><span class="err">types</span><span class="w">
+ </span><span class="nl">"currency"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="err">...</span><span class="p">},</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Currency</span><span class="w"> </span><span class="err">configuration</span><span class="w">
+ </span><span class="nl">"date"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">2025</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w"> </span><span class="mi">29</span><span class="p">],</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Return</span><span class="w"> </span><span class="err">the</span><span class="w"> [...]
+ </span><span class="nl">"amount"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Return</span><span class="w"> </span><span class="err">the</span><span class="w"> </span><span class="err">maximum</span><span class="w"> </span><span class="err">amount</span><span class="w"> </span><span class="err">that</span><span class="w"> </span><span class="er [...]
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_buy_down_fee_adjustment_template_api_to_retrieve_limits"><a class="link" href="#_buy_down_fee_adjustment_template_api_to_retrieve_limits">Buy Down Fee Adjustment Template API (to retrieve limits)</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Endpoint</strong>: <code>/loans/{loanId}/transactions/template?command=buyDownFeeAdjustment</code></p>
+</li>
+<li>
+<p><strong>Alternative Endpoint</strong>: <code>/loans/external-id/{loanExternalId}/transactions/template?command=buyDownFeeAdjustment</code></p>
+</li>
+<li>
+<p><strong>Method</strong>: <code>GET</code></p>
+</li>
+</ul>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="nl">"paymentTypeOptions"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">List</span><span class="w"> </span><span class="err">of</span><span class="w"> </span><span class="err">available</span><span class="w"> </span><span class="err">payment</span><span class="w"> </span><span class="err">types</span><span class="w">
+ </span><span class="nl">"currency"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="err">...</span><span class="p">},</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Currency</span><span class="w"> </span><span class="err">configuration</span><span class="w">
+ </span><span class="nl">"date"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">2025</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w"> </span><span class="mi">29</span><span class="p">],</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Return</span><span class="w"> </span><span class="err">the</span><span class="w"> [...]
+ </span><span class="nl">"amount"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Return</span><span class="w"> </span><span class="err">the</span><span class="w"> </span><span class="err">maximum</span><span class="w"> </span><span class="err">amount</span><span class="w"> </span><span class="err">that</span><span class="w"> </span><span class="er [...]
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_database_structure_3"><a class="link" href="#_database_structure_3">Database Structure</a></h4>
+<div class="sect4">
+<h5 id="_configuration_3"><a class="link" href="#_configuration_3">Configuration</a></h5>
+<div class="sect5">
+<h6 id="_loan_product_table_m_product_loan"><a class="link" href="#_loan_product_table_m_product_loan">Loan Product Table (<code>m_product_loan</code>)</a></h6>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 33.3333%;">
+<col style="width: 33.3334%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Field</th>
+<th class="tableblock halign-left valign-top">Data Type</th>
+<th class="tableblock halign-left valign-top">Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>enable_buy_down_fee</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>BOOLEAN</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Enable buy down fee feature (default: <code>false</code>)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>buy_down_fee_calculation_type</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>VARCHAR</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Calculation method (ENUM: <code>FLAT</code>)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>buy_down_fee_strategy</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>VARCHAR</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Amortization strategy (ENUM: <code>EQUAL_AMORTIZATION</code>)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>buy_down_fee_income_type</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>VARCHAR</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Income type (ENUM: <code>FEE</code>, <code>INTEREST</code>)</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_balances_2"><a class="link" href="#_balances_2">Balances</a></h5>
+<div class="sect5">
+<h6 id="_buy_down_fee_balance_table_m_loan_buy_down_fee_balance"><a class="link" href="#_buy_down_fee_balance_table_m_loan_buy_down_fee_balance">Buy Down Fee Balance Table (<code>m_loan_buy_down_fee_balance</code>)</a></h6>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 33.3333%;">
+<col style="width: 33.3334%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Field</th>
+<th class="tableblock halign-left valign-top">Data Type</th>
+<th class="tableblock halign-left valign-top">Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>id</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>BIGINT</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Primary Key (auto-increment)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>version</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>BIGINT</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Version for optimistic locking (NOT NULL)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>loan_id</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>BIGINT</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Foreign Key to <code>m_loan.id</code> (NOT NULL)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>loan_transaction_id</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>BIGINT</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Foreign Key to <code>m_loan_transaction.id</code> (NOT NULL)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>amount</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>DECIMAL(19,6)</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Buy down fee transaction amount (NOT NULL)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>date</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>DATE</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Buy down fee transaction date (NOT NULL)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>unrecognized_amount</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>DECIMAL(19,6)</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Not yet amortized amount (NOT NULL)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>charged_off_amount</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>DECIMAL(19,6)</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Charged-off balance (nullable)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>amount_adjustment</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>DECIMAL(19,6)</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Total adjustment amount (nullable)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>created_by</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>BIGINT</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">User who created the record (NOT NULL)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>created_on_utc</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>DATETIME</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Creation timestamp in UTC (NOT NULL)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>last_modified_by</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>BIGINT</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Last modifier user ID (NOT NULL)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>last_modified_on_utc</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>DATETIME</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Last modification timestamp in UTC (NOT NULL)</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_constraints_and_indexes"><a class="link" href="#_constraints_and_indexes">Constraints and Indexes</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Primary Key:</strong> <code>id</code></p>
+</li>
+<li>
+<p><strong>Foreign Keys:</strong></p>
+<div class="ulist">
+<ul>
+<li>
+<p><code>loan_id</code> → <code>m_loan(id)</code></p>
+</li>
+<li>
+<p><code>loan_transaction_id</code> → <code>m_loan_transaction(id)</code></p>
+</li>
+<li>
+<p><code>created_by</code> → <code>m_appuser(id)</code></p>
+</li>
+<li>
+<p><code>last_modified_by</code> → <code>m_appuser(id)</code></p>
+</li>
+</ul>
+</div>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_related_transaction_types"><a class="link" href="#_related_transaction_types">Related Transaction Types</a></h5>
+<div class="paragraph">
+<p>Buy Down Fee operations are stored in <code>m_loan_transaction</code> table with these transaction types:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><code>BUY_DOWN_FEE</code> - Initial buy down fee creation</p>
+</li>
+<li>
+<p><code>BUY_DOWN_FEE_ADJUSTMENT</code> - Adjustment to existing buy down fee</p>
+</li>
+<li>
+<p><code>BUY_DOWN_FEE_AMORTIZATION</code> - Daily amortization transaction</p>
+</li>
+<li>
+<p><code>BUY_DOWN_FEE_AMORTIZATION_ADJUSTMENT</code> - Adjustment to amortization</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_accounting_entries_10"><a class="link" href="#_accounting_entries_10">Accounting Entries</a></h4>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 33.3333%;">
+<col style="width: 33.3334%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Transaction Type</th>
+<th class="tableblock halign-left valign-top">Debit</th>
+<th class="tableblock halign-left valign-top">Credit</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Buy Down Fee</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Buy Down Expense Account</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Deferred Income Liability Account</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Buy Down Fee Amortization</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Deferred Income Liability Account</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Income from Buy Down Account</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Buy Down Fee Adjustment</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Deferred Income Liability Account</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Buy Down Expense Account</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Buy Down Fee Amortization Adjustment</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Income from Buy Down Account</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Deferred Income Liability Account</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+<div class="sect3">
+<h4 id="_business_events_3"><a class="link" href="#_business_events_3">Business Events</a></h4>
+<div class="sect4">
+<h5 id="_triggered_for_buy_down_fee"><a class="link" href="#_triggered_for_buy_down_fee">Triggered for Buy Down Fee</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p><code>LoanBuyDownFeeTransactionCreatedBusinessEvent</code></p>
+</li>
+<li>
+<p><code>LoanBalanceChangedBusinessEvent</code></p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_daily_amortization_4"><a class="link" href="#_daily_amortization_4">Daily Amortization</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p><code>LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent</code></p>
+</li>
+<li>
+<p><code>LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent</code></p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_buy_down_fee_adjustment_2"><a class="link" href="#_buy_down_fee_adjustment_2">Buy Down Fee Adjustment</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p><code>LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent</code></p>
+</li>
+<li>
+<p><code>LoanBalanceChangedBusinessEvent</code></p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_reversal_2"><a class="link" href="#_reversal_2">Reversal</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p><code>LoanAdjustTransactionBusinessEvent</code></p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_available_disbursement_calculation"><a class="link" href="#_available_disbursement_calculation">Available Disbursement Calculation</a></h4>
+<div class="paragraph">
+<p>Buy Down Fee does not affect the available disbursement amount calculation:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="nc">Available</span> <span class="nc">Disbursement</span> <span class="o">=</span> <span class="nc">Approved</span> <span class="nc">Loan</span> <span class="nc">Amount</span>
+ <span class="o">-</span> <span class="nc">Total</span> <span class="nc">Disbursed</span> <span class="nc">Amount</span>
+ <span class="o">-</span> <span class="nc">Total</span> <span class="nc">Capitalized</span> <span class="nc">Income</span></code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Buy Down Fee transactions are separate from the loan disbursement logic and do not reduce the available disbursement amount.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_notes_2"><a class="link" href="#_notes_2">Notes</a></h4>
+<div class="admonitionblock important">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-important" title="Important"></i>
+</td>
+<td class="content">
+<div class="ulist">
+<ul>
+<li>
+<p>Buy down fee transactions support backdating</p>
+</li>
+<li>
+<p>Adjustment transactions must not predate the original buy down fee</p>
+</li>
+<li>
+<p>No automatic reversal is supported; must be handled manually via dedicated transactions</p>
+</li>
+<li>
+<p>Proper GL accounts must be set for Buy Down Expense, Deferred Income Liability, and Income from Buy Down to enable this functionality</p>
+</li>
+</ul>
+</div>
+</td>
+</tr>
+</table>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_approved_amount_modification_on_loans"><a class="link" href="#_approved_amount_modification_on_loans">Approved amount modification on loans</a></h3>
+<div class="sect3">
+<h4 id="_overview_4"><a class="link" href="#_overview_4">Overview</a></h4>
+<div class="paragraph">
+<p>In Apache Fineract, after a loan is disbursed, it is possible to alter the principal amount that the loan was approved with. This means that the amount to be disbursed can be fine-tuned throughout the loan lifecycle. The approved loan amount can either be modified directly or indirectly through different endpoints.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_supported_loan_type_3"><a class="link" href="#_supported_loan_type_3">Supported Loan Type</a></h4>
+<div class="paragraph">
+<p>Approved amount modifications are supported on all loan types.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_business_events_4"><a class="link" href="#_business_events_4">Business Events</a></h4>
+<div class="ulist">
+<ul>
+<li>
+<p>Triggers a new business event: Loan Approved Amount Changed</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_api_endpoints_3"><a class="link" href="#_api_endpoints_3">API Endpoints</a></h4>
+<div class="sect4">
+<h5 id="_modifying_approved_amount_on_loans"><a class="link" href="#_modifying_approved_amount_on_loans">Modifying approved amount on loans</a></h5>
+<div class="paragraph">
+<p>Fineract supports the direct modification of the approved amount on loans</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Endpoint</strong>: <code>/loans/<loan_id>/approved-amount</code></p>
+</li>
+<li>
+<p><strong>Alternative Endpoint</strong>: <code>/loans/external-id/<loan_external_id>/approved-amount</code></p>
+</li>
+<li>
+<p><strong>Method</strong>: <code>PUT</code></p>
+</li>
+</ul>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="nl">"amount"</span><span class="p">:</span><span class="w"> </span><span class="mf">1000.0</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"locale"</span><span class="p">:</span><span class="w"> </span><span class="s2">"en"</span><span class="w">
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_response_body_6"><a class="link" href="#_response_body_6">Response Body</a></h6>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="nl">"changes"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"locale"</span><span class="p">:</span><span class="w"> </span><span class="s2">"en"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"newApprovedAmount"</span><span class="p">:</span><span class="w"> </span><span class="mf">1000.0</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"oldApprovedAmount"</span><span class="p">:</span><span class="w"> </span><span class="mf">1500.0</span><span class="w">
+ </span><span class="p">},</span><span class="w">
+ </span><span class="nl">"clientId"</span><span class="p">:</span><span class="w"> </span><span class="mi">6</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"groupId"</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"officeId"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"resourceExternalId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"95174ff9-1a75-4d72-a413-6f9b1cb988b7"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"resourceId"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="w">
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_validations"><a class="link" href="#_validations">Validations</a></h6>
+<div class="ulist">
+<ul>
+<li>
+<p>The approved amount of the loan cannot be lower than the <code>total principal disbursed + total expected principal + total principal from capitalized income transactions</code>.</p>
+</li>
+<li>
+<p>The approved amount of the loan cannot be set higher, than the proposed amount of the loan or if <code>allow approved/disbursed over applied amount</code> configuration is enabled then the calculated threshold.</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_modifying_available_disbursement_amount_on_loans"><a class="link" href="#_modifying_available_disbursement_amount_on_loans">Modifying available disbursement amount on loans</a></h5>
+<div class="paragraph">
+<p>Fineract supports the indirect modification of the approved amount on loans. This is called modifying the available disbursement amount.</p>
+</div>
+<div class="admonitionblock important">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-important" title="Important"></i>
+</td>
+<td class="content">
+<div class="paragraph">
+<p>Available disbursement amount is only a calculated value used by this endpoint to indirectly update the approved amount of the loan. It is not stored anywhere.</p>
+</div>
+</td>
+</tr>
+</table>
+</div>
+<div class="paragraph">
+<p>The approved amount is calculated as: <code>total principal disbursed + total expected principal + total principal from capitalized income + "amount" from the request</code>.</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Endpoint</strong>: <code>/loans/<loan_id>/available-disbursement-amount</code></p>
+</li>
+<li>
+<p><strong>Alternative Endpoint</strong>: <code>/loans/external-id/<loan_external_id>/available-disbursement-amount</code></p>
+</li>
+<li>
+<p><strong>Method</strong>: <code>PUT</code></p>
+</li>
+</ul>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="nl">"amount"</span><span class="p">:</span><span class="w"> </span><span class="mf">100.0</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"locale"</span><span class="p">:</span><span class="w"> </span><span class="s2">"en"</span><span class="w">
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_response_body_7"><a class="link" href="#_response_body_7">Response Body</a></h6>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="nl">"changes"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"locale"</span><span class="p">:</span><span class="w"> </span><span class="s2">"en"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"newApprovedAmount"</span><span class="p">:</span><span class="w"> </span><span class="mf">100.0</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"oldApprovedAmount"</span><span class="p">:</span><span class="w"> </span><span class="mf">1000.0</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"newAvailableDisbursementAmount"</span><span class="p">:</span><span class="w"> </span><span class="mf">100.0</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"oldAvailableDisbursementAmount"</span><span class="p">:</span><span class="w"> </span><span class="mf">1000.0</span><span class="w">
+ </span><span class="p">},</span><span class="w">
+ </span><span class="nl">"clientId"</span><span class="p">:</span><span class="w"> </span><span class="mi">6</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"groupId"</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"officeId"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"resourceExternalId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"95174ff9-1a75-4d72-a413-6f9b1cb988b7"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"resourceId"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="w">
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_validations_2"><a class="link" href="#_validations_2">Validations</a></h6>
+<div class="ulist">
+<ul>
+<li>
+<p>The available disbursement amount cannot be lower than 0.</p>
+</li>
+<li>
+<p>The approved amount of the loan cannot be set higher, than the proposed amount of the loan or if <code>allow approved/disbursed over applied amount</code> configuration is enabled then the calculated threshold. This means that the new available disbursement amount cannot be higher than <code>maximumLoanPrincipalThreshold - total principal disbursed - total expected principal - total principal from capitalized income</code></p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_approved_amount_history"><a class="link" href="#_approved_amount_history">Approved amount history</a></h5>
+<div class="paragraph">
+<p>Modifying the approved amount of the loan through either endpoint also creates a history entry that can be used to observe the changes overtime.</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Endpoint</strong>: <code>/loans/<loan_id>/approved-amount</code></p>
+</li>
+<li>
+<p><strong>Alternative Endpoint</strong>: <code>/loans/external-id/<loan_external_id>/approved-amount</code></p>
+</li>
+<li>
+<p><strong>Method</strong>: <code>GET</code></p>
+</li>
+</ul>
+</div>
+<div class="sect5">
+<h6 id="_response_body_8"><a class="link" href="#_response_body_8">Response Body</a></h6>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">[</span><span class="w">
+ </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"loanId"</span><span class="p">:</span><span class="w"> </span><span class="mi">152</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"externalLoanId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"9e058913-3de8-4f6e-9e09-4b2067c4bb91"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"newApprovedAmount"</span><span class="p">:</span><span class="w"> </span><span class="mf">800.000000</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"oldApprovedAmount"</span><span class="p">:</span><span class="w"> </span><span class="mf">1000.000000</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"dateOfChange"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2025-08-05T16:35:43.427229+02:00"</span><span class="w">
+ </span><span class="p">},</span><span class="w">
+ </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"loanId"</span><span class="p">:</span><span class="w"> </span><span class="mi">152</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"externalLoanId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"9e058913-3de8-4f6e-9e09-4b2067c4bb91"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"newApprovedAmount"</span><span class="p">:</span><span class="w"> </span><span class="mf">600.000000</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"oldApprovedAmount"</span><span class="p">:</span><span class="w"> </span><span class="mf">800.000000</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"dateOfChange"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2025-08-05T16:35:43.543779+02:00"</span><span class="w">
+ </span><span class="p">},</span><span class="w">
+ </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"loanId"</span><span class="p">:</span><span class="w"> </span><span class="mi">152</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"externalLoanId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"9e058913-3de8-4f6e-9e09-4b2067c4bb91"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"newApprovedAmount"</span><span class="p">:</span><span class="w"> </span><span class="mf">400.000000</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"oldApprovedAmount"</span><span class="p">:</span><span class="w"> </span><span class="mf">600.000000</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"dateOfChange"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2025-08-05T16:35:43.603855+02:00"</span><span class="w">
+ </span><span class="p">}</span><span class="w">
+</span><span class="p">]</span></code></pre>
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_backdated_interest_modification"><a class="link" href="#_backdated_interest_modification">Backdated interest modification</a></h3>
+<div class="paragraph">
+<p>In the previous implementation we were only allowing the interest rate modification from current date and from now on we will allow Interest modification backdated on progressive loans as well.</p>
+</div>
+<div class="admonitionblock important">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-important" title="Important"></i>
+</td>
+<td class="content">
+<div class="paragraph">
+<p>Only available on progressive loans.</p>
+</div>
+</td>
+</tr>
+</table>
+</div>
+<div class="sect3">
+<h4 id="_functionality"><a class="link" href="#_functionality">Functionality</a></h4>
+<div class="paragraph">
+<p>Validations updated to allow backdated interest change, even on charged-off or otherwise closed loan. Making progressive loans more flexible.</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>Interest rate can be modified from backdate any date from first disbursement date</p>
+</li>
+<li>
+<p>Interest will be affected from the applied date itself.</p>
+</li>
+<li>
+<p>Backdate can be done on already paid Installments as well</p>
+</li>
+<li>
+<p>Repayment schedule will be recalculated with New EMI and Interest from the Interest applied schedule date</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Backdated PAID Installments and Unpaid/Partial Paid Installment EMI would be changed as per the new calculated EMI</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Installments paid Interest amount will be reverse replayed as per the new Interest rate from the applied date</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Transactions will be reversed replayed if there is any change is allocations</p>
+</li>
+<li>
+<p>Accrual adjustments will be done during reverse replay (during the COB process)</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>No of Installments will remain the same, only EMI and Interest would get affected</p>
+</li>
+<li>
+<p>Backdated Interest modification allowed on the loan that is charged off</p>
+<div class="ulist">
+<ul>
+<li>
+<p>If the repayment that was made before the charge-off is reversed and replayed due to backdated Interest modification,then the accounting entry of the reversed transaction and replayed transaction should follow standard accounting rules and not charge-off accounting rules</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Backdated interest modification allowed on the loan that is overpaid, and CBR is complete.</p>
+<div class="ulist">
+<ul>
+<li>
+<p>Any action that triggers the recalculation (ex: reversal of backdated transaction) on the CBR loan will result in treating CBR as a credit transaction during reverse-replay. Same logic to be applied if the backdated interest modification is allowed on CBR loan accounts.</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Asset transfer (externalization)</p>
+<div class="ulist">
+<ul>
+<li>
+<p>If the repayment that was made before the asset owner change got reversed and replayed due to backdated Interest modification,<br>
+then, the accounting entry for the reversed transaction and replayed transaction include the tag of the current asset owner</p>
+</li>
+</ul>
+</div>
+</li>
+<li>
+<p>Since backdated Interest modification is allowed on CBR/overpaid loans, we can keep the modification open on closed loans as well</p>
+<div class="ulist">
+<ul>
+<li>
+<p>system will do the chronological reverse-replay when the backdated Interest is changed on closed loans, schedule and transaction will be allocated accordingly</p>
+</li>
+</ul>
+</div>
+</li>
+</ol>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_api_endpoints_4"><a class="link" href="#_api_endpoints_4">API endpoints</a></h4>
+<div class="sect4">
+<h5 id="_create_reschedule_loans_request_create_reschedule"><a class="link" href="#_create_reschedule_loans_request_create_reschedule">Create reschedule loans request (create reschedule)</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Endpoint</strong>: <code>/rescheduleloans</code></p>
+</li>
+<li>
+<p><strong>Method</strong>: <code>POST</code></p>
+</li>
+</ul>
+</div>
+<div class="listingblock">
+<div class="title">Example interest rate change request</div>
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="nl">"loanId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w">
+ </span><span class="nl">"newInterestRate"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w"> </span><span class="err">for</span><span class="w"> </span><span class="err">interest</span><span class="w"> </span><span class="err">rate</span><span class="w"> </span><span class="err">change</span><sp [...]
+ </span><span class="nl">"rescheduleFromDate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2024-01-01"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w">
+ </span><span class="nl">"rescheduleReasonId"</span><span class="p">:</span><span class="w"> </span><span class="mi">54</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w">
+ </span><span class="nl">"submittedOnDate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2024-01-01"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w">
+ </span><span class="nl">"dateFormat"</span><span class="p">:</span><span class="w"> </span><span class="s2">"yyyy-MM-dd"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w">
+ </span><span class="nl">"locale"</span><span class="p">:</span><span class="w"> </span><span class="s2">"en"</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w">
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+<div class="listingblock">
+<div class="title">Example interest rate change response</div>
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="nl">"resourceId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"loanId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"clientId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"officeId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="w">
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_update_reschedule_loans_request_approve_reschedule"><a class="link" href="#_update_reschedule_loans_request_approve_reschedule">Update reschedule loans request (approve reschedule)</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Endpoint</strong>: <code>/rescheduleloans/<reschedule_Id></code></p>
+</li>
+<li>
+<p><strong>Method</strong>: <code>POST</code></p>
+</li>
+</ul>
+</div>
+<div class="listingblock">
+<div class="title">Example reschedule approve request</div>
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="nl">"approvedOnDate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2024-01-01"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w"> </span><span class="err">for</span><span class="w"> </span><span class="err">approval</span><span class="w">
+ </span><span class="nl">"submittedOnDate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2024-01-01"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w">
+ </span><span class="nl">"dateFormat"</span><span class="p">:</span><span class="w"> </span><span class="s2">"yyyy-MM-dd"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w">
+ </span><span class="nl">"locale"</span><span class="p">:</span><span class="w"> </span><span class="s2">"en"</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Mandatory</span><span class="w">
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+<div class="listingblock">
+<div class="title">Example reschedule approve response</div>
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="nl">"changes"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"approvedByUserId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"approvedOnDate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2024-01-01"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"dateFormat"</span><span class="p">:</span><span class="w"> </span><span class="s2">"yyyy-MM-dd"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"locale"</span><span class="p">:</span><span class="w"> </span><span class="s2">"en"</span><span class="w">
+ </span><span class="p">},</span><span class="w">
+ </span><span class="nl">"clientId"</span><span class="p">:</span><span class="w"> </span><span class="mi">186</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"loanId"</span><span class="p">:</span><span class="w"> </span><span class="mi">188</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"officeId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"resourceId"</span><span class="p">:</span><span class="w"> </span><span class="mi">35</span><span class="w">
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_interest_rate_modification_for_progressive_loan"><a class="link" href="#_interest_rate_modification_for_progressive_loan">Interest Rate Modification For Progressive Loan</a></h3>
+<div class="sect3">
+<h4 id="_overview_5"><a class="link" href="#_overview_5">Overview</a></h4>
+<div class="paragraph">
+<p>The Original Interest Rate Modification feature allows updating the interest rate for <strong>active loans</strong>. The updated interest rate can be applied only for active loans and effective date should be in the future.</p>
+</div>
+<div class="paragraph">
+<p>This capability is introduced to support flexible interest rate management in loan lifecycles, reflecting changes due to inflation, risk reassessment, or customer-specific conditions.</p>
+</div>
+<div class="paragraph">
+<p>New Progressive Loan Interest Change Modification feature can be applied for overpaid, charged off or even backdated cases, which makes it much more usable.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_scope_and_limitations"><a class="link" href="#_scope_and_limitations">Scope and Limitations</a></h4>
+<div class="ulist">
+<ul>
+<li>
+<p>Only supported for <strong>progressive</strong> loan types.</p>
+</li>
+<li>
+<p>Loans must be <strong>disbursed</strong></p>
+</li>
+<li>
+<p>Interest rate modifications apply from a specified <strong>applied date</strong>, which can be backdated from the <strong>original disbursement date</strong> onward.</p>
+</li>
+<li>
+<p><strong>Paid EMIs (Equal Monthly Installments) and interest amounts may be affected</strong> by backdated interest rate changes.</p>
+</li>
+<li>
+<p>The modified interest rate affects <strong>EMI amounts</strong></p>
+</li>
+<li>
+<p>Installment counts are not affected</p>
+</li>
+<li>
+<p><strong>Reversals/backdated repayments</strong> are allowed after modification.</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_feature_behavior"><a class="link" href="#_feature_behavior">Feature Behavior</a></h4>
+<div class="ulist">
+<ul>
+<li>
+<p>When a new interest rate is applied, the system recalculates EMI values starting from the <strong>applied date</strong>.</p>
+</li>
+<li>
+<p>If the applied date is in the past, previously paid installments will be reprocessed under the new interest rate.</p>
+</li>
+<li>
+<p>The updated interest rate is effective <strong>from the applied date itself</strong>.</p>
+</li>
+<li>
+<p>Repayment schedule is updated using the current recalculation strategy.</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_configuration_4"><a class="link" href="#_configuration_4">Configuration</a></h4>
+<div class="paragraph">
+<p>Interest calculation and interest recalculation strategies are <strong>inherited from the loan product</strong> configuration at the time of loan application.</p>
+</div>
+<div class="paragraph">
+<p>This means:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>The interest calculation method (e.g., declining balance, flat) and</p>
+</li>
+<li>
+<p>The interest recalculation strategy</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>are <strong>fixed per loan account</strong> once the loan is created.</p>
+</div>
+<div class="paragraph">
+<p>As a result, <strong>no further changes</strong> to these configurations are possible after loan creation. Any interest rate modifications must operate within the originally defined calculation and recalculation strategies.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_api_specification"><a class="link" href="#_api_specification">API Specification</a></h4>
+<div class="sect4">
+<h5 id="_endpoint"><a class="link" href="#_endpoint">Endpoint</a></h5>
+<div class="paragraph">
+<p><code>POST <a href="https://DomainName/api/v1/rescheduleloans" class="bare">DomainName/api/v1/rescheduleloans</a></code></p>
+</div>
+<div class="sect5">
+<h6 id="_related_api_s"><a class="link" href="#_related_api_s">related API-s</a></h6>
+<div class="paragraph">
+<p>Retrieve a Loan Reschedule Request</p>
+</div>
+<div class="paragraph">
+<p><code>GET <a href="https://DomainName/api/v1/rescheduleloans/{requestId}" class="bare">DomainName/api/v1/rescheduleloans/{requestId}</a></code></p>
+</div>
+<div class="paragraph">
+<p>Retrieve a Preview of The New Loan Repayment Schedule</p>
+</div>
+<div class="paragraph">
+<p><code>GET <a href="https://DomainName/api/v1/rescheduleloans/{requestId}?command=previewLoanReschedule" class="bare">DomainName/api/v1/rescheduleloans/{requestId}?command=previewLoanReschedule</a></code></p>
+</div>
+<div class="paragraph">
+<p>Reject a Loan Reschedule Request</p>
+</div>
+<div class="paragraph">
+<p><code>POST <a href="https://DomainName/api/v1/rescheduleloans/{requestId}?command=reject" class="bare">DomainName/api/v1/rescheduleloans/{requestId}?command=reject</a></code></p>
+</div>
+<div class="paragraph">
+<p>Approve a Loan Reschedule Request</p>
+</div>
+<div class="paragraph">
+<p><code>POST <a href="https://DomainName/api/v1/rescheduleloans/{requestId}?command=approve" class="bare">DomainName/api/v1/rescheduleloans/{requestId}?command=approve</a></code></p>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_request_payload"><a class="link" href="#_request_payload">Request Payload</a></h5>
+<div class="paragraph">
+<p>The following fields are accepted in the request body for interest rate modification. All date fields must follow the format specified by the <code>dateFormat</code> field, and parsing is performed using the specified <code>locale</code>.</p>
+</div>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 20%;">
+<col style="width: 30%;">
+<col style="width: 50%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Field</th>
+<th class="tableblock halign-left valign-top">Type</th>
+<th class="tableblock halign-left valign-top">Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>loanId</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Long</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Identifier of the loan to be modified. Must refer to a disbursed progressive loan.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>newInterestRate</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">BigDecimal</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Required. The new interest rate to be applied. Must be zero or positive, and comply with the loan product’s min/max rate constraints.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>dateFormat</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">String</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Required when any date fields are provided. Defines the expected format of date values (e.g., <code>yyyy-MM-dd</code>, <code>dd-MM-yyyy</code>, etc.).</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>locale</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">String</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Required. Specifies the locale (e.g., <code>en</code>, <code>fr</code>, <code>in</code>) used for parsing numbers and dates.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>submittedOnDate</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">String</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Optional. The date the request is submitted. If provided, must match the specified <code>dateFormat</code>.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>rescheduleFromDate</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">String</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Required. The date from which the new interest rate becomes effective. Must be on or after the loan disbursement date. Format must match <code>dateFormat</code>.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>rescheduleReasonComment</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">String</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Optional. A free-text comment describing the reason for the interest rate change.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>rescheduleReasonId</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Long</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Optional. ID referencing a predefined rescheduling reason (e.g., from a dropdown or lookup table).</p></td>
+</tr>
+</tbody>
+</table>
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-note" title="Note"></i>
+</td>
+<td class="content">
+<div class="paragraph">
+<p>The following fields are <strong>not applicable</strong> in the context of an interest rate change request:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><code>adjustedDueDate</code></p>
+</li>
+<li>
+<p><code>extraTerms</code></p>
+</li>
+<li>
+<p><code>graceOnPrincipal</code></p>
+</li>
+<li>
+<p><code>graceOnInterest</code></p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>These parameters are reserved for other loan rescheduling operations.</p>
+</div>
+</td>
+</tr>
+</table>
+</div>
+<div class="sect5">
+<h6 id="_newinterestrate"><a class="link" href="#_newinterestrate">newInterestRate</a></h6>
+<div class="paragraph">
+<p>When processing an interest rate modification request, the system validates the <code>newInterestRate</code> parameter as follows:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>The value must be a valid <code>BigDecimal</code> parsed with the appropriate locale.</p>
+</li>
+<li>
+<p>The interest rate must be <strong>zero or positive</strong>.</p>
+</li>
+<li>
+<p>If defined on the loan product, the new interest rate must satisfy the following boundaries:</p>
+<div class="ulist">
+<ul>
+<li>
+<p>It must be <strong>greater than or equal to</strong> the product-level <code>minNominalInterestRatePerPeriod</code>.</p>
+</li>
+<li>
+<p>It must be <strong>less than or equal to</strong> the product-level <code>maxNominalInterestRatePerPeriod</code>.</p>
+</li>
+</ul>
+</div>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>These boundaries are enforced using the product’s configured range at the time the loan was applied. If no minimum or maximum is set on the product, only the zero-or-positive constraint is enforced.</p>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_example"><a class="link" href="#_example">Example</a></h5>
+<div class="sect5">
+<h6 id="_example_create_request"><a class="link" href="#_example_create_request">Example Create Request</a></h6>
+<div class="paragraph">
+<p><code>POST <a href="https://DomainName/api/v1/rescheduleloans" class="bare">DomainName/api/v1/rescheduleloans</a></code></p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="no">POST</span> <span class="n">rescheduleloans</span>
+<span class="nc">Content</span><span class="o">-</span><span class="nl">Type:</span> <span class="n">application</span><span class="o">/</span><span class="n">json</span></code></pre>
+</div>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="JSON">{
+"loanId": 1,
+"graceOnPrincipal": null,
+"graceOnInterest": null,
+"extraTerms": null,
+"rescheduleFromDate": "04 December 2014",
+"dateFormat": "dd MMMM yyyy",
+"locale": "en",
+"recalculateInterest": null,
+"submittedOnDate": "04 September 2014",
+"newInterestRate" : 28,
+"rescheduleReasonId": 1
+}</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Response</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="JSON">{
+ "loanId": 1,
+ "resourceId": 2
+}</code></pre>
+</div>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_example_approval"><a class="link" href="#_example_approval">Example Approval</a></h6>
+<div class="paragraph">
+<p><code>POST <a href="https://DomainName/api/v1/rescheduleloans/{requestId}?command=approve" class="bare">DomainName/api/v1/rescheduleloans/{requestId}?command=approve</a></code></p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="java"><span class="no">POST</span> <span class="n">rescheduleloans</span><span class="o">/</span><span class="mi">2</span><span class="o">?</span><span class="n">command</span><span class="o">=</span><span class="n">approve</span>
+<span class="nc">Content</span><span class="o">-</span><span class="nl">Type:</span> <span class="n">application</span><span class="o">/</span><span class="n">json</span></code></pre>
+</div>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="JSON">{
+"locale": "en",
+"dateFormat": "dd MMMM yyyy",
+"approvedOnDate": "11 September 2014"
+}</code></pre>
+</div>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="JSON">{
+"loanId": 1,
+"resourceId": 2,
+"changes": {
+"locale": "en",
+"dateFormat": "dd MMMM yyyy",
+"approvedOnDate": "11 September 2014",
+"approvedByUserId": 3
+}
+}</code></pre>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_developer_notes"><a class="link" href="#_developer_notes">Developer Notes</a></h4>
+<div class="paragraph">
+<p>The core concept is that the <code>AdvancedPaymentScheduleTransactionProcessor</code> processes transactions in order of their effective date, allowing it to handle backdated transaction cases.</p>
+</div>
+<div class="paragraph">
+<p>The transaction processor uses the <code>EMICalculator</code> to manage interest rate changes over time, ensuring that changes only affect future transactions relative to the actual processing transaction. The <code>ProgressiveLoanInterestScheduleModel</code> is responsible for holding and calculating interest for future installments.</p>
+</div>
+<div class="paragraph">
+<p>The underlying principle is to split repayment periods into smaller interest periods, enabling the calculation of interest for partial repayment periods. This approach makes it easier to adjust interest rates for specific interest periods as needed.</p>
+</div>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_contract_termination"><a class="link" href="#_contract_termination">Contract Termination</a></h3>
+<div class="sect3">
+<h4 id="_overview_6"><a class="link" href="#_overview_6">Overview</a></h4>
+<div class="paragraph">
+<p>Contract Termination in Apache Fineract is a loan management feature that allows financial institutions to terminate loan contracts. When applied to loans with unpaid installments, this functionality accelerates the maturity date and makes the outstanding loan balance immediately due as of the termination date.</p>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_purpose_7"><a class="link" href="#_purpose_7">Purpose</a></h4>
+<div class="paragraph">
+<p>This functionality enables financial institutions to:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Terminate loan contracts when required by business rules</p>
+</li>
+<li>
+<p>Accelerate payment schedules by making outstanding balances immediately due</p>
+</li>
+<li>
+<p>Maintain proper loan status tracking and accounting</p>
+</li>
+<li>
+<p>Support charge-off and recovery operations on terminated loans</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_supported_loan_type_4"><a class="link" href="#_supported_loan_type_4">Supported Loan Type</a></h4>
+<div class="admonitionblock important">
+<table>
+<tr>
+<td class="icon">
+<i class="fa icon-important" title="Important"></i>
+</td>
+<td class="content">
+<div class="paragraph">
+<p>Contract Termination is only supported for:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Progressive Loan Schedules</p>
+</li>
+<li>
+<p>Active loan accounts</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>Other loan schedule types and inactive loan states are not supported.</p>
+</div>
+</td>
+</tr>
+</table>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_business_rules"><a class="link" href="#_business_rules">Business Rules</a></h4>
+<div class="sect4">
+<h5 id="_eligibility_requirements"><a class="link" href="#_eligibility_requirements">Eligibility Requirements</a></h5>
+<div class="paragraph">
+<p>Contract termination can only be applied when:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Loan Status</strong>: The loan must be in <code>Active</code> status</p>
+</li>
+<li>
+<p><strong>Schedule Type</strong>: Only <code>Progressive</code> loan schedule type is supported</p>
+</li>
+<li>
+<p><strong>Not Charged Off</strong>: Loan must not be in charged-off state</p>
+</li>
+<li>
+<p><strong>Not Already Terminated</strong>: Loan must not already have contract termination applied</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_termination_date_rules"><a class="link" href="#_termination_date_rules">Termination Date Rules</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p>Contract termination can only be done as of the current business date</p>
+</li>
+<li>
+<p>Backdated termination is not allowed</p>
+</li>
+<li>
+<p>No future-dated termination permitted</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_schedule_impact"><a class="link" href="#_schedule_impact">Schedule Impact</a></h5>
+<div class="paragraph">
+<p>When contract termination is applied:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Maturity Acceleration</strong>: If unpaid installments exist, the loan maturity date is accelerated to the termination date</p>
+</li>
+<li>
+<p><strong>Interest Calculation</strong>: Interest is calculated only until the contract termination date</p>
+</li>
+<li>
+<p><strong>Maturity Date Updated</strong>: The loan maturity date is updated to the contract termination date</p>
+</li>
+<li>
+<p><strong>Principal Outstanding</strong>: The full outstanding principal balance becomes due</p>
+</li>
+<li>
+<p><strong>Delinquency Bucketing</strong>: Continues as per the new accelerated schedule</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_post_termination_operations"><a class="link" href="#_post_termination_operations">Post-Termination Operations</a></h5>
+<div class="paragraph">
+<p>After contract termination:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Charge-off Allowed</strong>: Terminated loans can be charged off</p>
+</li>
+<li>
+<p><strong>Charge-backs Allowed</strong>: Terminated loans support charge-back transactions</p>
+</li>
+<li>
+<p><strong>Future Installments</strong>: All installments scheduled after termination date are removed from the schedule</p>
+</li>
+<li>
+<p><strong>Accrual Activities</strong>: Accrual and accrual activity transactions stop after termination</p>
+</li>
+<li>
+<p><strong>Backdated Payments</strong>: Backdated payments and reversals are allowed</p>
+</li>
+<li>
+<p><strong>Contract Termination Reversal</strong>: The termination can be undone/reversed</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_special_handling_3"><a class="link" href="#_special_handling_3">Special Handling</a></h5>
+<div class="sect5">
+<h6 id="_post_maturity_termination"><a class="link" href="#_post_maturity_termination">Post-Maturity Termination</a></h6>
+<div class="paragraph">
+<p>If contract termination is done after the original maturity date:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>No schedule acceleration occurs (installments and maturity date remain unchanged)</p>
+</li>
+<li>
+<p>Interest calculation follows normal rules up to the original maturity date</p>
+</li>
+<li>
+<p>The loan remains in its current state without forced acceleration</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_contract_termination_reversal"><a class="link" href="#_contract_termination_reversal">Contract Termination Reversal</a></h6>
+<div class="ulist">
+<ul>
+<li>
+<p>Contract termination can be undone/reversed</p>
+</li>
+<li>
+<p>Schedule will be recalculated and reapplied accordingly</p>
+</li>
+<li>
+<p>All associated transactions are properly reversed and replayed</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_transaction_types"><a class="link" href="#_transaction_types">Transaction Types</a></h4>
+<div class="sect4">
+<h5 id="_contract_termination_transaction"><a class="link" href="#_contract_termination_transaction">Contract Termination Transaction</a></h5>
+<div class="paragraph">
+<p>The Contract Termination transaction in Apache Fineract performs the following actions:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Accelerates Payment Schedule</strong>: Makes all outstanding amounts immediately due</p>
+</li>
+<li>
+<p><strong>Creates Distinct Transaction</strong>: Tracked separately with transaction type "Contract Termination"</p>
+</li>
+<li>
+<p><strong>Updates Loan Sub-Status</strong>: Changes loan sub-status to <code>CONTRACT_TERMINATION</code> (value: 900)</p>
+</li>
+<li>
+<p><strong>Triggers Schedule Recalculation</strong>: Updates repayment schedule with accelerated terms</p>
+</li>
+<li>
+<p><strong>No Accounting Entries</strong>: Contract termination itself does not generate accounting entries</p>
+</li>
+<li>
+<p><strong>Stops Accrual Activity</strong>: Interest accrual and accrual activities cease after termination</p>
+</li>
+</ul>
+</div>
+<div class="sect5">
+<h6 id="_transaction_behavior_3"><a class="link" href="#_transaction_behavior_3">Transaction Behavior</a></h6>
+<div class="ulist">
+<ul>
+<li>
+<p>Transaction date is set to current business date</p>
+</li>
+<li>
+<p>Amount represents total outstanding balance as calculated by loan summary</p>
+</li>
+<li>
+<p>Triggers loan reprocessing for interest-bearing loans with recalculation enabled</p>
+</li>
+<li>
+<p>Non-monetary transaction (excluded from monetary transaction queries)</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_accrual_transactions"><a class="link" href="#_accrual_transactions">Accrual Transactions</a></h6>
+<div class="paragraph">
+<p>During contract termination, the system may generate:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Final accrual transaction up to termination date</p>
+</li>
+<li>
+<p>Accrual adjustment transaction if needed</p>
+</li>
+<li>
+<p>Associated journal entries</p>
+</li>
+<li>
+<p>Relevant business events for accrual processing</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_contract_termination_undo"><a class="link" href="#_contract_termination_undo">Contract Termination Undo</a></h5>
+<div class="paragraph">
+<p>The Contract Termination Undo transaction reverses a previous contract termination:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Reverses Termination Transaction</strong>: Marks the original termination as reversed</p>
+</li>
+<li>
+<p><strong>Removes Sub-Status</strong>: Restores loan to previous sub-status state</p>
+</li>
+<li>
+<p><strong>Recalculates Schedule</strong>: Regenerates original repayment schedule</p>
+</li>
+<li>
+<p><strong>Reprocesses Transactions</strong>: Re-runs transaction processing logic</p>
+</li>
+<li>
+<p><strong>Triggers Business Events</strong>: Notifies system of balance and status changes</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_api_endpoints_5"><a class="link" href="#_api_endpoints_5">API Endpoints</a></h4>
+<div class="sect4">
+<h5 id="_apply_contract_termination"><a class="link" href="#_apply_contract_termination">Apply Contract Termination</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Endpoint</strong>: <code>/loans/{loanId}?command=contractTermination</code></p>
+</li>
+<li>
+<p><strong>Alternative Endpoint</strong>: <code>/loans/external-id/{loanExternalId}?command=contractTermination</code></p>
+</li>
+<li>
+<p><strong>Method</strong>: <code>POST</code></p>
+</li>
+</ul>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="nl">"note"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Contract terminated due to default"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Optional</span><span class="w">
+ </span><span class="nl">"externalId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"95174ff9-1a75-4d72-a413-6f9b1cb988b7"</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Optional</span><span class="w">
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_response_body_9"><a class="link" href="#_response_body_9">Response Body</a></h6>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="nl">"entityId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"entityExternalId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"95174ff9-1a75-4d72-a413-6f9b1cb988b7"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"officeId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"clientId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"loanId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"changes"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"subStatus"</span><span class="p">:</span><span class="w"> </span><span class="mi">900</span><span class="w">
+ </span><span class="p">}</span><span class="w">
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_undo_contract_termination"><a class="link" href="#_undo_contract_termination">Undo Contract Termination</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Endpoint</strong>: <code>/loans/{loanId}?command=undoContractTermination</code></p>
+</li>
+<li>
+<p><strong>Alternative Endpoint</strong>: <code>/loans/external-id/{loanExternalId}?command=undoContractTermination</code></p>
+</li>
+<li>
+<p><strong>Method</strong>: <code>POST</code></p>
+</li>
+</ul>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="nl">"note"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Reversing contract termination"</span><span class="p">,</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Optional</span><span class="w">
+ </span><span class="nl">"reversalExternalId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"95174ff9-1a75-4d72-a413-6f9b1cb988b7"</span><span class="w"> </span><span class="err">//</span><span class="w"> </span><span class="err">Optional</span><span class="w">
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+<div class="sect5">
+<h6 id="_response_body_10"><a class="link" href="#_response_body_10">Response Body</a></h6>
+<div class="listingblock">
+<div class="content">
+<pre class="rouge highlight"><code data-lang="json"><span class="p">{</span><span class="w">
+ </span><span class="nl">"entityId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"entityExternalId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"95174ff9-1a75-4d72-a413-6f9b1cb988b7"</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"officeId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"clientId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"groupId"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"loanId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
+ </span><span class="nl">"changes"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
+ </span><span class="nl">"subStatus"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="w">
+ </span><span class="p">}</span><span class="w">
+</span><span class="p">}</span></code></pre>
+</div>
+</div>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_business_events_5"><a class="link" href="#_business_events_5">Business Events</a></h4>
+<div class="sect4">
+<h5 id="_triggered_for_contract_termination"><a class="link" href="#_triggered_for_contract_termination">Triggered for Contract Termination</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p><code>LoanTransactionContractTerminationPostBusinessEvent</code> - After termination processing</p>
+</li>
+<li>
+<p><code>LoanBalanceChangedBusinessEvent</code> - After termination processing</p>
+</li>
+<li>
+<p><code>LoanAdjustTransactionBusinessEvent</code> - During termination transaction processing</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_triggered_for_contract_termination_undo"><a class="link" href="#_triggered_for_contract_termination_undo">Triggered for Contract Termination Undo</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p><code>LoanUndoContractTerminationBusinessEvent</code> - Before and after undo operation</p>
+</li>
+<li>
+<p><code>LoanBalanceChangedBusinessEvent</code> - After schedule recalculation</p>
+</li>
+<li>
+<p><code>LoanAdjustTransactionBusinessEvent</code> - During reversal processing</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_database_impact"><a class="link" href="#_database_impact">Database Impact</a></h4>
+<div class="sect4">
+<h5 id="_loan_sub_status"><a class="link" href="#_loan_sub_status">Loan Sub-Status</a></h5>
+<div class="sect5">
+<h6 id="_updated_on_loan_m_loan"><a class="link" href="#_updated_on_loan_m_loan">Updated on Loan (<code>m_loan</code>)</a></h6>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 33.3333%;">
+<col style="width: 33.3334%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Field</th>
+<th class="tableblock halign-left valign-top">Data Type</th>
+<th class="tableblock halign-left valign-top">Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>sub_status_enum</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>SMALLINT</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Set to 900 (CONTRACT_TERMINATION) when terminated, reset to NULL when undone</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_transaction_records"><a class="link" href="#_transaction_records">Transaction Records</a></h5>
+<div class="sect5">
+<h6 id="_loan_transaction_m_loan_transaction"><a class="link" href="#_loan_transaction_m_loan_transaction">Loan Transaction (<code>m_loan_transaction</code>)</a></h6>
+<table class="tableblock frame-all grid-all stripes-even stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 33.3333%;">
+<col style="width: 33.3334%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Field</th>
+<th class="tableblock halign-left valign-top">Data Type</th>
+<th class="tableblock halign-left valign-top">Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>transaction_type_enum</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>SMALLINT</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Set to 38 (CONTRACT_TERMINATION)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>transaction_date</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>DATE</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Current business date</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>amount</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>DECIMAL(19,6)</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Total outstanding balance</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>is_reversed</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock"><code>BOOLEAN</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Set to TRUE when undone</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_validation_rules_4"><a class="link" href="#_validation_rules_4">Validation Rules</a></h4>
+<div class="sect4">
+<h5 id="_contract_termination_validation"><a class="link" href="#_contract_termination_validation">Contract Termination Validation</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p>Loan must be in active status (<code>loan.isOpen()</code>)</p>
+</li>
+<li>
+<p>Loan product must use Progressive schedule type</p>
+</li>
+<li>
+<p>Loan must not be charged off (<code>!loan.isChargedOff()</code>)</p>
+</li>
+<li>
+<p>Loan must not already be terminated (<code>!loan.isContractTermination()</code>)</p>
+</li>
+<li>
+<p>Client or group must be active</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_contract_termination_undo_validation"><a class="link" href="#_contract_termination_undo_validation">Contract Termination Undo Validation</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p>Original contract termination transaction must exist</p>
+</li>
+<li>
+<p>Transaction must not be already reversed</p>
+</li>
+<li>
+<p>Proper permissions required for reversal operations</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect3">
+<h4 id="_integration_points"><a class="link" href="#_integration_points">Integration Points</a></h4>
+<div class="sect4">
+<h5 id="_charge_operations"><a class="link" href="#_charge_operations">Charge Operations</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p>Charge-off operations can be performed on terminated loans</p>
+</li>
+<li>
+<p>Charge-back transactions are supported on terminated loans</p>
+</li>
+<li>
+<p>Charge adjustments follow normal business rules</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_delinquency_management"><a class="link" href="#_delinquency_management">Delinquency Management</a></h5>
+<div class="ulist">
+<ul>
+<li>
+<p>Delinquency bucketing continues based on accelerated schedule</p>
+</li>
+<li>
+<p>Delinquency calculations use the new due dates from termination</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect4">
+<h5 id="_accounting_integration"><a class="link" href="#_accounting_integration">Accounting Integration</a></h5>
+<div class="ulist">
+<ul>
+<li>
... 22200 lines suppressed ...