Added cembra pdf statement parser
This commit is contained in:
parent
445d5d9b74
commit
6b5aefd70b
7 changed files with 194 additions and 17 deletions
|
|
@ -2,6 +2,7 @@
|
||||||
{
|
{
|
||||||
public enum CreditCardProvider
|
public enum CreditCardProvider
|
||||||
{
|
{
|
||||||
Cembra
|
CembraCsv,
|
||||||
|
CembraPdf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
Schaad.Finance.Api/IPdfParsingService.cs
Normal file
10
Schaad.Finance.Api/IPdfParsingService.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Schaad.Finance.Api
|
||||||
|
{
|
||||||
|
public interface IPdfParsingService
|
||||||
|
{
|
||||||
|
IReadOnlyList<string> ExtractText(string file, int page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
using NUnit.Framework;
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
using Schaad.Finance.Api;
|
using Schaad.Finance.Api;
|
||||||
using Schaad.Finance.Services;
|
using Schaad.Finance.Services;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace Schaad.Finance.Tests
|
namespace Schaad.Finance.Tests
|
||||||
|
|
@ -13,16 +15,34 @@ namespace Schaad.Finance.Tests
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void Startup()
|
public void Startup()
|
||||||
{
|
{
|
||||||
service = new CreditCardStatementService();
|
var pdfParsingServiceMock = new Mock<IPdfParsingService>();
|
||||||
|
pdfParsingServiceMock.Setup(t => t.ExtractText(It.IsAny<string>(), It.IsAny<int>())).Returns(() => new List<string>()
|
||||||
|
{
|
||||||
|
" Einkaufs-Datum",
|
||||||
|
" 13.07.2019 14.07.2019 Clay Schaad 13.70",
|
||||||
|
" 13.07.2019 14.07.2019 Clay Schaad2 23.50",
|
||||||
|
" dummy test",
|
||||||
|
" Saldo per "
|
||||||
|
});
|
||||||
|
service = new CreditCardStatementService(pdfParsingServiceMock.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void ReadFile_Cembra()
|
public void ReadFile_CembraCsv()
|
||||||
{
|
{
|
||||||
var transactions = service.ReadFile(CreditCardProvider.Cembra, @"Data\cembra.csv", System.Text.Encoding.UTF8);
|
var transactions = service.ReadFile(CreditCardProvider.CembraCsv, @"Data\cembra.csv", System.Text.Encoding.UTF8);
|
||||||
|
|
||||||
Assert.That(transactions.Count, Is.EqualTo(4));
|
Assert.That(transactions.Count, Is.EqualTo(4));
|
||||||
Assert.That(transactions.Sum(t => t.Amount), Is.EqualTo(144.60m));
|
Assert.That(transactions.Sum(t => t.Amount), Is.EqualTo(144.60m));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ReadFile_CembraPdf()
|
||||||
|
{
|
||||||
|
var transactions = service.ReadFile(CreditCardProvider.CembraPdf, "", System.Text.Encoding.UTF8);
|
||||||
|
|
||||||
|
Assert.That(transactions.Count, Is.EqualTo(2));
|
||||||
|
Assert.That(transactions.Sum(t => t.Amount), Is.EqualTo(37.20m));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
||||||
|
<PackageReference Include="Moq" Version="4.12.0" />
|
||||||
<PackageReference Include="MSTest.TestAdapter" Version="1.4.0" />
|
<PackageReference Include="MSTest.TestAdapter" Version="1.4.0" />
|
||||||
<PackageReference Include="MSTest.TestFramework" Version="1.4.0" />
|
<PackageReference Include="MSTest.TestFramework" Version="1.4.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
|
|
@ -22,15 +23,6 @@
|
||||||
<Folder Include="Data\" />
|
<Folder Include="Data\" />
|
||||||
</ItemGroup>
|
</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>
|
<ItemGroup>
|
||||||
<None Update="Data\camt053.xml">
|
<None Update="Data\camt053.xml">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CsvHelper" Version="12.1.2" />
|
<PackageReference Include="CsvHelper" Version="12.1.2" />
|
||||||
|
<PackageReference Include="FreeSpire.PDF" Version="5.4.0" />
|
||||||
<PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
|
<PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
||||||
<PackageReference Include="System.Xml.XmlDocument" Version="4.3.0" />
|
<PackageReference Include="System.Xml.XmlDocument" Version="4.3.0" />
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
@ -11,19 +12,29 @@ namespace Schaad.Finance.Services
|
||||||
{
|
{
|
||||||
public class CreditCardStatementService : ICreditCardStatementService
|
public class CreditCardStatementService : ICreditCardStatementService
|
||||||
{
|
{
|
||||||
|
private readonly IPdfParsingService pdfParsingService;
|
||||||
|
|
||||||
|
public CreditCardStatementService(IPdfParsingService pdfParsingService)
|
||||||
|
{
|
||||||
|
this.pdfParsingService = pdfParsingService;
|
||||||
|
}
|
||||||
|
|
||||||
public IReadOnlyList<CreditCardTransaction> ReadFile(CreditCardProvider creditCardProvider, string filePath, Encoding encoding)
|
public IReadOnlyList<CreditCardTransaction> ReadFile(CreditCardProvider creditCardProvider, string filePath, Encoding encoding)
|
||||||
{
|
{
|
||||||
switch (creditCardProvider)
|
switch (creditCardProvider)
|
||||||
{
|
{
|
||||||
case CreditCardProvider.Cembra:
|
case CreditCardProvider.CembraCsv:
|
||||||
return ParseCembra(filePath, encoding);
|
return ParseCembraCsv(filePath, encoding);
|
||||||
|
|
||||||
|
case CreditCardProvider.CembraPdf:
|
||||||
|
return ParseCembraPdf(filePath);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new NotImplementedException($"Unnknown procider {creditCardProvider}");
|
throw new NotImplementedException($"Unnknown procider {creditCardProvider}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IReadOnlyList<CreditCardTransaction> ParseCembra(string filePath, Encoding encoding)
|
private IReadOnlyList<CreditCardTransaction> ParseCembraCsv(string filePath, Encoding encoding)
|
||||||
{
|
{
|
||||||
using (var reader = new StreamReader(filePath, encoding))
|
using (var reader = new StreamReader(filePath, encoding))
|
||||||
{
|
{
|
||||||
|
|
@ -35,5 +46,129 @@ namespace Schaad.Finance.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IReadOnlyList<CreditCardTransaction> ParseCembraPdf(string filePath)
|
||||||
|
{
|
||||||
|
var text = pdfParsingService.ExtractText(filePath, 2);
|
||||||
|
|
||||||
|
var transactionList = new List<CreditCardTransaction>();
|
||||||
|
|
||||||
|
var startReading = false;
|
||||||
|
foreach (var line in text)
|
||||||
|
{
|
||||||
|
var trimmedLine = line.Trim();
|
||||||
|
if (trimmedLine.StartsWith("Einkaufs-Datum"))
|
||||||
|
{
|
||||||
|
startReading = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trimmedLine.StartsWith("Saldo per"))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startReading)
|
||||||
|
{
|
||||||
|
var transaction = ParseLine(trimmedLine);
|
||||||
|
if (transaction != null)
|
||||||
|
{
|
||||||
|
transactionList.Add(transaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactionList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CreditCardTransaction ParseLine(string line)
|
||||||
|
{
|
||||||
|
var nextPart = GetNextPart(line, 10);
|
||||||
|
|
||||||
|
if (nextPart.part == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var purchaseDate = ParseDateTime(nextPart.part);
|
||||||
|
|
||||||
|
if (purchaseDate.HasValue == false)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextPart = GetNextPart(nextPart.rest, 10);
|
||||||
|
var bookingDate = ParseDateTime(nextPart.part);
|
||||||
|
|
||||||
|
if (bookingDate.HasValue == false)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextPart = GetNextPartUntilSpace(nextPart.rest, 2);
|
||||||
|
var transactionText = nextPart.part;
|
||||||
|
|
||||||
|
nextPart = GetNextPartUntilSpace(nextPart.rest, 2);
|
||||||
|
var amount = nextPart.part;
|
||||||
|
|
||||||
|
return new CreditCardTransaction
|
||||||
|
{
|
||||||
|
BookingDate = bookingDate.Value,
|
||||||
|
TransactionDate = purchaseDate.Value,
|
||||||
|
Amount = decimal.Parse(amount),
|
||||||
|
Transaction = transactionText
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private (string part, string rest) GetNextPart(string line, int length)
|
||||||
|
{
|
||||||
|
if (line.Length < length)
|
||||||
|
{
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
var part = line.Substring(0, length);
|
||||||
|
var rest = line.Substring(length, line.Length - length).TrimStart();
|
||||||
|
return (part, rest);
|
||||||
|
}
|
||||||
|
|
||||||
|
private (string part, string rest) GetNextPartUntilSpace(string line, int howManySpaces)
|
||||||
|
{
|
||||||
|
var part = "";
|
||||||
|
var spaceCount = 0;
|
||||||
|
foreach (var c in line)
|
||||||
|
{
|
||||||
|
if (c == ' ')
|
||||||
|
{
|
||||||
|
spaceCount++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
spaceCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
part += c;
|
||||||
|
|
||||||
|
if (spaceCount == howManySpaces)
|
||||||
|
{
|
||||||
|
part = part.Trim();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var rest = line.Substring(part.Length, line.Length - part.Length).TrimStart();
|
||||||
|
return (part, rest);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DateTime? ParseDateTime(string dateString)
|
||||||
|
{
|
||||||
|
var deCH = new CultureInfo("de-CH");
|
||||||
|
DateTime purchaseDate;
|
||||||
|
if (DateTime.TryParseExact(dateString, "dd.MM.yyyy", deCH, DateTimeStyles.None, out purchaseDate))
|
||||||
|
{
|
||||||
|
return purchaseDate;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
18
Schaad.Finance/Services/PdfParsingService.cs
Normal file
18
Schaad.Finance/Services/PdfParsingService.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
using Schaad.Finance.Api;
|
||||||
|
using Spire.Pdf;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Schaad.Finance.Services
|
||||||
|
{
|
||||||
|
public class PdfParsingService : IPdfParsingService
|
||||||
|
{
|
||||||
|
public IReadOnlyList<string> ExtractText(string file, int page)
|
||||||
|
{
|
||||||
|
var document = new PdfDocument();
|
||||||
|
document.LoadFromFile(file);
|
||||||
|
var text = document.Pages[page - 1].ExtractText();
|
||||||
|
return text.Split('\n').ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue