diff --git a/front/.bowerrc b/front/.bowerrc
new file mode 100755
index 0000000..69fad35
--- /dev/null
+++ b/front/.bowerrc
@@ -0,0 +1,3 @@
+{
+ "directory": "bower_components"
+}
diff --git a/front/.editorconfig b/front/.editorconfig
new file mode 100755
index 0000000..e717f5e
--- /dev/null
+++ b/front/.editorconfig
@@ -0,0 +1,13 @@
+# http://editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/front/.gitignore b/front/.gitignore
new file mode 100755
index 0000000..1f309e5
--- /dev/null
+++ b/front/.gitignore
@@ -0,0 +1,6 @@
+node_modules/
+bower_components/
+.sass-cache/
+.idea/
+.tmp/
+dist/
diff --git a/front/.jshintrc b/front/.jshintrc
new file mode 100755
index 0000000..f230768
--- /dev/null
+++ b/front/.jshintrc
@@ -0,0 +1,17 @@
+{
+ "strict": true,
+ "bitwise": true,
+ "curly": true,
+ "eqeqeq": true,
+ "latedef": false,
+ "noarg": true,
+ "undef": true,
+ "unused": true,
+ "validthis": true,
+ "jasmine": true,
+ "globals": {
+ "angular": false,
+ "inject": false,
+ "module": false
+ }
+}
diff --git a/front/.yo-rc.json b/front/.yo-rc.json
new file mode 100755
index 0000000..117f89c
--- /dev/null
+++ b/front/.yo-rc.json
@@ -0,0 +1,70 @@
+{
+ "generator-gulp-angular": {
+ "version": "0.12.1",
+ "props": {
+ "angularVersion": "~1.4.0",
+ "angularModules": [
+ {
+ "key": "animate",
+ "module": "ngAnimate"
+ },
+ {
+ "key": "cookies",
+ "module": "ngCookies"
+ },
+ {
+ "key": "touch",
+ "module": "ngTouch"
+ },
+ {
+ "key": "sanitize",
+ "module": "ngSanitize"
+ }
+ ],
+ "jQuery": {
+ "key": "jquery2"
+ },
+ "resource": {
+ "key": "angular-resource",
+ "module": "ngResource"
+ },
+ "router": {
+ "key": "angular-route",
+ "module": "ngRoute"
+ },
+ "ui": {
+ "key": "bootstrap",
+ "module": null
+ },
+ "bootstrapComponents": {
+ "key": "none",
+ "module": null
+ },
+ "cssPreprocessor": {
+ "key": "less",
+ "extension": "less"
+ },
+ "jsPreprocessor": {
+ "key": "none",
+ "extension": "js",
+ "srcExtension": "js"
+ },
+ "htmlPreprocessor": {
+ "key": "none",
+ "extension": "html"
+ },
+ "foundationComponents": {
+ "name": null,
+ "version": null,
+ "key": null,
+ "module": null
+ },
+ "paths": {
+ "src": "src",
+ "dist": "dist",
+ "e2e": "e2e",
+ "tmp": ".tmp"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/front/bower.json b/front/bower.json
new file mode 100755
index 0000000..d097a09
--- /dev/null
+++ b/front/bower.json
@@ -0,0 +1,30 @@
+{
+ "name": "loanWizard",
+ "version": "0.0.0",
+ "dependencies": {
+ "angular-animate": "~1.4.0",
+ "angular-cookies": "~1.4.0",
+ "angular-touch": "~1.4.0",
+ "angular-sanitize": "~1.4.0",
+ "jquery": "~2.1.4",
+ "angular-resource": "~1.4.0",
+ "angular-route": "~1.4.0",
+ "bootstrap": "~3.3.4",
+ "malarkey": "yuanqing/malarkey#~1.3.0",
+ "toastr": "~2.1.1",
+ "moment": "~2.10.3",
+ "animate.css": "~3.3.0",
+ "angular": "~1.4.0",
+ "Chart.js": "~1.0.2",
+ "eonasdan-bootstrap-datetimepicker": "~4.14.30",
+ "loaders.css": "0.1.0",
+ "underscore": "~1.8.3"
+ },
+ "devDependencies": {
+ "angular-mocks": "~1.4.0"
+ },
+ "resolutions": {
+ "jquery": "~2.1.4",
+ "angular": "~1.4.0"
+ }
+}
diff --git a/front/e2e/.jshintrc b/front/e2e/.jshintrc
new file mode 100755
index 0000000..5540069
--- /dev/null
+++ b/front/e2e/.jshintrc
@@ -0,0 +1,10 @@
+{
+ "extends": "../.jshintrc",
+ "globals": {
+ "browser": false,
+ "element": false,
+ "by": false,
+ "$": false,
+ "$$": false
+ }
+}
diff --git a/front/e2e/main.po.js b/front/e2e/main.po.js
new file mode 100755
index 0000000..0f0428c
--- /dev/null
+++ b/front/e2e/main.po.js
@@ -0,0 +1,15 @@
+/**
+ * This file uses the Page Object pattern to define the main page for tests
+ * https://docs.google.com/presentation/d/1B6manhG0zEXkC-H-tPo2vwU06JhL8w9-XCF9oehXzAQ
+ */
+
+'use strict';
+
+var MainPage = function() {
+ this.jumbEl = element(by.css('.jumbotron'));
+ this.h1El = this.jumbEl.element(by.css('h1'));
+ this.imgEl = this.jumbEl.element(by.css('img'));
+ this.thumbnailEls = element(by.css('body')).all(by.repeater('awesomeThing in main.awesomeThings'));
+};
+
+module.exports = new MainPage();
diff --git a/front/e2e/main.spec.js b/front/e2e/main.spec.js
new file mode 100755
index 0000000..ef2e5c1
--- /dev/null
+++ b/front/e2e/main.spec.js
@@ -0,0 +1,21 @@
+'use strict';
+
+describe('The main view', function () {
+ var page;
+
+ beforeEach(function () {
+ browser.get('/index.html');
+ page = require('./main.po');
+ });
+
+ it('should include jumbotron with correct data', function() {
+ expect(page.h1El.getText()).toBe('\'Allo, \'Allo!');
+ expect(page.imgEl.getAttribute('src')).toMatch(/assets\/images\/yeoman.png$/);
+ expect(page.imgEl.getAttribute('alt')).toBe('I\'m Yeoman');
+ });
+
+ it('should list more than 5 awesome things', function () {
+ expect(page.thumbnailEls.count()).toBeGreaterThan(5);
+ });
+
+});
diff --git a/front/gulp/.jshintrc b/front/gulp/.jshintrc
new file mode 100755
index 0000000..072135c
--- /dev/null
+++ b/front/gulp/.jshintrc
@@ -0,0 +1,4 @@
+{
+ "extends": "../.jshintrc",
+ "node": true
+}
diff --git a/front/gulp/build.js b/front/gulp/build.js
new file mode 100755
index 0000000..0e75010
--- /dev/null
+++ b/front/gulp/build.js
@@ -0,0 +1,94 @@
+'use strict';
+
+var path = require('path');
+var gulp = require('gulp');
+var conf = require('./conf');
+
+var $ = require('gulp-load-plugins')({
+ pattern: ['gulp-*', 'main-bower-files', 'uglify-save-license', 'del']
+});
+
+gulp.task('partials', function () {
+ return gulp.src([
+ path.join(conf.paths.src, '/app/**/*.html'),
+ path.join(conf.paths.tmp, '/serve/app/**/*.html')
+ ])
+ .pipe($.minifyHtml({
+ empty: true,
+ spare: true,
+ quotes: true
+ }))
+ .pipe($.angularTemplatecache('templateCacheHtml.js', {
+ module: 'loanWizard',
+ root: 'app'
+ }))
+ .pipe(gulp.dest(conf.paths.tmp + '/partials/'));
+});
+
+gulp.task('html', ['inject', 'partials'], function () {
+ var partialsInjectFile = gulp.src(path.join(conf.paths.tmp, '/partials/templateCacheHtml.js'), { read: false });
+ var partialsInjectOptions = {
+ starttag: '',
+ ignorePath: path.join(conf.paths.tmp, '/partials'),
+ addRootSlash: false
+ };
+
+ var htmlFilter = $.filter('*.html');
+ var jsFilter = $.filter('**/*.js');
+ var cssFilter = $.filter('**/*.css');
+ var assets;
+
+ return gulp.src(path.join(conf.paths.tmp, '/serve/*.html'))
+ .pipe($.inject(partialsInjectFile, partialsInjectOptions))
+ .pipe(assets = $.useref.assets())
+ .pipe($.rev())
+ .pipe(jsFilter)
+ .pipe($.ngAnnotate())
+ .pipe($.uglify({ preserveComments: $.uglifySaveLicense })).on('error', conf.errorHandler('Uglify'))
+ .pipe(jsFilter.restore())
+ .pipe(cssFilter)
+ .pipe($.replace('../../bower_components/bootstrap/fonts/', '../fonts/'))
+ .pipe($.csso())
+ .pipe(cssFilter.restore())
+ .pipe(assets.restore())
+ .pipe($.useref())
+ .pipe($.revReplace())
+ .pipe(htmlFilter)
+ .pipe($.minifyHtml({
+ empty: true,
+ spare: true,
+ quotes: true,
+ conditionals: true
+ }))
+ .pipe(htmlFilter.restore())
+ .pipe(gulp.dest(path.join(conf.paths.dist, '/')))
+ .pipe($.size({ title: path.join(conf.paths.dist, '/'), showFiles: true }));
+});
+
+// Only applies for fonts from bower dependencies
+// Custom fonts are handled by the "other" task
+gulp.task('fonts', function () {
+ return gulp.src($.mainBowerFiles())
+ .pipe($.filter('**/*.{eot,svg,ttf,woff,woff2}'))
+ .pipe($.flatten())
+ .pipe(gulp.dest(path.join(conf.paths.dist, '/fonts/')));
+});
+
+gulp.task('other', function () {
+ var fileFilter = $.filter(function (file) {
+ return file.stat.isFile();
+ });
+
+ return gulp.src([
+ path.join(conf.paths.src, '/**/*'),
+ path.join('!' + conf.paths.src, '/**/*.{html,css,js,less}')
+ ])
+ .pipe(fileFilter)
+ .pipe(gulp.dest(path.join(conf.paths.dist, '/')));
+});
+
+gulp.task('clean', function (done) {
+ $.del([path.join(conf.paths.dist, '/'), path.join(conf.paths.tmp, '/')], done);
+});
+
+gulp.task('build', ['html', 'fonts', 'other']);
diff --git a/front/gulp/conf.js b/front/gulp/conf.js
new file mode 100755
index 0000000..8f08ac6
--- /dev/null
+++ b/front/gulp/conf.js
@@ -0,0 +1,41 @@
+/**
+ * This file contains the variables used in other gulp files
+ * which defines tasks
+ * By design, we only put there very generic config values
+ * which are used in several places to keep good readability
+ * of the tasks
+ */
+
+var gutil = require('gulp-util');
+
+/**
+ * The main paths of your project handle these with care
+ */
+exports.paths = {
+ src: 'src',
+ dist: 'dist',
+ tmp: '.tmp',
+ e2e: 'e2e'
+};
+
+/**
+ * Wiredep is the lib which inject bower dependencies in your project
+ * Mainly used to inject script tags in the index.html but also used
+ * to inject css preprocessor deps and js files in karma
+ */
+exports.wiredep = {
+ exclude: [/bootstrap.js$/, /bootstrap\.css/],
+ directory: 'bower_components'
+};
+
+/**
+ * Common implementation for an error handler of a Gulp plugin
+ */
+exports.errorHandler = function(title) {
+ 'use strict';
+
+ return function(err) {
+ gutil.log(gutil.colors.red('[' + title + ']'), err.toString());
+ this.emit('end');
+ };
+};
diff --git a/front/gulp/e2e-tests.js b/front/gulp/e2e-tests.js
new file mode 100755
index 0000000..3a66702
--- /dev/null
+++ b/front/gulp/e2e-tests.js
@@ -0,0 +1,38 @@
+'use strict';
+
+var path = require('path');
+var gulp = require('gulp');
+var conf = require('./conf');
+
+var browserSync = require('browser-sync');
+
+var $ = require('gulp-load-plugins')();
+
+// Downloads the selenium webdriver
+gulp.task('webdriver-update', $.protractor.webdriver_update);
+
+gulp.task('webdriver-standalone', $.protractor.webdriver_standalone);
+
+function runProtractor (done) {
+ var params = process.argv;
+ var args = params.length > 3 ? [params[3], params[4]] : [];
+
+ gulp.src(path.join(conf.paths.e2e, '/**/*.js'))
+ .pipe($.protractor.protractor({
+ configFile: 'protractor.conf.js',
+ args: args
+ }))
+ .on('error', function (err) {
+ // Make sure failed tests cause gulp to exit non-zero
+ throw err;
+ })
+ .on('end', function () {
+ // Close browser sync server
+ browserSync.exit();
+ done();
+ });
+}
+
+gulp.task('protractor', ['protractor:src']);
+gulp.task('protractor:src', ['serve:e2e', 'webdriver-update'], runProtractor);
+gulp.task('protractor:dist', ['serve:e2e-dist', 'webdriver-update'], runProtractor);
diff --git a/front/gulp/inject.js b/front/gulp/inject.js
new file mode 100755
index 0000000..f1189f9
--- /dev/null
+++ b/front/gulp/inject.js
@@ -0,0 +1,36 @@
+'use strict';
+
+var path = require('path');
+var gulp = require('gulp');
+var conf = require('./conf');
+
+var $ = require('gulp-load-plugins')();
+
+var wiredep = require('wiredep').stream;
+var _ = require('lodash');
+
+gulp.task('inject', ['scripts', 'styles'], function () {
+ var injectStyles = gulp.src([
+ path.join(conf.paths.tmp, '/serve/app/**/*.css'),
+ path.join('!' + conf.paths.tmp, '/serve/app/vendor.css')
+ ], { read: false });
+
+ var injectScripts = gulp.src([
+ path.join(conf.paths.src, '/app/**/*.module.js'),
+ path.join(conf.paths.src, '/app/**/*.js'),
+ path.join('!' + conf.paths.src, '/app/**/*.spec.js'),
+ path.join('!' + conf.paths.src, '/app/**/*.mock.js')
+ ])
+ .pipe($.angularFilesort()).on('error', conf.errorHandler('AngularFilesort'));
+
+ var injectOptions = {
+ ignorePath: [conf.paths.src, path.join(conf.paths.tmp, '/serve')],
+ addRootSlash: false
+ };
+
+ return gulp.src(path.join(conf.paths.src, '/*.html'))
+ .pipe($.inject(injectStyles, injectOptions))
+ .pipe($.inject(injectScripts, injectOptions))
+ .pipe(wiredep(_.extend({}, conf.wiredep)))
+ .pipe(gulp.dest(path.join(conf.paths.tmp, '/serve')));
+});
diff --git a/front/gulp/scripts.js b/front/gulp/scripts.js
new file mode 100755
index 0000000..7a3e536
--- /dev/null
+++ b/front/gulp/scripts.js
@@ -0,0 +1,17 @@
+'use strict';
+
+var path = require('path');
+var gulp = require('gulp');
+var conf = require('./conf');
+
+var browserSync = require('browser-sync');
+
+var $ = require('gulp-load-plugins')();
+
+gulp.task('scripts', function () {
+ return gulp.src(path.join(conf.paths.src, '/app/**/*.js'))
+ .pipe($.jshint())
+ .pipe($.jshint.reporter('jshint-stylish'))
+ .pipe(browserSync.reload({ stream: true }))
+ .pipe($.size())
+});
diff --git a/front/gulp/server.js b/front/gulp/server.js
new file mode 100755
index 0000000..7c03f18
--- /dev/null
+++ b/front/gulp/server.js
@@ -0,0 +1,63 @@
+'use strict';
+
+var path = require('path');
+var gulp = require('gulp');
+var conf = require('./conf');
+
+var browserSync = require('browser-sync');
+var browserSyncSpa = require('browser-sync-spa');
+
+var util = require('util');
+
+var proxyMiddleware = require('http-proxy-middleware');
+
+function browserSyncInit(baseDir, browser) {
+ browser = browser === undefined ? 'default' : browser;
+
+ var routes = null;
+ if(baseDir === conf.paths.src || (util.isArray(baseDir) && baseDir.indexOf(conf.paths.src) !== -1)) {
+ routes = {
+ '/bower_components': 'bower_components'
+ };
+ }
+
+ var server = {
+ baseDir: baseDir,
+ routes: routes
+ };
+
+ /*
+ * You can add a proxy to your backend by uncommenting the line bellow.
+ * You just have to configure a context which will we redirected and the target url.
+ * Example: $http.get('/users') requests will be automatically proxified.
+ *
+ * For more details and option, https://github.com/chimurai/http-proxy-middleware/blob/v0.0.5/README.md
+ */
+ // server.middleware = proxyMiddleware('/users', {target: 'http://jsonplaceholder.typicode.com', proxyHost: 'jsonplaceholder.typicode.com'});
+
+ browserSync.instance = browserSync.init({
+ startPath: '/',
+ server: server,
+ browser: browser
+ });
+}
+
+browserSync.use(browserSyncSpa({
+ selector: '[ng-app]'// Only needed for angular apps
+}));
+
+gulp.task('serve', ['watch'], function () {
+ browserSyncInit([path.join(conf.paths.tmp, '/serve'), conf.paths.src]);
+});
+
+gulp.task('serve:dist', ['build'], function () {
+ browserSyncInit(conf.paths.dist);
+});
+
+gulp.task('serve:e2e', ['inject'], function () {
+ browserSyncInit([conf.paths.tmp + '/serve', conf.paths.src], []);
+});
+
+gulp.task('serve:e2e-dist', ['build'], function () {
+ browserSyncInit(conf.paths.dist, []);
+});
diff --git a/front/gulp/styles.js b/front/gulp/styles.js
new file mode 100755
index 0000000..8a5d20c
--- /dev/null
+++ b/front/gulp/styles.js
@@ -0,0 +1,49 @@
+'use strict';
+
+var path = require('path');
+var gulp = require('gulp');
+var conf = require('./conf');
+
+var browserSync = require('browser-sync');
+
+var $ = require('gulp-load-plugins')();
+
+var wiredep = require('wiredep').stream;
+var _ = require('lodash');
+
+gulp.task('styles', function () {
+ var lessOptions = {
+ options: [
+ 'bower_components',
+ path.join(conf.paths.src, '/app')
+ ]
+ };
+
+ var injectFiles = gulp.src([
+ path.join(conf.paths.src, '/app/**/*.less'),
+ path.join('!' + conf.paths.src, '/app/index.less')
+ ], { read: false });
+
+ var injectOptions = {
+ transform: function(filePath) {
+ filePath = filePath.replace(conf.paths.src + '/app/', '');
+ return '@import "' + filePath + '";';
+ },
+ starttag: '// injector',
+ endtag: '// endinjector',
+ addRootSlash: false
+ };
+
+
+ return gulp.src([
+ path.join(conf.paths.src, '/app/index.less')
+ ])
+ .pipe($.inject(injectFiles, injectOptions))
+ .pipe(wiredep(_.extend({}, conf.wiredep)))
+ .pipe($.sourcemaps.init())
+ .pipe($.less(lessOptions)).on('error', conf.errorHandler('Less'))
+ .pipe($.autoprefixer()).on('error', conf.errorHandler('Autoprefixer'))
+ .pipe($.sourcemaps.write())
+ .pipe(gulp.dest(path.join(conf.paths.tmp, '/serve/app/')))
+ .pipe(browserSync.reload({ stream: true }));
+});
diff --git a/front/gulp/unit-tests.js b/front/gulp/unit-tests.js
new file mode 100755
index 0000000..5e6aa75
--- /dev/null
+++ b/front/gulp/unit-tests.js
@@ -0,0 +1,25 @@
+'use strict';
+
+var path = require('path');
+var gulp = require('gulp');
+var conf = require('./conf');
+
+var karma = require('karma');
+
+function runTests (singleRun, done) {
+ karma.server.start({
+ configFile: path.join(__dirname, '/../karma.conf.js'),
+ singleRun: singleRun,
+ autoWatch: !singleRun
+ }, function() {
+ done();
+ });
+}
+
+gulp.task('test', ['scripts'], function(done) {
+ runTests(true, done);
+});
+
+gulp.task('test:auto', ['watch'], function(done) {
+ runTests(false, done);
+});
diff --git a/front/gulp/watch.js b/front/gulp/watch.js
new file mode 100755
index 0000000..8bc37f1
--- /dev/null
+++ b/front/gulp/watch.js
@@ -0,0 +1,39 @@
+'use strict';
+
+var path = require('path');
+var gulp = require('gulp');
+var conf = require('./conf');
+
+var browserSync = require('browser-sync');
+
+function isOnlyChange(event) {
+ return event.type === 'changed';
+}
+
+gulp.task('watch', ['inject'], function () {
+
+ gulp.watch([path.join(conf.paths.src, '/*.html'), 'bower.json'], ['inject']);
+
+ gulp.watch([
+ path.join(conf.paths.src, '/app/**/*.css'),
+ path.join(conf.paths.src, '/app/**/*.less')
+ ], function(event) {
+ if(isOnlyChange(event)) {
+ gulp.start('styles');
+ } else {
+ gulp.start('inject');
+ }
+ });
+
+ gulp.watch(path.join(conf.paths.src, '/app/**/*.js'), function(event) {
+ if(isOnlyChange(event)) {
+ gulp.start('scripts');
+ } else {
+ gulp.start('inject');
+ }
+ });
+
+ gulp.watch(path.join(conf.paths.src, '/app/**/*.html'), function(event) {
+ browserSync.reload(event.path);
+ });
+});
diff --git a/front/gulpfile.js b/front/gulpfile.js
new file mode 100755
index 0000000..5669b5d
--- /dev/null
+++ b/front/gulpfile.js
@@ -0,0 +1,29 @@
+/**
+ * Welcome to your gulpfile!
+ * The gulp tasks are splitted in several files in the gulp directory
+ * because putting all here was really too long
+ */
+
+'use strict';
+
+var gulp = require('gulp');
+var wrench = require('wrench');
+
+/**
+ * This will load all js or coffee files in the gulp directory
+ * in order to load all gulp tasks
+ */
+wrench.readdirSyncRecursive('./gulp').filter(function(file) {
+ return (/\.(js|coffee)$/i).test(file);
+}).map(function(file) {
+ require('./gulp/' + file);
+});
+
+
+/**
+ * Default task clean temporaries directories and launch the
+ * main optimization build task
+ */
+gulp.task('default', ['clean'], function () {
+ gulp.start('build');
+});
diff --git a/front/karma.conf.js b/front/karma.conf.js
new file mode 100755
index 0000000..cad0589
--- /dev/null
+++ b/front/karma.conf.js
@@ -0,0 +1,74 @@
+'use strict';
+
+var path = require('path');
+var conf = require('./gulp/conf');
+
+var _ = require('lodash');
+var wiredep = require('wiredep');
+
+function listFiles() {
+ var wiredepOptions = _.extend({}, conf.wiredep, {
+ dependencies: true,
+ devDependencies: true
+ });
+
+ return wiredep(wiredepOptions).js
+ .concat([
+ path.join(conf.paths.src, '/app/**/*.module.js'),
+ path.join(conf.paths.src, '/app/**/*.js'),
+ path.join(conf.paths.src, '/**/*.spec.js'),
+ path.join(conf.paths.src, '/**/*.mock.js'),
+ path.join(conf.paths.src, '/**/*.html')
+ ]);
+}
+
+module.exports = function(config) {
+
+ var configuration = {
+ files: listFiles(),
+
+ singleRun: true,
+
+ autoWatch: false,
+
+ frameworks: ['jasmine', 'angular-filesort'],
+
+ angularFilesort: {
+ whitelist: [path.join(conf.paths.src, '/**/!(*.html|*.spec|*.mock).js')]
+ },
+
+ ngHtml2JsPreprocessor: {
+ stripPrefix: 'src/',
+ moduleName: 'loanWizard'
+ },
+
+ browsers : ['PhantomJS'],
+
+ plugins : [
+ 'karma-phantomjs-launcher',
+ 'karma-angular-filesort',
+ 'karma-jasmine',
+ 'karma-ng-html2js-preprocessor'
+ ],
+
+ preprocessors: {
+ 'src/**/*.html': ['ng-html2js']
+ }
+ };
+
+ // This block is needed to execute Chrome on Travis
+ // If you ever plan to use Chrome and Travis, you can keep it
+ // If not, you can safely remove it
+ // https://github.com/karma-runner/karma/issues/1144#issuecomment-53633076
+ if(configuration.browsers[0] === 'Chrome' && process.env.TRAVIS) {
+ configuration.customLaunchers = {
+ 'chrome-travis-ci': {
+ base: 'Chrome',
+ flags: ['--no-sandbox']
+ }
+ };
+ configuration.browsers = ['chrome-travis-ci'];
+ }
+
+ config.set(configuration);
+};
diff --git a/front/package.json b/front/package.json
new file mode 100755
index 0000000..cfcd4e6
--- /dev/null
+++ b/front/package.json
@@ -0,0 +1,55 @@
+{
+ "name": "loanWizard",
+ "version": "0.0.0",
+ "dependencies": {},
+ "scripts": {
+ "test": "gulp test"
+ },
+ "devDependencies": {
+ "gulp": "~3.9.0",
+ "gulp-autoprefixer": "~2.3.1",
+ "gulp-angular-templatecache": "~1.6.0",
+ "del": "~1.2.0",
+ "lodash": "~3.9.3",
+ "gulp-csso": "~1.0.0",
+ "gulp-filter": "~2.0.2",
+ "gulp-flatten": "~0.0.4",
+ "gulp-jshint": "~1.11.0",
+ "gulp-load-plugins": "~0.10.0",
+ "gulp-size": "~1.2.1",
+ "gulp-uglify": "~1.2.0",
+ "gulp-useref": "~1.2.0",
+ "gulp-util": "~3.0.5",
+ "gulp-ng-annotate": "~1.0.0",
+ "gulp-replace": "~0.5.3",
+ "gulp-rename": "~1.2.2",
+ "gulp-rev": "~5.0.0",
+ "gulp-rev-replace": "~0.4.2",
+ "gulp-minify-html": "~1.0.3",
+ "gulp-inject": "~1.3.1",
+ "gulp-protractor": "~1.0.0",
+ "gulp-sourcemaps": "~1.5.2",
+ "gulp-less": "~3.0.3",
+ "gulp-angular-filesort": "~1.1.1",
+ "main-bower-files": "~2.8.0",
+ "merge-stream": "~0.1.7",
+ "jshint-stylish": "~2.0.0",
+ "wiredep": "~2.2.2",
+ "karma": "~0.12.36",
+ "karma-jasmine": "~0.3.5",
+ "karma-phantomjs-launcher": "~0.2.0",
+ "karma-angular-filesort": "~0.1.0",
+ "karma-ng-html2js-preprocessor": "~0.1.2",
+ "concat-stream": "~1.5.0",
+ "require-dir": "~0.3.0",
+ "browser-sync": "~2.7.12",
+ "browser-sync-spa": "~1.0.2",
+ "http-proxy-middleware": "~0.0.5",
+ "chalk": "~1.0.0",
+ "uglify-save-license": "~0.4.1",
+ "wrench": "~1.5.8"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+}
diff --git a/front/protractor.conf.js b/front/protractor.conf.js
new file mode 100755
index 0000000..f2db101
--- /dev/null
+++ b/front/protractor.conf.js
@@ -0,0 +1,27 @@
+'use strict';
+
+var paths = require('./.yo-rc.json')['generator-gulp-angular'].props.paths;
+
+// An example configuration file.
+exports.config = {
+ // The address of a running selenium server.
+ //seleniumAddress: 'http://localhost:4444/wd/hub',
+ //seleniumServerJar: deprecated, this should be set on node_modules/protractor/config.json
+
+ // Capabilities to be passed to the webdriver instance.
+ capabilities: {
+ 'browserName': 'chrome'
+ },
+
+ baseUrl: 'http://localhost:3000',
+
+ // Spec patterns are relative to the current working directly when
+ // protractor is called.
+ specs: [paths.e2e + '/**/*.js'],
+
+ // Options to be passed to Jasmine-node.
+ jasmineNodeOpts: {
+ showColors: true,
+ defaultTimeoutInterval: 30000
+ }
+};
diff --git a/front/src/app/common.less b/front/src/app/common.less
new file mode 100644
index 0000000..9f6253b
--- /dev/null
+++ b/front/src/app/common.less
@@ -0,0 +1,26 @@
+.row-no-padding {
+ [class*="col-"] {
+ padding-left: 0 !important;
+ padding-right: 0 !important;
+ }
+}
+
+.text-large {
+ font-size: @text-size-large;
+}
+
+.btn-default {
+ color: white;
+
+ background-image: -webkit-linear-gradient(top,@color-blue 0, darken(@color-blue, 10%), 100%);
+ background-image: -o-linear-gradient(top,@color-blue 0, darken(@color-blue, 10%) 100%);
+ background-image: -webkit-gradient(linear,left top,left bottom,from(@color-blue),to(darken(@color-blue, 10%)));
+ background-image: linear-gradient(to bottom,@color-blue 0, darken(@color-blue, 10%) 100%);
+
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+
+ background-repeat: repeat-x;
+ border-color: darken(@color-blue, 5%);
+
+}
diff --git a/front/src/app/components/datepicker/datepicker.directive.js b/front/src/app/components/datepicker/datepicker.directive.js
new file mode 100755
index 0000000..9649be5
--- /dev/null
+++ b/front/src/app/components/datepicker/datepicker.directive.js
@@ -0,0 +1,35 @@
+(function() {
+ 'use strict';
+
+ angular.module("loanWizard")
+ .directive('datepicker', datepickerDirective);
+
+ /*** @ngInject */
+ function datepickerDirective() {
+
+ var directive = {
+ restrict: 'A',
+ require: '?ngModel',
+ scope: {
+ select: "&"
+ },
+ link: link
+ };
+
+ return directive;
+
+ /*** @ngInject */
+ function link(scope, element, attrs, ngModelCtrl) {
+
+ var today = new Date();
+ element.datetimepicker({ format: 'DD/MM/YYYY' });
+
+ element.on("dp.change", function(e) {
+ scope.$apply(function(){
+ ngModelCtrl.$setViewValue(e.date.format("DD/MM/YYYY"));
+ });
+ });
+
+ }
+ }
+})();
diff --git a/front/src/app/components/loan/loan.directive.js b/front/src/app/components/loan/loan.directive.js
new file mode 100755
index 0000000..b83aaaf
--- /dev/null
+++ b/front/src/app/components/loan/loan.directive.js
@@ -0,0 +1,61 @@
+
+(function() {
+ 'use strict';
+
+ angular
+ .module('loanWizard')
+ .directive('loanPlan', loanDirective);
+
+ /*** @ngInject */
+ function loanDirective(dataservice, moment) {
+
+ var directive = {
+ restrict: 'EA',
+ templateUrl: 'app/components/loan/loan.htm',
+ controller: loanController,
+ controllerAs: 'ctrl',
+ scope: {
+ loan: '=',
+ id: '='
+ },
+ bindToController: true
+ };
+
+ /*** @ngInject */
+ function loanController() {
+
+ var ctrl = this;
+ ctrl.showForm = true;
+ ctrl.loading = false;
+
+ ctrl.startingAt = moment().format('DD/MM/YYYY');
+ // (54537.35, payments, 180, 3.6, RatePeriod.ANNUAL, 31.82
+
+ ctrl.updateStartingAt = function (value) {
+ ctrl.startingAt = value.date;
+ };
+
+ /*-------------------------*/
+ ctrl.getLoan = function() {
+ ctrl.loading = true;
+
+ var callback = function(data) {
+ if (data.error === undefined) {
+ ctrl.loan = data;
+ ctrl.showForm = false;
+ ctrl.loading = false;
+ } else {
+ ctrl.showForm = true;
+ ctrl.loading = false;
+ }
+ };
+
+ dataservice.getLoan(ctrl.loan, callback);
+ };
+
+ }
+
+ return directive;
+ }
+
+})();
diff --git a/front/src/app/components/loan/loan.htm b/front/src/app/components/loan/loan.htm
new file mode 100755
index 0000000..fb5da62
--- /dev/null
+++ b/front/src/app/components/loan/loan.htm
@@ -0,0 +1,179 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Début |
+ {{ ctrl.startingAt }} |
+
+
+ Emprunt |
+
+ {{ ctrl.loan.borrowed }} €
+ |
+
+
+ Période |
+
+ {{ ctrl.loan.period }} mois
+
+ {{ ctrl.loan.period / 12 | number: 1}} % années
+
+ |
+
+
+ Taux |
+
+ {{ ctrl.loan.rate }} %
+
+ {{ ctrl.loan.rate * 12 | number: 2}} % annuel
+
+ |
+
+
+ Assurance |
+
+ {{ ctrl.loan.insurance }} €
+ |
+
+
+ Décalé |
+
+ {{ ctrl.loan.shift }} mois
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+ Payement |
+
+
+
+ {{ ctrl.loan.payment + ctrl.loan.insurance | number : 2 }} €
+
+
+
+ [{{ ctrl.loan.payment }} + {{ ctrl.loan.insurance }}]
+
+
+
+ |
+
+
+ Coût total |
+
+ {{ ctrl.loan.totalCost | number : 2 }} €
+
+ {{ ctrl.loan.totalCost - ctrl.loan.borrowed | number : 2 }} € /
+
+ {{ ((ctrl.loan.totalCost / ctrl.loan.borrowed) -1) * 100 | number : 2 }} %
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/front/src/app/components/loan/loan.less b/front/src/app/components/loan/loan.less
new file mode 100755
index 0000000..3714ba7
--- /dev/null
+++ b/front/src/app/components/loan/loan.less
@@ -0,0 +1,124 @@
+.loanPlan {
+
+ .form-value {
+ font-size: 1.5em;
+ }
+
+ .loan__form {
+
+ width: 320px;
+ padding: 10px;
+ border-radius: 5px;
+ border: 1px solid lighten(@color-default, 20%);
+ box-shadow: 1px 1px 1px #fff;
+ margin: 0 auto;
+
+ div.form-group {
+ display: inline-block;
+ }
+
+ [datepicker], input[ng-model="ctrl.loan.rate"] {
+ width: 100px;
+ }
+
+ input[ng-model="ctrl.loan.borrowed"], input[ng-model="ctrl.loan.insurance"] {
+ width: 110px;
+ }
+
+ input[ng-model="ctrl.loan.shift"], input[ng-model="ctrl.loan.period"] {
+ width: 70px;
+ }
+
+ label {
+ font-weight: normal;
+ font-size: 0.9em;
+ }
+ }
+
+ .loan-form {
+ padding: 2px;
+ border-radius: 5px;
+ background-color: rgba(255, 255, 255, 0.7);
+ box-shadow: 0px 0px 10px;
+
+ button {
+ margin-top: 25px;
+ }
+ }
+
+ .monthRate {
+ line-height: 1em;
+ margin-top: 12px;
+ }
+
+ .info {
+ font-size: 0.8em;
+ text-transform: uppercase;
+ }
+
+ .loan__payments {
+ margin-top: 20px;
+ }
+
+ .loan__data {
+
+ margin-bottom: 20px;
+ background-color: rgba(255, 255, 255, 0.5);
+ border: 1px solid rgba(0, 0, 0, 0);
+ border-radius: 4px;
+ box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
+ padding: 20px 0;
+
+ &::after {
+ content: '';
+ display: block;
+ clear: both;
+ }
+
+ & > div[class*="col-"] {
+
+ & > table {
+ width: 100%;
+ & tr {
+ td {
+ width: 50%;
+ border-bottom: 1px solid rgb(221, 221, 221);
+ & + td {
+ text-align: right;
+ }
+ }
+ }
+ }
+ }
+
+ & button.edit {
+ margin-top: -10px;
+ margin-right: 10px;
+ margin-bottom: 10px;
+ }
+ }
+}
+
+.subloan__widget {
+ margin-top: 10px;
+}
+
+.ball-clip-rotate-multiple {
+ & > div {
+ border: 2px solid @color-blue;
+
+ &:last-child {
+ border-color: @color-blue transparent @color-blue transparent;
+ }
+ }
+}
+
+.loan__form-submit {
+ label {
+ display: block;
+ }
+}
+
+button.btn + button.btn {
+ margin-left: 10px;
+}
\ No newline at end of file
diff --git a/front/src/app/components/loanChart/loanchart.directive.js b/front/src/app/components/loanChart/loanchart.directive.js
new file mode 100755
index 0000000..c19265a
--- /dev/null
+++ b/front/src/app/components/loanChart/loanchart.directive.js
@@ -0,0 +1,140 @@
+(function() {
+ 'use strict';
+
+ angular
+ .module('loanWizard')
+ .directive('ebizLoanCharts', loanCharts);
+
+ /*** @ngInject */
+ function loanCharts() {
+
+ var directive = {
+ template: '',
+ link: link,
+ restrict: 'EA',
+ scope: {
+ height : "=",
+ width : "=",
+ payments: "="
+ },
+ controller: LoanChartsController,
+ controllerAs: 'ctrl',
+ bindToController: true // because the scope is isolated
+ };
+ return directive;
+
+ //LoanChartsController.$inject = ['$scope'];
+
+ /*** @ngInject */
+ function link(scope, element, attributes) {
+ var canvas = element.find('canvas');
+ }
+
+ /*** @ngInject */
+ function LoanChartsController($scope, $element) {
+
+ var ctrl = this;
+ var ctx = $element.find('canvas').get(0).getContext("2d");
+ ctrl.chart;
+
+ ctx.canvas.width = ctrl.width;
+ ctx.canvas.height = ctrl.height;
+
+ $scope.$watch('ctrl.payments', function () {
+ createChart(ctrl.payments);
+ });
+
+ function createChart (payments) {
+
+ // Get context with jQuery - using jQuery's .get() method.
+
+ var fillColor1 = "rgba(229, 104, 0, 0.2)";
+ var strokeColor1 = "rgba(229, 104, 0, 1)";
+
+ var fillColor2 = "rgba(151,187,205,0.2)";
+ var strokeColor2 = "rgba(151,187,205,1)";
+
+ var fillColor3 = "rgba(114, 63, 189, 0.2)";
+ var strokeColor3 = "rgba(114, 63, 189, 1)";
+
+ var fillColor4 = "rgba(84, 98, 153, 0.2)";
+ var strokeColor4 = "rgba(84, 98, 153, 1)";
+
+
+ var labels = [];
+ var basePayments = [];
+ var fullPayments = [];
+ var interest = [];
+ var capital = [];
+
+ _.each(ctrl.payments, function(p){
+ labels.push(p.period);
+ basePayments.push(p.basePayment);
+ fullPayments.push(p.fullPayment);
+ interest.push(p.interest);
+ capital.push(p.capital);
+ });
+
+ var fullPayementDataset = {
+ label: "Payements",
+ fillColor: fillColor1,
+ strokeColor: strokeColor1,
+ pointColor: strokeColor1,
+ pointStrokeColor: "#fff",
+ pointHighlightFill: "#fff",
+ pointHighlightStroke: strokeColor1,
+ data: fullPayments
+ };
+
+ var basePayementDataset = {
+ label: "Base",
+ fillColor: fillColor2,
+ strokeColor: strokeColor2,
+ pointColor: strokeColor2,
+ pointStrokeColor: "#fff",
+ pointHighlightFill: "#fff",
+ pointHighlightStroke: strokeColor2,
+ data: basePayments
+ };
+
+ var capitalDataset = {
+ label: "Intêret",
+ fillColor: fillColor3,
+ strokeColor: strokeColor3,
+ pointColor: strokeColor3,
+ pointStrokeColor: "#fff",
+ pointHighlightFill: "#fff",
+ pointHighlightStroke: strokeColor3,
+ data: capital
+ };
+
+ var interestDataset = {
+ label: "Intêret",
+ fillColor: fillColor4,
+ strokeColor: strokeColor4,
+ pointColor: strokeColor4,
+ pointStrokeColor: "#fff",
+ pointHighlightFill: "#fff",
+ pointHighlightStroke: strokeColor4,
+ data: interest
+ };
+
+
+ var data = {
+ labels: labels,
+ datasets: [
+ fullPayementDataset,
+ basePayementDataset,
+ capitalDataset,
+ interestDataset
+ ]
+ };
+
+ ctrl.chart = new Chart(ctx).Line(data, {pointDot : false});
+ }
+ }
+
+ }
+
+})();
+
diff --git a/front/src/app/components/loanChart/loanchart.less b/front/src/app/components/loanChart/loanchart.less
new file mode 100644
index 0000000..66c2afa
--- /dev/null
+++ b/front/src/app/components/loanChart/loanchart.less
@@ -0,0 +1,4 @@
+.loanChart {
+
+
+}
diff --git a/front/src/app/components/loanProject/loanProject.directive.js b/front/src/app/components/loanProject/loanProject.directive.js
new file mode 100755
index 0000000..511dd73
--- /dev/null
+++ b/front/src/app/components/loanProject/loanProject.directive.js
@@ -0,0 +1,54 @@
+(function() {
+ 'use strict';
+
+ angular.module('loanWizard').directive('loanProject', loanProjectDirective);
+
+ /** @ngInject */
+ function loanProjectDirective() {
+
+ var directive = {
+ restrict: 'EA',
+ templateUrl: 'app/components/loanProject/loanProject.htm',
+ controller: loanProjectController,
+ controllerAs: 'ctrl',
+ bindToController: true
+ };
+
+ return directive;
+
+ /** @ngInject */
+ function loanProjectController($scope, $compile) {
+
+ var ctrl = this;
+
+ ctrl.loans = [];
+
+ ctrl.addLoanPlan = function () {
+ var loan = {
+ "rate" : 2.15,
+ "borrowed": 10000,
+ "totalCost": "",
+ "insurance": 31.82,
+ "period": 180,
+ "payment": "",
+ "payments": []
+ };
+
+ ctrl.loans.push(loan);
+ ctrl.addDirective(ctrl.loans.length-1);
+ };
+
+ ctrl.addDirective = function(index) {
+ angular.element(
+ document.getElementById('loanProject')
+ ).append(
+ $compile('')($scope)
+ );
+ };
+
+ ctrl.addLoanPlan();
+ }
+ }
+
+
+})();
diff --git a/front/src/app/components/loanProject/loanProject.htm b/front/src/app/components/loanProject/loanProject.htm
new file mode 100755
index 0000000..38f2823
--- /dev/null
+++ b/front/src/app/components/loanProject/loanProject.htm
@@ -0,0 +1,8 @@
+
+
+ Dossier de prêt
+
+
+
diff --git a/front/src/app/components/payments/payments.directive.js b/front/src/app/components/payments/payments.directive.js
new file mode 100755
index 0000000..0519177
--- /dev/null
+++ b/front/src/app/components/payments/payments.directive.js
@@ -0,0 +1,37 @@
+(function() {
+ 'use strict';
+
+ angular
+ .module('loanWizard')
+ .directive('ebizPayments', paymentsDirective);
+
+ /*** @ngInject */
+ function paymentsDirective(moment) {
+
+ var directive = {
+ restrict: 'EA',
+ templateUrl: 'app/components/payments/payments.htm',
+ controller: paymentsController,
+ controllerAs: 'ctrl',
+ bindToController: true,
+ scope: {
+ payments: "=",
+ startingAt: "="
+ }
+ };
+
+ return directive;
+
+ /*** @ngInject */
+ function paymentsController() {
+ var ctrl = this;
+
+ ctrl.dateAt = function (period) {
+ var start = moment(ctrl.startingAt, "DD/MM/YYYY");
+ return start.add(period-1, "M").format("MMM YYYY");
+ };
+ }
+
+ }
+
+})();
diff --git a/front/src/app/components/payments/payments.htm b/front/src/app/components/payments/payments.htm
new file mode 100755
index 0000000..84e8eda
--- /dev/null
+++ b/front/src/app/components/payments/payments.htm
@@ -0,0 +1,29 @@
+
+
+
[{{payment.period}}] {{ ctrl.dateAt(payment.period) }}
+
+
Emprunt
+
{{ payment.borrowed | number : 2}}
+
+
+
Payement
+
{{ payment.basePayment | number : 2}}
+
+
+
Capital
+
{{ payment.capital | number : 2 }}
+
+
+
Intérêt
+
{{ payment.interest | number : 2}}
+
+
+
Assurance
+
{{ payment.insurance | number : 2}}
+
+
+
Total
+
{{ payment.fullPayment | number : 2}}
+
+
+
diff --git a/front/src/app/components/payments/payments.less b/front/src/app/components/payments/payments.less
new file mode 100755
index 0000000..88b8502
--- /dev/null
+++ b/front/src/app/components/payments/payments.less
@@ -0,0 +1,52 @@
+@import "../../variables";
+
+.payments {
+
+ overflow: scroll;
+ overflow-y: hidden;
+ -ms-overflow-y: hidden;
+
+ white-space: nowrap;
+ height: 140px;
+
+ .payments__line-above::before {
+ border-top: 1px solid #c5c5c5;
+ position: relative;
+ top: 0;
+ left: 10px;
+ width: 90%;
+ }
+
+ .payments__period {
+
+ text-align: center;
+
+ }
+ .payments__onePayment {
+ font-size: 0.8em;
+ position: relative;
+ display: inline-block;
+ border: 1px solid @border-light-grey;
+ padding: 5px 10px;
+ width: 130px;
+ margin-left: 5px;
+
+ }
+
+}
+
+.color-total {
+ color: @color-total;
+}
+
+.color-capital {
+ color: @color-capital;
+}
+
+.color-interest {
+ color: @color-interest;
+}
+
+.color-payment {
+ color: @color-payment;
+}
diff --git a/front/src/app/components/subLoan/subloan.directive.js b/front/src/app/components/subLoan/subloan.directive.js
new file mode 100644
index 0000000..e2a4c2e
--- /dev/null
+++ b/front/src/app/components/subLoan/subloan.directive.js
@@ -0,0 +1,34 @@
+(function() {
+ 'use strict';
+
+ angular
+ .module('loanWizard')
+ .directive('subLoan', subLoanDirective);
+
+ /*** @ngInject */
+ function subLoanDirective() {
+
+ var directive = {
+ restrict: 'EA',
+ templateUrl: 'app/components/subLoan/subloan.htm',
+ controller: subLoanController,
+ controllerAs: 'ctrl',
+ scope: {
+ subloan: '=data',
+ },
+ bindToController: true
+ };
+
+ /*** @ngInject */
+ function subLoanController() {
+
+ var ctrl = this;
+
+ ctrl.validated = false;
+
+ }
+
+ return directive;
+ }
+
+})();
\ No newline at end of file
diff --git a/front/src/app/components/subLoan/subloan.htm b/front/src/app/components/subLoan/subloan.htm
new file mode 100644
index 0000000..53d0a41
--- /dev/null
+++ b/front/src/app/components/subLoan/subloan.htm
@@ -0,0 +1,37 @@
+
\ No newline at end of file
diff --git a/front/src/app/components/subLoan/subloan.less b/front/src/app/components/subLoan/subloan.less
new file mode 100644
index 0000000..69759f0
--- /dev/null
+++ b/front/src/app/components/subLoan/subloan.less
@@ -0,0 +1,45 @@
+.subloan__form {
+ & > div {
+ display: inline-block;
+ text-align: center;
+ }
+
+ .form-control {
+ padding: 6px;
+ background-color: rgb(247, 247, 247);
+ border: none;
+ border-bottom: 1px solid rgb(204, 204, 204);
+ border-radius: 0;
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+ }
+
+ label {
+ font-weight: normal;
+ text-transform: capitalize;
+ font-size: 0.8em;
+ width: 100%;
+ }
+}
+
+.subloan__directive {
+ width: 100%;
+}
+
+.subloan__form-amount {
+ width: 30%;
+}
+
+.subloan__form-rate {
+ width: 16%;
+}
+
+.subloan__form-start,
+.subloan__form-period {
+ width: 12%;
+}
+
+.subloan__form-insurance{
+ width: 16%;
+}
\ No newline at end of file
diff --git a/front/src/app/components/twelveCalculator/twelveCalculator.directive.js b/front/src/app/components/twelveCalculator/twelveCalculator.directive.js
new file mode 100755
index 0000000..664465d
--- /dev/null
+++ b/front/src/app/components/twelveCalculator/twelveCalculator.directive.js
@@ -0,0 +1,49 @@
+(function() {
+ 'use strict';
+
+ angular
+ .module('loanWizard')
+ .directive('ebizTwelveCalculator', twelveCalculator);
+
+ function twelveCalculator() {
+
+ var directive = {
+ restrict: 'EA',
+ templateUrl: 'app/components/twelveCalculator/twelveCalculator.htm',
+ scope: {
+ label : "@",
+ type : "@",
+ },
+ controller: twelveCalculatorController,
+ controllerAs: 'ctrl',
+ bindToController: true // because the scope is isolated
+ };
+
+ return directive;
+
+ //TwelveDividerController.$inject = ['$scope'];
+
+ /*** @ngInject */
+ function twelveCalculatorController() {
+
+ var ctrl = this;
+
+ ctrl.yearly = 1;
+ ctrl.monthly = ctrl.type === "divide" ? Math.round((ctrl.yearly / 12) * 10000) / 10000 : ctrl.yearly * 12;
+
+ ctrl.updateMonthtly = function () {
+ if (ctrl.type === "divide") {
+ ctrl.monthly = Math.round((ctrl.yearly / 12) * 10000) / 10000;
+ } else {
+ ctrl.monthly = ctrl.yearly * 12;
+ }
+ };
+
+ }
+
+ }
+
+})();
+
+
+
diff --git a/front/src/app/components/twelveCalculator/twelveCalculator.htm b/front/src/app/components/twelveCalculator/twelveCalculator.htm
new file mode 100755
index 0000000..b51c279
--- /dev/null
+++ b/front/src/app/components/twelveCalculator/twelveCalculator.htm
@@ -0,0 +1,32 @@
+
+
+
{{ ctrl.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/front/src/app/components/twelveCalculator/twelveCalculator.less b/front/src/app/components/twelveCalculator/twelveCalculator.less
new file mode 100644
index 0000000..6c4360f
--- /dev/null
+++ b/front/src/app/components/twelveCalculator/twelveCalculator.less
@@ -0,0 +1,57 @@
+#twelve__directive {
+
+ margin-bottom: 20px;
+
+ border: 1px solid lighten(@color-default, 20%);
+ box-shadow: 1px 1px 1px #fff;
+
+ h3 {
+ margin: 0 0 15px 0;
+
+ font-family: 'Roboto', sans-serif;
+ font-size: 1em;
+ text-transform: uppercase;
+ background-color: lighten(@color-default, 20%);
+ color: white;
+ padding: 5px;
+ }
+
+ label {
+ color: @color-title;
+ }
+
+ .twelve__input {
+ width: 70%;
+ margin: 0 auto;
+ }
+
+ .twelve__result, input {
+ display: block;
+ width: 100%;
+ height: 34px;
+ text-align: center;
+ padding: 6px 12px;
+ font-size: 18px;
+ line-height: 1.42857143;
+ color: #555;
+ margin-bottom: 20px;
+ }
+
+ input {
+
+ font-size: 16px;
+ color: #555;
+ background: none;
+ border: 0;
+ border-bottom: 1px solid #CCC;
+
+ //box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
+ transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
+ }
+
+ i.font {
+ margin-top: 20%;
+ color: @color-default;
+ }
+}
diff --git a/front/src/app/data/data.json b/front/src/app/data/data.json
new file mode 100755
index 0000000..472707f
--- /dev/null
+++ b/front/src/app/data/data.json
@@ -0,0 +1,442 @@
+{
+ "rate":0.2042,
+ "borrowed":10000.00,
+ "totalCost":10508.16,
+ "insurance":0.0000,
+ "period":48,
+ "payment":218.92,
+ "payments":[
+ {
+ "borrowed":9801.50,
+ "capital":198.50,
+ "interest":20.42,
+ "period":1,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":9602.59,
+ "capital":198.91,
+ "interest":20.01,
+ "period":2,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":9403.28,
+ "capital":199.31,
+ "interest":19.61,
+ "period":3,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":9203.56,
+ "capital":199.72,
+ "interest":19.20,
+ "period":4,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":9003.43,
+ "capital":200.13,
+ "interest":18.79,
+ "period":5,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":8802.90,
+ "capital":200.53,
+ "interest":18.39,
+ "period":6,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":8601.96,
+ "capital":200.94,
+ "interest":17.98,
+ "period":7,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":8400.61,
+ "capital":201.35,
+ "interest":17.57,
+ "period":8,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":8198.84,
+ "capital":201.77,
+ "interest":17.15,
+ "period":9,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":7996.66,
+ "capital":202.18,
+ "interest":16.74,
+ "period":10,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":7794.07,
+ "capital":202.59,
+ "interest":16.33,
+ "period":11,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":7591.07,
+ "capital":203.00,
+ "interest":15.92,
+ "period":12,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":7387.65,
+ "capital":203.42,
+ "interest":15.50,
+ "period":13,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":7183.82,
+ "capital":203.83,
+ "interest":15.09,
+ "period":14,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":6979.57,
+ "capital":204.25,
+ "interest":14.67,
+ "period":15,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":6774.90,
+ "capital":204.67,
+ "interest":14.25,
+ "period":16,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":6569.81,
+ "capital":205.09,
+ "interest":13.83,
+ "period":17,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":6364.31,
+ "capital":205.50,
+ "interest":13.42,
+ "period":18,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":6158.39,
+ "capital":205.92,
+ "interest":13.00,
+ "period":19,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":5952.05,
+ "capital":206.34,
+ "interest":12.58,
+ "period":20,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":5745.28,
+ "capital":206.77,
+ "interest":12.15,
+ "period":21,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":5538.09,
+ "capital":207.19,
+ "interest":11.73,
+ "period":22,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":5330.48,
+ "capital":207.61,
+ "interest":11.31,
+ "period":23,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":5122.44,
+ "capital":208.04,
+ "interest":10.88,
+ "period":24,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":4913.98,
+ "capital":208.46,
+ "interest":10.46,
+ "period":25,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":4705.09,
+ "capital":208.89,
+ "interest":10.03,
+ "period":26,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":4495.78,
+ "capital":209.31,
+ "interest":9.61,
+ "period":27,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":4286.04,
+ "capital":209.74,
+ "interest":9.18,
+ "period":28,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":4075.87,
+ "capital":210.17,
+ "interest":8.75,
+ "period":29,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":3865.27,
+ "capital":210.60,
+ "interest":8.32,
+ "period":30,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":3654.24,
+ "capital":211.03,
+ "interest":7.89,
+ "period":31,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":3442.78,
+ "capital":211.46,
+ "interest":7.46,
+ "period":32,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":3230.89,
+ "capital":211.89,
+ "interest":7.03,
+ "period":33,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":3018.57,
+ "capital":212.32,
+ "interest":6.60,
+ "period":34,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":2805.81,
+ "capital":212.76,
+ "interest":6.16,
+ "period":35,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":2592.62,
+ "capital":213.19,
+ "interest":5.73,
+ "period":36,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":2378.99,
+ "capital":213.63,
+ "interest":5.29,
+ "period":37,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":2164.93,
+ "capital":214.06,
+ "interest":4.86,
+ "period":38,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":1950.43,
+ "capital":214.50,
+ "interest":4.42,
+ "period":39,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":1735.49,
+ "capital":214.94,
+ "interest":3.98,
+ "period":40,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":1520.11,
+ "capital":215.38,
+ "interest":3.54,
+ "period":41,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":1304.29,
+ "capital":215.82,
+ "interest":3.10,
+ "period":42,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":1088.03,
+ "capital":216.26,
+ "interest":2.66,
+ "period":43,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":871.33,
+ "capital":216.70,
+ "interest":2.22,
+ "period":44,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":654.19,
+ "capital":217.14,
+ "interest":1.78,
+ "period":45,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":436.61,
+ "capital":217.58,
+ "interest":1.34,
+ "period":46,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":218.58,
+ "capital":218.03,
+ "interest":0.89,
+ "period":47,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ },
+ {
+ "borrowed":0.11,
+ "capital":218.47,
+ "interest":0.45,
+ "period":48,
+ "insurance":0.0000,
+ "fullPayment":218.92,
+ "basePayment":218.92
+ }
+ ]
+}
diff --git a/front/src/app/home.less b/front/src/app/home.less
new file mode 100755
index 0000000..3464ed8
--- /dev/null
+++ b/front/src/app/home.less
@@ -0,0 +1,34 @@
+
+.title {
+ font-family: 'Dancing Script', cursive;
+ font-weight: bold;
+ color: @color-title;
+ text-shadow: 1px 1px @color-shadow-title;
+}
+
+.text-blue {
+ color: @color-blue;
+}
+
+body{
+ margin: 0;
+ padding: 0;
+ color: @color-default;
+ background:#efefef url('../assets/images/gray_jean.png');
+ text-shadow: 0 0 1px transparent; /* google font pixelation fix */
+}
+
+nav {
+ margin: 0 !important;
+ background-color: @bckg-nav;
+ min-height: 100% !important;
+ position: absolute !important;
+}
+
+#main__tools {
+
+ .title {
+ border-bottom: 1px solid @border-light-grey;
+ }
+
+}
diff --git a/front/src/app/index.config.js b/front/src/app/index.config.js
new file mode 100755
index 0000000..6f1f87c
--- /dev/null
+++ b/front/src/app/index.config.js
@@ -0,0 +1,23 @@
+(function() {
+ 'use strict';
+
+ angular
+ .module('loanWizard')
+ .config(config);
+
+ /** @ngInject */
+ function config($logProvider, toastr, $httpProvider) {
+ // Enable log
+ $logProvider.debugEnabled(true);
+
+ // Set options third-party lib
+ toastr.options.timeOut = 3000;
+ toastr.options.positionClass = 'toast-top-right';
+ toastr.options.preventDuplicates = true;
+ toastr.options.progressBar = true;
+
+ $httpProvider.defaults.useXDomain = true;
+ delete $httpProvider.defaults.headers.common['X-Requested-With'];
+ }
+''
+})();
diff --git a/front/src/app/index.constants.js b/front/src/app/index.constants.js
new file mode 100755
index 0000000..1346927
--- /dev/null
+++ b/front/src/app/index.constants.js
@@ -0,0 +1,10 @@
+/* global toastr:false, moment:false */
+(function() {
+ 'use strict';
+
+ angular
+ .module('loanWizard')
+ .constant('toastr', toastr)
+ .constant('moment', moment);
+
+})();
diff --git a/front/src/app/index.less b/front/src/app/index.less
new file mode 100755
index 0000000..15791d1
--- /dev/null
+++ b/front/src/app/index.less
@@ -0,0 +1,25 @@
+/**
+ * Do not remove this comments bellow. It's the markers used by wiredep to inject
+ * less dependencies when defined in the bower.json of your dependencies
+ */
+// bower:less
+// endbower
+
+/**
+ * If you want to override some bootstrap variables, you have to change values here.
+ * The list of variables are listed here bower_components/bootstrap/less/variables.less
+ */
+@icon-font-path: '../../bower_components/bootstrap/fonts/';
+@import url(http://fonts.googleapis.com/css?family=Dancing+Script);
+@import url(http://fonts.googleapis.com/css?family=Roboto:400,100);
+
+nav {
+ padding-top: 50px;
+}
+
+/**
+ * Do not remove this comments bellow. It's the markers used by gulp-inject to inject
+ * all your less files automatically
+ */
+// injector
+// endinjector
diff --git a/front/src/app/index.module.js b/front/src/app/index.module.js
new file mode 100755
index 0000000..77b8567
--- /dev/null
+++ b/front/src/app/index.module.js
@@ -0,0 +1,15 @@
+(function() {
+ 'use strict';
+
+ angular
+ .module('loanWizard',
+ [
+ 'ngAnimate',
+ 'ngCookies',
+ 'ngTouch',
+ 'ngSanitize',
+ 'ngResource',
+ 'ngRoute'
+ ]);
+
+})();
diff --git a/front/src/app/index.route.js b/front/src/app/index.route.js
new file mode 100755
index 0000000..cab8395
--- /dev/null
+++ b/front/src/app/index.route.js
@@ -0,0 +1,20 @@
+(function() {
+ 'use strict';
+
+ angular
+ .module('loanWizard')
+ .config(routeConfig);
+
+ function routeConfig($routeProvider) {
+ $routeProvider
+ .when('/', {
+ templateUrl: 'app/main/main.html',
+ controller: 'MainController',
+ controllerAs: 'ctrl'
+ })
+ .otherwise({
+ redirectTo: '/'
+ });
+ }
+
+})();
diff --git a/front/src/app/index.run.js b/front/src/app/index.run.js
new file mode 100755
index 0000000..905eb88
--- /dev/null
+++ b/front/src/app/index.run.js
@@ -0,0 +1,14 @@
+(function() {
+ 'use strict';
+
+ angular
+ .module('loanWizard')
+ .run(runBlock);
+
+ /** @ngInject */
+ function runBlock($log) {
+
+ $log.debug('runBlock end');
+ }
+
+})();
diff --git a/front/src/app/main/main.controller.js b/front/src/app/main/main.controller.js
new file mode 100755
index 0000000..8a0b610
--- /dev/null
+++ b/front/src/app/main/main.controller.js
@@ -0,0 +1,22 @@
+(function() {
+ 'use strict';
+
+ angular
+ .module('loanWizard')
+ .controller('MainController', MainController);
+
+ /** @ngInject */
+ function MainController($timeout, toastr) {
+ var vm = this;
+
+ vm.awesomeThings = [];
+ vm.classAnimation = '';
+ vm.showToastr = showToastr;
+
+ function showToastr() {
+ toastr.info('Fork generator-gulp-angular');
+ vm.classAnimation = '';
+ }
+
+ }
+})();
diff --git a/front/src/app/main/main.controller.spec.js b/front/src/app/main/main.controller.spec.js
new file mode 100755
index 0000000..1c1d669
--- /dev/null
+++ b/front/src/app/main/main.controller.spec.js
@@ -0,0 +1,15 @@
+(function() {
+ 'use strict';
+
+ describe('controllers', function(){
+
+ beforeEach(module('loanWizard'));
+
+ it('should define more than 5 awesome things', inject(function($controller) {
+ var vm = $controller('MainController');
+
+ expect(angular.isArray(vm.awesomeThings)).toBeTruthy();
+ expect(vm.awesomeThings.length > 5).toBeTruthy();
+ }));
+ });
+})();
diff --git a/front/src/app/main/main.html b/front/src/app/main/main.html
new file mode 100755
index 0000000..e6740fa
--- /dev/null
+++ b/front/src/app/main/main.html
@@ -0,0 +1,17 @@
+
+
+
Thibault de Lambilly
+
+
+
+
+
+
diff --git a/front/src/app/services/data.service.js b/front/src/app/services/data.service.js
new file mode 100755
index 0000000..dcf8b85
--- /dev/null
+++ b/front/src/app/services/data.service.js
@@ -0,0 +1,51 @@
+(function() {
+ 'use strict';
+
+ angular
+ .module('loanWizard')
+ .factory('dataservice', dataservice);
+
+ /*** @ngInject */
+ function dataservice($http) {
+
+ return {
+ getLoan: getLoan
+ };
+
+ function getLoan(loan, callback) {
+ //*
+ var url = 'http://localhost:5001/loan/get'
+ + "/" + loan.borrowed
+ + "/" + loan.period
+ + "/" + loan.rate
+ + "/" + loan.insurance;
+
+ return $http.get(url)
+ .success(getData)
+ .error(getDataFailed);
+ //*/
+ /*
+ // {capital}/{period}/{rate}/{insurance}
+ return $http.post('/loan/get', loan)
+ .then(getData)
+ .catch(getDataFailed);
+ //*/
+
+ /*
+ return $http.get('app/data/data.json')
+ .then(getData)
+ .catch(getDataFailed);
+ //*/
+
+ function getData(data) {
+ callback(data);
+ }
+
+ function getDataFailed(data, status, headers, config) {
+ callback({error: "Exception"});
+ }
+ }
+ }
+
+
+})();
diff --git a/front/src/app/variables.less b/front/src/app/variables.less
new file mode 100755
index 0000000..de28c12
--- /dev/null
+++ b/front/src/app/variables.less
@@ -0,0 +1,16 @@
+@color-default: #8E8E8E;
+@color-title: #8E8E8E;
+@color-shadow-title: #fff;
+@color-blue: #51A0C3;
+
+@color-total: rgb(229, 104, 0);
+@color-capital: rgb(114, 63, 189);
+@color-interest: rgb(84, 98, 153);
+@color-payment: rgb(151,187,205);
+
+@border-light-grey: #C5C5C5;
+
+@bckg-nav: #222;
+
+@text-size-default: 1em;
+@text-size-large: 2.3em;
diff --git a/front/src/assets/images/gray_jean.png b/front/src/assets/images/gray_jean.png
new file mode 100755
index 0000000..355fba2
Binary files /dev/null and b/front/src/assets/images/gray_jean.png differ
diff --git a/front/src/favicon.ico b/front/src/favicon.ico
new file mode 100755
index 0000000..6527905
Binary files /dev/null and b/front/src/favicon.ico differ
diff --git a/front/src/index.html b/front/src/index.html
new file mode 100755
index 0000000..946bb50
--- /dev/null
+++ b/front/src/index.html
@@ -0,0 +1,60 @@
+
+
+
+
+ loanWizard
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/webapp/.gitignore b/webapp/.gitignore
new file mode 100755
index 0000000..a722fd5
--- /dev/null
+++ b/webapp/.gitignore
@@ -0,0 +1,3 @@
+target/
+*.iml
+.idea
diff --git a/webapp/pom.xml b/webapp/pom.xml
new file mode 100755
index 0000000..199fcb6
--- /dev/null
+++ b/webapp/pom.xml
@@ -0,0 +1,101 @@
+
+
+ 4.0.0
+
+ biz.eventual
+ loanwizard
+ 0.0.1-SNAPSHOT
+ jar
+
+ loanwizard
+ Easy way to play with loan figures
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 1.2.4.RELEASE
+
+
+
+
+ UTF-8
+ 1.8
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-rest
+
+
+
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ mysql
+ mysql-connector-java
+ runtime
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ org.projectlombok
+ lombok
+ 1.16.4
+
+
+
+ com.google.code.gson
+ gson
+ 2.3.1
+
+
+
+ org.mockito
+ mockito-all
+ 1.10.19
+
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.apache.maven.plugins
+ maven-war-plugin
+
+ false
+
+
+
+
+
+
diff --git a/webapp/src/main/java/biz/eventual/LoanwizardApplication.java b/webapp/src/main/java/biz/eventual/LoanwizardApplication.java
new file mode 100755
index 0000000..1923267
--- /dev/null
+++ b/webapp/src/main/java/biz/eventual/LoanwizardApplication.java
@@ -0,0 +1,12 @@
+package biz.eventual;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class LoanwizardApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(LoanwizardApplication.class, args);
+ }
+}
diff --git a/webapp/src/main/java/biz/eventual/bean/LoanPlan.java b/webapp/src/main/java/biz/eventual/bean/LoanPlan.java
new file mode 100755
index 0000000..2c78352
--- /dev/null
+++ b/webapp/src/main/java/biz/eventual/bean/LoanPlan.java
@@ -0,0 +1,106 @@
+package biz.eventual.bean;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.Setter;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Created by Thibault de Lambilly on 19/06/2015.
+ */
+/*
+@Entity
+@Table(name = "loanplan")
+*/
+@Getter
+@Setter
+public class LoanPlan
+{
+ public final static int PER_YEAR = 12;
+ public final static int REFUND_NOW = 0;
+
+/* @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ @Getter
+ @Setter
+ int id;
+*/
+ /***
+ * Said to be MONTHLY rate
+ */
+ private BigDecimal rate;
+
+ private BigDecimal borrowed;
+
+ @Setter(AccessLevel.NONE)
+ private BigDecimal totalCost = BigDecimal.ZERO;
+
+ private BigDecimal insurance = BigDecimal.ZERO;
+
+ private BigDecimal fee = BigDecimal.ZERO;
+
+ /***
+ * Number of month
+ */
+ private int period = -1;
+
+ /***
+ * When starting to refund
+ */
+ private int shiftRefundBy = 0;
+
+ private BigDecimal payment;
+
+ private List payments;
+
+ private List subLoan;
+
+ public LoanPlan() {}
+
+ public LoanPlan(BigDecimal borrowed, BigDecimal rate, int period) {
+ this.borrowed = borrowed;
+ this.rate = rate;
+ this.period = period;
+ }
+
+ public List getPayments() {
+ if (payments == null) {
+ payments = new ArrayList();
+ }
+
+ return payments;
+ }
+
+ /***
+ * Calculate the totalt cost by add all Payments
+ * @return
+ */
+ public BigDecimal getTotalCost() {
+
+ if (totalCost.compareTo(BigDecimal.ZERO) == 0) {
+ for(Payment payment : payments) {
+ totalCost = totalCost.add(payment.getBasePayment());
+ }
+ }
+
+ return totalCost.setScale(2, BigDecimal.ROUND_HALF_UP);
+ }
+
+ public String toJson()
+ {
+ ObjectMapper mapper = new ObjectMapper();
+ String json;
+ try {
+ json = mapper.writeValueAsString(this);
+ } catch (JsonProcessingException e) {
+ json = "{erreur : Impossible to parse object LoanPlan in JSON}";
+ }
+
+ return json;
+ }
+}
diff --git a/webapp/src/main/java/biz/eventual/bean/Payment.java b/webapp/src/main/java/biz/eventual/bean/Payment.java
new file mode 100755
index 0000000..89552d3
--- /dev/null
+++ b/webapp/src/main/java/biz/eventual/bean/Payment.java
@@ -0,0 +1,41 @@
+package biz.eventual.bean;
+
+import java.math.BigDecimal;
+
+import lombok.Getter;
+import lombok.Setter;
+import com.google.gson.Gson;
+
+/**
+ * Created by Thibault de Lambilly on 19/06/2015.
+ */
+@Getter
+@Setter
+public class Payment
+{
+ BigDecimal borrowed = BigDecimal.ZERO;
+
+ BigDecimal capital = BigDecimal.ZERO;
+
+ BigDecimal interest = BigDecimal.ZERO;
+
+ int period = 0;
+
+ BigDecimal insurance = BigDecimal.ZERO;
+
+ public BigDecimal getBasePayment() {
+ return capital.add(interest).setScale(2, BigDecimal.ROUND_HALF_UP);
+ }
+
+ public BigDecimal getFullPayment() {
+ return capital
+ .add(interest)
+ .add(insurance)
+ .setScale(2, BigDecimal.ROUND_HALF_UP);
+ }
+
+ public String toJson() {
+ Gson gson = new Gson();
+ return gson.toJson(this);
+ }
+}
diff --git a/webapp/src/main/java/biz/eventual/bean/StepLoan.java b/webapp/src/main/java/biz/eventual/bean/StepLoan.java
new file mode 100755
index 0000000..f5fa060
--- /dev/null
+++ b/webapp/src/main/java/biz/eventual/bean/StepLoan.java
@@ -0,0 +1,41 @@
+package biz.eventual.bean;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import lombok.Getter;
+import lombok.Setter;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Created by tweety on 20/06/15.
+ */
+@Getter
+@Setter
+public class StepLoan {
+
+ private BigDecimal payment;
+ private List payments;
+
+ private LoanPlan loanPlan;
+ private List subloans;
+
+ public StepLoan (LoanPlan loanPlan, List subLoans) {
+ this.loanPlan = loanPlan;
+ this.subloans = subLoans;
+ }
+
+ public String toJson()
+ {
+ ObjectMapper mapper = new ObjectMapper();
+ String json;
+ try {
+ json = mapper.writeValueAsString(this);
+ } catch (JsonProcessingException e) {
+ json = "{erreur : Impossible to parse object StepLoan in JSON}";
+ }
+
+ return json;
+ }
+}
diff --git a/webapp/src/main/java/biz/eventual/bean/SubLoan.java b/webapp/src/main/java/biz/eventual/bean/SubLoan.java
new file mode 100755
index 0000000..b25eba1
--- /dev/null
+++ b/webapp/src/main/java/biz/eventual/bean/SubLoan.java
@@ -0,0 +1,31 @@
+package biz.eventual.bean;
+
+import java.math.BigDecimal;
+
+import lombok.Getter;
+import lombok.Setter;
+import com.google.gson.Gson;
+
+import biz.eventual.bean.LoanPlan;
+
+/**
+ * Created by tweety on 19/06/15.
+ */
+@Getter
+@Setter
+public class SubLoan extends LoanPlan {
+
+ int startingAt = 0;
+
+ public SubLoan() {}
+
+ public SubLoan(BigDecimal borrowed, BigDecimal rate, int period, int startingAt) {
+ super(borrowed, rate, period);
+ this.startingAt = startingAt;
+ }
+
+ public String toJson() {
+ Gson gson = new Gson();
+ return gson.toJson(this);
+ }
+}
diff --git a/webapp/src/main/java/biz/eventual/business/LoanBusiness.java b/webapp/src/main/java/biz/eventual/business/LoanBusiness.java
new file mode 100755
index 0000000..61c318a
--- /dev/null
+++ b/webapp/src/main/java/biz/eventual/business/LoanBusiness.java
@@ -0,0 +1,300 @@
+package biz.eventual.business;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import lombok.extern.slf4j.Slf4j;
+
+import biz.eventual.bean.LoanPlan;
+import biz.eventual.bean.Payment;
+import biz.eventual.enums.RatePeriod;
+
+/**
+ * Created by Thibault de Lambilly on 19/06/2015.
+ */
+@Slf4j
+public class LoanBusiness
+{
+ // http://www.math.hawaii.edu/~hile/math100/consf.htm
+ public static BigDecimal calculateMonthlyAmount(LoanPlan loanPlan) {
+
+ BigDecimal borrowed = loanPlan.getBorrowed().setScale(2);
+
+ BigDecimal zeroRate = loanPlan.getRate().divide(new BigDecimal(100.00));
+ BigDecimal oneRate = zeroRate.add(new BigDecimal(1));
+
+ int period = loanPlan.getPeriod() - loanPlan.getShiftRefundBy(); // starting at 1
+ BigDecimal powerRate = oneRate.pow(period);
+
+ // (borrowed * zeroRate * powerRate) / (powerRate - 1);
+ BigDecimal top = (borrowed.multiply(zeroRate)).multiply(powerRate);
+ //top.setScale(2, BigDecimal.ROUND_HALF_UP);
+
+ BigDecimal divideBy = powerRate.subtract(new BigDecimal(1));
+ BigDecimal payment = top.divide(divideBy, 2, RoundingMode.HALF_UP);
+
+ return payment.setScale(2, BigDecimal.ROUND_HALF_UP);
+ }
+
+ public static List getPayments(LoanPlan loanPlan) {
+
+ List payments = new ArrayList();
+
+ BigDecimal borrowed = loanPlan.getBorrowed();
+ BigDecimal zeroRate = loanPlan.getRate().divide(new BigDecimal("100.0"));
+
+ BigDecimal basePayment = LoanBusiness.calculateMonthlyAmount(loanPlan);
+ int period = loanPlan.getPeriod();
+
+ for (int i=0; i < period; i++) {
+
+ BigDecimal interest = borrowed.multiply(zeroRate).setScale(2, BigDecimal.ROUND_HALF_UP);
+
+ Payment payment = new Payment();
+ payment.setInterest(interest);
+
+ BigDecimal refunded = BigDecimal.ZERO;
+
+ if (i > (loanPlan.getShiftRefundBy()-1)) {
+ refunded = basePayment.subtract(interest);
+ }
+
+ payment.setCapital(refunded);
+ payments.add(payment);
+
+ borrowed = borrowed.subtract(refunded);
+ }
+
+ return payments;
+ }
+
+ /***
+ * Find how many payments for an amount loaned with a monthly payment according a rate
+ * @param borrowed
+ * @param basePayment
+ * @param rate
+ * @return
+ */
+ public static List findPeriod(BigDecimal borrowed, BigDecimal basePayment, BigDecimal rate)
+ {
+
+ List loans = new ArrayList();
+ int period = 0;
+
+ BigDecimal zeroRate = rate.divide(new BigDecimal(100.00));
+
+ BigDecimal left = borrowed;
+
+ BigDecimal interest;
+ // interest.setScale(2, BigDecimal.ROUND_HALF_UP);
+
+ BigDecimal refunded;
+ // refunded.setScale(2, BigDecimal.ROUND_HALF_UP);
+
+ do {
+
+ interest = left.multiply(zeroRate);
+ refunded = basePayment.subtract(interest);
+ left = left.subtract(refunded);
+
+ period++;
+
+ } while(left.intValue() > 0 && period < 1000);
+
+ loans.add(new LoanPlan(borrowed, rate, (period-1)));
+ loans.add(new LoanPlan(borrowed, rate, period));
+
+ return loans;
+ }
+
+ /***
+ * Transform values to get a LoanPlan object
+ * @param borrowed
+ * @param period
+ * @param rate
+ * @param ratePeriod
+ * @return LoanPlan
+ */
+ public static LoanPlan getLoanPlan(final BigDecimal borrowed, final int period, final BigDecimal rate, final RatePeriod ratePeriod, final BigDecimal insurance) {
+
+ LoanPlan loan = new LoanPlan();
+ loan.setBorrowed(borrowed);
+ loan.setPeriod(period);
+ loan.setInsurance(insurance);
+
+ BigDecimal actualRate = rate;
+
+ if (ratePeriod == RatePeriod.ANNUAL) {
+ actualRate = getMontlyRateFromYearly(actualRate);
+ }
+
+ loan.setRate(actualRate);
+
+ return loan;
+ }
+
+ /***
+ * Get LoanPlan without shiftRefundBy paramater
+ * @param borrowed
+ * @param period
+ * @param rate
+ * @param ratePeriod
+ * @param insurance
+ * @return
+ */
+ public static LoanPlan getLoanPlan(final double borrowed,
+ final int period,
+ final double rate,
+ final RatePeriod ratePeriod,
+ final double insurance) {
+
+ return getLoanPlan(borrowed, period,rate, ratePeriod, insurance, LoanPlan.REFUND_NOW);
+ }
+
+ /***
+ * Get LoanPlan including shiftRefundBy paramater
+ * @param borrowed
+ * @param period
+ * @param rate
+ * @param ratePeriod
+ * @param insurance
+ * @param refundStartingAt
+ * @return
+ */
+ public static LoanPlan getLoanPlan(final double borrowed,
+ final int period,
+ final double rate,
+ final RatePeriod ratePeriod,
+ final double insurance,
+ final int refundStartingAt) {
+
+ BigDecimal borrowedBg = new BigDecimal(borrowed).setScale(2, BigDecimal.ROUND_HALF_UP);
+ BigDecimal actualRate = new BigDecimal(rate).setScale(4, BigDecimal.ROUND_HALF_UP);
+ BigDecimal insuranceBg = new BigDecimal(insurance).setScale(4, BigDecimal.ROUND_HALF_UP);
+
+ LoanPlan loan = new LoanPlan();
+ loan.setBorrowed(borrowedBg);
+ loan.setPeriod(period);
+ loan.setInsurance(insuranceBg);
+ loan.setShiftRefundBy(refundStartingAt);
+
+ if (ratePeriod == RatePeriod.ANNUAL) {
+ actualRate = getMontlyRateFromYearly(actualRate);
+ }
+
+ loan.setRate(actualRate);
+
+ return loan;
+ }
+
+ /***
+ * Centralized method to get the period rate according to the annual capital
+ * @param rate
+ * @return
+ */
+ public static BigDecimal getMontlyRateFromYearly(BigDecimal rate)
+ {
+ return rate.divide(new BigDecimal(LoanPlan.PER_YEAR), 4, BigDecimal.ROUND_HALF_UP);
+ }
+
+ public static LoanPlan findScheduleStepPayment(LoanPlan loanPlan, TreeMap payments) {
+
+ BigDecimal insurance = loanPlan.getInsurance();
+ BigDecimal borrowed = loanPlan.getBorrowed();
+ BigDecimal zeroRate = loanPlan.getRate().divide(new BigDecimal("100.0"), 6, BigDecimal.ROUND_HALF_UP);
+
+ BigDecimal basePayment = loanPlan.getPayment();
+ BigDecimal baseAjustedPayment = basePayment;
+
+ int period = 1;
+ do {
+
+ BigDecimal interest = borrowed.multiply(zeroRate).setScale(2, BigDecimal.ROUND_HALF_UP);
+
+ // get the current payment
+ BigDecimal whichBasePayment = loanPlan.getPayment();
+ // If step payment
+ if (payments != null && payments.size() > 0) {
+ BigDecimal value = getBasePayment(payments, period);
+ if (value != null) {
+ whichBasePayment = value;
+ }
+ }
+
+ // changing base
+ if ( whichBasePayment.compareTo(basePayment) != 0) { // 0 means equals
+ basePayment = whichBasePayment;
+ /* TODO: To be found!!
+ LoanPlan revisedLoan = findPeriod(borrowed, basePayment, loanPlan.getRate()).get(0);
+ baseAjustedPayment = calculateMonthlyAmount(loanPlan);
+ */
+ baseAjustedPayment = basePayment;
+ }
+
+ BigDecimal refunded = BigDecimal.ZERO;
+ // Do I have started to refund ?
+ if (period > loanPlan.getShiftRefundBy()) {
+ refunded = baseAjustedPayment.subtract(interest);
+ }
+
+ borrowed = borrowed.subtract(refunded);
+
+ Payment payment = new Payment();
+ payment.setBorrowed(borrowed);
+ payment.setPeriod(period++);
+ payment.setInterest(interest);
+ payment.setCapital(refunded);
+ payment.setInsurance(insurance);
+
+ loanPlan.getPayments().add(payment);
+
+ } while(borrowed.intValue() > 0 && period < 500);
+
+ return loanPlan;
+ }
+
+ /***
+ * Return the current Payment according to the current period
+ * @param payments
+ * @param period
+ * @return
+ */
+ private static BigDecimal getBasePayment(TreeMap payments, int period)
+ {
+ BigDecimal basePayment = null;
+
+ for (Map.Entry entry : payments.entrySet()) {
+ if (period >= entry.getKey()) {
+ basePayment = ((Payment) entry.getValue()).getBasePayment();
+ }
+ }
+
+ return basePayment;
+ }
+
+ public static BigDecimal getBorrowedAmountLeftAt(LoanPlan loanPlan, int until) {
+
+ BigDecimal borrowed = loanPlan.getBorrowed();
+ BigDecimal zeroRate = loanPlan.getRate().divide(new BigDecimal("100.0"));
+
+ BigDecimal basePayment = LoanBusiness.calculateMonthlyAmount(loanPlan);
+
+ for (int i=0; i < until; i++) {
+
+ BigDecimal interest = borrowed.multiply(zeroRate).setScale(2, BigDecimal.ROUND_HALF_UP);
+
+ Payment payment = new Payment();
+ payment.setInterest(interest);
+
+ BigDecimal refunded = basePayment.subtract(interest);
+ borrowed = borrowed.subtract(refunded);
+ }
+
+ return borrowed.setScale(2, BigDecimal.ROUND_HALF_UP);
+ }
+
+}
diff --git a/webapp/src/main/java/biz/eventual/business/StepLoanBusiness.java b/webapp/src/main/java/biz/eventual/business/StepLoanBusiness.java
new file mode 100755
index 0000000..8b92b53
--- /dev/null
+++ b/webapp/src/main/java/biz/eventual/business/StepLoanBusiness.java
@@ -0,0 +1,125 @@
+package biz.eventual.business;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+import biz.eventual.bean.StepLoan;
+import biz.eventual.bean.LoanPlan;
+import biz.eventual.bean.Payment;
+import biz.eventual.bean.SubLoan;
+
+/**
+ * Created by Thibault de Lambilly on 25/06/2015.
+ */
+public class StepLoanBusiness
+{
+
+ public static StepLoan findPayment(LoanPlan loanPlan, List subLoans) {
+ return StepLoanBusiness.findPayment(new StepLoan(loanPlan, subLoans));
+ }
+
+ public static StepLoan findPayment(StepLoan stepLoan) {
+
+ BigDecimal searchAmount = LoanBusiness.calculateMonthlyAmount(stepLoan.getLoanPlan());
+ // keep it for record!
+ stepLoan.getLoanPlan().setPayment(searchAmount);
+
+ double[] step = new double[] {100, 10, 5, 1, 0.1};
+
+ for (int i = 0; i < step.length; i++) {
+ BigDecimal max = findMax(stepLoan, step[i], searchAmount);
+ if (max.compareTo(searchAmount) == 1) {
+ searchAmount = max.subtract(new BigDecimal(step[i])).setScale(2, BigDecimal.ROUND_HALF_UP);
+ }
+ }
+
+ stepLoan.setPayment(searchAmount);
+
+ return stepLoan;
+ }
+
+ private static BigDecimal findMax(StepLoan stepLoan, double step, BigDecimal amount) {
+
+ BigDecimal leftAmount = BigDecimal.ZERO;
+ BigDecimal stepBg = new BigDecimal(step)
+ .setScale(1, BigDecimal.ROUND_HALF_UP);
+
+ BigDecimal stepAmount = amount.subtract(stepBg); // -step 4 the first round
+
+ do {
+ stepAmount = stepAmount.add(stepBg);
+ leftAmount = workAmountForScaleLoan(stepLoan, stepAmount);
+ } while (leftAmount.intValue() > 0);
+
+ return stepAmount;
+ }
+
+
+ private static BigDecimal workAmountForScaleLoan(StepLoan stepLoan, BigDecimal basePayment) {
+
+ stepLoan.setPayments(new ArrayList());
+
+ BigDecimal searchAmount = stepLoan.getLoanPlan()
+ .getBorrowed()
+ .setScale(2, BigDecimal.ROUND_HALF_UP);
+
+ BigDecimal zeroRate = stepLoan.getLoanPlan()
+ .getRate()
+ .divide(new BigDecimal(100));
+
+ int period = stepLoan.getLoanPlan().getPeriod();
+
+ if (stepLoan.getSubloans() == null) stepLoan.setSubloans(new ArrayList());
+
+ List subLoansClone = new ArrayList(stepLoan.getSubloans());
+
+ SubLoan currentSubLoan = null;
+ if (subLoansClone != null && subLoansClone.size() > 0) {
+ currentSubLoan = subLoansClone.get(0);
+ }
+
+ /***
+ * Attention : the sub loan can it be bigger than the monthly interest payment ?
+ */
+ for (int i=0; i < period; i++) {
+
+ BigDecimal interest = searchAmount
+ .multiply(zeroRate)
+ .setScale(2, BigDecimal.ROUND_HALF_UP);
+
+ Payment onePayment = new Payment();
+ onePayment.setInterest(interest);
+
+ BigDecimal subPaymentDeduction = BigDecimal.ZERO;
+ if (currentSubLoan != null) {
+ int start = currentSubLoan.getStartingAt();
+ int lastSubPayment = (start + currentSubLoan.getPeriod()) - 1;
+
+ // Getting value
+ if (i >= (start-1) && i < lastSubPayment ) {
+ subPaymentDeduction = currentSubLoan.getPayment();
+ }
+
+ // subPayment done - switching to the next one
+ if (i == lastSubPayment - 1) {
+ subLoansClone.remove(currentSubLoan);
+
+ if (subLoansClone.size() > 0) {
+ currentSubLoan = subLoansClone.get(0);
+ }
+ }
+ }
+
+ BigDecimal stepAmount = basePayment.subtract(subPaymentDeduction);
+ BigDecimal refunded = stepAmount.subtract(interest);
+
+ onePayment.setCapital(refunded);
+ stepLoan.getPayments().add(onePayment);
+
+ searchAmount = searchAmount.subtract(refunded);
+ }
+
+ return searchAmount;
+ }
+}
diff --git a/webapp/src/main/java/biz/eventual/controller/CorsFilter.java b/webapp/src/main/java/biz/eventual/controller/CorsFilter.java
new file mode 100644
index 0000000..988fd1e
--- /dev/null
+++ b/webapp/src/main/java/biz/eventual/controller/CorsFilter.java
@@ -0,0 +1,39 @@
+package biz.eventual.controller;
+
+import java.io.IOException;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.stereotype.Component;
+
+/**
+ * Created by tweety on 11/07/15.
+ */
+@Component
+public class CorsFilter implements Filter {
+
+ // https://spring.io/blog/2015/06/08/cors-support-in-spring-framework
+
+ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
+ throws IOException, ServletException {
+
+ HttpServletResponse response = (HttpServletResponse) res;
+ response.setHeader("Access-Control-Allow-Origin", "*");
+ response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
+ response.setHeader("Access-Control-Max-Age", "3600");
+ response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
+ chain.doFilter(req, res);
+
+ }
+
+ public void init(FilterConfig filterConfig) {
+ }
+
+ public void destroy() {
+ }
+
+}
\ No newline at end of file
diff --git a/webapp/src/main/java/biz/eventual/controller/LoanController.java b/webapp/src/main/java/biz/eventual/controller/LoanController.java
new file mode 100755
index 0000000..de1ddf6
--- /dev/null
+++ b/webapp/src/main/java/biz/eventual/controller/LoanController.java
@@ -0,0 +1,106 @@
+package biz.eventual.controller;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.TreeMap;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import biz.eventual.bean.LoanPlan;
+import biz.eventual.bean.Payment;
+import biz.eventual.dto.PaymentDto;
+import biz.eventual.enums.RatePeriod;
+import biz.eventual.service.ServiceLoan;
+
+/**
+ * Created by tweety on 29/06/15.
+ */
+@RestController
+public class LoanController {
+
+ @Autowired
+ ServiceLoan serviceLoan;
+
+
+ /*
+ /!\ Spring considers that anything behind the last dot is a file extension such as .json or .xml and trucate it to retrieve your parameter.
+ */
+ @RequestMapping(value = "/loan/get/{capital}/{period}/{rate:.+}", method = RequestMethod.GET)
+ public LoanPlan getLoanNoInsurance(@PathVariable double capital,
+ @PathVariable int period,
+ @PathVariable double rate) {
+
+ // return serviceLoan.findScheduleStepPayment(54537.35, payments, 180, 3.6, RatePeriod.ANNUAL, 31.82);
+ return serviceLoan.findScheduleStepPayment(capital, null, period, rate, RatePeriod.ANNUAL, 0);
+ }
+
+ @RequestMapping(value = "/loan/get/{loan}", method = RequestMethod.POST)
+ public LoanPlan getLoanFromLoan(@PathVariable LoanPlan loan) {
+
+ // return serviceLoan.findScheduleStepPayment(54537.35, payments, 180, 3.6, RatePeriod.ANNUAL, 31.82);
+ return serviceLoan.findScheduleStepPayment(
+ loan.getBorrowed().doubleValue(),
+ null,
+ loan.getPeriod(),
+ loan.getRate().doubleValue(),
+ RatePeriod.MONTHLY,
+ 0
+ );
+ }
+
+
+ @RequestMapping(value = "/loan/get/{capital}/{period}/{rate}/{insurance:.+}", method = RequestMethod.GET)
+ public LoanPlan getLoan(@PathVariable double capital,
+ @PathVariable int period,
+ @PathVariable double rate,
+ @PathVariable double insurance) {
+
+ return serviceLoan.findScheduleStepPayment(capital, null, period, rate, RatePeriod.ANNUAL, insurance);
+ }
+
+ @RequestMapping(value = "/loan/step/{capital}/{period}/{rate}/{insurance}/{steps:.+}", method = RequestMethod.GET)
+ public LoanPlan getLoanWithStepPayment(@PathVariable double capital,
+ @PathVariable int period,
+ @PathVariable double rate,
+ @PathVariable double insurance,
+ @PathVariable String steps) {
+
+ // http://localhost:5001/loan/step/1000.0/12/2.0/0/[{"k":1000, "i":1.0, "p": 6}]
+
+ ObjectMapper mapper = new ObjectMapper();
+ List paymentsData = null;
+
+ try {
+ // List
+ paymentsData = mapper.readValue(steps, new TypeReference>(){});
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ TreeMap payments = new TreeMap();
+
+ for (PaymentDto dto : paymentsData) {
+ Payment payment = new Payment();
+ payment.setCapital(new BigDecimal(dto.getK()).setScale(2, BigDecimal.ROUND_HALF_UP));
+ payment.setInsurance(new BigDecimal(dto.getI()).setScale(2, BigDecimal.ROUND_HALF_UP));
+
+ payments.put(dto.getP(), payment);
+ }
+
+ /*
+ Payment payment = new Payment();
+ payment.setCapital(new BigDecimal(510).setScale(2, BigDecimal.ROUND_HALF_UP));
+ payment.setInsurance(new BigDecimal(31.82).setScale(2, BigDecimal.ROUND_HALF_UP));
+ payments.put(79, payment);
+ */
+
+ return serviceLoan.findScheduleStepPayment(capital, payments, period, rate, RatePeriod.ANNUAL, insurance);
+ }
+}
diff --git a/webapp/src/main/java/biz/eventual/dto/PaymentDto.java b/webapp/src/main/java/biz/eventual/dto/PaymentDto.java
new file mode 100755
index 0000000..8dc7cc1
--- /dev/null
+++ b/webapp/src/main/java/biz/eventual/dto/PaymentDto.java
@@ -0,0 +1,17 @@
+package biz.eventual.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Created by thibault.delambilly on 02/07/2015.
+ */
+@Getter
+@Setter
+public class PaymentDto
+{
+ private double k = 0.0; // capital
+ private double i = 0.0; // interest
+ private int p = 0; // period
+ private int f = 0; // insurance
+}
diff --git a/webapp/src/main/java/biz/eventual/enums/RatePeriod.java b/webapp/src/main/java/biz/eventual/enums/RatePeriod.java
new file mode 100755
index 0000000..4093124
--- /dev/null
+++ b/webapp/src/main/java/biz/eventual/enums/RatePeriod.java
@@ -0,0 +1,9 @@
+package biz.eventual.enums;
+
+/**
+ * Created by Thibault de Lambilly on 19/06/2015.
+ */
+public enum RatePeriod
+{
+ MONTHLY, ANNUAL
+}
diff --git a/webapp/src/main/java/biz/eventual/service/ServiceLoan.java b/webapp/src/main/java/biz/eventual/service/ServiceLoan.java
new file mode 100755
index 0000000..ae75d46
--- /dev/null
+++ b/webapp/src/main/java/biz/eventual/service/ServiceLoan.java
@@ -0,0 +1,31 @@
+package biz.eventual.service;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.TreeMap;
+
+import biz.eventual.bean.LoanPlan;
+import biz.eventual.bean.Payment;
+import biz.eventual.enums.RatePeriod;
+
+/**
+ * Created by Thibault de Lambilly on 19/06/2015.
+ */
+public interface ServiceLoan
+{
+ BigDecimal getMonthlyAmount(final double borrowed, final int period, final double rate, final RatePeriod ratePeriod);
+ BigDecimal getMonthlyAmount(final double borrowed, final int period, final double rate, RatePeriod ratePeriod, final int refundStartingAt);
+
+ List getPayments(final double borrowed, final double rate, final int period, final RatePeriod ratePeriod);
+ List getPayments(final double borrowed, final double rate, final int period, final RatePeriod ratePeriod, final int refundStartingAt);
+
+ LoanPlan getLoanWithPayments(final double borrowed,
+ final double rate,
+ final int period,
+ final RatePeriod ratePeriod);
+
+ List findPeriod(final double borrowed, final double basePayment, double rate, final RatePeriod ratePeriod);
+
+ LoanPlan findScheduleStepPayment(final double borrowed, final TreeMap payments, final int initialPeriod, final double rate, final RatePeriod ratePeriod, final double insurance);
+ LoanPlan findScheduleStepPayment(final double borrowed, final TreeMap payments, final int initialPeriod, final double rate, final RatePeriod ratePeriod, final double insurance, final int refundStartingAt);
+}
\ No newline at end of file
diff --git a/webapp/src/main/java/biz/eventual/service/ServiceLoanImpl.java b/webapp/src/main/java/biz/eventual/service/ServiceLoanImpl.java
new file mode 100755
index 0000000..39de330
--- /dev/null
+++ b/webapp/src/main/java/biz/eventual/service/ServiceLoanImpl.java
@@ -0,0 +1,122 @@
+package biz.eventual.service;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.TreeMap;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import biz.eventual.business.LoanBusiness;
+import biz.eventual.bean.LoanPlan;
+import biz.eventual.bean.Payment;
+import biz.eventual.enums.RatePeriod;
+
+/**
+ * Created by Thibault de Lambilly on 19/06/2015.
+ */
+@Slf4j
+@Service
+public class ServiceLoanImpl implements ServiceLoan
+{
+ @Override
+ public BigDecimal getMonthlyAmount(final double borrowed, final int period, final double rate, RatePeriod ratePeriod)
+ {
+ LoanPlan loan = LoanBusiness.getLoanPlan(borrowed, period, rate, ratePeriod, LoanPlan.REFUND_NOW);
+ return LoanBusiness.calculateMonthlyAmount(loan);
+ }
+
+ @Override
+ public BigDecimal getMonthlyAmount(final double borrowed, final int period, final double rate, RatePeriod ratePeriod, final int refundStartingAt)
+ {
+ LoanPlan loan = LoanBusiness.getLoanPlan(borrowed, period, rate, ratePeriod, refundStartingAt);
+ return LoanBusiness.calculateMonthlyAmount(loan);
+ }
+
+ @Override
+ public List getPayments(final double borrowed, final double rate, final int period, RatePeriod ratePeriod)
+ {
+ LoanPlan loan = LoanBusiness.getLoanPlan(borrowed, period, rate, ratePeriod, 0, LoanPlan.REFUND_NOW);
+ return LoanBusiness.getPayments(loan);
+ }
+
+ @Override
+ public List getPayments(final double borrowed, final double rate, final int period, RatePeriod ratePeriod, final int refundStartingAt)
+ {
+ LoanPlan loan = LoanBusiness.getLoanPlan(borrowed, period, rate, ratePeriod, 0, refundStartingAt);
+ return LoanBusiness.getPayments(loan);
+ }
+
+ @Override
+ public LoanPlan getLoanWithPayments(double borrowed, double rate, int period, RatePeriod ratePeriod)
+ {
+ LoanPlan loan = LoanBusiness.getLoanPlan(borrowed, period, rate, ratePeriod, 0);
+ loan.setPayments(LoanBusiness.getPayments(loan));
+
+ log.debug("getLoanWithPayments: %s", loan.toJson());
+
+ return loan;
+ }
+
+ @Override
+ public List findPeriod(double borrowed, double basePayment, double rate, RatePeriod ratePeriod) {
+
+ BigDecimal borrowedBg = new BigDecimal(borrowed).setScale(2, BigDecimal.ROUND_HALF_UP);
+ BigDecimal baseBg = new BigDecimal(basePayment).setScale(2, BigDecimal.ROUND_HALF_UP);
+ BigDecimal actualRate = new BigDecimal(rate).setScale(4, BigDecimal.ROUND_HALF_UP);
+
+ if (ratePeriod == RatePeriod.ANNUAL) {
+ actualRate = LoanBusiness.getMontlyRateFromYearly(actualRate);
+ }
+
+ return LoanBusiness.findPeriod(borrowedBg, baseBg, actualRate);
+ }
+
+ /***
+ * Without shiftRefundBy
+ * @param borrowed
+ * @param payments
+ * @param initialPeriod
+ * @param rate
+ * @param ratePeriod
+ * @param insurance
+ * @return
+ */
+ @Override
+ public LoanPlan findScheduleStepPayment(final double borrowed,
+ final TreeMap payments,
+ final int initialPeriod,
+ final double rate,
+ final RatePeriod ratePeriod,
+ final double insurance) {
+
+ return findScheduleStepPayment(borrowed, payments, initialPeriod, rate, ratePeriod, insurance, LoanPlan.REFUND_NOW);
+ }
+
+ /***
+ * Including shiftRefundBy
+ * @param borrowed
+ * @param refundStartingAt
+ * @param payments
+ * @param initialPeriod
+ * @param rate
+ * @param ratePeriod
+ * @param insurance
+ * @return
+ */
+ @Override
+ public LoanPlan findScheduleStepPayment(final double borrowed,
+ final TreeMap payments,
+ final int initialPeriod,
+ final double rate,
+ final RatePeriod ratePeriod,
+ final double insurance,
+ final int refundStartingAt)
+ {
+ LoanPlan loan = LoanBusiness.getLoanPlan(borrowed, initialPeriod, rate, ratePeriod, insurance, refundStartingAt);
+ BigDecimal payment = LoanBusiness.calculateMonthlyAmount(loan);
+ loan.setPayment(payment);
+
+ return LoanBusiness.findScheduleStepPayment(loan, payments);
+ }
+}
diff --git a/webapp/src/main/java/biz/eventual/service/ServiceStepLoan.java b/webapp/src/main/java/biz/eventual/service/ServiceStepLoan.java
new file mode 100755
index 0000000..e87740c
--- /dev/null
+++ b/webapp/src/main/java/biz/eventual/service/ServiceStepLoan.java
@@ -0,0 +1,16 @@
+package biz.eventual.service;
+
+import java.util.List;
+
+import biz.eventual.bean.StepLoan;
+import biz.eventual.bean.SubLoan;
+import biz.eventual.enums.RatePeriod;
+
+/**
+ * Created by Thibault de Lambilly on 19/06/2015.
+ */
+public interface ServiceStepLoan
+{
+ StepLoan getPayment(final double borrowed, final int period, final double rate, final RatePeriod ratePeriod, final List subLoans, final double interest);
+
+}
diff --git a/webapp/src/main/java/biz/eventual/service/ServiceStepLoanImpl.java b/webapp/src/main/java/biz/eventual/service/ServiceStepLoanImpl.java
new file mode 100755
index 0000000..39fe241
--- /dev/null
+++ b/webapp/src/main/java/biz/eventual/service/ServiceStepLoanImpl.java
@@ -0,0 +1,34 @@
+package biz.eventual.service;
+
+import java.util.List;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import biz.eventual.bean.LoanPlan;
+import biz.eventual.bean.StepLoan;
+import biz.eventual.bean.SubLoan;
+import biz.eventual.business.LoanBusiness;
+import biz.eventual.business.StepLoanBusiness;
+import biz.eventual.enums.RatePeriod;
+
+/**
+ * Created by Thibault de Lambilly on 19/06/2015.
+ */
+@Slf4j
+@Service
+public class ServiceStepLoanImpl implements ServiceStepLoan
+{
+ @Override
+ public StepLoan getPayment(final double borrowed,
+ final int period,
+ final double rate,
+ final RatePeriod ratePeriod,
+ final List subLoans,
+ final double interest)
+ {
+ LoanPlan loan = LoanBusiness.getLoanPlan(borrowed, period, rate, ratePeriod, interest);
+
+ return StepLoanBusiness.findPayment(loan, subLoans);
+ }
+}
diff --git a/webapp/src/main/resources/application.properties b/webapp/src/main/resources/application.properties
new file mode 100755
index 0000000..1df34b1
--- /dev/null
+++ b/webapp/src/main/resources/application.properties
@@ -0,0 +1,43 @@
+## database config
+#spring.datasource.url = jdbc:mysql://localhost:3306/loanwizard
+#spring.datasource.username = root
+#spring.datasource.password =
+
+## Show or not log for each sql query
+spring.jpa.show-sql = true
+
+# Hibernate ddl auto (create, create-drop, update)
+#spring.jpa.hibernate.ddl-auto = update
+
+## Naming strategy
+spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy
+
+# Use spring.jpa.properties.* for Hibernate native properties (the prefix is
+# stripped before adding them to the entity manager)
+
+## The SQL dialect makes Hibernate generate better SQL for the chosen database.
+spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
+
+## for automatical reload
+spring.thymeleaf.cache=false
+
+## random port number
+server.port:5001
+
+# define the messages folder
+spring.messages.basename=i18n/messages
+
+## add /shutdown command
+endpoints.shutdown.enabled=true
+## health complet
+endpoints.health.sensitive=false
+
+#logging.file=
+logging.level.org.springframework.web = DEBUG
+logging.level.biz.eventual = DEBUG
+logging.level.org.hibernate = DEBUG
+
+# Log file location (in addition to the console)
+logging.file = ./application.log
+
+debug=true
\ No newline at end of file
diff --git a/webapp/src/main/resources/banner.txt b/webapp/src/main/resources/banner.txt
new file mode 100755
index 0000000..f2b02be
--- /dev/null
+++ b/webapp/src/main/resources/banner.txt
@@ -0,0 +1,5 @@
+ _____ _____ _ _ _____ _ _ _ _ ___ _ _ ___ ___ _ _ ___ ___ ___
+ | __\ \ / / __| \| |_ _| | | |/_\ | | | _ ) | | / __|_ _| \| | __/ __/ __|
+ | _| \ V /| _|| .` | | | | |_| / _ \| |__ | _ \ |_| \__ \| || .` | _|\__ \__ \
+ |___| \_/ |___|_|\_| |_| \___/_/ \_\____| |___/\___/|___/___|_|\_|___|___/___/
+
diff --git a/webapp/src/main/resources/logback.xml.old b/webapp/src/main/resources/logback.xml.old
new file mode 100755
index 0000000..2410b2c
--- /dev/null
+++ b/webapp/src/main/resources/logback.xml.old
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/webapp/src/main/resources/templates/index.html b/webapp/src/main/resources/templates/index.html
new file mode 100755
index 0000000..ea1559f
--- /dev/null
+++ b/webapp/src/main/resources/templates/index.html
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+ Yo
+
+
\ No newline at end of file
diff --git a/webapp/src/test/java/biz/eventual/Business/LoanBusinessTest.java b/webapp/src/test/java/biz/eventual/Business/LoanBusinessTest.java
new file mode 100755
index 0000000..9e431af
--- /dev/null
+++ b/webapp/src/test/java/biz/eventual/Business/LoanBusinessTest.java
@@ -0,0 +1,24 @@
+package biz.eventual.Business;
+
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.boot.test.SpringApplicationConfiguration;
+import org.springframework.test.context.web.WebAppConfiguration;
+
+import biz.eventual.LoanwizardApplication;
+
+/**
+ * Created by Thibault de Lambilly on 30/07/2015.
+ */
+@RunWith(MockitoJUnitRunner.class)
+@SpringApplicationConfiguration(classes = LoanwizardApplication.class)
+@WebAppConfiguration
+public class LoanBusinessTest
+{
+ @Before
+ public void setUp() throws Exception
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/webapp/src/test/java/biz/eventual/LoanwizardApplicationTests.java b/webapp/src/test/java/biz/eventual/LoanwizardApplicationTests.java
new file mode 100755
index 0000000..649eeda
--- /dev/null
+++ b/webapp/src/test/java/biz/eventual/LoanwizardApplicationTests.java
@@ -0,0 +1,18 @@
+package biz.eventual;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.SpringApplicationConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.web.WebAppConfiguration;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@SpringApplicationConfiguration(classes = LoanwizardApplication.class)
+@WebAppConfiguration
+public class LoanwizardApplicationTests {
+
+ @Test
+ public void contextLoads() {
+ }
+
+}
diff --git a/webapp/src/test/java/biz/eventual/service/ServiceLoanTest.java b/webapp/src/test/java/biz/eventual/service/ServiceLoanTest.java
new file mode 100755
index 0000000..7efb28b
--- /dev/null
+++ b/webapp/src/test/java/biz/eventual/service/ServiceLoanTest.java
@@ -0,0 +1,123 @@
+package biz.eventual.service;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.TreeMap;
+
+import lombok.extern.slf4j.Slf4j;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.SpringApplicationConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.web.WebAppConfiguration;
+import junit.framework.TestCase;
+
+import biz.eventual.LoanwizardApplication;
+import biz.eventual.bean.StepLoan;
+import biz.eventual.bean.LoanPlan;
+import biz.eventual.bean.Payment;
+import biz.eventual.enums.RatePeriod;
+
+/**
+ * Created by Thibault de Lambilly on 19/06/2015.
+ */
+@Slf4j
+@RunWith(SpringJUnit4ClassRunner.class)
+@SpringApplicationConfiguration(classes = LoanwizardApplication.class)
+@WebAppConfiguration
+public class ServiceLoanTest extends TestCase
+{
+
+ @Autowired
+ ServiceLoan serviceLoan;
+
+ @Autowired
+ ServiceStepLoan serviceStepLoan;
+
+ @Test
+ public void checkMonthlyAmount() {
+
+ StepLoan stepLoan;
+
+ BigDecimal amount = serviceLoan.getMonthlyAmount(90000.0, 240, 2, RatePeriod.ANNUAL);
+ log.debug("montant "+amount);
+
+ amount = serviceLoan.getMonthlyAmount(15000.0, 60, 1, RatePeriod.ANNUAL);
+ log.debug("montant "+amount);
+
+ //double capital = serviceLoan.getMonthlyAmount(1000.0, 24, 18.0, RatePeriod.ANNUAL);
+ //assertEquals(49.92, capital);
+
+ amount = serviceLoan.getMonthlyAmount(65000, 96, 2.85, RatePeriod.ANNUAL);
+ assertEquals(758.00, amount.doubleValue());
+
+ }
+
+ @Test
+ public void findPeriod() {
+
+ List loans = serviceLoan.findPeriod(20000, 660, 12, RatePeriod.ANNUAL);
+ assertTrue(loans.size() == 2);
+ assertEquals(36, loans.get(0).getPeriod());
+ assertEquals(37, loans.get(1).getPeriod());
+ }
+
+ @Test
+ public void getPayment() {
+
+ List payments;
+
+ payments = serviceLoan.getPayments(20000, 12, 36, RatePeriod.ANNUAL);
+ assertEquals(36, payments.size());
+ assertEquals(664.29, payments.get(0).getBasePayment().doubleValue());
+
+ payments = serviceLoan.getPayments(54537.35, 3.6, 180, RatePeriod.ANNUAL);
+ assertEquals(180, payments.size());
+ assertEquals(392.56, payments.get(0).getBasePayment().doubleValue());
+
+ // shift by 12 payments
+ payments = serviceLoan.getPayments(54537.35, 3.6, 192, RatePeriod.ANNUAL, 12);
+ assertEquals(192, payments.size());
+ // only interest
+ assertEquals(163.61, payments.get(0).getBasePayment().doubleValue());
+ // last interest
+ assertEquals(163.61, payments.get(11).getBasePayment().doubleValue());
+ // start refunding
+ assertEquals(392.56, payments.get(12).getBasePayment().doubleValue());
+
+ }
+
+ @Test
+ public void findScheduleStepPayment() {
+
+ LoanPlan loanPlan;
+
+ // My loan
+ BigDecimal amount = serviceLoan.getMonthlyAmount(54537.35, 180, 3.6, RatePeriod.ANNUAL);
+ assertEquals(392.56, amount.doubleValue());
+
+ // No change
+ loanPlan = serviceLoan.findScheduleStepPayment(54537.35, null, 180, 3.6, RatePeriod.ANNUAL, 0);
+ assertEquals(180, loanPlan.getPayments().size());
+
+ // With step
+ TreeMap payments = new TreeMap();
+ Payment payment = new Payment();
+
+ payment.setCapital(new BigDecimal(510).setScale(2, BigDecimal.ROUND_HALF_UP));
+ payment.setInsurance(new BigDecimal(31.82).setScale(2, BigDecimal.ROUND_HALF_UP));
+ payments.put(79, payment);
+
+ loanPlan = serviceLoan.findScheduleStepPayment(54537.35, payments, 180, 3.6, RatePeriod.ANNUAL, 0);
+ assertEquals(154, loanPlan.getPayments().size());
+
+ payment = new Payment();
+ payment.setCapital(new BigDecimal(560).setScale(2, BigDecimal.ROUND_HALF_UP));
+ payments.put(103, payment);
+
+ loanPlan = serviceLoan.findScheduleStepPayment(54537.35, payments, 180, 3.6, RatePeriod.ANNUAL, 0);
+ assertEquals(149, loanPlan.getPayments().size());
+ }
+
+}
\ No newline at end of file
diff --git a/webapp/src/test/java/biz/eventual/service/ServiceStepLoanTest.java b/webapp/src/test/java/biz/eventual/service/ServiceStepLoanTest.java
new file mode 100755
index 0000000..3c33b49
--- /dev/null
+++ b/webapp/src/test/java/biz/eventual/service/ServiceStepLoanTest.java
@@ -0,0 +1,75 @@
+package biz.eventual.service;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+import lombok.extern.slf4j.Slf4j;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.SpringApplicationConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.web.WebAppConfiguration;
+import junit.framework.TestCase;
+
+import biz.eventual.LoanwizardApplication;
+import biz.eventual.bean.StepLoan;
+import biz.eventual.bean.SubLoan;
+import biz.eventual.enums.RatePeriod;
+
+/**
+ * Created by Thibault de Lambilly on 19/06/2015.
+ */
+@Slf4j
+@RunWith(SpringJUnit4ClassRunner.class)
+@SpringApplicationConfiguration(classes = LoanwizardApplication.class)
+@WebAppConfiguration
+public class ServiceStepLoanTest extends TestCase
+{
+
+ @Autowired
+ ServiceLoan serviceLoan;
+
+ @Autowired
+ ServiceStepLoan serviceStepLoan;
+
+ @Test
+ public void checkStepMonthlyAmount() {
+
+ StepLoan stepLoan;
+
+ // Subloan empty
+ stepLoan = serviceStepLoan.getPayment(65000.0, 96, 0.2375, RatePeriod.MONTHLY, new ArrayList(), 0);
+ assertEquals(758.00, stepLoan.getPayment().doubleValue());
+
+
+ List subLoans = new ArrayList();
+ BigDecimal borrowed = new BigDecimal(15000.0).setScale(2, BigDecimal.ROUND_HALF_UP);
+ BigDecimal rate = new BigDecimal(1).setScale(2, BigDecimal.ROUND_HALF_UP);
+ SubLoan subLoan = new SubLoan(borrowed, rate, 60, 1);
+
+ BigDecimal payment = serviceLoan.getMonthlyAmount(15000.0, 60, 1, RatePeriod.ANNUAL);
+ subLoan.setPayment(payment);
+ subLoans.add(subLoan);
+
+ stepLoan = serviceStepLoan.getPayment(90000.0, 240, 2, RatePeriod.ANNUAL, subLoans, 0);
+ log.debug("XXXXX "+stepLoan.toJson());
+
+ }
+
+ @Test
+ public void getStepLoan() {
+
+ BigDecimal monthlyPayment;
+
+ monthlyPayment = serviceLoan.getMonthlyAmount(20000, 36, 12, RatePeriod.ANNUAL);
+ assertEquals(664.29, monthlyPayment);
+
+ StepLoan stepLoan = serviceStepLoan.getPayment(20000, 36, 12, RatePeriod.ANNUAL, null, 0);
+ assertEquals(664.29, stepLoan.getPayments().get(0).getBasePayment().doubleValue());
+
+ }
+
+
+}
\ No newline at end of file