Initital
This commit is contained in:
commit
6da5187e4b
31 changed files with 15457 additions and 0 deletions
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
################################################################################
|
||||||
|
# This .gitignore file was automatically created by Microsoft(R) Visual Studio.
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
/Schaad.Finance/bin/Debug
|
||||||
|
/Schaad.Finance/obj
|
||||||
|
*.user
|
||||||
|
/Schaad.Finance.Api/bin/Debug/netstandard2.0
|
||||||
|
/Schaad.Finance.Api/obj
|
||||||
|
/Schaad.Finance.Tests/bin/Debug/netcoreapp2.0
|
||||||
|
/Schaad.Finance.Tests/obj
|
||||||
|
/Schaad.Finance.Api/.vs/Schaad.Finances.Api
|
||||||
|
/.vs/Schaad.Finance
|
||||||
42
Schaad.Finance.Api/AccountStatements/AccountStatement.cs
Normal file
42
Schaad.Finance.Api/AccountStatements/AccountStatement.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Schaad.Finance.Api.AccountStatements
|
||||||
|
{
|
||||||
|
public class AccountStatement
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The account number (IBAN)
|
||||||
|
/// </summary>
|
||||||
|
public string AccountNumber { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Account Currency
|
||||||
|
/// </summary>
|
||||||
|
public string Currency { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The starting balance
|
||||||
|
/// </summary>
|
||||||
|
public Balance StartBalance { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The end balance
|
||||||
|
/// </summary>
|
||||||
|
public Balance EndBalance { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Summary of all credit transactions (camt)
|
||||||
|
/// </summary>
|
||||||
|
public TransactionSummary CreditTransactionSummary { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Summary of all debit transactions (camt)
|
||||||
|
/// </summary>
|
||||||
|
public TransactionSummary DebitTransactionSummary { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A list of transactions
|
||||||
|
/// </summary>
|
||||||
|
public List<Transaction> Transactions { get; set; } = new List<Transaction>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
namespace Schaad.Finance.Api.AccountStatements
|
||||||
|
{
|
||||||
|
public class AccountStatementResult
|
||||||
|
{
|
||||||
|
public readonly AccountStatement AccountStatement;
|
||||||
|
|
||||||
|
public readonly bool IsSuccess;
|
||||||
|
|
||||||
|
public readonly string Error;
|
||||||
|
|
||||||
|
public AccountStatementResult(AccountStatement accountStatement, string error)
|
||||||
|
{
|
||||||
|
AccountStatement = accountStatement;
|
||||||
|
Error = error;
|
||||||
|
IsSuccess = string.IsNullOrEmpty(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
Schaad.Finance.Api/AccountStatements/Balance.cs
Normal file
25
Schaad.Finance.Api/AccountStatements/Balance.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Schaad.Finance.Api.AccountStatements
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Detailed balance info
|
||||||
|
/// </summary>
|
||||||
|
public class Balance
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Date of balance
|
||||||
|
/// </summary>
|
||||||
|
public DateTime BookingDate { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Currency of balance
|
||||||
|
/// </summary>
|
||||||
|
public string Currency { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Value
|
||||||
|
/// </summary>
|
||||||
|
public double Value { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
57
Schaad.Finance.Api/AccountStatements/Transaction.cs
Normal file
57
Schaad.Finance.Api/AccountStatements/Transaction.cs
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
using System;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Schaad.Finance.Api.AccountStatements
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A transaction
|
||||||
|
/// </summary>
|
||||||
|
public class Transaction
|
||||||
|
{
|
||||||
|
public string Id
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
id = value;
|
||||||
|
}
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(id))
|
||||||
|
{
|
||||||
|
return GetHashString(ValueDate.ToString("yyyyMMdd") + Value + Text);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private string id;
|
||||||
|
|
||||||
|
public DateTime ValueDate { get; set; }
|
||||||
|
|
||||||
|
public DateTime BookingDate { get; set; }
|
||||||
|
|
||||||
|
public double Value { get; set; }
|
||||||
|
|
||||||
|
public string Text { get; set; }
|
||||||
|
|
||||||
|
private string GetHashString(string inputString)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
foreach (byte b in GetHash(inputString))
|
||||||
|
{
|
||||||
|
sb.Append(b.ToString("X2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] GetHash(string inputString)
|
||||||
|
{
|
||||||
|
var algorithm = MD5.Create(); //or use SHA1.Create();
|
||||||
|
return algorithm.ComputeHash(Encoding.UTF8.GetBytes(inputString));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
namespace Schaad.Finance.Api.AccountStatements
|
||||||
|
{
|
||||||
|
public class TransactionSummary
|
||||||
|
{
|
||||||
|
public int NumberOfTransaction { get; set; }
|
||||||
|
|
||||||
|
public decimal Sum { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
7
Schaad.Finance.Api/CreditCardProvider.cs
Normal file
7
Schaad.Finance.Api/CreditCardProvider.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Schaad.Finance.Api
|
||||||
|
{
|
||||||
|
public enum CreditCardProvider
|
||||||
|
{
|
||||||
|
Cembra
|
||||||
|
}
|
||||||
|
}
|
||||||
13
Schaad.Finance.Api/Datasets/CreditCardTransaction.cs
Normal file
13
Schaad.Finance.Api/Datasets/CreditCardTransaction.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Schaad.Finance.Api.Datasets
|
||||||
|
{
|
||||||
|
public class CreditCardTransaction
|
||||||
|
{
|
||||||
|
public DateTime BookingDate { get; set; }
|
||||||
|
public DateTime TransactionDate { get; set; }
|
||||||
|
public string Transaction { get; set; }
|
||||||
|
public string TransactionType { get; set; }
|
||||||
|
public decimal Amount { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Schaad.Finance.Api/IAccountStatementParsingService.cs
Normal file
12
Schaad.Finance.Api/IAccountStatementParsingService.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
using Schaad.Finance.Api.AccountStatements;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Schaad.Finance.Api
|
||||||
|
{
|
||||||
|
public interface IAccountStatementParsingService
|
||||||
|
{
|
||||||
|
List<AccountStatement> ReadFile(string filePath, Encoding encoding);
|
||||||
|
string ValidateAccountStatement(AccountStatement accountStatement);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
Schaad.Finance.Api/IAccountStatementService.cs
Normal file
11
Schaad.Finance.Api/IAccountStatementService.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
using Schaad.Finance.Api.AccountStatements;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Schaad.Finance.Api
|
||||||
|
{
|
||||||
|
public interface IAccountStatementService
|
||||||
|
{
|
||||||
|
List<AccountStatementResult> ReadFile(string filePath, Encoding encoding);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
Schaad.Finance.Api/ICreditCardStatementService.cs
Normal file
11
Schaad.Finance.Api/ICreditCardStatementService.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
using Schaad.Finance.Api.Datasets;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Schaad.Finance.Api
|
||||||
|
{
|
||||||
|
public interface ICreditCardStatementService
|
||||||
|
{
|
||||||
|
IReadOnlyList<CreditCardTransaction> ReadFile(CreditCardProvider creditCardProvider, string filePath, Encoding encoding);
|
||||||
|
}
|
||||||
|
}
|
||||||
10
Schaad.Finance.Api/IFxService.cs
Normal file
10
Schaad.Finance.Api/IFxService.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Schaad.Finance.Api
|
||||||
|
{
|
||||||
|
public interface IFxService
|
||||||
|
{
|
||||||
|
decimal ConvertCurrency(decimal amount, string fromCurrency, string toCurrency, string fixerIoApiKey);
|
||||||
|
decimal GetFxRate(string fromCurrency, string toCurrency, DateTime date, string fixerIoApiKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
Schaad.Finance.Api/Schaad.Finance.Api.csproj
Normal file
11
Schaad.Finance.Api/Schaad.Finance.Api.csproj
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="ve-3237.tmp" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
33
Schaad.Finance.Tests/AccountStatementServiceTest.cs
Normal file
33
Schaad.Finance.Tests/AccountStatementServiceTest.cs
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Schaad.Finance.Services;
|
||||||
|
|
||||||
|
namespace Schaad.Finance.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class AccountStatementServiceTest
|
||||||
|
{
|
||||||
|
private AccountStatementService service;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Startup()
|
||||||
|
{
|
||||||
|
service = new AccountStatementService();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ReadFile_MT940()
|
||||||
|
{
|
||||||
|
var statements = service.ReadFile(@"Data\mt940.sta", System.Text.Encoding.UTF8);
|
||||||
|
|
||||||
|
Assert.That(statements.TrueForAll(s => s.IsSuccess));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ReadFile_CAMT053()
|
||||||
|
{
|
||||||
|
var statements = service.ReadFile(@"Data\camt053.xml", System.Text.Encoding.UTF8);
|
||||||
|
|
||||||
|
Assert.That(statements.TrueForAll(s => s.IsSuccess));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Schaad.Finance.Tests/CreditCardStatementServiceTest.cs
Normal file
28
Schaad.Finance.Tests/CreditCardStatementServiceTest.cs
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Schaad.Finance.Api;
|
||||||
|
using Schaad.Finance.Services;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Schaad.Finance.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CreditCardStatementServiceTest
|
||||||
|
{
|
||||||
|
private CreditCardStatementService service;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Startup()
|
||||||
|
{
|
||||||
|
service = new CreditCardStatementService();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ReadFile_Cembra()
|
||||||
|
{
|
||||||
|
var transactions = service.ReadFile(CreditCardProvider.Cembra, @"Data\cembra.csv", System.Text.Encoding.UTF8);
|
||||||
|
|
||||||
|
Assert.That(transactions.Count, Is.EqualTo(4));
|
||||||
|
Assert.That(transactions.Sum(t => t.Amount), Is.EqualTo(144.60m));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
162
Schaad.Finance.Tests/Data/camt053.xml
Normal file
162
Schaad.Finance.Tests/Data/camt053.xml
Normal file
|
|
@ -0,0 +1,162 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.04">
|
||||||
|
<BkToCstmrStmt>
|
||||||
|
<GrpHdr>
|
||||||
|
<MsgId>MSG-C053-190706014611-01</MsgId>
|
||||||
|
<CreDtTm>2019-07-06T00:00:00</CreDtTm>
|
||||||
|
<MsgPgntn>
|
||||||
|
<PgNb>1</PgNb>
|
||||||
|
<LastPgInd>true</LastPgInd>
|
||||||
|
</MsgPgntn>
|
||||||
|
<AddtlInf>RECONSTRUCTION</AddtlInf>
|
||||||
|
</GrpHdr>
|
||||||
|
<Stmt>
|
||||||
|
<Id>STM-C053-190706014611-02</Id>
|
||||||
|
<ElctrncSeqNb>999999</ElctrncSeqNb>
|
||||||
|
<CreDtTm>2019-07-06T00:00:00</CreDtTm>
|
||||||
|
<FrToDt>
|
||||||
|
<FrDtTm>2019-07-01T00:00:00</FrDtTm>
|
||||||
|
<ToDtTm>2019-07-05T00:00:00</ToDtTm>
|
||||||
|
</FrToDt>
|
||||||
|
<CpyDplctInd>DUPL</CpyDplctInd>
|
||||||
|
<Acct>
|
||||||
|
<Id>
|
||||||
|
<IBAN>CH3789144975191379986</IBAN>
|
||||||
|
</Id>
|
||||||
|
<Ownr>
|
||||||
|
<Nm>Schaad Claudio</Nm>
|
||||||
|
</Ownr>
|
||||||
|
</Acct>
|
||||||
|
<Bal>
|
||||||
|
<Tp>
|
||||||
|
<CdOrPrtry>
|
||||||
|
<Cd>OPBD</Cd>
|
||||||
|
</CdOrPrtry>
|
||||||
|
</Tp>
|
||||||
|
<Amt Ccy="CHF">2468.71</Amt>
|
||||||
|
<CdtDbtInd>CRDT</CdtDbtInd>
|
||||||
|
<Dt>
|
||||||
|
<Dt>2019-07-01</Dt>
|
||||||
|
</Dt>
|
||||||
|
</Bal>
|
||||||
|
<Bal>
|
||||||
|
<Tp>
|
||||||
|
<CdOrPrtry>
|
||||||
|
<Cd>CLBD</Cd>
|
||||||
|
</CdOrPrtry>
|
||||||
|
</Tp>
|
||||||
|
<Amt Ccy="CHF">2444.01</Amt>
|
||||||
|
<CdtDbtInd>CRDT</CdtDbtInd>
|
||||||
|
<Dt>
|
||||||
|
<Dt>2019-07-05</Dt>
|
||||||
|
</Dt>
|
||||||
|
</Bal>
|
||||||
|
<TxsSummry>
|
||||||
|
<TtlDbtNtries>
|
||||||
|
<NbOfNtries>2</NbOfNtries>
|
||||||
|
<Sum>24.70</Sum>
|
||||||
|
</TtlDbtNtries>
|
||||||
|
</TxsSummry>
|
||||||
|
<Ntry>
|
||||||
|
<NtryRef>1532133113</NtryRef>
|
||||||
|
<Amt Ccy="CHF">15.45</Amt>
|
||||||
|
<CdtDbtInd>DBIT</CdtDbtInd>
|
||||||
|
<Sts>BOOK</Sts>
|
||||||
|
<BookgDt>
|
||||||
|
<Dt>2019-07-01</Dt>
|
||||||
|
</BookgDt>
|
||||||
|
<ValDt>
|
||||||
|
<Dt>2019-06-28</Dt>
|
||||||
|
</ValDt>
|
||||||
|
<BkTxCd>
|
||||||
|
<Domn>
|
||||||
|
<Cd>PMNT</Cd>
|
||||||
|
<Fmly>
|
||||||
|
<Cd>*</Cd>
|
||||||
|
<SubFmlyCd>*</SubFmlyCd>
|
||||||
|
</Fmly>
|
||||||
|
</Domn>
|
||||||
|
</BkTxCd>
|
||||||
|
<NtryDtls>
|
||||||
|
<TxDtls>
|
||||||
|
<Amt Ccy="CHF">15.45</Amt>
|
||||||
|
<CdtDbtInd>DBIT</CdtDbtInd>
|
||||||
|
<BkTxCd>
|
||||||
|
<Domn>
|
||||||
|
<Cd>PMNT</Cd>
|
||||||
|
<Fmly>
|
||||||
|
<Cd>*</Cd>
|
||||||
|
<SubFmlyCd>*</SubFmlyCd>
|
||||||
|
</Fmly>
|
||||||
|
</Domn>
|
||||||
|
</BkTxCd>
|
||||||
|
<RltdPties/>
|
||||||
|
<RmtInf>
|
||||||
|
<Ustrd>Migros M EX Sihlpass</Ustrd>
|
||||||
|
</RmtInf>
|
||||||
|
<AddtlTxInf>Warenbezug und Dienstleistungen
|
||||||
|
Migros M EX Sihlpass
|
||||||
|
Maestro Karte
|
||||||
|
Kartennummer xxx
|
||||||
|
28.06.2019 18:43</AddtlTxInf>
|
||||||
|
</TxDtls>
|
||||||
|
</NtryDtls>
|
||||||
|
<AddtlNtryInf>Warenbezug und Dienstleistungen
|
||||||
|
Migros M EX Sihlpass
|
||||||
|
Maestro Karte
|
||||||
|
Kartennummer xxx
|
||||||
|
28.06.2019 18:43</AddtlNtryInf>
|
||||||
|
</Ntry>
|
||||||
|
<Ntry>
|
||||||
|
<NtryRef>1531481701</NtryRef>
|
||||||
|
<Amt Ccy="CHF">9.25</Amt>
|
||||||
|
<CdtDbtInd>DBIT</CdtDbtInd>
|
||||||
|
<Sts>BOOK</Sts>
|
||||||
|
<BookgDt>
|
||||||
|
<Dt>2019-07-01</Dt>
|
||||||
|
</BookgDt>
|
||||||
|
<ValDt>
|
||||||
|
<Dt>2019-06-28</Dt>
|
||||||
|
</ValDt>
|
||||||
|
<BkTxCd>
|
||||||
|
<Domn>
|
||||||
|
<Cd>PMNT</Cd>
|
||||||
|
<Fmly>
|
||||||
|
<Cd>*</Cd>
|
||||||
|
<SubFmlyCd>*</SubFmlyCd>
|
||||||
|
</Fmly>
|
||||||
|
</Domn>
|
||||||
|
</BkTxCd>
|
||||||
|
<NtryDtls>
|
||||||
|
<TxDtls>
|
||||||
|
<Amt Ccy="CHF">9.25</Amt>
|
||||||
|
<CdtDbtInd>DBIT</CdtDbtInd>
|
||||||
|
<BkTxCd>
|
||||||
|
<Domn>
|
||||||
|
<Cd>PMNT</Cd>
|
||||||
|
<Fmly>
|
||||||
|
<Cd>*</Cd>
|
||||||
|
<SubFmlyCd>*</SubFmlyCd>
|
||||||
|
</Fmly>
|
||||||
|
</Domn>
|
||||||
|
</BkTxCd>
|
||||||
|
<RltdPties/>
|
||||||
|
<RmtInf>
|
||||||
|
<Ustrd>Coop-5013 ZH Maagpla</Ustrd>
|
||||||
|
</RmtInf>
|
||||||
|
<AddtlTxInf>Warenbezug und Dienstleistungen
|
||||||
|
Coop-5013 ZH Maagpla
|
||||||
|
Maestro Karte
|
||||||
|
Kartennummer xxx
|
||||||
|
28.06.2019 06:49</AddtlTxInf>
|
||||||
|
</TxDtls>
|
||||||
|
</NtryDtls>
|
||||||
|
<AddtlNtryInf>Warenbezug und Dienstleistungen
|
||||||
|
Coop-5013 ZH Maagpla
|
||||||
|
Maestro Karte
|
||||||
|
Kartennummer xxx
|
||||||
|
28.06.2019 06:49</AddtlNtryInf>
|
||||||
|
</Ntry>
|
||||||
|
</Stmt>
|
||||||
|
</BkToCstmrStmt>
|
||||||
|
</Document>
|
||||||
5
Schaad.Finance.Tests/Data/cembra.csv
Normal file
5
Schaad.Finance.Tests/Data/cembra.csv
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
Booking Date;Transaction Date;;Transaction;Type;Amount (CHF);
|
||||||
|
05.07.2019;05.07.2019;;MIGROS WEINFELDEN;Debit;37.00;
|
||||||
|
04.07.2019;04.07.2019;;MIGROS WEINFELDEN;Debit;46.95;
|
||||||
|
02.07.2019;02.07.2019;;MIGROS WEINFELDEN;Debit;47.75;
|
||||||
|
02.07.2019;02.07.2019;;ITUNES.COM/BILL 0800001853 IRL;Debit;12.90;
|
||||||
|
47
Schaad.Finance.Tests/Data/mt940.sta
Normal file
47
Schaad.Finance.Tests/Data/mt940.sta
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
|
||||||
|
:20:STARTUMS
|
||||||
|
:25:123456789
|
||||||
|
:28:00001
|
||||||
|
:NS:22 Schaad Claudio
|
||||||
|
23Privatkonto
|
||||||
|
3000000784
|
||||||
|
314448809
|
||||||
|
32000
|
||||||
|
:60F:C190701CHF000000002468,71
|
||||||
|
:61:1906280701D000000000015,45FMSCNONREF
|
||||||
|
:NS:01Warenbezug und Dienstleistungen
|
||||||
|
02Migros M EX Sihlpass
|
||||||
|
03Maestro Karte
|
||||||
|
04Kartennummer xxx
|
||||||
|
0528.06.2019 18:43
|
||||||
|
06
|
||||||
|
07
|
||||||
|
08
|
||||||
|
09
|
||||||
|
10
|
||||||
|
11
|
||||||
|
12
|
||||||
|
13
|
||||||
|
14
|
||||||
|
15
|
||||||
|
16
|
||||||
|
17Warenbezug und Dienstleistungen
|
||||||
|
:61:1906280701D000000000009,25FMSCNONREF
|
||||||
|
:NS:01Warenbezug und Dienstleistungen
|
||||||
|
02Coop-5013 ZH Maagpla
|
||||||
|
03Maestro Karte
|
||||||
|
04Kartennummer xxx
|
||||||
|
0528.06.2019 06:49
|
||||||
|
06
|
||||||
|
07
|
||||||
|
08
|
||||||
|
09
|
||||||
|
10
|
||||||
|
11
|
||||||
|
12
|
||||||
|
13
|
||||||
|
14
|
||||||
|
15
|
||||||
|
16
|
||||||
|
17Warenbezug und Dienstleistungen
|
||||||
|
:62F:C190705CHF000000002444,01
|
||||||
35
Schaad.Finance.Tests/FxServiceTest.cs
Normal file
35
Schaad.Finance.Tests/FxServiceTest.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Schaad.Finance.Services;
|
||||||
|
|
||||||
|
namespace Schaad.Finance.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class FxServiceTest
|
||||||
|
{
|
||||||
|
private FxService service;
|
||||||
|
|
||||||
|
private const string FIXERIO_API_KEY = "<insert fixer io api key here>";
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Startup()
|
||||||
|
{
|
||||||
|
service = new FxService();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void GetFxRate_EUR2CHF()
|
||||||
|
{
|
||||||
|
var fxRate = service.GetFxRate("EUR", "CHF", new System.DateTime(2019, 07, 01), FIXERIO_API_KEY);
|
||||||
|
|
||||||
|
Assert.That(fxRate, Is.EqualTo(1.114262m));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ConvertCurrency_100EUR2CHF()
|
||||||
|
{
|
||||||
|
var amount = service.ConvertCurrency(100, "EUR", "CHF", FIXERIO_API_KEY);
|
||||||
|
|
||||||
|
Assert.That(amount, Is.GreaterThan(100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
46
Schaad.Finance.Tests/Schaad.Finance.Tests.csproj
Normal file
46
Schaad.Finance.Tests/Schaad.Finance.Tests.csproj
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||||
|
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
||||||
|
<PackageReference Include="MSTest.TestAdapter" Version="1.4.0" />
|
||||||
|
<PackageReference Include="MSTest.TestFramework" Version="1.4.0" />
|
||||||
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
|
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Schaad.Finance\Schaad.Finance.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Data\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Schaad.Web">
|
||||||
|
<HintPath>..\dlls\Schaad.Web.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Schaad.Web.Api">
|
||||||
|
<HintPath>..\dlls\Schaad.Web.Api.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="Data\camt053.xml">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Data\cembra.csv">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Data\mt940.sta">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
37
Schaad.Finance.sln
Normal file
37
Schaad.Finance.sln
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio 15
|
||||||
|
VisualStudioVersion = 15.0.27703.2035
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Schaad.Finance.Api", "Schaad.Finance.Api\Schaad.Finance.Api.csproj", "{C5845C75-0772-4819-B0DB-D57B5AF8921D}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Schaad.Finance", "Schaad.Finance\Schaad.Finance.csproj", "{4BCEDAF9-FE9B-49D2-9CAF-A7B06B4FF521}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Schaad.Finance.Tests", "Schaad.Finance.Tests\Schaad.Finance.Tests.csproj", "{C3A9FC28-9CF7-4775-90B7-B9F29DC299FD}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{C5845C75-0772-4819-B0DB-D57B5AF8921D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C5845C75-0772-4819-B0DB-D57B5AF8921D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C5845C75-0772-4819-B0DB-D57B5AF8921D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{C5845C75-0772-4819-B0DB-D57B5AF8921D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{4BCEDAF9-FE9B-49D2-9CAF-A7B06B4FF521}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{4BCEDAF9-FE9B-49D2-9CAF-A7B06B4FF521}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{4BCEDAF9-FE9B-49D2-9CAF-A7B06B4FF521}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{4BCEDAF9-FE9B-49D2-9CAF-A7B06B4FF521}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{C3A9FC28-9CF7-4775-90B7-B9F29DC299FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C3A9FC28-9CF7-4775-90B7-B9F29DC299FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C3A9FC28-9CF7-4775-90B7-B9F29DC299FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{C3A9FC28-9CF7-4775-90B7-B9F29DC299FD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {38CCE775-5C10-4F00-BA79-EB86BE0BB2B5}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
164
Schaad.Finance/Formats/AccountStatements/CAMT053.cs
Normal file
164
Schaad.Finance/Formats/AccountStatements/CAMT053.cs
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Xml.Serialization;
|
||||||
|
using Schaad.Finance.Api;
|
||||||
|
using Schaad.Finance.Api.AccountStatements;
|
||||||
|
using Schaad.Finance.Formats.AccountStatements.Models;
|
||||||
|
|
||||||
|
namespace Schaad.Finance.Formats.AccountStatements
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Reads CAMT053 format
|
||||||
|
/// https://www.six-interbank-clearing.com/dam/downloads/de/standardization/iso/swiss-recommendations/implementation-guidelines-camt.pdf
|
||||||
|
/// </summary>
|
||||||
|
public class CAMT053 : IAccountStatementParsingService
|
||||||
|
{
|
||||||
|
public List<AccountStatement> ReadFile(string filePath, Encoding encoding)
|
||||||
|
{
|
||||||
|
var accountStatements = new List<AccountStatement>();
|
||||||
|
var serializer = new XmlSerializer(typeof(Document));
|
||||||
|
using (var fileStream = new FileStream(filePath, FileMode.Open))
|
||||||
|
{
|
||||||
|
using (var reader = new StreamReader(fileStream, encoding))
|
||||||
|
{
|
||||||
|
var document = (Document)serializer.Deserialize(reader);
|
||||||
|
foreach (var stmt in document.BkToCstmrStmt.Stmt)
|
||||||
|
{
|
||||||
|
var accountStatement = new AccountStatement();
|
||||||
|
accountStatements.Add(accountStatement);
|
||||||
|
|
||||||
|
accountStatement.AccountNumber = stmt.Acct.Id.Item.ToString();
|
||||||
|
accountStatement.Currency = stmt.Acct.Ccy;
|
||||||
|
|
||||||
|
foreach (var balance in stmt.Bal)
|
||||||
|
{
|
||||||
|
// var balanceAmount = balance.Amt.Value;
|
||||||
|
// var balanceCurrency = balance.Amt.Ccy;
|
||||||
|
// var balanceDate = balance.Dt.Item;
|
||||||
|
// var creditDebit = balance.CdtDbtInd;
|
||||||
|
|
||||||
|
var balanceType = (BalanceType12Code)balance.Tp.CdOrPrtry.Item;
|
||||||
|
if (balanceType == BalanceType12Code.OPBD)
|
||||||
|
{
|
||||||
|
accountStatement.StartBalance = new Balance
|
||||||
|
{
|
||||||
|
BookingDate = balance.Dt.Item,
|
||||||
|
Value = (double)balance.Amt.Value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (balanceType == BalanceType12Code.CLBD)
|
||||||
|
{
|
||||||
|
accountStatement.EndBalance = new Balance
|
||||||
|
{
|
||||||
|
BookingDate = balance.Dt.Item,
|
||||||
|
Value = (double)balance.Amt.Value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stmt.TxsSummry != null)
|
||||||
|
{
|
||||||
|
if (stmt.TxsSummry.TtlCdtNtries != null)
|
||||||
|
{
|
||||||
|
accountStatement.CreditTransactionSummary = new TransactionSummary
|
||||||
|
{
|
||||||
|
NumberOfTransaction = int.Parse(stmt.TxsSummry.TtlCdtNtries.NbOfNtries),
|
||||||
|
Sum = stmt.TxsSummry.TtlCdtNtries.Sum
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
accountStatement.CreditTransactionSummary = new TransactionSummary();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stmt.TxsSummry.TtlDbtNtries != null)
|
||||||
|
{
|
||||||
|
accountStatement.DebitTransactionSummary = new TransactionSummary
|
||||||
|
{
|
||||||
|
NumberOfTransaction = int.Parse(stmt.TxsSummry.TtlDbtNtries.NbOfNtries),
|
||||||
|
Sum = stmt.TxsSummry.TtlDbtNtries.Sum
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
accountStatement.DebitTransactionSummary = new TransactionSummary();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
accountStatement.Transactions = new List<Transaction>();
|
||||||
|
if (stmt.Ntry != null)
|
||||||
|
{
|
||||||
|
foreach (var entry in stmt.Ntry)
|
||||||
|
{
|
||||||
|
var transaction = new Transaction();
|
||||||
|
accountStatement.Transactions.Add(transaction);
|
||||||
|
|
||||||
|
// var amount = entry.Amt.Value;
|
||||||
|
// var currency = entry.Amt.Ccy;
|
||||||
|
// var valueDate = entry.ValDt.Item;
|
||||||
|
// var bookingDate = entry.BookgDt.Item;
|
||||||
|
//var creditDepit = entry.CdtDbtInd;
|
||||||
|
// var trxId = entry.NtryRef;
|
||||||
|
// var text = entry.AddtlNtryInf;
|
||||||
|
|
||||||
|
transaction.Id = stmt.Id + entry.NtryRef;
|
||||||
|
transaction.BookingDate = entry.BookgDt.Item;
|
||||||
|
transaction.Text = entry.AddtlNtryInf;
|
||||||
|
transaction.Value = (double)entry.Amt.Value;
|
||||||
|
transaction.ValueDate = entry.ValDt.Item;
|
||||||
|
|
||||||
|
if (entry.CdtDbtInd == CreditDebitCode.DBIT)
|
||||||
|
{
|
||||||
|
transaction.Value *= -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return accountStatements;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ValidateAccountStatement(AccountStatement accountStatement)
|
||||||
|
{
|
||||||
|
if (accountStatement.StartBalance.Currency != accountStatement.EndBalance.Currency)
|
||||||
|
{
|
||||||
|
return $"Startbalance currency ({accountStatement.StartBalance.Currency}) does not match endbalance currency ({accountStatement.EndBalance.Currency})";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accountStatement.CreditTransactionSummary != null && accountStatement.DebitTransactionSummary != null)
|
||||||
|
{
|
||||||
|
var transactionSum = Math.Round(accountStatement.Transactions.Sum(t => (decimal)t.Value), 2);
|
||||||
|
var totalNumber = accountStatement.Transactions.Count;
|
||||||
|
|
||||||
|
var transactionSumValidation = accountStatement.CreditTransactionSummary.Sum - accountStatement.DebitTransactionSummary.Sum;
|
||||||
|
var totalNumberValidation = accountStatement.CreditTransactionSummary.NumberOfTransaction + accountStatement.DebitTransactionSummary.NumberOfTransaction;
|
||||||
|
|
||||||
|
if (transactionSum != transactionSumValidation)
|
||||||
|
{
|
||||||
|
return $"Transaction sum ({transactionSum}) does not match validation sum ({transactionSumValidation})";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalNumber != totalNumberValidation)
|
||||||
|
{
|
||||||
|
return $"Transaction count ({totalNumber}) does not match validation count ({totalNumberValidation})";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (accountStatement.StartBalance != null && accountStatement.EndBalance != null)
|
||||||
|
{
|
||||||
|
var transactionSum = Math.Round(accountStatement.Transactions.Sum(t => t.Value), 2);
|
||||||
|
var end = accountStatement.StartBalance.Value + transactionSum;
|
||||||
|
if (end != accountStatement.EndBalance.Value)
|
||||||
|
{
|
||||||
|
return $"Enbalance ({accountStatement.EndBalance.Value}) does not match startbalance ({accountStatement.StartBalance.Value}) + transactions ({transactionSum})";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
195
Schaad.Finance/Formats/AccountStatements/MT940.cs
Normal file
195
Schaad.Finance/Formats/AccountStatements/MT940.cs
Normal file
|
|
@ -0,0 +1,195 @@
|
||||||
|
using Schaad.Finance.Api;
|
||||||
|
using Schaad.Finance.Api.AccountStatements;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Schaad.Finance.Formats.AccountStatements
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Reads MT940 format
|
||||||
|
/// http://www.kontopruef.de/mt940s.shtml
|
||||||
|
/// </summary>
|
||||||
|
public class MT940 : IAccountStatementParsingService
|
||||||
|
{
|
||||||
|
public List<AccountStatement> ReadFile(string filePath, Encoding encoding)
|
||||||
|
{
|
||||||
|
var accountList = new List<AccountStatement>();
|
||||||
|
AccountStatement account = null;
|
||||||
|
Transaction currentTransaction = null;
|
||||||
|
List<string> transactionTexts = null;
|
||||||
|
foreach (var line in File.ReadLines(filePath, encoding))
|
||||||
|
{
|
||||||
|
if (line.Length < 3)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var codeValue = ParseLine(line);
|
||||||
|
switch (codeValue.Item1)
|
||||||
|
{
|
||||||
|
// Account
|
||||||
|
case ":25:":
|
||||||
|
account = new AccountStatement();
|
||||||
|
accountList.Add(account);
|
||||||
|
account.AccountNumber = codeValue.Item2;
|
||||||
|
break;
|
||||||
|
// Start balance
|
||||||
|
case ":60F:":
|
||||||
|
account.StartBalance = ParseBalance(codeValue.Item2);
|
||||||
|
break;
|
||||||
|
// End balance
|
||||||
|
case ":62F:":
|
||||||
|
account.EndBalance = ParseBalance(codeValue.Item2);
|
||||||
|
break;
|
||||||
|
// Transaction
|
||||||
|
case ":61:":
|
||||||
|
// have we an old transaction
|
||||||
|
if (currentTransaction != null && transactionTexts.Any())
|
||||||
|
{
|
||||||
|
currentTransaction.Text = string.Join(", ", transactionTexts);
|
||||||
|
}
|
||||||
|
transactionTexts = new List<string>();
|
||||||
|
currentTransaction = ParseTransaction(codeValue.Item2);
|
||||||
|
account.Transactions.Add(currentTransaction);
|
||||||
|
break;
|
||||||
|
// Transaction text
|
||||||
|
case ":NS:":
|
||||||
|
//if (currentTransaction != null) currentTransaction.Text = codeValue.Item2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (codeValue.Item1.IndexOf(':') == -1 && currentTransaction != null)
|
||||||
|
{
|
||||||
|
if (transactionTexts.Contains(codeValue.Item2) == false)
|
||||||
|
{
|
||||||
|
transactionTexts.Add(codeValue.Item2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// have we an old transaction
|
||||||
|
if (currentTransaction != null && transactionTexts.Any())
|
||||||
|
{
|
||||||
|
currentTransaction.Text = string.Join(", ", transactionTexts);
|
||||||
|
}
|
||||||
|
|
||||||
|
return accountList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ValidateAccountStatement(AccountStatement accountStatement)
|
||||||
|
{
|
||||||
|
if (accountStatement.StartBalance.Currency != accountStatement.EndBalance.Currency)
|
||||||
|
{
|
||||||
|
return $"Startbalance currency ({accountStatement.StartBalance.Currency}) does not match endbalance currency ({accountStatement.EndBalance.Currency})";
|
||||||
|
}
|
||||||
|
|
||||||
|
var transactionSum = Math.Round(accountStatement.Transactions.Sum(t => t.Value), 2);
|
||||||
|
var end = accountStatement.StartBalance.Value + transactionSum;
|
||||||
|
if (end != accountStatement.EndBalance.Value)
|
||||||
|
{
|
||||||
|
return $"Enbalance ({accountStatement.EndBalance.Value}) does not match startbalance ({accountStatement.StartBalance.Value}) + transactions ({transactionSum})";
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse a line
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="line"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private Tuple<string, string> ParseLine(string line)
|
||||||
|
{
|
||||||
|
if (line.StartsWith(":"))
|
||||||
|
{
|
||||||
|
var pos = line.IndexOf(':', 1);
|
||||||
|
var code = line.Substring(0, pos + 1);
|
||||||
|
return new Tuple<string, string>(code, line.Substring(code.Length));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return new Tuple<string, string>(line.Substring(0, 2), line.Substring(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse a balance
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private Balance ParseBalance(string value)
|
||||||
|
{
|
||||||
|
var balance = new Balance();
|
||||||
|
|
||||||
|
bool isDebit = false;
|
||||||
|
if (value.First() == 'C')
|
||||||
|
isDebit = false;
|
||||||
|
else if (value.First() == 'D')
|
||||||
|
isDebit = true;
|
||||||
|
value = value.Remove(0, 1);
|
||||||
|
|
||||||
|
balance.BookingDate = ParseDate(value.Substring(0, 6));
|
||||||
|
value = value.Remove(0, 6);
|
||||||
|
|
||||||
|
balance.Currency = value.Substring(0, 3);
|
||||||
|
value = value.Remove(0, 3);
|
||||||
|
|
||||||
|
balance.Value = ParseBalanceValue(value);
|
||||||
|
|
||||||
|
if (isDebit)
|
||||||
|
balance.Value *= -1.0;
|
||||||
|
|
||||||
|
return balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double ParseBalanceValue(string balanceValue)
|
||||||
|
{
|
||||||
|
return Double.Parse(balanceValue.Replace(',', '.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse a transaction
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private Transaction ParseTransaction(string value)
|
||||||
|
{
|
||||||
|
var transaction = new Transaction();
|
||||||
|
|
||||||
|
transaction.ValueDate = ParseDate(value.Substring(0, 6));
|
||||||
|
value = value.Remove(0, 6);
|
||||||
|
|
||||||
|
string year = (transaction.ValueDate.Year - 2000).ToString();
|
||||||
|
transaction.BookingDate = ParseDate(year + value.Substring(0, 4));
|
||||||
|
value = value.Remove(0, 4);
|
||||||
|
|
||||||
|
bool isDebit = false;
|
||||||
|
if (value.First() == 'C')
|
||||||
|
isDebit = false;
|
||||||
|
else if (value.First() == 'D')
|
||||||
|
isDebit = true;
|
||||||
|
else
|
||||||
|
throw new NotSupportedException("Code not supported: " + value.First());
|
||||||
|
value = value.Remove(0, 1);
|
||||||
|
|
||||||
|
transaction.Value = ParseBalanceValue(value.Substring(0, 15));
|
||||||
|
value = value.Remove(0, 15);
|
||||||
|
|
||||||
|
if (isDebit)
|
||||||
|
transaction.Value *= -1.0;
|
||||||
|
|
||||||
|
return transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse a date in format: JJMMTT
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dateValue"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private DateTime ParseDate(string dateValue)
|
||||||
|
{
|
||||||
|
if (dateValue.Length != 6)
|
||||||
|
throw new Exception("Wrong format for date. Need: JJMMTT");
|
||||||
|
return new DateTime(2000 + Int32.Parse(dateValue.Substring(0, 2)), Int32.Parse(dateValue.Substring(2, 2)), Int32.Parse(dateValue.Substring(4, 2)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11206
Schaad.Finance/Formats/AccountStatements/Models/camt_053_001_04.cs
Normal file
11206
Schaad.Finance/Formats/AccountStatements/Models/camt_053_001_04.cs
Normal file
File diff suppressed because it is too large
Load diff
1298
Schaad.Finance/Formats/AccountStatements/camt.053.001.02.xsd
Normal file
1298
Schaad.Finance/Formats/AccountStatements/camt.053.001.02.xsd
Normal file
File diff suppressed because it is too large
Load diff
1764
Schaad.Finance/Formats/AccountStatements/camt.053.001.04.xsd
Normal file
1764
Schaad.Finance/Formats/AccountStatements/camt.053.001.04.xsd
Normal file
File diff suppressed because it is too large
Load diff
17
Schaad.Finance/Formats/CreditCardProvider/CembraMap.cs
Normal file
17
Schaad.Finance/Formats/CreditCardProvider/CembraMap.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
using CsvHelper.Configuration;
|
||||||
|
using Schaad.Finance.Api.Datasets;
|
||||||
|
|
||||||
|
namespace Schaad.Finance.Formats.CreditCardProvider
|
||||||
|
{
|
||||||
|
class CembraMap : ClassMap<CreditCardTransaction>
|
||||||
|
{
|
||||||
|
public CembraMap()
|
||||||
|
{
|
||||||
|
Map(m => m.BookingDate).Name("Booking Date");
|
||||||
|
Map(m => m.TransactionDate).Name("Transaction Date");
|
||||||
|
Map(m => m.Transaction).Name("Transaction");
|
||||||
|
Map(m => m.TransactionType).Name("Type");
|
||||||
|
Map(m => m.Amount).Name("Amount (CHF)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
Schaad.Finance/Schaad.Finance.csproj
Normal file
17
Schaad.Finance/Schaad.Finance.csproj
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="CsvHelper" Version="12.1.2" />
|
||||||
|
<PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
||||||
|
<PackageReference Include="System.Xml.XmlDocument" Version="4.3.0" />
|
||||||
|
<PackageReference Include="System.Xml.XmlSerializer" Version="4.3.0" />
|
||||||
|
<PackageReference Include="System.Runtime.Serialization.Formatters" Version="4.3.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Schaad.Finance.Api\Schaad.Finance.Api.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
44
Schaad.Finance/Services/AccountStatementService.cs
Normal file
44
Schaad.Finance/Services/AccountStatementService.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Schaad.Finance.Api;
|
||||||
|
using Schaad.Finance.Api.AccountStatements;
|
||||||
|
using Schaad.Finance.Formats.AccountStatements;
|
||||||
|
|
||||||
|
namespace Schaad.Finance.Services
|
||||||
|
{
|
||||||
|
public class AccountStatementService : IAccountStatementService
|
||||||
|
{
|
||||||
|
public List<AccountStatementResult> ReadFile(string filePath, Encoding encoding)
|
||||||
|
{
|
||||||
|
IAccountStatementParsingService accountStatementParsingService = null;
|
||||||
|
var extension = Path.GetExtension(filePath).ToLower();
|
||||||
|
switch (extension)
|
||||||
|
{
|
||||||
|
case ".xml":
|
||||||
|
accountStatementParsingService = new CAMT053() as IAccountStatementParsingService;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ".sta":
|
||||||
|
accountStatementParsingService = new MT940() as IAccountStatementParsingService;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new NotImplementedException($"Unknwon ending {extension}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var accountStatementResults = new List<AccountStatementResult>();
|
||||||
|
var accountStatements = accountStatementParsingService.ReadFile(filePath, encoding);
|
||||||
|
foreach (var accountStatement in accountStatements)
|
||||||
|
{
|
||||||
|
var error = accountStatementParsingService.ValidateAccountStatement(accountStatement);
|
||||||
|
var accountStatementResult = new AccountStatementResult(accountStatement, error);
|
||||||
|
accountStatementResults.Add(accountStatementResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
return accountStatementResults;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
Schaad.Finance/Services/CreditCardStatementService.cs
Normal file
39
Schaad.Finance/Services/CreditCardStatementService.cs
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using CsvHelper;
|
||||||
|
using Schaad.Finance.Api;
|
||||||
|
using Schaad.Finance.Api.Datasets;
|
||||||
|
|
||||||
|
namespace Schaad.Finance.Services
|
||||||
|
{
|
||||||
|
public class CreditCardStatementService : ICreditCardStatementService
|
||||||
|
{
|
||||||
|
public IReadOnlyList<CreditCardTransaction> ReadFile(CreditCardProvider creditCardProvider, string filePath, Encoding encoding)
|
||||||
|
{
|
||||||
|
switch (creditCardProvider)
|
||||||
|
{
|
||||||
|
case CreditCardProvider.Cembra:
|
||||||
|
return ParseCembra(filePath, encoding);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new NotImplementedException($"Unnknown procider {creditCardProvider}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IReadOnlyList<CreditCardTransaction> ParseCembra(string filePath, Encoding encoding)
|
||||||
|
{
|
||||||
|
using (var reader = new StreamReader(filePath, encoding))
|
||||||
|
{
|
||||||
|
using (var csv = new CsvReader(reader))
|
||||||
|
{
|
||||||
|
csv.Configuration.RegisterClassMap<Formats.CreditCardProvider.CembraMap>();
|
||||||
|
var records = csv.GetRecords<CreditCardTransaction>();
|
||||||
|
return records.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
81
Schaad.Finance/Services/FxService.cs
Normal file
81
Schaad.Finance/Services/FxService.cs
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Schaad.Finance.Api;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace Schaad.Finance.Services
|
||||||
|
{
|
||||||
|
public class FxService : IFxService
|
||||||
|
{
|
||||||
|
private Dictionary<string, decimal> fxRateCache;
|
||||||
|
|
||||||
|
public FxService()
|
||||||
|
{
|
||||||
|
fxRateCache = new Dictionary<string, decimal>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public decimal ConvertCurrency(decimal amount, string fromCurrency, string toCurrency, string fixerIoApiKey)
|
||||||
|
{
|
||||||
|
if (fromCurrency == toCurrency)
|
||||||
|
{
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fxRate = GetFxRate(fromCurrency, toCurrency, DateTime.Now, fixerIoApiKey);
|
||||||
|
return Math.Round(fxRate * amount, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public decimal GetFxRate(string fromCurrency, string toCurrency, DateTime date, string fixerIoApiKey)
|
||||||
|
{
|
||||||
|
if (fromCurrency == toCurrency)
|
||||||
|
{
|
||||||
|
return 1.0M;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = GetKey(fromCurrency, toCurrency, date);
|
||||||
|
if (fxRateCache.ContainsKey(key) == false)
|
||||||
|
{
|
||||||
|
// Base Currency ist EUR
|
||||||
|
var euroToFrom = GetFx(GetUrl(fromCurrency, date, fixerIoApiKey), fromCurrency);
|
||||||
|
var euroToTo = GetFx(GetUrl(toCurrency, date, fixerIoApiKey), toCurrency);
|
||||||
|
|
||||||
|
fxRateCache[key] = euroToTo / euroToFrom;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fxRateCache[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetUrl(string currency, DateTime date, string fixerIoApiKey)
|
||||||
|
{
|
||||||
|
var url = $"http://data.fixer.io/api/{date:yyyy-MM-dd}?access_key={fixerIoApiKey}&symbols={currency}";
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
private decimal GetFx(string url, string currency)
|
||||||
|
{
|
||||||
|
var response = HttpGet(url);
|
||||||
|
dynamic stuff = JObject.Parse(response);
|
||||||
|
return stuff.rates[currency];
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetKey(string fromCurrency, string toCurrency, DateTime date)
|
||||||
|
{
|
||||||
|
return $"{fromCurrency}_{toCurrency}_{date:yyyyMMdd}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private string HttpGet(string url)
|
||||||
|
{
|
||||||
|
var request = (HttpWebRequest)WebRequest.Create(url);
|
||||||
|
var response = request.GetResponseAsync().Result;
|
||||||
|
string myResponse = "";
|
||||||
|
using (var sr = new System.IO.StreamReader(response.GetResponseStream()))
|
||||||
|
{
|
||||||
|
myResponse = sr.ReadToEnd();
|
||||||
|
return myResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Loading…
Reference in a new issue