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
|
||||
{
|
||||
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.Services;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Schaad.Finance.Tests
|
||||
|
|
@ -13,16 +15,34 @@ namespace Schaad.Finance.Tests
|
|||
[SetUp]
|
||||
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]
|
||||
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.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>
|
||||
<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.TestFramework" Version="1.4.0" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
|
|
@ -22,15 +23,6 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<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="Newtonsoft.Json" Version="11.0.2" />
|
||||
<PackageReference Include="System.Xml.XmlDocument" Version="4.3.0" />
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
|
@ -11,19 +12,29 @@ namespace Schaad.Finance.Services
|
|||
{
|
||||
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)
|
||||
{
|
||||
switch (creditCardProvider)
|
||||
{
|
||||
case CreditCardProvider.Cembra:
|
||||
return ParseCembra(filePath, encoding);
|
||||
case CreditCardProvider.CembraCsv:
|
||||
return ParseCembraCsv(filePath, encoding);
|
||||
|
||||
case CreditCardProvider.CembraPdf:
|
||||
return ParseCembraPdf(filePath);
|
||||
|
||||
default:
|
||||
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))
|
||||
{
|
||||
|
|
@ -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