From 89b63b7da71078f8558bde1407ebabe723743043 Mon Sep 17 00:00:00 2001 From: Alexander Preibisch Date: Sun, 28 Nov 2021 18:23:23 +0100 Subject: [PATCH 01/14] Add Docker multiarch build #51 #61 --- .github/workflows/docker-image-master.yml | 22 ++++++++++++++----- .../workflows/docker-image-pre-release.yml | 21 +++++++++++++----- OpenBudgeteer.Blazor/Dockerfile | 1 - OpenBudgeteer.Blazor/Startup.cs | 4 ---- 4 files changed, 31 insertions(+), 17 deletions(-) diff --git a/.github/workflows/docker-image-master.yml b/.github/workflows/docker-image-master.yml index 803f8e5..bbf3479 100644 --- a/.github/workflows/docker-image-master.yml +++ b/.github/workflows/docker-image-master.yml @@ -16,9 +16,19 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build Docker image - run: docker build . -t "axelander/openbudgeteer:latest" -f "OpenBudgeteer.Blazor/Dockerfile" - - - name: Push Docker image - run: docker push axelander/openbudgeteer:latest + + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Build and push Docker image + uses: docker/build-push-action@v2 + with: + context: . + push: true + tags: axelander/openbudgeteer:latest + file: OpenBudgeteer.Blazor/Dockerfile + platforms: linux/arm64,linux/amd64 + \ No newline at end of file diff --git a/.github/workflows/docker-image-pre-release.yml b/.github/workflows/docker-image-pre-release.yml index 01409be..4af5dab 100644 --- a/.github/workflows/docker-image-pre-release.yml +++ b/.github/workflows/docker-image-pre-release.yml @@ -19,9 +19,18 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build Docker image - run: docker build . -t "axelander/openbudgeteer:pre-release" -f "OpenBudgeteer.Blazor/Dockerfile" - - - name: Push Docker image - run: docker push axelander/openbudgeteer:pre-release + + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Build and push Docker image + uses: docker/build-push-action@v2 + with: + context: . + push: true + tags: axelander/openbudgeteer:pre-release + file: OpenBudgeteer.Blazor/Dockerfile + platforms: linux/arm64,linux/amd64 diff --git a/OpenBudgeteer.Blazor/Dockerfile b/OpenBudgeteer.Blazor/Dockerfile index 759e07c..c4937e3 100644 --- a/OpenBudgeteer.Blazor/Dockerfile +++ b/OpenBudgeteer.Blazor/Dockerfile @@ -20,5 +20,4 @@ RUN dotnet publish "OpenBudgeteer.Blazor.csproj" -c Release -o /app/publish FROM base AS final WORKDIR /app COPY --from=publish /app/publish . -RUN mkdir database ENTRYPOINT ["dotnet", "OpenBudgeteer.dll"] \ No newline at end of file diff --git a/OpenBudgeteer.Blazor/Startup.cs b/OpenBudgeteer.Blazor/Startup.cs index 2ca6d23..7fcaffd 100644 --- a/OpenBudgeteer.Blazor/Startup.cs +++ b/OpenBudgeteer.Blazor/Startup.cs @@ -55,11 +55,7 @@ public void ConfigureServices(IServiceCollection services) break; case "sqlite": -#if DEBUG connectionString = "Data Source=openbudgeteer.db"; -#else - connectionString = "Data Source=database/openbudgeteer.db"; -#endif services.AddDbContext(options => options.UseSqlite( connectionString, b => b.MigrationsAssembly("OpenBudgeteer.Core")), From 33b173bfc31913c08e99ff486408522ea33bcb28 Mon Sep 17 00:00:00 2001 From: Alexander Preibisch Date: Mon, 17 Jan 2022 17:07:16 +0100 Subject: [PATCH 02/14] Add option to set Localization #52 --- CHANGELOG.md | 4 ++++ OpenBudgeteer.Blazor/Shared/NavMenu.razor | 2 +- OpenBudgeteer.Blazor/Startup.cs | 29 +++++++++++++---------- OpenBudgeteer.Blazor/appsettings.json | 7 ++++-- README.md | 8 +++++++ 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46ebcf7..96191e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +### 1.5 (20xx-xx-xx) + +* [Add] Option to set Localization [#52](https://github.com/TheAxelander/OpenBudgeteer/issues/52) + ### 1.4.1 (2021-11-28) * [Changed] Handling of Bucket Group creation (fixes also crashes during creation cancellation [#56](https://github.com/TheAxelander/OpenBudgeteer/issues/56)) diff --git a/OpenBudgeteer.Blazor/Shared/NavMenu.razor b/OpenBudgeteer.Blazor/Shared/NavMenu.razor index cc5be44..739f9b5 100644 --- a/OpenBudgeteer.Blazor/Shared/NavMenu.razor +++ b/OpenBudgeteer.Blazor/Shared/NavMenu.razor @@ -54,7 +54,7 @@ diff --git a/OpenBudgeteer.Blazor/Startup.cs b/OpenBudgeteer.Blazor/Startup.cs index 7fcaffd..9462a20 100644 --- a/OpenBudgeteer.Blazor/Startup.cs +++ b/OpenBudgeteer.Blazor/Startup.cs @@ -27,21 +27,23 @@ public Startup(IConfiguration configuration) // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { + services.AddLocalization(); services.AddRazorPages(); services.AddServerSideBlazor(); services.AddFileReaderService(); services.AddScoped(); - var configurationSection = Configuration.GetSection("Connection"); - var provider = configurationSection?["Provider"]; + + var configurationConnectionSection = Configuration.GetSection("Connection"); + var provider = configurationConnectionSection?["Provider"]; string connectionString; switch (provider) { case "mysql": - connectionString = $"Server={configurationSection?["Server"]};" + - $"Port={configurationSection?["Port"]};" + - $"Database={configurationSection?["Database"]};" + - $"User={configurationSection?["User"]};" + - $"Password={configurationSection?["Password"]}"; + connectionString = $"Server={configurationConnectionSection?["Server"]};" + + $"Port={configurationConnectionSection?["Port"]};" + + $"Database={configurationConnectionSection?["Database"]};" + + $"User={configurationConnectionSection?["User"]};" + + $"Password={configurationConnectionSection?["Password"]}"; services.AddDbContext(options => options.UseMySql( connectionString, @@ -70,6 +72,11 @@ public void ConfigureServices(IServiceCollection services) throw new ArgumentOutOfRangeException($"Database provider {provider} not supported"); } + // var configurationAppSettingSection = Configuration.GetSection("AppSettings"); + // var cultureInfo = new CultureInfo(configurationAppSettingSection["Culture"]); + // CultureInfo.DefaultThreadCurrentCulture = cultureInfo; + // CultureInfo.DefaultThreadCurrentUICulture = cultureInfo; + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); // Required to read ANSI Text files } @@ -89,6 +96,9 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseHttpsRedirection(); app.UseStaticFiles(); + + var configurationAppSettingSection = Configuration.GetSection("AppSettings"); + app.UseRequestLocalization(configurationAppSettingSection["Culture"]); app.UseRouting(); @@ -97,10 +107,5 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) endpoints.MapBlazorHub(); endpoints.MapFallbackToPage("/_Host"); }); - - // TODO Get Culture from Settings - var cultureInfo = new CultureInfo("de-DE"); - CultureInfo.DefaultThreadCurrentCulture = cultureInfo; - CultureInfo.DefaultThreadCurrentUICulture = cultureInfo; } } diff --git a/OpenBudgeteer.Blazor/appsettings.json b/OpenBudgeteer.Blazor/appsettings.json index 4691fa9..2aacd51 100644 --- a/OpenBudgeteer.Blazor/appsettings.json +++ b/OpenBudgeteer.Blazor/appsettings.json @@ -2,11 +2,14 @@ "Connection": { "Provider" : "mysql", "Database": "openbudgeteer-dev", - "Server": "127.0.0.1", + "Server": "192.168.178.86", "Port": "3306", "User": "openbudgeteer-dev", "Password": "openbudgeteer-dev" - }, + }, + "AppSettings": { + "Culture": "en-US" + }, "Logging": { "LogLevel": { "Default": "Information", diff --git a/README.md b/README.md index 604ab47..368a803 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,7 @@ services: - Connection:Database=openbudgeteer - Connection:User=openbudgeteer - Connection:Password=openbudgeteer + - AppSettings:Culture=en-US depends_on: - mysql networks: @@ -132,6 +133,13 @@ volumes: data: ``` +## Additional Settings + +| Variable | Description | Default | +|---------------------|------------------------------------------------------------------------------------------------------------|-------------------------| +| AppSettings:Culture | Localization identifier to set things like Currency, Date and Number Format. Must be a BCP 47 language tag | en-US | + + ## How to use ### Create Bank Account From d7b594baba46536b983eebd6d44492906c37b3e2 Mon Sep 17 00:00:00 2001 From: Alexander Preibisch Date: Mon, 17 Jan 2022 17:11:54 +0100 Subject: [PATCH 03/14] Fixed Sqlite database path #63 --- CHANGELOG.md | 1 + OpenBudgeteer.Blazor/Dockerfile | 1 + OpenBudgeteer.Blazor/Startup.cs | 2 +- .../Common/Database/SqliteDatabaseContextFactory.cs | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96191e2..c3e7334 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ### 1.5 (20xx-xx-xx) * [Add] Option to set Localization [#52](https://github.com/TheAxelander/OpenBudgeteer/issues/52) +* [Fixed] Moved Sqlite database path back to `/app/database` [#63](https://github.com/TheAxelander/OpenBudgeteer/issues/63) ### 1.4.1 (2021-11-28) diff --git a/OpenBudgeteer.Blazor/Dockerfile b/OpenBudgeteer.Blazor/Dockerfile index c4937e3..759e07c 100644 --- a/OpenBudgeteer.Blazor/Dockerfile +++ b/OpenBudgeteer.Blazor/Dockerfile @@ -20,4 +20,5 @@ RUN dotnet publish "OpenBudgeteer.Blazor.csproj" -c Release -o /app/publish FROM base AS final WORKDIR /app COPY --from=publish /app/publish . +RUN mkdir database ENTRYPOINT ["dotnet", "OpenBudgeteer.dll"] \ No newline at end of file diff --git a/OpenBudgeteer.Blazor/Startup.cs b/OpenBudgeteer.Blazor/Startup.cs index 9462a20..a03604d 100644 --- a/OpenBudgeteer.Blazor/Startup.cs +++ b/OpenBudgeteer.Blazor/Startup.cs @@ -57,7 +57,7 @@ public void ConfigureServices(IServiceCollection services) break; case "sqlite": - connectionString = "Data Source=openbudgeteer.db"; + connectionString = "Data Source=database/openbudgeteer.db"; services.AddDbContext(options => options.UseSqlite( connectionString, b => b.MigrationsAssembly("OpenBudgeteer.Core")), diff --git a/OpenBudgeteer.Core/Common/Database/SqliteDatabaseContextFactory.cs b/OpenBudgeteer.Core/Common/Database/SqliteDatabaseContextFactory.cs index 8fd8864..3cf38b8 100644 --- a/OpenBudgeteer.Core/Common/Database/SqliteDatabaseContextFactory.cs +++ b/OpenBudgeteer.Core/Common/Database/SqliteDatabaseContextFactory.cs @@ -8,7 +8,7 @@ public class SqliteDatabaseContextFactory : IDesignTimeDbContextFactory(); - optionsBuilder.UseSqlite("Data Source=openbudgeteer.db"); + optionsBuilder.UseSqlite("Data Source=database/openbudgeteer.db"); return new SqliteDatabaseContext(optionsBuilder.Options); } From 3aca076eba4cb4fb791c7e593114a03a6783cf52 Mon Sep 17 00:00:00 2001 From: Alexander Preibisch Date: Fri, 21 Jan 2022 15:53:49 +0100 Subject: [PATCH 04/14] Add instructions for non-Docker deployment #54 --- README.md | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 108 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 368a803..e8b4cd9 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,22 @@ OpenBudgeteer is a budgeting app based on the Bucket Budgeting Principle and ins ![Screenshot 1](assets/screenshot1.png) +-------------------- + +## Table of contents + +- [Installation (Docker)](#installation-docker) + - [Pre-release Version (Docker)](#pre-release-version-docker) + - [Docker-Compose](#docker-compose) +- [Build on Linux and Deploy on nginx](#build-on-linux-and-deploy-on-nginx) +- [Additional Settings](#additional-settings) +- [How to use](#how-to-use) + - [Create Bank Account](#create-bank-account) + - [Import Transactions](#import-transactions) + - [Create Buckets](#create-buckets) + - [Bucket Assignment](#bucket-assignment) + - [Bucket History](#bucket-history) + ## Installation (Docker) You can use the pre-built Docker Image from [Docker Hub](https://hub.docker.com/r/axelander/openbudgeteer). It requires a connection to a `MySQL` database which can be achieved by passing the following variables: @@ -76,7 +92,7 @@ docker run -d --name='openbudgeteer' \ ### Docker-Compose -Below an example how to deploy OpenBudgeteer together with MySql Server and phpMyAdmin for administration. Please note that user and database `openbudgeteer` need to be availabe, otherwise the container `openbudgeteer` will not work. +Below an example how to deploy OpenBudgeteer together with MySql Server and phpMyAdmin for administration. Please note that user and database `openbudgeteer` need to be available, otherwise the container `openbudgeteer` will not work. So what you can do this is running below Docker Compose, create user and database using phpMyAdmin and then restart either container `openbudgeteer` or the whole Docker Compose. @@ -133,6 +149,93 @@ volumes: data: ``` +## Build on Linux and Deploy on nginx + +Install .NET SDK 6 for your respective Linux distribution. See [here](https://docs.microsoft.com/en-us/dotnet/core/install/linux) for more details. Below example is for Debian 11 + +```bash +wget https://packages.microsoft.com/config/debian/11/packages-microsoft-prod.deb -O packages-microsoft-prod.deb +sudo dpkg -i packages-microsoft-prod.deb +rm packages-microsoft-prod.deb + +sudo apt-get update; \ + sudo apt-get install -y apt-transport-https && \ + sudo apt-get update && \ + sudo apt-get install -y dotnet-sdk-6.0 +``` + +Install nginx + +```bash +sudo apt install nginx + +sudo systemctl start nginx +``` + +Clone git Repository and Build project + +```bash +git clone https://github.com/TheAxelander/OpenBudgeteer.git +cd OpenBudgeteer/OpenBudgeteer.Blazor + +dotnet publish -c Release --self-contained -r linux-x64 +``` + +Modify `appsettings.json` and enter credentials for a running MySql Server or use Sqlite + +```bash +cd bin/Release/net6.0/linux-x64/publish + +nano appsettings.json +``` + +For MySQL: + +```json +{ + "Connection": { + "Provider" : "mysql", + "Database": "openbudgeteer", + "Server": "192.168.178.100", + "Port": "3306", + "User": "openbudgeteer", + "Password": "openbudgeteer" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} +``` + +For Sqlite: + +```json +{ + "Connection": { + "Provider" : "sqlite", + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} +``` + +Start server running on port 5000 + +```bash +./OpenBudgeteer --urls http://0.0.0.0:5000 +``` + ## Additional Settings | Variable | Description | Default | @@ -170,17 +273,17 @@ If you are happy with your setup, put some money into your Buckets. You can do i ### Bucket Assignment -In the final step you assign your Transactions to certain Buckets. Go back to the `Transaction Page`, edit a Transaction and select an appropiate Bucket. You can also do a mass edit. If a Transaction belongs to more than one Bucket just reduce the assigned amount and you get automatically the option to assign the remaining amount to anthoer Bucket. +In the final step you assign your Transactions to certain Buckets. Go back to the `Transaction Page`, edit a Transaction and select an appropriate Bucket. You can also do a mass edit. If a Transaction belongs to more than one Bucket just reduce the assigned amount and you get automatically the option to assign the remaining amount to another Bucket. ![Screenshot 5](assets/screenshot5.png) -Transactions which represent your (monthly) income can be assigned to the pre-defined `Income` Bucket. If you have transffered money from one Account to another you can use the `Transfer` Bucket. Please ensure that all `Transfer` Transaction have in total a 0 Balance to prevent data inconsistency and wrong calculations. +Transactions which represent your (monthly) income can be assigned to the pre-defined `Income` Bucket. If you have transferred money from one Account to another you can use the `Transfer` Bucket. Please ensure that all `Transfer` Transaction have in total a 0 Balance to prevent data inconsistency and wrong calculations. Once all Transactions are assigned properly you can go back to the Bucket Overview to see if your Budget management is still fine or if you need to do some movements. You should always ensure that your Buckets don't have a negative Balance. Also your `Remaining Budget` should never be negative. -### Bucket Histroy +### Bucket History -OpenBudgeteer has a built-in versioning for Buckets which enables a proper histroy view on previous months. If you modify a Bucket, like changing the Type or the Target Amount, it will create a new version for the current selected month. It is not recommended to change a Bucket in the past, a change between two Bucket Version is prevented. +OpenBudgeteer has a built-in versioning for Buckets which enables a proper history view on previous months. If you modify a Bucket, like changing the Type or the Target Amount, it will create a new version for the current selected month. It is not recommended to change a Bucket in the past, a change between two Bucket Version is prevented. If you close a Bucket it will be marked as `Inactive` for the next month. This can be only done if the Bucket Balance is 0 to prevent wrong calculations. From d62ae85c0b67353a68df996eb2bbaaaa2f45709a Mon Sep 17 00:00:00 2001 From: Axelander Date: Sun, 13 Feb 2022 16:01:29 +0100 Subject: [PATCH 05/14] Enable mapping of seperated debit and credit columns for file import #53 --- CHANGELOG.md | 1 + OpenBudgeteer.Blazor/Pages/Import.razor | 27 +- .../Database/MySqlDatabaseContextFactory.cs | 2 +- ...20213104939_ImportCreditColumn.Designer.cs | 288 ++++++++++++++++++ .../20220213104939_ImportCreditColumn.cs | 212 +++++++++++++ .../MySql/DatabaseServiceModelSnapshot.cs | 46 +-- ...20213105005_ImportCreditColumn.Designer.cs | 282 +++++++++++++++++ .../20220213105005_ImportCreditColumn.cs | 36 +++ .../SqliteDatabaseContextModelSnapshot.cs | 11 +- OpenBudgeteer.Core/Models/ImportProfile.cs | 14 + .../ViewModels/ImportDataViewModel.cs | 66 +++- 11 files changed, 957 insertions(+), 28 deletions(-) create mode 100644 OpenBudgeteer.Core/Migrations/MySql/20220213104939_ImportCreditColumn.Designer.cs create mode 100644 OpenBudgeteer.Core/Migrations/MySql/20220213104939_ImportCreditColumn.cs create mode 100644 OpenBudgeteer.Core/Migrations/Sqlite/20220213105005_ImportCreditColumn.Designer.cs create mode 100644 OpenBudgeteer.Core/Migrations/Sqlite/20220213105005_ImportCreditColumn.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index c3e7334..99eadb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ### 1.5 (20xx-xx-xx) * [Add] Option to set Localization [#52](https://github.com/TheAxelander/OpenBudgeteer/issues/52) +* [Add] Enable mapping of seperated columns for Debit and Credit Amount on Import Page [#53](https://github.com/TheAxelander/OpenBudgeteer/issues/53) * [Fixed] Moved Sqlite database path back to `/app/database` [#63](https://github.com/TheAxelander/OpenBudgeteer/issues/63) ### 1.4.1 (2021-11-28) diff --git a/OpenBudgeteer.Blazor/Pages/Import.razor b/OpenBudgeteer.Blazor/Pages/Import.razor index 1080aa6..c11ea53 100644 --- a/OpenBudgeteer.Blazor/Pages/Import.razor +++ b/OpenBudgeteer.Blazor/Pages/Import.razor @@ -177,7 +177,7 @@
- +
+
+ + +

