diff --git a/README.md b/README.md index 761ea97..29b1b9e 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,10 @@ https://letsencrypt.org/getting-started/ # TODO - Server: SSL und logs konfigurieren -- Transaktionen mit Passwort-Management ausstatten +- Transaktionen mit Passwort-Management ausstatten, Usermanagement korrekt aufsetzen und Rollen durchreichen (derzeit noch inMemoryAuthentication) - http://kielczewski.eu/2014/12/spring-boot-security-application/ - http://spr.com/part-5-integrating-spring-security-with-spring-boot-web/ -- Metadaten für Icon einbauen +- Metadaten für Icon einbauen (warum funktioniert das nicht?!?) diff --git a/src/main/java/de/tilman/transactions/Application.java b/src/main/java/de/tilman/transactions/Application.java index b5533f4..62317cd 100644 --- a/src/main/java/de/tilman/transactions/Application.java +++ b/src/main/java/de/tilman/transactions/Application.java @@ -1,7 +1,10 @@ package de.tilman.transactions; +import org.h2.server.web.WebServlet; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.embedded.ServletRegistrationBean; +import org.springframework.context.annotation.Bean; @SpringBootApplication public class Application { @@ -46,6 +49,6 @@ public class Application { // ServletRegistrationBean registration = new ServletRegistrationBean(new WebServlet()); // registration.addUrlMappings("/console/*"); // return registration; -// } +// } } diff --git a/src/main/java/de/tilman/transactions/SecurityConfiguration.java b/src/main/java/de/tilman/transactions/SecurityConfiguration.java index 7e8fc08..504aefe 100644 --- a/src/main/java/de/tilman/transactions/SecurityConfiguration.java +++ b/src/main/java/de/tilman/transactions/SecurityConfiguration.java @@ -16,8 +16,8 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() - .withUser("adam@mail.com").password("test").roles("USER", "ADMIN").and() - .withUser("betty@mail.com").password("test").roles("USER"); + .withUser("adam").password("test").roles("USER", "ADMIN").and() + .withUser("betty").password("test").roles("USER"); } @Override @@ -29,7 +29,7 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { .anyRequest().authenticated(); http.httpBasic(); // XXX mit HTTP Basic Auth funktioniert der Logout nicht richtig -// http.formLogin(); + http.formLogin(); http.csrf().disable(); // XXX später wieder aktivieren // http.headers().frameOptions().disable(); // for H2 console diff --git a/src/main/java/de/tilman/transactions/domain/Pattern.java b/src/main/java/de/tilman/transactions/domain/Pattern.java new file mode 100644 index 0000000..4b35b43 --- /dev/null +++ b/src/main/java/de/tilman/transactions/domain/Pattern.java @@ -0,0 +1,105 @@ +package de.tilman.transactions.domain; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToOne; + +@Entity +public class Pattern { + + @Id + @GeneratedValue + private Long id; + + @ManyToOne(optional = false, cascade = CascadeType.ALL) + private Account account; + + private Integer position; + + private String typePattern; + private String datePattern; + private String textPattern; + private String amountPattern; + + @OneToOne + private Category targetCategory; + + private String targetDescription; + + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Account getAccount() { + return account; + } + + public void setAccount(Account account) { + this.account = account; + } + + public Category getTargetCategory() { + return targetCategory; + } + + public void setTargetCategory(Category targetCategory) { + this.targetCategory = targetCategory; + } + + public String getTargetDescription() { + return targetDescription; + } + + public void setTargetDescription(String targetDescription) { + this.targetDescription = targetDescription; + } + + public Integer getPosition() { + return position; + } + + public void setPosition(Integer position) { + this.position = position; + } + + public String getTypePattern() { + return typePattern; + } + + public void setTypePattern(String typePattern) { + this.typePattern = typePattern; + } + + public String getDatePattern() { + return datePattern; + } + + public void setDatePattern(String datePattern) { + this.datePattern = datePattern; + } + + public String getTextPattern() { + return textPattern; + } + + public void setTextPattern(String textPattern) { + this.textPattern = textPattern; + } + + public String getAmountPattern() { + return amountPattern; + } + + public void setAmountPattern(String amountPattern) { + this.amountPattern = amountPattern; + } + +} diff --git a/src/main/java/de/tilman/transactions/repository/PatternRepository.java b/src/main/java/de/tilman/transactions/repository/PatternRepository.java new file mode 100644 index 0000000..9031975 --- /dev/null +++ b/src/main/java/de/tilman/transactions/repository/PatternRepository.java @@ -0,0 +1,31 @@ +package de.tilman.transactions.repository; + +import java.util.List; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.data.rest.core.annotation.RestResource; +import org.springframework.security.access.prepost.PreAuthorize; + +import de.tilman.transactions.domain.Pattern; + +public interface PatternRepository extends PagingAndSortingRepository { + + @RestResource(path = "listForAccount") + List findByAccountIdOrderByPositionAsc(@Param("accountId") Long accountId); + + @PreAuthorize("hasRole('ROLE_ADMIN')") + @Override + Pattern save(Pattern pattern); + + @PreAuthorize("hasRole('ROLE_ADMIN')") + @Override + void delete(Pattern pattern); + + @PreAuthorize("hasRole('ROLE_ADMIN')") + @Override + Page findAll(Pageable pageable); + +} diff --git a/src/main/resources/data-h2.sql b/src/main/resources/data-h2.sql index f6d7d92..b898e1c 100644 --- a/src/main/resources/data-h2.sql +++ b/src/main/resources/data-h2.sql @@ -11,24 +11,29 @@ INSERT INTO Category (id, name, account_id, position) VALUES INSERT INTO Account_User (account_id, user_id) VALUES (1, 1), (1, 2), (1, 3), (2, 1); +INSERT INTO Pattern (id, account_id, position, type_pattern, date_pattern, text_pattern, amount_pattern, target_category_id, target_description) VALUES + (NULL, 1, 1, '', '', '*.LIDL*.', '', 1, 'Lidl'), + (NULL, 1, 1, null, null, '*.Buchungstext: none*.', -40.0, 4, '(in Rücklage)'), + (NULL, 1, 1, 'Kontoübertrag', null, 'Auftraggeber: Tilman Liero Kto/IBAN: DE44200411330649163305*.', null, 4, '(Entnahme aus Rücklagen)'); + INSERT INTO Transaction (id, amount, account_id, category_id, date, description, creditor_id) VALUES - (NULL, 1280.81, 1, 2, '2016-04-01 14:14:14', 'Gehalt', null), - (NULL, -2.21, 1, 1, '2016-04-10', 'Brot', null), - (NULL, -3.41, 1, 1, '2016-04-12', 'Gemüse', null), - (NULL, -7.81, 1, 3, '2016-04-19', 'Kantine', null), - (NULL, -4.81, 1, 3, '2016-04-18', 'Currywurst (Auslage Adam)', 1), - (NULL, -4.50, 1, 3, '2016-04-19', 'Currywurst', null), - (NULL, -4.50, 1, 3, '2016-04-20', 'Currywurst', null), - (NULL, -4.09, 1, 3, '2016-04-18', 'Currywurst-Frühstück', null), - (NULL, -4.90, 1, 3, '2016-04-18', 'Currywurst (Auslage Betty)', 2), - (NULL, -8.90, 1, 3, '2016-04-18', 'Curry beim Inder (Auslage Adam)', 1), - - (NULL, -20, 1, 4, '2016-04-12 18:20:15', 'Entnahme Betty', 2), - (NULL, -10, 1, 4, '2016-04-14 10:03:00', 'Entnahme Adam', 1), - - (NULL, 1340.22, 2, 5, '2016-04-02', 'Gehalt Adam', null), - (NULL, -4.50, 2, 6, '2016-04-20', 'WebPack', null), - (NULL, -12.90, 2, 6, '2016-04-16', 'Host Europe', null); + (NULL, 1280.81, 1, 2, '2016-04-01 14:14:14', 'Gehalt', null), + (NULL, -2.21, 1, 1, '2016-04-10', 'Brot', null), + (NULL, -3.41, 1, 1, '2016-04-12', 'Gemüse', null), + (NULL, -7.81, 1, 3, '2016-04-19', 'Kantine', null), + (NULL, -4.81, 1, 3, '2016-04-18', 'Currywurst (Auslage Adam)', 1), + (NULL, -4.50, 1, 3, '2016-04-19', 'Currywurst', null), + (NULL, -4.50, 1, 3, '2016-04-20', 'Currywurst', null), + (NULL, -4.09, 1, 3, '2016-04-18', 'Currywurst-Frühstück', null), + (NULL, -4.90, 1, 3, '2016-04-18', 'Currywurst (Auslage Betty)', 2), + (NULL, -8.90, 1, 3, '2016-04-18', 'Curry beim Inder (Auslage Adam)', 1), + + (NULL, -20, 1, 4, '2016-04-12 18:20:15', 'Entnahme Betty', 2), + (NULL, -10, 1, 4, '2016-04-14 10:03:00', 'Entnahme Adam', 1), + + (NULL, 1340.22, 2, 5, '2016-04-02', 'Gehalt Adam', null), + (NULL, -4.50, 2, 6, '2016-04-20', 'WebPack', null), + (NULL, -12.90, 2, 6, '2016-04-16', 'Host Europe', null); -- add as many transactions as needed diff --git a/src/main/resources/static/css/app.css b/src/main/resources/static/css/app.css index 9216ef8..e5ef541 100644 --- a/src/main/resources/static/css/app.css +++ b/src/main/resources/static/css/app.css @@ -73,6 +73,19 @@ label { color: green; } +.importTable th, td { + font-size: 80%; + border: none; + border-bottom: 1px solid #ddd; + background-color: #fff; + padding: 1ex; +} + +.importTable .spacer { + background-color: transparent; + border: none; +} + /* for angular-ui, see https://angular-ui.github.io/bootstrap/#/getting_started */ .nav, .pagination, .carousel, .panel-title a { cursor: pointer; } diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index e04f734..0963d7c 100644 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -16,12 +16,13 @@ - + + diff --git a/src/main/resources/static/js/app.js b/src/main/resources/static/js/app.js index 85987db..7bb7d8a 100644 --- a/src/main/resources/static/js/app.js +++ b/src/main/resources/static/js/app.js @@ -4,6 +4,12 @@ transactionsApp.config([ '$routeProvider', function($routeProvider) { $routeProvider.when('/list', { templateUrl : 'partials/transaction-list.html', controller : 'TransactionListCtrl' + }).when('/import', { + templateUrl : 'partials/import.html', + controller : 'ImportCtrl' + }).when('/importList', { + templateUrl : 'partials/import-list.html', + controller : 'ImportCtrl' }).otherwise({ redirectTo : '/list' }); diff --git a/src/main/resources/static/js/controllers.js b/src/main/resources/static/js/controllers.js index 403d718..83f44ab 100644 --- a/src/main/resources/static/js/controllers.js +++ b/src/main/resources/static/js/controllers.js @@ -358,3 +358,19 @@ transactionControllers.controller('OutstandingsModalCtrl', [ '$scope', '$uibModa }); } } ]); + +transactionControllers.controller('ImportCtrl', [ '$scope', '$location', 'ImportService', function($scope, $location, ImportService) { + + $scope.importCSV = function () { + ImportService.saveData($scope.csvInput); + $location.path('importList'); + } + + $scope.processCSV = function () { + $scope.csv = Papa.parse("\"08.07.2016\";\"08.07.2016\";\"Übertrag/Überweisung\";\"Empfänger: Tilman Liero Kto/IBAN: DE44200411330649163305 BLZ/BIC: COBADEHD001 Buchungstext: none End-to-End-Ref.: nicht angegeben Ref. JK21619010302362/2\";\"-40,00\";\n\"07.07.2016\";\"07.07.2016\";\"Übertrag/Überweisung\";\"Empfänger: Tilman Liero Kto/IBAN: DE44200411330649163305 BLZ/BIC: COBADEHD001 Buchungstext: Uebertrag auf Tagesgeld PLUS-Konto End-to-End-Ref.: nicht angegeben Ref. J521618965322854/2\";\"-185,76\";\n\"06.07.2016\";\"06.07.2016\";\"Übertrag/Überweisung\";\"Auftraggeber: Bayer AG Buchungstext: /EDAM WORSHOP MIT/ Delaware (15.-16 End-to-End-Ref.: 8000012618 Ref. J321618880156700/705\";\"226,90\";\n\"01.07.2016\";\"01.07.2016\";\"Lastschrift/Belast.\";\"Auftraggeber: comdirect Visa-Monatsabrechnung Buchungstext: VISA-KARTE NR. 42635401XXXX5230 SUMME MONATSABRECHNUNG VISA TILMAN LIERO Ref. I421618341001491/75009\";\"-346,67\";\n"); +// $scope.csv = Papa.parse(ImportService.getData()); + console.log($scope.csv.meta.delimiter); + console.log($scope.csv); + } + +} ]); diff --git a/src/main/resources/static/js/papaparse.min.js b/src/main/resources/static/js/papaparse.min.js new file mode 100644 index 0000000..e0524a3 --- /dev/null +++ b/src/main/resources/static/js/papaparse.min.js @@ -0,0 +1,6 @@ +/*! + Papa Parse + v4.1.2 + https://github.com/mholt/PapaParse +*/ +!function(a,b){"function"==typeof define&&define.amd?define([],b):"object"==typeof module&&module.exports?module.exports=b():a.Papa=b()}(this,function(){"use strict";function a(a,b){if(b=b||{},b.worker&&y.WORKERS_SUPPORTED){var c=j();return c.userStep=b.step,c.userChunk=b.chunk,c.userComplete=b.complete,c.userError=b.error,b.step=q(b.step),b.chunk=q(b.chunk),b.complete=q(b.complete),b.error=q(b.error),delete b.worker,void c.postMessage({input:a,config:b,workerId:c.id})}var g=null;return"string"==typeof a?g=b.download?new d(b):new f(b):(s.File&&a instanceof File||a instanceof Object)&&(g=new e(b)),g.stream(a)}function b(a,b){function c(){"object"==typeof b&&("string"==typeof b.delimiter&&1===b.delimiter.length&&-1===y.BAD_DELIMITERS.indexOf(b.delimiter)&&(i=b.delimiter),("boolean"==typeof b.quotes||b.quotes instanceof Array)&&(h=b.quotes),"string"==typeof b.newline&&(j=b.newline))}function d(a){if("object"!=typeof a)return[];var b=[];for(var c in a)b.push(c);return b}function e(a,b){var c="";"string"==typeof a&&(a=JSON.parse(a)),"string"==typeof b&&(b=JSON.parse(b));var d=a instanceof Array&&a.length>0,e=!(b[0]instanceof Array);if(d){for(var g=0;g0&&(c+=i),c+=f(a[g],g);b.length>0&&(c+=j)}for(var h=0;hl;l++){l>0&&(c+=i);var m=d&&e?a[l]:l;c+=f(b[h][m],l)}h-1||" "===a.charAt(0)||" "===a.charAt(a.length-1);return c?'"'+a+'"':a}function g(a,b){for(var c=0;c-1)return!0;return!1}var h=!1,i=",",j="\r\n";if(c(),"string"==typeof a&&(a=JSON.parse(a)),a instanceof Array){if(!a.length||a[0]instanceof Array)return e(null,a);if("object"==typeof a[0])return e(d(a[0]),a)}else if("object"==typeof a)return"string"==typeof a.data&&(a.data=JSON.parse(a.data)),a.data instanceof Array&&(a.fields||(a.fields=a.meta&&a.meta.fields),a.fields||(a.fields=a.data[0]instanceof Array?a.fields:d(a.data[0])),a.data[0]instanceof Array||"object"==typeof a.data[0]||(a.data=[a.data])),e(a.fields||[],a.data||[]);throw"exception: Unable to serialize unrecognized input"}function c(a){function b(a){var b=o(a);b.chunkSize=parseInt(b.chunkSize),a.step||a.chunk||(b.chunkSize=null),this._handle=new g(b),this._handle.streamer=this,this._config=b}this._handle=null,this._paused=!1,this._finished=!1,this._input=null,this._baseIndex=0,this._partialLine="",this._rowCount=0,this._start=0,this._nextChunk=null,this.isFirstChunk=!0,this._completeResults={data:[],errors:[],meta:{}},b.call(this,a),this.parseChunk=function(a){if(this.isFirstChunk&&q(this._config.beforeFirstChunk)){var b=this._config.beforeFirstChunk(a);void 0!==b&&(a=b)}this.isFirstChunk=!1;var c=this._partialLine+a;this._partialLine="";var d=this._handle.parse(c,this._baseIndex,!this._finished);if(!this._handle.paused()&&!this._handle.aborted()){var e=d.meta.cursor;this._finished||(this._partialLine=c.substring(e-this._baseIndex),this._baseIndex=e),d&&d.data&&(this._rowCount+=d.data.length);var f=this._finished||this._config.preview&&this._rowCount>=this._config.preview;if(u)s.postMessage({results:d,workerId:y.WORKER_ID,finished:f});else if(q(this._config.chunk)){if(this._config.chunk(d,this._handle),this._paused)return;d=void 0,this._completeResults=void 0}return this._config.step||this._config.chunk||(this._completeResults.data=this._completeResults.data.concat(d.data),this._completeResults.errors=this._completeResults.errors.concat(d.errors),this._completeResults.meta=d.meta),!f||!q(this._config.complete)||d&&d.meta.aborted||this._config.complete(this._completeResults,this._input),f||d&&d.meta.paused||this._nextChunk(),d}},this._sendError=function(a){q(this._config.error)?this._config.error(a):u&&this._config.error&&s.postMessage({workerId:y.WORKER_ID,error:a,finished:!1})}}function d(a){function b(a){var b=a.getResponseHeader("Content-Range");return parseInt(b.substr(b.lastIndexOf("/")+1))}a=a||{},a.chunkSize||(a.chunkSize=y.RemoteChunkSize),c.call(this,a);var d;t?this._nextChunk=function(){this._readChunk(),this._chunkLoaded()}:this._nextChunk=function(){this._readChunk()},this.stream=function(a){this._input=a,this._nextChunk()},this._readChunk=function(){if(this._finished)return void this._chunkLoaded();if(d=new XMLHttpRequest,this._config.withCredentials&&(d.withCredentials=this._config.withCredentials),t||(d.onload=p(this._chunkLoaded,this),d.onerror=p(this._chunkError,this)),d.open("GET",this._input,!t),this._config.chunkSize){var a=this._start+this._config.chunkSize-1;d.setRequestHeader("Range","bytes="+this._start+"-"+a),d.setRequestHeader("If-None-Match","webkit-no-cache")}try{d.send()}catch(b){this._chunkError(b.message)}t&&0===d.status?this._chunkError():this._start+=this._config.chunkSize},this._chunkLoaded=function(){if(4==d.readyState){if(d.status<200||d.status>=400)return void this._chunkError();this._finished=!this._config.chunkSize||this._start>b(d),this.parseChunk(d.responseText)}},this._chunkError=function(a){var b=d.statusText||a;this._sendError(b)}}function e(a){a=a||{},a.chunkSize||(a.chunkSize=y.LocalChunkSize),c.call(this,a);var b,d,e="undefined"!=typeof FileReader;this.stream=function(a){this._input=a,d=a.slice||a.webkitSlice||a.mozSlice,e?(b=new FileReader,b.onload=p(this._chunkLoaded,this),b.onerror=p(this._chunkError,this)):b=new FileReaderSync,this._nextChunk()},this._nextChunk=function(){this._finished||this._config.preview&&!(this._rowCount=this._input.size,this.parseChunk(a.target.result)},this._chunkError=function(){this._sendError(b.error)}}function f(a){a=a||{},c.call(this,a);var b,d;this.stream=function(a){return b=a,d=a,this._nextChunk()},this._nextChunk=function(){if(!this._finished){var a=this._config.chunkSize,b=a?d.substr(0,a):d;return d=a?d.substr(a):"",this._finished=!d,this.parseChunk(b)}}}function g(a){function b(){if(v&&m&&(j("Delimiter","UndetectableDelimiter","Unable to auto-detect delimiting character; defaulted to '"+y.DefaultDelimiter+"'"),m=!1),a.skipEmptyLines)for(var b=0;b=u.length?(c.__parsed_extra||(c.__parsed_extra=[]),c.__parsed_extra.push(v.data[b][d])):c[u[d]]=v.data[b][d])}a.header&&(v.data[b]=c,d>u.length?j("FieldMismatch","TooManyFields","Too many fields: expected "+u.length+" fields but parsed "+d,b):d1&&(k+=Math.abs(o-f),f=o):f=o}m.data.length>0&&(l/=m.data.length),("undefined"==typeof e||e>k)&&l>1.99&&(e=k,d=j)}return a.delimiter=d,{successful:!!d,bestDelimiter:d}}function g(a){a=a.substr(0,1048576);var b=a.split("\r"),c=a.split("\n"),d=c.length>1&&c[0].length=b.length/2?"\r\n":"\r"}function i(a){var b=n.test(a);return b?parseFloat(a):a}function j(a,b,c,d){v.errors.push({type:a,code:b,message:c,row:d})}var k,l,m,n=/^\s*-?(\d*\.?\d+|\d+\.?\d*)(e[-+]?\d+)?\s*$/i,p=this,r=0,s=!1,t=!1,u=[],v={data:[],errors:[],meta:{}};if(q(a.step)){var w=a.step;a.step=function(d){if(v=d,c())b();else{if(b(),0===v.data.length)return;r+=d.data.length,a.preview&&r>a.preview?l.abort():w(v,p)}}}this.parse=function(c,d,e){if(a.newline||(a.newline=g(c)),m=!1,!a.delimiter){var i=f(c,a.newline);i.successful?a.delimiter=i.bestDelimiter:(m=!0,a.delimiter=y.DefaultDelimiter),v.meta.delimiter=a.delimiter}var j=o(a);return a.preview&&a.header&&j.preview++,k=c,l=new h(j),v=l.parse(k,d,e),b(),s?{meta:{paused:!0}}:v||{meta:{paused:!1}}},this.paused=function(){return s},this.pause=function(){s=!0,l.abort(),k=k.substr(l.getCharIndex())},this.resume=function(){s=!1,p.streamer.parseChunk(k)},this.aborted=function(){return t},this.abort=function(){t=!0,l.abort(),v.meta.aborted=!0,q(a.complete)&&a.complete(v),k=""}}function h(a){a=a||{};var b=a.delimiter,c=a.newline,d=a.comments,e=a.step,f=a.preview,g=a.fastMode;if(("string"!=typeof b||y.BAD_DELIMITERS.indexOf(b)>-1)&&(b=","),d===b)throw"Comment character same as delimiter";d===!0?d="#":("string"!=typeof d||y.BAD_DELIMITERS.indexOf(d)>-1)&&(d=!1),"\n"!=c&&"\r"!=c&&"\r\n"!=c&&(c="\n");var h=0,i=!1;this.parse=function(a,j,k){function l(a){v.push(a),y=h}function m(b){return k?o():("undefined"==typeof b&&(b=a.substr(h)),x.push(b),h=q,l(x),u&&p(),o())}function n(b){h=b,l(x),x=[],C=a.indexOf(c,h)}function o(a){return{data:v,errors:w,meta:{delimiter:b,linebreak:c,aborted:i,truncated:!!a,cursor:y+(j||0)}}}function p(){e(o()),v=[],w=[]}if("string"!=typeof a)throw"Input must be a string";var q=a.length,r=b.length,s=c.length,t=d.length,u="function"==typeof e;h=0;var v=[],w=[],x=[],y=0;if(!a)return o();if(g||g!==!1&&-1===a.indexOf('"')){for(var z=a.split(c),A=0;A=f)return v=v.slice(0,f),o(!0)}}return o()}for(var B=a.indexOf(b,h),C=a.indexOf(c,h);;)if('"'!==a[h])if(d&&0===x.length&&a.substr(h,t)===d){if(-1===C)return o();h=C+s,C=a.indexOf(c,h),B=a.indexOf(b,h)}else if(-1!==B&&(C>B||-1===C))x.push(a.substring(h,B)),h=B+r,B=a.indexOf(b,h);else{if(-1===C)break;if(x.push(a.substring(h,C)),n(C+s),u&&(p(),i))return o();if(f&&v.length>=f)return o(!0)}else{var D=h;for(h++;;){var D=a.indexOf('"',D+1);if(-1===D)return k||w.push({type:"Quotes",code:"MissingQuotes",message:"Quoted field unterminated",row:v.length,index:h}),m();if(D===q-1){var E=a.substring(h,D).replace(/""/g,'"');return m(E)}if('"'!==a[D+1]){if(a[D+1]===b){x.push(a.substring(h,D).replace(/""/g,'"')),h=D+1+r,B=a.indexOf(b,h),C=a.indexOf(c,h);break}if(a.substr(D+1,s)===c){if(x.push(a.substring(h,D).replace(/""/g,'"')),n(D+1+s),B=a.indexOf(b,h),u&&(p(),i))return o();if(f&&v.length>=f)return o(!0);break}}else D++}}return m()},this.abort=function(){i=!0},this.getCharIndex=function(){return h}}function i(){var a=document.getElementsByTagName("script");return a.length?a[a.length-1].src:""}function j(){if(!y.WORKERS_SUPPORTED)return!1;if(!v&&null===y.SCRIPT_PATH)throw new Error("Script path cannot be determined automatically when Papa Parse is loaded asynchronously. You need to set Papa.SCRIPT_PATH manually.");var a=y.SCRIPT_PATH||r;a+=(-1!==a.indexOf("?")?"&":"?")+"papaworker";var b=new s.Worker(a);return b.onmessage=k,b.id=x++,w[b.id]=b,b}function k(a){var b=a.data,c=w[b.workerId],d=!1;if(b.error)c.userError(b.error,b.file);else if(b.results&&b.results.data){var e=function(){d=!0,l(b.workerId,{data:[],errors:[],meta:{aborted:!0}})},f={abort:e,pause:m,resume:m};if(q(c.userStep)){for(var g=0;g +
+ + + + + + + + + + + + + + + + + + + + + + + +
WertstellungTypTextBetragKategorieNotizPattern
{{line[1]}}{{line[2]}}{{line[3]}}{{line[4]}}
+
+ \ No newline at end of file diff --git a/src/main/resources/static/partials/import.html b/src/main/resources/static/partials/import.html new file mode 100644 index 0000000..c5267cf --- /dev/null +++ b/src/main/resources/static/partials/import.html @@ -0,0 +1,14 @@ +
+
+
+ + +
+ +
+
+
+
\ No newline at end of file diff --git a/src/main/resources/static/partials/transaction-list.html b/src/main/resources/static/partials/transaction-list.html index 6d6914b..b928d30 100644 --- a/src/main/resources/static/partials/transaction-list.html +++ b/src/main/resources/static/partials/transaction-list.html @@ -101,7 +101,9 @@ - + + +
{{user.name}} {{user.outstandingAmount}} €