46 |
47 |
FinReconcile and RuleEngine
48 |
56 |
57 |
58 |
Technologies Used
59 |
60 | - C#
61 | - ASP.NET MVC 5
62 | - Bootstrap,Jquery
63 | - Specflow for BDD (http://specflow.org/)
64 | -
65 | Microsoft Azure for Hosting
66 |
67 | -
68 | CI/CD using Appveyor/Azure Team Services
69 |
70 | -
71 | Living Documentation Generation using http://www.picklesdoc.com/
72 |
73 |
74 |
75 |
76 |
Basic Architecture
77 |
78 |

79 |
80 |
81 |
Rule Engine
82 |
83 | - is used match the transactions for reconciliation
84 |
85 | - Wiki
86 |
87 |
88 |
93 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/Tests/FinConcile.Tests/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace FinConcile.Tests.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FinConcile.Tests.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// Looks up a localized string similar to ProfileName,TransactionDate,TransactionAmount,TransactionNarrative,TransactionDescription,TransactionID,TransactionType,WalletReference
65 | ///Card Campaign,2014-01-11 22:27:44,-20000,*MOLEPS ATM25 MOLEPOLOLE BW,DEDUCT,0584011808649511,1,P_NzI2ODY2ODlfMTM4MjcwMTU2NS45MzA5,
66 | ///Card Campaign,2014-01-11 22:39:11,-10000,*MOGODITSHANE2 MOGODITHSANE BW,DEDUCT,0584011815513406,1,P_NzI1MjA1NjZfMTM3ODczODI3Mi4wNzY5,
67 | ///Card Campaign,2014-01-11 23:28:11,-5000,CAPITAL BANK MOGODITSHANE B [rest of string was truncated]";.
68 | ///
69 | internal static string BankMarkoffFile20140113 {
70 | get {
71 | return ResourceManager.GetString("BankMarkoffFile20140113", resourceCulture);
72 | }
73 | }
74 |
75 | ///
76 | /// Looks up a localized string similar to ProfileName,TransactionDate,TransactionAmount,TransactionNarrative,TransactionDescription,TransactionID,TransactionType,WalletReference
77 | ///Card Campaign,2014-01-11 22:27:44,-20000,*MOLEPS ATM25 MOLEPOLOLE BW,DEDUCT,0584011808649511,1,P_NzI2ODY2ODlfMTM4MjcwMTU2NS45MzA5,
78 | ///Card Campaign,2014-01-11 22:39:11,-10000,*MOGODITSHANE2 MOGODITHSANE BW,DEDUCT,0584011815513406,1,P_NzI1MjA1NjZfMTM3ODczODI3Mi4wNzY5,
79 | ///Card Campaign,2014-01-11 23:28:11,-5000,CAPITAL BANK MOGODITSHANE B [rest of string was truncated]";.
80 | ///
81 | internal static string ClientMarkoffFile20140113 {
82 | get {
83 | return ResourceManager.GetString("ClientMarkoffFile20140113", resourceCulture);
84 | }
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/FinConcile.Web/Views/Transaction/UnmatchedDetailedReportPartial.cshtml:
--------------------------------------------------------------------------------
1 | @model FinReconcile.Models.CompareResult
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | | @Model.ClientMarkOffFile |
10 | |
11 | |
12 | @Model.BankMarkOffFile |
13 | |
14 | |
15 |
16 |
17 |
18 |
19 | | Date |
20 | Amount |
21 | Reference |
22 | Id |
23 | Description |
24 | ProfileName |
25 | Narrative |
26 | Type |
27 | Date |
28 | Amount |
29 | Reference |
30 | Id |
31 | Description |
32 | ProfileName |
33 | Narrative |
34 | Type |
35 |
36 |
37 |
38 |
39 | @for (int i = 0; i < Model.Result.NotMatchedItems.Count; i++)
40 | {
41 | var clientTrans = Model.Result.NotMatchedItems[i].ClientTransaction;
42 | var bankTrans = Model.Result.NotMatchedItems[i].BankTransaction;
43 |
44 | @if (clientTrans != null)
45 | {
46 | | @clientTrans.Date.ToString() |
47 | @clientTrans.Amount.ToString() |
48 | @clientTrans.WalletReference |
49 | @clientTrans.Id |
50 | @clientTrans.Description |
51 | @clientTrans.ProfileName |
52 | @clientTrans.Narrative |
53 | @clientTrans.Type |
54 | }
55 | else
56 | {
57 | |
58 | |
59 | |
60 | |
61 | |
62 | |
63 | }
64 | @if (bankTrans != null)
65 | {
66 | @bankTrans.Date.ToString() |
67 | @bankTrans.Amount.ToString() |
68 | @bankTrans.WalletReference |
69 | @bankTrans.Id |
70 | @bankTrans.Description |
71 | @bankTrans.ProfileName |
72 | @bankTrans.Narrative |
73 | @bankTrans.Type |
74 | }
75 | else
76 | {
77 | |
78 | |
79 | |
80 | |
81 | |
82 | |
83 | }
84 |
85 | }
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/src/FinConcile.Web/Controllers/TransactionController.cs:
--------------------------------------------------------------------------------
1 | using FinReconcile.Core.Domain.Interfaces;
2 | using FinReconcile.Core.Engines;
3 | using FinReconcile.Infra;
4 | using FinReconcile.Infra.Parsers;
5 | using FinReconcile.Infra.Providers;
6 | using FinReconcile.Models;
7 | using System;
8 | using System.Collections.Generic;
9 | using System.IO;
10 | using System.Linq;
11 | using System.Web;
12 | using System.Web.Mvc;
13 |
14 | namespace FinReconcile.Controllers
15 | {
16 | [RoutePrefix("transaction")]
17 | public class TransactionController : Controller
18 | {
19 | private IMarkOffFileProvider _markOffFileProvider;
20 | private IMarkOffFileParser _csvFileReader;
21 | private IReconcileEngine _reconcileEngine;
22 | public TransactionController(IMarkOffFileProvider markoffFileProvider, IMarkOffFileParser csvFileReader, IReconcileEngine reconcileEngine)
23 | {
24 | _markOffFileProvider = markoffFileProvider;
25 | _csvFileReader = csvFileReader;
26 | _reconcileEngine = reconcileEngine;
27 | }
28 | // GET: Transaction
29 | public ActionResult Compare()
30 | {
31 | ViewBag.Title = "Compare Files";
32 | return View();
33 | }
34 | [HttpPost]
35 | public ActionResult Compare(CompareModel compareFiles)
36 | {
37 | try
38 | {
39 | if (ModelState.IsValid)
40 | {
41 | string clientFileName = null, bankFileName = null, sessionId;
42 | if (compareFiles.ClientMarkOffFile.ContentLength > 0 && compareFiles.BankMarkOfffile.ContentLength > 0)
43 | {
44 | clientFileName = Path.GetFileName(compareFiles.ClientMarkOffFile.FileName);
45 | sessionId = SessionIdGenerator.CreateNewId();
46 |
47 |
48 | ParserResult result = _csvFileReader.Validate(compareFiles.ClientMarkOffFile.InputStream);
49 | if (!result.IsValid)
50 | {
51 | ModelState.AddModelError("ClientMarkOffFile", result.Errors[1]);
52 | return View();
53 | }
54 | else
55 | {
56 | _markOffFileProvider.SaveMarkOffFile(compareFiles.ClientMarkOffFile.InputStream, sessionId, clientFileName);
57 | }
58 |
59 | result = _csvFileReader.Validate(compareFiles.BankMarkOfffile.InputStream);
60 | if (!result.IsValid)
61 | {
62 | ModelState.AddModelError("BankMarkOfffile", result.Errors[1]);
63 | return View();
64 | }
65 | else
66 | {
67 | bankFileName = Path.GetFileName(compareFiles.BankMarkOfffile.FileName);
68 | _markOffFileProvider.SaveMarkOffFile(compareFiles.BankMarkOfffile.InputStream, sessionId, bankFileName);
69 | }
70 | return RedirectToAction("compareresult", new { sid = sessionId, cfn = clientFileName, tfn = bankFileName });
71 | }
72 | }
73 |
74 | return View();
75 | }
76 | catch (Exception)
77 | {
78 |
79 | throw;
80 | }
81 | }
82 |
83 | [HttpGet]
84 | [Route("transaction/{sessionid}/compareresult",Name ="compareresult")]
85 | public ActionResult CompareResult(string sid,string cfn,string tfn)
86 | {
87 | var clientTransactions = _csvFileReader.GetRecords(_markOffFileProvider.GetMarkOffFile(sid, cfn));
88 | var bankTransactions = _csvFileReader.GetRecords(_markOffFileProvider.GetMarkOffFile(sid, tfn));
89 | IReconcileResult result = _reconcileEngine.Reconcile(clientTransactions, bankTransactions);
90 | CompareResult model = new CompareResult(cfn, tfn,result);
91 | ViewBag.Title = "Compare Files Result";
92 | return View("CompareResult", model);
93 | }
94 |
95 | [HttpGet]
96 | [Route("transaction/unmatchedreport", Name = "unmatchedreport")]
97 | public ActionResult UnmatchedReport(string sid, string cfn, string tfn)
98 | {
99 | var clientTransactions = _csvFileReader.GetRecords(_markOffFileProvider.GetMarkOffFile(sid, cfn));
100 | var bankTransactions = _csvFileReader.GetRecords(_markOffFileProvider.GetMarkOffFile(sid, tfn));
101 | IReconcileResult result = _reconcileEngine.Reconcile(clientTransactions, bankTransactions);
102 | CompareResult model = new CompareResult(cfn, tfn, result);
103 | ViewBag.Title = "Compare Files Result";
104 | return View("UnmatchedReport", model);
105 | }
106 | }
107 | }
--------------------------------------------------------------------------------
/src/FinConcile.Web/docs/js/featureSearch.js:
--------------------------------------------------------------------------------
1 |
2 | // feature navigation namespace
3 | (function(window) {
4 |
5 | window.FeatureNavigation = {
6 | getCurrent : getCurrentFeature,
7 | setCurrent: setCurrentFeature,
8 | getCurrentLink: getCurrentFeatureLink
9 | };
10 |
11 | function getCurrentFeature() {
12 | var query = window.location.search;
13 |
14 | if (query == null || query == '') {
15 | // support previous style relative path in hash ...
16 | var hash = window.location.hash;
17 | if (hash != null && hash != '') {
18 | // clear hash from url
19 | window.location.hash = '';
20 | // trim leading hash before returning
21 | return removeBeginningHash(hash);
22 | }
23 |
24 | return '';
25 | }
26 |
27 | // trim leading question mark
28 | query = query.substring(1);
29 |
30 | var queryVars = query.split('&');
31 |
32 | // find feature query param
33 | for (var i=0;i
-1;
99 | }
100 |
101 | function matchesFeatureTag(searchString, feature) {
102 | var foundMatch = false;
103 | var tags = searchString.split(" ");
104 | $.each(tags, function (index, tag) {
105 | tag = tag.toLowerCase();
106 | if (tag.indexOf("@") > -1) {
107 | $.each(feature.Feature.Tags, function (key, scenarioTag) {
108 | var lowerCasedTag = scenarioTag.toLowerCase();
109 | if (lowerCasedTag.indexOf(tag) > -1) {
110 | foundMatch = true;
111 | }
112 | });
113 | }
114 | });
115 |
116 | return foundMatch;
117 | }
118 |
119 | function matchesScenarioName(searchString, feature) {
120 | for (var i = 0; i < feature.Feature.FeatureElements.length; i++) {
121 | var scenarioName = feature.Feature.FeatureElements[i].Name.toLowerCase();
122 | if (scenarioName.indexOf(searchString) > -1) {
123 | return true;
124 | }
125 | }
126 | return false;
127 | }
128 |
129 | function matchesScenarioTag(searchString, feature) {
130 | var foundMatch = false;
131 | var tags = searchString.split(" ");
132 | $.each(tags, function (index, tag) {
133 | tag = tag.toLowerCase();
134 | if (tag.indexOf("@") > -1) {
135 | for (var i = 0; i < feature.Feature.FeatureElements.length; i++) {
136 | $.each(feature.Feature.FeatureElements[i].Tags, function (key, scenarioTag) {
137 | var lowerCasedTag = scenarioTag.toLowerCase();
138 | if (lowerCasedTag.indexOf(tag) > -1) {
139 | foundMatch = true;
140 | }
141 | });
142 | if (foundMatch) { break; }
143 | }
144 | }
145 | });
146 | return foundMatch;
147 | }
148 |
--------------------------------------------------------------------------------
/src/FinConcile.Web/docs/TransactionCompare.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Compare Transactions
6 |
7 |
8 |
9 |
10 |
15 |
16 |
17 |
18 |
19 |
20 |
43 |
44 |
Compare Transactions
45 |
46 |
In order to reconcile transactions
47 | As a user
48 | I want to compare client and bank markoff files
49 |
50 |
51 | -
52 |
53 |
Tags: @tranascationcompare
54 |
Browse Compare Page
55 |
56 |
57 |
58 | -
59 | When the user goes to compare user screen
60 | -
61 | Then the compare user view should be displayed
62 |
63 |
64 |
65 |
66 |
67 |
68 | -
69 |
70 |
Tags: @transactioncompare
71 |
On Successful upload of Transaction Files,user should be shown Comparison Result Page
72 |
73 |
74 |
75 | -
76 | Given ClientMarkOffFile and BankMarkOffFile
77 | -
78 | When the user clicks on the Compare button
79 | -
80 | Then user should be redirected to compare result Page
81 |
82 |
83 |
84 |
85 |
86 |
87 | -
88 |
89 |
Tags: @transactioncompare
90 |
Details in Comparison Result Page
91 |
92 |
93 |
94 | -
95 | Given ClientMarkOffFile and BankMarkOffFile
96 | -
97 | When the user clicks on the Compare button
98 | -
99 | Then Comparison Result should contain Both Names of the Files 'ClientMarkoffFile20140113' and 'BankMarkoffFile20140113'
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
113 |
114 |
115 |
--------------------------------------------------------------------------------
/Tests/FinConcile.Tests/RulesEvaluator.feature:
--------------------------------------------------------------------------------
1 | Feature: RulesEvaluator
2 | In order to reconcile transactions
3 | As a user
4 | I want to define rules
5 |
6 | @rules
7 | Scenario: Match Ids of two transactions
8 | Given two transacions with same Ids
9 | And A rule to match Ids
10 | When I call Evaluate
11 | Then the result should be matched ReconciledItem
12 |
13 | @rules
14 | Scenario: Match Dates of two transactions which are within 2 minutes
15 | Given two transacions with same id '584011808649511' and dates '1/11/2014 10:27:00 PM' and '1/11/2014 10:27:44 PM' respectively
16 | And A rule to match Ids and Dates with a delta of 120 seconds
17 | When I call Evaluate
18 | Then the result should be matched ReconciledItem
19 |
20 | @rules
21 | Scenario: Match Ids and Amount
22 | Given two transacions with same '584011808649511' and amount -20000
23 | And A rule to match Ids and amount
24 | When I call Evaluate
25 | Then the result should be matched ReconciledItem
26 |
27 | @rules
28 | Scenario: Match Ids and But Not Amount
29 | Given two transacions with same Id '584011808649511' and different amount -20000 and -20002
30 | And A rule to match Ids and amount
31 | When I call Evaluate
32 | Then the result should be not matched ReconciledItem
33 |
34 | @rules
35 | Scenario Outline: Match all the fields of transactions
36 | Given ClientTransacion with '' '' '' '' '' '' ''
37 | And BankTransacion with '' '' '' '' '' '' ''
38 | And A ruleset to match all fields of Transaction
39 | When I call Evaluate
40 | Then the result should be matched ReconciledItem
41 |
42 | Examples:
43 | | c_id | t_id | c_profilename | t_profilename | c_date | t_date | c_amount | t_amount | c_narrative | t_narrative | c_description | t_description | c_walletreference | t_walletreference |
44 | | 584011808649511 | 584011808649511 | Card Campaign | Card Campaign | 1/11/2014 10:27:00 PM | 1/11/2014 10:27:00 PM | -20000 | -20000 | *MOLEPS ATM25 MOLEPOLOLE BW | *MOLEPS ATM25 MOLEPOLOLE BW | DEDUCT | DEDUCT | P_NzI2ODY2ODlfMTM4MjcwMTU2NS45MzA5 | P_NzI2ODY2ODlfMTM4MjcwMTU2NS45MzA5 |
45 | | 84012233581869 | 84012233581869 | Card Campaign | Card Campaign | 1/12/2014 6:26:00 AM | 1/12/2014 6:26:00 AM | -20000 | -20000 | Molepolole Filli100558 Gaborone BW | Molepolole Filli100558 Gaborone BW | DEDUCT | DEDUCT | P_NzI5OTE3NjZfMTM4MTkzNjk5Mi45NTc2 | P_NzI5OTE3NjZfMTM4MTkzNjk5Mi45NTc2 |
46 | | 0584012395072004 | 0584012395072004 | Card Campaign | Card Campaign | 2014-01-12 14:58:27 | 2014-01-12 14:58:27 | -10000 | -10000 | MAHALAPYE BRANCH BOTSWANA BW | MAHALAPYE BRANCH BOTSWANA BW | DEDUCT | DEDUCT | P_NzUzMDAzODVfMTM4NzI4MTQ5NC4zNzI2 | P_NzUzMDAzODVfMTM4NzI4MTQ5NC4zNzI2 |
47 | | 0164012401925347 | 0164012401925347 | Card Campaign | Card Campaign | 2014-01-12 15:09:52 | 2014-01-12 15:09:52 | 3880 | 3880 | 370592 ENGEN LOBATSE BOTSWANA BW | 370592 ENGEN LOBATSE BOTSWANA BW | REVERSAL | REVERSAL | P_NzUzNDA5MjRfMTM4MDg4NDc0OC4yNjA1 | P_NzUzNDA5MjRfMTM4MDg4NDc0OC4yNjA1 |
48 |
49 |
50 | @rules
51 | Scenario Outline: Match all the fields exactly and dates with a delta of 120 seconds
52 | Given ClientTransacion with '' '' '' '' '' '' ''
53 | And BankTransacion with '' '' '' '' '' '' ''
54 | And A ruleset to match all fields exactly and date field with a delta of 120 seconds
55 | When I call Evaluate
56 | Then the result should be matched ReconciledItem
57 |
58 | Examples:
59 | | c_id | t_id | c_profilename | t_profilename | c_date | t_date | c_amount | t_amount | c_narrative | t_narrative | c_description | t_description | c_walletreference | t_walletreference |
60 | | 584011808649511 | 584011808649511 | Card Campaign | Card Campaign | 1/11/2014 10:27:00 PM | 1/11/2014 10:27:44 PM | -20000 | -20000 | *MOLEPS ATM25 MOLEPOLOLE BW | *MOLEPS ATM25 MOLEPOLOLE BW | DEDUCT | DEDUCT | P_NzI2ODY2ODlfMTM4MjcwMTU2NS45MzA5 | P_NzI2ODY2ODlfMTM4MjcwMTU2NS45MzA5 |
61 | | 84012233581869 | 84012233581869 | Card Campaign | Card Campaign | 1/12/2014 6:26:00 AM | 1/12/2014 6:26:17 AM | -20000 | -20000 | Molepolole Filli100558 Gaborone BW | Molepolole Filli100558 Gaborone BW | DEDUCT | DEDUCT | P_NzI5OTE3NjZfMTM4MTkzNjk5Mi45NTc2 | P_NzI5OTE3NjZfMTM4MTkzNjk5Mi45NTc2 |
62 | | 0584012395072004 | 0584012395072004 | Card Campaign | Card Campaign | 2014-01-12 14:58:40 | 2014-01-12 14:58:27 | -10000 | -10000 | MAHALAPYE BRANCH BOTSWANA BW | MAHALAPYE BRANCH BOTSWANA BW | DEDUCT | DEDUCT | P_NzUzMDAzODVfMTM4NzI4MTQ5NC4zNzI2 | P_NzUzMDAzODVfMTM4NzI4MTQ5NC4zNzI2 |
63 | | 0164012401925347 | 0164012401925347 | Card Campaign | Card Campaign | 2014-01-12 15:09:00 | 2014-01-12 15:10:20 | 3880 | 3880 | 370592 ENGEN LOBATSE BOTSWANA BW | 370592 ENGEN LOBATSE BOTSWANA BW | REVERSAL | REVERSAL | P_NzUzNDA5MjRfMTM4MDg4NDc0OC4yNjA1 | P_NzUzNDA5MjRfMTM4MDg4NDc0OC4yNjA1 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/Tests/FinConcile.Tests/TransactionCompare.feature.cs:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | //
3 | // This code was generated by SpecFlow (http://www.specflow.org/).
4 | // SpecFlow Version:2.2.0.0
5 | // SpecFlow Generator Version:2.2.0.0
6 | //
7 | // Changes to this file may cause incorrect behavior and will be lost if
8 | // the code is regenerated.
9 | //
10 | // ------------------------------------------------------------------------------
11 | #region Designer generated code
12 | #pragma warning disable
13 | namespace FinConcile.Tests
14 | {
15 | using TechTalk.SpecFlow;
16 |
17 |
18 | [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "2.2.0.0")]
19 | [System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
20 | [NUnit.Framework.TestFixtureAttribute()]
21 | [NUnit.Framework.DescriptionAttribute("Compare Transactions")]
22 | public partial class CompareTransactionsFeature
23 | {
24 |
25 | private TechTalk.SpecFlow.ITestRunner testRunner;
26 |
27 | #line 1 "TransactionCompare.feature"
28 | #line hidden
29 |
30 | [NUnit.Framework.OneTimeSetUpAttribute()]
31 | public virtual void FeatureSetup()
32 | {
33 | testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner();
34 | TechTalk.SpecFlow.FeatureInfo featureInfo = new TechTalk.SpecFlow.FeatureInfo(new System.Globalization.CultureInfo("en-US"), "Compare Transactions", "\tIn order to reconcile transactions\r\n\tAs a user\r\n\tI want to compare client and ba" +
35 | "nk markoff files", ProgrammingLanguage.CSharp, ((string[])(null)));
36 | testRunner.OnFeatureStart(featureInfo);
37 | }
38 |
39 | [NUnit.Framework.OneTimeTearDownAttribute()]
40 | public virtual void FeatureTearDown()
41 | {
42 | testRunner.OnFeatureEnd();
43 | testRunner = null;
44 | }
45 |
46 | [NUnit.Framework.SetUpAttribute()]
47 | public virtual void TestInitialize()
48 | {
49 | }
50 |
51 | [NUnit.Framework.TearDownAttribute()]
52 | public virtual void ScenarioTearDown()
53 | {
54 | testRunner.OnScenarioEnd();
55 | }
56 |
57 | public virtual void ScenarioSetup(TechTalk.SpecFlow.ScenarioInfo scenarioInfo)
58 | {
59 | testRunner.OnScenarioStart(scenarioInfo);
60 | }
61 |
62 | public virtual void ScenarioCleanup()
63 | {
64 | testRunner.CollectScenarioErrors();
65 | }
66 |
67 | [NUnit.Framework.TestAttribute()]
68 | [NUnit.Framework.DescriptionAttribute("Browse Compare Page")]
69 | [NUnit.Framework.CategoryAttribute("tranascationcompare")]
70 | public virtual void BrowseComparePage()
71 | {
72 | TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Browse Compare Page", new string[] {
73 | "tranascationcompare"});
74 | #line 7
75 | this.ScenarioSetup(scenarioInfo);
76 | #line 8
77 | testRunner.When("the user goes to compare user screen", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When ");
78 | #line 9
79 | testRunner.Then("the compare user view should be displayed", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then ");
80 | #line hidden
81 | this.ScenarioCleanup();
82 | }
83 |
84 | [NUnit.Framework.TestAttribute()]
85 | [NUnit.Framework.DescriptionAttribute("On Successful upload of Transaction Files,user should be shown Comparison Result " +
86 | "Page")]
87 | [NUnit.Framework.CategoryAttribute("transactioncompare")]
88 | public virtual void OnSuccessfulUploadOfTransactionFilesUserShouldBeShownComparisonResultPage()
89 | {
90 | TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("On Successful upload of Transaction Files,user should be shown Comparison Result " +
91 | "Page", new string[] {
92 | "transactioncompare"});
93 | #line 12
94 | this.ScenarioSetup(scenarioInfo);
95 | #line 13
96 | testRunner.Given("ClientMarkOffFile and BankMarkOffFile", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given ");
97 | #line 14
98 | testRunner.When("the user clicks on the Compare button", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When ");
99 | #line 15
100 | testRunner.Then("user should be redirected to compare result Page", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then ");
101 | #line hidden
102 | this.ScenarioCleanup();
103 | }
104 |
105 | [NUnit.Framework.TestAttribute()]
106 | [NUnit.Framework.DescriptionAttribute("Details in Comparison Result Page")]
107 | [NUnit.Framework.CategoryAttribute("transactioncompare")]
108 | public virtual void DetailsInComparisonResultPage()
109 | {
110 | TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Details in Comparison Result Page", new string[] {
111 | "transactioncompare"});
112 | #line 18
113 | this.ScenarioSetup(scenarioInfo);
114 | #line 19
115 | testRunner.Given("ClientMarkOffFile and BankMarkOffFile", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given ");
116 | #line 20
117 | testRunner.When("the user clicks on the Compare button", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When ");
118 | #line 21
119 | testRunner.Then("Comparison Result should contain Both Names of the Files \'ClientMarkoffFile201401" +
120 | "13\' and \'BankMarkoffFile20140113\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then ");
121 | #line hidden
122 | this.ScenarioCleanup();
123 | }
124 | }
125 | }
126 | #pragma warning restore
127 | #endregion
128 |
--------------------------------------------------------------------------------
/src/FinConcile.Web/ApplicationInsights.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
12 | search|spider|crawl|Bot|Monitor|AlwaysOn
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
54 | System.Web.Handlers.TransferRequestHandler
55 | Microsoft.VisualStudio.Web.PageInspector.Runtime.Tracing.RequestDataHttpHandler
56 | System.Web.StaticFileHandler
57 | System.Web.Handlers.AssemblyResourceLoader
58 | System.Web.Optimization.BundleHandler
59 | System.Web.Script.Services.ScriptHandlerFactory
60 | System.Web.Handlers.TraceHandler
61 | System.Web.Services.Discovery.DiscoveryRequestHandler
62 | System.Web.HttpDebugHandler
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | 5
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/src/FinConcile.Web/docs/js/picklesOverview.js:
--------------------------------------------------------------------------------
1 | var PicklesOverview = function(summary) {
2 |
3 | Chart.defaults.global.responsive = true;
4 |
5 | return {
6 | getTotalsData: getTotalsData,
7 | createOverallTotalsChart: createOverallTotalsChart,
8 | createByTagChart: createByTagChart,
9 | createByRootFolderChart: createByRootFolderChart,
10 | createNotTestedByRootFolderChart: createNotTestedByRootFolderChart
11 | };
12 |
13 | /////
14 |
15 | function defaultColors() {
16 | return {
17 | passing: {
18 | color: "rgba(70,191,189,1)",
19 | stroke: "rgba(70,191,189,1)",
20 | highlight: "rgba(90,211,209,1)",
21 | highlightstroke: "rgba(90,211,209,1)"
22 | },
23 | failing: {
24 | color: "rgba(247,70,74,1)",
25 | stroke: "rgba(247,70,74,1)",
26 | highlight: "rgba(255,90,94,1)",
27 | highlightstroke: "rgba(255,90,94,1)"
28 | },
29 | inconclusive: {
30 | color: "rgba(253,180,92,1)",
31 | stroke: "rgba(253,180,92,1)",
32 | highlight: "rgba(255,200,112,1)",
33 | highlightstroke: "rgba(255,200,112,1)"
34 | }
35 | };
36 | }
37 |
38 | function getTotalsData() {
39 | var colors = defaultColors();
40 |
41 | return [
42 | {
43 | value: summary.Scenarios.Inconclusive,
44 | color: colors.inconclusive.color,
45 | highlight: colors.inconclusive.highlight,
46 | label: "Inconclusive Scenarios"
47 | },
48 | {
49 | value: summary.Scenarios.Failing,
50 | color: colors.failing.color,
51 | highlight: colors.failing.highlight,
52 | label: "Failing Scenarios"
53 | },
54 | {
55 | value: summary.Scenarios.Passing,
56 | color: colors.passing.color,
57 | highlight: colors.passing.highlight,
58 | label: "Passing Scenarios"
59 | }
60 | ];
61 |
62 | }
63 |
64 | function createOverallTotalsChart(context) {
65 | var chart = new Chart(context);
66 | var data = getTotalsData();
67 |
68 | var options = {
69 | percentageInnerCutout: 40
70 | };
71 |
72 | chart.Doughnut(data, options);
73 | }
74 |
75 | function getChartData(labels, passingData, failingData, inconclusiveData) {
76 | var colors = defaultColors();
77 |
78 | return {
79 | labels: labels,
80 | datasets: [
81 | {
82 | fillColor: colors.inconclusive.color,
83 | strokeColor: colors.inconclusive.stroke,
84 | highlightFill: colors.inconclusive.highlight,
85 | highlightStroke: colors.inconclusive.highlightstroke,
86 | label: "Inconclusive Scenarios",
87 | data: inconclusiveData
88 | },
89 | {
90 | fillColor: colors.failing.color,
91 | strokeColor: colors.failing.stroke,
92 | highlightFill: colors.failing.highlight,
93 | highlightStroke: colors.failing.highlightstroke,
94 | label: "Failing Scenarios",
95 | data: failingData
96 | },
97 | {
98 | fillColor: colors.passing.color,
99 | strokeColor: colors.passing.stroke,
100 | highlightFill: colors.passing.highlight,
101 | highlightStroke: colors.passing.highlightstroke,
102 | label: "Passing Scenarios",
103 | data: passingData
104 | }
105 | ]
106 | };
107 |
108 | }
109 |
110 | function createByTagChart(context) {
111 | var chart = new Chart(context);
112 |
113 | var labels = [];
114 | var passingData = [];
115 | var failingData = [];
116 | var inconclusiveData = [];
117 |
118 | summary.Tags.sort(function(a, b) {
119 | return b.Total - a.Total;
120 | });
121 |
122 | summary.Tags.slice(0, 20).forEach(function(tag) {
123 | labels.push(tag.Tag);
124 | passingData.push(tag.Passing);
125 | failingData.push(tag.Failing);
126 | inconclusiveData.push(tag.Inconclusive);
127 | });
128 |
129 | var data = getChartData(labels, passingData, failingData, inconclusiveData);
130 | var options = { };
131 |
132 | chart.StackedBar(data, options);
133 | }
134 |
135 | function internalCreateByRootFolderChart(context, folderCollection) {
136 | var chart = new Chart(context);
137 |
138 | var labels = [];
139 | var passingData = [];
140 | var failingData = [];
141 | var inconclusiveData = [];
142 |
143 | folderCollection.sort(function(a, b) {
144 | return a.Folder.localeCompare(b.Folder);
145 | });
146 |
147 | folderCollection.forEach(function(folder) {
148 | labels.push(folder.Folder);
149 | passingData.push(folder.Passing);
150 | failingData.push(folder.Failing);
151 | inconclusiveData.push(folder.Inconclusive);
152 | });
153 |
154 | var data = getChartData(labels, passingData, failingData, inconclusiveData);
155 | var options = { };
156 |
157 | chart.StackedBar(data, options);
158 | }
159 |
160 | function createByRootFolderChart(context) {
161 | internalCreateByRootFolderChart(context, summary.Folders);
162 | }
163 |
164 | function createNotTestedByRootFolderChart(context) {
165 | internalCreateByRootFolderChart(context, summary.NotTestedFolders);
166 | }
167 | };
168 |
--------------------------------------------------------------------------------
/Tests/FinConcile.Tests/TransactionCompareSteps.cs:
--------------------------------------------------------------------------------
1 | using FinConcile.Tests.Properties;
2 | using FinConcile.Tests.TestUtils;
3 | using FinReconcile.Controllers;
4 | using FinReconcile.Core.Engines;
5 | using FinReconcile.Infra;
6 | using FinReconcile.Infra.Parsers;
7 | using FinReconcile.Infra.Providers;
8 | using FinReconcile.Models;
9 | using Moq;
10 | using NUnit.Framework;
11 | using System;
12 | using System.IO;
13 | using System.Reflection;
14 | using System.Web;
15 | using System.Web.Mvc;
16 | using TechTalk.SpecFlow;
17 |
18 | namespace FinConcile.Tests
19 | {
20 | [Binding]
21 | public class CompareTransactionsSteps
22 | {
23 | ActionResult _result;
24 | TransactionController _controller;
25 | HttpPostedFileBase _clientMarkOffFile, _bankMarkOffFile;
26 |
27 | Mock _mockMarkOffFileProvider;
28 | IMarkOffFileParser _csvReader;
29 | Mock _mockReconcileEngine;
30 | const string ClientMarkoffFileName = "ClientMarkoffFile20140113";
31 | const string BankMarkoffFileName = "BankMarkoffFile20140113";
32 | string _sessionId;
33 |
34 | [BeforeScenario]
35 | public void InitScenario()
36 | {
37 | _mockMarkOffFileProvider = new Mock();
38 | _mockReconcileEngine = new Mock();
39 | _csvReader = new CSVMarkOffFileParser();
40 | _sessionId = SessionIdGenerator.CreateNewId();
41 | _mockMarkOffFileProvider.Setup(x => x.SaveMarkOffFile(It.IsAny(),_sessionId, ClientMarkoffFileName)).Returns(ClientMarkoffFileName);
42 | _mockMarkOffFileProvider.Setup(x => x.SaveMarkOffFile(It.IsAny(),_sessionId, BankMarkoffFileName)).Returns(BankMarkoffFileName);
43 |
44 | _mockMarkOffFileProvider.Setup(x=>x.GetMarkOffFile(_sessionId,ClientMarkoffFileName)).Returns(Resources.ClientMarkoffFile20140113);
45 | _mockMarkOffFileProvider.Setup(x => x.GetMarkOffFile(_sessionId, BankMarkoffFileName)).Returns(Resources.BankMarkoffFile20140113);
46 | }
47 |
48 | [When(@"the user goes to compare user screen")]
49 | public void WhenTheUserGoesToCompareUserScreen()
50 | {
51 | _controller = new TransactionController(_mockMarkOffFileProvider.Object,_csvReader, _mockReconcileEngine.Object);
52 | _result = _controller.Compare();
53 | }
54 |
55 | [Then(@"the compare user view should be displayed")]
56 | public void ThenTheCompareUserViewShouldBeDisplayed()
57 | {
58 | Assert.IsInstanceOf(_result);
59 | Assert.AreEqual("Compare Files",
60 | _controller.ViewData["Title"],
61 | "Compare Files");
62 | }
63 |
64 | [Given(@"ClientMarkOffFile and BankMarkOffFile")]
65 | public void GivenClientMarkOffFileAndBankMarkOffFile()
66 | {
67 | var assembly = Assembly.GetExecutingAssembly();
68 | //read the file from assembly resource and convert to http posted file
69 | _clientMarkOffFile = Utilities.GetMockHttpPostedFile(Resources.ClientMarkoffFile20140113, ClientMarkoffFileName);
70 | _bankMarkOffFile = Utilities.GetMockHttpPostedFile(Resources.BankMarkoffFile20140113, BankMarkoffFileName);
71 | }
72 |
73 |
74 | [When(@"the user clicks on the Compare button")]
75 | public void WhenTheUserClicksOnTheCompareButton()
76 | {
77 | _controller = new TransactionController(_mockMarkOffFileProvider.Object,_csvReader,_mockReconcileEngine.Object);
78 | _result= _controller.Compare( new CompareModel() { ClientMarkOffFile = _clientMarkOffFile, BankMarkOfffile = _bankMarkOffFile });
79 | }
80 |
81 | [Then(@"user should be redirected to compare result Page")]
82 | public void ThenUserShouldBeRedirectedToTheCompareResultPage()
83 | {
84 | Assert.IsInstanceOf(_result);
85 | Assert.IsNotEmpty(((RedirectToRouteResult)_result).RouteValues["sid"].ToString());
86 | Assert.AreEqual(ClientMarkoffFileName, ((RedirectToRouteResult)_result).RouteValues["cfn"].ToString());
87 | Assert.AreEqual(BankMarkoffFileName, ((RedirectToRouteResult)_result).RouteValues["tfn"].ToString());
88 |
89 | }
90 |
91 | [Then(@"Comparison Result should contain Both Names of the Files '(.*)' and '(.*)'")]
92 | public void ThenComparisonResultShouldContainBothNamesOfTheFilesAnd(string clientFileName, string bankFileName)
93 | {
94 | Assert.IsInstanceOf(_result);
95 | RedirectToRouteResult redirectResult = (RedirectToRouteResult)_result;
96 | Assert.IsNotEmpty(redirectResult.RouteValues["sid"].ToString());
97 | Assert.AreEqual("compareresult", redirectResult.RouteValues["action"]);
98 | Assert.AreEqual(ClientMarkoffFileName, redirectResult.RouteValues["cfn"].ToString());
99 | Assert.AreEqual(BankMarkoffFileName, redirectResult.RouteValues["tfn"].ToString());
100 | }
101 |
102 | [Then(@"TotalRecords (.*)")]
103 | public void ThenTotalRecords(int totalRecords)
104 | {
105 | var model = (CompareResult)((ViewResult)_result).Model;
106 | Assert.AreEqual(totalRecords, model.TotalClientRecords+model.TotalBankRecords);
107 | }
108 |
109 | [Then(@"MatchingRecords (.*)")]
110 | public void ThenMatchingRecords(int matchingRecords)
111 | {
112 | var model = (CompareResult)((ViewResult)_result).Model;
113 | Assert.AreEqual(matchingRecords, model.MatchingClientRecords+model.MatchingBankRecords);
114 | }
115 |
116 | [Then(@"UnmatchedRecords (.*)")]
117 | public void ThenUnmatchedRecords(int unMatchedRecords)
118 | {
119 | var model = (CompareResult)((ViewResult)_result).Model;
120 | Assert.AreEqual(unMatchedRecords, model.UnmatchedClientRecords+model.UnmatchedTutkaRecords);
121 | }
122 |
123 |
124 |
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # MSTest test Results
33 | [Tt]est[Rr]esult*/
34 | [Bb]uild[Ll]og.*
35 |
36 | # NUNIT
37 | *.VisualState.xml
38 | TestResult.xml
39 |
40 | # Build Results of an ATL Project
41 | [Dd]ebugPS/
42 | [Rr]eleasePS/
43 | dlldata.c
44 |
45 | # .NET Core
46 | project.lock.json
47 | project.fragment.lock.json
48 | artifacts/
49 | **/Properties/launchSettings.json
50 |
51 | *_i.c
52 | *_p.c
53 | *_i.h
54 | *.ilk
55 | *.meta
56 | *.obj
57 | *.pch
58 | *.pdb
59 | *.pgc
60 | *.pgd
61 | *.rsp
62 | *.sbr
63 | *.tlb
64 | *.tli
65 | *.tlh
66 | *.tmp
67 | *.tmp_proj
68 | *.log
69 | *.vspscc
70 | *.vssscc
71 | .builds
72 | *.pidb
73 | *.svclog
74 | *.scc
75 |
76 | # Chutzpah Test files
77 | _Chutzpah*
78 |
79 | # Visual C++ cache files
80 | ipch/
81 | *.aps
82 | *.ncb
83 | *.opendb
84 | *.opensdf
85 | *.sdf
86 | *.cachefile
87 | *.VC.db
88 | *.VC.VC.opendb
89 |
90 | # Visual Studio profiler
91 | *.psess
92 | *.vsp
93 | *.vspx
94 | *.sap
95 |
96 | # TFS 2012 Local Workspace
97 | $tf/
98 |
99 | # Guidance Automation Toolkit
100 | *.gpState
101 |
102 | # ReSharper is a .NET coding add-in
103 | _ReSharper*/
104 | *.[Rr]e[Ss]harper
105 | *.DotSettings.user
106 |
107 | # JustCode is a .NET coding add-in
108 | .JustCode
109 |
110 | # TeamCity is a build add-in
111 | _TeamCity*
112 |
113 | # DotCover is a Code Coverage Tool
114 | *.dotCover
115 |
116 | # Visual Studio code coverage results
117 | *.coverage
118 | *.coveragexml
119 |
120 | # NCrunch
121 | _NCrunch_*
122 | .*crunch*.local.xml
123 | nCrunchTemp_*
124 |
125 | # MightyMoose
126 | *.mm.*
127 | AutoTest.Net/
128 |
129 | # Web workbench (sass)
130 | .sass-cache/
131 |
132 | # Installshield output folder
133 | [Ee]xpress/
134 |
135 | # DocProject is a documentation generator add-in
136 | DocProject/buildhelp/
137 | DocProject/Help/*.HxT
138 | DocProject/Help/*.HxC
139 | DocProject/Help/*.hhc
140 | DocProject/Help/*.hhk
141 | DocProject/Help/*.hhp
142 | DocProject/Help/Html2
143 | DocProject/Help/html
144 |
145 | # Click-Once directory
146 | publish/
147 |
148 | # Publish Web Output
149 | *.[Pp]ublish.xml
150 | *.azurePubxml
151 | # TODO: Comment the next line if you want to checkin your web deploy settings
152 | # but database connection strings (with potential passwords) will be unencrypted
153 | *.pubxml
154 | *.publishproj
155 |
156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
157 | # checkin your Azure Web App publish settings, but sensitive information contained
158 | # in these scripts will be unencrypted
159 | PublishScripts/
160 |
161 | # NuGet Packages
162 | *.nupkg
163 | # The packages folder can be ignored because of Package Restore
164 | **/packages/*
165 | # except build/, which is used as an MSBuild target.
166 | !**/packages/build/
167 | # Uncomment if necessary however generally it will be regenerated when needed
168 | #!**/packages/repositories.config
169 | # NuGet v3's project.json files produces more ignorable files
170 | *.nuget.props
171 | *.nuget.targets
172 |
173 | # Microsoft Azure Build Output
174 | csx/
175 | *.build.csdef
176 |
177 | # Microsoft Azure Emulator
178 | ecf/
179 | rcf/
180 |
181 | # Windows Store app package directories and files
182 | AppPackages/
183 | BundleArtifacts/
184 | Package.StoreAssociation.xml
185 | _pkginfo.txt
186 |
187 | # Visual Studio cache files
188 | # files ending in .cache can be ignored
189 | *.[Cc]ache
190 | # but keep track of directories ending in .cache
191 | !*.[Cc]ache/
192 |
193 | # Others
194 | ClientBin/
195 | ~$*
196 | *~
197 | *.dbmdl
198 | *.dbproj.schemaview
199 | *.jfm
200 | *.pfx
201 | *.publishsettings
202 | orleans.codegen.cs
203 |
204 | # Since there are multiple workflows, uncomment next line to ignore bower_components
205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
206 | #bower_components/
207 |
208 | # RIA/Silverlight projects
209 | Generated_Code/
210 |
211 | # Backup & report files from converting an old project file
212 | # to a newer Visual Studio version. Backup files are not needed,
213 | # because we have git ;-)
214 | _UpgradeReport_Files/
215 | Backup*/
216 | UpgradeLog*.XML
217 | UpgradeLog*.htm
218 |
219 | # SQL Server files
220 | *.mdf
221 | *.ldf
222 | *.ndf
223 |
224 | # Business Intelligence projects
225 | *.rdl.data
226 | *.bim.layout
227 | *.bim_*.settings
228 |
229 | # Microsoft Fakes
230 | FakesAssemblies/
231 |
232 | # GhostDoc plugin setting file
233 | *.GhostDoc.xml
234 |
235 | # Node.js Tools for Visual Studio
236 | .ntvs_analysis.dat
237 | node_modules/
238 |
239 | # Typescript v1 declaration files
240 | typings/
241 |
242 | # Visual Studio 6 build log
243 | *.plg
244 |
245 | # Visual Studio 6 workspace options file
246 | *.opt
247 |
248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
249 | *.vbw
250 |
251 | # Visual Studio LightSwitch build output
252 | **/*.HTMLClient/GeneratedArtifacts
253 | **/*.DesktopClient/GeneratedArtifacts
254 | **/*.DesktopClient/ModelManifest.xml
255 | **/*.Server/GeneratedArtifacts
256 | **/*.Server/ModelManifest.xml
257 | _Pvt_Extensions
258 |
259 | # Paket dependency manager
260 | .paket/paket.exe
261 | paket-files/
262 |
263 | # FAKE - F# Make
264 | .fake/
265 |
266 | # JetBrains Rider
267 | .idea/
268 | *.sln.iml
269 |
270 | # CodeRush
271 | .cr/
272 |
273 | # Python Tools for Visual Studio (PTVS)
274 | __pycache__/
275 | *.pyc
276 |
277 | # Cake - Uncomment if you are using it
278 | # tools/**
279 | # !tools/packages.config
280 |
281 | # Telerik's JustMock configuration file
282 | *.jmconfig
283 |
284 | # BizTalk build output
285 | *.btp.cs
286 | *.btm.cs
287 | *.odx.cs
288 | *.xsd.cs
289 | /src/Uploads/
--------------------------------------------------------------------------------
/src/FinConcile.Web/docs/css/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding-top: 60px;
3 | padding-bottom: 40px;
4 | }
5 |
6 | .sidebar-nav {
7 | padding: 9px 0;
8 | }
9 |
10 | #featureDetails .description {
11 | font-size: larger;
12 | }
13 |
14 | .feature-folder-header {
15 | font-size: small;
16 | font-weight: bolder;
17 | margin-top: 5px;
18 | }
19 |
20 | .feature-folder-header span {
21 | cursor: pointer;
22 | }
23 |
24 | .feature-folder-contents {
25 | margin-left: 15px;
26 | }
27 |
28 | .navbar .overview {
29 | padding-top: 8px;
30 | font-weight: bolder;
31 | }
32 |
33 | .navbar-below {
34 | margin-top: -1px;
35 | margin-left: 10px;
36 | border-top: 0;
37 | -ms-border-bottom-left-radius: 10px;
38 | border-bottom-left-radius: 10px;
39 | -ms-border-bottom-right-radius: 10px;
40 | border-bottom-right-radius: 10px;
41 | width: 180px;
42 | background-color: black;
43 | color: rgb(173, 173, 173);
44 | -webkit-transition: height 400ms;
45 | transition: height 400ms;
46 | overflow: hidden;
47 | }
48 |
49 | .navbar-below div {
50 | display: inline-block;
51 | padding-left: 8px;
52 | padding-right: 8px;
53 | cursor: pointer;
54 | }
55 |
56 | #scenarios, .steps {
57 | list-style-type: none;
58 | margin: 0;
59 | padding: 0;
60 | }
61 |
62 | .scenario {
63 | transition: border-color 1s ease;
64 | background-color: #F5F5F5;
65 | border: 1px solid #D0D0D0;
66 | margin: 6px 0px 28px 0px;
67 | padding: 6px;
68 | }
69 |
70 | .scenario-name {
71 | font-weight: bolder;
72 | font-size: 1.1em;
73 | }
74 |
75 | .scenario-active {
76 | border-color: #B21515;
77 | }
78 |
79 | #clipboardText {
80 | width: 100%;
81 | padding: 0;
82 | border: none;
83 | outline: none;
84 | box-shadow: none;
85 | background: transparent;
86 | cursor: pointer;
87 | text-decoration: underline;
88 | color: blue;
89 | }
90 |
91 | .scenario a.scenario-link {
92 | float: right;
93 | margin-top: -25px;
94 | width: 32px;
95 | }
96 |
97 | li.step {
98 | list-style-type: none;
99 | margin: 6px 0px 6px 0px;
100 | padding: 0;
101 | }
102 |
103 | .comment {
104 | font-weight: bold;
105 | color: #0088CC;
106 | }
107 |
108 | .keyword {
109 | font-weight: bold;
110 | color: #0000FF;
111 | }
112 |
113 | .tags {
114 | color: #44546a;
115 | font-style: italic;
116 | font-weight: normal;
117 | }
118 |
119 | .steps table {
120 | margin-bottom: 0px;
121 | }
122 |
123 | .steps table td {
124 | color: #B21515;
125 | }
126 |
127 | .steps table th {
128 | font-style: italic;
129 | }
130 |
131 | #SearchButton {
132 | margin-top: 0;
133 | }
134 |
135 | .nav .nav-tabs .nav-stacked .active {
136 | background-color: #0088CC;
137 | color: white;
138 | }
139 |
140 | #FolderNav {
141 | border-right: 1px lightgrey solid;
142 | }
143 |
144 | #FolderNav a:hover {
145 | background-color: #EEEEEE;
146 | color: #1A668C;
147 | text-decoration: none;
148 | }
149 |
150 | .feature {
151 | padding: 5px;
152 | }
153 |
154 | .feature:hover {
155 | background-color: #EEEEEE;
156 | cursor: pointer;
157 | color: #1A668C;
158 | }
159 |
160 | .canHighlight {
161 | }
162 |
163 | .highlight {
164 | background-color: yellow;
165 | }
166 |
167 | .inconclusive, .failed, .passed {
168 | font-size: 300%;
169 | }
170 |
171 | .pre {
172 | border-color: #fff;
173 | margin: 0;
174 | padding: 0;
175 | overflow: auto;
176 | white-space: pre-wrap;
177 | font-style: italic;
178 | }
179 |
180 | .selected {
181 | text-decoration: underline;
182 | }
183 |
184 | #Content {
185 | margin: 0px 0px 0px 10px;
186 | }
187 |
188 | #HideIt {
189 | float: right;
190 | position: relative;
191 | left: 0px;
192 | top: 0px;
193 | cursor: pointer;
194 | }
195 |
196 | #ShowIt {
197 | float: left;
198 | position: absolute;
199 | left: 0px;
200 | top: 60px;
201 | cursor: pointer;
202 | }
203 |
204 | #SutInfo {
205 | margin-top: 30px;
206 | border-top: 1px lightgrey solid;
207 | }
208 |
209 | .clickable {
210 | cursor: pointer;
211 | }
212 |
213 | .table {
214 | width: auto;
215 | max-width: 90%;
216 | }
217 |
218 | .table-condensed th,
219 | .table-condensed td {
220 | padding: 5px 8px;
221 | }
222 |
223 | /* Overview Styles */
224 | .modal-host {
225 | display: block;
226 | width: 100%;
227 | }
228 |
229 | .modal-overlay {
230 | margin-top: 40px;
231 | background: white;
232 | bottom: 0;
233 | left: 0;
234 | position: absolute;
235 | right: 0;
236 | top: 0;
237 | z-index: -1;
238 | opacity: 0;
239 | overflow: hidden;
240 | -ms-transform: scale(0.5);
241 | transform: scale(0.5);
242 | -webkit-transition: all 0.75s cubic-bezier(0.19, 1, 0.22, 1);
243 | transition: all 0.75s cubic-bezier(0.19, 1, 0.22, 1);
244 | overflow-y: auto;
245 | }
246 |
247 | .modal-overlay-visible {
248 | opacity: 1;
249 | -webkit-transform: scale(1);
250 | -ms-transform: scale(1);
251 | transform: scale(1);
252 | -webkit-transition: all 0.75s cubic-bezier(0.19, 1, 0.22, 1);
253 | transition: all 0.75s cubic-bezier(0.19, 1, 0.22, 1);
254 | z-index: 10;
255 | }
256 |
257 | .modal-box {
258 | padding: 1em .75em;
259 | position: relative;
260 | margin: 1em auto;
261 | width: 95%;
262 | }
263 |
264 | .modal-dialog .btn {
265 | background-image: none !important;
266 | }
267 |
268 | #overallCompleteCanvas-container, #overallCompleteSummary {
269 | width: 50%;
270 | }
271 |
272 | #overallCompleteSummary ul {
273 | font-size: 1.3em;
274 | list-style: none;
275 | }
276 |
277 | #overallCompleteSummary ul * {
278 | vertical-align: middle;
279 | }
280 |
281 | #overallCompleteSummary span {
282 | display: inline-block;
283 | margin: 5px;
284 | text-align: center;
285 | }
286 |
287 | #overallCompleteSummary .color-key {
288 | background-color: lightyellow;
289 | display: inline-block;
290 | padding: 0px 10px;
291 | min-width: 25px;
292 | line-height: 42px;
293 | }
294 |
295 | h3.header {
296 | text-align: center;
297 | margin: 20px 0px 5px 0px;
298 | line-height: 32px;
299 | }
300 |
301 |
302 |
--------------------------------------------------------------------------------
/src/FinReconcile.Infra/MarkOffReader/CSVMarkOffFileParser.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using CsvHelper;
3 | using System.IO;
4 | using System.Linq;
5 | using System;
6 | using FinReconcile.Core.Domain;
7 | using System.Text.RegularExpressions;
8 | using FinReconcile.Infra.MarkOffReader;
9 |
10 | namespace FinReconcile.Infra.Parsers
11 | {
12 | public class CSVMarkOffFileParser : IMarkOffFileParser
13 | {
14 |
15 | List headersToMatch = new List() { "ProfileName","TransactionDate","TransactionAmount"
16 | ,"TransactionNarrative","TransactionDescription","TransactionID","TransactionType","WalletReference" };
17 |
18 |
19 | public CSVMarkOffFileParser()
20 | {
21 |
22 | }
23 |
24 | private ParserResult ValidateAndInitHeaders(CsvReader reader)
25 | {
26 |
27 | ParserResult result;
28 | reader.Read();//read the header
29 | string headerLine = reader.Context.RawRecord;
30 |
31 | ParserContext context = new ParserContext();
32 |
33 | if (FindAndLoadAllHeaderIndexes(context,headerLine))
34 | {
35 | result = new ParserResult(true);
36 |
37 | result.Headers.AddRange(context.HeaderToIndexMap.Keys.ToList());
38 | result.HeaderIndexes = context.HeaderToIndexMap;
39 | return result;
40 | }
41 | else
42 | {
43 | result = new ParserResult(false);
44 | result.Errors.Add(1, context.HeaderError);
45 | }
46 |
47 | return result;
48 | }
49 | ///
50 | /// This function load the headers and it's indexes even if the headers are in different order
51 | ///
52 | ///
53 | ///
54 | private bool FindAndLoadAllHeaderIndexes(ParserContext context,string headerLine)
55 | {
56 | //create a map which stores the index of each header in a sorted dictionary in the order of index
57 | SortedDictionary _indexToHeaderMap = new SortedDictionary();
58 |
59 | string headers = string.Empty;
60 |
61 | bool isValid = true;
62 | int index;
63 | //add index along with whether the header is avaialble or not.
64 | foreach (var header in headersToMatch)
65 | {
66 | index = headerLine.IndexOf(header, StringComparison.InvariantCultureIgnoreCase);
67 | if (index<0)
68 | {
69 | isValid = false;
70 | headers += header;
71 | }
72 | else
73 | {
74 | _indexToHeaderMap.Add(index, header);
75 | }
76 |
77 | }
78 | if (isValid==false)
79 | {
80 | context.HeaderError=string.Format("Invalid file, {0} header(s) are not found",headers);
81 | return false;
82 | }
83 |
84 | index = 0;
85 | foreach (var value in _indexToHeaderMap.Values)
86 | {
87 | context.HeaderToIndexMap.Add(value, index);
88 | index++;
89 | }
90 |
91 | return true;
92 | }
93 |
94 | public ParserResult GetRecords(TextReader reader)
95 | {
96 | var csv = new CsvReader(reader);
97 | ParserResult result = ValidateAndInitHeaders(csv);
98 | int lineNo = 2;
99 | if (result.IsValid)
100 | {
101 | while (csv.Read())
102 | {
103 | try
104 | {
105 | Transaction trans = new Transaction();
106 | trans.ProfileName = csv.GetField(result.HeaderIndexes["ProfileName"]);
107 | trans.Id = csv.GetField(result.HeaderIndexes["TransactionID"]);
108 | trans.Amount = csv.GetField(result.HeaderIndexes["TransactionAmount"]);
109 | trans.Date = csv.GetField(result.HeaderIndexes["TransactionDate"]);
110 | trans.Description = csv.GetField(result.HeaderIndexes["TransactionDescription"]);
111 | trans.Narrative = csv.GetField(result.HeaderIndexes["TransactionNarrative"]);
112 | trans.Type = (TransactionType)Enum.Parse(typeof(TransactionType), csv.GetField(result.HeaderIndexes["TransactionType"]));
113 | trans.WalletReference = csv.GetField(result.HeaderIndexes["WalletReference"]);
114 | result.Add(trans);
115 |
116 | }
117 | catch (System.Exception ex)
118 | {
119 | result.InvalidEntries.Add(lineNo, csv.Context.RawRecord);
120 | result.Errors.Add(lineNo, string.Format("Line {0} :Error {1}", lineNo, ex.Message));
121 | }
122 | lineNo++;
123 | }
124 | }
125 |
126 | return result;
127 | }
128 |
129 | public ParserResult GetRecords(string markOffFileContent)
130 | {
131 | using (TextReader sr = new StringReader(markOffFileContent))
132 | {
133 | return GetRecords(sr);
134 | }
135 | }
136 |
137 | private ParserResult Validate(CsvReader reader)
138 | {
139 | return ValidateAndInitHeaders(reader);
140 | }
141 |
142 | public ParserResult Validate(Stream stream)
143 | {
144 | try
145 | {
146 | stream.Position = 0;
147 | var result = ValidateAndInitHeaders(new CsvReader(new StreamReader(stream)));
148 | stream.Position = 0;
149 | return result;
150 | }
151 | catch (Exception ex)
152 | {
153 | stream.Position = 0;
154 | stream.Close();
155 | throw;
156 | }
157 |
158 | }
159 |
160 | public ParserResult Validate(string markOffFileContent)
161 | {
162 | using (TextReader sr = new StringReader(markOffFileContent))
163 | {
164 | return Validate(new CsvReader(sr));
165 | }
166 | }
167 | }
168 | }
--------------------------------------------------------------------------------