diff --git a/Sloth.Core/CreateMigration.sh b/Sloth.Core/CreateMigrationAndExecute.sh similarity index 100% rename from Sloth.Core/CreateMigration.sh rename to Sloth.Core/CreateMigrationAndExecute.sh diff --git a/Sloth.Core/Domain/JournalRequest.cs b/Sloth.Core/Domain/JournalRequest.cs index 1e4825bb..0c9f077a 100644 --- a/Sloth.Core/Domain/JournalRequest.cs +++ b/Sloth.Core/Domain/JournalRequest.cs @@ -36,5 +36,12 @@ public JournalRequest() [MinLength(1)] [Required] public IList Transactions { get; set; } + + /// + /// Really, a journal request can only have 1 transaction, but if we re-send a JR we loose the tie to the original transaction + /// This doesn't have to be a foreign key, but it should be a unique identifier for the transaction + /// + [MaxLength(255)] + public string SavedTransactionId { get; set; } } } diff --git a/Sloth.Core/Jobs/AggieEnterpriseJournalJob.cs b/Sloth.Core/Jobs/AggieEnterpriseJournalJob.cs index 60bcb304..c9a769bf 100644 --- a/Sloth.Core/Jobs/AggieEnterpriseJournalJob.cs +++ b/Sloth.Core/Jobs/AggieEnterpriseJournalJob.cs @@ -114,7 +114,7 @@ public async Task UploadTransactions(ILogger l // here we will store the result of the transaction upload var journalRequest = new JournalRequest - { Transactions = new[] { transaction }, Source = source }; + { Transactions = new[] { transaction }, Source = source, SavedTransactionId = transaction.Id }; if (requestStatus.RequestId.HasValue && requestStatus.RequestStatus == RequestStatus.Pending) diff --git a/Sloth.Core/Migrations/20250211175713_JRField.Designer.cs b/Sloth.Core/Migrations/20250211175713_JRField.Designer.cs new file mode 100644 index 00000000..613b59f8 --- /dev/null +++ b/Sloth.Core/Migrations/20250211175713_JRField.Designer.cs @@ -0,0 +1,1273 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Sloth.Core; + +#nullable disable + +namespace Sloth.Core.Migrations +{ + [DbContext(typeof(SlothDbContext))] + [Migration("20250211175713_JRField")] + partial class JRField + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); + + modelBuilder.HasSequence("Document_Number_Seq") + .HasMin(1L) + .HasMax(999999999L) + .IsCyclic(); + + modelBuilder.HasSequence("KFS_Tracking_Number_Seq") + .HasMin(1L) + .HasMax(9999999999L) + .IsCyclic(); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Sloth.Core.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("Issued") + .HasColumnType("datetime2"); + + b.Property("Key") + .HasColumnType("nvarchar(450)"); + + b.Property("Revoked") + .HasColumnType("datetime2"); + + b.Property("TeamId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("Key") + .IsUnique() + .HasFilter("[Key] IS NOT NULL"); + + b.HasIndex("TeamId"); + + b.ToTable("ApiKeys", (string)null); + }); + + modelBuilder.Entity("Sloth.Core.Models.Blob", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("Container") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("FileName") + .HasColumnType("nvarchar(450)"); + + b.Property("MediaType") + .HasColumnType("nvarchar(max)"); + + b.Property("UploadedDate") + .HasColumnType("datetime2"); + + b.Property("Uri") + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("FileName"); + + b.HasIndex("UploadedDate"); + + b.HasIndex("Uri"); + + b.ToTable("Blobs"); + }); + + modelBuilder.Entity("Sloth.Core.Models.Integration", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ClearingAccount") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("HoldingAccount") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("MerchantId") + .HasColumnType("nvarchar(max)"); + + b.Property("ReportPasswordKey") + .HasColumnType("nvarchar(max)"); + + b.Property("ReportUsername") + .HasColumnType("nvarchar(max)"); + + b.Property("SourceId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("TeamId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Type") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("SourceId"); + + b.HasIndex("TeamId"); + + b.ToTable("Integrations"); + }); + + modelBuilder.Entity("Sloth.Core.Models.JobRecord", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("Details") + .HasColumnType("nvarchar(max)"); + + b.Property("EndedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ProcessedDate") + .HasColumnType("datetime2"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasMaxLength(32) + .HasColumnType("nvarchar(32)"); + + b.Property("TotalTransactions") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("EndedAt"); + + b.HasIndex("Name"); + + b.HasIndex("ProcessedDate"); + + b.HasIndex("StartedAt"); + + b.HasIndex("Status"); + + b.ToTable("JobRecords"); + }); + + modelBuilder.Entity("Sloth.Core.Models.JournalRequest", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("RequestId") + .HasColumnType("uniqueidentifier"); + + b.Property("SavedTransactionId") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("SourceId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("SourceId"); + + b.ToTable("JournalRequests"); + }); + + modelBuilder.Entity("Sloth.Core.Models.LogMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("CorrelationId") + .HasColumnType("nvarchar(450)"); + + b.Property("Exception") + .HasColumnType("nvarchar(max)"); + + b.Property("JobId") + .HasColumnType("nvarchar(450)"); + + b.Property("JobName") + .HasColumnType("nvarchar(450)"); + + b.Property("Level") + .HasColumnType("nvarchar(max)"); + + b.Property("LogEvent") + .HasColumnType("nvarchar(max)"); + + b.Property("Message") + .HasColumnType("nvarchar(max)"); + + b.Property("MessageTemplate") + .HasColumnType("nvarchar(max)"); + + b.Property("Properties") + .HasColumnType("xml"); + + b.Property("Source") + .HasColumnType("nvarchar(450)"); + + b.Property("TimeStamp") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.HasIndex("CorrelationId"); + + b.HasIndex("Source"); + + b.HasIndex("JobName", "JobId"); + + b.ToTable("Logs", (string)null); + }); + + modelBuilder.Entity("Sloth.Core.Models.Scrubber", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("BatchDate") + .HasColumnType("datetime2"); + + b.Property("BatchSequenceNumber") + .HasColumnType("int"); + + b.Property("BlobId") + .HasColumnType("nvarchar(450)"); + + b.Property("SourceId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Uri") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("BlobId"); + + b.HasIndex("SourceId"); + + b.ToTable("Scrubbers"); + }); + + modelBuilder.Entity("Sloth.Core.Models.Source", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("Chart") + .HasMaxLength(2) + .HasColumnType("nvarchar(2)"); + + b.Property("Description") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("DocumentType") + .HasMaxLength(4) + .HasColumnType("nvarchar(4)"); + + b.Property("KfsFtpPasswordKeyName") + .HasColumnType("nvarchar(max)"); + + b.Property("KfsFtpUsername") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("OrganizationCode") + .HasMaxLength(4) + .HasColumnType("nvarchar(4)"); + + b.Property("OriginCode") + .HasMaxLength(2) + .HasColumnType("nvarchar(2)"); + + b.Property("TeamId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.HasIndex("TeamId"); + + b.ToTable("Sources"); + }); + + modelBuilder.Entity("Sloth.Core.Models.Team", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("KfsContactDepartmentName") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("KfsContactEmail") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.Property("KfsContactMailingAddress") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("KfsContactPhoneNumber") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("KfsContactUserId") + .IsRequired() + .HasMaxLength(8) + .HasColumnType("nvarchar(8)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("Teams", (string)null); + }); + + modelBuilder.Entity("Sloth.Core.Models.TeamRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique() + .HasFilter("[Name] IS NOT NULL"); + + b.ToTable("TeamRoles"); + }); + + modelBuilder.Entity("Sloth.Core.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConsumerTrackingId") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("CreatorId") + .HasColumnType("nvarchar(450)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("DocumentNumber") + .HasMaxLength(14) + .HasColumnType("nvarchar(14)"); + + b.Property("JournalRequestId") + .HasColumnType("nvarchar(450)"); + + b.Property("KfsTrackingNumber") + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("LastModified") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("SYSUTCDATETIME()"); + + b.Property("MerchantTrackingNumber") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("MerchantTrackingUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("ProcessorTrackingNumber") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ReversalOfTransactionId") + .HasColumnType("nvarchar(450)"); + + b.Property("ReversalTransactionId") + .HasColumnType("nvarchar(450)"); + + b.Property("ScrubberId") + .HasColumnType("nvarchar(450)"); + + b.Property("SourceId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Status") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("TransactionDate") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("CreatorId"); + + b.HasIndex("JournalRequestId"); + + b.HasIndex("KfsTrackingNumber"); + + b.HasIndex("LastModified"); + + b.HasIndex("MerchantTrackingNumber"); + + b.HasIndex("ProcessorTrackingNumber"); + + b.HasIndex("ReversalOfTransactionId") + .IsUnique() + .HasFilter("[ReversalOfTransactionId] IS NOT NULL"); + + b.HasIndex("ReversalTransactionId") + .IsUnique() + .HasFilter("[ReversalTransactionId] IS NOT NULL"); + + b.HasIndex("ScrubberId"); + + b.HasIndex("SourceId"); + + b.HasIndex("Status"); + + b.HasIndex("TransactionDate"); + + b.ToTable("Transactions"); + }); + + modelBuilder.Entity("Sloth.Core.Models.TransactionBlob", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("BlobId") + .HasColumnType("nvarchar(450)"); + + b.Property("IntegrationId") + .HasColumnType("nvarchar(450)"); + + b.Property("TransactionId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("BlobId"); + + b.HasIndex("IntegrationId"); + + b.HasIndex("TransactionId"); + + b.ToTable("TransactionBlobs"); + }); + + modelBuilder.Entity("Sloth.Core.Models.TransactionMetadata", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("TransactionId") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("TransactionId"); + + b.HasIndex("TransactionId", "Name", "Value"); + + b.ToTable("TransactionMetadata"); + }); + + modelBuilder.Entity("Sloth.Core.Models.TransactionStatusEvent", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("EventDate") + .HasColumnType("datetime2"); + + b.Property("EventDetails") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("nvarchar(450)"); + + b.Property("TransactionId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("EventDate"); + + b.HasIndex("Status"); + + b.HasIndex("TransactionId"); + + b.ToTable("TransactionStatusEvents"); + }); + + modelBuilder.Entity("Sloth.Core.Models.Transfer", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("Account") + .HasMaxLength(7) + .HasColumnType("nvarchar(7)"); + + b.Property("AccountingDate") + .HasColumnType("datetime2"); + + b.Property("Amount") + .HasColumnType("decimal(18,2)"); + + b.Property("Chart") + .HasMaxLength(1) + .HasColumnType("nvarchar(1)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.Property("Direction") + .HasColumnType("int"); + + b.Property("FinancialSegmentString") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("FiscalPeriod") + .HasColumnType("int"); + + b.Property("FiscalYear") + .HasColumnType("int"); + + b.Property("ObjectCode") + .HasMaxLength(4) + .HasColumnType("nvarchar(4)"); + + b.Property("ObjectType") + .HasMaxLength(2) + .HasColumnType("nvarchar(2)"); + + b.Property("Project") + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("ReferenceId") + .HasMaxLength(8) + .HasColumnType("nvarchar(8)"); + + b.Property("SequenceNumber") + .HasColumnType("int"); + + b.Property("SubAccount") + .HasMaxLength(5) + .HasColumnType("nvarchar(5)"); + + b.Property("SubObjectCode") + .HasMaxLength(3) + .HasColumnType("nvarchar(3)"); + + b.Property("TransactionId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("TransactionId"); + + b.ToTable("Transfers"); + }); + + modelBuilder.Entity("Sloth.Core.Models.User", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("FullName") + .HasColumnType("nvarchar(max)"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Sloth.Core.Models.UserTeamRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.Property("TeamId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId", "TeamId"); + + b.HasIndex("RoleId"); + + b.HasIndex("TeamId"); + + b.ToTable("UserTeamRoles", (string)null); + }); + + modelBuilder.Entity("Sloth.Core.Models.WebHook", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ContentType") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("TeamId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Url") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TeamId"); + + b.ToTable("WebHooks"); + }); + + modelBuilder.Entity("Sloth.Core.Models.WebHookRequest", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("LastRequestDate") + .HasColumnType("datetime2"); + + b.Property("Payload") + .HasColumnType("nvarchar(max)"); + + b.Property("Persist") + .HasColumnType("bit"); + + b.Property("RequestCount") + .HasColumnType("int"); + + b.Property("ResponseBody") + .HasColumnType("nvarchar(max)"); + + b.Property("ResponseStatus") + .HasColumnType("int"); + + b.Property("WebHookId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("LastRequestDate"); + + b.HasIndex("ResponseStatus"); + + b.HasIndex("WebHookId"); + + b.ToTable("WebHookRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Sloth.Core.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Sloth.Core.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Sloth.Core.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Sloth.Core.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Sloth.Core.Models.ApiKey", b => + { + b.HasOne("Sloth.Core.Models.Team", "Team") + .WithMany("ApiKeys") + .HasForeignKey("TeamId"); + + b.Navigation("Team"); + }); + + modelBuilder.Entity("Sloth.Core.Models.Integration", b => + { + b.HasOne("Sloth.Core.Models.Source", "Source") + .WithMany() + .HasForeignKey("SourceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Sloth.Core.Models.Team", "Team") + .WithMany("Integrations") + .HasForeignKey("TeamId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Source"); + + b.Navigation("Team"); + }); + + modelBuilder.Entity("Sloth.Core.Models.JournalRequest", b => + { + b.HasOne("Sloth.Core.Models.Source", "Source") + .WithMany() + .HasForeignKey("SourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Source"); + }); + + modelBuilder.Entity("Sloth.Core.Models.Scrubber", b => + { + b.HasOne("Sloth.Core.Models.Blob", "Blob") + .WithMany("Scrubbers") + .HasForeignKey("BlobId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Sloth.Core.Models.Source", "Source") + .WithMany() + .HasForeignKey("SourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Blob"); + + b.Navigation("Source"); + }); + + modelBuilder.Entity("Sloth.Core.Models.Source", b => + { + b.HasOne("Sloth.Core.Models.Team", "Team") + .WithMany("Sources") + .HasForeignKey("TeamId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Team"); + }); + + modelBuilder.Entity("Sloth.Core.Models.Transaction", b => + { + b.HasOne("Sloth.Core.Models.User", "Creator") + .WithMany() + .HasForeignKey("CreatorId"); + + b.HasOne("Sloth.Core.Models.JournalRequest", "JournalRequest") + .WithMany("Transactions") + .HasForeignKey("JournalRequestId"); + + b.HasOne("Sloth.Core.Models.Transaction", "ReversalOfTransaction") + .WithOne() + .HasForeignKey("Sloth.Core.Models.Transaction", "ReversalOfTransactionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Sloth.Core.Models.Transaction", "ReversalTransaction") + .WithOne() + .HasForeignKey("Sloth.Core.Models.Transaction", "ReversalTransactionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Sloth.Core.Models.Scrubber", "Scrubber") + .WithMany("Transactions") + .HasForeignKey("ScrubberId"); + + b.HasOne("Sloth.Core.Models.Source", "Source") + .WithMany() + .HasForeignKey("SourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("JournalRequest"); + + b.Navigation("ReversalOfTransaction"); + + b.Navigation("ReversalTransaction"); + + b.Navigation("Scrubber"); + + b.Navigation("Source"); + }); + + modelBuilder.Entity("Sloth.Core.Models.TransactionBlob", b => + { + b.HasOne("Sloth.Core.Models.Blob", "Blob") + .WithMany("TransactionBlobs") + .HasForeignKey("BlobId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Sloth.Core.Models.Integration", "Integration") + .WithMany("TransactionBlobs") + .HasForeignKey("IntegrationId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Sloth.Core.Models.Transaction", "Transaction") + .WithMany("TransactionBlobs") + .HasForeignKey("TransactionId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Blob"); + + b.Navigation("Integration"); + + b.Navigation("Transaction"); + }); + + modelBuilder.Entity("Sloth.Core.Models.TransactionMetadata", b => + { + b.HasOne("Sloth.Core.Models.Transaction", "Transaction") + .WithMany("Metadata") + .HasForeignKey("TransactionId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Transaction"); + }); + + modelBuilder.Entity("Sloth.Core.Models.TransactionStatusEvent", b => + { + b.HasOne("Sloth.Core.Models.Transaction", "Transaction") + .WithMany("StatusEvents") + .HasForeignKey("TransactionId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Transaction"); + }); + + modelBuilder.Entity("Sloth.Core.Models.Transfer", b => + { + b.HasOne("Sloth.Core.Models.Transaction", "Transaction") + .WithMany("Transfers") + .HasForeignKey("TransactionId"); + + b.Navigation("Transaction"); + }); + + modelBuilder.Entity("Sloth.Core.Models.UserTeamRole", b => + { + b.HasOne("Sloth.Core.Models.TeamRole", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Sloth.Core.Models.Team", "Team") + .WithMany("UserTeamRoles") + .HasForeignKey("TeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Sloth.Core.Models.User", "User") + .WithMany("UserTeamRoles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("Team"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Sloth.Core.Models.WebHook", b => + { + b.HasOne("Sloth.Core.Models.Team", "Team") + .WithMany("WebHooks") + .HasForeignKey("TeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Team"); + }); + + modelBuilder.Entity("Sloth.Core.Models.WebHookRequest", b => + { + b.HasOne("Sloth.Core.Models.WebHook", "WebHook") + .WithMany("WebHookrequests") + .HasForeignKey("WebHookId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("WebHook"); + }); + + modelBuilder.Entity("Sloth.Core.Models.Blob", b => + { + b.Navigation("Scrubbers"); + + b.Navigation("TransactionBlobs"); + }); + + modelBuilder.Entity("Sloth.Core.Models.Integration", b => + { + b.Navigation("TransactionBlobs"); + }); + + modelBuilder.Entity("Sloth.Core.Models.JournalRequest", b => + { + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Sloth.Core.Models.Scrubber", b => + { + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Sloth.Core.Models.Team", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Integrations"); + + b.Navigation("Sources"); + + b.Navigation("UserTeamRoles"); + + b.Navigation("WebHooks"); + }); + + modelBuilder.Entity("Sloth.Core.Models.Transaction", b => + { + b.Navigation("Metadata"); + + b.Navigation("StatusEvents"); + + b.Navigation("TransactionBlobs"); + + b.Navigation("Transfers"); + }); + + modelBuilder.Entity("Sloth.Core.Models.User", b => + { + b.Navigation("UserTeamRoles"); + }); + + modelBuilder.Entity("Sloth.Core.Models.WebHook", b => + { + b.Navigation("WebHookrequests"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Sloth.Core/Migrations/20250211175713_JRField.cs b/Sloth.Core/Migrations/20250211175713_JRField.cs new file mode 100644 index 00000000..dccc1c7b --- /dev/null +++ b/Sloth.Core/Migrations/20250211175713_JRField.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Sloth.Core.Migrations +{ + public partial class JRField : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "SavedTransactionId", + table: "JournalRequests", + type: "nvarchar(255)", + maxLength: 255, + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "SavedTransactionId", + table: "JournalRequests"); + } + } +} diff --git a/Sloth.Core/Migrations/SlothDbContextModelSnapshot.cs b/Sloth.Core/Migrations/SlothDbContextModelSnapshot.cs index 0028aa07..c3d23f30 100644 --- a/Sloth.Core/Migrations/SlothDbContextModelSnapshot.cs +++ b/Sloth.Core/Migrations/SlothDbContextModelSnapshot.cs @@ -327,6 +327,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("RequestId") .HasColumnType("uniqueidentifier"); + b.Property("SavedTransactionId") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + b.Property("SourceId") .IsRequired() .HasColumnType("nvarchar(450)"); @@ -679,6 +683,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Value") .IsRequired() + .HasMaxLength(450) .HasColumnType("nvarchar(450)"); b.HasKey("Id"); diff --git a/Sloth.Web/Controllers/TransactionsController.cs b/Sloth.Web/Controllers/TransactionsController.cs index 08b73796..a2a9ad2c 100644 --- a/Sloth.Web/Controllers/TransactionsController.cs +++ b/Sloth.Web/Controllers/TransactionsController.cs @@ -667,6 +667,7 @@ public async Task CallWebHook(string id) [Authorize(Policy = PolicyCodes.TeamAnyRole)] public async Task Search(TransactionsFilterModel filter = null) { + if (string.IsNullOrWhiteSpace(filter?.TrackingNum)) { return RedirectToAction("Index"); @@ -684,13 +685,27 @@ public async Task Search(TransactionsFilterModel filter = null) var txn = await DbContext.Transactions.Where(t => t.Source.Team.Slug == TeamSlug && t.Id == filter.TrackingNum).FirstOrDefaultAsync(); if (txn != null) { + Message = $"Transaction found by Id: {filter.TrackingNum}"; return RedirectToAction("Details", new { id = txn.Id }); } } - - ErrorMessage = $"Search Returned no results: {filter.TrackingNum}"; - return RedirectToAction("Index"); + //Try to find by request id + if(Guid.TryParse(filter.TrackingNum, out var reqId)) + { + var txn = await DbContext.JournalRequests.Include(a => a.Transactions).Where(t => t.Source.Team.Slug == TeamSlug && t.RequestId == reqId).FirstOrDefaultAsync(); + if (txn != null && txn.Transactions != null && txn.Transactions.Count >= 1) + { + Message = $"Transaction found by Request Id: {filter.TrackingNum}"; + //This will work if we found an "active" journal request, but not if we replaced it. + return RedirectToAction("Details", new { id = txn.Transactions.First().Id }); + } + if(txn != null) + { + Message = $"Replaced Journal Request found by Request Id: {filter.TrackingNum}"; + return RedirectToAction("Details", new { id = txn.SavedTransactionId }); + } + } } @@ -735,6 +750,41 @@ public async Task Search(TransactionsFilterModel filter = null) return RedirectToAction("Details", new { txns.First(a => a.MerchantTrackingNumber == filter.TrackingNum).Id }); } + if (User.IsInRole(Roles.SystemAdmin)) + { + //Allow System Admins to search all transactions for specific types. + //Try to find by id + if (Guid.TryParse(filter.TrackingNum, out var txnId)) + { + var txn = await DbContext.Transactions.Include(a => a.Source.Team).Where(t => t.Id == filter.TrackingNum).FirstOrDefaultAsync(); + if (txn != null) + { + Message = $"Different Team!!! - Transaction found by Id: {filter.TrackingNum}"; + return RedirectToAction("Details", "Transactions", new { id = txn.Id, team = txn.Source.Team.Slug }); + } + } + + //Try to find by request id + if (Guid.TryParse(filter.TrackingNum, out var reqId)) + { + var txn = await DbContext.JournalRequests.Include(a => a.Transactions).Include(a => a.Source).Where(t => t.RequestId == reqId).FirstOrDefaultAsync(); + if (txn != null && txn.Transactions != null && txn.Transactions.Count >= 1) + { + var teamSlug = await DbContext.Sources.Include(a => a.Team).Where(a => a.Id == txn.Source.Id).Select(a => a.Team.Slug).FirstOrDefaultAsync(); + Message = $"Different Team!!! - Transaction found by Request Id: {filter.TrackingNum}"; + //This will work if we found an "active" journal request, but not if we replaced it. + return RedirectToAction("Details", "Transactions", new { id = txn.Transactions.First().Id, team = teamSlug }); + } + if (txn != null) + { + var teamSlug = await DbContext.Sources.Include(a => a.Team).Where(a => a.Id == txn.Source.Id).Select(a => a.Team.Slug).FirstOrDefaultAsync(); + Message = $"Different Team!!! - Replaced Journal Request found by Request Id: {filter.TrackingNum}"; + return RedirectToAction("Details", "Transactions", new { id = txn.SavedTransactionId, team = teamSlug}); + } + } + } + + ErrorMessage = $"Search Returned no results: {filter.TrackingNum}"; return RedirectToAction("Index");