├── src ├── FinConcile.Web │ ├── docs │ │ ├── css │ │ │ ├── print.css │ │ │ ├── fonts │ │ │ │ ├── FontAwesome.ttf │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ └── fontawesome-webfont.woff │ │ │ ├── master.css │ │ │ ├── reset.css │ │ │ ├── global.css │ │ │ └── styles.css │ │ ├── img │ │ │ ├── link.png │ │ │ ├── failure.png │ │ │ ├── success.png │ │ │ ├── inconclusive.png │ │ │ ├── glyphicons-halflings.png │ │ │ └── glyphicons-halflings-white.png │ │ ├── js │ │ │ ├── logger.js │ │ │ ├── jquery.highlight-4.closure.js │ │ │ ├── typeaheadList.js │ │ │ ├── scripts.js │ │ │ ├── stringFormatting.js │ │ │ ├── html5.js │ │ │ ├── heirarchyBuilder.js │ │ │ ├── featuresModel.js │ │ │ ├── featureSearch.js │ │ │ └── picklesOverview.js │ │ └── TransactionCompare.html │ ├── Views │ │ ├── _ViewStart.cshtml │ │ ├── Shared │ │ │ ├── Error.cshtml │ │ │ └── _Layout.cshtml │ │ ├── Transaction │ │ │ ├── CompareResult.cshtml │ │ │ ├── UnmatchedReport.cshtml │ │ │ ├── Compare.cshtml │ │ │ ├── CompareResultPartial.cshtml │ │ │ ├── UnmatchedReportPartial.cshtml │ │ │ └── UnmatchedDetailedReportPartial.cshtml │ │ ├── Rules │ │ │ └── Index.cshtml │ │ ├── Home │ │ │ └── Index.cshtml │ │ └── Web.config │ ├── favicon.ico │ ├── Global.asax │ ├── Content │ │ ├── Onion Architecture.png │ │ └── Site.css │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.ttf │ │ └── glyphicons-halflings-regular.woff │ ├── App_Start │ │ ├── FilterConfig.cs │ │ ├── RouteConfig.cs │ │ ├── WebApiConfig.cs │ │ ├── BundleConfig.cs │ │ ├── DIConfig.cs │ │ └── RulesConfig.cs │ ├── Controllers │ │ ├── HomeController.cs │ │ ├── RulesController.cs │ │ └── TransactionController.cs │ ├── Models │ │ ├── CompareModel.cs │ │ └── CompareResult.cs │ ├── Global.asax.cs │ ├── Web.Debug.config │ ├── Web.Release.config │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── packages.config │ ├── Web.config │ ├── Scripts │ │ └── respond.min.js │ ├── finreconcile.html │ └── ApplicationInsights.config ├── FinReconcile.Core │ ├── RuleEngine │ │ ├── RuleType.cs │ │ ├── IRule.cs │ │ ├── RuleSet.cs │ │ ├── Rules │ │ │ ├── DateRule.cs │ │ │ ├── MethodBaseRule.cs │ │ │ ├── PropertyRule.cs │ │ │ └── PropertyFuzzyMatchRule.cs │ │ └── RuleEngine.cs │ ├── Domain │ │ ├── ReconciledMatchType.cs │ │ ├── TransactionType.cs │ │ ├── Interfaces │ │ │ ├── IReconcileEngine.cs │ │ │ └── IReconcileResult.cs │ │ ├── Transaction.cs │ │ ├── TransactionSet.cs │ │ ├── ReconciledItem.cs │ │ └── ReconcileResult.cs │ ├── ReconcileEngine │ │ ├── IReconcileEngine.cs │ │ └── ReconcileEngine.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ └── FinReconcile.Core.csproj ├── FinReconcile.Infra │ ├── Providers │ │ ├── IMarkOffFileProvider.cs │ │ ├── SessionIdGenerator.cs │ │ └── MarkOffFileProvider.cs │ ├── MarkOffReader │ │ ├── IMarkOffFileParser.cs │ │ ├── ParserContext.cs │ │ ├── ParserResult.cs │ │ └── CSVMarkOffFileParser.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ └── FinReconcile.Infra.csproj └── FinReconcile.sln ├── libs └── CsvHelper.dll ├── README.md ├── Tests └── FinConcile.Tests │ ├── Steps │ └── TransactionTestData.cs │ ├── packages.config │ ├── TransactionCompare.feature │ ├── TestUtils │ ├── Utilities.cs │ └── SpecflowExtensions.cs │ ├── App.config │ ├── Properties │ ├── AssemblyInfo.cs │ └── Resources.Designer.cs │ ├── RulesSteps.cs │ ├── CSVParserSteps.cs │ ├── ReconcileEngineSteps.cs │ ├── RulesEvaluator.feature │ ├── TransactionCompare.feature.cs │ └── TransactionCompareSteps.cs ├── RuleEngineSample.txt ├── .gitattributes └── .gitignore /src/FinConcile.Web/docs/css/print.css: -------------------------------------------------------------------------------- 1 | #FolderNav, #TopNav { display: none !important; } 2 | -------------------------------------------------------------------------------- /libs/CsvHelper.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohithkrajan/FinReconcile/HEAD/libs/CsvHelper.dll -------------------------------------------------------------------------------- /src/FinConcile.Web/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "~/Views/Shared/_Layout.cshtml"; 3 | } 4 | -------------------------------------------------------------------------------- /src/FinConcile.Web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohithkrajan/FinReconcile/HEAD/src/FinConcile.Web/favicon.ico -------------------------------------------------------------------------------- /src/FinConcile.Web/docs/img/link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohithkrajan/FinReconcile/HEAD/src/FinConcile.Web/docs/img/link.png -------------------------------------------------------------------------------- /src/FinConcile.Web/Global.asax: -------------------------------------------------------------------------------- 1 | <%@ Application Codebehind="Global.asax.cs" Inherits="FinReconcile.WebApiApplication" Language="C#" %> 2 | -------------------------------------------------------------------------------- /src/FinConcile.Web/docs/img/failure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohithkrajan/FinReconcile/HEAD/src/FinConcile.Web/docs/img/failure.png -------------------------------------------------------------------------------- /src/FinConcile.Web/docs/img/success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohithkrajan/FinReconcile/HEAD/src/FinConcile.Web/docs/img/success.png -------------------------------------------------------------------------------- /src/FinConcile.Web/docs/img/inconclusive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohithkrajan/FinReconcile/HEAD/src/FinConcile.Web/docs/img/inconclusive.png -------------------------------------------------------------------------------- /src/FinConcile.Web/Content/Onion Architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohithkrajan/FinReconcile/HEAD/src/FinConcile.Web/Content/Onion Architecture.png -------------------------------------------------------------------------------- /src/FinConcile.Web/docs/css/fonts/FontAwesome.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohithkrajan/FinReconcile/HEAD/src/FinConcile.Web/docs/css/fonts/FontAwesome.ttf -------------------------------------------------------------------------------- /src/FinConcile.Web/docs/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohithkrajan/FinReconcile/HEAD/src/FinConcile.Web/docs/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /src/FinReconcile.Core/RuleEngine/RuleType.cs: -------------------------------------------------------------------------------- 1 | namespace FinReconcile.Core.Engines 2 | { 3 | public enum RuleType 4 | { 5 | And, 6 | Or 7 | } 8 | } -------------------------------------------------------------------------------- /src/FinConcile.Web/docs/css/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohithkrajan/FinReconcile/HEAD/src/FinConcile.Web/docs/css/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /src/FinConcile.Web/docs/css/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohithkrajan/FinReconcile/HEAD/src/FinConcile.Web/docs/css/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /src/FinConcile.Web/docs/css/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohithkrajan/FinReconcile/HEAD/src/FinConcile.Web/docs/css/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /src/FinConcile.Web/docs/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohithkrajan/FinReconcile/HEAD/src/FinConcile.Web/docs/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /src/FinConcile.Web/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohithkrajan/FinReconcile/HEAD/src/FinConcile.Web/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /src/FinConcile.Web/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohithkrajan/FinReconcile/HEAD/src/FinConcile.Web/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /src/FinConcile.Web/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohithkrajan/FinReconcile/HEAD/src/FinConcile.Web/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /src/FinConcile.Web/docs/css/master.css: -------------------------------------------------------------------------------- 1 | @import url("reset.css"); 2 | 3 | @import url("global.css"); 4 | 5 | @import url("structure.css"); 6 | 7 | @import url("font-awesome.css"); -------------------------------------------------------------------------------- /src/FinConcile.Web/docs/js/logger.js: -------------------------------------------------------------------------------- 1 | function logSomething(somethingInteresting) { 2 | if (typeof console != 'undefined') { 3 | console.log(somethingInteresting); 4 | } 5 | } -------------------------------------------------------------------------------- /src/FinReconcile.Core/Domain/ReconciledMatchType.cs: -------------------------------------------------------------------------------- 1 | namespace FinReconcile.Core.Domain 2 | { 3 | public enum ReconciledMatchType 4 | { 5 | Matched, 6 | NeedReview, 7 | NotMatched 8 | } 9 | } -------------------------------------------------------------------------------- /src/FinReconcile.Core/RuleEngine/IRule.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace FinReconcile.Core.Engines 4 | { 5 | public interface IRule 6 | { 7 | Expression BuildExpression(ParameterExpression source, ParameterExpression target); 8 | } 9 | } -------------------------------------------------------------------------------- /src/FinReconcile.Core/Domain/TransactionType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | 6 | namespace FinReconcile.Core.Domain 7 | { 8 | public enum TransactionType 9 | { 10 | Store = 0, 11 | ATM =1, 12 | Unknown=9 13 | } 14 | } -------------------------------------------------------------------------------- /src/FinConcile.Web/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Error 6 | 7 | 8 |
9 |

Error.

10 |

An error occurred while processing your request.

