From 3942151c54c174891cb3723d5efdfdb8358b6f0e Mon Sep 17 00:00:00 2001 From: Thomas Andersen Date: Fri, 22 Sep 2023 20:54:55 +0200 Subject: [PATCH] Clean up of purchase data (#190) Co-authored-by: Jonas Anker Rasmussen --- ...0230711144034_AddPurchaseTypes.Designer.cs | 572 +++++++++++++++++ .../20230711144034_AddPurchaseTypes.cs | 53 ++ ...0711161456_PurchaseDataCleanup.Designer.cs | 579 +++++++++++++++++ .../20230711161456_PurchaseDataCleanup.cs | 33 + ...UniqueAndExternalTransactionId.Designer.cs | 580 ++++++++++++++++++ ...seOrderIdUniqueAndExternalTransactionId.cs | 65 ++ ...201050_PurchaseTypeNonNullable.Designer.cs | 580 ++++++++++++++++++ .../20230818201050_PurchaseTypeNonNullable.cs | 34 + .../CoffeeCardContextModelSnapshot.cs | 53 +- .../Persistence/CoffeecardContext.cs | 7 + .../Services/MapperService.cs | 4 +- .../Services/PurchaseService.cs | 10 +- .../Services/v2/PurchaseService.cs | 29 +- .../v2/Purchase/PaymentType.cs | 17 + .../CoffeeCard.Models/Entities/PosPurchase.cs | 17 + .../CoffeeCard.Models/Entities/Purchase.cs | 10 +- .../Entities/PurchaseType.cs | 16 + .../CoffeeCard.Models/Entities/Voucher.cs | 4 + .../Services/v2/PurchaseServiceTests.cs | 2 +- coffeecard/CoffeeCard.WebApi/appsettings.json | 2 +- 20 files changed, 2638 insertions(+), 29 deletions(-) create mode 100644 coffeecard/CoffeeCard.Library/Migrations/20230711144034_AddPurchaseTypes.Designer.cs create mode 100644 coffeecard/CoffeeCard.Library/Migrations/20230711144034_AddPurchaseTypes.cs create mode 100644 coffeecard/CoffeeCard.Library/Migrations/20230711161456_PurchaseDataCleanup.Designer.cs create mode 100644 coffeecard/CoffeeCard.Library/Migrations/20230711161456_PurchaseDataCleanup.cs create mode 100644 coffeecard/CoffeeCard.Library/Migrations/20230714211716_PurchaseOrderIdUniqueAndExternalTransactionId.Designer.cs create mode 100644 coffeecard/CoffeeCard.Library/Migrations/20230714211716_PurchaseOrderIdUniqueAndExternalTransactionId.cs create mode 100644 coffeecard/CoffeeCard.Library/Migrations/20230818201050_PurchaseTypeNonNullable.Designer.cs create mode 100644 coffeecard/CoffeeCard.Library/Migrations/20230818201050_PurchaseTypeNonNullable.cs create mode 100644 coffeecard/CoffeeCard.Models/Entities/PosPurchase.cs create mode 100644 coffeecard/CoffeeCard.Models/Entities/PurchaseType.cs diff --git a/coffeecard/CoffeeCard.Library/Migrations/20230711144034_AddPurchaseTypes.Designer.cs b/coffeecard/CoffeeCard.Library/Migrations/20230711144034_AddPurchaseTypes.Designer.cs new file mode 100644 index 00000000..a91dd671 --- /dev/null +++ b/coffeecard/CoffeeCard.Library/Migrations/20230711144034_AddPurchaseTypes.Designer.cs @@ -0,0 +1,572 @@ +// +using System; +using CoffeeCard.Library.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CoffeeCard.Library.Migrations +{ + [DbContext(typeof(CoffeeCardContext))] + [Migration("20230711144034_AddPurchaseTypes")] + partial class AddPurchaseTypes + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("dbo") + .HasAnnotation("ProductVersion", "6.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); + + modelBuilder.Entity("CoffeeCard.Models.Entities.LoginAttempt", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Time") + .HasColumnType("datetime2"); + + b.Property("User_Id") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("User_Id"); + + b.ToTable("LoginAttempts", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.PosPurhase", b => + { + b.Property("PurchaseId") + .HasColumnType("int"); + + b.Property("BaristaInitials") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("PurchaseId"); + + b.ToTable("PosPurchases", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ExperienceWorth") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("NumberOfTickets") + .HasColumnType("int"); + + b.Property("Price") + .HasColumnType("int"); + + b.Property("Visible") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.ToTable("Products", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.ProductUserGroup", b => + { + b.Property("ProductId") + .HasColumnType("int"); + + b.Property("UserGroup") + .HasColumnType("int"); + + b.HasKey("ProductId", "UserGroup"); + + b.ToTable("ProductUserGroups", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Programme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("FullName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ShortName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SortPriority") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Programmes", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Purchase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("ExternalTransactionId") + .HasColumnType("nvarchar(450)"); + + b.Property("NumberOfTickets") + .HasColumnType("int"); + + b.Property("OrderId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Price") + .HasColumnType("int"); + + b.Property("ProductId") + .HasColumnType("int"); + + b.Property("ProductName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PurchasedById") + .HasColumnType("int") + .HasColumnName("PurchasedBy_Id"); + + b.Property("Status") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Type") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ExternalTransactionId"); + + b.HasIndex("OrderId") + .IsUnique(); + + b.HasIndex("ProductId"); + + b.HasIndex("PurchasedById"); + + b.ToTable("Purchases", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Statistic", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("ExpiryDate") + .HasColumnType("datetime2"); + + b.Property("LastSwipe") + .HasColumnType("datetime2"); + + b.Property("Preset") + .HasColumnType("int"); + + b.Property("SwipeCount") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int") + .HasColumnName("User_Id"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("Preset", "ExpiryDate"); + + b.ToTable("Statistics", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Ticket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("DateUsed") + .HasColumnType("datetime2"); + + b.Property("IsUsed") + .HasColumnType("bit"); + + b.Property("OwnerId") + .HasColumnType("int") + .HasColumnName("Owner_Id"); + + b.Property("ProductId") + .HasColumnType("int"); + + b.Property("PurchaseId") + .HasColumnType("int") + .HasColumnName("Purchase_Id"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.HasIndex("PurchaseId"); + + b.ToTable("Tickets", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Token", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("TokenHash") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("int") + .HasColumnName("User_Id"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("DateUpdated") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Experience") + .HasColumnType("int"); + + b.Property("IsVerified") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Password") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PrivacyActivated") + .HasColumnType("bit"); + + b.Property("ProgrammeId") + .HasColumnType("int") + .HasColumnName("Programme_Id"); + + b.Property("Salt") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserGroup") + .HasColumnType("int"); + + b.Property("UserState") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("Email"); + + b.HasIndex("ProgrammeId"); + + b.ToTable("Users", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Voucher", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Code") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("DateUsed") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("int") + .HasColumnName("Product_Id"); + + b.Property("PurchaseId") + .HasColumnType("int"); + + b.Property("Requester") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("int") + .HasColumnName("User_Id"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("ProductId"); + + b.HasIndex("PurchaseId"); + + b.HasIndex("UserId"); + + b.ToTable("Vouchers", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.WebhookConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("LastUpdated") + .HasColumnType("datetime2"); + + b.Property("SignatureKey") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Url") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("WebhookConfigurations", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.LoginAttempt", b => + { + b.HasOne("CoffeeCard.Models.Entities.User", "User") + .WithMany("LoginAttempts") + .HasForeignKey("User_Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.PosPurhase", b => + { + b.HasOne("CoffeeCard.Models.Entities.Purchase", "Purchase") + .WithMany() + .HasForeignKey("PurchaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Purchase"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.ProductUserGroup", b => + { + b.HasOne("CoffeeCard.Models.Entities.Product", "Product") + .WithMany("ProductUserGroup") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Purchase", b => + { + b.HasOne("CoffeeCard.Models.Entities.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CoffeeCard.Models.Entities.User", "PurchasedBy") + .WithMany("Purchases") + .HasForeignKey("PurchasedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("PurchasedBy"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Statistic", b => + { + b.HasOne("CoffeeCard.Models.Entities.User", "User") + .WithMany("Statistics") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Ticket", b => + { + b.HasOne("CoffeeCard.Models.Entities.User", "Owner") + .WithMany("Tickets") + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("CoffeeCard.Models.Entities.Purchase", "Purchase") + .WithMany("Tickets") + .HasForeignKey("PurchaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + + b.Navigation("Purchase"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Token", b => + { + b.HasOne("CoffeeCard.Models.Entities.User", "User") + .WithMany("Tokens") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.User", b => + { + b.HasOne("CoffeeCard.Models.Entities.Programme", "Programme") + .WithMany("Users") + .HasForeignKey("ProgrammeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Programme"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Voucher", b => + { + b.HasOne("CoffeeCard.Models.Entities.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CoffeeCard.Models.Entities.Purchase", "Purchase") + .WithMany() + .HasForeignKey("PurchaseId"); + + b.HasOne("CoffeeCard.Models.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Product"); + + b.Navigation("Purchase"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Product", b => + { + b.Navigation("ProductUserGroup"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Programme", b => + { + b.Navigation("Users"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Purchase", b => + { + b.Navigation("Tickets"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.User", b => + { + b.Navigation("LoginAttempts"); + + b.Navigation("Purchases"); + + b.Navigation("Statistics"); + + b.Navigation("Tickets"); + + b.Navigation("Tokens"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/coffeecard/CoffeeCard.Library/Migrations/20230711144034_AddPurchaseTypes.cs b/coffeecard/CoffeeCard.Library/Migrations/20230711144034_AddPurchaseTypes.cs new file mode 100644 index 00000000..5054536d --- /dev/null +++ b/coffeecard/CoffeeCard.Library/Migrations/20230711144034_AddPurchaseTypes.cs @@ -0,0 +1,53 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CoffeeCard.Library.Migrations +{ + public partial class AddPurchaseTypes : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "PurchaseId", + schema: "dbo", + table: "Vouchers", + type: "int", + nullable: true); + + migrationBuilder.AddColumn( + name: "Type", + schema: "dbo", + table: "Purchases", + type: "nvarchar(max)", + nullable: true); + + migrationBuilder.CreateTable( + name: "PosPurchases", + schema: "dbo", + columns: table => new + { + PurchaseId = table.Column(type: "int", nullable: false), + BaristaInitials = table.Column(type: "nvarchar(max)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PosPurchases", x => x.PurchaseId); + table.ForeignKey( + name: "FK_PosPurchases_Purchases_PurchaseId", + column: x => x.PurchaseId, + principalSchema: "dbo", + principalTable: "Purchases", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "PosPurchases", + schema: "dbo"); + } + } +} diff --git a/coffeecard/CoffeeCard.Library/Migrations/20230711161456_PurchaseDataCleanup.Designer.cs b/coffeecard/CoffeeCard.Library/Migrations/20230711161456_PurchaseDataCleanup.Designer.cs new file mode 100644 index 00000000..83d831fc --- /dev/null +++ b/coffeecard/CoffeeCard.Library/Migrations/20230711161456_PurchaseDataCleanup.Designer.cs @@ -0,0 +1,579 @@ +// +using System; +using CoffeeCard.Library.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CoffeeCard.Library.Migrations +{ + [DbContext(typeof(CoffeeCardContext))] + [Migration("20230711161456_PurchaseDataCleanup")] + partial class PurchaseDataCleanup + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("dbo") + .HasAnnotation("ProductVersion", "6.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); + + modelBuilder.Entity("CoffeeCard.Models.Entities.LoginAttempt", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Time") + .HasColumnType("datetime2"); + + b.Property("User_Id") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("User_Id"); + + b.ToTable("LoginAttempts", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.PosPurhase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("BaristaInitials") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PurchaseId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("PurchaseId"); + + b.ToTable("PosPurchases", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ExperienceWorth") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("NumberOfTickets") + .HasColumnType("int"); + + b.Property("Price") + .HasColumnType("int"); + + b.Property("Visible") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.ToTable("Products", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.ProductUserGroup", b => + { + b.Property("ProductId") + .HasColumnType("int"); + + b.Property("UserGroup") + .HasColumnType("int"); + + b.HasKey("ProductId", "UserGroup"); + + b.ToTable("ProductUserGroups", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Programme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("FullName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ShortName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SortPriority") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Programmes", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Purchase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("NumberOfTickets") + .HasColumnType("int"); + + b.Property("OrderId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Price") + .HasColumnType("int"); + + b.Property("ProductId") + .HasColumnType("int"); + + b.Property("ProductName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PurchasedById") + .HasColumnType("int") + .HasColumnName("PurchasedBy_Id"); + + b.Property("Status") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("TransactionId") + .HasColumnType("nvarchar(450)"); + + b.Property("Type") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.HasIndex("ProductId"); + + b.HasIndex("PurchasedById"); + + b.HasIndex("TransactionId"); + + b.ToTable("Purchases", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Statistic", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("ExpiryDate") + .HasColumnType("datetime2"); + + b.Property("LastSwipe") + .HasColumnType("datetime2"); + + b.Property("Preset") + .HasColumnType("int"); + + b.Property("SwipeCount") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int") + .HasColumnName("User_Id"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("Preset", "ExpiryDate"); + + b.ToTable("Statistics", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Ticket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("DateUsed") + .HasColumnType("datetime2"); + + b.Property("IsUsed") + .HasColumnType("bit"); + + b.Property("OwnerId") + .HasColumnType("int") + .HasColumnName("Owner_Id"); + + b.Property("ProductId") + .HasColumnType("int"); + + b.Property("PurchaseId") + .HasColumnType("int") + .HasColumnName("Purchase_Id"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.HasIndex("PurchaseId"); + + b.ToTable("Tickets", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Token", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("TokenHash") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("int") + .HasColumnName("User_Id"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("DateUpdated") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Experience") + .HasColumnType("int"); + + b.Property("IsVerified") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Password") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PrivacyActivated") + .HasColumnType("bit"); + + b.Property("ProgrammeId") + .HasColumnType("int") + .HasColumnName("Programme_Id"); + + b.Property("Salt") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserGroup") + .HasColumnType("int"); + + b.Property("UserState") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("Email"); + + b.HasIndex("ProgrammeId"); + + b.ToTable("Users", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Voucher", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Code") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("DateUsed") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("int") + .HasColumnName("Product_Id"); + + b.Property("PurchaseId") + .HasColumnType("int"); + + b.Property("Requester") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("int") + .HasColumnName("User_Id"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("ProductId"); + + b.HasIndex("PurchaseId"); + + b.HasIndex("UserId"); + + b.ToTable("Vouchers", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.WebhookConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("LastUpdated") + .HasColumnType("datetime2"); + + b.Property("SignatureKey") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Url") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("WebhookConfigurations", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.LoginAttempt", b => + { + b.HasOne("CoffeeCard.Models.Entities.User", "User") + .WithMany("LoginAttempts") + .HasForeignKey("User_Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.PosPurhase", b => + { + b.HasOne("CoffeeCard.Models.Entities.Purchase", "Purchase") + .WithMany() + .HasForeignKey("PurchaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Purchase"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.ProductUserGroup", b => + { + b.HasOne("CoffeeCard.Models.Entities.Product", "Product") + .WithMany("ProductUserGroup") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Purchase", b => + { + b.HasOne("CoffeeCard.Models.Entities.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CoffeeCard.Models.Entities.User", "PurchasedBy") + .WithMany("Purchases") + .HasForeignKey("PurchasedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("PurchasedBy"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Statistic", b => + { + b.HasOne("CoffeeCard.Models.Entities.User", "User") + .WithMany("Statistics") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Ticket", b => + { + b.HasOne("CoffeeCard.Models.Entities.User", "Owner") + .WithMany("Tickets") + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("CoffeeCard.Models.Entities.Purchase", "Purchase") + .WithMany("Tickets") + .HasForeignKey("PurchaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + + b.Navigation("Purchase"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Token", b => + { + b.HasOne("CoffeeCard.Models.Entities.User", "User") + .WithMany("Tokens") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.User", b => + { + b.HasOne("CoffeeCard.Models.Entities.Programme", "Programme") + .WithMany("Users") + .HasForeignKey("ProgrammeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Programme"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Voucher", b => + { + b.HasOne("CoffeeCard.Models.Entities.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CoffeeCard.Models.Entities.Purchase", "Purchase") + .WithMany() + .HasForeignKey("PurchaseId"); + + b.HasOne("CoffeeCard.Models.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Product"); + + b.Navigation("Purchase"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Product", b => + { + b.Navigation("ProductUserGroup"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Programme", b => + { + b.Navigation("Users"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Purchase", b => + { + b.Navigation("Tickets"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.User", b => + { + b.Navigation("LoginAttempts"); + + b.Navigation("Purchases"); + + b.Navigation("Statistics"); + + b.Navigation("Tickets"); + + b.Navigation("Tokens"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/coffeecard/CoffeeCard.Library/Migrations/20230711161456_PurchaseDataCleanup.cs b/coffeecard/CoffeeCard.Library/Migrations/20230711161456_PurchaseDataCleanup.cs new file mode 100644 index 00000000..1095fdc8 --- /dev/null +++ b/coffeecard/CoffeeCard.Library/Migrations/20230711161456_PurchaseDataCleanup.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CoffeeCard.Library.Migrations +{ + public partial class PurchaseDataCleanup : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + // Set the Type Column on the purchases table + migrationBuilder.Sql("UPDATE dbo.Purchases SET Type = 'Voucher' WHERE TransactionId like 'VOUCHER%'"); + migrationBuilder.Sql("UPDATE dbo.Purchases SET Type = 'Free' Where TransactionId = '00000000-0000-0000-0000-000000000000'"); + migrationBuilder.Sql("UPDATE dbo.Purchases SET Type = 'MobilePayV1' Where (len(TransactionId) < 36 and TransactionId not like 'VOUCHER%' and not OrderId = 'Analog')"); + migrationBuilder.Sql("UPDATE dbo.Purchases SET Type = 'MobilePayV1' Where TransactionId is null"); + migrationBuilder.Sql("UPDATE dbo.Purchases SET Type = 'MobilePayV2' Where (len(TransactionId) = 36 and not TransactionId = '00000000-0000-0000-0000-000000000000') "); + migrationBuilder.Sql("UPDATE dbo.Purchases SET Type = 'PointOfSale' Where OrderId like 'Analog'"); + + // Update vouchers with references to the purchase issued by its redemption + migrationBuilder.Sql("update Vouchers set PurchaseId = Purchases.Id from Purchases where Purchases.OrderId = Vouchers.Code"); + + // Create new vouchers for purchases where the same voucher have been used to issue multiple purchase + migrationBuilder.Sql("insert into Vouchers (Code, DateCreated, DateUsed, Description, Requester, Product_Id, User_Id, PurchaseId) select Purchases.OrderId + '-' + cast(Purchases.Id as varchar), Purchases.DateCreated, Purchases.DateCreated, 'Creation of extra vouchers for purchases without references', 'AnalogIO', Purchases.ProductId, Purchases.PurchasedBy_Id, Id from Purchases where Type = 'Voucher' and Id not in (select PurchaseId from Vouchers where PurchaseId is not null)"); + // Insert data to the new Pos table + migrationBuilder.Sql("insert into PosPurchases (PurchaseId, BaristaInitials) select Id, TransactionId from Purchases where OrderId like 'Analog'"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/coffeecard/CoffeeCard.Library/Migrations/20230714211716_PurchaseOrderIdUniqueAndExternalTransactionId.Designer.cs b/coffeecard/CoffeeCard.Library/Migrations/20230714211716_PurchaseOrderIdUniqueAndExternalTransactionId.Designer.cs new file mode 100644 index 00000000..7c1b3949 --- /dev/null +++ b/coffeecard/CoffeeCard.Library/Migrations/20230714211716_PurchaseOrderIdUniqueAndExternalTransactionId.Designer.cs @@ -0,0 +1,580 @@ +// +using System; +using CoffeeCard.Library.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CoffeeCard.Library.Migrations +{ + [DbContext(typeof(CoffeeCardContext))] + [Migration("20230714211716_PurchaseOrderIdUniqueAndExternalTransactionId")] + partial class PurchaseOrderIdUniqueAndExternalTransactionId + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("dbo") + .HasAnnotation("ProductVersion", "6.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); + + modelBuilder.Entity("CoffeeCard.Models.Entities.LoginAttempt", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Time") + .HasColumnType("datetime2"); + + b.Property("User_Id") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("User_Id"); + + b.ToTable("LoginAttempts", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.PosPurhase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("BaristaInitials") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PurchaseId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("PurchaseId"); + + b.ToTable("PosPurchases", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ExperienceWorth") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("NumberOfTickets") + .HasColumnType("int"); + + b.Property("Price") + .HasColumnType("int"); + + b.Property("Visible") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.ToTable("Products", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.ProductUserGroup", b => + { + b.Property("ProductId") + .HasColumnType("int"); + + b.Property("UserGroup") + .HasColumnType("int"); + + b.HasKey("ProductId", "UserGroup"); + + b.ToTable("ProductUserGroups", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Programme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("FullName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ShortName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SortPriority") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Programmes", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Purchase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("ExternalTransactionId") + .HasColumnType("nvarchar(450)"); + + b.Property("NumberOfTickets") + .HasColumnType("int"); + + b.Property("OrderId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Price") + .HasColumnType("int"); + + b.Property("ProductId") + .HasColumnType("int"); + + b.Property("ProductName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PurchasedById") + .HasColumnType("int") + .HasColumnName("PurchasedBy_Id"); + + b.Property("Status") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Type") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ExternalTransactionId"); + + b.HasIndex("OrderId") + .IsUnique(); + + b.HasIndex("ProductId"); + + b.HasIndex("PurchasedById"); + + b.ToTable("Purchases", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Statistic", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("ExpiryDate") + .HasColumnType("datetime2"); + + b.Property("LastSwipe") + .HasColumnType("datetime2"); + + b.Property("Preset") + .HasColumnType("int"); + + b.Property("SwipeCount") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int") + .HasColumnName("User_Id"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("Preset", "ExpiryDate"); + + b.ToTable("Statistics", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Ticket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("DateUsed") + .HasColumnType("datetime2"); + + b.Property("IsUsed") + .HasColumnType("bit"); + + b.Property("OwnerId") + .HasColumnType("int") + .HasColumnName("Owner_Id"); + + b.Property("ProductId") + .HasColumnType("int"); + + b.Property("PurchaseId") + .HasColumnType("int") + .HasColumnName("Purchase_Id"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.HasIndex("PurchaseId"); + + b.ToTable("Tickets", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Token", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("TokenHash") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("int") + .HasColumnName("User_Id"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("DateUpdated") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Experience") + .HasColumnType("int"); + + b.Property("IsVerified") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Password") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PrivacyActivated") + .HasColumnType("bit"); + + b.Property("ProgrammeId") + .HasColumnType("int") + .HasColumnName("Programme_Id"); + + b.Property("Salt") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserGroup") + .HasColumnType("int"); + + b.Property("UserState") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("Email"); + + b.HasIndex("ProgrammeId"); + + b.ToTable("Users", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Voucher", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Code") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("DateUsed") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("int") + .HasColumnName("Product_Id"); + + b.Property("PurchaseId") + .HasColumnType("int"); + + b.Property("Requester") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("int") + .HasColumnName("User_Id"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("ProductId"); + + b.HasIndex("PurchaseId"); + + b.HasIndex("UserId"); + + b.ToTable("Vouchers", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.WebhookConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("LastUpdated") + .HasColumnType("datetime2"); + + b.Property("SignatureKey") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Url") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("WebhookConfigurations", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.LoginAttempt", b => + { + b.HasOne("CoffeeCard.Models.Entities.User", "User") + .WithMany("LoginAttempts") + .HasForeignKey("User_Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.PosPurhase", b => + { + b.HasOne("CoffeeCard.Models.Entities.Purchase", "Purchase") + .WithMany() + .HasForeignKey("PurchaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Purchase"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.ProductUserGroup", b => + { + b.HasOne("CoffeeCard.Models.Entities.Product", "Product") + .WithMany("ProductUserGroup") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Purchase", b => + { + b.HasOne("CoffeeCard.Models.Entities.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CoffeeCard.Models.Entities.User", "PurchasedBy") + .WithMany("Purchases") + .HasForeignKey("PurchasedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("PurchasedBy"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Statistic", b => + { + b.HasOne("CoffeeCard.Models.Entities.User", "User") + .WithMany("Statistics") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Ticket", b => + { + b.HasOne("CoffeeCard.Models.Entities.User", "Owner") + .WithMany("Tickets") + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("CoffeeCard.Models.Entities.Purchase", "Purchase") + .WithMany("Tickets") + .HasForeignKey("PurchaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + + b.Navigation("Purchase"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Token", b => + { + b.HasOne("CoffeeCard.Models.Entities.User", "User") + .WithMany("Tokens") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.User", b => + { + b.HasOne("CoffeeCard.Models.Entities.Programme", "Programme") + .WithMany("Users") + .HasForeignKey("ProgrammeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Programme"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Voucher", b => + { + b.HasOne("CoffeeCard.Models.Entities.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CoffeeCard.Models.Entities.Purchase", "Purchase") + .WithMany() + .HasForeignKey("PurchaseId"); + + b.HasOne("CoffeeCard.Models.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Product"); + + b.Navigation("Purchase"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Product", b => + { + b.Navigation("ProductUserGroup"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Programme", b => + { + b.Navigation("Users"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Purchase", b => + { + b.Navigation("Tickets"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.User", b => + { + b.Navigation("LoginAttempts"); + + b.Navigation("Purchases"); + + b.Navigation("Statistics"); + + b.Navigation("Tickets"); + + b.Navigation("Tokens"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/coffeecard/CoffeeCard.Library/Migrations/20230714211716_PurchaseOrderIdUniqueAndExternalTransactionId.cs b/coffeecard/CoffeeCard.Library/Migrations/20230714211716_PurchaseOrderIdUniqueAndExternalTransactionId.cs new file mode 100644 index 00000000..1ffd6457 --- /dev/null +++ b/coffeecard/CoffeeCard.Library/Migrations/20230714211716_PurchaseOrderIdUniqueAndExternalTransactionId.cs @@ -0,0 +1,65 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CoffeeCard.Library.Migrations +{ + public partial class PurchaseOrderIdUniqueAndExternalTransactionId : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + // Make transactionId null, where not MobilePayV1, or MobilepayV2, and generate unique Guid orderIds + migrationBuilder.Sql("update Purchases set OrderId = NEWID(), TransactionId=null where Type != 'MobilePayV1' and Type != 'MobilePayV2' or OrderId = 'OLD PURCHASES'"); + + migrationBuilder.DropIndex( + name: "IX_Purchases_OrderId", + schema: "dbo", + table: "Purchases"); + + migrationBuilder.RenameColumn( + name: "TransactionId", + schema: "dbo", + table: "Purchases", + newName: "ExternalTransactionId"); + + migrationBuilder.RenameIndex( + name: "IX_Purchases_TransactionId", + schema: "dbo", + table: "Purchases", + newName: "IX_Purchases_ExternalTransactionId"); + + migrationBuilder.CreateIndex( + name: "IX_Purchases_OrderId", + schema: "dbo", + table: "Purchases", + column: "OrderId", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_Purchases_OrderId", + schema: "dbo", + table: "Purchases"); + + migrationBuilder.RenameColumn( + name: "ExternalTransactionId", + schema: "dbo", + table: "Purchases", + newName: "TransactionId"); + + migrationBuilder.RenameIndex( + name: "IX_Purchases_ExternalTransactionId", + schema: "dbo", + table: "Purchases", + newName: "IX_Purchases_TransactionId"); + + migrationBuilder.CreateIndex( + name: "IX_Purchases_OrderId", + schema: "dbo", + table: "Purchases", + column: "OrderId"); + } + } +} diff --git a/coffeecard/CoffeeCard.Library/Migrations/20230818201050_PurchaseTypeNonNullable.Designer.cs b/coffeecard/CoffeeCard.Library/Migrations/20230818201050_PurchaseTypeNonNullable.Designer.cs new file mode 100644 index 00000000..32c3bc37 --- /dev/null +++ b/coffeecard/CoffeeCard.Library/Migrations/20230818201050_PurchaseTypeNonNullable.Designer.cs @@ -0,0 +1,580 @@ +// +using System; +using CoffeeCard.Library.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CoffeeCard.Library.Migrations +{ + [DbContext(typeof(CoffeeCardContext))] + [Migration("20230818201050_PurchaseTypeNonNullable")] + partial class PurchaseTypeNonNullable + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("dbo") + .HasAnnotation("ProductVersion", "6.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); + + modelBuilder.Entity("CoffeeCard.Models.Entities.LoginAttempt", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Time") + .HasColumnType("datetime2"); + + b.Property("User_Id") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("User_Id"); + + b.ToTable("LoginAttempts", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.PosPurhase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("BaristaInitials") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PurchaseId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("PurchaseId"); + + b.ToTable("PosPurchases", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ExperienceWorth") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("NumberOfTickets") + .HasColumnType("int"); + + b.Property("Price") + .HasColumnType("int"); + + b.Property("Visible") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.ToTable("Products", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.ProductUserGroup", b => + { + b.Property("ProductId") + .HasColumnType("int"); + + b.Property("UserGroup") + .HasColumnType("int"); + + b.HasKey("ProductId", "UserGroup"); + + b.ToTable("ProductUserGroups", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Programme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("FullName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ShortName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SortPriority") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Programmes", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Purchase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("ExternalTransactionId") + .HasColumnType("nvarchar(450)"); + + b.Property("NumberOfTickets") + .HasColumnType("int"); + + b.Property("OrderId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Price") + .HasColumnType("int"); + + b.Property("ProductId") + .HasColumnType("int"); + + b.Property("ProductName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PurchasedById") + .HasColumnType("int") + .HasColumnName("PurchasedBy_Id"); + + b.Property("Status") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Type") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ExternalTransactionId"); + + b.HasIndex("OrderId") + .IsUnique(); + + b.HasIndex("ProductId"); + + b.HasIndex("PurchasedById"); + + b.ToTable("Purchases", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Statistic", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("ExpiryDate") + .HasColumnType("datetime2"); + + b.Property("LastSwipe") + .HasColumnType("datetime2"); + + b.Property("Preset") + .HasColumnType("int"); + + b.Property("SwipeCount") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int") + .HasColumnName("User_Id"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("Preset", "ExpiryDate"); + + b.ToTable("Statistics", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Ticket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("DateUsed") + .HasColumnType("datetime2"); + + b.Property("IsUsed") + .HasColumnType("bit"); + + b.Property("OwnerId") + .HasColumnType("int") + .HasColumnName("Owner_Id"); + + b.Property("ProductId") + .HasColumnType("int"); + + b.Property("PurchaseId") + .HasColumnType("int") + .HasColumnName("Purchase_Id"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.HasIndex("PurchaseId"); + + b.ToTable("Tickets", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Token", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("TokenHash") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("int") + .HasColumnName("User_Id"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("DateUpdated") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Experience") + .HasColumnType("int"); + + b.Property("IsVerified") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Password") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PrivacyActivated") + .HasColumnType("bit"); + + b.Property("ProgrammeId") + .HasColumnType("int") + .HasColumnName("Programme_Id"); + + b.Property("Salt") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserGroup") + .HasColumnType("int"); + + b.Property("UserState") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("Email"); + + b.HasIndex("ProgrammeId"); + + b.ToTable("Users", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Voucher", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Code") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("DateUsed") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("int") + .HasColumnName("Product_Id"); + + b.Property("PurchaseId") + .HasColumnType("int"); + + b.Property("Requester") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("int") + .HasColumnName("User_Id"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("ProductId"); + + b.HasIndex("PurchaseId"); + + b.HasIndex("UserId"); + + b.ToTable("Vouchers", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.WebhookConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("LastUpdated") + .HasColumnType("datetime2"); + + b.Property("SignatureKey") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Url") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("WebhookConfigurations", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.LoginAttempt", b => + { + b.HasOne("CoffeeCard.Models.Entities.User", "User") + .WithMany("LoginAttempts") + .HasForeignKey("User_Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.PosPurhase", b => + { + b.HasOne("CoffeeCard.Models.Entities.Purchase", "Purchase") + .WithMany() + .HasForeignKey("PurchaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Purchase"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.ProductUserGroup", b => + { + b.HasOne("CoffeeCard.Models.Entities.Product", "Product") + .WithMany("ProductUserGroup") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Purchase", b => + { + b.HasOne("CoffeeCard.Models.Entities.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CoffeeCard.Models.Entities.User", "PurchasedBy") + .WithMany("Purchases") + .HasForeignKey("PurchasedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("PurchasedBy"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Statistic", b => + { + b.HasOne("CoffeeCard.Models.Entities.User", "User") + .WithMany("Statistics") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Ticket", b => + { + b.HasOne("CoffeeCard.Models.Entities.User", "Owner") + .WithMany("Tickets") + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("CoffeeCard.Models.Entities.Purchase", "Purchase") + .WithMany("Tickets") + .HasForeignKey("PurchaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + + b.Navigation("Purchase"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Token", b => + { + b.HasOne("CoffeeCard.Models.Entities.User", "User") + .WithMany("Tokens") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.User", b => + { + b.HasOne("CoffeeCard.Models.Entities.Programme", "Programme") + .WithMany("Users") + .HasForeignKey("ProgrammeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Programme"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Voucher", b => + { + b.HasOne("CoffeeCard.Models.Entities.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CoffeeCard.Models.Entities.Purchase", "Purchase") + .WithMany() + .HasForeignKey("PurchaseId"); + + b.HasOne("CoffeeCard.Models.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Product"); + + b.Navigation("Purchase"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Product", b => + { + b.Navigation("ProductUserGroup"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Programme", b => + { + b.Navigation("Users"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Purchase", b => + { + b.Navigation("Tickets"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.User", b => + { + b.Navigation("LoginAttempts"); + + b.Navigation("Purchases"); + + b.Navigation("Statistics"); + + b.Navigation("Tickets"); + + b.Navigation("Tokens"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/coffeecard/CoffeeCard.Library/Migrations/20230818201050_PurchaseTypeNonNullable.cs b/coffeecard/CoffeeCard.Library/Migrations/20230818201050_PurchaseTypeNonNullable.cs new file mode 100644 index 00000000..47e60fdb --- /dev/null +++ b/coffeecard/CoffeeCard.Library/Migrations/20230818201050_PurchaseTypeNonNullable.cs @@ -0,0 +1,34 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CoffeeCard.Library.Migrations +{ + public partial class PurchaseTypeNonNullable : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Type", + schema: "dbo", + table: "Purchases", + type: "nvarchar(max)", + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Type", + schema: "dbo", + table: "Purchases", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)"); + } + } +} diff --git a/coffeecard/CoffeeCard.Library/Migrations/CoffeeCardContextModelSnapshot.cs b/coffeecard/CoffeeCard.Library/Migrations/CoffeeCardContextModelSnapshot.cs index e75cccef..558a6e23 100644 --- a/coffeecard/CoffeeCard.Library/Migrations/CoffeeCardContextModelSnapshot.cs +++ b/coffeecard/CoffeeCard.Library/Migrations/CoffeeCardContextModelSnapshot.cs @@ -1,4 +1,4 @@ -// +// using System; using CoffeeCard.Library.Persistence; using Microsoft.EntityFrameworkCore; @@ -44,6 +44,20 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("LoginAttempts", "dbo"); }); + modelBuilder.Entity("CoffeeCard.Models.Entities.PosPurhase", b => + { + b.Property("PurchaseId") + .HasColumnType("int"); + + b.Property("BaristaInitials") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("PurchaseId"); + + b.ToTable("PosPurchases", "dbo"); + }); + modelBuilder.Entity("CoffeeCard.Models.Entities.Product", b => { b.Property("Id") @@ -125,6 +139,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("DateCreated") .HasColumnType("datetime2"); + b.Property("ExternalTransactionId") + .HasColumnType("nvarchar(450)"); + b.Property("NumberOfTickets") .HasColumnType("int"); @@ -150,19 +167,21 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("nvarchar(max)"); - b.Property("TransactionId") - .HasColumnType("nvarchar(450)"); + b.Property("Type") + .IsRequired() + .HasColumnType("nvarchar(max)"); b.HasKey("Id"); - b.HasIndex("OrderId"); + b.HasIndex("ExternalTransactionId"); + + b.HasIndex("OrderId") + .IsUnique(); b.HasIndex("ProductId"); b.HasIndex("PurchasedById"); - b.HasIndex("TransactionId"); - b.ToTable("Purchases", "dbo"); }); @@ -343,6 +362,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("int") .HasColumnName("Product_Id"); + b.Property("PurchaseId") + .HasColumnType("int"); + b.Property("Requester") .HasColumnType("nvarchar(max)"); @@ -357,6 +379,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("ProductId"); + b.HasIndex("PurchaseId"); + b.HasIndex("UserId"); b.ToTable("Vouchers", "dbo"); @@ -399,6 +423,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("User"); }); + modelBuilder.Entity("CoffeeCard.Models.Entities.PosPurhase", b => + { + b.HasOne("CoffeeCard.Models.Entities.Purchase", "Purchase") + .WithMany() + .HasForeignKey("PurchaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Purchase"); + }); + modelBuilder.Entity("CoffeeCard.Models.Entities.ProductUserGroup", b => { b.HasOne("CoffeeCard.Models.Entities.Product", "Product") @@ -487,12 +522,18 @@ protected override void BuildModel(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.HasOne("CoffeeCard.Models.Entities.Purchase", "Purchase") + .WithMany() + .HasForeignKey("PurchaseId"); + b.HasOne("CoffeeCard.Models.Entities.User", "User") .WithMany() .HasForeignKey("UserId"); b.Navigation("Product"); + b.Navigation("Purchase"); + b.Navigation("User"); }); diff --git a/coffeecard/CoffeeCard.Library/Persistence/CoffeecardContext.cs b/coffeecard/CoffeeCard.Library/Persistence/CoffeecardContext.cs index 0f428dc0..74ffab0d 100644 --- a/coffeecard/CoffeeCard.Library/Persistence/CoffeecardContext.cs +++ b/coffeecard/CoffeeCard.Library/Persistence/CoffeecardContext.cs @@ -21,6 +21,7 @@ public CoffeeCardContext(DbContextOptions options, DatabaseSe public DbSet Users { get; set; } public DbSet Tickets { get; set; } public DbSet Purchases { get; set; } + public DbSet PosPurchases { get; set; } public DbSet Products { get; set; } public DbSet Tokens { get; set; } public DbSet LoginAttempts { get; set; } @@ -44,11 +45,17 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) // Use Enum to Int for UserGroups var userGroupIntConverter = new EnumToNumberConverter(); + // Use Enum to String for PurchaseTypes + var purchaseTypeStringConverter = new EnumToStringConverter(); modelBuilder.Entity() .Property(u => u.UserGroup) .HasConversion(userGroupIntConverter); + modelBuilder.Entity() + .Property(p => p.Type) + .HasConversion(purchaseTypeStringConverter); + modelBuilder.Entity().Property(u => u.UserState).HasConversion(); modelBuilder.Entity() diff --git a/coffeecard/CoffeeCard.Library/Services/MapperService.cs b/coffeecard/CoffeeCard.Library/Services/MapperService.cs index bbc4d804..fb371582 100644 --- a/coffeecard/CoffeeCard.Library/Services/MapperService.cs +++ b/coffeecard/CoffeeCard.Library/Services/MapperService.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using CoffeeCard.Library.Utils; using CoffeeCard.Models.DataTransferObjects.Product; @@ -23,7 +23,7 @@ public PurchaseDto Map(Purchase purchase) NumberOfTickets = purchase.NumberOfTickets, DateCreated = purchase.DateCreated, OrderId = purchase.OrderId, - TransactionId = purchase.TransactionId + TransactionId = purchase.ExternalTransactionId }; } diff --git a/coffeecard/CoffeeCard.Library/Services/PurchaseService.cs b/coffeecard/CoffeeCard.Library/Services/PurchaseService.cs index 50c3b9c0..c9819841 100644 --- a/coffeecard/CoffeeCard.Library/Services/PurchaseService.cs +++ b/coffeecard/CoffeeCard.Library/Services/PurchaseService.cs @@ -1,10 +1,11 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Security.Claims; using CoffeeCard.Common; using CoffeeCard.Common.Errors; using CoffeeCard.Library.Persistence; +using CoffeeCard.Models.DataTransferObjects.v2.Purchase; using CoffeeCard.Models.Entities; using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; @@ -42,7 +43,9 @@ public Purchase RedeemVoucher(string voucherCode, IEnumerable claims) Price = 0, ProductId = voucher.Product.Id, ProductName = voucher.Product.Name, - PurchasedBy = user + PurchasedBy = user, + Status = PurchaseStatus.Completed, + Type = PurchaseType.Voucher }; user.Purchases.Add(purchase); @@ -51,6 +54,7 @@ public Purchase RedeemVoucher(string voucherCode, IEnumerable claims) voucher.DateUsed = DateTime.UtcNow; voucher.User = user; + voucher.Purchase = purchase; _context.Vouchers.Attach(voucher); _context.Entry(voucher).State = EntityState.Modified; @@ -72,7 +76,7 @@ public Purchase DeliverProductToUser(Purchase purchase, User user, string transa user.Tickets.Add(ticket); } - purchase.TransactionId = transactionId; + purchase.ExternalTransactionId = transactionId; _context.Users.Attach(user); _context.Entry(user).State = EntityState.Modified; diff --git a/coffeecard/CoffeeCard.Library/Services/v2/PurchaseService.cs b/coffeecard/CoffeeCard.Library/Services/v2/PurchaseService.cs index bf755fff..ad38df5b 100644 --- a/coffeecard/CoffeeCard.Library/Services/v2/PurchaseService.cs +++ b/coffeecard/CoffeeCard.Library/Services/v2/PurchaseService.cs @@ -97,7 +97,7 @@ private static void CheckUserIsAllowedToPurchaseProduct(User user, InitiatePurch var orderId = await GenerateUniqueOrderId(); PaymentDetails paymentDetails; PurchaseStatus purchaseStatus; - string transactionId; + string? transactionId; switch (purchaseRequest.PaymentType) { @@ -116,7 +116,7 @@ private static void CheckUserIsAllowedToPurchaseProduct(User user, InitiatePurch case PaymentType.FreePurchase: paymentDetails = new FreePurchasePaymentDetails(orderId.ToString()); purchaseStatus = PurchaseStatus.Completed; - transactionId = Guid.Empty.ToString(); + transactionId = null; break; default: @@ -132,10 +132,10 @@ private static void CheckUserIsAllowedToPurchaseProduct(User user, InitiatePurch NumberOfTickets = product.NumberOfTickets, DateCreated = DateTime.UtcNow, OrderId = paymentDetails.OrderId, - TransactionId = transactionId, + ExternalTransactionId = transactionId, PurchasedBy = user, - Status = purchaseStatus - // FIXME State management, PaymentType + Status = purchaseStatus, + Type = purchaseRequest.PaymentType.toPurchaseType() }; return (purchase, paymentDetails); @@ -155,7 +155,7 @@ public async Task GetPurchase(int purchaseId, User user) $"No purchase was found by Purchase Id: {purchaseId} and User Id: {user.Id}"); } - var paymentDetails = await _mobilePayPaymentsService.GetPayment(Guid.Parse(purchase.TransactionId)); + var paymentDetails = await _mobilePayPaymentsService.GetPayment(Guid.Parse(purchase.ExternalTransactionId)); return new SinglePurchaseResponse { @@ -189,7 +189,7 @@ public async Task HandleMobilePayPaymentUpdate(MobilePayWebhook webhook) { var purchase = await _context.Purchases .Include(p => p.PurchasedBy) - .Where(p => p.TransactionId.Equals(webhook.Data.Id)) + .Where(p => p.ExternalTransactionId.Equals(webhook.Data.Id)) .FirstOrDefaultAsync(); if (purchase == null) { @@ -234,25 +234,25 @@ public async Task HandleMobilePayPaymentUpdate(MobilePayWebhook webhook) private async Task CompletePurchase(Purchase purchase) { - await _mobilePayPaymentsService.CapturePayment(Guid.Parse(purchase.TransactionId), purchase.Price); + await _mobilePayPaymentsService.CapturePayment(Guid.Parse(purchase.ExternalTransactionId), purchase.Price); await _ticketService.IssueTickets(purchase); purchase.Status = PurchaseStatus.Completed; await _context.SaveChangesAsync(); - Log.Information("Completed purchase with Id {Id}, TransactionId {TransactionId}", purchase.Id, purchase.TransactionId); + Log.Information("Completed purchase with Id {Id}, TransactionId {TransactionId}", purchase.Id, purchase.ExternalTransactionId); await _emailService.SendInvoiceAsyncV2(purchase, purchase.PurchasedBy); } private async Task CancelPurchase(Purchase purchase) { - await _mobilePayPaymentsService.CancelPayment(Guid.Parse(purchase.TransactionId)); + await _mobilePayPaymentsService.CancelPayment(Guid.Parse(purchase.ExternalTransactionId)); purchase.Status = PurchaseStatus.Cancelled; await _context.SaveChangesAsync(); Log.Information("Purchase has been cancelled Purchase Id {PurchaseId}, Transaction Id {TransactionId}", - purchase.Id, purchase.TransactionId); + purchase.Id, purchase.ExternalTransactionId); } private async Task GenerateUniqueOrderId() @@ -286,18 +286,21 @@ public async Task RedeemVoucher(string voucherCode, User Price = 0, ProductId = voucher.Product.Id, ProductName = voucher.Product.Name, - PurchasedBy = user + PurchasedBy = user, + Status = PurchaseStatus.Completed, + Type = PurchaseType.Voucher }; user.Purchases.Add(purchase); await _ticketService.IssueTickets(purchase); - purchase.TransactionId = $"VOUCHER: {voucher.Id}"; + purchase.ExternalTransactionId = null; purchase.Status = PurchaseStatus.Completed; voucher.DateUsed = DateTime.UtcNow; voucher.User = user; + voucher.Purchase = purchase; _context.Vouchers.Attach(voucher); _context.Entry(voucher).State = EntityState.Modified; diff --git a/coffeecard/CoffeeCard.Models/DataTransferObjects/v2/Purchase/PaymentType.cs b/coffeecard/CoffeeCard.Models/DataTransferObjects/v2/Purchase/PaymentType.cs index 01afe37e..9e0f7e6c 100644 --- a/coffeecard/CoffeeCard.Models/DataTransferObjects/v2/Purchase/PaymentType.cs +++ b/coffeecard/CoffeeCard.Models/DataTransferObjects/v2/Purchase/PaymentType.cs @@ -1,3 +1,6 @@ +using System; +using CoffeeCard.Models.Entities; + namespace CoffeeCard.Models.DataTransferObjects.v2.Purchase { /// @@ -15,4 +18,18 @@ public enum PaymentType /// FreePurchase } + + public static class PaymentTypeExtension + { + /// Converts the payment type to a corresponding PurchasType + public static PurchaseType toPurchaseType(this PaymentType paymentType) + { + return paymentType switch + { + PaymentType.MobilePay => PurchaseType.MobilePayV2, + PaymentType.FreePurchase => PurchaseType.Free, + _ => throw new ArgumentException("Unknown enum given to PaymentTypeExtension"), + }; + } + } } \ No newline at end of file diff --git a/coffeecard/CoffeeCard.Models/Entities/PosPurchase.cs b/coffeecard/CoffeeCard.Models/Entities/PosPurchase.cs new file mode 100644 index 00000000..1bae921b --- /dev/null +++ b/coffeecard/CoffeeCard.Models/Entities/PosPurchase.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace CoffeeCard.Models.Entities +{ + /// Represents a purchase issued by a barista with the point of sale system in the cafe + public class PosPurhase + { + /// The foreign key linking to a purchase + [Key, ForeignKey(nameof(Purchase))] + public int PurchaseId { get; set; } + /// The purchase that was issued by the barista + public virtual Purchase Purchase { get; set; } + /// The ITU intials of the barista issueing the product + public string BaristaInitials { get; set; } + } +} \ No newline at end of file diff --git a/coffeecard/CoffeeCard.Models/Entities/Purchase.cs b/coffeecard/CoffeeCard.Models/Entities/Purchase.cs index b32105eb..be03aab8 100644 --- a/coffeecard/CoffeeCard.Models/Entities/Purchase.cs +++ b/coffeecard/CoffeeCard.Models/Entities/Purchase.cs @@ -7,8 +7,8 @@ namespace CoffeeCard.Models.Entities { // TODO Should me marked as unique - [Index(nameof(OrderId))] - [Index(nameof(TransactionId))] + [Index(nameof(OrderId), IsUnique = true)] + [Index(nameof(ExternalTransactionId))] public class Purchase { /// @@ -63,6 +63,7 @@ public class Purchase /// /// Order Id /// 79ef0af3-02dd-4634-83fa-c15ddc05d338 + // TODO Change type to GUID, so it maps to uniqueuIdentifier in ef public string OrderId { get; set; } /// @@ -70,7 +71,7 @@ public class Purchase /// /// Transaction Id /// 186d2b31-ff25-4414-9fd1-bfe9807fa8b7 - public string? TransactionId { get; set; } + public string? ExternalTransactionId { get; set; } // Status is nullable for migration purposes /// @@ -80,6 +81,9 @@ public class Purchase /// Completed public PurchaseStatus Status { get; set; } + /// The type of purchase e.g. MobilePayV1, Free + public PurchaseType Type { get; set; } + [Column(name: "PurchasedBy_Id")] public int PurchasedById { get; set; } diff --git a/coffeecard/CoffeeCard.Models/Entities/PurchaseType.cs b/coffeecard/CoffeeCard.Models/Entities/PurchaseType.cs new file mode 100644 index 00000000..7704e0ff --- /dev/null +++ b/coffeecard/CoffeeCard.Models/Entities/PurchaseType.cs @@ -0,0 +1,16 @@ +namespace CoffeeCard.Models.Entities +{ + public enum PurchaseType + { + /// Used for migration purposes. A purchase made using the mobilepay appswitch sdk + MobilePayV1, + /// A purchase made using the mobilepay webhookflow + MobilePayV2, + /// A purchase issued as a result of redeeming a voucher + Voucher, + /// A purchase issued by the user redeeming a product costing 0 + Free, + /// Purchases performed in the cafe, for users without in-app payment options + PointOfSale, + } +} \ No newline at end of file diff --git a/coffeecard/CoffeeCard.Models/Entities/Voucher.cs b/coffeecard/CoffeeCard.Models/Entities/Voucher.cs index b3a640f8..5900949a 100644 --- a/coffeecard/CoffeeCard.Models/Entities/Voucher.cs +++ b/coffeecard/CoffeeCard.Models/Entities/Voucher.cs @@ -29,5 +29,9 @@ public class Voucher public int? UserId { get; set; } public User? User { get; set; } + + public int? PurchaseId { get; set; } + + public virtual Purchase? Purchase { get; set; } } } \ No newline at end of file diff --git a/coffeecard/CoffeeCard.Tests.Unit/Services/v2/PurchaseServiceTests.cs b/coffeecard/CoffeeCard.Tests.Unit/Services/v2/PurchaseServiceTests.cs index 280be15a..614bc2f0 100644 --- a/coffeecard/CoffeeCard.Tests.Unit/Services/v2/PurchaseServiceTests.cs +++ b/coffeecard/CoffeeCard.Tests.Unit/Services/v2/PurchaseServiceTests.cs @@ -181,7 +181,7 @@ public async Task InitiatePurchasePaymentTypeMobilePay() // Assert // DB Contents - Assert.Equal(mobilepayPaymentId, purchaseInDatabase.TransactionId); + Assert.Equal(mobilepayPaymentId, purchaseInDatabase.ExternalTransactionId); Assert.Equal(orderId, purchaseInDatabase.OrderId); Assert.Equal(product1.Id, purchaseInDatabase.ProductId); diff --git a/coffeecard/CoffeeCard.WebApi/appsettings.json b/coffeecard/CoffeeCard.WebApi/appsettings.json index a5aaf876..d65081e1 100644 --- a/coffeecard/CoffeeCard.WebApi/appsettings.json +++ b/coffeecard/CoffeeCard.WebApi/appsettings.json @@ -9,7 +9,7 @@ "DeploymentUrl": "https://localhost:8080/" }, "DatabaseSettings": { - "ConnectionString": "Server=mssql;Initial Catalog=master;User=sa;Password=Your_password123;TrustServerCertificate=True;", + "ConnectionString": "Server=localhost;Initial Catalog=master;User=sa;Password=Your_password123;TrustServerCertificate=True;", "SchemaName": "dbo" }, "IdentitySettings": {