diff --git a/MyOffice.Data.Models/Accounts/Account.cs b/MyOffice.Data.Models/Accounts/Account.cs index 6ed7ab0..95f6fa5 100644 --- a/MyOffice.Data.Models/Accounts/Account.cs +++ b/MyOffice.Data.Models/Accounts/Account.cs @@ -30,8 +30,8 @@ public class AccountSimple public Guid Id { get; set; } public string Name { get; set; } public AccountAccessTypeEnum Type { get; set; } - public string CurrencyName { get; set; } - public string CurrencyShortName { get; set; } + public string? CurrencyName { get; set; } + public string? CurrencyShortName { get; set; } public decimal? CurrencyRate { get; set; } public int? CurrencyQuantity { get; set; } public decimal? TotalPlus { get; set; } diff --git a/MyOffice.Data.Repositories/Account/AccountRepository.cs b/MyOffice.Data.Repositories/Account/AccountRepository.cs index e2dabc6..515cc05 100644 --- a/MyOffice.Data.Repositories/Account/AccountRepository.cs +++ b/MyOffice.Data.Repositories/Account/AccountRepository.cs @@ -204,16 +204,17 @@ public class AccountRepository : AppRepository, IAccountRepository .Where(x => !x.Item.Category!.IsInternal) .GroupBy(x => new { - ItemId = x.Item.CategoryId, - ItemName = x.Item.Category!.Name, + Id = x.Item.CategoryId, + Name = x.Item.Category!.Name, CurrencyId = x.Account.CurrencyGlobalId, CurrencyName = x.Account.CurrencyGlobal!.Name, }) .Select(x => new MotionTotalSimple { + Id = x.Key.Id, + Name = x.Key.Name, CurrencyId = x.Key.CurrencyId, CurrencyName = x.Key.CurrencyName, - Name = x.Key.ItemName, Amount = x.Sum(a => a.AmountPlus), }).ToList(); } @@ -227,8 +228,8 @@ public class AccountRepository : AppRepository, IAccountRepository .Where(x => x.Item.Category!.UserId == userId) .GroupBy(x => new { - ItemId = x.Item.ItemGlobalId, - ItemName = x.Item.ItemGlobal!.Name, + Id = x.Item.ItemGlobalId, + Name = x.Item.ItemGlobal!.Name, CurrencyId = x.Account.CurrencyGlobalId, CurrencyName = x.Account.CurrencyGlobal!.Name, }) @@ -236,9 +237,10 @@ public class AccountRepository : AppRepository, IAccountRepository { CurrencyId = x.Key.CurrencyId, CurrencyName = x.Key.CurrencyName, - Name = x.Key.ItemName, + Name = x.Key.Name, Amount = x.Sum(a => a.AmountPlus), - }).ToList(); + }) + .ToList(); } @@ -250,18 +252,18 @@ public class AccountRepository : AppRepository, IAccountRepository .Where(x => !x.Item.Category!.IsInternal) .GroupBy(x => new { - ItemId = x.Item.CategoryId, - ItemName = x.Item.Category!.Name, + Id = x.Item.CategoryId, + Name = x.Item.Category!.Name, CurrencyId = x.Account.CurrencyGlobalId, CurrencyName = x.Account.CurrencyGlobal!.Name, }) .Select(x => new MotionTotalSimple { - Id = x.Key.ItemId, - Name = x.Key.ItemName, + Id = x.Key.Id, + Name = x.Key.Name, CurrencyId = x.Key.CurrencyId, CurrencyName = x.Key.CurrencyName, - Amount = x.Sum(a => a.AmountPlus), + Amount = x.Sum(a => a.AmountMinus), }).ToList(); } @@ -273,11 +275,20 @@ public class AccountRepository : AppRepository, IAccountRepository .Where(x => x.Item.CategoryId == categoryId) .Where(x => !x.Item.Category!.IsInternal) .Where(x => x.Item.Category!.UserId == userId) - .GroupBy(x => new { Id = x.Item.ItemGlobalId, x.Item.ItemGlobal.Name }) + .GroupBy(x => new + { + Id = x.Item.ItemGlobalId, + Name = x.Item.ItemGlobal!.Name, + CurrencyId = x.Account.CurrencyGlobalId, + CurrencyName = x.Account.CurrencyGlobal!.Name, + }) .Select(x => new MotionTotalSimple { + CurrencyId = x.Key.CurrencyId, + CurrencyName = x.Key.CurrencyName, Name = x.Key.Name, - Amount = x.Sum(a => a.AmountMinus) - }).ToList(); + Amount = x.Sum(a => a.AmountMinus), + }) + .ToList(); } } \ No newline at end of file diff --git a/MyOffice.DbContext/AppDbContext.cs b/MyOffice.DbContext/AppDbContext.cs index f3ecd1a..088a012 100644 --- a/MyOffice.DbContext/AppDbContext.cs +++ b/MyOffice.DbContext/AppDbContext.cs @@ -205,6 +205,9 @@ public class AppDbContext : DbContext .Entity() .Property(d => d.Type) .HasConversion(new EnumToStringConverter()); + + modelBuilder.Entity() + .HasIndex(p => new { p.AccountId, p.UserId}).IsUnique(); } private void MotionsCreating(ModelBuilder modelBuilder) diff --git a/MyOffice.Migration.Postgres/Migrations/20230804145453_AccountAccesUnique.Designer.cs b/MyOffice.Migration.Postgres/Migrations/20230804145453_AccountAccesUnique.Designer.cs new file mode 100644 index 0000000..2015f55 --- /dev/null +++ b/MyOffice.Migration.Postgres/Migrations/20230804145453_AccountAccesUnique.Designer.cs @@ -0,0 +1,729 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using MyOffice.DbContext; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace MyOffice.Migrations.Postgres.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20230804145453_AccountAccesUnique")] + partial class AccountAccesUnique + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:CollationDefinition:my_ci_collation", "en-u-ks-primary,en-u-ks-primary,icu,False") + .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CurrencyGlobalId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CurrencyGlobalId"); + + b.HasIndex("OwnerId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccess", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountId") + .HasColumnType("uuid"); + + b.Property("IsAllowManage") + .HasColumnType("boolean"); + + b.Property("IsAllowRead") + .HasColumnType("boolean"); + + b.Property("IsAllowWrite") + .HasColumnType("boolean"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.HasIndex("UserId"); + + b.HasIndex("AccountId", "UserId") + .IsUnique(); + + b.ToTable("AccountAccesses"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccessInvite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AcceptedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("AccountId") + .HasColumnType("uuid"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsAllowWrite") + .HasColumnType("boolean"); + + b.Property("RejectedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("UserId"); + + b.ToTable("AccountAccessInvites"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccountCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AccountId") + .HasColumnType("uuid"); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("CategoryId"); + + b.ToTable("AccountAccountCategories"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AccountCategories"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.Motion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountId") + .HasColumnType("uuid"); + + b.Property("AmountMinus") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("AmountPlus") + .HasPrecision(18, 6) + .HasColumnType("numeric(18,6)"); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ItemId") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AccountId"); + + b.HasIndex("ItemId"); + + b.HasIndex("UserId"); + + b.ToTable("Motions"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.Currency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CurrencyGlobalId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CurrentRateId") + .HasColumnType("integer"); + + b.Property("IsPrimary") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ShortName") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CurrencyGlobalId"); + + b.HasIndex("CurrentRateId"); + + b.HasIndex("UserId"); + + b.ToTable("Currencies"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.CurrencyGlobal", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("DefaultQuantity") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Symbol") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("CurrencyGlobals"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.CurrencyRate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CurrencyId") + .HasColumnType("uuid"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Quantity") + .HasColumnType("integer"); + + b.Property("Rate") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("CurrencyId"); + + b.ToTable("CurrencyRates"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.Item", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CategoryId") + .HasColumnType("uuid"); + + b.Property("ItemGlobalId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("ItemGlobalId"); + + b.ToTable("Items"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.ItemCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("IsInternal") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("ItemCategories"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.ItemGlobal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("ItemGlobals"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Users.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CurrencyId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text") + .UseCollation("my_ci_collation"); + + b.Property("FirstName") + .HasColumnType("text"); + + b.Property("FullName") + .HasColumnType("text"); + + b.Property("IsEmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LastName") + .HasColumnType("text"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("text"); + + b.Property("Phone") + .HasColumnType("text"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("text") + .UseCollation("my_ci_collation"); + + b.HasKey("Id"); + + b.HasIndex("CurrencyId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Users.UserExternal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedOn") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text") + .UseCollation("my_ci_collation"); + + b.Property("ExternalId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Provider") + .IsRequired() + .HasColumnType("text") + .UseCollation("my_ci_collation"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserClaims"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.Account", b => + { + b.HasOne("MyOffice.Data.Models.Currencies.CurrencyGlobal", "CurrencyGlobal") + .WithMany("Accounts") + .HasForeignKey("CurrencyGlobalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Users.User", "Owner") + .WithMany("Accounts") + .HasForeignKey("OwnerId"); + + b.Navigation("CurrencyGlobal"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccess", b => + { + b.HasOne("MyOffice.Data.Models.Accounts.Account", "Account") + .WithMany("AccessRights") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Users.User", "Owner") + .WithMany("AccountAccessOwners") + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Users.User", "User") + .WithMany("AccountAccess") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + + b.Navigation("Owner"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccessInvite", b => + { + b.HasOne("MyOffice.Data.Models.Accounts.Account", "Account") + .WithMany("Invites") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Users.User", "User") + .WithMany("AccountAccessInvites") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountAccountCategory", b => + { + b.HasOne("MyOffice.Data.Models.Accounts.Account", "Account") + .WithMany("Categories") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Accounts.AccountCategory", "Category") + .WithMany("Accounts") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountCategory", b => + { + b.HasOne("MyOffice.Data.Models.Users.User", "User") + .WithMany("AccountCategories") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.Motion", b => + { + b.HasOne("MyOffice.Data.Models.Accounts.Account", "Account") + .WithMany("Motions") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Items.Item", "Item") + .WithMany("Motions") + .HasForeignKey("ItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Users.User", null) + .WithMany("AccountMotions") + .HasForeignKey("UserId"); + + b.Navigation("Account"); + + b.Navigation("Item"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.Currency", b => + { + b.HasOne("MyOffice.Data.Models.Currencies.CurrencyGlobal", "CurrencyGlobal") + .WithMany("Currencies") + .HasForeignKey("CurrencyGlobalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Currencies.CurrencyRate", "CurrentRate") + .WithMany("Currencies") + .HasForeignKey("CurrentRateId"); + + b.HasOne("MyOffice.Data.Models.Users.User", "User") + .WithMany("Currencies") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CurrencyGlobal"); + + b.Navigation("CurrentRate"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.CurrencyRate", b => + { + b.HasOne("MyOffice.Data.Models.Currencies.Currency", "Currency") + .WithMany("Rates") + .HasForeignKey("CurrencyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Currency"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.Item", b => + { + b.HasOne("MyOffice.Data.Models.Items.ItemCategory", "Category") + .WithMany("Items") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MyOffice.Data.Models.Items.ItemGlobal", "ItemGlobal") + .WithMany("Items") + .HasForeignKey("ItemGlobalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("ItemGlobal"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.ItemCategory", b => + { + b.HasOne("MyOffice.Data.Models.Users.User", "User") + .WithMany("ItemCategories") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Users.User", b => + { + b.HasOne("MyOffice.Data.Models.Currencies.CurrencyGlobal", "Currency") + .WithMany() + .HasForeignKey("CurrencyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Currency"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Users.UserExternal", b => + { + b.HasOne("MyOffice.Data.Models.Users.User", "User") + .WithMany("UserClaims") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.Account", b => + { + b.Navigation("AccessRights"); + + b.Navigation("Categories"); + + b.Navigation("Invites"); + + b.Navigation("Motions"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Accounts.AccountCategory", b => + { + b.Navigation("Accounts"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.Currency", b => + { + b.Navigation("Rates"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.CurrencyGlobal", b => + { + b.Navigation("Accounts"); + + b.Navigation("Currencies"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Currencies.CurrencyRate", b => + { + b.Navigation("Currencies"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.Item", b => + { + b.Navigation("Motions"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.ItemCategory", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Items.ItemGlobal", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("MyOffice.Data.Models.Users.User", b => + { + b.Navigation("AccountAccess"); + + b.Navigation("AccountAccessInvites"); + + b.Navigation("AccountAccessOwners"); + + b.Navigation("AccountCategories"); + + b.Navigation("AccountMotions"); + + b.Navigation("Accounts"); + + b.Navigation("Currencies"); + + b.Navigation("ItemCategories"); + + b.Navigation("UserClaims"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/MyOffice.Migration.Postgres/Migrations/20230804145453_AccountAccesUnique.cs b/MyOffice.Migration.Postgres/Migrations/20230804145453_AccountAccesUnique.cs new file mode 100644 index 0000000..7b3f55d --- /dev/null +++ b/MyOffice.Migration.Postgres/Migrations/20230804145453_AccountAccesUnique.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace MyOffice.Migrations.Postgres.Migrations +{ + /// + public partial class AccountAccesUnique : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_AccountAccesses_AccountId", + table: "AccountAccesses"); + + migrationBuilder.CreateIndex( + name: "IX_AccountAccesses_AccountId_UserId", + table: "AccountAccesses", + columns: new[] { "AccountId", "UserId" }, + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_AccountAccesses_AccountId_UserId", + table: "AccountAccesses"); + + migrationBuilder.CreateIndex( + name: "IX_AccountAccesses_AccountId", + table: "AccountAccesses", + column: "AccountId"); + } + } +} diff --git a/MyOffice.Migration.Postgres/Migrations/AppDbContextModelSnapshot.cs b/MyOffice.Migration.Postgres/Migrations/AppDbContextModelSnapshot.cs index d12a24f..1827fed 100644 --- a/MyOffice.Migration.Postgres/Migrations/AppDbContextModelSnapshot.cs +++ b/MyOffice.Migration.Postgres/Migrations/AppDbContextModelSnapshot.cs @@ -84,12 +84,13 @@ namespace MyOffice.Migrations.Postgres.Migrations b.HasKey("Id"); - b.HasIndex("AccountId"); - b.HasIndex("OwnerId"); b.HasIndex("UserId"); + b.HasIndex("AccountId", "UserId") + .IsUnique(); + b.ToTable("AccountAccesses"); }); diff --git a/MyOffice.SPA/src/app/dashboard/income/dashboard.component.html b/MyOffice.SPA/src/app/dashboard/income/dashboard.component.html index f24e952..2a68e28 100644 --- a/MyOffice.SPA/src/app/dashboard/income/dashboard.component.html +++ b/MyOffice.SPA/src/app/dashboard/income/dashboard.component.html @@ -37,7 +37,7 @@
-
- -
+
- - - + + +
{{item.name}}{{item.value | number: '1.2'}} ({{item.currency}})
{{item.currency}}{{item.value | number: '1.2'}} ({{item.valueRaw}})
diff --git a/MyOffice.SPA/src/app/dashboard/outcome/dashboard.component.ts b/MyOffice.SPA/src/app/dashboard/outcome/dashboard.component.ts index eaa8473..8c23296 100644 --- a/MyOffice.SPA/src/app/dashboard/outcome/dashboard.component.ts +++ b/MyOffice.SPA/src/app/dashboard/outcome/dashboard.component.ts @@ -64,6 +64,11 @@ export class DashboardOutcomeComponent implements OnInit { this.loadData(); } + onRightClick(event?: Event) { + event?.preventDefault(); + this.loadData(true); + } + private loadData(update?: boolean, category?: string) { let params = new HttpParams() .set('from', moment(this.dateFrom).format('YYYY-MM-DD')) @@ -91,6 +96,7 @@ export class DashboardOutcomeComponent implements OnInit { color: this.colors[this.colors.length - i - 1], }); } + if (update) { this.chart.updateSeries([{ data: series diff --git a/MyOffice.SPA/src/assets/i18n/ua.json b/MyOffice.SPA/src/assets/i18n/ua.json index b590262..38167be 100644 --- a/MyOffice.SPA/src/assets/i18n/ua.json +++ b/MyOffice.SPA/src/assets/i18n/ua.json @@ -103,5 +103,6 @@ "OTHER": "Інші", "TOTAL": "Всього", "INCOME": "Надходження", - "OUTCOME": "Витрати" + "OUTCOME": "Витрати", + "PERIOD": "Період" } diff --git a/MyOffice.Services/Dashboard/DashboardService.cs b/MyOffice.Services/Dashboard/DashboardService.cs index f944fda..f5efa24 100644 --- a/MyOffice.Services/Dashboard/DashboardService.cs +++ b/MyOffice.Services/Dashboard/DashboardService.cs @@ -119,19 +119,44 @@ public class DashboardService ? _accountRepository.GetOutcomeByCategory(userId, category.Value, from.ToUtc(), to.ToUtc()) : _accountRepository.GetOutcomeByCategories(userId, from.ToUtc(), to.ToUtc()); + data = data + .Where(x => x.Amount != 0) + .ToList(); + var currencies = data .Where(x => x.Amount != 0) .Select(x => x.CurrencyId) .Distinct() .ToList(); + var rates = _currencyRateRepository + .GetLastRates(userId, currencies, to.ToUtc()) + .ToDictionary(x => x.Key, x => x.Value.Rate * x.Value.Quantity); + return new DashboardIncomeData { - Data = data.Select(x => new DashboardIncomeDataItem + Data = data.Select(x => new + { + Id = x.Id.ToShort(), + Name = x.Name, + Value = (x.Amount * rates.FirstOrDefault(r => r.Key == x.CurrencyId).Value), + }) + .GroupBy(x => new { x.Id, x.Name }) + .Select(x => new DashboardIncomeDataItem + { + Id = x.Key.Id, + Name = x.Key.Name, + Value = x.Sum(s => s.Value), + }) + .ToList(), + + Details = data.Select(x => new DashboardIncomeDataItem { - Id = x.Id?.ToShort(), + Id = x.Id.ToShort(), + Currency = x.CurrencyId, Name = x.Name, - Value = x.Amount, + ValueRaw = x.Amount, + Value = (x.Amount * rates.FirstOrDefault(r => r.Key == x.CurrencyId).Value), }).ToList() }; } diff --git a/MyOffice.Web/Identity/Repositories/UserStore.cs b/MyOffice.Web/Identity/Repositories/UserStore.cs index 9259915..17a224e 100644 --- a/MyOffice.Web/Identity/Repositories/UserStore.cs +++ b/MyOffice.Web/Identity/Repositories/UserStore.cs @@ -50,7 +50,7 @@ public class UserStore : return Task.FromResult(0); } - public async Task CreateAsync(ApplicationUser user, CancellationToken cancellationToken) + public Task CreateAsync(ApplicationUser user, CancellationToken cancellationToken) { var result = _userRepository.AddUser(new User { @@ -65,10 +65,10 @@ public class UserStore : IsEmailConfirmed = user.IsEmailConfirmed }); - return result == 1 ? IdentityResult.Success : IdentityResult.Failed(); + return Task.FromResult(result == 1 ? IdentityResult.Success : IdentityResult.Failed()); } - public async Task UpdateAsync(ApplicationUser user, CancellationToken cancellationToken) + public Task UpdateAsync(ApplicationUser user, CancellationToken cancellationToken) { var userDb = new User { @@ -84,7 +84,7 @@ public class UserStore : }; var result = _userRepository.UpdateUser(userDb); - return result == 1 ? IdentityResult.Success : IdentityResult.Failed(); + return Task.FromResult(result == 1 ? IdentityResult.Success : IdentityResult.Failed()); } public Task DeleteAsync(ApplicationUser user, CancellationToken cancellationToken) diff --git a/MyOffice.Web/Models/Currency/CurrencyRateViewModel.cs b/MyOffice.Web/Models/Currency/CurrencyRateViewModel.cs index 6964bc8..9f47435 100644 --- a/MyOffice.Web/Models/Currency/CurrencyRateViewModel.cs +++ b/MyOffice.Web/Models/Currency/CurrencyRateViewModel.cs @@ -2,7 +2,7 @@ public class CurrencyRateViewModel: BaseViewModel { - public string Currency { get; set; } + public string Currency { get; set; } = null!; public DateTime DateTime { get; set; } public int Quantity { get; set; } public decimal Rate { get; set; }