11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /src/FinConcile.Web/App_Start/FilterConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Web; 2 | using System.Web.Mvc; 3 | 4 | namespace FinReconcile 5 | { 6 | public class FilterConfig 7 | { 8 | public static void RegisterGlobalFilters(GlobalFilterCollection filters) 9 | { 10 | filters.Add(new HandleErrorAttribute()); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/FinConcile.Web/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | using System.Web.Mvc; 6 | 7 | namespace FinReconcile.Controllers 8 | { 9 | public class HomeController : Controller 10 | { 11 | public ActionResult Index() 12 | { 13 | ViewBag.Title = "FinReconcile"; 14 | 15 | return View(); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/FinConcile.Web/Content/Site.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | padding-bottom: 20px; 4 | } 5 | 6 | /* Set padding to keep content from hitting the edges */ 7 | .body-content { 8 | padding-left: 15px; 9 | padding-right: 15px; 10 | } 11 | .main-content{ 12 | margin-top:20px; 13 | } 14 | /* Set width on the form input elements since they're 100% wide by default */ 15 | input, 16 | select, 17 | textarea { 18 | max-width: 280px; 19 | } 20 | -------------------------------------------------------------------------------- /src/FinReconcile.Infra/Providers/IMarkOffFileProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace FinReconcile.Infra.Providers 9 | { 10 | public interface IMarkOffFileProvider 11 | { 12 | string SaveMarkOffFile(Stream stream, string sessionId,string fileName); 13 | string GetMarkOffFile(string sessionId,string fileName); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RuleEngine 2 | 3 | This is a simple ruleengine written in c# . A demo usage and BDD approach is taken to showcase the ruleengine used in a Finacial reconcilation platform.But the rule engine is quite flexible and can be used to wire up any type of rules easily. 4 | 5 | 6 | 7 | 8 | You can find more details about this project in 9 | 10 | Living Docs generated from Feature files:http://finreconcile.azurewebsites.net/docs/Index.html 11 | Also wiki: https://github.com/rohithkrajan/FinReconcile/wiki 12 | -------------------------------------------------------------------------------- /src/FinReconcile.Infra/MarkOffReader/IMarkOffFileParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Web; 6 | 7 | namespace FinReconcile.Infra.Parsers 8 | { 9 | public interface IMarkOffFileParser 10 | { 11 | ParserResult Validate(Stream stream); 12 | ParserResult Validate(string markOffFileContent); 13 | ParserResult GetRecords(TextReader reader); 14 | ParserResult GetRecords(string markOffFileContent); 15 | } 16 | } -------------------------------------------------------------------------------- /src/FinConcile.Web/Models/CompareModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Web; 6 | 7 | namespace FinReconcile.Models 8 | { 9 | public class CompareModel 10 | { 11 | [Required(ErrorMessage ="Please provide mark off file")] 12 | public HttpPostedFileBase ClientMarkOffFile { get; set; } 13 | 14 | [Required(ErrorMessage = "Please provide mark off file")] 15 | public HttpPostedFileBase BankMarkOfffile { get; set; } 16 | } 17 | } -------------------------------------------------------------------------------- /src/FinReconcile.Core/Domain/Interfaces/IReconcileEngine.cs: -------------------------------------------------------------------------------- 1 | using FinReconcile.Core.Engines; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace FinReconcile.Core.Domain.Interfaces 9 | { 10 | public interface IRuleEvaluator 11 | { 12 | ReconciledItem Evaluate(Transaction clientTransaction,Transaction bankTransaction); 13 | string RuleName { get; } 14 | ReconciledMatchType RuleType { get; } 15 | RuleSet RuleSet { get; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/FinReconcile.Core/ReconcileEngine/IReconcileEngine.cs: -------------------------------------------------------------------------------- 1 | using FinReconcile.Core.Domain; 2 | using FinReconcile.Core.Domain.Interfaces; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace FinReconcile.Core.Engines 10 | { 11 | public interface IReconcileEngine 12 | { 13 | IReconcileResult Reconcile(IEnumerable clientTransactions, IEnumerable bankTransactions); 14 | IList RuleEvaluators { get; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/FinReconcile.Infra/MarkOffReader/ParserContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace FinReconcile.Infra.MarkOffReader 8 | { 9 | internal class ParserContext 10 | { 11 | internal ParserContext() 12 | { 13 | HeaderToIndexMap = new Dictionary(); 14 | HeaderError = string.Empty; 15 | } 16 | 17 | internal Dictionary HeaderToIndexMap { get; private set; } 18 | internal string HeaderError {get; set;} 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/FinReconcile.Core/RuleEngine/RuleSet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | 6 | namespace FinReconcile.Core.Engines 7 | { 8 | public class RuleSet 9 | { 10 | private IList _rules= new List(); 11 | 12 | public RuleSet() 13 | { 14 | } 15 | public RuleSet(IEnumerable rules) 16 | { 17 | 18 | foreach (var item in rules) 19 | { 20 | _rules.Add(item); 21 | } 22 | } 23 | public IList Rules { get { return _rules; } } 24 | } 25 | } -------------------------------------------------------------------------------- /src/FinReconcile.Core/Domain/Transaction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | 6 | namespace FinReconcile.Core.Domain 7 | { 8 | public class Transaction 9 | { 10 | public string ProfileName { get; set; } 11 | 12 | public DateTime Date { get; set; } 13 | 14 | public long Amount { get; set; } 15 | 16 | public string Narrative { get; set; } 17 | 18 | public string Description { get; set; } 19 | 20 | public string Id { get; set; } 21 | 22 | public TransactionType Type { get; set; } 23 | 24 | public string WalletReference { get; set; } 25 | 26 | } 27 | } -------------------------------------------------------------------------------- /src/FinReconcile.Core/Domain/Interfaces/IReconcileResult.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace FinReconcile.Core.Domain.Interfaces 4 | { 5 | public interface IReconcileResult 6 | { 7 | 8 | IList MatchedItems { get; } 9 | IList NotMatchedItems { get; } 10 | void AddItems(IEnumerable items); 11 | void Add(ReconciledItem item); 12 | IList GetMatchedClientTransactions(); 13 | IList GetMatchedBankTransactions(); 14 | IList GetUnMatchedClientTransactions(); 15 | IList GetUnMatchedBankTransactions(); 16 | } 17 | } -------------------------------------------------------------------------------- /src/FinConcile.Web/App_Start/RouteConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | using System.Web.Mvc; 6 | using System.Web.Routing; 7 | 8 | namespace FinReconcile 9 | { 10 | public class RouteConfig 11 | { 12 | public static void RegisterRoutes(RouteCollection routes) 13 | { 14 | routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 15 | 16 | routes.MapRoute( 17 | name: "Default", 18 | url: "{controller}/{action}/{id}", 19 | defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } 20 | ); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/FinConcile.Web/App_Start/WebApiConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web.Http; 5 | 6 | namespace FinReconcile 7 | { 8 | public static class WebApiConfig 9 | { 10 | public static void Register(HttpConfiguration config) 11 | { 12 | // Web API configuration and services 13 | 14 | // Web API routes 15 | config.MapHttpAttributeRoutes(); 16 | 17 | config.Routes.MapHttpRoute( 18 | name: "DefaultApi", 19 | routeTemplate: "api/{controller}/{id}", 20 | defaults: new { id = RouteParameter.Optional } 21 | ); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Tests/FinConcile.Tests/Steps/TransactionTestData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace FinConcile.Tests.Steps 8 | { 9 | public class TransactionTestData 10 | { 11 | public string ProfileName { get; set; } 12 | 13 | public string Date { get; set; } 14 | 15 | public long Amount { get; set; } 16 | 17 | public string Narrative { get; set; } 18 | 19 | public string Description { get; set; } 20 | 21 | public string Id { get; set; } 22 | 23 | public string Type { get; set; } 24 | 25 | public string WalletReference { get; set; } 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/FinReconcile.Infra/Providers/SessionIdGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Web; 6 | 7 | namespace FinReconcile.Infra 8 | { 9 | public class SessionIdGenerator 10 | { 11 | public static string CreateNewId() 12 | { 13 | string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"; 14 | int len = 5; 15 | 16 | Random rnd = new Random(); 17 | StringBuilder b = new StringBuilder(len); 18 | for (int i = 0; i < len; i++) 19 | { 20 | b.Append(chars[rnd.Next(chars.Length)]); 21 | } 22 | return b.ToString(); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/FinConcile.Web/Global.asax.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | using System.Web.Http; 6 | using System.Web.Mvc; 7 | using System.Web.Optimization; 8 | using System.Web.Routing; 9 | 10 | namespace FinReconcile 11 | { 12 | public class WebApiApplication : System.Web.HttpApplication 13 | { 14 | protected void Application_Start() 15 | { 16 | AreaRegistration.RegisterAllAreas(); 17 | GlobalConfiguration.Configure(WebApiConfig.Register); 18 | FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); 19 | RouteConfig.RegisterRoutes(RouteTable.Routes); 20 | BundleConfig.RegisterBundles(BundleTable.Bundles); 21 | DIConfig.RegisterDepedencyInjection(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/FinConcile.Web/Views/Transaction/CompareResult.cshtml: -------------------------------------------------------------------------------- 1 | @model FinReconcile.Models.CompareResult 2 | 3 | @{ 4 | ViewBag.Title = "View"; 5 | Layout = "~/Views/Shared/_Layout.cshtml"; 6 | } 7 |
8 |
9 | @Html.Partial("CompareResultPartial", Model) 10 |
11 | @if (Model.UnmatchedClientRecords > 0 || Model.UnmatchedTutkaRecords>0) 12 | { 13 | Unmatched Report 14 | } 15 | Rules used 16 |
17 |
18 |
19 | -------------------------------------------------------------------------------- /Tests/FinConcile.Tests/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/FinConcile.Web/Views/Transaction/UnmatchedReport.cshtml: -------------------------------------------------------------------------------- 1 | @model FinReconcile.Models.CompareResult 2 | 3 | @{ 4 | ViewBag.Title = "View"; 5 | Layout = "~/Views/Shared/_Layout.cshtml"; 6 | } 7 |
8 |
9 | @Html.Partial("CompareResultPartial", Model) 10 |
11 | @if (Model.UnmatchedClientRecords > 0 || Model.UnmatchedTutkaRecords > 0) 12 | { 13 | Unmatched Report 14 | } 15 | Rules used 16 |
17 | @Html.Partial("UnmatchedReportPartial", Model) 18 |
19 |
20 | -------------------------------------------------------------------------------- /Tests/FinConcile.Tests/TransactionCompare.feature: -------------------------------------------------------------------------------- 1 | Feature: Compare Transactions 2 | In order to reconcile transactions 3 | As a user 4 | I want to compare client and bank markoff files 5 | 6 | @tranascationcompare 7 | Scenario: Browse Compare Page 8 | When the user goes to compare user screen 9 | Then the compare user view should be displayed 10 | 11 | @transactioncompare 12 | Scenario: On Successful upload of Transaction Files,user should be shown Comparison Result Page 13 | Given ClientMarkOffFile and BankMarkOffFile 14 | When the user clicks on the Compare button 15 | Then user should be redirected to compare result Page 16 | 17 | @transactioncompare 18 | Scenario: Details in Comparison Result Page 19 | Given ClientMarkOffFile and BankMarkOffFile 20 | When the user clicks on the Compare button 21 | Then Comparison Result should contain Both Names of the Files 'ClientMarkoffFile20140113' and 'BankMarkoffFile20140113' 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/FinConcile.Web/Controllers/RulesController.cs: -------------------------------------------------------------------------------- 1 | using FinReconcile.Core.Engines; 2 | using Newtonsoft.Json; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Web; 7 | using System.Web.Mvc; 8 | 9 | namespace FinReconcile.Controllers 10 | { 11 | public class RulesController : Controller 12 | { 13 | public IReconcileEngine _reconcileEngine; 14 | 15 | public RulesController(IReconcileEngine reconcileEngine) 16 | { 17 | _reconcileEngine = reconcileEngine; 18 | } 19 | // GET: Rules 20 | public ActionResult Index() 21 | { 22 | Dictionary rules = new Dictionary(); 23 | foreach (var item in _reconcileEngine.RuleEvaluators) 24 | { 25 | rules.Add(item.RuleName, JsonConvert.SerializeObject(item.RuleSet)); 26 | } 27 | return View(rules); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/FinReconcile.Infra/MarkOffReader/ParserResult.cs: -------------------------------------------------------------------------------- 1 | using FinReconcile.Core.Domain; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Web; 6 | 7 | namespace FinReconcile.Infra.Parsers 8 | { 9 | public class ParserResult:List 10 | { 11 | public ParserResult(bool isValid) 12 | { 13 | InvalidEntries = new Dictionary(); 14 | Headers = new List(); 15 | IsValid = isValid; 16 | Errors = new Dictionary(); 17 | HeaderIndexes = new Dictionary(); 18 | } 19 | public Dictionary InvalidEntries { get; private set; } 20 | public List Headers { get; private set; } 21 | public bool IsValid { get; private set; } 22 | public Dictionary Errors { get; private set; } 23 | public Dictionary HeaderIndexes { get; set; } 24 | } 25 | } -------------------------------------------------------------------------------- /src/FinConcile.Web/docs/js/jquery.highlight-4.closure.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | highlight v4 4 | 5 | Highlights arbitrary terms. 6 | 7 | 8 | 9 | MIT license. 10 | 11 | Johann Burkard 12 | 13 | 14 | 15 | */ 16 | 17 | jQuery.fn.highlight=function(c){function e(b,c){var d=0;if(3==b.nodeType){var a=b.data.toUpperCase().indexOf(c);if(0<=a){d=document.createElement("span");d.className="highlight";a=b.splitText(a);a.splitText(c.length);var f=a.cloneNode(!0);d.appendChild(f);a.parentNode.replaceChild(d,a);d=1}}else if(1==b.nodeType&&b.childNodes&&!/(script|style)/i.test(b.tagName))for(a=0;a file = new Mock(); 17 | file.Setup(x => x.InputStream).Returns(GenerateStreamFromString(fileContent)); 18 | file.Setup(x => x.ContentLength).Returns(fileContent.Length); 19 | file.Setup(x => x.FileName).Returns(fileName); 20 | return file.Object; 21 | } 22 | public static Stream GenerateStreamFromString(string s) 23 | { 24 | MemoryStream stream = new MemoryStream(); 25 | StreamWriter writer = new StreamWriter(stream); 26 | writer.Write(s); 27 | writer.Flush(); 28 | stream.Position = 0; 29 | return stream; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/FinConcile.Web/docs/css/reset.css: -------------------------------------------------------------------------------- 1 | html, body, div, span, applet, object, iframe, 2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 3 | a, abbr, acronym, address, big, cite, code, 4 | del, dfn, em, font, img, ins, kbd, q, s, samp, 5 | small, strike, strong, sub, sup, tt, var, 6 | dl, dt, dd, ol, ul, li, 7 | fieldset, form, label, legend, 8 | table, caption, tbody, tfoot, thead, tr, th, td { 9 | border: 0; 10 | font-family: inherit; 11 | font-size: 100%; 12 | font-style: inherit; 13 | font-weight: inherit; 14 | margin: 0; 15 | outline: 0; 16 | padding: 0; 17 | vertical-align: baseline; 18 | } 19 | /* remember to define focus styles! */ 20 | 21 | :focus { outline: 0; } 22 | 23 | body { 24 | background: white; 25 | color: black; 26 | line-height: 1; 27 | } 28 | 29 | ol, ul { list-style: none; } 30 | /* tables still need 'cellspacing="0"' in the markup */ 31 | 32 | table { 33 | border-collapse: separate; 34 | border-spacing: 0; 35 | } 36 | 37 | caption, th, td { 38 | font-weight: normal; 39 | text-align: left; 40 | } 41 | 42 | blockquote:before, blockquote:after, 43 | q:before, q:after { content: ""; } 44 | 45 | blockquote, q { quotes: "" ""; } -------------------------------------------------------------------------------- /src/FinConcile.Web/Views/Rules/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model Dictionary 2 | 3 | @{ 4 | ViewBag.Title = "View"; 5 | Layout = "~/Views/Shared/_Layout.cshtml"; 6 | } 7 | 8 |

Rules Configured

9 | 10 | 11 |
12 |
13 | @foreach (var rule in Model) 14 | { 15 |
16 |
17 |
18 |
19 |
@rule.Key
20 |
21 |

22 |                               @rule.Value
23 |             
24 |
25 |
26 |
27 |
28 |
29 | } 30 |
31 |
32 | 34 | 35 | -------------------------------------------------------------------------------- /Tests/FinConcile.Tests/TestUtils/SpecflowExtensions.cs: -------------------------------------------------------------------------------- 1 | using FinReconcile.Core.Domain; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TechTalk.SpecFlow; 8 | 9 | namespace FinConcile.Tests.TestUtils 10 | { 11 | public static class SpecflowExtensions 12 | { 13 | public static IList GetTransactions(this Table table) 14 | { 15 | List transactions = new List(); 16 | foreach (var row in table.Rows) 17 | { 18 | transactions.Add(new Transaction() { 19 | ProfileName = row["ProfileName"], 20 | Date = DateTime.Parse(row["Date"]), 21 | Amount =Int64.Parse(row["Amount"]), 22 | Narrative = row["Narrative"], 23 | Description = row["Description"], 24 | Type = (TransactionType)Enum.Parse(typeof(TransactionType),row["Type"]), 25 | Id = row["Id"], 26 | WalletReference = row["WalletReference"] 27 | }); 28 | } 29 | return transactions; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/FinConcile.Web/App_Start/BundleConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Web; 2 | using System.Web.Optimization; 3 | 4 | namespace FinReconcile 5 | { 6 | public class BundleConfig 7 | { 8 | // For more information on bundling, visit https://go.microsoft.com/fwlink/?LinkId=301862 9 | public static void RegisterBundles(BundleCollection bundles) 10 | { 11 | bundles.Add(new ScriptBundle("~/bundles/jquery").Include( 12 | "~/Scripts/jquery-{version}.js")); 13 | 14 | // Use the development version of Modernizr to develop with and learn from. Then, when you're 15 | // ready for production, use the build tool at https://modernizr.com to pick only the tests you need. 16 | bundles.Add(new ScriptBundle("~/bundles/modernizr").Include( 17 | "~/Scripts/modernizr-*")); 18 | 19 | bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include( 20 | "~/Scripts/bootstrap.js", 21 | "~/Scripts/respond.js")); 22 | 23 | bundles.Add(new StyleBundle("~/Content/css").Include( 24 | "~/Content/paper.css", 25 | "~/Content/site.css")); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/FinReconcile.Core/RuleEngine/Rules/DateRule.cs: -------------------------------------------------------------------------------- 1 | using FinReconcile.Core.Domain; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Web; 7 | 8 | namespace FinReconcile.Core.Engines.Rules 9 | { 10 | /// 11 | /// This Rule can match dates if if they are different within the allowed delta value 12 | /// 13 | public class DateRule : MethodBaseRule 14 | { 15 | 16 | public DateRule(double delta):base(CompareDateWithDelta) 17 | { 18 | constantArgument = delta; 19 | } 20 | 21 | public static bool CompareDateWithDelta(Transaction source,Transaction target,double delta) 22 | { 23 | if (source.Date==target.Date) 24 | { 25 | return true; 26 | } 27 | else 28 | { 29 | if (source.Date>target.Date) 30 | { 31 | return TimeSpan.FromSeconds(delta) >= (source.Date - target.Date); 32 | } 33 | else 34 | { 35 | return TimeSpan.FromSeconds(delta) >= (target.Date - source.Date); 36 | } 37 | 38 | } 39 | } 40 | 41 | 42 | } 43 | } -------------------------------------------------------------------------------- /src/FinConcile.Web/Web.Debug.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/FinConcile.Web/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 |
2 |

FinReconcile App

3 |

This is an application which can be used to reconcile two sets of finacial records to ensure figures are correct and in agreement

4 |

Start Reconcile »

5 |
6 |
7 |
8 |

BDD specs

9 |

This application is developed using ASP.NET MVC,C# ,Bootstrap etc using BDD and deployed on Microsoft Azure.You can browse BDD specs here

10 |

Browse specs »

11 |
12 |
13 |

RuleEngine

14 |

An Integral part of this application is the main Rule engine.RuleEngine allows you to define custom rules to match/unmatch transactions

15 |

Learn more »

16 |
17 |
18 |

Wiki and Code

19 |

This application code is open source and is available in github.

20 |

Browse wiki »

21 |
22 |
23 | -------------------------------------------------------------------------------- /Tests/FinConcile.Tests/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/FinConcile.Web/Web.Release.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 19 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/FinReconcile.Infra/Providers/MarkOffFileProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Web; 7 | 8 | namespace FinReconcile.Infra.Providers 9 | { 10 | public class MarkOffFileProvider : IMarkOffFileProvider 11 | { 12 | public string GetMarkOffFile(string sessionId, string fileName) 13 | { 14 | var clientMarkOffFilePath = Path.Combine(HttpContext.Current.Server.MapPath("~/Uploads"), sessionId, fileName); 15 | 16 | return File.ReadAllText(clientMarkOffFilePath); 17 | } 18 | 19 | public string SaveMarkOffFile(Stream stream,string sessionId,string fileName) 20 | { 21 | var sessionDirectory = Path.Combine(HttpContext.Current.Server.MapPath("~/Uploads"), sessionId); 22 | DirectoryInfo di = Directory.CreateDirectory(sessionDirectory); 23 | var clientMarkOffFilePath = Path.Combine(sessionDirectory, fileName); 24 | 25 | using (FileStream fileStream = File.Create(clientMarkOffFilePath, (int)stream.Length)) 26 | { 27 | byte[] bytesInStream = new byte[stream.Length]; 28 | stream.Read(bytesInStream, 0, (int)bytesInStream.Length); 29 | fileStream.Write(bytesInStream, 0, bytesInStream.Length); 30 | } 31 | return sessionId; 32 | 33 | } 34 | 35 | } 36 | } -------------------------------------------------------------------------------- /src/FinReconcile.Core/RuleEngine/Rules/MethodBaseRule.cs: -------------------------------------------------------------------------------- 1 | using FinReconcile.Core.Domain; 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Linq.Expressions; 7 | using System.Web; 8 | 9 | namespace FinReconcile.Core.Engines.Rules 10 | { 11 | public abstract class MethodBaseRule : IRule 12 | { 13 | Func _factoryMethod; 14 | 15 | public MethodBaseRule(Func factoryMethod) 16 | { 17 | _factoryMethod = factoryMethod; 18 | } 19 | 20 | public Expression BuildExpression(ParameterExpression source, ParameterExpression target) 21 | { 22 | return Expression.Call(_factoryMethod.Method, source, target); 23 | 24 | } 25 | } 26 | public abstract class MethodBaseRule : IRule 27 | { 28 | protected T constantArgument = default(T); 29 | 30 | Func _factoryMethod; 31 | 32 | public MethodBaseRule(Func factoryMethod) 33 | { 34 | _factoryMethod = factoryMethod; 35 | } 36 | 37 | public Expression BuildExpression(ParameterExpression source, ParameterExpression target) 38 | { 39 | return Expression.Call(_factoryMethod.Method, source, target, Expression.Constant(constantArgument)); 40 | 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/FinConcile.Web/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("FinReconcile")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("HP Inc.")] 12 | [assembly: AssemblyProduct("FinReconcile")] 13 | [assembly: AssemblyCopyright("Copyright © HP Inc. 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("088d8e7a-9103-4eef-a615-5a36218def07")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Revision and Build Numbers 33 | // by using the '*' as shown below: 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /Tests/FinConcile.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("FinConcile.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("HP Inc.")] 12 | [assembly: AssemblyProduct("FinConcile.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © HP Inc. 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("b39abd45-93f6-44a3-8f36-70eda5fa107f")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/FinReconcile.Core/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("FinReconcile.Core")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("HP Inc.")] 12 | [assembly: AssemblyProduct("FinReconcile.Core")] 13 | [assembly: AssemblyCopyright("Copyright © HP Inc. 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("3f2136c8-bb7c-4e46-b671-4cd545b76955")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/FinReconcile.Infra/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("FinReconcile.Infra")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("HP Inc.")] 12 | [assembly: AssemblyProduct("FinReconcile.Infra")] 13 | [assembly: AssemblyCopyright("Copyright © HP Inc. 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("757ee3de-ba4c-4559-b05f-066b23e36203")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/FinConcile.Web/docs/js/scripts.js: -------------------------------------------------------------------------------- 1 | function initializeToc() { 2 | $(".tocCollapser").one("click", function() { 3 | collapseToc(); 4 | }); 5 | } 6 | 7 | function collapseToc() { 8 | /* set class of toc element to collapsed. CSS will do the rest*/ 9 | $("#toc").addClass("collapsed"); 10 | 11 | /* change the text and title of the collapser to make it appear as an expander */ 12 | var tocCollapser = $(".tocCollapser"); 13 | tocCollapser.text("»"); 14 | tocCollapser.attr("title", "Expand Table of Content"); 15 | 16 | /* register a one-time handler for the click event that will expand the toc. */ 17 | $(".tocCollapser").one("click", function() { 18 | expandToc(); 19 | }); 20 | } 21 | 22 | function expandToc() { 23 | /* removes the collapsed class of toc element. CSS will do the rest*/ 24 | $("#toc").removeClass("collapsed"); 25 | 26 | /* change the text and title of the collapser to make it appear as an collapser again */ 27 | var tocCollapser = $(".tocCollapser"); 28 | tocCollapser.text("«"); 29 | tocCollapser.attr("title", "Collapse Table of Content"); 30 | 31 | /* register a one-time handler for the click event that will collapse the toc. */ 32 | $(".tocCollapser").one("click", function() { 33 | collapseToc(); 34 | }); 35 | } 36 | 37 | function showImageLink(scenarioSlug) { 38 | var url = window.location.origin + window.location.pathname + window.location.search; 39 | 40 | if (scenarioSlug != null && scenarioSlug != '') { 41 | url = url + '#' + scenarioSlug; 42 | } 43 | 44 | window.prompt("Scenario Link: (Ctrl+C to copy)", url); 45 | } -------------------------------------------------------------------------------- /src/FinReconcile.Core/RuleEngine/Rules/PropertyRule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Web; 6 | 7 | namespace FinReconcile.Core.Engines.Rules 8 | { 9 | // this is a simple rule which compares two property values of transaction 10 | public class PropertyRule:IRule 11 | { 12 | 13 | public string SourceProperty 14 | { 15 | get; 16 | set; 17 | } 18 | 19 | public string Operator 20 | { 21 | get; 22 | set; 23 | } 24 | 25 | public string TargetProperty 26 | { 27 | get; 28 | set; 29 | } 30 | //these values are purposely kept as string to build a Rulebuilder UI 31 | //We can also add an overload which will allow lambda expressions 32 | public PropertyRule(string SourceProperty, string Operator, string targetProperty) 33 | { 34 | this.SourceProperty = SourceProperty; 35 | this.Operator = Operator; 36 | this.TargetProperty = targetProperty; 37 | } 38 | 39 | public Expression BuildExpression(ParameterExpression source,ParameterExpression target) 40 | { 41 | //here it is quite trivail ato add support for other operator like Greaterthan,LessThan etc. 42 | //currently only Equal is implemented as it was matching the document provided. 43 | return Expression.Equal(Expression.Property(source, this.SourceProperty), 44 | Expression.Property(target, this.TargetProperty)); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/FinConcile.Web/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | @ViewBag.Title 7 | @Styles.Render("~/Content/css") 8 | @Scripts.Render("~/bundles/modernizr") 9 | 10 | 11 | 29 |
30 | @RenderBody() 31 |
32 |
33 |

© @DateTime.Now.Year - FinConcile Application

34 |
35 |
36 | 37 | @Scripts.Render("~/bundles/jquery") 38 | @Scripts.Render("~/bundles/bootstrap") 39 | @RenderSection("scripts", required: false) 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/FinReconcile.Core/Domain/TransactionSet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | 6 | namespace FinReconcile.Core.Domain 7 | { 8 | public class TransactionSet 9 | { 10 | private List _clientSet; 11 | private List _bankSet; 12 | private bool _initialized = false; 13 | public TransactionSet() 14 | { 15 | _clientSet = new List(); 16 | _bankSet = new List(); 17 | } 18 | public int Id { get; set; } 19 | public IEnumerable ClientSet { get { return _clientSet; } } 20 | public IEnumerable BankSet { get { return _bankSet; } } 21 | 22 | public bool IsReconciled 23 | { 24 | get 25 | { 26 | if (_initialized) 27 | { 28 | return _clientSet.Count == 0 && _bankSet.Count == 0; 29 | } 30 | 31 | return false; 32 | } 33 | } 34 | 35 | public void AddClientTransaction(Transaction clientTransaction) 36 | { 37 | _clientSet.Add(clientTransaction); 38 | _initialized = true; 39 | } 40 | public void AddBankTransaction(Transaction bankTransaction) 41 | { 42 | _bankSet.Add(bankTransaction); 43 | _initialized = true; 44 | } 45 | public void RemoveTransactions(Transaction clientTransaction, Transaction bankTransaction) 46 | { 47 | _clientSet.Remove(clientTransaction); 48 | _bankSet.Remove(bankTransaction); 49 | } 50 | 51 | } 52 | } -------------------------------------------------------------------------------- /src/FinConcile.Web/Models/CompareResult.cs: -------------------------------------------------------------------------------- 1 | using FinReconcile.Core.Domain.Interfaces; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Web; 6 | 7 | namespace FinReconcile.Models 8 | { 9 | public class CompareResult 10 | { 11 | private int _matchedClient; 12 | private int _matchedBank; 13 | private int _unMatchedClient; 14 | private int _unMatchedBank; 15 | 16 | public CompareResult(string clientMarkOffFile,string bankMarkOffFile, IReconcileResult result) 17 | { 18 | this.ClientMarkOffFile = clientMarkOffFile; 19 | this.BankMarkOffFile = bankMarkOffFile; 20 | 21 | _matchedClient = result.GetMatchedClientTransactions().Count; 22 | _matchedBank = result.GetMatchedBankTransactions().Count; 23 | _unMatchedClient = result.GetUnMatchedClientTransactions().Count; 24 | _unMatchedBank = result.GetUnMatchedBankTransactions().Count; 25 | 26 | Result = result; 27 | } 28 | public string ClientMarkOffFile { get; private set; } 29 | public string BankMarkOffFile { get; private set; } 30 | 31 | public int TotalClientRecords { get { return _matchedClient + _unMatchedClient; } } 32 | public int TotalBankRecords { get { return _matchedBank + _unMatchedBank; } } 33 | public int MatchingClientRecords { get { return _matchedClient; } } 34 | public int MatchingBankRecords { get { return _matchedBank; } } 35 | 36 | public int UnmatchedClientRecords { get { return _unMatchedClient; } } 37 | public int UnmatchedTutkaRecords { get { return _unMatchedBank; } } 38 | 39 | public IReconcileResult Result { get; private set; } 40 | } 41 | } -------------------------------------------------------------------------------- /src/FinConcile.Web/Views/Web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /RuleEngineSample.txt: -------------------------------------------------------------------------------- 1 | public class Transaction 2 | { 3 | public string ProfileName { get; set; } 4 | public DateTime Date { get; set; } 5 | 6 | public long Amount { get; set; } 7 | 8 | public string Narative { get; set; } 9 | 10 | public string Description { get; set; } 11 | 12 | public string Id { get; set; } 13 | 14 | public TransactionType Type {get;set;} 15 | 16 | public string WalletReference { get; set; } 17 | 18 | } 19 | public class Command 20 | { 21 | public string SourceMember 22 | { 23 | get; 24 | set; 25 | } 26 | 27 | public string Operator 28 | { 29 | get; 30 | set; 31 | } 32 | 33 | public string TargetMember 34 | { 35 | get; 36 | set; 37 | } 38 | 39 | public Command(string SourceMember, string Operator, string TargetMember) 40 | { 41 | this.SourceMember = SourceMember; 42 | this.Operator = Operator; 43 | this.TargetMember = TargetMember; 44 | } 45 | } 46 | public class RuleEngine 47 | { 48 | ParameterExpression transParam = Expression.Parameter(typeof(Transaction), "transactionSource"); 49 | ParameterExpression transTarget = Expression.Parameter(typeof(Transaction), "transactionTarget"); 50 | 51 | Command _command = new Command("Id", "Equal", "Id"); 52 | private Expression BuildExpression() 53 | { 54 | return Expression.Equal(Expression.Property(transParam, _command.SourceMember), 55 | Expression.Property(transTarget,_command.TargetMember)); 56 | 57 | } 58 | public Func CompileRule() 59 | { 60 | var finalExpression = BuildExpression(); 61 | 62 | return Expression.Lambda>(finalExpression, 63 | new ParameterExpression[] { transParam, transTarget }).Compile(); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/FinConcile.Web/docs/js/stringFormatting.js: -------------------------------------------------------------------------------- 1 | function addSpacesToCamelCasedString(unformattedString) { 2 | // IE does not implement trim() and the following replaces the functionality if unavailable 3 | // http://stackoverflow.com/questions/2308134/trim-in-javascript-not-working-in-ie 4 | if (typeof String.prototype.trim !== 'function') { 5 | String.prototype.trim = function () { 6 | return this.replace(/^\s+|\s+$/g, ''); 7 | } 8 | } 9 | 10 | var formattedString = ''; 11 | var i = 0; 12 | while (i <= unformattedString.length) { 13 | var ch = unformattedString.charAt(i); 14 | var nextChar = unformattedString.charAt(i + 1); 15 | 16 | if (ch == ch.toLowerCase() && nextChar == nextChar.toUpperCase()) { 17 | formattedString = formattedString.trim() + ch + ' '; 18 | } else if (ch == ch.toUpperCase() && nextChar == nextChar.toLowerCase() && nextChar != nextChar.toUpperCase() && nextChar != '') { 19 | formattedString = formattedString.trim() + ' ' + ch; 20 | } else { 21 | formattedString += ch; 22 | } 23 | i++; 24 | } 25 | 26 | return formattedString.trim(); 27 | } 28 | 29 | function removeBeginningHash(hashedString) { 30 | if (hashedString.substring(0, 1) == "#") { 31 | return hashedString.substring(1, hashedString.length); 32 | } else { 33 | return hashedString; 34 | } 35 | } 36 | 37 | function renderMarkdownBlock(markdownText) { 38 | if (markdownText != '') { 39 | // More info: http://code.google.com/p/pagedown/wiki/PageDown 40 | var converter = new Markdown.Converter(); 41 | 42 | setupMarkdownExtraWithBootstrapTableStyles(converter); 43 | 44 | var transformed = '

' + converter.makeHtml(markdownText); + '

' 45 | return transformed; 46 | } 47 | return markdownText; 48 | } 49 | 50 | function setupMarkdownExtraWithBootstrapTableStyles(converter) { 51 | // More Info: https://github.com/jmcmanus/pagedown-extra 52 | Markdown.Extra.init(converter, { table_class: "table table-bordered table-condensed table-striped" }); 53 | } -------------------------------------------------------------------------------- /src/FinConcile.Web/App_Start/DIConfig.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using Autofac.Integration.Mvc; 3 | using FinReconcile.App_Start; 4 | using FinReconcile.Core.Domain.Interfaces; 5 | using FinReconcile.Core.Engines; 6 | using FinReconcile.Infra.Parsers; 7 | using FinReconcile.Infra.Providers; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Linq; 11 | using System.Web; 12 | using System.Web.Mvc; 13 | 14 | namespace FinReconcile 15 | { 16 | public class DIConfig 17 | { 18 | private static ContainerBuilder _builder; 19 | 20 | public static void RegisterDepedencyInjection() 21 | { 22 | _builder = new ContainerBuilder(); 23 | 24 | // Register your MVC controllers. (MvcApplication is the name of 25 | // the class in Global.asax.) 26 | _builder.RegisterControllers(typeof(WebApiApplication).Assembly); 27 | 28 | // OPTIONAL: Register model binders that require DI. 29 | _builder.RegisterModelBinders(typeof(WebApiApplication).Assembly); 30 | _builder.RegisterModelBinderProvider(); 31 | 32 | // OPTIONAL: Register web abstractions like HttpContextBase. 33 | _builder.RegisterModule(); 34 | 35 | // OPTIONAL: Enable property injection in view pages. 36 | _builder.RegisterSource(new ViewRegistrationSource()); 37 | 38 | // OPTIONAL: Enable property injection into action filters. 39 | _builder.RegisterFilterProvider(); 40 | 41 | RegisterTypes(); 42 | 43 | // Set the dependency resolver to be Autofac. 44 | var container = _builder.Build(); 45 | DependencyResolver.SetResolver(new AutofacDependencyResolver(container)); 46 | 47 | } 48 | 49 | private static void RegisterTypes() 50 | { 51 | _builder.RegisterType().As(); 52 | _builder.RegisterType().As(); 53 | 54 | _builder.RegisterType().As(); 55 | _builder.RegisterInstance(RulesConfig.RegisteredRules()).As>(); 56 | 57 | 58 | } 59 | 60 | } 61 | } -------------------------------------------------------------------------------- /src/FinConcile.Web/App_Start/RulesConfig.cs: -------------------------------------------------------------------------------- 1 | using FinReconcile.Core.Domain; 2 | using FinReconcile.Core.Domain.Interfaces; 3 | using FinReconcile.Core.Engines; 4 | using FinReconcile.Core.Engines.Rules; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Web; 9 | 10 | namespace FinReconcile.App_Start 11 | { 12 | public static class RulesConfig 13 | { 14 | public static IList RegisteredRules() 15 | { 16 | IList _ruleSetEvaulators = new List(); 17 | _ruleSetEvaulators.Add(new RuleSetEvaluator("MatchAllFields", new RuleSet(new IRule[] { 18 | new PropertyRule("Id", "Equal", "Id") , 19 | new PropertyRule("Amount", "Equal", "Amount"), 20 | new PropertyRule("ProfileName", "Equal", "ProfileName"), 21 | new PropertyRule("Description", "Equal", "Description"), 22 | new PropertyRule("Narrative", "Equal", "Narrative"), 23 | new PropertyRule("WalletReference", "Equal", "WalletReference"), 24 | new PropertyRule("Date", "Equal", "Date") 25 | }), ReconciledMatchType.Matched)); 26 | 27 | _ruleSetEvaulators.Add(new RuleSetEvaluator("MatchDateWithDeltaof120SecondsAndAllOtherFields", new RuleSet(new IRule[] { 28 | new PropertyRule("Id", "Equal", "Id") , 29 | new PropertyRule("Amount", "Equal", "Amount"), 30 | new PropertyRule("ProfileName", "Equal", "ProfileName"), 31 | new PropertyRule("Description", "Equal", "Description"), 32 | new PropertyRule("Narrative", "Equal", "Narrative"), 33 | new PropertyRule("WalletReference", "Equal", "WalletReference"), 34 | new DateRule(120) 35 | }), ReconciledMatchType.Matched)); 36 | 37 | _ruleSetEvaulators.Add(new RuleSetEvaluator("DefaultRule", new RuleSet(new IRule[] { 38 | new PropertyRule("Amount", "Equal", "Amount"), 39 | new PropertyRule("WalletReference", "Equal", "WalletReference"), 40 | new DateRule(120) 41 | }), ReconciledMatchType.Matched)); 42 | 43 | return _ruleSetEvaulators; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /src/FinConcile.Web/docs/js/html5.js: -------------------------------------------------------------------------------- 1 | /*! HTML5 Shiv vpre3.6 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 2 | Uncompressed source: https://github.com/aFarkas/html5shiv */ 3 | (function(a,b){function h(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function i(){var a=l.elements;return typeof a=="string"?a.split(" "):a}function j(a){var b={},c=a.createElement,f=a.createDocumentFragment,g=f();a.createElement=function(a){if(!l.shivMethods)return c(a);var f;return b[a]?f=b[a].cloneNode():e.test(a)?f=(b[a]=c(a)).cloneNode():f=c(a),f.canHaveChildren&&!d.test(a)?g.appendChild(f):f},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+i().join().replace(/\w+/g,function(a){return c(a),g.createElement(a),'c("'+a+'")'})+");return n}")(l,g)}function k(a){var b;return a.documentShived?a:(l.shivCSS&&!f&&(b=!!h(a,"article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio{display:none}canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden]{display:none}audio[controls]{display:inline-block;*display:inline;*zoom:1}mark{background:#FF0;color:#000}")),g||(b=!j(a)),b&&(a.documentShived=b),a)}var c=a.html5||{},d=/^<|^(?:button|form|map|select|textarea|object|iframe|option|optgroup)$/i,e=/^<|^(?:a|b|button|code|div|fieldset|form|h1|h2|h3|h4|h5|h6|i|iframe|img|input|label|li|link|ol|option|p|param|q|script|select|span|strong|style|table|tbody|td|textarea|tfoot|th|thead|tr|ul)$/i,f,g;(function(){var c=b.createElement("a");c.innerHTML="",f="hidden"in c,f&&typeof injectElementWithStyles=="function"&&injectElementWithStyles("#modernizr{}",function(b){b.hidden=!0,f=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle).display=="none"}),g=c.childNodes.length==1||function(){try{b.createElement("a")}catch(a){return!0}var c=b.createDocumentFragment();return typeof c.cloneNode=="undefined"||typeof c.createDocumentFragment=="undefined"||typeof c.createElement=="undefined"}()})();var l={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:k};a.html5=l,k(b)})(this,document) -------------------------------------------------------------------------------- /src/FinConcile.Web/Views/Transaction/Compare.cshtml: -------------------------------------------------------------------------------- 1 | @model FinReconcile.Models.CompareModel 2 | @{ 3 | ViewBag.Title = "Compare"; 4 | Layout = "~/Views/Shared/_Layout.cshtml"; 5 | } 6 | @using (Html.BeginForm("compare","transaction",FormMethod.Post, new { enctype = "multipart/form-data" })) 7 | { 8 | @Html.AntiForgeryToken() 9 |
10 |
11 |
12 |
13 |
14 |
Compare Files
15 |
16 |
17 |
18 | 19 |
20 | 21 |
22 |
23 | @Html.ValidationMessageFor(model => model.ClientMarkOffFile) 24 |
25 |
26 |
27 |
28 |
29 |
30 | 31 |
32 | 33 |
34 |
35 | @Html.ValidationMessageFor(model => model.BankMarkOfffile) 36 |
37 |
38 | 39 |
40 |
41 |
42 | 43 |
44 |
45 |
46 |
47 | 48 |
49 | 50 | 51 |
52 | } 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/FinReconcile.Core/RuleEngine/RuleEngine.cs: -------------------------------------------------------------------------------- 1 | using FinReconcile.Core.Domain; 2 | using FinReconcile.Core.Domain.Interfaces; 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Linq.Expressions; 8 | using System.Web; 9 | 10 | namespace FinReconcile.Core.Engines 11 | { 12 | public class RuleSetEvaluator:IRuleEvaluator 13 | { 14 | ParameterExpression transSource = Expression.Parameter(typeof(Transaction), "transactionSource"); 15 | ParameterExpression transTarget= Expression.Parameter(typeof(Transaction), "transactionTarget"); 16 | 17 | RuleSet _ruleSet; 18 | Func _compiledRule; 19 | 20 | public RuleSetEvaluator(RuleSet ruleSet) 21 | { 22 | _ruleSet = ruleSet; 23 | _compiledRule = CompileRule(); 24 | } 25 | public RuleSetEvaluator(string ruleName,RuleSet ruleSet, ReconciledMatchType ruleType):this(ruleSet) 26 | { 27 | this.RuleName = ruleName; 28 | this.RuleType = ruleType; 29 | } 30 | public RuleSet RuleSet 31 | { 32 | get { return _ruleSet; } 33 | } 34 | public string RuleName { get; } 35 | public ReconciledMatchType RuleType { get; } 36 | 37 | public Func CompileRule() 38 | { 39 | Expression finalExpression = null; 40 | 41 | for (int i = 0; i < _ruleSet.Rules.Count; i++) 42 | { 43 | Expression currentExp = _ruleSet.Rules[i].BuildExpression(transSource, transTarget); 44 | 45 | if (finalExpression != null&&i<=_ruleSet.Rules.Count-1) 46 | { 47 | finalExpression = Expression.AndAlso(finalExpression, currentExp); 48 | } 49 | else 50 | { 51 | finalExpression = currentExp; 52 | } 53 | } 54 | 55 | return Expression.Lambda>(finalExpression, 56 | new ParameterExpression[] { transSource, transTarget }).Compile(); 57 | } 58 | 59 | public ReconciledItem Evaluate(Transaction clientTransaction,Transaction bankTransaction) 60 | { 61 | bool matched = _compiledRule(clientTransaction, bankTransaction); 62 | ReconciledItem resultItem = new ReconciledItem(clientTransaction, bankTransaction, matched ? ReconciledMatchType.Matched : ReconciledMatchType.NotMatched); 63 | 64 | return resultItem; 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /src/FinConcile.Web/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /src/FinReconcile.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26730.12 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinReconcile.Web", "FinConcile.Web\FinReconcile.Web.csproj", "{214C84FF-2C6B-4846-B40B-536CAADA8127}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinConcile.Tests", "..\Tests\FinConcile.Tests\FinConcile.Tests.csproj", "{B39ABD45-93F6-44A3-8F36-70EDA5FA107F}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinReconcile.Infra", "FinReconcile.Infra\FinReconcile.Infra.csproj", "{757EE3DE-BA4C-4559-B05F-066B23E36203}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinReconcile.Core", "FinReconcile.Core\FinReconcile.Core.csproj", "{3F2136C8-BB7C-4E46-B671-4CD545B76955}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {214C84FF-2C6B-4846-B40B-536CAADA8127}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {214C84FF-2C6B-4846-B40B-536CAADA8127}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {214C84FF-2C6B-4846-B40B-536CAADA8127}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {214C84FF-2C6B-4846-B40B-536CAADA8127}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {B39ABD45-93F6-44A3-8F36-70EDA5FA107F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {B39ABD45-93F6-44A3-8F36-70EDA5FA107F}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {B39ABD45-93F6-44A3-8F36-70EDA5FA107F}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {B39ABD45-93F6-44A3-8F36-70EDA5FA107F}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {757EE3DE-BA4C-4559-B05F-066B23E36203}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {757EE3DE-BA4C-4559-B05F-066B23E36203}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {757EE3DE-BA4C-4559-B05F-066B23E36203}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {757EE3DE-BA4C-4559-B05F-066B23E36203}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {3F2136C8-BB7C-4E46-B671-4CD545B76955}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {3F2136C8-BB7C-4E46-B671-4CD545B76955}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {3F2136C8-BB7C-4E46-B671-4CD545B76955}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {3F2136C8-BB7C-4E46-B671-4CD545B76955}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {551FD553-91AE-4D4F-B281-EC2E246D35F0} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /src/FinConcile.Web/Views/Transaction/CompareResultPartial.cshtml: -------------------------------------------------------------------------------- 1 | @model FinReconcile.Models.CompareResult 2 | 3 | 4 |
5 |
6 |
7 |
@Model.ClientMarkOffFile
8 |
9 |
10 |
11 | @Html.Label("Total Records") 12 |
13 | 14 |
15 | @Html.DisplayFor(model => model.TotalClientRecords) 16 |
17 | 18 |
19 | @Html.Label("Matching Records") 20 |
21 | 22 |
23 | @Html.DisplayFor(model => model.MatchingClientRecords) 24 |
25 | 26 |
27 | @Html.Label("Unmatched Records") 28 |
29 | 30 |
31 | @Html.DisplayFor(model => model.UnmatchedClientRecords) 32 |
33 | 34 |
35 |
36 |
37 |
38 |
39 |
40 |
@Model.BankMarkOffFile
41 |
42 |
43 | 44 |
45 | @Html.Label("Total Records") 46 |
47 | 48 |
49 | @Html.DisplayFor(model => model.TotalBankRecords) 50 |
51 | 52 |
53 | @Html.Label("Matching Records") 54 |
55 | 56 |
57 | @Html.DisplayFor(model => model.MatchingBankRecords) 58 |
59 | 60 |
61 | @Html.Label("Unmatched Records") 62 |
63 | 64 |
65 | @Html.DisplayFor(model => model.UnmatchedTutkaRecords) 66 |
67 | 68 |
69 |
70 |
71 |
72 |
73 | 74 | -------------------------------------------------------------------------------- /src/FinReconcile.Core/Domain/ReconciledItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | 6 | namespace FinReconcile.Core.Domain 7 | { 8 | public class ReconciledItem 9 | { 10 | public ReconciledItem(Transaction clientTrans,Transaction bankTrans,ReconciledMatchType matchedType) 11 | { 12 | _clientTransaction = clientTrans; 13 | _bankTransaction = bankTrans; 14 | MatchType = matchedType; 15 | } 16 | public ReconciledItem(TransactionSet transSet, ReconciledMatchType matchedType) 17 | { 18 | _resultSet = transSet; 19 | MatchType = matchedType; 20 | } 21 | public Transaction ClientTransaction 22 | { 23 | get 24 | { 25 | if (_clientTransaction != null) 26 | { 27 | return _clientTransaction; 28 | } 29 | else 30 | { 31 | if (_resultSet != null && _resultSet.ClientSet != null ) 32 | { 33 | return _resultSet.ClientSet.FirstOrDefault(); 34 | } 35 | return null; 36 | } 37 | } 38 | } 39 | public Transaction BankTransaction 40 | { 41 | get 42 | { 43 | if (_bankTransaction != null) 44 | { 45 | return _bankTransaction; 46 | } 47 | else 48 | { 49 | if (_resultSet != null && _resultSet.BankSet != null ) 50 | { 51 | return _resultSet.BankSet.FirstOrDefault(); 52 | } 53 | return null; 54 | } 55 | } 56 | } 57 | public IEnumerable GetAllClientTransactions() 58 | { 59 | if (_resultSet==null&& ClientTransaction!=null) 60 | { 61 | return new Transaction[] { ClientTransaction }; 62 | } 63 | else 64 | { 65 | return _resultSet.ClientSet; 66 | } 67 | } 68 | public IEnumerable GetAllBankTransactions() 69 | { 70 | if (_resultSet == null&& BankTransaction!=null) 71 | { 72 | return new Transaction[] { BankTransaction }; 73 | } 74 | else 75 | { 76 | return _resultSet.BankSet; 77 | } 78 | } 79 | 80 | private Transaction _clientTransaction; 81 | private Transaction _bankTransaction; 82 | private TransactionSet _resultSet; 83 | 84 | public ReconciledMatchType MatchType { get; private set; } 85 | public TransactionSet ResultSet { get { return _resultSet; } } 86 | } 87 | } -------------------------------------------------------------------------------- /src/FinReconcile.Core/Domain/ReconcileResult.cs: -------------------------------------------------------------------------------- 1 | using FinReconcile.Core.Domain.Interfaces; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Web; 6 | 7 | namespace FinReconcile.Core.Domain 8 | { 9 | public class ReconcileResult : IReconcileResult 10 | { 11 | private IEnumerable _reconciledItems; 12 | private IList _matchedItems; 13 | private IList _notMatchedItems; 14 | 15 | public ReconcileResult(IEnumerable items):this() 16 | { 17 | AddItems(items); 18 | } 19 | public ReconcileResult() 20 | { 21 | _reconciledItems = new List(); 22 | _matchedItems = new List(); 23 | _notMatchedItems = new List(); 24 | } 25 | public void AddItems(IEnumerable items) 26 | { 27 | foreach (var item in items) 28 | { 29 | switch (item.MatchType) 30 | { 31 | case ReconciledMatchType.Matched: 32 | _matchedItems.Add(item); 33 | break; 34 | case ReconciledMatchType.NotMatched: 35 | _notMatchedItems.Add(item); 36 | break; 37 | } 38 | } 39 | } 40 | public void Add(ReconciledItem item) 41 | { 42 | switch (item.MatchType) 43 | { 44 | case ReconciledMatchType.Matched: 45 | _matchedItems.Add(item); 46 | break; 47 | case ReconciledMatchType.NotMatched: 48 | _notMatchedItems.Add(item); 49 | break; 50 | } 51 | } 52 | 53 | public IList MatchedItems 54 | { 55 | get { return _matchedItems; } 56 | 57 | } 58 | public IList NotMatchedItems 59 | { 60 | get { return _notMatchedItems; } 61 | 62 | } 63 | 64 | public IList GetMatchedClientTransactions() 65 | { 66 | return _matchedItems.SelectMany(x => x.GetAllClientTransactions()).ToList(); 67 | } 68 | public IList GetMatchedBankTransactions() 69 | { 70 | return _matchedItems.SelectMany(x => x.GetAllBankTransactions()).ToList(); 71 | } 72 | public IList GetUnMatchedClientTransactions() 73 | { 74 | return _notMatchedItems.SelectMany(x => x.GetAllClientTransactions()).ToList(); 75 | } 76 | public IList GetUnMatchedBankTransactions() 77 | { 78 | return _notMatchedItems.SelectMany(x => x.GetAllBankTransactions()).ToList(); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /src/FinReconcile.Infra/FinReconcile.Infra.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {757EE3DE-BA4C-4559-B05F-066B23E36203} 8 | Library 9 | Properties 10 | FinReconcile.Infra 11 | FinReconcile.Infra 12 | v4.6.1 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | ..\..\libs\CsvHelper.dll 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | {3f2136c8-bb7c-4e46-b671-4cd545b76955} 59 | FinReconcile.Core 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/FinConcile.Web/docs/js/heirarchyBuilder.js: -------------------------------------------------------------------------------- 1 | function Directory(name) { 2 | this.Name = name; 3 | this.IsExpanded = ko.observable(false); 4 | this.features = new Array(); 5 | this.SubDirectories = new Array(); 6 | } 7 | 8 | function NavigationFeature(name, path, result) { 9 | this.Name = name; 10 | this.Path = path; 11 | this.Result = result; 12 | } 13 | 14 | function Result(data) { 15 | this.WasExecuted = data.WasExecuted || false; 16 | this.WasSuccessful = data.WasSuccessful || false; 17 | } 18 | 19 | function getFeaturesFromScenariosList(scenarios) { 20 | var features = new Array(); 21 | 22 | $.each(scenarios, function (key, val) { 23 | features.push(new NavigationFeature(val.Feature.Name, val.RelativeFolder, val.Feature.Result)); 24 | }); 25 | 26 | return features; 27 | } 28 | 29 | function createAnotherDirectory(dir, feature, pathArray, index) { 30 | var dirLookingAt = pathArray[index]; 31 | if (dirLookingAt.indexOf('.feature') > -1) { 32 | dir.features.push(feature); 33 | } else { 34 | var subDir; 35 | if (isElementInDirectoryList(dir.SubDirectories, pathArray[index])) { 36 | subDir = getElementInDirectoryList(dir.SubDirectories, pathArray[index]); 37 | } else { 38 | subDir = new Directory(dirLookingAt); 39 | dir.SubDirectories.push(subDir); 40 | } 41 | createAnotherDirectory(subDir, feature, pathArray, ++index); 42 | } 43 | } 44 | 45 | function buildFullHierarchy(paths) { 46 | var rootDir = new Directory(''); 47 | $.each(paths, function (key, value) { 48 | createAnotherDirectory(rootDir, value, splitDirectoryPathIntoArrayOfFormattedFolders(value.Path), 0); 49 | }); 50 | 51 | return rootDir; 52 | } 53 | 54 | function buildLevel(arrayOfPaths, arrayOfDirectories, level) { 55 | $.each(arrayOfPaths, function (key, dirArray) { 56 | if (!isElementInDirectoryList(arrayOfDirectories, dirArray[level])) { 57 | arrayOfDirectories.push(new Directory(dirArray[level])); 58 | } 59 | }); 60 | } 61 | 62 | function isElementInDirectoryList(list, directoryName) { 63 | return _.find(list, function (dir) { return dir.Name == directoryName; }) != null; 64 | } 65 | 66 | function getElementInDirectoryList(list, dirName) { 67 | return _.find(list, function (dir) { return dir.Name == dirName; }); 68 | }; 69 | 70 | function splitDirectoryPathIntoArrayOfFormattedFolders(path) { 71 | var paths = $.map(path.split(/[\\/]+/), function (directory) { 72 | return directory; 73 | }); 74 | 75 | $.each(paths, function (key, value) { 76 | if (value.indexOf('.feature') == -1) { 77 | paths[key] = addSpacesToCamelCasedString(value); 78 | } 79 | }); 80 | 81 | return paths; 82 | } 83 | 84 | function getFoldersWithASubdirectory(folderList) { 85 | return $.map(folderList, function (folder) { 86 | return folderHasSubdirectory(folder) ? folder : null; 87 | }); 88 | } 89 | 90 | function folderHasSubdirectory(folder) { 91 | return folder.indexOf('\\') > -1 || folder.indexOf('/') > -1; 92 | } 93 | 94 | 95 | -------------------------------------------------------------------------------- /Tests/FinConcile.Tests/RulesSteps.cs: -------------------------------------------------------------------------------- 1 | using FinConcile.Tests.TestUtils; 2 | using FinReconcile.Core.Domain; 3 | using FinReconcile.Core.Domain.Interfaces; 4 | using FinReconcile.Core.Engines; 5 | using FinReconcile.Core.Engines.Rules; 6 | using NUnit.Framework; 7 | using System; 8 | using System.Collections.Generic; 9 | using TechTalk.SpecFlow; 10 | 11 | namespace FinConcile.Tests 12 | { 13 | [Binding] 14 | public class RulesSteps 15 | { 16 | private IList _clientTransactions; 17 | private IList _bankTransactions; 18 | private IReconcileEngine _reconcileEngine; 19 | private IReconcileResult _result; 20 | private IRule rule; 21 | private RuleSet _ruleSet; 22 | private RuleSetEvaluator _evaluator; 23 | private IList _reconciledResult = new List(); 24 | 25 | [Given(@"a set of PropertyRules")] 26 | public void GivenASetOfPropertyRules(Table table) 27 | { 28 | _ruleSet = new RuleSet(); 29 | foreach (var row in table.Rows) 30 | { 31 | rule = new PropertyRule(row["SourceProperty"], row["Operator"], row["TargetProperty"]); 32 | _ruleSet.Rules.Add(rule); 33 | } 34 | } 35 | 36 | [Given(@"a DateRule with Delta\tof (.*) seconds")] 37 | public void GivenADateRuleWithDeltaOfSeconds(int delta) 38 | { 39 | _ruleSet = new RuleSet(); 40 | _ruleSet.Rules.Add(new DateRule(delta)); 41 | } 42 | 43 | 44 | [Given(@"I have a Rule")] 45 | public void GivenIHaveARule(Table table) 46 | { 47 | rule = new PropertyFuzzyMatchRule(table.Rows[0][0], table.Rows[0][1], Convert.ToInt32(table.Rows[0][2])); 48 | _ruleSet = new RuleSet(new IRule[] { rule }); 49 | } 50 | 51 | [Given(@"A list of client transactions to match")] 52 | public void GivenAListOfClientTransactions(Table table) 53 | { 54 | _clientTransactions = table.GetTransactions(); 55 | } 56 | [Given(@"a list of bank transactions slightly different descriptions")] 57 | public void GivenAListOfMatchingBankTransactions(Table table) 58 | { 59 | _bankTransactions = table.GetTransactions(); 60 | } 61 | 62 | [When(@"I call evaluate for each transactions")] 63 | public void WhenICallEvaulate() 64 | { 65 | _evaluator = new RuleSetEvaluator(_ruleSet); 66 | for (int i = 0; i < _clientTransactions.Count; i++) 67 | { 68 | _reconciledResult.Add(_evaluator.Evaluate(_clientTransactions[i], _bankTransactions[i])); 69 | } 70 | 71 | } 72 | 73 | [Then(@"the result should be matched ReconciledItems as Follows")] 74 | public void ThenTheResultShouldBeMatchedReconciledItemsAsFollows(Table table) 75 | { 76 | for (int i = 0; i < table.RowCount; i++) 77 | { 78 | Assert.AreEqual(Enum.Parse(typeof(ReconciledMatchType), table.Rows[i]["MatchType"]), _reconciledResult[i].MatchType); 79 | } 80 | } 81 | 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/FinConcile.Web/docs/css/global.css: -------------------------------------------------------------------------------- 1 | /***** Global Settings *****/ 2 | 3 | html, body { 4 | border: 0; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | body { font: 100%/1.25 Arial, Helvetica, sans-serif; } 10 | 11 | /***** Headings *****/ 12 | 13 | h1, h2, h3, h4, h5, h6 { 14 | font-weight: normal; 15 | margin: 0; 16 | padding: 0; 17 | } 18 | 19 | h1 { 20 | font-size: 2em; 21 | letter-spacing: -1px; 22 | padding: 30px 0 25px 0; 23 | } 24 | 25 | h2 { 26 | font-size: 1.5em; 27 | letter-spacing: -1px; 28 | padding: 20px 0; 29 | } 30 | 31 | h3 { 32 | font-size: 1em; 33 | font-weight: bold; 34 | } 35 | 36 | /***** Common Formatting *****/ 37 | 38 | p, ul, ol { 39 | margin: 0; 40 | padding: 0 0 1.25em 0; 41 | } 42 | 43 | ul, ol { padding: 0 0 1.25em 0; } 44 | 45 | blockquote { 46 | margin: 1.25em; 47 | padding: 1.25em 1.25em 0 1.25em; 48 | border: gray solid thin; 49 | border-left: gray solid 0.5em; 50 | } 51 | 52 | small { font-size: 0.85em; } 53 | 54 | img { border: 0; } 55 | 56 | sup { 57 | bottom: 0.3em; 58 | position: relative; 59 | vertical-align: baseline; 60 | } 61 | 62 | sub { 63 | bottom: -0.2em; 64 | position: relative; 65 | vertical-align: baseline; 66 | } 67 | 68 | acronym, abbr { 69 | border-bottom: 1px dashed; 70 | cursor: help; 71 | letter-spacing: 1px; 72 | } 73 | 74 | strong { 75 | font-weight: bold; 76 | } 77 | 78 | em { 79 | font-style: oblique; 80 | } 81 | 82 | /***** Links *****/ 83 | 84 | a, 85 | a:link, 86 | a:visited, 87 | a:hover { text-decoration: underline; } 88 | 89 | /***** Forms *****/ 90 | 91 | form { 92 | display: inline; 93 | margin: 0; 94 | padding: 0; 95 | } 96 | 97 | input, select, textarea { font: 1em Arial, Helvetica, sans-serif; } 98 | 99 | textarea { 100 | line-height: 1.25; 101 | width: 100%; 102 | } 103 | 104 | label { cursor: pointer; } 105 | 106 | /***** Tables *****/ 107 | 108 | table { 109 | border: 0; 110 | margin: 0 0 1.25em 0; 111 | padding: 0; 112 | } 113 | 114 | /***** Wrapper *****/ 115 | 116 | #wrap { 117 | margin: 0 auto; 118 | width: 960px; 119 | } 120 | 121 | /***** Global Classes *****/ 122 | 123 | .clear { clear: both; } 124 | 125 | .float-left { float: left; } 126 | 127 | .float-right { float: right; } 128 | 129 | .text-left { text-align: left; } 130 | 131 | .text-right { text-align: right; } 132 | 133 | .text-center { text-align: center; } 134 | 135 | .text-justify { text-align: justify; } 136 | 137 | .bold { font-weight: bold; } 138 | 139 | .italic { font-style: italic; } 140 | 141 | .underline { border-bottom: 1px solid; } 142 | 143 | .highlight { background: #ffc; } 144 | 145 | .wrap { 146 | margin: 0 auto; 147 | width: 960px; 148 | } 149 | 150 | .img-left { 151 | float: left; 152 | margin: 4px 10px 4px 0; 153 | } 154 | 155 | .img-right { 156 | float: right; 157 | margin: 4px 0 4px 10px; 158 | } 159 | 160 | .nopadding { padding: 0; } 161 | 162 | .noindent { 163 | margin-left: 0; 164 | padding-left: 0; 165 | } 166 | 167 | .nobullet { 168 | list-style: none; 169 | list-style-image: none; 170 | } -------------------------------------------------------------------------------- /src/FinReconcile.Core/FinReconcile.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {3F2136C8-BB7C-4E46-B671-4CD545B76955} 8 | Library 9 | Properties 10 | FinReconcile.Core 11 | FinReconcile.Core 12 | v4.6.1 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/FinConcile.Web/Views/Transaction/UnmatchedReportPartial.cshtml: -------------------------------------------------------------------------------- 1 | @model FinReconcile.Models.CompareResult 2 | 3 | 4 |
5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | @for (int i = 0; i < Model.Result.NotMatchedItems.Count; i++) 31 | { 32 | var clientTrans = Model.Result.NotMatchedItems[i].ClientTransaction; 33 | var bankTrans = Model.Result.NotMatchedItems[i].BankTransaction; 34 | 35 | @if (clientTrans != null) 36 | { 37 | 38 | 39 | 40 | } 41 | else 42 | { 43 | 44 | 45 | 46 | 47 | } 48 | @if (bankTrans != null) 49 | { 50 | 51 | 52 | 53 | 54 | } 55 | else 56 | { 57 | 58 | 59 | 60 | 61 | } 62 | 63 | } 64 | 65 |
@Model.ClientMarkOffFile@Model.BankMarkOffFile
DateAmountReferenceDateAmountReference
@clientTrans.Date.ToString()@clientTrans.Amount.ToString()@clientTrans.WalletReference@bankTrans.Date.ToString()@bankTrans.Amount.ToString()@bankTrans.WalletReference
66 |
67 | 68 |
69 | 70 | -------------------------------------------------------------------------------- /src/FinConcile.Web/Web.config: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /src/FinReconcile.Core/RuleEngine/Rules/PropertyFuzzyMatchRule.cs: -------------------------------------------------------------------------------- 1 | using FinReconcile.Core.Domain; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Web; 7 | 8 | namespace FinReconcile.Core.Engines.Rules 9 | { 10 | //this will hold the values passed the rule at runtime 11 | public class FuzzyMatchParam 12 | { 13 | public string SourceProperty { get; set; } 14 | public string TargetProperty { get; set; } 15 | public int LevenshteinDistance { get; set; } 16 | } 17 | //this is not really used but implemented to showcase the power of building a complex Rule using MethodBaseRule or IRule 18 | public class PropertyFuzzyMatchRule : MethodBaseRule 19 | { 20 | 21 | //calling the base contructor is what initialize the rule with the custom Method and parameters 22 | public PropertyFuzzyMatchRule(string sourceProperty,string targetproperty,int levenshteinDistance) :base(FuzzyMatch) 23 | { 24 | constantArgument = new FuzzyMatchParam 25 | { 26 | SourceProperty=sourceProperty, 27 | TargetProperty=targetproperty, 28 | LevenshteinDistance= levenshteinDistance 29 | 30 | }; 31 | } 32 | private static string GetValue(string propertyName, Transaction obj) 33 | { 34 | return (string)obj.GetType().GetProperty(propertyName).GetValue(obj, null); 35 | } 36 | //this method is called by the rule at runtime for matching the two transactions 37 | //you can have FuzzyMatch method implemented anywhere and initialize a rule with it 38 | public static bool FuzzyMatch(Transaction source, Transaction target, FuzzyMatchParam k) 39 | { 40 | string sourceValue = GetValue(k.SourceProperty, source); 41 | string targetValue = GetValue(k.SourceProperty, target); 42 | string text, pattern; 43 | int allowedIndex = 0; 44 | if (sourceValue.Length>=targetValue.Length) 45 | { 46 | text = sourceValue; 47 | pattern = targetValue; 48 | allowedIndex = sourceValue.Length - targetValue.Length; 49 | } 50 | else 51 | { 52 | pattern = sourceValue; 53 | text = targetValue; 54 | allowedIndex = targetValue.Length - sourceValue.Length; 55 | } 56 | int result = FuzzyMatchString(text,pattern , k.LevenshteinDistance); 57 | 58 | if (result<=allowedIndex) 59 | { 60 | return true; 61 | } 62 | return false; 63 | } 64 | private static int FuzzyMatchString(string text, string pattern, int levenshteinDistance) 65 | { 66 | int result = -1; 67 | int m = pattern.Length; 68 | int[] R; 69 | int[] patternMask = new int[128]; 70 | int i, d; 71 | 72 | if (string.IsNullOrEmpty(pattern)) return 0; 73 | if (m > 31) return -1; //Error: The pattern is too long! 74 | 75 | R = new int[(levenshteinDistance + 1) * sizeof(int)]; 76 | for (i = 0; i <= levenshteinDistance; ++i) 77 | R[i] = ~1; 78 | 79 | for (i = 0; i <= 127; ++i) 80 | patternMask[i] = ~0; 81 | 82 | for (i = 0; i < m; ++i) 83 | patternMask[pattern[i]] &= ~(1 << i); 84 | 85 | for (i = 0; i < text.Length; ++i) 86 | { 87 | int oldRd1 = R[0]; 88 | 89 | R[0] |= patternMask[text[i]]; 90 | R[0] <<= 1; 91 | 92 | for (d = 1; d <= levenshteinDistance; ++d) 93 | { 94 | int tmp = R[d]; 95 | 96 | R[d] = (oldRd1 & (R[d] | patternMask[text[i]])) << 1; 97 | oldRd1 = tmp; 98 | } 99 | 100 | if (0 == (R[levenshteinDistance] & (1 << m))) 101 | { 102 | result = (i - m) + 1; 103 | break; 104 | } 105 | } 106 | 107 | return result; 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Tests/FinConcile.Tests/CSVParserSteps.cs: -------------------------------------------------------------------------------- 1 | using FinConcile.Tests.Properties; 2 | using FinReconcile.Infra.Parsers; 3 | using NUnit.Framework; 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using System.Text; 8 | using TechTalk.SpecFlow; 9 | 10 | namespace FinConcile.Tests 11 | { 12 | [Binding] 13 | public class CSVParserSteps 14 | { 15 | IMarkOffFileParser _csvParser; 16 | string _content = string.Empty; 17 | ParserResult _parserResult; 18 | Table _contentTable; 19 | [BeforeScenario] 20 | public void InitScenario() 21 | { 22 | _csvParser = new CSVMarkOffFileParser(); 23 | } 24 | 25 | [Given(@"a markofffile\twith content")] 26 | public void GivenAMarkofffileWithContent(Table table) 27 | { 28 | _contentTable = table; 29 | StringBuilder content = new StringBuilder(); 30 | foreach (var row in table.Rows) 31 | { 32 | content.AppendLine(row[0]); 33 | } 34 | _content = content.ToString(); 35 | } 36 | 37 | [When(@"I call GetRecords")] 38 | public void WhenICallGetRecords() 39 | { 40 | _parserResult= _csvParser.GetRecords(_content); 41 | } 42 | 43 | [Then(@"the result should be (.*) transactions")] 44 | public void ThenTheResultShouldBeTransactions(int parsedTransactionsCount) 45 | { 46 | Assert.AreEqual(parsedTransactionsCount, _parserResult.Count); 47 | } 48 | 49 | [Then(@"(.*) Invalid Entries")] 50 | public void ThenInvalidEntries(int invalidEntriesCount) 51 | { 52 | Assert.AreEqual(invalidEntriesCount, _parserResult.InvalidEntries.Count); 53 | 54 | } 55 | [Then(@"Invalid entries matches line numbers (.*) and (.*)")] 56 | public void ThenInvalidEntriesMatchesLineNumbersAnd(int p0, int p1) 57 | { 58 | Assert.AreEqual(_contentTable.Rows[2][0].Trim(), _parserResult.InvalidEntries[p0].Trim()); 59 | Assert.AreEqual(_contentTable.Rows[3][0].Trim(), _parserResult.InvalidEntries[p1].Trim()); 60 | } 61 | 62 | 63 | 64 | [Given(@"a large client markofffile with content")] 65 | public void GivenALargeMarkofffileWithContent() 66 | { 67 | _content = Resources.ClientMarkoffFile20140113; 68 | } 69 | 70 | [Given(@"a large bank markofffile with content")] 71 | public void GivenALargeBankMarkofffileWithContent() 72 | { 73 | _content = Resources.BankMarkoffFile20140113; 74 | } 75 | 76 | [When(@"I call Validate")] 77 | public void WhenICallValidate() 78 | { 79 | _parserResult = _csvParser.GetRecords(_content); 80 | } 81 | 82 | [Then(@"the result should be valid file")] 83 | public void ThenTheResultShouldBeValidFile() 84 | { 85 | Assert.IsTrue(_parserResult.IsValid); 86 | } 87 | 88 | [Then(@"the result should be invalid file")] 89 | public void ThenTheResultShouldBeInvalidFile() 90 | { 91 | Assert.IsFalse(_parserResult.IsValid); 92 | Assert.AreEqual(_parserResult.Errors.Count, 1); 93 | 94 | } 95 | 96 | 97 | [Then(@"Error Message should contain headername '(.*)' with LineNo (.*)")] 98 | public void ThenErrorMessageShouldContainHeadernameWithLineNo(string headerName, int lineno) 99 | { 100 | Assert.IsTrue(_parserResult.Errors[lineno].Contains(headerName)); 101 | } 102 | 103 | [Then(@"Error Messages should contain headername '(.*)' and '(.*)' with LineNo (.*)")] 104 | public void ThenErrorMessagesShouldContainHeadernameAndWithLineNo(string header1, string header2, int linenum) 105 | { 106 | Assert.IsTrue(_parserResult.Errors[linenum].Contains(header1)); 107 | Assert.IsTrue(_parserResult.Errors[linenum].Contains(header2)); 108 | } 109 | 110 | [Then(@"with (.*) Transactions and No Errors")] 111 | public void ThenWithTransactionsAndNoErrors(int transCount) 112 | { 113 | Assert.AreEqual(transCount, _parserResult.Count); 114 | Assert.AreEqual(0, _parserResult.Errors.Count); 115 | } 116 | 117 | 118 | 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/FinReconcile.Core/ReconcileEngine/ReconcileEngine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | 6 | using System.Collections.Concurrent; 7 | 8 | using FinReconcile.Core.Domain.Interfaces; 9 | using FinReconcile.Core.Domain; 10 | 11 | namespace FinReconcile.Core.Engines 12 | { 13 | public class ReconcileEngine : IReconcileEngine 14 | { 15 | IList _ruleSetEvaulators = new List(); 16 | IDictionary _alignedTransactions= new Dictionary(); 17 | IReconcileResult _result = new ReconcileResult(); 18 | 19 | public IList RuleEvaluators { get { return _ruleSetEvaulators; } } 20 | 21 | public ReconcileEngine(IList ruleSetEvaluators) 22 | { 23 | foreach (var evaluator in ruleSetEvaluators) 24 | { 25 | _ruleSetEvaulators.Add(evaluator); 26 | } 27 | } 28 | 29 | public IReconcileResult Reconcile(IEnumerable clientTransactions, IEnumerable bankTransactions) 30 | { 31 | 32 | AlignTransactions(clientTransactions, bankTransactions); 33 | 34 | foreach (var transId in _alignedTransactions.Keys) 35 | { 36 | ReconcileTransactionSet(_alignedTransactions[transId]); 37 | } 38 | return _result; 39 | 40 | } 41 | 42 | private void AlignTransactions(IEnumerable clientTransactions, IEnumerable bankTransactions) 43 | { 44 | foreach (var clientTransaction in clientTransactions) 45 | { 46 | AlignTransaction(clientTransaction, null); 47 | } 48 | foreach (var tutkaTransaction in bankTransactions) 49 | { 50 | AlignTransaction(null, tutkaTransaction); 51 | } 52 | } 53 | 54 | private void ReconcileTransactionSet(TransactionSet transSet) 55 | { 56 | List reconciledItemList = new List(); 57 | 58 | foreach (var ruleEvaluator in _ruleSetEvaulators.Where(rule=>rule.RuleType==ReconciledMatchType.Matched)) 59 | { 60 | foreach (var cTrans in transSet.ClientSet.ToList()) 61 | { 62 | foreach (var tTrans in transSet.BankSet.ToList()) 63 | { 64 | ReconciledItem currentResult = ruleEvaluator.Evaluate(cTrans, tTrans); 65 | if (currentResult.MatchType == ReconciledMatchType.Matched) 66 | { 67 | reconciledItemList.Add(currentResult); 68 | transSet.RemoveTransactions(currentResult.ClientTransaction, currentResult.BankTransaction); 69 | } 70 | } 71 | } 72 | } 73 | 74 | if (!transSet.IsReconciled) 75 | { 76 | _result.Add(new ReconciledItem(transSet, ReconciledMatchType.NotMatched)); 77 | } 78 | 79 | _result.AddItems(reconciledItemList); 80 | 81 | 82 | } 83 | 84 | 85 | 86 | private void AlignTransaction(Transaction clientTranaction,Transaction bankTransaction) 87 | { 88 | if (clientTranaction!=null) 89 | { 90 | if (!_alignedTransactions.ContainsKey(clientTranaction.WalletReference)) 91 | { 92 | _alignedTransactions.Add(clientTranaction.WalletReference, new TransactionSet()); 93 | } 94 | _alignedTransactions[clientTranaction.WalletReference].AddClientTransaction(clientTranaction); 95 | } 96 | if (bankTransaction != null) 97 | { 98 | if (!_alignedTransactions.ContainsKey(bankTransaction.WalletReference)) 99 | { 100 | _alignedTransactions.Add(bankTransaction.WalletReference, new TransactionSet()); 101 | } 102 | _alignedTransactions[bankTransaction.WalletReference].AddBankTransaction(bankTransaction); 103 | } 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /Tests/FinConcile.Tests/ReconcileEngineSteps.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using TechTalk.SpecFlow; 3 | using FinConcile.Tests.TestUtils; 4 | using System.Collections.Generic; 5 | using NUnit.Framework; 6 | using FinReconcile.Core.Domain; 7 | using FinReconcile.Core.Engines; 8 | using FinReconcile.Core.Domain.Interfaces; 9 | using FinReconcile.Core.Engines.Rules; 10 | 11 | namespace FinConcile.Tests 12 | { 13 | [Binding] 14 | public class ReconcileEngineSteps 15 | { 16 | private IEnumerable _clientTransactions; 17 | private IEnumerable _bankTransactions; 18 | private IReconcileEngine _reconcileEngine; 19 | private IReconcileResult _result; 20 | IRule rule; 21 | private RuleSet _ruleSet; 22 | IList _ruleEvaluators = new List(); 23 | 24 | [Given(@"A list of Client transactions")] 25 | public void GivenAListOfClientTransactions(Table table) 26 | { 27 | _clientTransactions = table.GetTransactions(); 28 | } 29 | 30 | [Given(@"a list of matching Bank Transactions")] 31 | public void GivenAListOfMatchingBankTransactions(Table table) 32 | { 33 | _bankTransactions = table.GetTransactions(); 34 | } 35 | 36 | [Given(@"a list of Bank Transactions")] 37 | public void GivenAListOfBankTransactions(Table table) 38 | { 39 | _bankTransactions = table.GetTransactions(); 40 | } 41 | 42 | [Given(@"a RuleSet With PropertyRules")] 43 | public void GivenARuleSetWithPropertyRules(Table table) 44 | { 45 | _ruleSet = new RuleSet(); 46 | foreach (var row in table.Rows) 47 | { 48 | rule = new PropertyRule(row["SourceProperty"], row["Operator"], row["TargetProperty"]); 49 | _ruleSet.Rules.Add(rule); 50 | } 51 | _ruleEvaluators.Add(new RuleSetEvaluator(_ruleSet)); 52 | } 53 | 54 | 55 | [When(@"I call Reconcile")] 56 | public void WhenICallReconcile() 57 | { 58 | _reconcileEngine = new ReconcileEngine(_ruleEvaluators); 59 | _result= _reconcileEngine.Reconcile(_clientTransactions, _bankTransactions); 60 | } 61 | 62 | [Then(@"the result should be (.*) Matched ReconciledItem")] 63 | public void ThenTheResultShouldBeMatchedReconciledItem(int matchedCount) 64 | { 65 | Assert.AreEqual(matchedCount, _result.MatchedItems.Count); 66 | Assert.AreEqual(0, _result.NotMatchedItems.Count); 67 | } 68 | 69 | [Given(@"a list of Non matching Bank Transactions With Different Ids")] 70 | public void GivenAListOfNonMatchingBankTransactionsWithDifferentIds(Table table) 71 | { 72 | _bankTransactions = table.GetTransactions(); 73 | } 74 | 75 | 76 | [Then(@"the reconciled result should be (.*) Matched Client Transactions (.*) Unmatched Client transactions")] 77 | public void ThenTheReconciledResultShouldBeMatchedClientTransactionsUnmatchedClientTransactions(int matchedCount, int nonMatchedCount) 78 | { 79 | Assert.AreEqual(matchedCount, _result.GetMatchedClientTransactions().Count); 80 | Assert.AreEqual(nonMatchedCount, _result.GetUnMatchedClientTransactions().Count); 81 | } 82 | 83 | [Then(@"(.*) Matched Bank Transactions (.*) Unmatched Bank transactions")] 84 | public void ThenMatchedBankTransactionsUnmatchedBankTransactions(int matchedCount, int nonMatchedCount) 85 | { 86 | Assert.AreEqual(matchedCount, _result.GetMatchedBankTransactions().Count); 87 | Assert.AreEqual(nonMatchedCount, _result.GetUnMatchedBankTransactions().Count); 88 | } 89 | 90 | 91 | [Then(@"the result should be (.*) Matched ReconciledItems and (.*) Non Matched ReconciledItems")] 92 | public void ThenTheResultShouldBeMatchedReconciledItemsAndNonMatchedReconciledItems(int matched, int notMatched) 93 | { 94 | Assert.AreEqual(matched, _result.MatchedItems.Count); 95 | Assert.AreEqual(notMatched, _result.NotMatchedItems.Count); 96 | } 97 | 98 | [Then(@"the result should (.*) Matched ReconciledItem")] 99 | public void ThenTheResultShouldMatchedReconciledItem(int matched) 100 | { 101 | Assert.AreEqual(matched, _result.MatchedItems.Count); 102 | } 103 | 104 | 105 | 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/FinConcile.Web/docs/js/featuresModel.js: -------------------------------------------------------------------------------- 1 | function FeatureParent(data) { 2 | this.RelativeFolder = data.RelativeFolder; 3 | this.Feature = new Feature(data.Feature); 4 | } 5 | 6 | function Feature(data) { 7 | this.Name = data.Name || ''; 8 | this.Description = data.Description || ''; 9 | this.FeatureElements = $.map(data.FeatureElements, function (scenario) { return new Scenario(scenario); }) || new Array(); 10 | this.Background = data.Background == null ? null : new Background(data.Background); 11 | this.Result = data.Result == null ? null : new Result(data.Result); 12 | this.Tags = data.Tags || null; 13 | } 14 | 15 | function Scenario(data) { 16 | this.Name = data.Name || ''; 17 | this.Slug = data.Slug || ''; 18 | this.Description = data.Description || ''; 19 | this.Steps = $.map(data.Steps, function(step) { return new Step(step); }) || new Array(); 20 | this.Result = data.Result == null ? null : new Result(data.Result); 21 | this.Tags = data.Tags || null; 22 | this.Examples = data.Examples == null ? null : $.map(data.Examples, function (ex) { return new Examples(ex); }); 23 | this.IsShown = ko.observable(true); 24 | } 25 | 26 | function Step(data) { 27 | this.Name = data.Name || ''; 28 | this.Keyword = data.Keyword || ''; 29 | this.NativeKeyword = data.NativeKeyword || ''; 30 | this.DocStringArgument = data.DocStringArgument || ''; 31 | this.TableArgument = data.TableArgument == null ? null : new TableArgument(data.TableArgument.HeaderRow, data.TableArgument.DataRows); 32 | this.StepComments = data.StepComments == null ? null : $.map(data.StepComments, function (c) { return new Comment(c); }); 33 | this.AfterLastStepComments = data.AfterLastStepComments == null ? null : $.map(data.AfterLastStepComments, function (c) { return new Comment(c); }); 34 | } 35 | 36 | function Comment(data) { 37 | this.Text = data.Text || ''; 38 | } 39 | 40 | function TableArgument(headerRow, dataRows) { 41 | this.HeaderRow = headerRow || new Array(); 42 | this.DataRows = dataRows || new Array(); 43 | this.IsShown = ko.observable(true); 44 | } 45 | 46 | function Examples(data) { 47 | this.Name = data.Name || ''; 48 | this.Description = data.Description || ''; 49 | this.TableArgument = data.TableArgument == null ? null : new TableArgument(data.TableArgument.HeaderRow, data.TableArgument.DataRows); 50 | this.Tags = data.Tags || null; 51 | this.NativeKeyword = data.NativeKeyword || "Examples"; 52 | } 53 | 54 | function Background(data) { 55 | this.Name = data.Name || ''; 56 | this.Description = data.Description || ''; 57 | this.Steps = $.map(data.Steps, function(step) { return new Step(step); }) || new Array(); 58 | this.Tags = data.Tags || null; 59 | this.Result = data.Result == null ? null : new Result(data.Result); 60 | } 61 | function Result(data) { 62 | this.WasExecuted = data.WasExecuted || false; 63 | this.WasSuccessful = data.WasSuccessful || false; 64 | } 65 | 66 | /* JSON Sample 67 | { 68 | "RelativeFolder": "06CompareToAssist\\CompareTo.feature", 69 | "Feature": { 70 | "Name": "Show the compare to feature", 71 | "Description": "In order to show the compare to features of SpecFlow Assist\r\nAs a SpecFlow evanglist\r\nI want to show how the different versions of compareTo works", 72 | "FeatureElements": [ 73 | { 74 | "Name": "CompareToInstance", 75 | "Description": "", 76 | "Steps": [ 77 | { 78 | "Keyword": "Given", 79 | "NativeKeyword": "Given ", 80 | "Name": "I have the following person", 81 | "Slug": "i-have-the-following-person", 82 | "TableArgument": { 83 | "HeaderRow": [ 84 | "Field", 85 | "Value" 86 | ], 87 | "DataRows": [ 88 | [ 89 | "Name", 90 | "Marcus" 91 | ], 92 | [ 93 | "Style", 94 | "Butch" 95 | ], 96 | [ 97 | "Birth date", 98 | "1972-10-09" 99 | ] 100 | ] 101 | } 102 | }, 103 | END JSON Sample */ -------------------------------------------------------------------------------- /src/FinConcile.Web/Scripts/respond.min.js: -------------------------------------------------------------------------------- 1 | /* NUGET: BEGIN LICENSE TEXT 2 | * 3 | * Microsoft grants you the right to use these script files for the sole 4 | * purpose of either: (i) interacting through your browser with the Microsoft 5 | * website or online service, subject to the applicable licensing or use 6 | * terms; or (ii) using the files as included with a Microsoft product subject 7 | * to that product's license terms. Microsoft reserves all other rights to the 8 | * files not expressly granted by Microsoft, whether by implication, estoppel 9 | * or otherwise. Insofar as a script file is dual licensed under GPL, 10 | * Microsoft neither took the code under GPL nor distributes it thereunder but 11 | * under the terms set out in this paragraph. All notices and licenses 12 | * below are for informational purposes only. 13 | * 14 | * NUGET: END LICENSE TEXT */ 15 | /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */ 16 | /*! NOTE: If you're already including a window.matchMedia polyfill via Modernizr or otherwise, you don't need this part */ 17 | window.matchMedia=window.matchMedia||(function(e,f){var c,a=e.documentElement,b=a.firstElementChild||a.firstChild,d=e.createElement("body"),g=e.createElement("div");g.id="mq-test-1";g.style.cssText="position:absolute;top:-100em";d.style.background="none";d.appendChild(g);return function(h){g.innerHTML='­';a.insertBefore(d,b);c=g.offsetWidth==42;a.removeChild(d);return{matches:c,media:h}}})(document); 18 | 19 | /*! Respond.js v1.2.0: min/max-width media query polyfill. (c) Scott Jehl. MIT/GPLv2 Lic. j.mp/respondjs */ 20 | (function(e){e.respond={};respond.update=function(){};respond.mediaQueriesSupported=e.matchMedia&&e.matchMedia("only all").matches;if(respond.mediaQueriesSupported){return}var w=e.document,s=w.documentElement,i=[],k=[],q=[],o={},h=30,f=w.getElementsByTagName("head")[0]||s,g=w.getElementsByTagName("base")[0],b=f.getElementsByTagName("link"),d=[],a=function(){var D=b,y=D.length,B=0,A,z,C,x;for(;B-1,minw:F.match(/\(min\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:F.match(/\(max\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}}j()},l,r,v=function(){var z,A=w.createElement("div"),x=w.body,y=false;A.style.cssText="position:absolute;font-size:1em;width:1em";if(!x){x=y=w.createElement("body");x.style.background="none"}x.appendChild(A);s.insertBefore(x,s.firstChild);z=A.offsetWidth;if(y){s.removeChild(x)}else{x.removeChild(A)}z=p=parseFloat(z);return z},p,j=function(I){var x="clientWidth",B=s[x],H=w.compatMode==="CSS1Compat"&&B||w.body[x]||B,D={},G=b[b.length-1],z=(new Date()).getTime();if(I&&l&&z-l-1?(p||v()):1)}if(!!J){J=parseFloat(J)*(J.indexOf(y)>-1?(p||v()):1)}if(!K.hasquery||(!A||!L)&&(A||H>=C)&&(L||H<=J)){if(!D[K.media]){D[K.media]=[]}D[K.media].push(k[K.rules])}}for(var E in q){if(q[E]&&q[E].parentNode===f){f.removeChild(q[E])}}for(var E in D){var M=w.createElement("style"),F=D[E].join("\n");M.type="text/css";M.media=E;f.insertBefore(M,G.nextSibling);if(M.styleSheet){M.styleSheet.cssText=F}else{M.appendChild(w.createTextNode(F))}q.push(M)}},n=function(x,z){var y=c();if(!y){return}y.open("GET",x,true);y.onreadystatechange=function(){if(y.readyState!=4||y.status!=200&&y.status!=304){return}z(y.responseText)};if(y.readyState==4){return}y.send(null)},c=(function(){var x=false;try{x=new XMLHttpRequest()}catch(y){x=new ActiveXObject("Microsoft.XMLHTTP")}return function(){return x}})();a();respond.update=a;function t(){j(true)}if(e.addEventListener){e.addEventListener("resize",t,false)}else{if(e.attachEvent){e.attachEvent("onresize",t)}}})(this); -------------------------------------------------------------------------------- /src/FinConcile.Web/finreconcile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Project- Rohith Rajan 6 | 43 | 44 | 45 |
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 |
  • Clean/onion Architecture
78 | 79 |
80 |
81 |

Rule Engine

82 |
    83 |
  • is used match the transactions for reconciliation
  • 84 | 85 |
  • Wiki
  • 86 |
87 |
88 |
89 |

CI/CD Pipeline

90 | Appveyor 91 | Azure team services 92 |
93 |
94 |

Credits

95 | 103 |
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 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 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 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | } 55 | else 56 | { 57 | 58 | 59 | 60 | 61 | 62 | 63 | } 64 | @if (bankTrans != null) 65 | { 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | } 75 | else 76 | { 77 | 78 | 79 | 80 | 81 | 82 | 83 | } 84 | 85 | } 86 | 87 |
@Model.ClientMarkOffFile@Model.BankMarkOffFile
DateAmountReferenceIdDescriptionProfileNameNarrativeTypeDateAmountReferenceIdDescriptionProfileNameNarrativeType
@clientTrans.Date.ToString()@clientTrans.Amount.ToString()@clientTrans.WalletReference@clientTrans.Id@clientTrans.Description@clientTrans.ProfileName@clientTrans.Narrative@clientTrans.Type@bankTrans.Date.ToString()@bankTrans.Amount.ToString()@bankTrans.WalletReference@bankTrans.Id@bankTrans.Description@bankTrans.ProfileName@bankTrans.Narrative@bankTrans.Type
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 |
21 |

«

22 | 42 |
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 | } --------------------------------------------------------------------------------