Only required if Debit and Credit values are in separate columns

+
} @@ -475,6 +493,13 @@ CheckColumnMapping(); } + void CreditColumn_SelectionChanged(ChangeEventArgs e) + { + var value = Convert.ToString(e.Value); + _dataContext.SelectedImportProfile.CreditColumnName = value == _placeholderItemValue ? null : _dataContext.IdentifiedColumns.First(i => i == value); + CheckColumnMapping(); + } + void HandleResult(ViewModelOperationResult result) { if (!result.IsSuccessful) diff --git a/OpenBudgeteer.Core/Common/Database/MySqlDatabaseContextFactory.cs b/OpenBudgeteer.Core/Common/Database/MySqlDatabaseContextFactory.cs index 024da03..fb49b37 100644 --- a/OpenBudgeteer.Core/Common/Database/MySqlDatabaseContextFactory.cs +++ b/OpenBudgeteer.Core/Common/Database/MySqlDatabaseContextFactory.cs @@ -6,7 +6,7 @@ namespace OpenBudgeteer.Core.Common.Database; public class MySqlDatabaseContextFactory : IDesignTimeDbContextFactory { - private const string MysqlConnectionString = "Server=192.168.178.30;" + + private const string MysqlConnectionString = "Server=192.168.178.86;" + "Port=3306;" + "Database=openbudgeteer-dev;" + "User=openbudgeteer-dev;" + diff --git a/OpenBudgeteer.Core/Migrations/MySql/20220213104939_ImportCreditColumn.Designer.cs b/OpenBudgeteer.Core/Migrations/MySql/20220213104939_ImportCreditColumn.Designer.cs new file mode 100644 index 0000000..174d83e --- /dev/null +++ b/OpenBudgeteer.Core/Migrations/MySql/20220213104939_ImportCreditColumn.Designer.cs @@ -0,0 +1,288 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using OpenBudgeteer.Core.Common.Database; + +#nullable disable + +namespace OpenBudgeteer.Core.Migrations.MySql +{ + [DbContext(typeof(MySqlDatabaseContext))] + [Migration("20220213104939_ImportCreditColumn")] + partial class ImportCreditColumn + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.HasCharSet(modelBuilder, "utf8mb4"); + + modelBuilder.Entity("OpenBudgeteer.Core.Models.Account", b => + { + b.Property("AccountId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("int"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.HasKey("AccountId"); + + b.ToTable("Account"); + }); + + modelBuilder.Entity("OpenBudgeteer.Core.Models.BankTransaction", b => + { + b.Property("TransactionId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AccountId") + .HasColumnType("int"); + + b.Property("Amount") + .HasColumnType("decimal(65,2)"); + + b.Property("Memo") + .HasColumnType("longtext"); + + b.Property("Payee") + .HasColumnType("longtext"); + + b.Property("TransactionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("TransactionId"); + + b.ToTable("BankTransaction"); + }); + + modelBuilder.Entity("OpenBudgeteer.Core.Models.Bucket", b => + { + b.Property("BucketId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("BucketGroupId") + .HasColumnType("int"); + + b.Property("ColorCode") + .HasColumnType("longtext"); + + b.Property("IsInactive") + .HasColumnType("tinyint(1)"); + + b.Property("IsInactiveFrom") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("ValidFrom") + .HasColumnType("datetime(6)"); + + b.HasKey("BucketId"); + + b.ToTable("Bucket"); + }); + + modelBuilder.Entity("OpenBudgeteer.Core.Models.BucketGroup", b => + { + b.Property("BucketGroupId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("Position") + .HasColumnType("int"); + + b.HasKey("BucketGroupId"); + + b.ToTable("BucketGroup"); + }); + + modelBuilder.Entity("OpenBudgeteer.Core.Models.BucketMovement", b => + { + b.Property("BucketMovementId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Amount") + .HasColumnType("decimal(65,2)"); + + b.Property("BucketId") + .HasColumnType("int"); + + b.Property("MovementDate") + .HasColumnType("datetime(6)"); + + b.HasKey("BucketMovementId"); + + b.ToTable("BucketMovement"); + }); + + modelBuilder.Entity("OpenBudgeteer.Core.Models.BucketRuleSet", b => + { + b.Property("BucketRuleSetId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("Priority") + .HasColumnType("int"); + + b.Property("TargetBucketId") + .HasColumnType("int"); + + b.HasKey("BucketRuleSetId"); + + b.ToTable("BucketRuleSet"); + }); + + modelBuilder.Entity("OpenBudgeteer.Core.Models.BucketVersion", b => + { + b.Property("BucketVersionId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("BucketId") + .HasColumnType("int"); + + b.Property("BucketType") + .HasColumnType("int"); + + b.Property("BucketTypeXParam") + .HasColumnType("int"); + + b.Property("BucketTypeYParam") + .HasColumnType("decimal(65,2)"); + + b.Property("BucketTypeZParam") + .HasColumnType("datetime(6)"); + + b.Property("Notes") + .HasColumnType("longtext"); + + b.Property("ValidFrom") + .HasColumnType("datetime(6)"); + + b.Property("Version") + .HasColumnType("int"); + + b.HasKey("BucketVersionId"); + + b.ToTable("BucketVersion"); + }); + + modelBuilder.Entity("OpenBudgeteer.Core.Models.BudgetedTransaction", b => + { + b.Property("BudgetedTransactionId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Amount") + .HasColumnType("decimal(65,2)"); + + b.Property("BucketId") + .HasColumnType("int"); + + b.Property("TransactionId") + .HasColumnType("int"); + + b.HasKey("BudgetedTransactionId"); + + b.ToTable("BudgetedTransaction"); + }); + + modelBuilder.Entity("OpenBudgeteer.Core.Models.ImportProfile", b => + { + b.Property("ImportProfileId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AccountId") + .HasColumnType("int"); + + b.Property("AmountColumnName") + .HasColumnType("longtext"); + + b.Property("CreditColumnName") + .HasColumnType("longtext"); + + b.Property("DateFormat") + .HasColumnType("longtext"); + + b.Property("Delimiter") + .IsRequired() + .HasColumnType("varchar(1)"); + + b.Property("HasPositiveCreditColumnValues") + .HasColumnType("tinyint(1)"); + + b.Property("HeaderRow") + .HasColumnType("int"); + + b.Property("MemoColumnName") + .HasColumnType("longtext"); + + b.Property("NumberFormat") + .HasColumnType("longtext"); + + b.Property("PayeeColumnName") + .HasColumnType("longtext"); + + b.Property("ProfileName") + .HasColumnType("longtext"); + + b.Property("TextQualifier") + .IsRequired() + .HasColumnType("varchar(1)"); + + b.Property("TransactionDateColumnName") + .HasColumnType("longtext"); + + b.HasKey("ImportProfileId"); + + b.ToTable("ImportProfile"); + }); + + modelBuilder.Entity("OpenBudgeteer.Core.Models.MappingRule", b => + { + b.Property("MappingRuleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("BucketRuleSetId") + .HasColumnType("int"); + + b.Property("ComparisionField") + .HasColumnType("int"); + + b.Property("ComparisionType") + .HasColumnType("int"); + + b.Property("ComparisionValue") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("MappingRuleId"); + + b.ToTable("MappingRule"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/OpenBudgeteer.Core/Migrations/MySql/20220213104939_ImportCreditColumn.cs b/OpenBudgeteer.Core/Migrations/MySql/20220213104939_ImportCreditColumn.cs new file mode 100644 index 0000000..68ed766 --- /dev/null +++ b/OpenBudgeteer.Core/Migrations/MySql/20220213104939_ImportCreditColumn.cs @@ -0,0 +1,212 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace OpenBudgeteer.Core.Migrations.MySql +{ + public partial class ImportCreditColumn : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "CreditColumnName", + table: "ImportProfile", + type: "longtext", + nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AddColumn( + name: "HasPositiveCreditColumnValues", + table: "ImportProfile", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "CreditColumnName", + table: "ImportProfile"); + + migrationBuilder.DropColumn( + name: "HasPositiveCreditColumnValues", + table: "ImportProfile"); + + migrationBuilder.AlterColumn( + name: "ComparisionValue", + table: "MappingRule", + type: "longtext CHARACTER SET utf8mb4", + nullable: false, + oldClrType: typeof(string), + oldType: "longtext") + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AlterColumn( + name: "TransactionDateColumnName", + table: "ImportProfile", + type: "longtext CHARACTER SET utf8mb4", + nullable: true, + oldClrType: typeof(string), + oldType: "longtext", + oldNullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AlterColumn( + name: "ProfileName", + table: "ImportProfile", + type: "longtext CHARACTER SET utf8mb4", + nullable: true, + oldClrType: typeof(string), + oldType: "longtext", + oldNullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AlterColumn( + name: "PayeeColumnName", + table: "ImportProfile", + type: "longtext CHARACTER SET utf8mb4", + nullable: true, + oldClrType: typeof(string), + oldType: "longtext", + oldNullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AlterColumn( + name: "NumberFormat", + table: "ImportProfile", + type: "longtext CHARACTER SET utf8mb4", + nullable: true, + oldClrType: typeof(string), + oldType: "longtext", + oldNullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AlterColumn( + name: "MemoColumnName", + table: "ImportProfile", + type: "longtext CHARACTER SET utf8mb4", + nullable: true, + oldClrType: typeof(string), + oldType: "longtext", + oldNullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AlterColumn( + name: "DateFormat", + table: "ImportProfile", + type: "longtext CHARACTER SET utf8mb4", + nullable: true, + oldClrType: typeof(string), + oldType: "longtext", + oldNullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AlterColumn( + name: "AmountColumnName", + table: "ImportProfile", + type: "longtext CHARACTER SET utf8mb4", + nullable: true, + oldClrType: typeof(string), + oldType: "longtext", + oldNullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AlterColumn( + name: "Notes", + table: "BucketVersion", + type: "longtext CHARACTER SET utf8mb4", + nullable: true, + oldClrType: typeof(string), + oldType: "longtext", + oldNullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AlterColumn( + name: "Name", + table: "BucketRuleSet", + type: "longtext CHARACTER SET utf8mb4", + nullable: true, + oldClrType: typeof(string), + oldType: "longtext", + oldNullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AlterColumn( + name: "Name", + table: "BucketGroup", + type: "longtext CHARACTER SET utf8mb4", + nullable: true, + oldClrType: typeof(string), + oldType: "longtext", + oldNullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AlterColumn( + name: "Name", + table: "Bucket", + type: "longtext CHARACTER SET utf8mb4", + nullable: true, + oldClrType: typeof(string), + oldType: "longtext", + oldNullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AlterColumn( + name: "ColorCode", + table: "Bucket", + type: "longtext CHARACTER SET utf8mb4", + nullable: true, + oldClrType: typeof(string), + oldType: "longtext", + oldNullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AlterColumn( + name: "Payee", + table: "BankTransaction", + type: "longtext CHARACTER SET utf8mb4", + nullable: true, + oldClrType: typeof(string), + oldType: "longtext", + oldNullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AlterColumn( + name: "Memo", + table: "BankTransaction", + type: "longtext CHARACTER SET utf8mb4", + nullable: true, + oldClrType: typeof(string), + oldType: "longtext", + oldNullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AlterColumn( + name: "Name", + table: "Account", + type: "longtext CHARACTER SET utf8mb4", + nullable: true, + oldClrType: typeof(string), + oldType: "longtext", + oldNullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + } + } +} diff --git a/OpenBudgeteer.Core/Migrations/MySql/DatabaseServiceModelSnapshot.cs b/OpenBudgeteer.Core/Migrations/MySql/DatabaseServiceModelSnapshot.cs index 7b08ad5..85c4291 100644 --- a/OpenBudgeteer.Core/Migrations/MySql/DatabaseServiceModelSnapshot.cs +++ b/OpenBudgeteer.Core/Migrations/MySql/DatabaseServiceModelSnapshot.cs @@ -5,6 +5,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using OpenBudgeteer.Core.Common.Database; +#nullable disable + namespace OpenBudgeteer.Core.Migrations.MySql { [DbContext(typeof(MySqlDatabaseContext))] @@ -14,8 +16,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("Relational:MaxIdentifierLength", 64) - .HasAnnotation("ProductVersion", "3.1.5"); + .HasAnnotation("ProductVersion", "6.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.HasCharSet(modelBuilder, "utf8mb4"); modelBuilder.Entity("OpenBudgeteer.Core.Models.Account", b => { @@ -27,7 +31,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("int"); b.Property("Name") - .HasColumnType("longtext CHARACTER SET utf8mb4"); + .HasColumnType("longtext"); b.HasKey("AccountId"); @@ -47,10 +51,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("decimal(65,2)"); b.Property("Memo") - .HasColumnType("longtext CHARACTER SET utf8mb4"); + .HasColumnType("longtext"); b.Property("Payee") - .HasColumnType("longtext CHARACTER SET utf8mb4"); + .HasColumnType("longtext"); b.Property("TransactionDate") .HasColumnType("datetime(6)"); @@ -70,7 +74,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("int"); b.Property("ColorCode") - .HasColumnType("longtext CHARACTER SET utf8mb4"); + .HasColumnType("longtext"); b.Property("IsInactive") .HasColumnType("tinyint(1)"); @@ -79,7 +83,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("datetime(6)"); b.Property("Name") - .HasColumnType("longtext CHARACTER SET utf8mb4"); + .HasColumnType("longtext"); b.Property("ValidFrom") .HasColumnType("datetime(6)"); @@ -96,7 +100,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("int"); b.Property("Name") - .HasColumnType("longtext CHARACTER SET utf8mb4"); + .HasColumnType("longtext"); b.Property("Position") .HasColumnType("int"); @@ -133,7 +137,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("int"); b.Property("Name") - .HasColumnType("longtext CHARACTER SET utf8mb4"); + .HasColumnType("longtext"); b.Property("Priority") .HasColumnType("int"); @@ -168,7 +172,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("datetime(6)"); b.Property("Notes") - .HasColumnType("longtext CHARACTER SET utf8mb4"); + .HasColumnType("longtext"); b.Property("ValidFrom") .HasColumnType("datetime(6)"); @@ -211,36 +215,42 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("int"); b.Property("AmountColumnName") - .HasColumnType("longtext CHARACTER SET utf8mb4"); + .HasColumnType("longtext"); + + b.Property("CreditColumnName") + .HasColumnType("longtext"); b.Property("DateFormat") - .HasColumnType("longtext CHARACTER SET utf8mb4"); + .HasColumnType("longtext"); b.Property("Delimiter") .IsRequired() .HasColumnType("varchar(1)"); + b.Property("HasPositiveCreditColumnValues") + .HasColumnType("tinyint(1)"); + b.Property("HeaderRow") .HasColumnType("int"); b.Property("MemoColumnName") - .HasColumnType("longtext CHARACTER SET utf8mb4"); + .HasColumnType("longtext"); b.Property("NumberFormat") - .HasColumnType("longtext CHARACTER SET utf8mb4"); + .HasColumnType("longtext"); b.Property("PayeeColumnName") - .HasColumnType("longtext CHARACTER SET utf8mb4"); + .HasColumnType("longtext"); b.Property("ProfileName") - .HasColumnType("longtext CHARACTER SET utf8mb4"); + .HasColumnType("longtext"); b.Property("TextQualifier") .IsRequired() .HasColumnType("varchar(1)"); b.Property("TransactionDateColumnName") - .HasColumnType("longtext CHARACTER SET utf8mb4"); + .HasColumnType("longtext"); b.HasKey("ImportProfileId"); @@ -264,7 +274,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ComparisionValue") .IsRequired() - .HasColumnType("longtext CHARACTER SET utf8mb4"); + .HasColumnType("longtext"); b.HasKey("MappingRuleId"); diff --git a/OpenBudgeteer.Core/Migrations/Sqlite/20220213105005_ImportCreditColumn.Designer.cs b/OpenBudgeteer.Core/Migrations/Sqlite/20220213105005_ImportCreditColumn.Designer.cs new file mode 100644 index 0000000..918a7d0 --- /dev/null +++ b/OpenBudgeteer.Core/Migrations/Sqlite/20220213105005_ImportCreditColumn.Designer.cs @@ -0,0 +1,282 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using OpenBudgeteer.Core.Common.Database; + +#nullable disable + +namespace OpenBudgeteer.Core.Migrations.Sqlite +{ + [DbContext(typeof(SqliteDatabaseContext))] + [Migration("20220213105005_ImportCreditColumn")] + partial class ImportCreditColumn + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.0"); + + modelBuilder.Entity("OpenBudgeteer.Core.Models.Account", b => + { + b.Property("AccountId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("AccountId"); + + b.ToTable("Account"); + }); + + modelBuilder.Entity("OpenBudgeteer.Core.Models.BankTransaction", b => + { + b.Property("TransactionId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccountId") + .HasColumnType("INTEGER"); + + b.Property("Amount") + .HasColumnType("decimal(65, 2)"); + + b.Property("Memo") + .HasColumnType("TEXT"); + + b.Property("Payee") + .HasColumnType("TEXT"); + + b.Property("TransactionDate") + .HasColumnType("TEXT"); + + b.HasKey("TransactionId"); + + b.ToTable("BankTransaction"); + }); + + modelBuilder.Entity("OpenBudgeteer.Core.Models.Bucket", b => + { + b.Property("BucketId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BucketGroupId") + .HasColumnType("INTEGER"); + + b.Property("ColorCode") + .HasColumnType("TEXT"); + + b.Property("IsInactive") + .HasColumnType("INTEGER"); + + b.Property("IsInactiveFrom") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("ValidFrom") + .HasColumnType("TEXT"); + + b.HasKey("BucketId"); + + b.ToTable("Bucket"); + }); + + modelBuilder.Entity("OpenBudgeteer.Core.Models.BucketGroup", b => + { + b.Property("BucketGroupId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Position") + .HasColumnType("INTEGER"); + + b.HasKey("BucketGroupId"); + + b.ToTable("BucketGroup"); + }); + + modelBuilder.Entity("OpenBudgeteer.Core.Models.BucketMovement", b => + { + b.Property("BucketMovementId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Amount") + .HasColumnType("decimal(65, 2)"); + + b.Property("BucketId") + .HasColumnType("INTEGER"); + + b.Property("MovementDate") + .HasColumnType("TEXT"); + + b.HasKey("BucketMovementId"); + + b.ToTable("BucketMovement"); + }); + + modelBuilder.Entity("OpenBudgeteer.Core.Models.BucketRuleSet", b => + { + b.Property("BucketRuleSetId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Priority") + .HasColumnType("INTEGER"); + + b.Property("TargetBucketId") + .HasColumnType("INTEGER"); + + b.HasKey("BucketRuleSetId"); + + b.ToTable("BucketRuleSet"); + }); + + modelBuilder.Entity("OpenBudgeteer.Core.Models.BucketVersion", b => + { + b.Property("BucketVersionId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BucketId") + .HasColumnType("INTEGER"); + + b.Property("BucketType") + .HasColumnType("INTEGER"); + + b.Property("BucketTypeXParam") + .HasColumnType("INTEGER"); + + b.Property("BucketTypeYParam") + .HasColumnType("decimal(65, 2)"); + + b.Property("BucketTypeZParam") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("ValidFrom") + .HasColumnType("TEXT"); + + b.Property("Version") + .HasColumnType("INTEGER"); + + b.HasKey("BucketVersionId"); + + b.ToTable("BucketVersion"); + }); + + modelBuilder.Entity("OpenBudgeteer.Core.Models.BudgetedTransaction", b => + { + b.Property("BudgetedTransactionId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Amount") + .HasColumnType("decimal(65, 2)"); + + b.Property("BucketId") + .HasColumnType("INTEGER"); + + b.Property("TransactionId") + .HasColumnType("INTEGER"); + + b.HasKey("BudgetedTransactionId"); + + b.ToTable("BudgetedTransaction"); + }); + + modelBuilder.Entity("OpenBudgeteer.Core.Models.ImportProfile", b => + { + b.Property("ImportProfileId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccountId") + .HasColumnType("INTEGER"); + + b.Property("AmountColumnName") + .HasColumnType("TEXT"); + + b.Property("CreditColumnName") + .HasColumnType("TEXT"); + + b.Property("DateFormat") + .HasColumnType("TEXT"); + + b.Property("Delimiter") + .HasColumnType("TEXT"); + + b.Property("HasPositiveCreditColumnValues") + .HasColumnType("INTEGER"); + + b.Property("HeaderRow") + .HasColumnType("INTEGER"); + + b.Property("MemoColumnName") + .HasColumnType("TEXT"); + + b.Property("NumberFormat") + .HasColumnType("TEXT"); + + b.Property("PayeeColumnName") + .HasColumnType("TEXT"); + + b.Property("ProfileName") + .HasColumnType("TEXT"); + + b.Property("TextQualifier") + .HasColumnType("TEXT"); + + b.Property("TransactionDateColumnName") + .HasColumnType("TEXT"); + + b.HasKey("ImportProfileId"); + + b.ToTable("ImportProfile"); + }); + + modelBuilder.Entity("OpenBudgeteer.Core.Models.MappingRule", b => + { + b.Property("MappingRuleId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BucketRuleSetId") + .HasColumnType("INTEGER"); + + b.Property("ComparisionField") + .HasColumnType("INTEGER"); + + b.Property("ComparisionType") + .HasColumnType("INTEGER"); + + b.Property("ComparisionValue") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("MappingRuleId"); + + b.ToTable("MappingRule"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/OpenBudgeteer.Core/Migrations/Sqlite/20220213105005_ImportCreditColumn.cs b/OpenBudgeteer.Core/Migrations/Sqlite/20220213105005_ImportCreditColumn.cs new file mode 100644 index 0000000..380a0ad --- /dev/null +++ b/OpenBudgeteer.Core/Migrations/Sqlite/20220213105005_ImportCreditColumn.cs @@ -0,0 +1,36 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace OpenBudgeteer.Core.Migrations.Sqlite +{ + public partial class ImportCreditColumn : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "CreditColumnName", + table: "ImportProfile", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "HasPositiveCreditColumnValues", + table: "ImportProfile", + type: "INTEGER", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "CreditColumnName", + table: "ImportProfile"); + + migrationBuilder.DropColumn( + name: "HasPositiveCreditColumnValues", + table: "ImportProfile"); + } + } +} diff --git a/OpenBudgeteer.Core/Migrations/Sqlite/SqliteDatabaseContextModelSnapshot.cs b/OpenBudgeteer.Core/Migrations/Sqlite/SqliteDatabaseContextModelSnapshot.cs index 03142f2..d6cdb21 100644 --- a/OpenBudgeteer.Core/Migrations/Sqlite/SqliteDatabaseContextModelSnapshot.cs +++ b/OpenBudgeteer.Core/Migrations/Sqlite/SqliteDatabaseContextModelSnapshot.cs @@ -5,6 +5,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using OpenBudgeteer.Core.Common.Database; +#nullable disable + namespace OpenBudgeteer.Core.Migrations.Sqlite { [DbContext(typeof(SqliteDatabaseContext))] @@ -13,8 +15,7 @@ partial class SqliteDatabaseContextModelSnapshot : ModelSnapshot protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.8"); + modelBuilder.HasAnnotation("ProductVersion", "6.0.0"); modelBuilder.Entity("OpenBudgeteer.Core.Models.Account", b => { @@ -212,12 +213,18 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("AmountColumnName") .HasColumnType("TEXT"); + b.Property("CreditColumnName") + .HasColumnType("TEXT"); + b.Property("DateFormat") .HasColumnType("TEXT"); b.Property("Delimiter") .HasColumnType("TEXT"); + b.Property("HasPositiveCreditColumnValues") + .HasColumnType("INTEGER"); + b.Property("HeaderRow") .HasColumnType("INTEGER"); diff --git a/OpenBudgeteer.Core/Models/ImportProfile.cs b/OpenBudgeteer.Core/Models/ImportProfile.cs index c63ad16..bc18e09 100644 --- a/OpenBudgeteer.Core/Models/ImportProfile.cs +++ b/OpenBudgeteer.Core/Models/ImportProfile.cs @@ -85,4 +85,18 @@ public string AmountColumnName get => _amountColumnName; set => Set(ref _amountColumnName, value); } + + private string _creditColumnName; + public string CreditColumnName + { + get => _creditColumnName; + set => Set(ref _creditColumnName, value); + } + + private bool _hasPositiveCreditColumnValues; + public bool HasPositiveCreditColumnValues + { + get => _hasPositiveCreditColumnValues; + set => Set(ref _hasPositiveCreditColumnValues, value); + } } diff --git a/OpenBudgeteer.Core/ViewModels/ImportDataViewModel.cs b/OpenBudgeteer.Core/ViewModels/ImportDataViewModel.cs index 67a49ee..aec5580 100644 --- a/OpenBudgeteer.Core/ViewModels/ImportDataViewModel.cs +++ b/OpenBudgeteer.Core/ViewModels/ImportDataViewModel.cs @@ -14,6 +14,7 @@ using TinyCsvParser.Mapping; using TinyCsvParser.Tokenizer.RFC4180; using TinyCsvParser.TypeConverter; +using System.Linq.Expressions; namespace OpenBudgeteer.Core.ViewModels; @@ -32,15 +33,68 @@ private class CsvBankTransactionMapping : CsvMapping /// Collection of all CSV columns public CsvBankTransactionMapping(ImportProfile importProfile, IEnumerable identifiedColumns) : base() { - // TODO Add User Input for CultureInfo for Amount & TransactionDate conversion - - MapProperty(identifiedColumns.ToList().IndexOf(importProfile.AmountColumnName), x => x.Amount, new DecimalConverter(new CultureInfo(importProfile.NumberFormat))); - MapProperty(identifiedColumns.ToList().IndexOf(importProfile.MemoColumnName), x => x.Memo); + // Mandatory + MapProperty( + identifiedColumns.ToList().IndexOf(importProfile.MemoColumnName), + x => x.Memo); + MapProperty( + identifiedColumns.ToList().IndexOf(importProfile.TransactionDateColumnName), + x => x.TransactionDate, + new DateTimeConverter(importProfile.DateFormat)); + + // Optional if (!string.IsNullOrEmpty(importProfile.PayeeColumnName)) { - MapProperty(identifiedColumns.ToList().IndexOf(importProfile.PayeeColumnName), x => x.Payee); + MapProperty( + identifiedColumns.ToList().IndexOf(importProfile.PayeeColumnName), + x => x.Payee); + } + + // Amount Mapping + if (!string.IsNullOrEmpty(importProfile.CreditColumnName)) + { + MapUsing(((transaction, row) => + { + var debitValue = row.Tokens[identifiedColumns.ToList().IndexOf(importProfile.AmountColumnName)]; + var creditValue = row.Tokens[identifiedColumns.ToList().IndexOf(importProfile.CreditColumnName)]; + + if (string.IsNullOrWhiteSpace(debitValue) && string.IsNullOrWhiteSpace(creditValue)) + { + return false; + } + + Decimal result; + var converter = new DecimalConverter(new CultureInfo(importProfile.NumberFormat)); + + if (string.IsNullOrWhiteSpace(debitValue)) + { + var converterResult = converter.TryConvert(creditValue, out result); + if (converterResult) + { + transaction.Amount = result > 0 ? result * -1 : result; + } + + return converterResult; + } + else + { + var converterResult = converter.TryConvert(debitValue, out result); + if (converterResult) + { + transaction.Amount = result; + } + + return converterResult; + } + })); + } + else + { + MapProperty( + identifiedColumns.ToList().IndexOf(importProfile.AmountColumnName), + x => x.Amount, + new DecimalConverter(new CultureInfo(importProfile.NumberFormat))); } - MapProperty(identifiedColumns.ToList().IndexOf(importProfile.TransactionDateColumnName), x => x.TransactionDate, new DateTimeConverter(importProfile.DateFormat)); } } From 7eaeee8e1d92a9d9995fe1a1ca99acff7bd5f2a3 Mon Sep 17 00:00:00 2001 From: Axelander Date: Sun, 13 Feb 2022 17:05:57 +0100 Subject: [PATCH 06/14] Fixed crash on Rules Page in case a Bucket has been deleted with existing RuleSet #65 --- CHANGELOG.md | 1 + .../ItemViewModels/BucketViewModelItem.cs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99eadb1..3d5b3e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ * [Add] Option to set Localization [#52](https://github.com/TheAxelander/OpenBudgeteer/issues/52) * [Add] Enable mapping of seperated columns for Debit and Credit Amount on Import Page [#53](https://github.com/TheAxelander/OpenBudgeteer/issues/53) * [Fixed] Moved Sqlite database path back to `/app/database` [#63](https://github.com/TheAxelander/OpenBudgeteer/issues/63) +* [Fixed] Crash on Rules Page in case a Bucket has been deleted with an existing RuleSet [#65](https://github.com/TheAxelander/OpenBudgeteer/issues/65) ### 1.4.1 (2021-11-28) diff --git a/OpenBudgeteer.Core/ViewModels/ItemViewModels/BucketViewModelItem.cs b/OpenBudgeteer.Core/ViewModels/ItemViewModels/BucketViewModelItem.cs index a367e88..2330036 100644 --- a/OpenBudgeteer.Core/ViewModels/ItemViewModels/BucketViewModelItem.cs +++ b/OpenBudgeteer.Core/ViewModels/ItemViewModels/BucketViewModelItem.cs @@ -457,6 +457,7 @@ public void EditBucket() /// Updates a record in the database based on object to set it as inactive. In case there /// are no nor assigned to it, it will be deleted /// completely from the database (including ) + /// In case of a full deletion all will be also deleted. /// /// Bucket will be set to inactive for the next month /// Triggers @@ -487,10 +488,13 @@ public ViewModelOperationResult CloseBucket() else { // Bucket has no transactions & movements, so it can be directly deleted from the database + // Delete Bucket if (dbContext.DeleteBucket(Bucket) == 0) throw new Exception($"Unable to delete Bucket.{Environment.NewLine}" + $"{Environment.NewLine}" + $"Bucket ID: {Bucket.BucketId}{Environment.NewLine}"); + + // Delete all BucketVersion which refer to this Bucket var bucketVersions = dbContext.BucketVersion .Where(i => i.BucketId == Bucket.BucketId) .ToList(); @@ -502,6 +506,18 @@ public ViewModelOperationResult CloseBucket() $"Bucket Version ID: {bucketVersion.BucketVersionId}{Environment.NewLine}" + $"Bucket Version: {bucketVersion.Version}"); } + + // Delete all BucketRuleSet which refer to this Bucket + var bucketRuleSets = dbContext.BucketRuleSet + .Where(i => i.TargetBucketId == Bucket.BucketId) + .ToList(); + foreach (var bucketRuleSet in bucketRuleSets) + { + if (dbContext.DeleteBucketRuleSet(bucketRuleSet) == 0) + throw new Exception($"Unable to delete a Bucket Rule.{Environment.NewLine}" + + $"{Environment.NewLine}" + + $"Bucket Rule ID: {bucketRuleSet.BucketRuleSetId}{Environment.NewLine}"); + } } transaction.Commit(); } From 5b8feb16196883ef24b3bbc6cccd4fd134fa9f40 Mon Sep 17 00:00:00 2001 From: Axelander Date: Sun, 13 Feb 2022 17:52:49 +0100 Subject: [PATCH 07/14] Include Transactions in modification in all filters #67 --- CHANGELOG.md | 1 + OpenBudgeteer.Core/ViewModels/TransactionViewModel.cs | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d5b3e7..b95564b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * [Add] Enable mapping of seperated columns for Debit and Credit Amount on Import Page [#53](https://github.com/TheAxelander/OpenBudgeteer/issues/53) * [Fixed] Moved Sqlite database path back to `/app/database` [#63](https://github.com/TheAxelander/OpenBudgeteer/issues/63) * [Fixed] Crash on Rules Page in case a Bucket has been deleted with an existing RuleSet [#65](https://github.com/TheAxelander/OpenBudgeteer/issues/65) +* [Fixed] Include Transactions which are in modification in all filters to prevent immediate disappearance [#67](https://github.com/TheAxelander/OpenBudgeteer/issues/67) ### 1.4.1 (2021-11-28) diff --git a/OpenBudgeteer.Core/ViewModels/TransactionViewModel.cs b/OpenBudgeteer.Core/ViewModels/TransactionViewModel.cs index aa01e01..9889497 100644 --- a/OpenBudgeteer.Core/ViewModels/TransactionViewModel.cs +++ b/OpenBudgeteer.Core/ViewModels/TransactionViewModel.cs @@ -96,10 +96,14 @@ public ObservableCollection Transactions { case TransactionViewModelFilter.HideMapped: return new ObservableCollection( - _transactions.Where(i => i.Buckets.First().SelectedBucket.BucketId == 0)); + _transactions.Where(i => + i.Buckets.First().SelectedBucket.BucketId == 0 || + i.InModification)); case TransactionViewModelFilter.OnlyMapped: return new ObservableCollection( - _transactions.Where(i => i.Buckets.First().SelectedBucket.BucketId > 0)); + _transactions.Where(i => + i.Buckets.First().SelectedBucket.BucketId > 0 || + i.InModification)); case TransactionViewModelFilter.InModification: return new ObservableCollection( _transactions.Where(i => i.InModification)); From 193600ebaed0b8832363be76495928e95f3fc6ff Mon Sep 17 00:00:00 2001 From: Axelander Date: Sun, 13 Feb 2022 20:18:00 +0100 Subject: [PATCH 08/14] Duplicate check on Import Page #49 --- CHANGELOG.md | 1 + OpenBudgeteer.Blazor/Pages/Import.razor | 66 +++++++++++--- .../ViewModels/ImportDataViewModel.cs | 90 ++++++++++++++++--- 3 files changed, 135 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b95564b..d5ad31c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ * [Add] Option to set Localization [#52](https://github.com/TheAxelander/OpenBudgeteer/issues/52) * [Add] Enable mapping of seperated columns for Debit and Credit Amount on Import Page [#53](https://github.com/TheAxelander/OpenBudgeteer/issues/53) +* [Add] Duplicate Check before importing data with an option to exclude certain records [#49](https://github.com/TheAxelander/OpenBudgeteer/issues/49) * [Fixed] Moved Sqlite database path back to `/app/database` [#63](https://github.com/TheAxelander/OpenBudgeteer/issues/63) * [Fixed] Crash on Rules Page in case a Bucket has been deleted with an existing RuleSet [#65](https://github.com/TheAxelander/OpenBudgeteer/issues/65) * [Fixed] Include Transactions which are in modification in all filters to prevent immediate disappearance [#67](https://github.com/TheAxelander/OpenBudgeteer/issues/67) diff --git a/OpenBudgeteer.Blazor/Pages/Import.razor b/OpenBudgeteer.Blazor/Pages/Import.razor index c11ea53..8310112 100644 --- a/OpenBudgeteer.Blazor/Pages/Import.razor +++ b/OpenBudgeteer.Blazor/Pages/Import.razor @@ -219,15 +219,16 @@ {
Step 4: Validate and Import Data
- + @if (_dataContext.ValidRecords > 0) { - + }
Total Records: @_dataContext.TotalRecords
Valid Records: @_dataContext.ValidRecords
- Records with errors: @_dataContext.RecordsWithErrors + Records with errors: @_dataContext.RecordsWithErrors
+ Potential Duplicates: @_dataContext.PotentialDuplicates
@if (_validationErrorMessage != string.Empty) { Error message: @_validationErrorMessage @@ -289,6 +290,48 @@
} + + @if (_dataContext.Duplicates.Any()) + { +
+
Potential Duplicates:
+ + + + + + + + + + + + + @foreach (var duplicate in _dataContext.Duplicates) + { + + + + + + + + + foreach (var bankTransaction in duplicate.Item2) + { + + + + + + + + } + } + +
DateAccountPayeeMemoAmount
@duplicate.Item1.Result.TransactionDate.ToShortDateString()@_dataContext.SelectedAccount.Name@duplicate.Item1.Result.Payee@duplicate.Item1.Result.Memo@duplicate.Item1.Result.Amount
@bankTransaction.TransactionDate.ToShortDateString()@_dataContext.SelectedAccount.Name@bankTransaction.Payee@bankTransaction.Memo@bankTransaction.Amount
+
+ } }
@@ -377,14 +420,14 @@ _step2Enabled = true; } - void LoadProfile() + async Task LoadProfile() { // Workaround for setting the SelectedImportProfile as the first item is always selected but doesn't trigger OnChange if (_dataContext.SelectedImportProfile == null && _dataContext.AvailableImportProfiles.Count > 0) { _dataContext.SelectedImportProfile = _dataContext.AvailableImportProfiles.First(); } - var result = _dataContext.LoadProfile(); + var result = await _dataContext.LoadProfileAsync(); if (result.IsSuccessful) { _step3Enabled = _dataContext.SelectedImportProfile.ImportProfileId != 0; @@ -422,17 +465,16 @@ if (string.IsNullOrEmpty(_dataContext.SelectedImportProfile.AmountColumnName) || _dataContext.SelectedImportProfile.AmountColumnName == _placeholderItemValue) return; _step4Enabled = true; - ValidateData(); } - void ValidateData() + async Task ValidateDataAsync() { - _validationErrorMessage = _dataContext.ValidateData().Message; + _validationErrorMessage = (await _dataContext.ValidateDataAsync()).Message; } - void ImportData() + async Task ImportDataAsync() { - var result = _dataContext.ImportData(); + var result = await _dataContext.ImportDataAsync(); _importConfirmationMessage = result.Message; _isConfirmationModalDialogVisible = true; } @@ -449,13 +491,13 @@ HandleResult((_dataContext.LoadData())); } - void ImportProfile_SelectionChanged(ChangeEventArgs e) + async void ImportProfile_SelectionChanged(ChangeEventArgs e) { _step3Enabled = false; _step4Enabled = false; var value = Convert.ToInt32(e.Value); _dataContext.SelectedImportProfile = value == _placeholderItemId ? new ImportProfile() : _dataContext.AvailableImportProfiles.First(i => i.ImportProfileId == value); - if (_dataContext.SelectedImportProfile != null) LoadProfile(); + if (_dataContext.SelectedImportProfile != null) await LoadProfile(); } void TargetAccount_SelectionChanged(ChangeEventArgs e) diff --git a/OpenBudgeteer.Core/ViewModels/ImportDataViewModel.cs b/OpenBudgeteer.Core/ViewModels/ImportDataViewModel.cs index aec5580..97a2e93 100644 --- a/OpenBudgeteer.Core/ViewModels/ImportDataViewModel.cs +++ b/OpenBudgeteer.Core/ViewModels/ImportDataViewModel.cs @@ -167,6 +167,16 @@ public int ValidRecords get => _validRecords; private set => Set(ref _validRecords, value); } + + private int _potentialDuplicates; + /// + /// Number of records which have been identified as potential duplicate + /// + public int PotentialDuplicates + { + get => _potentialDuplicates; + private set => Set(ref _potentialDuplicates, value); + } private ObservableCollection _availableImportProfiles; /// @@ -207,7 +217,17 @@ public ObservableCollection> ParsedRecords get => _parsedRecords; private set => Set(ref _parsedRecords, value); } - + + private ObservableCollection, List>> _duplicates; + /// + /// Collection of all parsed CSV records which are a potential duplicate of existing + /// + public ObservableCollection, List>> Duplicates + { + get => _duplicates; + private set => Set(ref _duplicates, value); + } + private bool _isProfileValid; private string[] _fileLines; private readonly DbContextOptions _dbOptions; @@ -222,6 +242,7 @@ public ImportDataViewModel(DbContextOptions dbOptions) AvailableAccounts = new ObservableCollection(); IdentifiedColumns = new ObservableCollection(); ParsedRecords = new ObservableCollection>(); + Duplicates = new ObservableCollection, List>>(); SelectedImportProfile = new ImportProfile(); SelectedAccount = new Account(); _dbOptions = dbOptions; @@ -326,7 +347,7 @@ public async Task HandleOpenFileAsync(Stream stream) /// Loads all settings based on /// /// Object which contains information and results of this method - public ViewModelOperationResult LoadProfile() + public async Task LoadProfileAsync() { try { @@ -341,7 +362,7 @@ public ViewModelOperationResult LoadProfile() var result = LoadHeaders(); if (!result.IsSuccessful) throw new Exception(result.Message); - ValidateData(); + await ValidateDataAsync(); _isProfileValid = true; return new ViewModelOperationResult(true); @@ -389,6 +410,8 @@ private void ResetLoadedProfileData() TotalRecords = 0; RecordsWithErrors = 0; ValidRecords = 0; + PotentialDuplicates = 0; + Duplicates.Clear(); } /// @@ -399,7 +422,7 @@ private void ResetLoadedProfileData() /// Sets also figures of the ViewModel like or /// /// Object which contains information and results of this method - public ViewModelOperationResult ValidateData() + public async Task ValidateDataAsync() { try { @@ -428,9 +451,8 @@ public ViewModelOperationResult ValidateData() ParsedRecords.Add(parsedResult); } - TotalRecords = parsedResults.Count; - RecordsWithErrors = parsedResults.Count(i => !i.IsValid); - ValidRecords = parsedResults.Count(i => i.IsValid); + await DuplicateCheckOnParsedRecordsAsync(); + UpdateCountValues(); if (ValidRecords > 0) _isProfileValid = true; return new ViewModelOperationResult(true); @@ -440,23 +462,71 @@ public ViewModelOperationResult ValidateData() TotalRecords = 0; RecordsWithErrors = 0; ValidRecords = 0; + PotentialDuplicates = 0; ParsedRecords.Clear(); + Duplicates.Clear(); return new ViewModelOperationResult(false, e.Message); } } + /// + /// Update counts of several statistics + /// + private void UpdateCountValues() + { + TotalRecords = ParsedRecords.Count; + RecordsWithErrors = ParsedRecords.Count(i => !i.IsValid); + ValidRecords = ParsedRecords.Count(i => i.IsValid); + PotentialDuplicates = Duplicates.Count; + } + + /// + /// Checks each parsed CSV records on potential existing + /// + private async Task DuplicateCheckOnParsedRecordsAsync() + { + foreach (var parsedRecord in ParsedRecords.Where(i => i.IsValid).ToList()) + { + var duplicateSearches = Task.Run(() => + { + using (var dbContext = new DatabaseContext(_dbOptions)) + { + var duplicates = dbContext.BankTransaction.Where(i => + i.TransactionDate.Date == parsedRecord.Result.TransactionDate.Date && + i.Amount == parsedRecord.Result.Amount && + (i.Memo == parsedRecord.Result.Memo || i.Payee == parsedRecord.Result.Payee)); + if (duplicates.Any()) + Duplicates.Add(new Tuple, List>(parsedRecord, duplicates.ToList())); + } + }); + + await Task.WhenAll(duplicateSearches); + } + } + + /// + /// Removes the passed duplicate from the parsed records to exclude it from import + /// + /// Duplicate that should be excluded + public void ExcludeDuplicateRecord(Tuple, List> duplicate) + { + ParsedRecords.Remove(duplicate.Item1); + Duplicates.Remove(duplicate); + UpdateCountValues(); + } + /// /// Uses data from to store it in the database /// /// - /// This method will call + /// This method will call /// /// Object which contains information and results of this method - public ViewModelOperationResult ImportData() + public async Task ImportDataAsync() { if (!_isProfileValid) return new ViewModelOperationResult(false, "Unable to Import Data as current settings are invalid."); - ValidateData(); + await ValidateDataAsync(); using (var dbContext = new DatabaseContext(_dbOptions)) { using (var transaction = dbContext.Database.BeginTransaction()) From 1b8f9323ce7b9a29ea1b8979aa5b975518b5cc48 Mon Sep 17 00:00:00 2001 From: Axelander Date: Sun, 13 Feb 2022 20:58:43 +0100 Subject: [PATCH 09/14] Removed unnecessary field from database migrations --- ... => 20220213195537_ImportCreditColumn.Designer.cs} | 5 +---- ...Column.cs => 20220213195537_ImportCreditColumn.cs} | 11 ----------- .../Migrations/MySql/DatabaseServiceModelSnapshot.cs | 3 --- ... => 20220213195523_ImportCreditColumn.Designer.cs} | 5 +---- ...Column.cs => 20220213195523_ImportCreditColumn.cs} | 11 ----------- .../Sqlite/SqliteDatabaseContextModelSnapshot.cs | 3 --- OpenBudgeteer.Core/Models/ImportProfile.cs | 7 ------- 7 files changed, 2 insertions(+), 43 deletions(-) rename OpenBudgeteer.Core/Migrations/MySql/{20220213104939_ImportCreditColumn.Designer.cs => 20220213195537_ImportCreditColumn.Designer.cs} (98%) rename OpenBudgeteer.Core/Migrations/MySql/{20220213104939_ImportCreditColumn.cs => 20220213195537_ImportCreditColumn.cs} (95%) rename OpenBudgeteer.Core/Migrations/Sqlite/{20220213105005_ImportCreditColumn.Designer.cs => 20220213195523_ImportCreditColumn.Designer.cs} (98%) rename OpenBudgeteer.Core/Migrations/Sqlite/{20220213105005_ImportCreditColumn.cs => 20220213195523_ImportCreditColumn.cs} (64%) diff --git a/OpenBudgeteer.Core/Migrations/MySql/20220213104939_ImportCreditColumn.Designer.cs b/OpenBudgeteer.Core/Migrations/MySql/20220213195537_ImportCreditColumn.Designer.cs similarity index 98% rename from OpenBudgeteer.Core/Migrations/MySql/20220213104939_ImportCreditColumn.Designer.cs rename to OpenBudgeteer.Core/Migrations/MySql/20220213195537_ImportCreditColumn.Designer.cs index 174d83e..a6c475f 100644 --- a/OpenBudgeteer.Core/Migrations/MySql/20220213104939_ImportCreditColumn.Designer.cs +++ b/OpenBudgeteer.Core/Migrations/MySql/20220213195537_ImportCreditColumn.Designer.cs @@ -11,7 +11,7 @@ namespace OpenBudgeteer.Core.Migrations.MySql { [DbContext(typeof(MySqlDatabaseContext))] - [Migration("20220213104939_ImportCreditColumn")] + [Migration("20220213195537_ImportCreditColumn")] partial class ImportCreditColumn { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -229,9 +229,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("varchar(1)"); - b.Property("HasPositiveCreditColumnValues") - .HasColumnType("tinyint(1)"); - b.Property("HeaderRow") .HasColumnType("int"); diff --git a/OpenBudgeteer.Core/Migrations/MySql/20220213104939_ImportCreditColumn.cs b/OpenBudgeteer.Core/Migrations/MySql/20220213195537_ImportCreditColumn.cs similarity index 95% rename from OpenBudgeteer.Core/Migrations/MySql/20220213104939_ImportCreditColumn.cs rename to OpenBudgeteer.Core/Migrations/MySql/20220213195537_ImportCreditColumn.cs index 68ed766..0c5559b 100644 --- a/OpenBudgeteer.Core/Migrations/MySql/20220213104939_ImportCreditColumn.cs +++ b/OpenBudgeteer.Core/Migrations/MySql/20220213195537_ImportCreditColumn.cs @@ -14,13 +14,6 @@ protected override void Up(MigrationBuilder migrationBuilder) type: "longtext", nullable: true) .Annotation("MySql:CharSet", "utf8mb4"); - - migrationBuilder.AddColumn( - name: "HasPositiveCreditColumnValues", - table: "ImportProfile", - type: "tinyint(1)", - nullable: false, - defaultValue: false); } protected override void Down(MigrationBuilder migrationBuilder) @@ -29,10 +22,6 @@ protected override void Down(MigrationBuilder migrationBuilder) name: "CreditColumnName", table: "ImportProfile"); - migrationBuilder.DropColumn( - name: "HasPositiveCreditColumnValues", - table: "ImportProfile"); - migrationBuilder.AlterColumn( name: "ComparisionValue", table: "MappingRule", diff --git a/OpenBudgeteer.Core/Migrations/MySql/DatabaseServiceModelSnapshot.cs b/OpenBudgeteer.Core/Migrations/MySql/DatabaseServiceModelSnapshot.cs index 85c4291..c581184 100644 --- a/OpenBudgeteer.Core/Migrations/MySql/DatabaseServiceModelSnapshot.cs +++ b/OpenBudgeteer.Core/Migrations/MySql/DatabaseServiceModelSnapshot.cs @@ -227,9 +227,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("varchar(1)"); - b.Property("HasPositiveCreditColumnValues") - .HasColumnType("tinyint(1)"); - b.Property("HeaderRow") .HasColumnType("int"); diff --git a/OpenBudgeteer.Core/Migrations/Sqlite/20220213105005_ImportCreditColumn.Designer.cs b/OpenBudgeteer.Core/Migrations/Sqlite/20220213195523_ImportCreditColumn.Designer.cs similarity index 98% rename from OpenBudgeteer.Core/Migrations/Sqlite/20220213105005_ImportCreditColumn.Designer.cs rename to OpenBudgeteer.Core/Migrations/Sqlite/20220213195523_ImportCreditColumn.Designer.cs index 918a7d0..49311e3 100644 --- a/OpenBudgeteer.Core/Migrations/Sqlite/20220213105005_ImportCreditColumn.Designer.cs +++ b/OpenBudgeteer.Core/Migrations/Sqlite/20220213195523_ImportCreditColumn.Designer.cs @@ -11,7 +11,7 @@ namespace OpenBudgeteer.Core.Migrations.Sqlite { [DbContext(typeof(SqliteDatabaseContext))] - [Migration("20220213105005_ImportCreditColumn")] + [Migration("20220213195523_ImportCreditColumn")] partial class ImportCreditColumn { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -224,9 +224,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Delimiter") .HasColumnType("TEXT"); - b.Property("HasPositiveCreditColumnValues") - .HasColumnType("INTEGER"); - b.Property("HeaderRow") .HasColumnType("INTEGER"); diff --git a/OpenBudgeteer.Core/Migrations/Sqlite/20220213105005_ImportCreditColumn.cs b/OpenBudgeteer.Core/Migrations/Sqlite/20220213195523_ImportCreditColumn.cs similarity index 64% rename from OpenBudgeteer.Core/Migrations/Sqlite/20220213105005_ImportCreditColumn.cs rename to OpenBudgeteer.Core/Migrations/Sqlite/20220213195523_ImportCreditColumn.cs index 380a0ad..3334761 100644 --- a/OpenBudgeteer.Core/Migrations/Sqlite/20220213105005_ImportCreditColumn.cs +++ b/OpenBudgeteer.Core/Migrations/Sqlite/20220213195523_ImportCreditColumn.cs @@ -13,13 +13,6 @@ protected override void Up(MigrationBuilder migrationBuilder) table: "ImportProfile", type: "TEXT", nullable: true); - - migrationBuilder.AddColumn( - name: "HasPositiveCreditColumnValues", - table: "ImportProfile", - type: "INTEGER", - nullable: false, - defaultValue: false); } protected override void Down(MigrationBuilder migrationBuilder) @@ -27,10 +20,6 @@ protected override void Down(MigrationBuilder migrationBuilder) migrationBuilder.DropColumn( name: "CreditColumnName", table: "ImportProfile"); - - migrationBuilder.DropColumn( - name: "HasPositiveCreditColumnValues", - table: "ImportProfile"); } } } diff --git a/OpenBudgeteer.Core/Migrations/Sqlite/SqliteDatabaseContextModelSnapshot.cs b/OpenBudgeteer.Core/Migrations/Sqlite/SqliteDatabaseContextModelSnapshot.cs index d6cdb21..cb3d93b 100644 --- a/OpenBudgeteer.Core/Migrations/Sqlite/SqliteDatabaseContextModelSnapshot.cs +++ b/OpenBudgeteer.Core/Migrations/Sqlite/SqliteDatabaseContextModelSnapshot.cs @@ -222,9 +222,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Delimiter") .HasColumnType("TEXT"); - b.Property("HasPositiveCreditColumnValues") - .HasColumnType("INTEGER"); - b.Property("HeaderRow") .HasColumnType("INTEGER"); diff --git a/OpenBudgeteer.Core/Models/ImportProfile.cs b/OpenBudgeteer.Core/Models/ImportProfile.cs index bc18e09..3de186e 100644 --- a/OpenBudgeteer.Core/Models/ImportProfile.cs +++ b/OpenBudgeteer.Core/Models/ImportProfile.cs @@ -92,11 +92,4 @@ public string CreditColumnName get => _creditColumnName; set => Set(ref _creditColumnName, value); } - - private bool _hasPositiveCreditColumnValues; - public bool HasPositiveCreditColumnValues - { - get => _hasPositiveCreditColumnValues; - set => Set(ref _hasPositiveCreditColumnValues, value); - } } From 4783f2ac6cf672724b09c8e94ff0a08d8add2462 Mon Sep 17 00:00:00 2001 From: Axelander Date: Sun, 13 Feb 2022 21:52:19 +0100 Subject: [PATCH 10/14] Update Nuget Packages --- OpenBudgeteer.Blazor/OpenBudgeteer.Blazor.csproj | 2 +- OpenBudgeteer.Core.Test/OpenBudgeteer.Core.Test.csproj | 2 +- OpenBudgeteer.Core/OpenBudgeteer.Core.csproj | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/OpenBudgeteer.Blazor/OpenBudgeteer.Blazor.csproj b/OpenBudgeteer.Blazor/OpenBudgeteer.Blazor.csproj index 8c663b1..5dca0c8 100644 --- a/OpenBudgeteer.Blazor/OpenBudgeteer.Blazor.csproj +++ b/OpenBudgeteer.Blazor/OpenBudgeteer.Blazor.csproj @@ -9,7 +9,7 @@ - + diff --git a/OpenBudgeteer.Core.Test/OpenBudgeteer.Core.Test.csproj b/OpenBudgeteer.Core.Test/OpenBudgeteer.Core.Test.csproj index 5166efd..eb8e98d 100644 --- a/OpenBudgeteer.Core.Test/OpenBudgeteer.Core.Test.csproj +++ b/OpenBudgeteer.Core.Test/OpenBudgeteer.Core.Test.csproj @@ -10,7 +10,7 @@ - + diff --git a/OpenBudgeteer.Core/OpenBudgeteer.Core.csproj b/OpenBudgeteer.Core/OpenBudgeteer.Core.csproj index bc4494c..ce6d7e9 100644 --- a/OpenBudgeteer.Core/OpenBudgeteer.Core.csproj +++ b/OpenBudgeteer.Core/OpenBudgeteer.Core.csproj @@ -5,12 +5,12 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + From d335e8ed7a9b3418b83b9ad2c79693861e3e9588 Mon Sep 17 00:00:00 2001 From: Axelander Date: Sun, 13 Feb 2022 22:09:25 +0100 Subject: [PATCH 11/14] Fixed missing Duplicates clearing --- OpenBudgeteer.Core/ViewModels/ImportDataViewModel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OpenBudgeteer.Core/ViewModels/ImportDataViewModel.cs b/OpenBudgeteer.Core/ViewModels/ImportDataViewModel.cs index 97a2e93..286a49c 100644 --- a/OpenBudgeteer.Core/ViewModels/ImportDataViewModel.cs +++ b/OpenBudgeteer.Core/ViewModels/ImportDataViewModel.cs @@ -446,6 +446,7 @@ public async Task ValidateDataAsync() var parsedResults = csvParser.ReadFromString(csvReaderOptions, stringBuilder.ToString()).ToList(); ParsedRecords.Clear(); + Duplicates.Clear(); foreach (var parsedResult in parsedResults) { ParsedRecords.Add(parsedResult); @@ -526,7 +527,7 @@ public async Task ImportDataAsync() { if (!_isProfileValid) return new ViewModelOperationResult(false, "Unable to Import Data as current settings are invalid."); - await ValidateDataAsync(); + //await ValidateDataAsync(); using (var dbContext = new DatabaseContext(_dbOptions)) { using (var transaction = dbContext.Database.BeginTransaction()) From d9eb3cb9dfad0a9b52855fc1c33d22a6868351d2 Mon Sep 17 00:00:00 2001 From: Axelander Date: Sun, 13 Feb 2022 22:12:34 +0100 Subject: [PATCH 12/14] Removed re-validation before importing data to consider removed duplicates --- OpenBudgeteer.Core/ViewModels/ImportDataViewModel.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/OpenBudgeteer.Core/ViewModels/ImportDataViewModel.cs b/OpenBudgeteer.Core/ViewModels/ImportDataViewModel.cs index 286a49c..5200641 100644 --- a/OpenBudgeteer.Core/ViewModels/ImportDataViewModel.cs +++ b/OpenBudgeteer.Core/ViewModels/ImportDataViewModel.cs @@ -519,15 +519,11 @@ public void ExcludeDuplicateRecord(Tuple, List /// /// Uses data from to store it in the database /// - /// - /// This method will call - /// /// Object which contains information and results of this method public async Task ImportDataAsync() { if (!_isProfileValid) return new ViewModelOperationResult(false, "Unable to Import Data as current settings are invalid."); - //await ValidateDataAsync(); using (var dbContext = new DatabaseContext(_dbOptions)) { using (var transaction = dbContext.Database.BeginTransaction()) From 938a9e64f356863ccf2367aeef7198a242c889ef Mon Sep 17 00:00:00 2001 From: Axelander Date: Wed, 16 Feb 2022 18:14:21 +0100 Subject: [PATCH 13/14] Add Test Cases for ImportDataViewModel --- .../OpenBudgeteer.Core.Test.csproj | 22 + .../Resources/TestImportFile1.txt | 15 + .../Resources/TestImportFile2.txt | 15 + .../Resources/TestImportFile3.txt | 15 + .../Resources/TestImportFile4.txt | 15 + .../ViewModelTest/ImportDataViewModelTest.cs | 630 ++++++++++++++++++ 6 files changed, 712 insertions(+) create mode 100644 OpenBudgeteer.Core.Test/Resources/TestImportFile1.txt create mode 100644 OpenBudgeteer.Core.Test/Resources/TestImportFile2.txt create mode 100644 OpenBudgeteer.Core.Test/Resources/TestImportFile3.txt create mode 100644 OpenBudgeteer.Core.Test/Resources/TestImportFile4.txt create mode 100644 OpenBudgeteer.Core.Test/ViewModelTest/ImportDataViewModelTest.cs diff --git a/OpenBudgeteer.Core.Test/OpenBudgeteer.Core.Test.csproj b/OpenBudgeteer.Core.Test/OpenBudgeteer.Core.Test.csproj index eb8e98d..91eae39 100644 --- a/OpenBudgeteer.Core.Test/OpenBudgeteer.Core.Test.csproj +++ b/OpenBudgeteer.Core.Test/OpenBudgeteer.Core.Test.csproj @@ -9,6 +9,28 @@ net6.0 + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + diff --git a/OpenBudgeteer.Core.Test/Resources/TestImportFile1.txt b/OpenBudgeteer.Core.Test/Resources/TestImportFile1.txt new file mode 100644 index 0000000..77bd5c2 --- /dev/null +++ b/OpenBudgeteer.Core.Test/Resources/TestImportFile1.txt @@ -0,0 +1,15 @@ +"Account:";"DE0123456789 / My Bank Account"; + +"This file contains the following details:"; +"Valid Records:";"4"; +"Invalid Records:";"0"; +"Delimiter:";";"; +"Text Qualifier:";"""; +"Date Format:";"dd.MM.yyyy"; +"Number Format:";"de-DE"; + +"Accounting Date";"Date";"Type";"Payee";"Memo";"IBAN";"Amount (EUR)"; +"14.02.2022";"14.02.2022";"Online Payment";"Lorem ipsum";"dolor sit amet";"DE987654321";"-2,95"; +"15.02.2022";"15.02.2022";"Credit Card";"Foobar Company";"";"DE987654321";"-27,50"; +"16.02.2022";"16.02.2022";"Bank Transfer";"EMPLOYER";"Income Feb/2022";"DE987654321";"43,00"; +"17.02.2022";"17.02.2022";"Bank Transfer";"The Webshop.com";"Billing";"DE987654321";"-6,34"; \ No newline at end of file diff --git a/OpenBudgeteer.Core.Test/Resources/TestImportFile2.txt b/OpenBudgeteer.Core.Test/Resources/TestImportFile2.txt new file mode 100644 index 0000000..1ea1a31 --- /dev/null +++ b/OpenBudgeteer.Core.Test/Resources/TestImportFile2.txt @@ -0,0 +1,15 @@ +"Account:";"DE0123456789 / My Bank Account"; + +"This file contains the following details:"; +"Valid Records:";"4"; +"Invalid Records:";"0"; +"Delimiter:";";"; +"Text Qualifier:";"""; +"Date Format:";"dd.MM.yyyy"; +"Number Format:";"de-DE"; + +"Accounting Date";"Date";"Type";"Payee";"Memo";"IBAN";"Debit (EUR)";"Credit (EUR)"; +"14.02.2022";"14.02.2022";"Online Payment";"Lorem ipsum";"dolor sit amet";"DE987654321";"";"-2,95"; +"15.02.2022";"15.02.2022";"Credit Card";"Foobar Company";"";"DE987654321";"";"-27,50"; +"16.02.2022";"16.02.2022";"Bank Transfer";"EMPLOYER";"Income Feb/2022";"DE987654321";"43,00";""; +"17.02.2022";"17.02.2022";"Bank Transfer";"The Webshop.com";"Billing";"DE987654321";"";"6,34"; \ No newline at end of file diff --git a/OpenBudgeteer.Core.Test/Resources/TestImportFile3.txt b/OpenBudgeteer.Core.Test/Resources/TestImportFile3.txt new file mode 100644 index 0000000..1196a0d --- /dev/null +++ b/OpenBudgeteer.Core.Test/Resources/TestImportFile3.txt @@ -0,0 +1,15 @@ +"Account:";"DE0123456789 / My Bank Account"; + +"This file contains the following details:"; +"Valid Records:";"2"; +"Invalid Records:";"2"; +"Delimiter:";";"; +"Text Qualifier:";"""; +"Date Format:";"dd.MM.yyyy"; +"Number Format:";"de-DE"; + +"Accounting Date";"Date";"Type";"Payee";"Memo";"IBAN";"Amount (EUR)"; +"14.02.2022";"14.02.2022";"Online Payment";"Lorem ipsum";"dolor sit amet";"DE987654321";"-2,95"; +"15.02.2022";"15.02.2022";"Credit Card";"Foobar Company";"";"DE987654321";"abc"; +"16.02.2022";"16.02.2022";"Bank Transfer";"EMPLOYER";"Income Feb/2022";"DE987654321";"43,00"; +"17.02.2022";"17.02.2022";"Bank Transfer";"The Webshop.com";"Billing";"DE987654321";"","-6,34"; \ No newline at end of file diff --git a/OpenBudgeteer.Core.Test/Resources/TestImportFile4.txt b/OpenBudgeteer.Core.Test/Resources/TestImportFile4.txt new file mode 100644 index 0000000..7b64cf2 --- /dev/null +++ b/OpenBudgeteer.Core.Test/Resources/TestImportFile4.txt @@ -0,0 +1,15 @@ +'Account:','DE0123456789 / My Bank Account', + +'This file contains the following details:', +'Valid Records:','4', +'Invalid Records:','0', +'Delimiter:',',', +'Text Qualifier:',''', +'Date Format:','yyyy-MM-dd', +'Number Format:','en-US', + +'Accounting Date','Date','Type','Payee','Memo','IBAN','Amount (USD)', +'2022-02-14','2022-02-14','Online Payment','Lorem ipsum','dolor sit amet','DE987654321','-2.95', +'2022-02-15','2022-02-15','Credit Card','Foobar Company','','DE987654321','-27.50', +'2022-02-16','2022-02-16','Bank Transfer','EMPLOYER','Income Feb/2022','DE987654321','43.00', +'2022-02-17','2022-02-17','Bank Transfer','The Webshop.com','Billing','DE987654321','-6.34', \ No newline at end of file diff --git a/OpenBudgeteer.Core.Test/ViewModelTest/ImportDataViewModelTest.cs b/OpenBudgeteer.Core.Test/ViewModelTest/ImportDataViewModelTest.cs new file mode 100644 index 0000000..97af012 --- /dev/null +++ b/OpenBudgeteer.Core.Test/ViewModelTest/ImportDataViewModelTest.cs @@ -0,0 +1,630 @@ +using Microsoft.EntityFrameworkCore; +using OpenBudgeteer.Core.Common.Database; +using OpenBudgeteer.Core.Models; +using OpenBudgeteer.Core.ViewModels; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace OpenBudgeteer.Core.Test.ViewModelTest; +public class ImportDataViewModelTest +{ + private readonly DbContextOptions _dbOptions; + private readonly Account _testAccount; + + public ImportDataViewModelTest() + { + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); // Required to read ANSI Text files + _dbOptions = DbConnector.GetDbContextOptions(nameof(ImportDataViewModelTest)); + _testAccount = new Account() { Name = "Test Account", IsActive = 1 }; + using (var dbContext = new DatabaseContext(_dbOptions)) + { + dbContext.CreateAccount(_testAccount); + } + } + + public static IEnumerable TestData_LoadData_CheckAvailableProfiles + { + get + { + return new[] + { + new object[] + { + new ImportProfile() + { + ProfileName = "Test Profile", + + TransactionDateColumnName = "Date", + PayeeColumnName = "Payee", + MemoColumnName = "Memo", + AmountColumnName = "Amount", + CreditColumnName = "Credit", + + Delimiter = ';', + TextQualifier = '"', + DateFormat = "dd.MM.yyyy", + NumberFormat = "de-DE", + HeaderRow = 2 + } + } + }; + } + } + + [Theory] + [MemberData(nameof(TestData_LoadData_CheckAvailableProfiles))] + public void LoadData_CheckAvailableProfiles( + ImportProfile importProfile) + { + try + { + using (var dbContext = new DatabaseContext(_dbOptions)) + { + var inactiveTestAccount = new Account() { Name = "Inactive Test Account", IsActive = 0 }; + dbContext.CreateAccount(inactiveTestAccount); + + importProfile.AccountId = _testAccount.AccountId; + dbContext.CreateImportProfile(importProfile); + + var viewModel = new ImportDataViewModel(_dbOptions); + viewModel.LoadData(); + var loadedImportProfile = viewModel.AvailableImportProfiles.Single( + i => i.ImportProfileId == importProfile.ImportProfileId); + + var foo = viewModel.AvailableAccounts; + var bar = dbContext.Account; + + Assert.Single(viewModel.AvailableAccounts); + Assert.Equal(_testAccount.Name, viewModel.AvailableAccounts.First().Name); + Assert.Equal(_testAccount.IsActive, viewModel.AvailableAccounts.First().IsActive); + + Assert.Single(viewModel.AvailableImportProfiles); + Assert.Equal(importProfile.ProfileName, loadedImportProfile.ProfileName); + Assert.Equal(importProfile.AccountId, loadedImportProfile.AccountId); + Assert.Equal(importProfile.TransactionDateColumnName, loadedImportProfile.TransactionDateColumnName); + Assert.Equal(importProfile.PayeeColumnName, loadedImportProfile.PayeeColumnName); + Assert.Equal(importProfile.MemoColumnName, loadedImportProfile.MemoColumnName); + Assert.Equal(importProfile.AmountColumnName, loadedImportProfile.AmountColumnName); + Assert.Equal(importProfile.CreditColumnName, loadedImportProfile.CreditColumnName); + Assert.Equal(importProfile.Delimiter, loadedImportProfile.Delimiter); + Assert.Equal(importProfile.TextQualifier, loadedImportProfile.TextQualifier); + Assert.Equal(importProfile.DateFormat, loadedImportProfile.DateFormat); + Assert.Equal(importProfile.NumberFormat, loadedImportProfile.NumberFormat); + Assert.Equal(importProfile.HeaderRow, loadedImportProfile.HeaderRow); + } + } + finally + { + DbConnector.CleanupDatabase(nameof(ImportDataViewModelTest)); + } + } + + public static IEnumerable TestData_LoadProfileAsync_CheckSelectedImportProfileHeaders + { + get + { + return new[] + { + new object[] + { + new ImportProfile() + { + ProfileName = "Test Profile", + + TransactionDateColumnName = "Date", + PayeeColumnName = "Payee", + MemoColumnName = "Memo", + AmountColumnName = "Amount (EUR)", + CreditColumnName = "Credit", + + Delimiter = ';', + TextQualifier = '"', + DateFormat = "dd.MM.yyyy", + NumberFormat = "de-DE", + HeaderRow = 11 + }, + "./Resources/TestImportFile1.txt", + new List {"Accounting Date", "Date", "Type", "Payee", "Memo", "IBAN", "Amount (EUR)"} + } + }; + } + } + + [Theory] + [MemberData(nameof(TestData_LoadProfileAsync_CheckSelectedImportProfileHeaders))] + public async Task LoadProfileAsync_CheckSelectedImportProfileHeaders( + ImportProfile importProfile, + string testFilePath, + List fileHeaders) + { + try + { + using (var dbContext = new DatabaseContext(_dbOptions)) + { + importProfile.AccountId = _testAccount.AccountId; + dbContext.CreateImportProfile(importProfile); + + var viewModel = new ImportDataViewModel(_dbOptions); + viewModel.LoadData(); + + await viewModel.HandleOpenFileAsync(File.OpenRead(testFilePath)); + viewModel.SelectedImportProfile = viewModel.AvailableImportProfiles.Single( + i => i.ImportProfileId == importProfile.ImportProfileId); + await viewModel.LoadProfileAsync(); + + Assert.Equal(_testAccount.AccountId, viewModel.SelectedAccount.AccountId); + + Assert.Equal(7, viewModel.IdentifiedColumns.Count); + for (int i = 0; i < viewModel.IdentifiedColumns.Count; i++) + { + Assert.Equal(fileHeaders[i], viewModel.IdentifiedColumns[i]); + } + } + } + finally + { + DbConnector.CleanupDatabase(nameof(ImportDataViewModelTest)); + } + } + + public static IEnumerable TestData_LoadProfileAsync_CheckValidateData + { + get + { + return new[] + { + new object[] + { + new ImportProfile() + { + ProfileName = "Test Profile", + + TransactionDateColumnName = "Date", + PayeeColumnName = "Payee", + MemoColumnName = "Memo", + AmountColumnName = "Amount (EUR)", + CreditColumnName = string.Empty, + + Delimiter = ';', + TextQualifier = '"', + DateFormat = "dd.MM.yyyy", + NumberFormat = "de-DE", + HeaderRow = 11 + }, + "./Resources/TestImportFile1.txt" + } + }; + } + } + + [Theory] + [MemberData(nameof(TestData_LoadProfileAsync_CheckValidateData))] + public async Task LoadProfileAsync_CheckValidateData( + ImportProfile importProfile, + string testFilePath) + { + try + { + using (var dbContext = new DatabaseContext(_dbOptions)) + { + importProfile.AccountId = _testAccount.AccountId; + dbContext.CreateImportProfile(importProfile); + + var viewModel = new ImportDataViewModel(_dbOptions); + viewModel.LoadData(); + + await viewModel.HandleOpenFileAsync(File.OpenRead(testFilePath)); + viewModel.SelectedImportProfile = viewModel.AvailableImportProfiles.Single( + i => i.ImportProfileId == importProfile.ImportProfileId); + await viewModel.LoadProfileAsync(); + + Assert.Equal(4, viewModel.TotalRecords); + Assert.Equal(4, viewModel.ValidRecords); + Assert.Equal(0, viewModel.RecordsWithErrors); + Assert.Equal(0, viewModel.PotentialDuplicates); + Assert.Equal(4, viewModel.ParsedRecords.Count); + Assert.Empty(viewModel.Duplicates); + + // Check Record 1 + var checkRecord = viewModel.ParsedRecords[0].Result; + Assert.Equal(new DateTime(2022,02,14), checkRecord.TransactionDate); + Assert.Equal("Lorem ipsum", checkRecord.Payee); + Assert.Equal("dolor sit amet", checkRecord.Memo); + Assert.Equal(new decimal(-2.95), checkRecord.Amount); + + // Check Record 2 + checkRecord = viewModel.ParsedRecords[1].Result; + Assert.Equal(new DateTime(2022, 02, 15), checkRecord.TransactionDate); + Assert.Equal("Foobar Company", checkRecord.Payee); + Assert.Equal(string.Empty, checkRecord.Memo); + Assert.Equal(new decimal(-27.5), checkRecord.Amount); + + // Check Record 3 + checkRecord = viewModel.ParsedRecords[2].Result; + Assert.Equal(new DateTime(2022, 02, 16), checkRecord.TransactionDate); + Assert.Equal("EMPLOYER", checkRecord.Payee); + Assert.Equal("Income Feb/2022", checkRecord.Memo); + Assert.Equal(new decimal(43), checkRecord.Amount); + + // Check Record 4 + checkRecord = viewModel.ParsedRecords[3].Result; + Assert.Equal(new DateTime(2022, 02, 17), checkRecord.TransactionDate); + Assert.Equal("The Webshop.com", checkRecord.Payee); + Assert.Equal("Billing", checkRecord.Memo); + Assert.Equal(new decimal(-6.34), checkRecord.Amount); + } + } + finally + { + DbConnector.CleanupDatabase(nameof(ImportDataViewModelTest)); + } + } + + public static IEnumerable TestData_LoadProfileAsync_CheckValidateDataWithCreditColumn + { + get + { + return new[] + { + new object[] + { + new ImportProfile() + { + ProfileName = "Test Profile", + + TransactionDateColumnName = "Date", + PayeeColumnName = "Payee", + MemoColumnName = "Memo", + AmountColumnName = "Debit (EUR)", + CreditColumnName = "Credit (EUR)", + + Delimiter = ';', + TextQualifier = '"', + DateFormat = "dd.MM.yyyy", + NumberFormat = "de-DE", + HeaderRow = 11 + }, + "./Resources/TestImportFile2.txt" + } + }; + } + } + + [Theory] + [MemberData(nameof(TestData_LoadProfileAsync_CheckValidateDataWithCreditColumn))] + public async Task LoadProfileAsync_CheckValidateDataWithCreditColumn( + ImportProfile importProfile, + string testFilePath) + { + try + { + using (var dbContext = new DatabaseContext(_dbOptions)) + { + importProfile.AccountId = _testAccount.AccountId; + dbContext.CreateImportProfile(importProfile); + + var viewModel = new ImportDataViewModel(_dbOptions); + viewModel.LoadData(); + + await viewModel.HandleOpenFileAsync(File.OpenRead(testFilePath)); + viewModel.SelectedImportProfile = viewModel.AvailableImportProfiles.Single( + i => i.ImportProfileId == importProfile.ImportProfileId); + await viewModel.LoadProfileAsync(); + + Assert.Equal(4, viewModel.TotalRecords); + Assert.Equal(4, viewModel.ValidRecords); + Assert.Equal(0, viewModel.RecordsWithErrors); + Assert.Equal(0, viewModel.PotentialDuplicates); + Assert.Equal(4, viewModel.ParsedRecords.Count); + Assert.Empty(viewModel.Duplicates); + + // Check Record 1 + var checkRecord = viewModel.ParsedRecords[0].Result; + Assert.Equal(new DateTime(2022, 02, 14), checkRecord.TransactionDate); + Assert.Equal("Lorem ipsum", checkRecord.Payee); + Assert.Equal("dolor sit amet", checkRecord.Memo); + Assert.Equal(new decimal(-2.95), checkRecord.Amount); + + // Check Record 2 + checkRecord = viewModel.ParsedRecords[1].Result; + Assert.Equal(new DateTime(2022, 02, 15), checkRecord.TransactionDate); + Assert.Equal("Foobar Company", checkRecord.Payee); + Assert.Equal(string.Empty, checkRecord.Memo); + Assert.Equal(new decimal(-27.5), checkRecord.Amount); + + // Check Record 3 + checkRecord = viewModel.ParsedRecords[2].Result; + Assert.Equal(new DateTime(2022, 02, 16), checkRecord.TransactionDate); + Assert.Equal("EMPLOYER", checkRecord.Payee); + Assert.Equal("Income Feb/2022", checkRecord.Memo); + Assert.Equal(new decimal(43), checkRecord.Amount); + + // Check Record 4 + // Credit Column value is positive in file and should be negative after import + checkRecord = viewModel.ParsedRecords[3].Result; + Assert.Equal(new DateTime(2022, 02, 17), checkRecord.TransactionDate); + Assert.Equal("The Webshop.com", checkRecord.Payee); + Assert.Equal("Billing", checkRecord.Memo); + Assert.Equal(new decimal(-6.34), checkRecord.Amount); + } + } + finally + { + DbConnector.CleanupDatabase(nameof(ImportDataViewModelTest)); + } + } + + public static IEnumerable TestData_LoadProfileAsync_CheckValidateDataWithInvalidRecords + { + get + { + return new[] + { + new object[] + { + new ImportProfile() + { + ProfileName = "Test Profile", + + TransactionDateColumnName = "Date", + PayeeColumnName = "Payee", + MemoColumnName = "Memo", + AmountColumnName = "Amount (EUR)", + CreditColumnName = string.Empty, + + Delimiter = ';', + TextQualifier = '"', + DateFormat = "dd.MM.yyyy", + NumberFormat = "de-DE", + HeaderRow = 11 + }, + "./Resources/TestImportFile3.txt" + } + }; + } + } + + [Theory] + [MemberData(nameof(TestData_LoadProfileAsync_CheckValidateDataWithInvalidRecords))] + public async Task LoadProfileAsync_CheckValidateDataWithInvalidRecords( + ImportProfile importProfile, + string testFilePath) + { + try + { + using (var dbContext = new DatabaseContext(_dbOptions)) + { + importProfile.AccountId = _testAccount.AccountId; + dbContext.CreateImportProfile(importProfile); + + var viewModel = new ImportDataViewModel(_dbOptions); + viewModel.LoadData(); + + await viewModel.HandleOpenFileAsync(File.OpenRead(testFilePath)); + viewModel.SelectedImportProfile = viewModel.AvailableImportProfiles.Single( + i => i.ImportProfileId == importProfile.ImportProfileId); + await viewModel.LoadProfileAsync(); + + Assert.Equal(4, viewModel.TotalRecords); + Assert.Equal(2, viewModel.ValidRecords); + Assert.Equal(2, viewModel.RecordsWithErrors); + Assert.Equal(0, viewModel.PotentialDuplicates); + Assert.Equal(4, viewModel.ParsedRecords.Count); + Assert.Empty(viewModel.Duplicates); + + // Check Valid Record 1 + var checkRecord = viewModel.ParsedRecords[0].Result; + Assert.Equal(new DateTime(2022, 02, 14), checkRecord.TransactionDate); + Assert.Equal("Lorem ipsum", checkRecord.Payee); + Assert.Equal("dolor sit amet", checkRecord.Memo); + Assert.Equal(new decimal(-2.95), checkRecord.Amount); + + // Check Valid Record 2 + checkRecord = viewModel.ParsedRecords[2].Result; + Assert.Equal(new DateTime(2022, 02, 16), checkRecord.TransactionDate); + Assert.Equal("EMPLOYER", checkRecord.Payee); + Assert.Equal("Income Feb/2022", checkRecord.Memo); + Assert.Equal(new decimal(43), checkRecord.Amount); + } + } + finally + { + DbConnector.CleanupDatabase(nameof(ImportDataViewModelTest)); + } + } + + public static IEnumerable TestData_LoadProfileAsync_CheckValidateDataWithDuplicates + { + get + { + return new[] + { + new object[] + { + new ImportProfile() + { + ProfileName = "Test Profile", + + TransactionDateColumnName = "Date", + PayeeColumnName = "Payee", + MemoColumnName = "Memo", + AmountColumnName = "Amount (EUR)", + CreditColumnName = string.Empty, + + Delimiter = ';', + TextQualifier = '"', + DateFormat = "dd.MM.yyyy", + NumberFormat = "de-DE", + HeaderRow = 11 + }, + "./Resources/TestImportFile3.txt", + "./Resources/TestImportFile1.txt" + } + }; + } + } + + [Theory] + [MemberData(nameof(TestData_LoadProfileAsync_CheckValidateDataWithDuplicates))] + public async Task LoadProfileAsync_CheckValidateDataWithDuplicates( + ImportProfile importProfile, + string testFilePath1, + string testFilePath2) + { + try + { + using (var dbContext = new DatabaseContext(_dbOptions)) + { + importProfile.AccountId = _testAccount.AccountId; + dbContext.CreateImportProfile(importProfile); + + var viewModel = new ImportDataViewModel(_dbOptions); + viewModel.LoadData(); + + await viewModel.HandleOpenFileAsync(File.OpenRead(testFilePath1)); + viewModel.SelectedImportProfile = viewModel.AvailableImportProfiles.Single( + i => i.ImportProfileId == importProfile.ImportProfileId); + await viewModel.LoadProfileAsync(); + + Assert.Equal(4, viewModel.TotalRecords); + Assert.Equal(2, viewModel.ValidRecords); + Assert.Equal(2, viewModel.RecordsWithErrors); + Assert.Equal(0, viewModel.PotentialDuplicates); + Assert.Equal(4, viewModel.ParsedRecords.Count); + Assert.Empty(viewModel.Duplicates); + + await viewModel.ImportDataAsync(); + + Assert.Equal(2, dbContext.BankTransaction.Count()); + + // Load next file including two duplicates on existing BankTransaction + await viewModel.HandleOpenFileAsync(File.OpenRead(testFilePath2)); + await viewModel.ValidateDataAsync(); + + Assert.Equal(4, viewModel.TotalRecords); + Assert.Equal(4, viewModel.ValidRecords); + Assert.Equal(0, viewModel.RecordsWithErrors); + Assert.Equal(2, viewModel.PotentialDuplicates); + Assert.Equal(4, viewModel.ParsedRecords.Count); + Assert.Equal(2, viewModel.Duplicates.Count); + + // Exclude just one duplicate and import the rest + viewModel.ExcludeDuplicateRecord(viewModel.Duplicates.First()); + + Assert.Equal(3, viewModel.TotalRecords); + Assert.Equal(3, viewModel.ValidRecords); + Assert.Equal(0, viewModel.RecordsWithErrors); + Assert.Equal(1, viewModel.PotentialDuplicates); + Assert.Equal(3, viewModel.ParsedRecords.Count); + Assert.Single(viewModel.Duplicates); + + await viewModel.ImportDataAsync(); + + Assert.Equal(5, dbContext.BankTransaction.Count()); + } + } + finally + { + DbConnector.CleanupDatabase(nameof(ImportDataViewModelTest)); + } + } + + public static IEnumerable TestData_LoadProfileAsync_CheckValidateDataWithDifferentSettings + { + get + { + return new[] + { + new object[] + { + new ImportProfile() + { + ProfileName = "Test Profile", + + TransactionDateColumnName = "Date", + PayeeColumnName = "Payee", + MemoColumnName = "Memo", + AmountColumnName = "Amount (USD)", + CreditColumnName = string.Empty, + + Delimiter = ',', + TextQualifier = '\'', + DateFormat = "yyyy-MM-dd", + NumberFormat = "en-US", + HeaderRow = 11 + }, + "./Resources/TestImportFile4.txt" + } + }; + } + } + + [Theory] + [MemberData(nameof(TestData_LoadProfileAsync_CheckValidateDataWithDifferentSettings))] + public async Task LoadProfileAsync_CheckValidateDataWithDifferentSettings( + ImportProfile importProfile, + string testFilePath) + { + try + { + using (var dbContext = new DatabaseContext(_dbOptions)) + { + importProfile.AccountId = _testAccount.AccountId; + dbContext.CreateImportProfile(importProfile); + + var viewModel = new ImportDataViewModel(_dbOptions); + viewModel.LoadData(); + + await viewModel.HandleOpenFileAsync(File.OpenRead(testFilePath)); + viewModel.SelectedImportProfile = viewModel.AvailableImportProfiles.Single( + i => i.ImportProfileId == importProfile.ImportProfileId); + await viewModel.LoadProfileAsync(); + + Assert.Equal(4, viewModel.TotalRecords); + Assert.Equal(4, viewModel.ValidRecords); + Assert.Equal(0, viewModel.RecordsWithErrors); + Assert.Equal(0, viewModel.PotentialDuplicates); + Assert.Equal(4, viewModel.ParsedRecords.Count); + Assert.Empty(viewModel.Duplicates); + + // Check Record 1 + var checkRecord = viewModel.ParsedRecords[0].Result; + Assert.Equal(new DateTime(2022, 02, 14), checkRecord.TransactionDate); + Assert.Equal("Lorem ipsum", checkRecord.Payee); + Assert.Equal("dolor sit amet", checkRecord.Memo); + Assert.Equal(new decimal(-2.95), checkRecord.Amount); + + // Check Record 2 + checkRecord = viewModel.ParsedRecords[1].Result; + Assert.Equal(new DateTime(2022, 02, 15), checkRecord.TransactionDate); + Assert.Equal("Foobar Company", checkRecord.Payee); + Assert.Equal(string.Empty, checkRecord.Memo); + Assert.Equal(new decimal(-27.5), checkRecord.Amount); + + // Check Record 3 + checkRecord = viewModel.ParsedRecords[2].Result; + Assert.Equal(new DateTime(2022, 02, 16), checkRecord.TransactionDate); + Assert.Equal("EMPLOYER", checkRecord.Payee); + Assert.Equal("Income Feb/2022", checkRecord.Memo); + Assert.Equal(new decimal(43), checkRecord.Amount); + + // Check Record 4 + checkRecord = viewModel.ParsedRecords[3].Result; + Assert.Equal(new DateTime(2022, 02, 17), checkRecord.TransactionDate); + Assert.Equal("The Webshop.com", checkRecord.Payee); + Assert.Equal("Billing", checkRecord.Memo); + Assert.Equal(new decimal(-6.34), checkRecord.Amount); + } + } + finally + { + DbConnector.CleanupDatabase(nameof(ImportDataViewModelTest)); + } + } + +} From 90eaf0e9c5d77e477d6ff900b7664670781819f1 Mon Sep 17 00:00:00 2001 From: Alexander Preibisch Date: Sat, 19 Feb 2022 10:33:11 +0100 Subject: [PATCH 14/14] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5ad31c..031979e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -### 1.5 (20xx-xx-xx) +### 1.5 (2022-02-19) * [Add] Option to set Localization [#52](https://github.com/TheAxelander/OpenBudgeteer/issues/52) * [Add] Enable mapping of seperated columns for Debit and Credit Amount on Import Page [#53](https://github.com/TheAxelander/OpenBudgeteer/issues/53)