Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 932d09c135 | |||
| 455ce699ad | |||
| 7d7e9f1318 | |||
| 87e3112a9a | |||
| 801276559d | |||
| b1bda1448e | |||
| 58247a1a0b | |||
| 3a503abe6a | |||
| 855eb44118 | |||
| 33dc4adce2 | |||
| 5b2b39b7bf | |||
| 2c39b34c5f | |||
| 99832df134 | |||
| 2be9d9b396 | |||
| a12e170205 | |||
| fdfca7cf5e | |||
| e84ce29064 | |||
| 2be2928ad1 | |||
| 872fd42c4a | |||
| d79c5e55d0 | |||
| 50d3113c28 | |||
| 34d33571de | |||
| 0f575370d4 | |||
| e4061fa94c | |||
| 5f8bc70174 | |||
| f2d5c9f50a | |||
| 9e39981c9a | |||
| bd8b2d8b44 | |||
| f26557a66e | |||
| 91959913a8 | |||
| 48f607d623 | |||
| 82be251878 | |||
| 209ee7da2d | |||
| 6b9f27573b | |||
| a0df2a1bbf | |||
| d1c3d4e22f | |||
| a80512994b | |||
| 2de9e5e4ad | |||
| 5707bd4c31 | |||
| 28c465f59b | |||
| 21b38b5276 | |||
| 992cd292fa | |||
| 7b6c9ac92a | |||
| eb0b042f0c | |||
| a980e636f5 | |||
| b992d07689 | |||
| a4d58f3377 | |||
| 94db2a311c | |||
| 87c807c16c |
@@ -0,0 +1,209 @@
|
||||
using Distribution.Features.Notifications;
|
||||
using Distribution.Infra.UoW;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Distribution.Tests.Services;
|
||||
|
||||
public class NotificationServiceTests
|
||||
{
|
||||
private readonly Mock<INotificationRepository> _repo = new();
|
||||
private readonly Mock<IUnitOfWork> _uow = new();
|
||||
private readonly Mock<IHubContext<NotificationHub>> _hub = new();
|
||||
private readonly Mock<IHubClients> _clients = new();
|
||||
private readonly Mock<IClientProxy> _proxy = new();
|
||||
private readonly NotificationService _sut;
|
||||
|
||||
public NotificationServiceTests()
|
||||
{
|
||||
_repo.Setup(r => r.FilterActiveUserIdsAsync(It.IsAny<IEnumerable<Guid>>()))
|
||||
.ReturnsAsync((IEnumerable<Guid> ids) => ids.ToList());
|
||||
_hub.SetupGet(h => h.Clients).Returns(_clients.Object);
|
||||
_clients.Setup(c => c.User(It.IsAny<string>())).Returns(_proxy.Object);
|
||||
|
||||
_sut = new NotificationService(
|
||||
_repo.Object,
|
||||
new NotificationRecipientResolver(_repo.Object),
|
||||
_hub.Object,
|
||||
_uow.Object,
|
||||
NullLogger<NotificationService>.Instance);
|
||||
}
|
||||
|
||||
private static NotificationArgs Args(Guid? actor = null) =>
|
||||
new(new { StoreName = "Fort Mini Mart" }, "DeliveryTask", Guid.NewGuid(), "/operations/delivery-tasks/1", actor);
|
||||
|
||||
// Registry
|
||||
|
||||
[Fact]
|
||||
public void EveryNotificationTypeConstant_HasADefinition()
|
||||
{
|
||||
var constants = typeof(NotificationTypes)
|
||||
.GetFields()
|
||||
.Where(f => f.IsLiteral && f.FieldType == typeof(string))
|
||||
.Select(f => (string)f.GetRawConstantValue()!)
|
||||
.ToList();
|
||||
|
||||
Assert.NotEmpty(constants);
|
||||
Assert.Equal(constants.Count, constants.Distinct().Count());
|
||||
Assert.All(constants, c => Assert.True(NotificationTypes.All.ContainsKey(c), $"{c} has no definition"));
|
||||
}
|
||||
|
||||
// NotifyAsync
|
||||
|
||||
[Fact]
|
||||
public async Task NotifyAsync_DoesNothing_WhenTypeIsNotRegistered()
|
||||
{
|
||||
await _sut.NotifyAsync("NotARealType", [RecipientRule.Users(Guid.NewGuid())], Args());
|
||||
|
||||
_repo.Verify(r => r.AddAsync(It.IsAny<Models.Notification>()), Times.Never);
|
||||
_uow.Verify(u => u.CommitAsync(It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NotifyAsync_DoesNothing_WhenThereAreNoRecipients()
|
||||
{
|
||||
await _sut.NotifyAsync(NotificationTypes.DeliveryTaskFailed, [RecipientRule.Users()], Args());
|
||||
|
||||
_repo.Verify(r => r.AddAsync(It.IsAny<Models.Notification>()), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NotifyAsync_ExcludesTheActingUser()
|
||||
{
|
||||
var actor = Guid.NewGuid();
|
||||
var other = Guid.NewGuid();
|
||||
Models.Notification? saved = null;
|
||||
_repo.Setup(r => r.AddAsync(It.IsAny<Models.Notification>()))
|
||||
.Callback<Models.Notification>(n => saved = n)
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
await _sut.NotifyAsync(NotificationTypes.DeliveryTaskFailed,
|
||||
[RecipientRule.Users(actor, other)], Args(actor));
|
||||
|
||||
Assert.NotNull(saved);
|
||||
var recipient = Assert.Single(saved!.Recipients);
|
||||
Assert.Equal(other, recipient.FK_UserId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NotifyAsync_RendersTemplate_CommitsAndPushesToEachRecipient()
|
||||
{
|
||||
var a = Guid.NewGuid();
|
||||
var b = Guid.NewGuid();
|
||||
Models.Notification? saved = null;
|
||||
_repo.Setup(r => r.AddAsync(It.IsAny<Models.Notification>()))
|
||||
.Callback<Models.Notification>(n => saved = n)
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
await _sut.NotifyAsync(NotificationTypes.DeliveryTaskFailed, [RecipientRule.Users(a, b)], Args());
|
||||
|
||||
Assert.Equal("Delivery to Fort Mini Mart failed.", saved!.Message);
|
||||
Assert.Equal(Models.NotificationSeverity.Warning, saved.Severity);
|
||||
_uow.Verify(u => u.CommitAsync(It.IsAny<CancellationToken>()), Times.Once);
|
||||
_clients.Verify(c => c.User(a.ToString()), Times.Once);
|
||||
_clients.Verify(c => c.User(b.ToString()), Times.Once);
|
||||
_proxy.Verify(p => p.SendCoreAsync(NotificationHub.ClientEvent,
|
||||
It.IsAny<object?[]>(), It.IsAny<CancellationToken>()), Times.Exactly(2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NotifyAsync_DoesNotThrow_WhenSavingFails_AndDiscardsTheEntity()
|
||||
{
|
||||
_uow.Setup(u => u.CommitAsync(It.IsAny<CancellationToken>())).ThrowsAsync(new InvalidOperationException("db down"));
|
||||
|
||||
var ex = await Record.ExceptionAsync(() =>
|
||||
_sut.NotifyAsync(NotificationTypes.DeliveryTaskFailed, [RecipientRule.Users(Guid.NewGuid())], Args()));
|
||||
|
||||
Assert.Null(ex);
|
||||
_repo.Verify(r => r.Discard(It.IsAny<Models.Notification>()), Times.Once);
|
||||
_proxy.Verify(p => p.SendCoreAsync(It.IsAny<string>(), It.IsAny<object?[]>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NotifyAsync_DoesNotThrow_WhenPushFails_AndKeepsTheSavedNotification()
|
||||
{
|
||||
_proxy.Setup(p => p.SendCoreAsync(It.IsAny<string>(), It.IsAny<object?[]>(), It.IsAny<CancellationToken>()))
|
||||
.ThrowsAsync(new InvalidOperationException("socket gone"));
|
||||
|
||||
var ex = await Record.ExceptionAsync(() =>
|
||||
_sut.NotifyAsync(NotificationTypes.DeliveryTaskFailed, [RecipientRule.Users(Guid.NewGuid())], Args()));
|
||||
|
||||
Assert.Null(ex);
|
||||
_uow.Verify(u => u.CommitAsync(It.IsAny<CancellationToken>()), Times.Once);
|
||||
_repo.Verify(r => r.Discard(It.IsAny<Models.Notification>()), Times.Never);
|
||||
}
|
||||
|
||||
// Recipient rules
|
||||
|
||||
[Fact]
|
||||
public async Task PermissionInScope_ResolvesToTheIntersectionOfPermissionAndScope()
|
||||
{
|
||||
var both = Guid.NewGuid();
|
||||
var permissionOnly = Guid.NewGuid();
|
||||
var scopeOnly = Guid.NewGuid();
|
||||
var storeId = Guid.NewGuid();
|
||||
_repo.Setup(r => r.GetUserIdsByPermissionAsync("store.manage")).ReturnsAsync([both, permissionOnly]);
|
||||
_repo.Setup(r => r.GetUserIdsByScopeAsync(ScopeKind.Store, storeId)).ReturnsAsync([both, scopeOnly]);
|
||||
|
||||
var ids = await new NotificationRecipientResolver(_repo.Object)
|
||||
.ResolveAsync([RecipientRule.PermissionInScope("store.manage", ScopeKind.Store, storeId)], actingUserId: null);
|
||||
|
||||
Assert.Equal([both], ids);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReportsTo_ResolvesToTheSupervisor_AndIsEmptyWhenThereIsNone()
|
||||
{
|
||||
var user = Guid.NewGuid();
|
||||
var supervisor = Guid.NewGuid();
|
||||
var resolver = new NotificationRecipientResolver(_repo.Object);
|
||||
|
||||
_repo.Setup(r => r.GetReportsToUserIdAsync(user)).ReturnsAsync(supervisor);
|
||||
Assert.Equal([supervisor], await resolver.ResolveAsync([RecipientRule.ReportsTo(user)], null));
|
||||
|
||||
_repo.Setup(r => r.GetReportsToUserIdAsync(user)).ReturnsAsync((Guid?)null);
|
||||
Assert.Empty(await resolver.ResolveAsync([RecipientRule.ReportsTo(user)], null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RouteOwner_ResolvesToTheOwner_AndIsEmptyWhenTheRouteHasNone()
|
||||
{
|
||||
var storeId = Guid.NewGuid();
|
||||
var owner = Guid.NewGuid();
|
||||
var resolver = new NotificationRecipientResolver(_repo.Object);
|
||||
|
||||
_repo.Setup(r => r.GetRouteOwnerUserIdAsync(ScopeKind.Store, storeId)).ReturnsAsync(owner);
|
||||
Assert.Equal([owner], await resolver.ResolveAsync([RecipientRule.RouteOwner(ScopeKind.Store, storeId)], null));
|
||||
|
||||
_repo.Setup(r => r.GetRouteOwnerUserIdAsync(ScopeKind.Store, storeId)).ReturnsAsync((Guid?)null);
|
||||
Assert.Empty(await resolver.ResolveAsync([RecipientRule.RouteOwner(ScopeKind.Store, storeId)], null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OptionalTokenAtTheEndOfATemplate_LeavesNoTrailingSpace()
|
||||
{
|
||||
Models.Notification? saved = null;
|
||||
_repo.Setup(r => r.AddAsync(It.IsAny<Models.Notification>()))
|
||||
.Callback<Models.Notification>(n => saved = n)
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
await _sut.NotifyAsync(NotificationTypes.DeliveryTaskFailed, [RecipientRule.Users(Guid.NewGuid())],
|
||||
new NotificationArgs(new { StoreName = "Fort Mini Mart", Note = (string?)null }, "DeliveryTask", Guid.NewGuid(), "/x"));
|
||||
|
||||
Assert.Equal("Delivery to Fort Mini Mart failed.", saved!.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Resolve_DeduplicatesUsersAcrossRules()
|
||||
{
|
||||
var user = Guid.NewGuid();
|
||||
_repo.Setup(r => r.GetUserIdsByPermissionAsync("stock.manage")).ReturnsAsync([user]);
|
||||
|
||||
var ids = await new NotificationRecipientResolver(_repo.Object)
|
||||
.ResolveAsync([RecipientRule.Users(user), RecipientRule.Permission("stock.manage")], null);
|
||||
|
||||
Assert.Equal([user], ids);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,9 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
|
||||
public DbSet<ShopPO> ShopPOs { get; set; }
|
||||
public DbSet<ShopPOItem> ShopPOItems { get; set; }
|
||||
public DbSet<Invoice> Invoices { get; set; }
|
||||
public DbSet<InvoiceItem> InvoiceItems { get; set; }
|
||||
public DbSet<InvoiceVersion> InvoiceVersions { get; set; }
|
||||
public DbSet<InvoiceVersionItem> InvoiceVersionItems { get; set; }
|
||||
public DbSet<LoadingSheetOrder> LoadingSheetOrders { get; set; }
|
||||
public DbSet<DeliveryRun> DeliveryRuns { get; set; }
|
||||
public DbSet<User> Users { get; set; }
|
||||
@@ -65,6 +68,15 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
|
||||
public DbSet<RegionalTaxRule> RegionalTaxRules { get; set; }
|
||||
public DbSet<RouteVisitLog> RouteVisitLogs { get; set; }
|
||||
public DbSet<SysConfig> SysConfigs { get; set; }
|
||||
public DbSet<BankAccount> BankAccounts { get; set; }
|
||||
public DbSet<ChequeBook> ChequeBooks { get; set; }
|
||||
public DbSet<ChequePage> ChequePages { get; set; }
|
||||
public DbSet<ReceivedCheque> ReceivedCheques { get; set; }
|
||||
public DbSet<SupplierPayment> SupplierPayments { get; set; }
|
||||
public DbSet<SupplierPaymentGRN> SupplierPaymentGRNs { get; set; }
|
||||
public DbSet<Notification> Notifications { get; set; }
|
||||
public DbSet<NotificationRecipient> NotificationRecipients { get; set; }
|
||||
public DbSet<StoreCreditMovement> StoreCreditMovements { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
@@ -166,6 +178,54 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
|
||||
.HasForeignKey(i => i.FK_ShopPOId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<InvoiceItem>()
|
||||
.HasOne(i => i.Invoice)
|
||||
.WithMany(inv => inv.Items)
|
||||
.HasForeignKey(i => i.FK_InvoiceId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<InvoiceItem>()
|
||||
.HasOne(i => i.ShopPOItem)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.FK_ShopPOItemId)
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired(false);
|
||||
|
||||
modelBuilder.Entity<InvoiceItem>()
|
||||
.HasOne(i => i.Item)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.FK_ItemId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<InvoiceVersion>()
|
||||
.HasOne(v => v.Invoice)
|
||||
.WithMany()
|
||||
.HasForeignKey(v => v.FK_InvoiceId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<InvoiceVersion>()
|
||||
.HasIndex(v => new { v.FK_InvoiceId, v.VersionNumber })
|
||||
.IsUnique();
|
||||
|
||||
modelBuilder.Entity<InvoiceVersionItem>()
|
||||
.HasOne(i => i.InvoiceVersion)
|
||||
.WithMany(v => v.Items)
|
||||
.HasForeignKey(i => i.FK_InvoiceVersionId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<InvoiceVersionItem>()
|
||||
.HasOne(i => i.ShopPOItem)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.FK_ShopPOItemId)
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired(false);
|
||||
|
||||
modelBuilder.Entity<InvoiceVersionItem>()
|
||||
.HasOne(i => i.Item)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.FK_ItemId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<Invoice>()
|
||||
.HasMany(i => i.Payments)
|
||||
.WithOne(p => p.Invoice)
|
||||
@@ -594,6 +654,165 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
|
||||
new SysConfig { Key = Distribution.Features.SysConfig.SysConfigService.KeyDefaultWarrantyPeriodDays, Value = "0", UpdatedAt = new DateTime(2026, 10, 1), UpdatedBy = null }
|
||||
);
|
||||
|
||||
// --- Payment service: Bank Accounts & Cheques ---
|
||||
|
||||
modelBuilder.Entity<BankAccount>()
|
||||
.Property(a => a.AccountName).HasMaxLength(200);
|
||||
modelBuilder.Entity<BankAccount>()
|
||||
.Property(a => a.BankName).HasMaxLength(200);
|
||||
modelBuilder.Entity<BankAccount>()
|
||||
.Property(a => a.CashAccountTypeName).HasMaxLength(100);
|
||||
modelBuilder.Entity<BankAccount>()
|
||||
.Property(a => a.AccountNumber).HasMaxLength(100);
|
||||
modelBuilder.Entity<BankAccount>()
|
||||
.Property(a => a.CurrencyCode).HasMaxLength(10);
|
||||
modelBuilder.Entity<BankAccount>()
|
||||
.HasIndex(a => new { a.AccountType, a.AccountName })
|
||||
.IsUnique();
|
||||
|
||||
modelBuilder.Entity<BankAccount>().HasData(new BankAccount
|
||||
{
|
||||
Id = new Guid("11111111-1111-1111-1111-000000000001"),
|
||||
CreatedAt = new DateTime(2026, 10, 19),
|
||||
IsActive = true,
|
||||
AccountType = BankAccountType.Cash,
|
||||
AccountName = "Petty Cash",
|
||||
CashAccountTypeName = "Petty Cash",
|
||||
CurrencyCode = "LKR",
|
||||
IsDefault = true
|
||||
});
|
||||
|
||||
modelBuilder.Entity<ChequeBook>()
|
||||
.Property(b => b.ChequeBookNo).HasMaxLength(100);
|
||||
modelBuilder.Entity<ChequeBook>()
|
||||
.Property(b => b.StartChequeNo).HasMaxLength(50);
|
||||
modelBuilder.Entity<ChequeBook>()
|
||||
.Property(b => b.EndChequeNo).HasMaxLength(50);
|
||||
modelBuilder.Entity<ChequeBook>()
|
||||
.HasIndex(b => b.ChequeBookNo).IsUnique();
|
||||
modelBuilder.Entity<ChequeBook>()
|
||||
.HasOne(b => b.BankAccount)
|
||||
.WithMany()
|
||||
.HasForeignKey(b => b.FK_BankAccountId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<ChequePage>()
|
||||
.Property(p => p.ChequeNo).HasMaxLength(50);
|
||||
modelBuilder.Entity<ChequePage>()
|
||||
.Property(p => p.CurrencyCode).HasMaxLength(10);
|
||||
modelBuilder.Entity<ChequePage>()
|
||||
.HasIndex(p => p.ChequeNo).IsUnique();
|
||||
modelBuilder.Entity<ChequePage>()
|
||||
.HasOne(p => p.ChequeBook)
|
||||
.WithMany(b => b.Pages)
|
||||
.HasForeignKey(p => p.FK_ChequeBookId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<ReceivedCheque>()
|
||||
.Property(c => c.ChequeNo).HasMaxLength(50);
|
||||
modelBuilder.Entity<ReceivedCheque>()
|
||||
.Property(c => c.ReceivedFromName).HasMaxLength(200);
|
||||
modelBuilder.Entity<ReceivedCheque>()
|
||||
.HasIndex(c => c.ChequeNo);
|
||||
modelBuilder.Entity<ReceivedCheque>()
|
||||
.HasOne(c => c.DepositBankAccount)
|
||||
.WithMany()
|
||||
.HasForeignKey(c => c.FK_DepositBankAccountId)
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired(false);
|
||||
|
||||
// --- SupplierPayment ---
|
||||
|
||||
modelBuilder.Entity<SupplierPayment>()
|
||||
.HasOne(p => p.Supplier)
|
||||
.WithMany()
|
||||
.HasForeignKey(p => p.FK_SupplierId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<SupplierPayment>()
|
||||
.HasOne(p => p.BankAccount)
|
||||
.WithMany()
|
||||
.HasForeignKey(p => p.FK_BankAccountId)
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired(false);
|
||||
|
||||
modelBuilder.Entity<SupplierPayment>()
|
||||
.HasOne(p => p.ChequePage)
|
||||
.WithMany()
|
||||
.HasForeignKey(p => p.FK_ChequePageId)
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired(false);
|
||||
|
||||
modelBuilder.Entity<SupplierPayment>()
|
||||
.Property(p => p.Amount).HasPrecision(18, 2);
|
||||
|
||||
modelBuilder.Entity<SupplierPaymentGRN>()
|
||||
.HasOne(a => a.SupplierPayment)
|
||||
.WithMany(p => p.Allocations)
|
||||
.HasForeignKey(a => a.FK_SupplierPaymentId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<SupplierPaymentGRN>()
|
||||
.HasOne(a => a.GoodsReceipt)
|
||||
.WithMany()
|
||||
.HasForeignKey(a => a.FK_GoodsReceiptId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<SupplierPaymentGRN>()
|
||||
.HasIndex(a => a.FK_GoodsReceiptId);
|
||||
|
||||
// --- Notifications ---
|
||||
|
||||
modelBuilder.Entity<Notification>()
|
||||
.HasIndex(n => new { n.EntityType, n.EntityId });
|
||||
|
||||
modelBuilder.Entity<NotificationRecipient>()
|
||||
.HasOne(r => r.Notification)
|
||||
.WithMany(n => n.Recipients)
|
||||
.HasForeignKey(r => r.FK_NotificationId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<NotificationRecipient>()
|
||||
.HasOne(r => r.User)
|
||||
.WithMany()
|
||||
.HasForeignKey(r => r.FK_UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<NotificationRecipient>()
|
||||
.HasIndex(r => new { r.FK_UserId, r.IsRead });
|
||||
|
||||
modelBuilder.Entity<NotificationRecipient>()
|
||||
.HasIndex(r => new { r.FK_NotificationId, r.FK_UserId })
|
||||
.IsUnique();
|
||||
|
||||
// --- StoreCreditMovement ---
|
||||
|
||||
modelBuilder.Entity<StoreCreditMovement>()
|
||||
.HasOne(m => m.Store)
|
||||
.WithMany()
|
||||
.HasForeignKey(m => m.FK_StoreId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<StoreCreditMovement>()
|
||||
.HasOne(m => m.Invoice)
|
||||
.WithMany()
|
||||
.HasForeignKey(m => m.FK_InvoiceId)
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired(false);
|
||||
|
||||
modelBuilder.Entity<StoreCreditMovement>()
|
||||
.HasOne(m => m.Payment)
|
||||
.WithMany()
|
||||
.HasForeignKey(m => m.FK_PaymentId)
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired(false);
|
||||
|
||||
modelBuilder.Entity<StoreCreditMovement>()
|
||||
.Property(m => m.Amount).HasPrecision(18, 2);
|
||||
|
||||
modelBuilder.Entity<StoreCreditMovement>()
|
||||
.HasIndex(m => new { m.FK_StoreId, m.OccurredAt });
|
||||
|
||||
Seed.RoleAndPermissionSeed.Apply(modelBuilder);
|
||||
Seed.MasterDataSeed.Apply(modelBuilder);
|
||||
Seed.OperationsDataSeed.Apply(modelBuilder);
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
using Distribution.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.BankAccount;
|
||||
|
||||
[ApiController]
|
||||
[Route("api")]
|
||||
public class BankAccountController(BankAccountService service) : ControllerBase
|
||||
{
|
||||
[HttpGet("bank-accounts")]
|
||||
public async Task<IActionResult> List([FromQuery] BankAccountType? accountType)
|
||||
{
|
||||
var result = await service.ListAsync(accountType);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost("bank-accounts")]
|
||||
public async Task<IActionResult> CreateBank(CreateBankAccountRequest request, CancellationToken ct)
|
||||
{
|
||||
try { return Ok(await service.CreateBankAsync(request, ct)); }
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPost("cash-accounts")]
|
||||
public async Task<IActionResult> CreateCash(CreateCashAccountRequest request, CancellationToken ct)
|
||||
{
|
||||
try { return Ok(await service.CreateCashAsync(request, ct)); }
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPut("cash-accounts/{id:guid}")]
|
||||
public async Task<IActionResult> UpdateCash(Guid id, UpdateCashAccountRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await service.UpdateCashAsync(id, request, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPatch("cash-accounts/{id:guid}/status")]
|
||||
public async Task<IActionResult> SetCashStatus(Guid id, SetCashAccountStatusRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await service.SetCashStatusAsync(id, request, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpDelete("cash-accounts/{id:guid}")]
|
||||
public async Task<IActionResult> DeleteCash(Guid id, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var ok = await service.DeleteCashAsync(id, ct);
|
||||
return ok ? NoContent() : NotFound();
|
||||
}
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpGet("cash-account-types")]
|
||||
public async Task<IActionResult> ListCashAccountTypes() => Ok(await service.ListCashAccountTypesAsync());
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.BankAccount;
|
||||
|
||||
public record BankAccountResponse(
|
||||
BankAccountType AccountType,
|
||||
Guid AccountId,
|
||||
string AccountName,
|
||||
string? BankName,
|
||||
string? CashAccountTypeName,
|
||||
string? AccountNumber,
|
||||
string CurrencyCode,
|
||||
DateTime CreatedAt,
|
||||
bool? IsActive,
|
||||
bool? IsDefault);
|
||||
|
||||
public record CreateBankAccountRequest(
|
||||
string AccountName,
|
||||
string? BankName,
|
||||
string? AccountNumber,
|
||||
string? CurrencyCode);
|
||||
|
||||
public record CreateCashAccountRequest(
|
||||
string AccountName,
|
||||
string CashAccountTypeName,
|
||||
string? AccountNumber,
|
||||
string? CurrencyCode);
|
||||
|
||||
public record UpdateCashAccountRequest(string AccountName);
|
||||
|
||||
public record SetCashAccountStatusRequest(bool IsActive);
|
||||
|
||||
public record CashAccountTypeDto(string Name);
|
||||
@@ -0,0 +1,40 @@
|
||||
using Distribution.Data;
|
||||
using Distribution.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.BankAccount;
|
||||
|
||||
public class BankAccountRepository(AppDBContext context) : IBankAccountRepository
|
||||
{
|
||||
public async Task<Models.BankAccount?> GetByIdAsync(Guid id) =>
|
||||
await context.BankAccounts.FirstOrDefaultAsync(a => a.Id == id);
|
||||
|
||||
public async Task<List<Models.BankAccount>> ListAsync(BankAccountType? accountType)
|
||||
{
|
||||
var query = context.BankAccounts.AsQueryable();
|
||||
if (accountType is not null)
|
||||
query = query.Where(a => a.AccountType == accountType);
|
||||
return await query.OrderByDescending(a => a.CreatedAt).ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<List<string>> ListCashAccountTypeNamesAsync() =>
|
||||
await context.BankAccounts
|
||||
.Where(a => a.AccountType == BankAccountType.Cash && a.CashAccountTypeName != null)
|
||||
.Select(a => a.CashAccountTypeName!)
|
||||
.Distinct()
|
||||
.OrderBy(n => n)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task<bool> ExistsByNameAsync(BankAccountType type, string accountName, Guid? excludeId = null) =>
|
||||
await context.BankAccounts.AnyAsync(a =>
|
||||
a.AccountType == type &&
|
||||
a.AccountName == accountName &&
|
||||
(excludeId == null || a.Id != excludeId));
|
||||
|
||||
public async Task AddAsync(Models.BankAccount account) =>
|
||||
await context.BankAccounts.AddAsync(account);
|
||||
|
||||
public void Update(Models.BankAccount account) => context.BankAccounts.Update(account);
|
||||
|
||||
public void Remove(Models.BankAccount account) => context.BankAccounts.Remove(account);
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
using Distribution.Infra.UoW;
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.BankAccount;
|
||||
|
||||
public class BankAccountService(IBankAccountRepository repo, IUnitOfWork uow)
|
||||
{
|
||||
private const string DefaultCurrency = "LKR";
|
||||
|
||||
private static BankAccountResponse ToResponse(Models.BankAccount a) =>
|
||||
new(
|
||||
a.AccountType,
|
||||
a.Id,
|
||||
a.AccountName,
|
||||
a.AccountType == BankAccountType.Bank ? a.BankName : null,
|
||||
a.AccountType == BankAccountType.Cash ? a.CashAccountTypeName : null,
|
||||
a.AccountNumber,
|
||||
a.CurrencyCode,
|
||||
a.CreatedAt,
|
||||
a.AccountType == BankAccountType.Cash ? a.IsActive : null,
|
||||
a.AccountType == BankAccountType.Cash ? a.IsDefault : null);
|
||||
|
||||
public async Task<List<BankAccountResponse>> ListAsync(BankAccountType? accountType) =>
|
||||
(await repo.ListAsync(accountType)).Select(ToResponse).ToList();
|
||||
|
||||
public async Task<List<CashAccountTypeDto>> ListCashAccountTypesAsync() =>
|
||||
(await repo.ListCashAccountTypeNamesAsync()).Select(n => new CashAccountTypeDto(n)).ToList();
|
||||
|
||||
public async Task<BankAccountResponse> CreateBankAsync(CreateBankAccountRequest request, CancellationToken ct = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.AccountName))
|
||||
throw new InvalidOperationException("AccountName is required");
|
||||
if (await repo.ExistsByNameAsync(BankAccountType.Bank, request.AccountName))
|
||||
throw new InvalidOperationException("A bank account with this name already exists");
|
||||
|
||||
var account = new Models.BankAccount
|
||||
{
|
||||
AccountType = BankAccountType.Bank,
|
||||
AccountName = request.AccountName,
|
||||
BankName = request.BankName,
|
||||
AccountNumber = request.AccountNumber,
|
||||
CurrencyCode = string.IsNullOrWhiteSpace(request.CurrencyCode) ? DefaultCurrency : request.CurrencyCode!,
|
||||
IsActive = true,
|
||||
IsDefault = false
|
||||
};
|
||||
|
||||
await repo.AddAsync(account);
|
||||
await uow.CommitAsync(ct);
|
||||
return ToResponse(account);
|
||||
}
|
||||
|
||||
public async Task<BankAccountResponse> CreateCashAsync(CreateCashAccountRequest request, CancellationToken ct = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.AccountName))
|
||||
throw new InvalidOperationException("AccountName is required");
|
||||
if (string.IsNullOrWhiteSpace(request.CashAccountTypeName))
|
||||
throw new InvalidOperationException("CashAccountTypeName is required");
|
||||
if (await repo.ExistsByNameAsync(BankAccountType.Cash, request.AccountName))
|
||||
throw new InvalidOperationException("A cash account with this name already exists");
|
||||
|
||||
var account = new Models.BankAccount
|
||||
{
|
||||
AccountType = BankAccountType.Cash,
|
||||
AccountName = request.AccountName,
|
||||
CashAccountTypeName = request.CashAccountTypeName,
|
||||
AccountNumber = request.AccountNumber,
|
||||
CurrencyCode = string.IsNullOrWhiteSpace(request.CurrencyCode) ? DefaultCurrency : request.CurrencyCode!,
|
||||
IsActive = true,
|
||||
IsDefault = false
|
||||
};
|
||||
|
||||
await repo.AddAsync(account);
|
||||
await uow.CommitAsync(ct);
|
||||
return ToResponse(account);
|
||||
}
|
||||
|
||||
public async Task<BankAccountResponse?> UpdateCashAsync(Guid id, UpdateCashAccountRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var account = await repo.GetByIdAsync(id);
|
||||
if (account is null || account.AccountType != BankAccountType.Cash) return null;
|
||||
if (string.IsNullOrWhiteSpace(request.AccountName))
|
||||
throw new InvalidOperationException("AccountName is required");
|
||||
if (await repo.ExistsByNameAsync(BankAccountType.Cash, request.AccountName, excludeId: id))
|
||||
throw new InvalidOperationException("A cash account with this name already exists");
|
||||
|
||||
account.AccountName = request.AccountName;
|
||||
repo.Update(account);
|
||||
await uow.CommitAsync(ct);
|
||||
return ToResponse(account);
|
||||
}
|
||||
|
||||
public async Task<BankAccountResponse?> SetCashStatusAsync(Guid id, SetCashAccountStatusRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var account = await repo.GetByIdAsync(id);
|
||||
if (account is null || account.AccountType != BankAccountType.Cash) return null;
|
||||
if (account.IsDefault && !request.IsActive)
|
||||
throw new InvalidOperationException("The default cash account cannot be deactivated");
|
||||
|
||||
account.IsActive = request.IsActive;
|
||||
repo.Update(account);
|
||||
await uow.CommitAsync(ct);
|
||||
return ToResponse(account);
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteCashAsync(Guid id, CancellationToken ct = default)
|
||||
{
|
||||
var account = await repo.GetByIdAsync(id);
|
||||
if (account is null || account.AccountType != BankAccountType.Cash) return false;
|
||||
if (account.IsDefault)
|
||||
throw new InvalidOperationException("The default cash account cannot be deleted");
|
||||
|
||||
repo.Remove(account);
|
||||
await uow.CommitAsync(ct);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.BankAccount;
|
||||
|
||||
public interface IBankAccountRepository
|
||||
{
|
||||
Task<Models.BankAccount?> GetByIdAsync(Guid id);
|
||||
Task<List<Models.BankAccount>> ListAsync(BankAccountType? accountType);
|
||||
Task<List<string>> ListCashAccountTypeNamesAsync();
|
||||
Task<bool> ExistsByNameAsync(BankAccountType type, string accountName, Guid? excludeId = null);
|
||||
Task AddAsync(Models.BankAccount account);
|
||||
void Update(Models.BankAccount account);
|
||||
void Remove(Models.BankAccount account);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using Distribution.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.ChequeBook;
|
||||
|
||||
[ApiController]
|
||||
[Route("api")]
|
||||
public class ChequeBookController(ChequeBookService service) : ControllerBase
|
||||
{
|
||||
[HttpGet("cheque-books")]
|
||||
public async Task<IActionResult> List(
|
||||
[FromQuery] Guid? bankAccountId,
|
||||
[FromQuery] int? branchId,
|
||||
[FromQuery] ChequeBookStatus? status,
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 50)
|
||||
=> Ok(await service.ListAsync(bankAccountId, branchId, status, page, pageSize));
|
||||
|
||||
[HttpGet("cheque-books/{chequeBookNo}")]
|
||||
public async Task<IActionResult> Get(string chequeBookNo, [FromQuery] string? expand)
|
||||
{
|
||||
var expandPages = string.Equals(expand, "pages", StringComparison.OrdinalIgnoreCase);
|
||||
var result = await service.GetAsync(chequeBookNo, expandPages);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost("cheque-books")]
|
||||
public async Task<IActionResult> Create(CreateChequeBookRequest request, CancellationToken ct)
|
||||
{
|
||||
try { return Ok(await service.CreateAsync(request, ct)); }
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPut("cheque-pages/{chequeNo}/issue")]
|
||||
public async Task<IActionResult> Issue(string chequeNo, IssueChequePageRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await service.IssueAsync(chequeNo, request, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPut("cheque-pages/{chequeNo}/status")]
|
||||
public async Task<IActionResult> UpdateStatus(string chequeNo, UpdateChequePageStatusRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await service.UpdatePageStatusAsync(chequeNo, request, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.ChequeBook;
|
||||
|
||||
public record ChequePageResponse(
|
||||
Guid Id,
|
||||
string ChequeNo,
|
||||
string ChequeBookNo,
|
||||
ChequePageIssueStatus IssueStatus,
|
||||
PayeeType? PayeeType,
|
||||
int? PayeeId,
|
||||
string? PayeeName,
|
||||
DateTime? IssueDate,
|
||||
decimal? Amount,
|
||||
string? CurrencyCode,
|
||||
int? VoucherId,
|
||||
string? ReferenceNo,
|
||||
string? Purpose,
|
||||
bool? IsCrossCheque,
|
||||
bool? IsAccountPayee,
|
||||
bool? IsPostDated,
|
||||
string? Notes,
|
||||
string? PrintedBy,
|
||||
DateTime? PrintedAt,
|
||||
DateTime? ClearedDate,
|
||||
bool? ClearedByBank,
|
||||
string? CancelReason);
|
||||
|
||||
public record ChequeBookResponse(
|
||||
string ChequeBookNo,
|
||||
int BranchId,
|
||||
Guid BankAccountId,
|
||||
string StartChequeNo,
|
||||
string EndChequeNo,
|
||||
int TotalLeaves,
|
||||
DateTime ReceivedDate,
|
||||
string? Description,
|
||||
string? CreatedBy,
|
||||
ChequeBookStatus Status,
|
||||
List<ChequePageResponse> Pages);
|
||||
|
||||
public record PagedResult<T>(IReadOnlyList<T> Items, int TotalCount, int? Page, int? PageSize);
|
||||
|
||||
public record CreateChequeBookRequest(
|
||||
int BranchId,
|
||||
Guid BankAccountId,
|
||||
string ChequeBookNo,
|
||||
string StartChequeNo,
|
||||
string EndChequeNo,
|
||||
int TotalLeaves,
|
||||
DateTime ReceivedDate,
|
||||
string? Description,
|
||||
string? CreatedBy);
|
||||
|
||||
public record IssueChequePageRequest(
|
||||
PayeeType PayeeType,
|
||||
int? PayeeId,
|
||||
string PayeeName,
|
||||
DateTime IssueDate,
|
||||
decimal Amount,
|
||||
string? CurrencyCode,
|
||||
int? VoucherId,
|
||||
string? ReferenceNo,
|
||||
string? Purpose,
|
||||
bool? IsCrossCheque,
|
||||
bool? IsAccountPayee,
|
||||
bool? IsPostDated,
|
||||
string? Notes,
|
||||
string? PrintedBy);
|
||||
|
||||
public record UpdateChequePageStatusRequest(
|
||||
ChequePageStatusAction Action,
|
||||
DateTime? ClearedDate,
|
||||
string? CancelReason,
|
||||
string? PerformedBy);
|
||||
@@ -0,0 +1,46 @@
|
||||
using Distribution.Data;
|
||||
using Distribution.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.ChequeBook;
|
||||
|
||||
public class ChequeBookRepository(AppDBContext context) : IChequeBookRepository
|
||||
{
|
||||
public async Task<Models.ChequeBook?> GetByNoAsync(string chequeBookNo, bool includePages)
|
||||
{
|
||||
var query = context.ChequeBooks.AsQueryable();
|
||||
if (includePages) query = query.Include(b => b.Pages);
|
||||
return await query.FirstOrDefaultAsync(b => b.ChequeBookNo == chequeBookNo);
|
||||
}
|
||||
|
||||
public async Task<(List<Models.ChequeBook> Items, int TotalCount)> ListAsync(
|
||||
Guid? bankAccountId, int? branchId, ChequeBookStatus? status, int page, int pageSize)
|
||||
{
|
||||
var query = context.ChequeBooks.AsQueryable();
|
||||
if (bankAccountId is not null) query = query.Where(b => b.FK_BankAccountId == bankAccountId);
|
||||
if (branchId is not null) query = query.Where(b => b.BranchId == branchId);
|
||||
if (status is not null) query = query.Where(b => b.Status == status);
|
||||
|
||||
var total = await query.CountAsync();
|
||||
var items = await query
|
||||
.OrderByDescending(b => b.CreatedAt)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
return (items, total);
|
||||
}
|
||||
|
||||
public async Task<bool> BookNoExistsAsync(string chequeBookNo) =>
|
||||
await context.ChequeBooks.AnyAsync(b => b.ChequeBookNo == chequeBookNo);
|
||||
|
||||
public async Task AddAsync(Models.ChequeBook book) => await context.ChequeBooks.AddAsync(book);
|
||||
|
||||
public void Update(Models.ChequeBook book) => context.ChequeBooks.Update(book);
|
||||
|
||||
public async Task<Models.ChequePage?> GetPageByNoAsync(string chequeNo) =>
|
||||
await context.ChequePages
|
||||
.Include(p => p.ChequeBook)
|
||||
.FirstOrDefaultAsync(p => p.ChequeNo == chequeNo);
|
||||
|
||||
public void UpdatePage(Models.ChequePage page) => context.ChequePages.Update(page);
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
using Distribution.Features.BankAccount;
|
||||
using Distribution.Infra.UoW;
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.ChequeBook;
|
||||
|
||||
public class ChequeBookService(
|
||||
IChequeBookRepository repo,
|
||||
IBankAccountRepository bankAccountRepo,
|
||||
IUnitOfWork uow)
|
||||
{
|
||||
private const string DefaultCurrency = "LKR";
|
||||
|
||||
private static ChequePageResponse ToPageResponse(Models.ChequePage p, string chequeBookNo) =>
|
||||
new(p.Id, p.ChequeNo, chequeBookNo, p.IssueStatus, p.PayeeType, p.PayeeId, p.PayeeName,
|
||||
p.IssueDate, p.Amount, p.CurrencyCode, p.VoucherId, p.ReferenceNo, p.Purpose,
|
||||
p.IsCrossCheque, p.IsAccountPayee, p.IsPostDated, p.Notes,
|
||||
p.PrintedBy, p.PrintedAt, p.ClearedDate, p.ClearedByBank, p.CancelReason);
|
||||
|
||||
private static ChequeBookResponse ToBookResponse(Models.ChequeBook b, bool includePages) =>
|
||||
new(b.ChequeBookNo, b.BranchId, b.FK_BankAccountId, b.StartChequeNo, b.EndChequeNo,
|
||||
b.TotalLeaves, b.ReceivedDate, b.Description, b.CreatedBy, b.Status,
|
||||
includePages ? b.Pages.OrderBy(p => p.ChequeNo).Select(p => ToPageResponse(p, b.ChequeBookNo)).ToList() : new());
|
||||
|
||||
public async Task<PagedResult<ChequeBookResponse>> ListAsync(
|
||||
Guid? bankAccountId, int? branchId, ChequeBookStatus? status, int page, int pageSize)
|
||||
{
|
||||
if (page < 1) page = 1;
|
||||
if (pageSize < 1 || pageSize > 200) pageSize = 50;
|
||||
|
||||
var (items, total) = await repo.ListAsync(bankAccountId, branchId, status, page, pageSize);
|
||||
return new PagedResult<ChequeBookResponse>(
|
||||
items.Select(b => ToBookResponse(b, includePages: false)).ToList(),
|
||||
total, page, pageSize);
|
||||
}
|
||||
|
||||
public async Task<ChequeBookResponse?> GetAsync(string chequeBookNo, bool expandPages)
|
||||
{
|
||||
var book = await repo.GetByNoAsync(chequeBookNo, includePages: expandPages);
|
||||
return book is null ? null : ToBookResponse(book, includePages: expandPages);
|
||||
}
|
||||
|
||||
public async Task<ChequeBookResponse> CreateAsync(CreateChequeBookRequest request, CancellationToken ct = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.ChequeBookNo))
|
||||
throw new InvalidOperationException("ChequeBookNo is required");
|
||||
if (request.TotalLeaves <= 0)
|
||||
throw new InvalidOperationException("TotalLeaves must be greater than zero");
|
||||
if (!long.TryParse(request.StartChequeNo, out var start) ||
|
||||
!long.TryParse(request.EndChequeNo, out var end))
|
||||
throw new InvalidOperationException("StartChequeNo and EndChequeNo must be numeric");
|
||||
if (end < start)
|
||||
throw new InvalidOperationException("EndChequeNo must be greater than or equal to StartChequeNo");
|
||||
if (end - start + 1 != request.TotalLeaves)
|
||||
throw new InvalidOperationException("TotalLeaves does not match the Start/End cheque range");
|
||||
if (await repo.BookNoExistsAsync(request.ChequeBookNo))
|
||||
throw new InvalidOperationException("A cheque book with this number already exists");
|
||||
|
||||
var bankAccount = await bankAccountRepo.GetByIdAsync(request.BankAccountId);
|
||||
if (bankAccount is null || bankAccount.AccountType != BankAccountType.Bank)
|
||||
throw new InvalidOperationException("BankAccountId does not reference a valid bank account");
|
||||
|
||||
var book = new Models.ChequeBook
|
||||
{
|
||||
ChequeBookNo = request.ChequeBookNo,
|
||||
BranchId = request.BranchId,
|
||||
FK_BankAccountId = request.BankAccountId,
|
||||
StartChequeNo = request.StartChequeNo,
|
||||
EndChequeNo = request.EndChequeNo,
|
||||
TotalLeaves = request.TotalLeaves,
|
||||
ReceivedDate = request.ReceivedDate,
|
||||
Description = request.Description,
|
||||
CreatedBy = request.CreatedBy,
|
||||
Status = ChequeBookStatus.Active
|
||||
};
|
||||
|
||||
var width = request.StartChequeNo.Length;
|
||||
for (var n = start; n <= end; n++)
|
||||
{
|
||||
book.Pages.Add(new Models.ChequePage
|
||||
{
|
||||
ChequeNo = n.ToString().PadLeft(width, '0'),
|
||||
IssueStatus = ChequePageIssueStatus.Unused
|
||||
});
|
||||
}
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
await repo.AddAsync(book);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return ToBookResponse(book, includePages: true);
|
||||
}
|
||||
|
||||
public async Task<ChequePageResponse?> IssueAsync(string chequeNo, IssueChequePageRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var page = await repo.GetPageByNoAsync(chequeNo);
|
||||
if (page is null) return null;
|
||||
if (page.IssueStatus != ChequePageIssueStatus.Unused)
|
||||
throw new InvalidOperationException($"Cannot issue a cheque in status '{page.IssueStatus}'");
|
||||
if (page.ChequeBook is null || page.ChequeBook.Status != ChequeBookStatus.Active)
|
||||
throw new InvalidOperationException("The cheque book is not active");
|
||||
if (request.Amount <= 0)
|
||||
throw new InvalidOperationException("Amount must be greater than zero");
|
||||
if (string.IsNullOrWhiteSpace(request.PayeeName))
|
||||
throw new InvalidOperationException("PayeeName is required");
|
||||
|
||||
page.IssueStatus = ChequePageIssueStatus.Issued;
|
||||
page.PayeeType = request.PayeeType;
|
||||
page.PayeeId = request.PayeeId;
|
||||
page.PayeeName = request.PayeeName;
|
||||
page.IssueDate = request.IssueDate;
|
||||
page.Amount = request.Amount;
|
||||
page.CurrencyCode = string.IsNullOrWhiteSpace(request.CurrencyCode) ? DefaultCurrency : request.CurrencyCode;
|
||||
page.VoucherId = request.VoucherId;
|
||||
page.ReferenceNo = request.ReferenceNo;
|
||||
page.Purpose = request.Purpose;
|
||||
page.IsCrossCheque = request.IsCrossCheque;
|
||||
page.IsAccountPayee = request.IsAccountPayee;
|
||||
page.IsPostDated = request.IsPostDated;
|
||||
page.Notes = request.Notes;
|
||||
page.PrintedBy = request.PrintedBy;
|
||||
page.PrintedAt = DateTime.Now;
|
||||
|
||||
repo.UpdatePage(page);
|
||||
await uow.CommitAsync(ct);
|
||||
return ToPageResponse(page, page.ChequeBook!.ChequeBookNo);
|
||||
}
|
||||
|
||||
public async Task<ChequePageResponse?> UpdatePageStatusAsync(string chequeNo, UpdateChequePageStatusRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var page = await repo.GetPageByNoAsync(chequeNo);
|
||||
if (page is null) return null;
|
||||
|
||||
switch (request.Action)
|
||||
{
|
||||
case ChequePageStatusAction.Clear:
|
||||
if (page.IssueStatus != ChequePageIssueStatus.Issued)
|
||||
throw new InvalidOperationException("Only issued cheques can be cleared");
|
||||
if (request.ClearedDate is null)
|
||||
throw new InvalidOperationException("ClearedDate is required when clearing a cheque");
|
||||
page.IssueStatus = ChequePageIssueStatus.Cleared;
|
||||
page.ClearedDate = request.ClearedDate;
|
||||
page.ClearedByBank = true;
|
||||
break;
|
||||
|
||||
case ChequePageStatusAction.Bounce:
|
||||
if (page.IssueStatus != ChequePageIssueStatus.Issued)
|
||||
throw new InvalidOperationException("Only issued cheques can be bounced");
|
||||
page.IssueStatus = ChequePageIssueStatus.Bounced;
|
||||
break;
|
||||
|
||||
case ChequePageStatusAction.Cancel:
|
||||
if (page.IssueStatus != ChequePageIssueStatus.Issued)
|
||||
throw new InvalidOperationException("Only issued cheques can be cancelled");
|
||||
if (string.IsNullOrWhiteSpace(request.CancelReason))
|
||||
throw new InvalidOperationException("CancelReason is required when cancelling a cheque");
|
||||
page.IssueStatus = ChequePageIssueStatus.Cancelled;
|
||||
page.CancelReason = request.CancelReason;
|
||||
break;
|
||||
|
||||
case ChequePageStatusAction.Void:
|
||||
if (page.IssueStatus != ChequePageIssueStatus.Unused)
|
||||
throw new InvalidOperationException("Only unused cheques can be voided");
|
||||
page.IssueStatus = ChequePageIssueStatus.Void;
|
||||
break;
|
||||
}
|
||||
|
||||
repo.UpdatePage(page);
|
||||
await uow.CommitAsync(ct);
|
||||
return ToPageResponse(page, page.ChequeBook!.ChequeBookNo);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.ChequeBook;
|
||||
|
||||
public interface IChequeBookRepository
|
||||
{
|
||||
Task<Models.ChequeBook?> GetByNoAsync(string chequeBookNo, bool includePages);
|
||||
Task<(List<Models.ChequeBook> Items, int TotalCount)> ListAsync(
|
||||
Guid? bankAccountId, int? branchId, ChequeBookStatus? status, int page, int pageSize);
|
||||
Task<bool> BookNoExistsAsync(string chequeBookNo);
|
||||
Task AddAsync(Models.ChequeBook book);
|
||||
void Update(Models.ChequeBook book);
|
||||
|
||||
Task<Models.ChequePage?> GetPageByNoAsync(string chequeNo);
|
||||
void UpdatePage(Models.ChequePage page);
|
||||
}
|
||||
@@ -2,21 +2,27 @@ namespace Distribution.Features.DashboardReport;
|
||||
|
||||
public record DashboardFilter(DateTime From, DateTime To, Guid? RegionId, Guid? CategoryId, Guid? WarehouseId);
|
||||
|
||||
public record SalesTotals(decimal GrossSales, decimal ReturnValue, int Orders, decimal CostedSales, decimal CostOfSales);
|
||||
public record SalesTotals(decimal GrossSales, decimal ReturnValue, decimal Discounts, int Orders, decimal CostedSales, decimal CostOfSales);
|
||||
|
||||
public record SalesKpis(
|
||||
decimal GrossSales, decimal ReturnValue, decimal NetSales, int Orders, decimal AverageOrderValue,
|
||||
decimal GrossSales, decimal ReturnValue, decimal Discounts, decimal NetSales, int Orders, decimal AverageOrderValue,
|
||||
decimal GrossProfit, decimal? GrossProfitMarginPercent);
|
||||
|
||||
public record SalesKpiComparison(SalesKpis Current, SalesKpis Previous, DateTime PreviousFrom, DateTime PreviousTo);
|
||||
|
||||
public record ReceivablesTotals(decimal Collected, decimal Outstanding, decimal Overdue);
|
||||
|
||||
public record CashKpis(decimal Collected, decimal Outstanding, decimal Overdue, decimal? CollectionRatePercent);
|
||||
|
||||
public record CashKpiComparison(CashKpis Current, CashKpis Previous);
|
||||
|
||||
public record TrendPoint(string Period, decimal Sales, decimal Returns);
|
||||
|
||||
public record NamedAmount(Guid? Id, string Name, decimal Amount);
|
||||
|
||||
public record SalesSummaryResponse(
|
||||
DateTime From, DateTime To, string Granularity,
|
||||
SalesKpiComparison Kpis, List<TrendPoint> Trend,
|
||||
SalesKpiComparison Kpis, CashKpiComparison Cash, List<TrendPoint> Trend,
|
||||
List<NamedAmount> ByCategory, List<NamedAmount> ByRegion, List<NamedAmount> ByRep);
|
||||
|
||||
public record PeriodAmount(DateTime Date, decimal Amount);
|
||||
|
||||
@@ -19,29 +19,30 @@ public class DashboardReportRepository(AppDBContext db) : IDashboardReportReposi
|
||||
public Guid RepId { get; set; }
|
||||
}
|
||||
|
||||
// Same definition of "sold" as ProductReportRepository: fulfilled lines on non-cancelled POs
|
||||
// whose invoice is Finalized, dated by Invoice.FinalizedAt. 'To' is inclusive (whole day).
|
||||
// Same definition of "sold" as ProductReportRepository: billable (non-removed) invoice lines of
|
||||
// Finalized invoices on non-cancelled POs, dated by Invoice.FinalizedAt. 'To' is inclusive (whole day).
|
||||
// Payment status/method is deliberately ignored: a sale is recognised when invoiced.
|
||||
private IQueryable<SoldLine> SoldLines(DashboardFilter f)
|
||||
{
|
||||
var start = f.From.Date;
|
||||
var end = f.To.Date.AddDays(1);
|
||||
var q =
|
||||
from li in db.ShopPOItems.AsNoTracking()
|
||||
join inv in db.Invoices.AsNoTracking() on li.FK_ShopPOId equals inv.FK_ShopPOId
|
||||
where inv.Status == InvoiceStatus.Finalized
|
||||
&& li.ShopPO.Status != ShopPOStatus.Cancelled
|
||||
&& inv.FinalizedAt >= start && inv.FinalizedAt < end
|
||||
from li in db.InvoiceItems.AsNoTracking()
|
||||
where li.IsActive && !li.IsRemoved
|
||||
&& li.Invoice.Status == InvoiceStatus.Finalized
|
||||
&& li.Invoice.ShopPO.Status != ShopPOStatus.Cancelled
|
||||
&& li.Invoice.FinalizedAt >= start && li.Invoice.FinalizedAt < end
|
||||
select new SoldLine
|
||||
{
|
||||
Date = inv.FinalizedAt!.Value.Date,
|
||||
PoId = li.FK_ShopPOId,
|
||||
Value = li.QuantityFulfilled * li.UnitPrice,
|
||||
Cost = li.Item.CostPrice == null ? null : li.QuantityFulfilled * li.Item.CostPrice,
|
||||
Date = li.Invoice.FinalizedAt!.Value.Date,
|
||||
PoId = li.Invoice.FK_ShopPOId,
|
||||
Value = li.Quantity * li.UnitPrice,
|
||||
Cost = li.Item.CostPrice == null ? null : li.Quantity * li.Item.CostPrice,
|
||||
CategoryId = li.Item.FK_CategoryId,
|
||||
CategoryName = li.Item.Category.Name,
|
||||
RegionId = li.ShopPO.Store.Route.FK_RegionId,
|
||||
RegionName = li.ShopPO.Store.Route.Region.Name,
|
||||
RepId = li.ShopPO.RaisedById,
|
||||
RegionId = li.Invoice.ShopPO.Store.Route.FK_RegionId,
|
||||
RegionName = li.Invoice.ShopPO.Store.Route.Region.Name,
|
||||
RepId = li.Invoice.ShopPO.RaisedById,
|
||||
};
|
||||
if (f.RegionId.HasValue) q = q.Where(l => l.RegionId == f.RegionId.Value);
|
||||
if (f.CategoryId.HasValue) q = q.Where(l => l.CategoryId == f.CategoryId.Value);
|
||||
@@ -52,8 +53,10 @@ public class DashboardReportRepository(AppDBContext db) : IDashboardReportReposi
|
||||
{
|
||||
var start = f.From.Date;
|
||||
var end = f.To.Date.AddDays(1);
|
||||
// Only credit-deduction returns reduce sales; an item exchange is replaced like-for-like (net zero).
|
||||
var q = db.SaleReturnItems.AsNoTracking()
|
||||
.Where(r => r.SaleReturn.CreatedAt >= start && r.SaleReturn.CreatedAt < end);
|
||||
.Where(r => r.SaleReturn.CreatedAt >= start && r.SaleReturn.CreatedAt < end
|
||||
&& r.SaleReturn.Mode == ReturnMode.CreditDeduction);
|
||||
if (f.RegionId.HasValue) q = q.Where(r => r.SaleReturn.ShopPO.Store.Route.FK_RegionId == f.RegionId.Value);
|
||||
if (f.CategoryId.HasValue) q = q.Where(r => r.Item.FK_CategoryId == f.CategoryId.Value);
|
||||
return q;
|
||||
@@ -68,7 +71,59 @@ public class DashboardReportRepository(AppDBContext db) : IDashboardReportReposi
|
||||
var costedSales = await costed.SumAsync(l => (decimal?)l.Value) ?? 0m;
|
||||
var cost = await costed.SumAsync(l => l.Cost) ?? 0m;
|
||||
var returns = await ReturnLines(f).SumAsync(r => (decimal?)(r.Quantity * r.UnitPrice)) ?? 0m;
|
||||
return new SalesTotals(gross, returns, orders, costedSales, cost);
|
||||
return new SalesTotals(gross, returns, await GetDiscountsAsync(f), orders, costedSales, cost);
|
||||
}
|
||||
|
||||
// Discount carried on the invoice (Invoice.DiscountAmount) for invoices finalized in the range, same dating as sales.
|
||||
// A discount belongs to the whole invoice, so it can't be split by category: with a category filter it is 0.
|
||||
private async Task<decimal> GetDiscountsAsync(DashboardFilter f)
|
||||
{
|
||||
if (f.CategoryId.HasValue) return 0m;
|
||||
var start = f.From.Date;
|
||||
var end = f.To.Date.AddDays(1);
|
||||
var q = db.Invoices.AsNoTracking()
|
||||
.Where(i => i.IsActive && i.Status == InvoiceStatus.Finalized && i.ShopPO.Status != ShopPOStatus.Cancelled
|
||||
&& i.FinalizedAt >= start && i.FinalizedAt < end);
|
||||
if (f.RegionId.HasValue) q = q.Where(i => i.ShopPO.Store.Route.FK_RegionId == f.RegionId.Value);
|
||||
return await q.SumAsync(i => (decimal?)i.DiscountAmount) ?? 0m;
|
||||
}
|
||||
|
||||
// Cash view, independent of Net Sales. Credit-method payments are store-credit usage, so they stay
|
||||
// receivable (not collected). Outstanding/Overdue are balances as of the end of the range;
|
||||
// overdue = unpaid balance on invoices finalized more than OverdueDays before that date.
|
||||
private const int OverdueDays = 30;
|
||||
|
||||
public async Task<ReceivablesTotals> GetReceivablesAsync(DashboardFilter f)
|
||||
{
|
||||
var start = f.From.Date;
|
||||
var end = f.To.Date.AddDays(1);
|
||||
|
||||
var pays = db.Payments.AsNoTracking().Where(p => p.IsActive && p.Method != PaymentMethod.Credit);
|
||||
var invs = db.Invoices.AsNoTracking().Where(i => i.IsActive && i.Status == InvoiceStatus.Finalized
|
||||
&& i.ShopPO.Status != ShopPOStatus.Cancelled && i.FinalizedAt < end);
|
||||
if (f.RegionId.HasValue)
|
||||
{
|
||||
pays = pays.Where(p => p.Invoice.ShopPO.Store.Route.FK_RegionId == f.RegionId.Value);
|
||||
invs = invs.Where(i => i.ShopPO.Store.Route.FK_RegionId == f.RegionId.Value);
|
||||
}
|
||||
|
||||
var collected = await pays.Where(p => p.CollectedAt >= start && p.CollectedAt < end)
|
||||
.SumAsync(p => (decimal?)p.Amount) ?? 0m;
|
||||
|
||||
var rows = await invs.Select(i => new
|
||||
{
|
||||
i.FinalizedAt,
|
||||
i.TotalAmount,
|
||||
Paid = i.Payments.Where(p => p.IsActive && p.Method != PaymentMethod.Credit && p.CollectedAt < end)
|
||||
.Sum(p => (decimal?)p.Amount) ?? 0m,
|
||||
}).ToListAsync();
|
||||
|
||||
var balances = rows
|
||||
.Select(r => (Due: r.FinalizedAt!.Value.AddDays(OverdueDays), Balance: Math.Max(0m, r.TotalAmount - r.Paid)))
|
||||
.ToList();
|
||||
return new ReceivablesTotals(collected,
|
||||
balances.Sum(b => b.Balance),
|
||||
balances.Where(b => b.Due < end).Sum(b => b.Balance));
|
||||
}
|
||||
|
||||
public async Task<List<PeriodAmount>> GetDailySalesAsync(DashboardFilter f) =>
|
||||
|
||||
@@ -3,7 +3,7 @@ namespace Distribution.Features.DashboardReport;
|
||||
public class DashboardReportService(IDashboardReportRepository repo)
|
||||
{
|
||||
public const int MaxRangeDays = 366;
|
||||
private const int DailyGranularityMaxDays = 62;
|
||||
private const int DailyGranularityMaxDays = 92; // covers a "last 3 months" daily trend
|
||||
|
||||
public static DashboardFilter BuildFilter(DateTime? from, DateTime? to, Guid? regionId, Guid? categoryId, Guid? warehouseId)
|
||||
{
|
||||
@@ -25,14 +25,18 @@ public class DashboardReportService(IDashboardReportRepository repo)
|
||||
|
||||
private static SalesKpis ToKpis(SalesTotals t)
|
||||
{
|
||||
var net = t.GrossSales - t.ReturnValue;
|
||||
var net = t.GrossSales - t.ReturnValue - t.Discounts;
|
||||
var gp = t.CostedSales - t.CostOfSales;
|
||||
return new SalesKpis(
|
||||
t.GrossSales, t.ReturnValue, net, t.Orders,
|
||||
t.GrossSales, t.ReturnValue, t.Discounts, net, t.Orders,
|
||||
t.Orders > 0 ? Math.Round(t.GrossSales / t.Orders, 2) : 0m,
|
||||
gp, t.CostedSales > 0 ? Math.Round(gp / t.CostedSales * 100, 2) : null);
|
||||
}
|
||||
|
||||
private static CashKpis ToCash(ReceivablesTotals r, decimal grossInvoiced) =>
|
||||
new(r.Collected, r.Outstanding, r.Overdue,
|
||||
grossInvoiced > 0 ? Math.Round(r.Collected / grossInvoiced * 100, 2) : null);
|
||||
|
||||
public async Task<SalesSummaryResponse> GetSalesSummaryAsync(DashboardFilter f)
|
||||
{
|
||||
var days = (f.To - f.From).Days + 1;
|
||||
@@ -41,6 +45,8 @@ public class DashboardReportService(IDashboardReportRepository repo)
|
||||
|
||||
var current = await repo.GetSalesTotalsAsync(f);
|
||||
var previous = await repo.GetSalesTotalsAsync(prev);
|
||||
var currentCash = await repo.GetReceivablesAsync(f);
|
||||
var previousCash = await repo.GetReceivablesAsync(prev);
|
||||
var sales = await repo.GetDailySalesAsync(f);
|
||||
var returns = await repo.GetDailyReturnsAsync(f);
|
||||
|
||||
@@ -55,6 +61,9 @@ public class DashboardReportService(IDashboardReportRepository repo)
|
||||
return new SalesSummaryResponse(
|
||||
f.From, f.To, daily ? "day" : "month",
|
||||
new SalesKpiComparison(ToKpis(current), ToKpis(previous), prev.From, prev.To),
|
||||
new CashKpiComparison(
|
||||
ToCash(currentCash, current.GrossSales - current.Discounts),
|
||||
ToCash(previousCash, previous.GrossSales - previous.Discounts)),
|
||||
trend,
|
||||
(await repo.GetSalesByCategoryAsync(f)).Select(Map).ToList(),
|
||||
(await repo.GetSalesByRegionAsync(f)).Select(Map).ToList(),
|
||||
|
||||
@@ -5,6 +5,7 @@ public record GroupAggregate(Guid? Key, string Name, decimal Amount);
|
||||
public interface IDashboardReportRepository
|
||||
{
|
||||
Task<SalesTotals> GetSalesTotalsAsync(DashboardFilter f);
|
||||
Task<ReceivablesTotals> GetReceivablesAsync(DashboardFilter f);
|
||||
Task<List<PeriodAmount>> GetDailySalesAsync(DashboardFilter f);
|
||||
Task<List<PeriodAmount>> GetDailyReturnsAsync(DashboardFilter f);
|
||||
Task<List<GroupAggregate>> GetSalesByCategoryAsync(DashboardFilter f);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Distribution.Features.LoadingEvent;
|
||||
using Distribution.Features.Notifications;
|
||||
using Distribution.Features.Ride;
|
||||
using Distribution.Features.ShopPO;
|
||||
using Distribution.Features.Stock;
|
||||
@@ -13,8 +14,18 @@ public class DeliveryTaskService(
|
||||
IShopPORepository shopPORepo,
|
||||
ILoadingSheetRepository loadingSheetRepo,
|
||||
StockMover mover,
|
||||
INotificationService notifications,
|
||||
IUnitOfWork uow)
|
||||
{
|
||||
private const string DeliveriesLink = "/operations/deliveries";
|
||||
|
||||
// Route owner + managers whose scope covers the store being delivered to.
|
||||
private static List<RecipientRule> DeliveryRecipients(Guid storeId) =>
|
||||
[
|
||||
RecipientRule.RouteOwner(ScopeKind.Store, storeId),
|
||||
RecipientRule.PermissionInScope("delivery-task.manage", ScopeKind.Store, storeId),
|
||||
];
|
||||
|
||||
private static DeliveryTaskResponse ToResponse(DeliveryTask t) =>
|
||||
new(t.Id,
|
||||
t.FK_RideId,
|
||||
@@ -181,6 +192,13 @@ public class DeliveryTaskService(
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
if (!fullyDelivered && shopPO.Store is not null)
|
||||
await notifications.NotifyAsync(
|
||||
NotificationTypes.DeliveryTaskPartiallyDelivered,
|
||||
DeliveryRecipients(shopPO.FK_StoreId),
|
||||
new NotificationArgs(new { shopPO.Store.StoreName }, "DeliveryTask", task.Id, DeliveriesLink, userId),
|
||||
ct);
|
||||
|
||||
var saved = await repo.GetByIdAsync(taskId);
|
||||
return ToResponse(saved!);
|
||||
}
|
||||
@@ -204,6 +222,13 @@ public class DeliveryTaskService(
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
if (task.ShopPO?.Store is not null)
|
||||
await notifications.NotifyAsync(
|
||||
NotificationTypes.DeliveryTaskFailed,
|
||||
DeliveryRecipients(task.ShopPO.FK_StoreId),
|
||||
new NotificationArgs(new { task.ShopPO.Store.StoreName, Note = note }, "DeliveryTask", task.Id, DeliveriesLink),
|
||||
ct);
|
||||
|
||||
return ToResponse(task);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,12 @@ public interface IInvoiceRepository
|
||||
Task<List<Models.Invoice>> GetAllAsync(Guid? agentId, Guid? routeId, Guid? shopId, InvoiceStatus? status, PaymentStatus? paymentStatus, int page, int pageSize);
|
||||
Task<int> GetCountAsync(Guid? agentId, Guid? routeId, Guid? shopId, InvoiceStatus? status, PaymentStatus? paymentStatus);
|
||||
Task<List<Models.Invoice>> GetOutstandingAsync();
|
||||
Task<decimal> GetSchemeDiscountAsync(Guid shopPOId);
|
||||
Task AddAsync(Models.Invoice invoice);
|
||||
Task<Models.Item?> GetItemAsync(Guid itemId);
|
||||
Task AddItemsAsync(IEnumerable<InvoiceItem> items);
|
||||
Task AddVersionAsync(InvoiceVersion version);
|
||||
Task<int> GetLatestVersionNumberAsync(Guid invoiceId);
|
||||
Task<List<InvoiceVersion>> GetVersionsAsync(Guid invoiceId);
|
||||
void Update(Models.Invoice invoice);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,52 @@ public class InvoiceController(InvoiceService service, ICurrentUserService curre
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create([FromBody] CreateInvoiceRequest request, CancellationToken ct)
|
||||
{
|
||||
// TEMPORARY: currentUser.UserId is always unset due to the broken auth middleware
|
||||
// (testbug.md #28) — fall back to the explicitly supplied user until that's fixed.
|
||||
var userId = currentUser.UserId ?? request.CreatedByUserId;
|
||||
if (userId is null) return BadRequest(new { error = "CreatedByUserId is required while auth is unavailable" });
|
||||
try
|
||||
{
|
||||
var result = await service.CreateAsync(request, userId.Value, ct);
|
||||
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
||||
}
|
||||
catch (KeyNotFoundException) { return NotFound(); }
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/preview-totals")]
|
||||
public async Task<IActionResult> PreviewTotals(Guid id, [FromBody] PreviewInvoiceTotalsRequest request)
|
||||
{
|
||||
var result = await service.PreviewTotalsAsync(id, request);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/versions")]
|
||||
public async Task<IActionResult> GetVersions(Guid id)
|
||||
{
|
||||
var result = await service.GetVersionsAsync(id);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}/items")]
|
||||
public async Task<IActionResult> UpdateItems(Guid id, [FromBody] UpdateInvoiceItemsRequest request, CancellationToken ct)
|
||||
{
|
||||
// TEMPORARY: currentUser.UserId is always unset due to the broken auth middleware
|
||||
// (testbug.md #28) — fall back to the explicitly supplied user until that's fixed.
|
||||
var userId = currentUser.UserId ?? request.EditedByUserId;
|
||||
if (userId is null) return BadRequest(new { error = "EditedByUserId is required while auth is unavailable" });
|
||||
try
|
||||
{
|
||||
var result = await service.UpdateItemsAsync(id, request, userId.Value, ct);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (KeyNotFoundException) { return NotFound(); }
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/print")]
|
||||
public async Task<IActionResult> Print(Guid id, [FromBody] PrintInvoiceRequest? request, CancellationToken ct)
|
||||
{
|
||||
|
||||
@@ -1,15 +1,52 @@
|
||||
using Distribution.Models;
|
||||
using Distribution.Features.ShopPO;
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.Invoice;
|
||||
|
||||
public record CreateInvoiceRequest(Guid ShopPOId);
|
||||
public record InvoiceItemAdjustment(Guid ShopPOItemId, int AdjustedQuantity, string? Note = null);
|
||||
public record CreateInvoiceRequest(Guid ShopPOId, Guid? CreatedByUserId = null);
|
||||
public record InvoiceItemAdjustment(Guid InvoiceItemId, int AdjustedQuantity, bool IsRemoved = false, string? Note = null);
|
||||
public record FinalizeInvoiceRequest(List<InvoiceItemAdjustment>? Adjustments = null, Guid? FinalizedByUserId = null);
|
||||
public record PrintInvoiceRequest(Guid? PrintedByUserId = null);
|
||||
|
||||
// InvoiceItemId set = edit/remove/restore an existing line; ItemId set (InvoiceItemId null) = add a new line.
|
||||
public record InvoiceItemEdit(Guid? InvoiceItemId, Guid? ItemId, int Quantity, decimal? UnitPrice = null, bool IsRemoved = false);
|
||||
// DiscountSchemeIds: the percentage-discount scheme chosen in the editor (at most one); null = leave as is.
|
||||
public record UpdateInvoiceItemsRequest(List<InvoiceItemEdit> Items, Guid? EditedByUserId = null, List<Guid>? DiscountSchemeIds = null);
|
||||
|
||||
public record PreviewLine(Guid ItemId, int Quantity, decimal UnitPrice, bool IsFree = false, bool IsRemoved = false, Guid? SchemeId = null);
|
||||
public record PreviewInvoiceTotalsRequest(List<PreviewLine> Items, List<Guid>? DiscountSchemeIds = null);
|
||||
// Quantity = how many free units the scheme still entitles for the lines as typed.
|
||||
public record FreeEntitlement(Guid SchemeId, int Quantity);
|
||||
public record InvoiceTotalsPreview(decimal DiscountAmount, decimal TotalAmount, List<FreeEntitlement> FreeEntitlements);
|
||||
|
||||
public record InvoiceVersionItemResponse(Guid? ShopPOItemId, Guid ItemId, string ItemName, int Quantity, decimal UnitPrice, bool IsRemoved);
|
||||
|
||||
public record InvoiceVersionResponse(
|
||||
Guid Id,
|
||||
int VersionNumber,
|
||||
InvoiceVersionStage Stage,
|
||||
decimal TotalAmount,
|
||||
Guid CreatedBy,
|
||||
DateTime CreatedAt,
|
||||
List<InvoiceVersionItemResponse> Items);
|
||||
|
||||
public record InvoiceItemResponse(
|
||||
Guid Id,
|
||||
Guid? ShopPOItemId,
|
||||
Guid ItemId,
|
||||
string ItemName,
|
||||
int? OrderedQuantity,
|
||||
int Quantity,
|
||||
decimal UnitPrice,
|
||||
bool IsRemoved,
|
||||
bool IsFree = false,
|
||||
Guid? SchemeId = null,
|
||||
string? SchemeName = null);
|
||||
|
||||
public record InvoiceResponse(
|
||||
Guid Id,
|
||||
Guid ShopPOId,
|
||||
Guid StoreId,
|
||||
string StoreName,
|
||||
InvoiceStatus Status,
|
||||
PaymentStatus PaymentStatus,
|
||||
@@ -19,7 +56,11 @@ public record InvoiceResponse(
|
||||
DateTime? FinalizedAt,
|
||||
Guid? FinalizedBy,
|
||||
decimal TotalAmount,
|
||||
decimal DiscountAmount,
|
||||
decimal PaidAmount,
|
||||
bool IsActive,
|
||||
DateTime CreatedAt);
|
||||
DateTime CreatedAt,
|
||||
List<InvoiceItemResponse> Items,
|
||||
List<ShopPODiscountResponse>? Discounts = null);
|
||||
|
||||
public record OutstandingPaymentsResponse(int InvoiceCount, decimal OutstandingAmount);
|
||||
|
||||
@@ -10,6 +10,8 @@ public class InvoiceRepository(AppDBContext context) : IInvoiceRepository
|
||||
await context.Invoices
|
||||
.Include(i => i.ShopPO).ThenInclude(p => p.Store)
|
||||
.Include(i => i.ShopPO).ThenInclude(p => p.Items.Where(x => x.IsActive))
|
||||
.Include(i => i.Items.Where(x => x.IsActive)).ThenInclude(x => x.Item)
|
||||
.Include(i => i.Items.Where(x => x.IsActive)).ThenInclude(x => x.ShopPOItem)
|
||||
.Include(i => i.Payments.Where(p => p.IsActive))
|
||||
.FirstOrDefaultAsync(i => i.Id == id && i.IsActive);
|
||||
|
||||
@@ -21,6 +23,7 @@ public class InvoiceRepository(AppDBContext context) : IInvoiceRepository
|
||||
public async Task<List<Models.Invoice>> GetAllAsync(Guid? agentId, Guid? routeId, Guid? shopId, InvoiceStatus? status, PaymentStatus? paymentStatus, int page, int pageSize) =>
|
||||
await context.Invoices
|
||||
.Include(i => i.ShopPO).ThenInclude(p => p.Store)
|
||||
.Include(i => i.Payments.Where(p => p.IsActive))
|
||||
.Where(i => i.IsActive
|
||||
&& (agentId == null || i.ShopPO.RaisedById == agentId)
|
||||
&& (routeId == null || i.ShopPO.Store.FK_RouteId == routeId)
|
||||
@@ -48,9 +51,35 @@ public class InvoiceRepository(AppDBContext context) : IInvoiceRepository
|
||||
.Where(i => i.IsActive && i.PaymentStatus != PaymentStatus.Paid)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task<decimal> GetSchemeDiscountAsync(Guid shopPOId) =>
|
||||
await context.SchemeRedemptions
|
||||
.Where(r => r.FK_ShopPOId == shopPOId)
|
||||
.SumAsync(r => r.DiscountAmount) ?? 0m;
|
||||
|
||||
public async Task AddAsync(Models.Invoice invoice) =>
|
||||
await context.Invoices.AddAsync(invoice);
|
||||
|
||||
public async Task<Models.Item?> GetItemAsync(Guid itemId) =>
|
||||
await context.Items.FirstOrDefaultAsync(i => i.Id == itemId && i.IsActive);
|
||||
|
||||
public async Task AddItemsAsync(IEnumerable<InvoiceItem> items) =>
|
||||
await context.InvoiceItems.AddRangeAsync(items);
|
||||
|
||||
public async Task AddVersionAsync(InvoiceVersion version) =>
|
||||
await context.InvoiceVersions.AddAsync(version);
|
||||
|
||||
public async Task<int> GetLatestVersionNumberAsync(Guid invoiceId) =>
|
||||
await context.InvoiceVersions
|
||||
.Where(v => v.FK_InvoiceId == invoiceId)
|
||||
.MaxAsync(v => (int?)v.VersionNumber) ?? 0;
|
||||
|
||||
public async Task<List<InvoiceVersion>> GetVersionsAsync(Guid invoiceId) =>
|
||||
await context.InvoiceVersions
|
||||
.Include(v => v.Items).ThenInclude(i => i.Item)
|
||||
.Where(v => v.FK_InvoiceId == invoiceId && v.IsActive)
|
||||
.OrderBy(v => v.VersionNumber)
|
||||
.ToListAsync();
|
||||
|
||||
public void Update(Models.Invoice invoice) =>
|
||||
context.Invoices.Update(invoice);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Distribution.Features.Stock;
|
||||
using Distribution.Features.Scheme;
|
||||
using Distribution.Features.ShopPO;
|
||||
using Distribution.Features.Stock;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Infra.UoW;
|
||||
using Distribution.Models;
|
||||
@@ -7,21 +9,126 @@ namespace Distribution.Features.Invoice;
|
||||
|
||||
public class InvoiceService(
|
||||
IInvoiceRepository repo,
|
||||
IShopPORepository shopPoRepo,
|
||||
SchemeService schemeService,
|
||||
IUnitOfWork uow,
|
||||
ICurrentUserService currentUser)
|
||||
{
|
||||
private static InvoiceResponse ToResponse(Models.Invoice i) =>
|
||||
new(i.Id, i.FK_ShopPOId,
|
||||
i.ShopPO?.FK_StoreId ?? Guid.Empty,
|
||||
i.ShopPO?.Store?.StoreName ?? string.Empty,
|
||||
i.Status, i.PaymentStatus, i.CreatedBy,
|
||||
i.PrintedAt, i.PrintedBy,
|
||||
i.FinalizedAt, i.FinalizedBy,
|
||||
i.TotalAmount, i.IsActive, i.CreatedAt);
|
||||
i.TotalAmount, i.DiscountAmount,
|
||||
i.Payments?.Where(p => p.IsActive).Sum(p => p.Amount) ?? 0m,
|
||||
i.IsActive, i.CreatedAt,
|
||||
i.Items.Where(x => x.IsActive).Select(x =>
|
||||
new InvoiceItemResponse(x.Id, x.FK_ShopPOItemId, x.FK_ItemId,
|
||||
x.Item?.Name ?? string.Empty, x.ShopPOItem?.Quantity,
|
||||
x.Quantity, x.UnitPrice, x.IsRemoved, x.IsFree, x.FK_SchemeId, x.SchemeName)).ToList());
|
||||
|
||||
private static decimal BillableTotal(IEnumerable<InvoiceItem> items) =>
|
||||
items.Where(i => i.IsActive && !i.IsRemoved).Sum(i => i.Quantity * i.UnitPrice);
|
||||
|
||||
// The lines that count toward a percentage discount: active, not struck out. Free issues carry the flag so the
|
||||
// scheme rules ignore them.
|
||||
private static List<ShopPOItem> DiscountableLines(IEnumerable<InvoiceItem> items) =>
|
||||
items.Where(i => i.IsActive && !i.IsRemoved)
|
||||
.Select(i => new ShopPOItem { FK_ItemId = i.FK_ItemId, Quantity = i.Quantity, UnitPrice = i.UnitPrice, IsFree = i.IsFree })
|
||||
.ToList();
|
||||
|
||||
// TotalAmount = billable lines - scheme discount. The discount is re-evaluated against the current lines
|
||||
// (so adding/removing/re-pricing items moves it, and a slab level can be gained or lost) whenever lines change
|
||||
// and at print/finalize. The redemption amount is updated with it.
|
||||
private async Task ApplyTotalsAsync(Models.Invoice invoice, IEnumerable<InvoiceItem> items)
|
||||
{
|
||||
var lines = items.ToList();
|
||||
// Lowering/removing the paid item that earned a free issue lowers the free quantity with it.
|
||||
await schemeService.ReconcileInvoiceFreeLinesAsync(invoice.FK_ShopPOId, lines);
|
||||
var billable = BillableTotal(lines);
|
||||
var discount = await schemeService.CalculateDiscountAsync(invoice.FK_ShopPOId, DiscountableLines(lines), persist: true);
|
||||
invoice.DiscountAmount = Math.Min(discount, billable);
|
||||
invoice.TotalAmount = billable - invoice.DiscountAmount;
|
||||
}
|
||||
|
||||
// Live preview for the draft editor: what the discount and total would be for these unsaved lines. Changes nothing.
|
||||
public async Task<InvoiceTotalsPreview?> PreviewTotalsAsync(Guid id, PreviewInvoiceTotalsRequest request)
|
||||
{
|
||||
var invoice = await repo.GetByIdAsync(id);
|
||||
if (invoice is null) return null;
|
||||
|
||||
var lines = request.Items.Where(l => !l.IsRemoved)
|
||||
.Select(l => new ShopPOItem { FK_ItemId = l.ItemId, Quantity = l.Quantity, UnitPrice = l.UnitPrice, IsFree = l.IsFree, FK_SchemeId = l.SchemeId })
|
||||
.ToList();
|
||||
var billable = lines.Sum(l => l.Quantity * l.UnitPrice);
|
||||
var chosen = request.DiscountSchemeIds is null
|
||||
? await schemeService.CalculateDiscountAsync(invoice.FK_ShopPOId, lines, persist: false)
|
||||
: await schemeService.CalculateDiscountForSchemesAsync(request.DiscountSchemeIds, lines);
|
||||
var discount = Math.Min(chosen, billable);
|
||||
|
||||
var schemeIds = lines.Where(l => l.IsFree && l.FK_SchemeId.HasValue).Select(l => l.FK_SchemeId!.Value).ToList();
|
||||
var entitlements = (await schemeService.GetFreeEntitlementsAsync(schemeIds, lines))
|
||||
.Select(e => new FreeEntitlement(e.Key, e.Value)).ToList();
|
||||
return new InvoiceTotalsPreview(discount, billable - discount, entitlements);
|
||||
}
|
||||
|
||||
// Immutable copy of the invoice lines as they stand right now.
|
||||
public static InvoiceVersion BuildVersion(Models.Invoice invoice, IEnumerable<InvoiceItem> items, int versionNumber, InvoiceVersionStage stage, Guid userId) =>
|
||||
new()
|
||||
{
|
||||
FK_InvoiceId = invoice.Id,
|
||||
VersionNumber = versionNumber,
|
||||
Stage = stage,
|
||||
TotalAmount = invoice.TotalAmount,
|
||||
CreatedBy = userId,
|
||||
Items = items.Select(i => new InvoiceVersionItem
|
||||
{
|
||||
FK_ShopPOItemId = i.FK_ShopPOItemId,
|
||||
FK_ItemId = i.FK_ItemId,
|
||||
Quantity = i.Quantity,
|
||||
UnitPrice = i.UnitPrice,
|
||||
IsRemoved = i.IsRemoved
|
||||
}).ToList()
|
||||
};
|
||||
|
||||
public async Task<List<InvoiceVersionResponse>?> GetVersionsAsync(Guid id)
|
||||
{
|
||||
if (await repo.GetByIdAsync(id) is null) return null;
|
||||
var versions = await repo.GetVersionsAsync(id);
|
||||
return versions.Select(v => new InvoiceVersionResponse(
|
||||
v.Id, v.VersionNumber, v.Stage, v.TotalAmount, v.CreatedBy, v.CreatedAt,
|
||||
v.Items.Select(i => new InvoiceVersionItemResponse(
|
||||
i.FK_ShopPOItemId, i.FK_ItemId, i.Item?.Name ?? string.Empty, i.Quantity, i.UnitPrice, i.IsRemoved)).ToList())).ToList();
|
||||
}
|
||||
|
||||
private async Task AddVersionAsync(Models.Invoice invoice, InvoiceVersionStage stage, Guid userId, IEnumerable<InvoiceItem>? addedLines = null)
|
||||
{
|
||||
var next = await repo.GetLatestVersionNumberAsync(invoice.Id) + 1;
|
||||
// Added lines may or may not be in invoice.Items yet, so merge by reference.
|
||||
var lines = invoice.Items.Concat(addedLines ?? []).Where(i => i.IsActive).Distinct();
|
||||
await repo.AddVersionAsync(BuildVersion(invoice, lines, next, stage, userId));
|
||||
}
|
||||
|
||||
public static List<InvoiceItem> BuildItemsFromShopPO(Models.ShopPO po, Guid invoiceId) =>
|
||||
po.Items.Where(i => i.IsActive).Select(i => new InvoiceItem
|
||||
{
|
||||
FK_InvoiceId = invoiceId,
|
||||
FK_ShopPOItemId = i.Id,
|
||||
FK_ItemId = i.FK_ItemId,
|
||||
Quantity = i.Quantity,
|
||||
UnitPrice = i.UnitPrice,
|
||||
IsFree = i.IsFree,
|
||||
FK_SchemeId = i.FK_SchemeId,
|
||||
SchemeName = i.SchemeName
|
||||
}).ToList();
|
||||
|
||||
public async Task<InvoiceResponse?> GetByIdAsync(Guid id)
|
||||
{
|
||||
var invoice = await repo.GetByIdAsync(id);
|
||||
return invoice is null ? null : ToResponse(invoice);
|
||||
if (invoice is null) return null;
|
||||
return ToResponse(invoice) with { Discounts = await schemeService.GetDiscountsForShopPOAsync(invoice.FK_ShopPOId) };
|
||||
}
|
||||
|
||||
public async Task<PagedResponse<InvoiceResponse>> GetAllAsync(Guid? agentId, Guid? routeId, Guid? shopId, InvoiceStatus? status, PaymentStatus? paymentStatus, int page, int pageSize)
|
||||
@@ -39,6 +146,131 @@ public class InvoiceService(
|
||||
return new OutstandingPaymentsResponse(count, outstanding);
|
||||
}
|
||||
|
||||
public async Task<InvoiceResponse> CreateAsync(CreateInvoiceRequest request, Guid createdBy, CancellationToken ct = default)
|
||||
{
|
||||
var po = await shopPoRepo.GetByIdAsync(request.ShopPOId)
|
||||
?? throw new KeyNotFoundException("Shop PO not found");
|
||||
|
||||
if (po.ConfirmedAt is null)
|
||||
throw new InvalidOperationException("Shop PO must be confirmed before an invoice can be created");
|
||||
if (po.Status == ShopPOStatus.Cancelled)
|
||||
throw new InvalidOperationException("Cannot create an invoice for a cancelled Shop PO");
|
||||
if (po.InvoiceId is not null)
|
||||
throw new InvalidOperationException("An invoice already exists for this Shop PO");
|
||||
|
||||
var invoice = new Models.Invoice
|
||||
{
|
||||
FK_ShopPOId = po.Id,
|
||||
Status = InvoiceStatus.Draft,
|
||||
PaymentStatus = PaymentStatus.Pending,
|
||||
CreatedBy = createdBy
|
||||
};
|
||||
var lines = BuildItemsFromShopPO(po, invoice.Id);
|
||||
await ApplyTotalsAsync(invoice, lines);
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
await repo.AddAsync(invoice);
|
||||
await repo.AddItemsAsync(lines);
|
||||
await repo.AddVersionAsync(BuildVersion(invoice, lines, 1, InvoiceVersionStage.Created, createdBy));
|
||||
po.InvoiceId = invoice.Id;
|
||||
shopPoRepo.Update(po);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
var saved = await repo.GetByIdAsync(invoice.Id);
|
||||
return ToResponse(saved!);
|
||||
}
|
||||
|
||||
public async Task<InvoiceResponse> UpdateItemsAsync(Guid id, UpdateInvoiceItemsRequest request, Guid editedBy, CancellationToken ct = default)
|
||||
{
|
||||
var invoice = await repo.GetByIdAsync(id)
|
||||
?? throw new KeyNotFoundException("Invoice not found");
|
||||
|
||||
if (invoice.Status != InvoiceStatus.Draft)
|
||||
throw new InvalidOperationException($"Items can only be edited while the invoice is Draft (current: {invoice.Status})");
|
||||
|
||||
var newLines = new List<InvoiceItem>();
|
||||
|
||||
foreach (var edit in request.Items)
|
||||
{
|
||||
if (edit.UnitPrice is < 0)
|
||||
throw new InvalidOperationException("Unit price cannot be negative");
|
||||
|
||||
if (edit.InvoiceItemId is Guid lineId)
|
||||
{
|
||||
// Existing line: edit quantity/price, or strike it (IsRemoved) / restore it.
|
||||
var item = invoice.Items.FirstOrDefault(i => i.Id == lineId && i.IsActive)
|
||||
?? throw new InvalidOperationException($"Invoice item {lineId} not found on this invoice");
|
||||
|
||||
item.IsRemoved = edit.IsRemoved;
|
||||
if (!edit.IsRemoved)
|
||||
{
|
||||
ValidateQuantity(item, edit.Quantity);
|
||||
item.Quantity = edit.Quantity;
|
||||
}
|
||||
if (edit.UnitPrice is decimal price)
|
||||
{
|
||||
if (item.IsFree && price != 0m)
|
||||
throw new InvalidOperationException("A free-issue line must stay at zero price");
|
||||
item.UnitPrice = price;
|
||||
}
|
||||
}
|
||||
else if (edit.ItemId is Guid itemId)
|
||||
{
|
||||
// New line for an item that was not on the PO.
|
||||
if (edit.IsRemoved) continue;
|
||||
if (edit.Quantity <= 0)
|
||||
throw new InvalidOperationException("Quantity for an added item must be greater than zero");
|
||||
if (edit.UnitPrice is null)
|
||||
throw new InvalidOperationException("Unit price is required for an added item");
|
||||
if (invoice.Items.Concat(newLines).Any(i => i.IsActive && !i.IsRemoved && i.FK_ItemId == itemId))
|
||||
throw new InvalidOperationException("This item is already on the invoice — edit its existing line instead");
|
||||
|
||||
var catalogItem = await repo.GetItemAsync(itemId)
|
||||
?? throw new InvalidOperationException($"Item {itemId} not found");
|
||||
|
||||
newLines.Add(new InvoiceItem
|
||||
{
|
||||
FK_InvoiceId = invoice.Id,
|
||||
FK_ItemId = catalogItem.Id,
|
||||
Item = catalogItem,
|
||||
Quantity = edit.Quantity,
|
||||
UnitPrice = edit.UnitPrice.Value
|
||||
});
|
||||
}
|
||||
else
|
||||
throw new InvalidOperationException("Each edit needs an invoiceItemId (existing line) or an itemId (new line)");
|
||||
}
|
||||
|
||||
var allLines = invoice.Items.Concat(newLines).ToList();
|
||||
await schemeService.ApplyInvoiceDiscountSelectionAsync(
|
||||
invoice.FK_ShopPOId, invoice.ShopPO?.FK_StoreId ?? Guid.Empty, allLines, request.DiscountSchemeIds);
|
||||
await ApplyTotalsAsync(invoice, allLines);
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
repo.Update(invoice);
|
||||
if (newLines.Count > 0) await repo.AddItemsAsync(newLines);
|
||||
await AddVersionAsync(invoice, InvoiceVersionStage.DraftEdited, editedBy, newLines);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return (await GetByIdAsync(id))!;
|
||||
}
|
||||
|
||||
private static void ValidateQuantity(InvoiceItem item, int quantity)
|
||||
{
|
||||
var ordered = item.ShopPOItem?.Quantity ?? int.MaxValue;
|
||||
if (quantity < 0 || quantity > ordered)
|
||||
throw new InvalidOperationException(
|
||||
$"Quantity for item {item.Id} must be between 0 and {ordered}");
|
||||
}
|
||||
|
||||
public async Task<InvoiceResponse> PrintAsync(Guid id, Guid printedBy, CancellationToken ct = default)
|
||||
{
|
||||
var invoice = await repo.GetByIdAsync(id)
|
||||
@@ -47,12 +279,21 @@ public class InvoiceService(
|
||||
if (invoice.Status != InvoiceStatus.Draft)
|
||||
throw new InvalidOperationException($"Cannot print an invoice in {invoice.Status} status");
|
||||
|
||||
// Printing fixes the amount the customer sees, so pick up any scheme discount applied since the last edit.
|
||||
var lines = invoice.Items.Where(i => i.IsActive).ToList();
|
||||
if (lines.Count > 0) await ApplyTotalsAsync(invoice, lines);
|
||||
|
||||
invoice.Status = InvoiceStatus.Printed;
|
||||
invoice.PrintedAt = DateTime.Now;
|
||||
invoice.PrintedBy = printedBy;
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try { repo.Update(invoice); await uow.CommitAsync(ct); }
|
||||
try
|
||||
{
|
||||
repo.Update(invoice);
|
||||
await AddVersionAsync(invoice, InvoiceVersionStage.Printed, printedBy);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return ToResponse(invoice);
|
||||
@@ -66,31 +307,37 @@ public class InvoiceService(
|
||||
if (invoice.Status != InvoiceStatus.Printed)
|
||||
throw new InvalidOperationException($"Cannot finalize an invoice in {invoice.Status} status");
|
||||
|
||||
var items = invoice.Items.Where(i => i.IsActive).ToList();
|
||||
var adjustments = request.Adjustments ?? [];
|
||||
if (adjustments.Count > 0)
|
||||
|
||||
// Agent's handwritten edits from the printed copy are written onto the invoice lines.
|
||||
foreach (var adjustment in adjustments)
|
||||
{
|
||||
var poItems = invoice.ShopPO.Items.Where(i => i.IsActive).ToList();
|
||||
decimal total = 0;
|
||||
foreach (var item in poItems)
|
||||
var item = items.FirstOrDefault(i => i.Id == adjustment.InvoiceItemId)
|
||||
?? throw new InvalidOperationException($"Invoice item {adjustment.InvoiceItemId} is not on this invoice");
|
||||
|
||||
item.IsRemoved = adjustment.IsRemoved;
|
||||
if (!adjustment.IsRemoved)
|
||||
{
|
||||
var adjustment = adjustments.FirstOrDefault(a => a.ShopPOItemId == item.Id);
|
||||
var billedQuantity = adjustment is not null ? adjustment.AdjustedQuantity : item.Quantity;
|
||||
|
||||
if (billedQuantity < 0 || billedQuantity > item.Quantity)
|
||||
throw new InvalidOperationException(
|
||||
$"Adjusted quantity for item {item.Id} must be between 0 and {item.Quantity}");
|
||||
|
||||
total += billedQuantity * item.UnitPrice;
|
||||
ValidateQuantity(item, adjustment.AdjustedQuantity);
|
||||
item.Quantity = adjustment.AdjustedQuantity;
|
||||
}
|
||||
invoice.TotalAmount = total;
|
||||
}
|
||||
|
||||
if (items.Count > 0)
|
||||
await ApplyTotalsAsync(invoice, items);
|
||||
|
||||
invoice.Status = InvoiceStatus.Finalized;
|
||||
invoice.FinalizedAt = DateTime.Now;
|
||||
invoice.FinalizedBy = finalizedBy;
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try { repo.Update(invoice); await uow.CommitAsync(ct); }
|
||||
try
|
||||
{
|
||||
repo.Update(invoice);
|
||||
await AddVersionAsync(invoice, InvoiceVersionStage.Finalized, finalizedBy);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return ToResponse(invoice);
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
namespace Distribution.Features.Notifications;
|
||||
|
||||
public interface INotificationRepository
|
||||
{
|
||||
// ── Inbox ────────────────────────────────────────────────────────────
|
||||
Task AddAsync(Models.Notification notification);
|
||||
/// <summary>Stops tracking a notification (and its recipients) whose save failed.</summary>
|
||||
void Discard(Models.Notification notification);
|
||||
|
||||
Task<int> GetInboxCountAsync(Guid userId, bool unreadOnly);
|
||||
Task<List<Models.NotificationRecipient>> GetInboxAsync(Guid userId, bool unreadOnly, int page, int pageSize);
|
||||
Task<int> GetUnreadCountAsync(Guid userId);
|
||||
Task<Models.NotificationRecipient?> GetRecipientAsync(Guid userId, Guid recipientId);
|
||||
Task<List<Models.NotificationRecipient>> GetUnreadRecipientsAsync(Guid userId);
|
||||
void RemoveRecipient(Models.NotificationRecipient recipient);
|
||||
|
||||
// ── Recipient resolution ─────────────────────────────────────────────
|
||||
Task<List<Guid>> FilterActiveUserIdsAsync(IEnumerable<Guid> userIds);
|
||||
Task<List<Guid>> GetUserIdsByPermissionAsync(string permissionCode);
|
||||
Task<List<Guid>> GetUserIdsByScopeAsync(ScopeKind kind, Guid id);
|
||||
Task<Guid?> GetReportsToUserIdAsync(Guid userId);
|
||||
Task<Guid?> GetRouteOwnerUserIdAsync(ScopeKind kind, Guid id);
|
||||
|
||||
// ── Retention ────────────────────────────────────────────────────────
|
||||
Task<List<Models.NotificationRecipient>> GetExpiredReadRecipientsAsync(DateTime cutoff, int take);
|
||||
Task<List<Models.Notification>> GetOrphanNotificationsAsync(int take);
|
||||
void RemoveRecipients(IEnumerable<Models.NotificationRecipient> recipients);
|
||||
void RemoveNotifications(IEnumerable<Models.Notification> notifications);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using Distribution.Infra.UoW;
|
||||
|
||||
namespace Distribution.Features.Notifications;
|
||||
|
||||
/// <summary>
|
||||
/// Daily retention purge: deletes read inbox rows older than <c>Notifications:RetentionDays</c>
|
||||
/// (default 90), then any notification left with no recipients.
|
||||
/// </summary>
|
||||
public class NotificationCleanupJob(
|
||||
IServiceScopeFactory scopes,
|
||||
IConfiguration config,
|
||||
ILogger<NotificationCleanupJob> logger) : BackgroundService
|
||||
{
|
||||
private const int BatchSize = 500;
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
// Let the app finish starting before the first run.
|
||||
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
try { await RunOnceAsync(stoppingToken); }
|
||||
catch (OperationCanceledException) { break; }
|
||||
catch (Exception ex) { logger.LogError(ex, "Notification cleanup failed."); }
|
||||
|
||||
await Task.Delay(TimeSpan.FromHours(24), stoppingToken);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RunOnceAsync(CancellationToken ct)
|
||||
{
|
||||
var retentionDays = config.GetValue("Notifications:RetentionDays", 90);
|
||||
var cutoff = DateTime.Now.AddDays(-retentionDays);
|
||||
|
||||
using var scope = scopes.CreateScope();
|
||||
var repo = scope.ServiceProvider.GetRequiredService<INotificationRepository>();
|
||||
var uow = scope.ServiceProvider.GetRequiredService<IUnitOfWork>();
|
||||
|
||||
var removed = 0;
|
||||
while (true)
|
||||
{
|
||||
var expired = await repo.GetExpiredReadRecipientsAsync(cutoff, BatchSize);
|
||||
if (expired.Count == 0) break;
|
||||
|
||||
repo.RemoveRecipients(expired);
|
||||
await uow.CommitAsync(ct);
|
||||
removed += expired.Count;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
var orphans = await repo.GetOrphanNotificationsAsync(BatchSize);
|
||||
if (orphans.Count == 0) break;
|
||||
|
||||
repo.RemoveNotifications(orphans);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
|
||||
if (removed > 0)
|
||||
logger.LogInformation("Notification cleanup removed {Count} read inbox rows older than {Days} days.", removed, retentionDays);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using Distribution.Infra.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Notifications;
|
||||
|
||||
/// <summary>Every endpoint acts on the signed-in user's own notifications only.</summary>
|
||||
[ApiController]
|
||||
[Route("api/notifications")]
|
||||
public class NotificationController(NotificationService service, ICurrentUserService currentUser) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetInbox(
|
||||
[FromQuery] bool unreadOnly = false, [FromQuery] int page = 1, [FromQuery] int pageSize = 20)
|
||||
{
|
||||
if (currentUser.UserId is not { } userId) return Unauthorized();
|
||||
return Ok(await service.GetInboxAsync(userId, unreadOnly, page, pageSize));
|
||||
}
|
||||
|
||||
[HttpGet("unread-count")]
|
||||
public async Task<IActionResult> GetUnreadCount()
|
||||
{
|
||||
if (currentUser.UserId is not { } userId) return Unauthorized();
|
||||
return Ok(await service.GetUnreadCountAsync(userId));
|
||||
}
|
||||
|
||||
[HttpPatch("{id:guid}/read")]
|
||||
public async Task<IActionResult> MarkRead(Guid id, CancellationToken ct)
|
||||
{
|
||||
if (currentUser.UserId is not { } userId) return Unauthorized();
|
||||
try
|
||||
{
|
||||
await service.MarkReadAsync(userId, id, ct);
|
||||
return NoContent();
|
||||
}
|
||||
catch (KeyNotFoundException ex) { return NotFound(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPatch("read-all")]
|
||||
public async Task<IActionResult> MarkAllRead(CancellationToken ct)
|
||||
{
|
||||
if (currentUser.UserId is not { } userId) return Unauthorized();
|
||||
await service.MarkAllReadAsync(userId, ct);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
||||
{
|
||||
if (currentUser.UserId is not { } userId) return Unauthorized();
|
||||
try
|
||||
{
|
||||
await service.DeleteAsync(userId, id, ct);
|
||||
return NoContent();
|
||||
}
|
||||
catch (KeyNotFoundException ex) { return NotFound(new { error = ex.Message }); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.Notifications;
|
||||
|
||||
/// <param name="Values">Object whose public properties fill the {Token} placeholders in the type's templates.</param>
|
||||
/// <param name="CreatedByUserId">The acting user; excluded from the recipients.</param>
|
||||
public record NotificationArgs(
|
||||
object? Values,
|
||||
string EntityType,
|
||||
Guid EntityId,
|
||||
string Link,
|
||||
Guid? CreatedByUserId = null);
|
||||
|
||||
/// <param name="Id">The caller's own recipient row id — use it for read / delete.</param>
|
||||
public record NotificationResponse(
|
||||
Guid Id,
|
||||
string Type,
|
||||
NotificationSeverity Severity,
|
||||
string Title,
|
||||
string Message,
|
||||
string EntityType,
|
||||
Guid EntityId,
|
||||
string Link,
|
||||
DateTime CreatedAt,
|
||||
bool IsRead,
|
||||
DateTime? ReadAt);
|
||||
|
||||
public record UnreadCountResponse(int Count);
|
||||
@@ -0,0 +1,22 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace Distribution.Features.Notifications;
|
||||
|
||||
/// <summary>
|
||||
/// Server → client only. The server pushes a "notification" event to each recipient; clients never
|
||||
/// invoke hub methods. Users are addressed by their id (see <see cref="NotificationUserIdProvider"/>).
|
||||
/// </summary>
|
||||
[Authorize]
|
||||
public class NotificationHub : Hub
|
||||
{
|
||||
public const string Path = "/hubs/notifications";
|
||||
public const string ClientEvent = "notification";
|
||||
}
|
||||
|
||||
/// <summary>Maps a SignalR connection to the "UserId" claim that <c>AuthMiddleware</c> puts on the principal.</summary>
|
||||
public class NotificationUserIdProvider : IUserIdProvider
|
||||
{
|
||||
public string? GetUserId(HubConnectionContext connection) =>
|
||||
connection.User?.FindFirst("UserId")?.Value;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
namespace Distribution.Features.Notifications;
|
||||
|
||||
public class NotificationRecipientResolver(INotificationRepository repo)
|
||||
{
|
||||
/// <summary>
|
||||
/// Expands the rules into a de-duplicated list of active user ids, never including
|
||||
/// <paramref name="actingUserId"/>.
|
||||
/// </summary>
|
||||
public async Task<List<Guid>> ResolveAsync(IEnumerable<RecipientRule> rules, Guid? actingUserId)
|
||||
{
|
||||
var ids = new HashSet<Guid>();
|
||||
|
||||
foreach (var rule in rules)
|
||||
ids.UnionWith(await ResolveRuleAsync(rule));
|
||||
|
||||
if (actingUserId is { } actor)
|
||||
ids.Remove(actor);
|
||||
|
||||
if (ids.Count == 0) return [];
|
||||
return await repo.FilterActiveUserIdsAsync(ids);
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<Guid>> ResolveRuleAsync(RecipientRule rule)
|
||||
{
|
||||
switch (rule)
|
||||
{
|
||||
case UsersRule r:
|
||||
return r.Ids;
|
||||
|
||||
case PermissionRule r:
|
||||
return await repo.GetUserIdsByPermissionAsync(r.Code);
|
||||
|
||||
case ScopeRule r:
|
||||
return await repo.GetUserIdsByScopeAsync(r.Kind, r.Id);
|
||||
|
||||
case PermissionInScopeRule r:
|
||||
var withPermission = await repo.GetUserIdsByPermissionAsync(r.Code);
|
||||
var inScope = await repo.GetUserIdsByScopeAsync(r.Kind, r.Id);
|
||||
return withPermission.Intersect(inScope);
|
||||
|
||||
case ReportsToRule r:
|
||||
var supervisor = await repo.GetReportsToUserIdAsync(r.UserId);
|
||||
return supervisor is { } s ? [s] : [];
|
||||
|
||||
case RouteOwnerRule r:
|
||||
var owner = await repo.GetRouteOwnerUserIdAsync(r.Kind, r.Id);
|
||||
return owner is { } o ? [o] : [];
|
||||
|
||||
default:
|
||||
throw new NotSupportedException($"Unknown recipient rule {rule.GetType().Name}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
using Distribution.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.Notifications;
|
||||
|
||||
public class NotificationRepository(AppDBContext context) : INotificationRepository
|
||||
{
|
||||
// ── Inbox ────────────────────────────────────────────────────────────
|
||||
|
||||
public async Task AddAsync(Models.Notification notification) =>
|
||||
await context.Notifications.AddAsync(notification);
|
||||
|
||||
public void Discard(Models.Notification notification)
|
||||
{
|
||||
foreach (var recipient in notification.Recipients)
|
||||
context.Entry(recipient).State = EntityState.Detached;
|
||||
context.Entry(notification).State = EntityState.Detached;
|
||||
}
|
||||
|
||||
private IQueryable<Models.NotificationRecipient> InboxQuery(Guid userId, bool unreadOnly)
|
||||
{
|
||||
var query = context.NotificationRecipients.Where(r => r.FK_UserId == userId && r.IsActive);
|
||||
return unreadOnly ? query.Where(r => !r.IsRead) : query;
|
||||
}
|
||||
|
||||
public Task<int> GetInboxCountAsync(Guid userId, bool unreadOnly) =>
|
||||
InboxQuery(userId, unreadOnly).CountAsync();
|
||||
|
||||
public Task<List<Models.NotificationRecipient>> GetInboxAsync(Guid userId, bool unreadOnly, int page, int pageSize) =>
|
||||
InboxQuery(userId, unreadOnly)
|
||||
.Include(r => r.Notification)
|
||||
.OrderByDescending(r => r.Notification.CreatedAt)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
|
||||
public Task<int> GetUnreadCountAsync(Guid userId) =>
|
||||
InboxQuery(userId, unreadOnly: true).CountAsync();
|
||||
|
||||
public Task<Models.NotificationRecipient?> GetRecipientAsync(Guid userId, Guid recipientId) =>
|
||||
context.NotificationRecipients
|
||||
.FirstOrDefaultAsync(r => r.Id == recipientId && r.FK_UserId == userId && r.IsActive);
|
||||
|
||||
public Task<List<Models.NotificationRecipient>> GetUnreadRecipientsAsync(Guid userId) =>
|
||||
InboxQuery(userId, unreadOnly: true).ToListAsync();
|
||||
|
||||
public void RemoveRecipient(Models.NotificationRecipient recipient) =>
|
||||
context.NotificationRecipients.Remove(recipient);
|
||||
|
||||
// ── Recipient resolution ─────────────────────────────────────────────
|
||||
|
||||
public Task<List<Guid>> FilterActiveUserIdsAsync(IEnumerable<Guid> userIds)
|
||||
{
|
||||
var ids = userIds.ToList();
|
||||
return context.Users
|
||||
.Where(u => u.IsActive && ids.Contains(u.Id))
|
||||
.Select(u => u.Id)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public Task<List<Guid>> GetUserIdsByPermissionAsync(string permissionCode) =>
|
||||
context.Users
|
||||
.Where(u => u.IsActive && context.RolePermissions.Any(rp =>
|
||||
rp.FK_RoleId == u.FK_RoleId && rp.IsActive &&
|
||||
rp.Permission.IsActive && rp.Permission.Code == permissionCode))
|
||||
.Select(u => u.Id)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task<List<Guid>> GetUserIdsByScopeAsync(ScopeKind kind, Guid id)
|
||||
{
|
||||
Guid? regionId = null;
|
||||
Guid? routeId = null;
|
||||
Guid? storeId = null;
|
||||
|
||||
switch (kind)
|
||||
{
|
||||
case ScopeKind.Region:
|
||||
regionId = id;
|
||||
break;
|
||||
case ScopeKind.Route:
|
||||
routeId = id;
|
||||
regionId = await context.Routes.Where(r => r.Id == id)
|
||||
.Select(r => (Guid?)r.FK_RegionId).FirstOrDefaultAsync();
|
||||
break;
|
||||
case ScopeKind.Store:
|
||||
storeId = id;
|
||||
routeId = await context.Stores.Where(s => s.Id == id)
|
||||
.Select(s => (Guid?)s.FK_RouteId).FirstOrDefaultAsync();
|
||||
if (routeId is { } rid)
|
||||
regionId = await context.Routes.Where(r => r.Id == rid)
|
||||
.Select(r => (Guid?)r.FK_RegionId).FirstOrDefaultAsync();
|
||||
break;
|
||||
}
|
||||
|
||||
var userIds = new HashSet<Guid>();
|
||||
|
||||
if (regionId is { } region)
|
||||
userIds.UnionWith(await context.UserRegionScopes
|
||||
.Where(s => s.IsActive && s.FK_RegionId == region)
|
||||
.Select(s => s.FK_UserId).ToListAsync());
|
||||
|
||||
if (routeId is { } route)
|
||||
userIds.UnionWith(await context.UserRouteScopes
|
||||
.Where(s => s.IsActive && s.FK_RouteId == route)
|
||||
.Select(s => s.FK_UserId).ToListAsync());
|
||||
|
||||
if (storeId is { } store)
|
||||
userIds.UnionWith(await context.UserStoreScopes
|
||||
.Where(s => s.IsActive && s.FK_StoreId == store)
|
||||
.Select(s => s.FK_UserId).ToListAsync());
|
||||
|
||||
return userIds.ToList();
|
||||
}
|
||||
|
||||
public Task<Guid?> GetReportsToUserIdAsync(Guid userId) =>
|
||||
context.Users.Where(u => u.Id == userId)
|
||||
.Select(u => u.FK_ReportsToUserId)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
public async Task<Guid?> GetRouteOwnerUserIdAsync(ScopeKind kind, Guid id)
|
||||
{
|
||||
Guid? routeId = kind switch
|
||||
{
|
||||
ScopeKind.Route => id,
|
||||
ScopeKind.Store => await context.Stores.Where(s => s.Id == id)
|
||||
.Select(s => (Guid?)s.FK_RouteId).FirstOrDefaultAsync(),
|
||||
_ => null
|
||||
};
|
||||
if (routeId is not { } rid) return null;
|
||||
|
||||
return await context.Routes.Where(r => r.Id == rid && r.IsActive)
|
||||
.Select(r => r.FK_OwnerUserId)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
// ── Retention ────────────────────────────────────────────────────────
|
||||
|
||||
public Task<List<Models.NotificationRecipient>> GetExpiredReadRecipientsAsync(DateTime cutoff, int take) =>
|
||||
context.NotificationRecipients
|
||||
.Where(r => r.IsRead && r.ReadAt != null && r.ReadAt < cutoff)
|
||||
.Take(take)
|
||||
.ToListAsync();
|
||||
|
||||
public Task<List<Models.Notification>> GetOrphanNotificationsAsync(int take) =>
|
||||
context.Notifications
|
||||
.Where(n => !n.Recipients.Any())
|
||||
.Take(take)
|
||||
.ToListAsync();
|
||||
|
||||
public void RemoveRecipients(IEnumerable<Models.NotificationRecipient> recipients) =>
|
||||
context.NotificationRecipients.RemoveRange(recipients);
|
||||
|
||||
public void RemoveNotifications(IEnumerable<Models.Notification> notifications) =>
|
||||
context.Notifications.RemoveRange(notifications);
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Distribution.Features.Stock;
|
||||
using Distribution.Infra.UoW;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace Distribution.Features.Notifications;
|
||||
|
||||
public interface INotificationService
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a notification for every resolved recipient and pushes it live. Best-effort: this method
|
||||
/// never throws, so a notification problem can never fail the business action that triggered it.
|
||||
/// Call it only AFTER the owning service's <c>uow.CommitAsync()</c> has succeeded.
|
||||
/// </summary>
|
||||
Task NotifyAsync(string type, IReadOnlyList<RecipientRule> recipients, NotificationArgs args, CancellationToken ct = default);
|
||||
}
|
||||
|
||||
public class NotificationService(
|
||||
INotificationRepository repo,
|
||||
NotificationRecipientResolver resolver,
|
||||
IHubContext<NotificationHub> hub,
|
||||
IUnitOfWork uow,
|
||||
ILogger<NotificationService> logger) : INotificationService
|
||||
{
|
||||
private static readonly Regex TokenPattern = new(@"\{(\w+)\}", RegexOptions.Compiled);
|
||||
|
||||
// ── Trigger side ─────────────────────────────────────────────────────
|
||||
|
||||
public async Task NotifyAsync(string type, IReadOnlyList<RecipientRule> recipients, NotificationArgs args, CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!NotificationTypes.All.TryGetValue(type, out var definition))
|
||||
{
|
||||
logger.LogError("Notification type {Type} is not registered in NotificationTypes; skipped.", type);
|
||||
return;
|
||||
}
|
||||
|
||||
var userIds = await resolver.ResolveAsync(recipients, args.CreatedByUserId);
|
||||
if (userIds.Count == 0) return;
|
||||
|
||||
var values = ToDictionary(args.Values);
|
||||
var notification = new Models.Notification
|
||||
{
|
||||
Type = type,
|
||||
Severity = definition.Severity,
|
||||
Title = Truncate(Render(definition.TitleTemplate, values), 150),
|
||||
Message = Truncate(Render(definition.MessageTemplate, values), 500),
|
||||
EntityType = args.EntityType,
|
||||
EntityId = args.EntityId,
|
||||
Link = args.Link,
|
||||
CreatedByUserId = args.CreatedByUserId,
|
||||
CreatedAt = DateTime.Now,
|
||||
Recipients = userIds.Select(id => new Models.NotificationRecipient { FK_UserId = id }).ToList()
|
||||
};
|
||||
|
||||
await repo.AddAsync(notification);
|
||||
try
|
||||
{
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
repo.Discard(notification);
|
||||
throw;
|
||||
}
|
||||
|
||||
await PushAsync(notification);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Failed to create {Type} notification for {EntityType} {EntityId}.",
|
||||
type, args.EntityType, args.EntityId);
|
||||
}
|
||||
}
|
||||
|
||||
// Separate from persistence: a failed push still leaves the notification in the inbox.
|
||||
private async Task PushAsync(Models.Notification notification)
|
||||
{
|
||||
foreach (var recipient in notification.Recipients)
|
||||
{
|
||||
try
|
||||
{
|
||||
await hub.Clients.User(recipient.FK_UserId.ToString())
|
||||
.SendAsync(NotificationHub.ClientEvent, ToResponse(notification, recipient));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogWarning(ex, "Failed to push notification {NotificationId} to user {UserId}.",
|
||||
notification.Id, recipient.FK_UserId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Inbox side ───────────────────────────────────────────────────────
|
||||
|
||||
public async Task<PagedResponse<NotificationResponse>> GetInboxAsync(Guid userId, bool unreadOnly, int page, int pageSize)
|
||||
{
|
||||
page = Math.Max(1, page);
|
||||
pageSize = Math.Clamp(pageSize, 1, 100);
|
||||
|
||||
var total = await repo.GetInboxCountAsync(userId, unreadOnly);
|
||||
var rows = await repo.GetInboxAsync(userId, unreadOnly, page, pageSize);
|
||||
return new PagedResponse<NotificationResponse>(page, pageSize, total,
|
||||
rows.Select(r => ToResponse(r.Notification, r)).ToList());
|
||||
}
|
||||
|
||||
public async Task<UnreadCountResponse> GetUnreadCountAsync(Guid userId) =>
|
||||
new(await repo.GetUnreadCountAsync(userId));
|
||||
|
||||
public async Task MarkReadAsync(Guid userId, Guid recipientId, CancellationToken ct = default)
|
||||
{
|
||||
var recipient = await repo.GetRecipientAsync(userId, recipientId)
|
||||
?? throw new KeyNotFoundException("Notification not found.");
|
||||
if (recipient.IsRead) return;
|
||||
|
||||
recipient.IsRead = true;
|
||||
recipient.ReadAt = DateTime.Now;
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
|
||||
public async Task MarkAllReadAsync(Guid userId, CancellationToken ct = default)
|
||||
{
|
||||
var unread = await repo.GetUnreadRecipientsAsync(userId);
|
||||
if (unread.Count == 0) return;
|
||||
|
||||
var now = DateTime.Now;
|
||||
foreach (var recipient in unread)
|
||||
{
|
||||
recipient.IsRead = true;
|
||||
recipient.ReadAt = now;
|
||||
}
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(Guid userId, Guid recipientId, CancellationToken ct = default)
|
||||
{
|
||||
var recipient = await repo.GetRecipientAsync(userId, recipientId)
|
||||
?? throw new KeyNotFoundException("Notification not found.");
|
||||
|
||||
repo.RemoveRecipient(recipient);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
private static NotificationResponse ToResponse(Models.Notification n, Models.NotificationRecipient r) =>
|
||||
new(r.Id, n.Type, n.Severity, n.Title, n.Message, n.EntityType, n.EntityId, n.Link,
|
||||
n.CreatedAt, r.IsRead, r.ReadAt);
|
||||
|
||||
private static Dictionary<string, string> ToDictionary(object? values) =>
|
||||
values is null
|
||||
? []
|
||||
: values.GetType().GetProperties()
|
||||
.ToDictionary(p => p.Name, p => p.GetValue(values)?.ToString() ?? string.Empty);
|
||||
|
||||
private static string Render(string template, Dictionary<string, string> values) =>
|
||||
TokenPattern.Replace(template, m => values.TryGetValue(m.Groups[1].Value, out var v) ? v : string.Empty).Trim();
|
||||
|
||||
private static string Truncate(string value, int max) =>
|
||||
value.Length <= max ? value : value[..(max - 1)] + "…";
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.Notifications;
|
||||
|
||||
public record NotificationTypeDefinition(NotificationSeverity Severity, string TitleTemplate, string MessageTemplate);
|
||||
|
||||
/// <summary>
|
||||
/// Registry of every notification type. Services must pass one of these constants to
|
||||
/// <see cref="INotificationService.NotifyAsync"/> — never a string literal. Templates use {Token}
|
||||
/// placeholders filled from <see cref="NotificationArgs.Values"/>.
|
||||
/// </summary>
|
||||
public static class NotificationTypes
|
||||
{
|
||||
public const string RideCancelled = "RideCancelled";
|
||||
public const string RideDriverAssigned = "RideDriverAssigned";
|
||||
public const string DeliveryTaskFailed = "DeliveryTaskFailed";
|
||||
public const string DeliveryTaskPartiallyDelivered = "DeliveryTaskPartiallyDelivered";
|
||||
public const string SupplierClaimApproved = "SupplierClaimApproved";
|
||||
public const string SupplierClaimRejected = "SupplierClaimRejected";
|
||||
public const string ShopPOConfirmed = "ShopPOConfirmed";
|
||||
public const string ShopPOCancelled = "ShopPOCancelled";
|
||||
public const string StockLow = "StockLow";
|
||||
public const string CreditLimitBreached = "CreditLimitBreached";
|
||||
|
||||
public static readonly IReadOnlyDictionary<string, NotificationTypeDefinition> All =
|
||||
new Dictionary<string, NotificationTypeDefinition>
|
||||
{
|
||||
[RideCancelled] = new(NotificationSeverity.Critical,
|
||||
"Ride cancelled", "A ride was cancelled ({CancelType}). {Reason}"),
|
||||
[RideDriverAssigned] = new(NotificationSeverity.Info,
|
||||
"Ride assigned", "You have been assigned to a ride."),
|
||||
[DeliveryTaskFailed] = new(NotificationSeverity.Warning,
|
||||
"Delivery failed", "Delivery to {StoreName} failed. {Note}"),
|
||||
[DeliveryTaskPartiallyDelivered] = new(NotificationSeverity.Warning,
|
||||
"Partial delivery", "Delivery to {StoreName} was only partly delivered."),
|
||||
[SupplierClaimApproved] = new(NotificationSeverity.Success,
|
||||
"Supplier claim approved", "Your supplier claim was approved."),
|
||||
[SupplierClaimRejected] = new(NotificationSeverity.Warning,
|
||||
"Supplier claim rejected", "Your supplier claim was rejected."),
|
||||
[ShopPOConfirmed] = new(NotificationSeverity.Info,
|
||||
"Shop order confirmed", "Shop order for {StoreName} was confirmed."),
|
||||
[ShopPOCancelled] = new(NotificationSeverity.Warning,
|
||||
"Shop order cancelled", "Shop order for {StoreName} was cancelled: {Reason}"),
|
||||
[StockLow] = new(NotificationSeverity.Warning,
|
||||
"Low stock", "{ItemName} is down to {Quantity} at {Location}."),
|
||||
[CreditLimitBreached] = new(NotificationSeverity.Warning,
|
||||
"Credit limit exceeded", "{StoreName} has exceeded its credit limit."),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
namespace Distribution.Features.Notifications;
|
||||
|
||||
public enum ScopeKind { Region, Route, Store }
|
||||
|
||||
/// <summary>
|
||||
/// Who a notification goes to. There is deliberately no "everyone" rule — every trigger must say who
|
||||
/// needs to know. Scope coverage follows Region → Route → Store: a region scope covers its routes
|
||||
/// and their stores, a route scope covers its stores.
|
||||
/// </summary>
|
||||
public abstract record RecipientRule
|
||||
{
|
||||
/// <summary>These specific users (creator, driver, approver, route owner …).</summary>
|
||||
public static RecipientRule Users(params Guid[] ids) => new UsersRule(ids);
|
||||
|
||||
/// <summary>Every active user whose role holds the permission code.</summary>
|
||||
public static RecipientRule Permission(string code) => new PermissionRule(code);
|
||||
|
||||
/// <summary>Every active user whose scope covers the region / route / store.</summary>
|
||||
public static RecipientRule Scope(ScopeKind kind, Guid id) => new ScopeRule(kind, id);
|
||||
|
||||
/// <summary>Users who hold the permission AND whose scope covers the region / route / store.</summary>
|
||||
public static RecipientRule PermissionInScope(string code, ScopeKind kind, Guid id) =>
|
||||
new PermissionInScopeRule(code, kind, id);
|
||||
|
||||
/// <summary>The user's supervisor (User.FK_ReportsToUserId).</summary>
|
||||
public static RecipientRule ReportsTo(Guid userId) => new ReportsToRule(userId);
|
||||
|
||||
/// <summary>The owner of the route (<see cref="ScopeKind.Route"/>) or of the store's route (<see cref="ScopeKind.Store"/>).</summary>
|
||||
public static RecipientRule RouteOwner(ScopeKind kind, Guid id) => new RouteOwnerRule(kind, id);
|
||||
}
|
||||
|
||||
public sealed record UsersRule(IReadOnlyCollection<Guid> Ids) : RecipientRule;
|
||||
public sealed record PermissionRule(string Code) : RecipientRule;
|
||||
public sealed record ScopeRule(ScopeKind Kind, Guid Id) : RecipientRule;
|
||||
public sealed record PermissionInScopeRule(string Code, ScopeKind Kind, Guid Id) : RecipientRule;
|
||||
public sealed record ReportsToRule(Guid UserId) : RecipientRule;
|
||||
public sealed record RouteOwnerRule(ScopeKind Kind, Guid Id) : RecipientRule;
|
||||
@@ -1,4 +1,4 @@
|
||||
using Distribution.Models;
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.Payment;
|
||||
|
||||
@@ -6,8 +6,14 @@ public record CreatePaymentRequest(
|
||||
decimal Amount,
|
||||
PaymentMethod Method,
|
||||
EntryMode EntryMode = EntryMode.RealTime,
|
||||
// Cash: required. Cheque: optional (used as the deposit account hint if provided).
|
||||
Guid? BankAccountId = null,
|
||||
// Cheque: required.
|
||||
string? ChequeNumber = null,
|
||||
DateTime? ChequeDate = null,
|
||||
// Cheque: optional metadata forwarded to the auto-created ReceivedCheque.
|
||||
string? DrawerBankName = null,
|
||||
string? AccountHolderName = null,
|
||||
Guid? CollectedByUserId = null);
|
||||
|
||||
public record PaymentResponse(
|
||||
@@ -16,6 +22,8 @@ public record PaymentResponse(
|
||||
decimal Amount,
|
||||
PaymentMethod Method,
|
||||
EntryMode EntryMode,
|
||||
Guid? BankAccountId,
|
||||
Guid? ReceivedChequeId,
|
||||
string? ChequeNumber,
|
||||
DateTime? ChequeDate,
|
||||
DateTime CollectedAt,
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using Distribution.Features.Invoice;
|
||||
using Distribution.Features.BankAccount;
|
||||
using Distribution.Features.Invoice;
|
||||
using Distribution.Features.ReceivedCheque;
|
||||
using Distribution.Features.StoreCredit;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Infra.UoW;
|
||||
using Distribution.Models;
|
||||
@@ -8,11 +11,16 @@ namespace Distribution.Features.Payment;
|
||||
public class PaymentService(
|
||||
IPaymentRepository repo,
|
||||
IInvoiceRepository invoiceRepo,
|
||||
IBankAccountRepository bankAccountRepo,
|
||||
IReceivedChequeRepository receivedChequeRepo,
|
||||
IStoreCreditMovementRepository creditRepo,
|
||||
IUnitOfWork uow,
|
||||
ICurrentUserService currentUser)
|
||||
{
|
||||
private static PaymentResponse ToResponse(Models.Payment p) =>
|
||||
new(p.Id, p.FK_InvoiceId, p.Amount, p.Method, p.EntryMode, p.ChequeNumber, p.ChequeDate,
|
||||
new(p.Id, p.FK_InvoiceId, p.Amount, p.Method, p.EntryMode,
|
||||
p.FK_BankAccountId, p.FK_ReceivedChequeId,
|
||||
p.ChequeNumber, p.ChequeDate,
|
||||
p.CollectedAt, p.CollectedBy, p.RecordedBy, p.CreatedAt);
|
||||
|
||||
public async Task<List<PaymentResponse>?> GetByInvoiceIdAsync(Guid invoiceId)
|
||||
@@ -28,13 +36,46 @@ public class PaymentService(
|
||||
{
|
||||
var invoice = await invoiceRepo.GetByIdAsync(invoiceId);
|
||||
if (invoice is null || !invoice.IsActive) return null;
|
||||
if (invoice.Status == InvoiceStatus.Finalized)
|
||||
throw new InvalidOperationException("Cannot record a payment against a finalized invoice");
|
||||
|
||||
if (request.Amount <= 0)
|
||||
throw new InvalidOperationException("Amount must be greater than zero");
|
||||
|
||||
var existingPaid = (await repo.GetByInvoiceIdAsync(invoiceId)).Sum(p => p.Amount);
|
||||
var remaining = invoice.TotalAmount - existingPaid;
|
||||
if (request.Amount > remaining)
|
||||
throw new InvalidOperationException(
|
||||
$"Amount exceeds the outstanding balance of {remaining:0.##}");
|
||||
|
||||
if (request.Method == PaymentMethod.Cheque &&
|
||||
(string.IsNullOrWhiteSpace(request.ChequeNumber) || request.ChequeDate is null))
|
||||
throw new InvalidOperationException("ChequeNumber and ChequeDate are required when Method is Cheque");
|
||||
|
||||
if (request.Method == PaymentMethod.Cash && request.BankAccountId is null)
|
||||
throw new InvalidOperationException("BankAccountId is required when Method is Cash");
|
||||
|
||||
if (request.Method == PaymentMethod.Credit)
|
||||
{
|
||||
var store = invoice.ShopPO?.Store
|
||||
?? throw new InvalidOperationException("Invoice is missing the owning store; cannot record a credit payment");
|
||||
var currentOutstanding = await creditRepo.GetOutstandingForStoreAsync(store.Id);
|
||||
if (currentOutstanding + request.Amount > store.CreditLimit)
|
||||
{
|
||||
var available = store.CreditLimit - currentOutstanding;
|
||||
throw new InvalidOperationException(
|
||||
$"Credit limit exceeded. Available credit: {available:0.##}");
|
||||
}
|
||||
}
|
||||
|
||||
Models.BankAccount? bankAccount = null;
|
||||
if (request.BankAccountId is not null)
|
||||
{
|
||||
bankAccount = await bankAccountRepo.GetByIdAsync(request.BankAccountId.Value);
|
||||
if (bankAccount is null)
|
||||
throw new InvalidOperationException("BankAccountId does not reference a valid account");
|
||||
if (bankAccount.IsActive is false)
|
||||
throw new InvalidOperationException("The selected account is inactive");
|
||||
}
|
||||
|
||||
var recorderId = currentUser.UserId ?? request.CollectedByUserId
|
||||
?? throw new InvalidOperationException("CollectedByUserId is required while auth is unavailable");
|
||||
var collectedBy = request.CollectedByUserId ?? recorderId;
|
||||
@@ -50,6 +91,7 @@ public class PaymentService(
|
||||
EntryMode = request.EntryMode,
|
||||
ChequeNumber = request.Method == PaymentMethod.Cheque ? request.ChequeNumber : null,
|
||||
ChequeDate = request.Method == PaymentMethod.Cheque ? request.ChequeDate : null,
|
||||
FK_BankAccountId = request.BankAccountId,
|
||||
CollectedAt = DateTime.Now,
|
||||
CollectedBy = collectedBy,
|
||||
RecordedBy = recordedBy
|
||||
@@ -58,11 +100,48 @@ public class PaymentService(
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
// Auto-create a ReceivedCheque for the physical cheque. Status starts at Received;
|
||||
// bookkeeping of the deposit/clear transitions happens through the Received Cheques screen.
|
||||
if (request.Method == PaymentMethod.Cheque)
|
||||
{
|
||||
var cheque = new Models.ReceivedCheque
|
||||
{
|
||||
CompanyId = null,
|
||||
BranchId = null,
|
||||
ReceivedFromType = ReceivedFromType.Customer,
|
||||
ReceivedFromId = null,
|
||||
ReceivedFromName = $"Invoice {invoice.Id}",
|
||||
DrawerBankName = request.DrawerBankName,
|
||||
AccountHolderName = request.AccountHolderName,
|
||||
ChequeNo = request.ChequeNumber!,
|
||||
ChequeDate = request.ChequeDate!.Value,
|
||||
Amount = request.Amount,
|
||||
ReceivedDate = DateTime.Now,
|
||||
ReferenceType = "Invoice",
|
||||
Status = ReceivedChequeStatus.Received
|
||||
};
|
||||
await receivedChequeRepo.AddAsync(cheque);
|
||||
payment.FK_ReceivedChequeId = cheque.Id;
|
||||
}
|
||||
|
||||
await repo.AddAsync(payment);
|
||||
|
||||
var allPayments = await repo.GetByInvoiceIdAsync(invoiceId);
|
||||
var total = allPayments.Sum(p => p.Amount) + request.Amount;
|
||||
if (request.Method == PaymentMethod.Credit)
|
||||
{
|
||||
var movement = new StoreCreditMovement
|
||||
{
|
||||
FK_StoreId = invoice.ShopPO!.Store!.Id,
|
||||
Direction = StoreCreditMovementDirection.Usage,
|
||||
Amount = request.Amount,
|
||||
FK_InvoiceId = invoice.Id,
|
||||
FK_PaymentId = payment.Id,
|
||||
OccurredAt = DateTime.Now,
|
||||
RecordedBy = recorderId
|
||||
};
|
||||
await creditRepo.AddAsync(movement);
|
||||
}
|
||||
|
||||
var total = existingPaid + request.Amount;
|
||||
invoice.PaymentStatus = total == 0
|
||||
? PaymentStatus.Pending
|
||||
: total < invoice.TotalAmount
|
||||
@@ -70,6 +149,11 @@ public class PaymentService(
|
||||
: PaymentStatus.Paid;
|
||||
|
||||
invoiceRepo.Update(invoice);
|
||||
|
||||
// TODO ledger: when accounting is wired in, post a journal entry here —
|
||||
// Debit Cash/Bank (BankAccount) OR Cheques-in-hand (for Cheque payments)
|
||||
// Credit Accounts Receivable (invoice.Store)
|
||||
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Distribution.Features.PaymentReport;
|
||||
|
||||
public interface IPaymentReportRepository
|
||||
{
|
||||
/// <summary>Active payments collected within [From, To] (whole days).</summary>
|
||||
Task<List<PaymentRow>> GetPaymentsAsync(PaymentReportFilter f);
|
||||
|
||||
/// <summary>Finalized, non-cancelled invoices up to the end of 'To', each with cash/cheque paid as of then.</summary>
|
||||
Task<List<InvoiceBalanceRow>> GetInvoiceBalancesAsync(PaymentReportFilter f);
|
||||
|
||||
/// <summary>Sum of TotalAmount of invoices finalized within [From, To].</summary>
|
||||
Task<decimal> GetInvoicedAsync(PaymentReportFilter f);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.PaymentReport;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/reports/payments")]
|
||||
public class PaymentReportController(PaymentReportService service) : ControllerBase
|
||||
{
|
||||
[HttpGet("analytics")]
|
||||
public async Task<IActionResult> Analytics(
|
||||
[FromQuery] DateTime? from, [FromQuery] DateTime? to, [FromQuery] Guid? regionId)
|
||||
{
|
||||
var f = PaymentReportService.BuildFilter(from, to, regionId);
|
||||
var error = PaymentReportService.Validate(f);
|
||||
return error is null ? Ok(await service.GetAnalyticsAsync(f)) : BadRequest(new { message = error });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.PaymentReport;
|
||||
|
||||
public record PaymentReportFilter(DateTime From, DateTime To, Guid? RegionId);
|
||||
|
||||
public record PaymentSummary(
|
||||
decimal Invoiced,
|
||||
decimal Collected,
|
||||
decimal CreditExtended,
|
||||
decimal Outstanding,
|
||||
decimal Overdue,
|
||||
decimal? CollectionRatePct,
|
||||
int PaymentCount);
|
||||
|
||||
public record MethodBreakdown(string Method, int Count, decimal Amount, decimal SharePct);
|
||||
|
||||
public record PaymentTrendPoint(string Period, decimal Cash, decimal Cheque, decimal Credit);
|
||||
|
||||
public record AgingBucket(string Bucket, int Invoices, decimal Balance);
|
||||
|
||||
public record StoreOutstanding(Guid StoreId, string StoreName, int Invoices, decimal Balance, decimal Overdue);
|
||||
|
||||
public record AccountCollection(Guid? AccountId, string AccountName, int Count, decimal Amount);
|
||||
|
||||
public record ChequePipeline(string Status, int Count, decimal Amount);
|
||||
|
||||
public record PaymentAnalyticsResponse(
|
||||
DateTime From,
|
||||
DateTime To,
|
||||
PaymentSummary Summary,
|
||||
List<MethodBreakdown> ByMethod,
|
||||
List<PaymentTrendPoint> Trend,
|
||||
List<AgingBucket> Aging,
|
||||
List<StoreOutstanding> TopOutstanding,
|
||||
List<AccountCollection> ByAccount,
|
||||
List<ChequePipeline> Cheques);
|
||||
|
||||
// Repository row shapes
|
||||
public record PaymentRow(PaymentMethod Method, decimal Amount, DateTime CollectedAt, Guid? AccountId, string? AccountName, string? ChequeStatus);
|
||||
|
||||
public record InvoiceBalanceRow(Guid StoreId, string StoreName, DateTime FinalizedAt, decimal Total, decimal Paid);
|
||||
@@ -0,0 +1,59 @@
|
||||
using Distribution.Data;
|
||||
using Distribution.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.PaymentReport;
|
||||
|
||||
public class PaymentReportRepository(AppDBContext db) : IPaymentReportRepository
|
||||
{
|
||||
public async Task<List<PaymentRow>> GetPaymentsAsync(PaymentReportFilter f)
|
||||
{
|
||||
var start = f.From.Date;
|
||||
var end = f.To.Date.AddDays(1);
|
||||
var q = db.Payments.AsNoTracking()
|
||||
.Where(p => p.IsActive && p.CollectedAt >= start && p.CollectedAt < end
|
||||
&& p.Invoice.ShopPO.Status != ShopPOStatus.Cancelled);
|
||||
if (f.RegionId.HasValue)
|
||||
q = q.Where(p => p.Invoice.ShopPO.Store.Route.FK_RegionId == f.RegionId.Value);
|
||||
|
||||
return await q.Select(p => new PaymentRow(
|
||||
p.Method, p.Amount, p.CollectedAt,
|
||||
p.FK_BankAccountId,
|
||||
p.BankAccount == null ? null : p.BankAccount.AccountName,
|
||||
p.ReceivedCheque == null ? null : p.ReceivedCheque.Status.ToString()))
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<List<InvoiceBalanceRow>> GetInvoiceBalancesAsync(PaymentReportFilter f)
|
||||
{
|
||||
var end = f.To.Date.AddDays(1);
|
||||
var q = db.Invoices.AsNoTracking()
|
||||
.Where(i => i.IsActive && i.Status == InvoiceStatus.Finalized
|
||||
&& i.ShopPO.Status != ShopPOStatus.Cancelled && i.FinalizedAt < end);
|
||||
if (f.RegionId.HasValue)
|
||||
q = q.Where(i => i.ShopPO.Store.Route.FK_RegionId == f.RegionId.Value);
|
||||
|
||||
// Credit payments do not reduce the receivable (same rule as the dashboard).
|
||||
return await q.Select(i => new InvoiceBalanceRow(
|
||||
i.ShopPO.Store.Id,
|
||||
i.ShopPO.Store.StoreName,
|
||||
i.FinalizedAt!.Value,
|
||||
i.TotalAmount,
|
||||
i.Payments.Where(p => p.IsActive && p.Method != PaymentMethod.Credit && p.CollectedAt < end)
|
||||
.Sum(p => (decimal?)p.Amount) ?? 0m))
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<decimal> GetInvoicedAsync(PaymentReportFilter f)
|
||||
{
|
||||
var start = f.From.Date;
|
||||
var end = f.To.Date.AddDays(1);
|
||||
var q = db.Invoices.AsNoTracking()
|
||||
.Where(i => i.IsActive && i.Status == InvoiceStatus.Finalized
|
||||
&& i.ShopPO.Status != ShopPOStatus.Cancelled
|
||||
&& i.FinalizedAt >= start && i.FinalizedAt < end);
|
||||
if (f.RegionId.HasValue)
|
||||
q = q.Where(i => i.ShopPO.Store.Route.FK_RegionId == f.RegionId.Value);
|
||||
return await q.SumAsync(i => (decimal?)i.TotalAmount) ?? 0m;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.PaymentReport;
|
||||
|
||||
public class PaymentReportService(IPaymentReportRepository repo)
|
||||
{
|
||||
public const int MaxRangeDays = 366;
|
||||
private const int DailyGranularityMaxDays = 92;
|
||||
private const int OverdueDays = 30; // matches the dashboard receivables definition
|
||||
private const int TopStores = 10;
|
||||
|
||||
public static PaymentReportFilter BuildFilter(DateTime? from, DateTime? to, Guid? regionId)
|
||||
{
|
||||
var end = (to ?? DateTime.Now).Date;
|
||||
var start = (from ?? end.AddDays(-29)).Date;
|
||||
return new PaymentReportFilter(start, end, regionId);
|
||||
}
|
||||
|
||||
public static string? Validate(PaymentReportFilter f)
|
||||
{
|
||||
if (f.To < f.From) return "'to' must not be before 'from'.";
|
||||
if ((f.To - f.From).TotalDays > MaxRangeDays) return $"Date range cannot exceed {MaxRangeDays} days.";
|
||||
return null;
|
||||
}
|
||||
|
||||
private static decimal Pct(decimal part, decimal whole) => whole > 0 ? Math.Round(part / whole * 100, 2) : 0m;
|
||||
|
||||
public async Task<PaymentAnalyticsResponse> GetAnalyticsAsync(PaymentReportFilter f)
|
||||
{
|
||||
var payments = await repo.GetPaymentsAsync(f);
|
||||
var balances = await repo.GetInvoiceBalancesAsync(f);
|
||||
var invoiced = await repo.GetInvoicedAsync(f);
|
||||
var asOf = f.To.Date.AddDays(1);
|
||||
|
||||
var collected = payments.Where(p => p.Method != PaymentMethod.Credit).Sum(p => p.Amount);
|
||||
var credit = payments.Where(p => p.Method == PaymentMethod.Credit).Sum(p => p.Amount);
|
||||
var total = payments.Sum(p => p.Amount);
|
||||
|
||||
var open = balances
|
||||
.Select(b => (b.StoreId, b.StoreName, Age: (asOf - b.FinalizedAt).TotalDays, Balance: Math.Max(0m, b.Total - b.Paid)))
|
||||
.Where(b => b.Balance > 0)
|
||||
.ToList();
|
||||
var outstanding = open.Sum(b => b.Balance);
|
||||
var overdue = open.Where(b => b.Age > OverdueDays).Sum(b => b.Balance);
|
||||
|
||||
var summary = new PaymentSummary(
|
||||
invoiced, collected, credit, outstanding, overdue,
|
||||
invoiced > 0 ? Math.Round(collected / invoiced * 100, 2) : null,
|
||||
payments.Count);
|
||||
|
||||
var byMethod = Enum.GetValues<PaymentMethod>().Select(m =>
|
||||
{
|
||||
var rows = payments.Where(p => p.Method == m).ToList();
|
||||
var amount = rows.Sum(p => p.Amount);
|
||||
return new MethodBreakdown(m.ToString(), rows.Count, amount, Pct(amount, total));
|
||||
}).ToList();
|
||||
|
||||
var daily = (f.To - f.From).TotalDays <= DailyGranularityMaxDays;
|
||||
var trend = payments
|
||||
.GroupBy(p => daily ? p.CollectedAt.ToString("yyyy-MM-dd") : p.CollectedAt.ToString("yyyy-MM"))
|
||||
.OrderBy(g => g.Key)
|
||||
.Select(g => new PaymentTrendPoint(g.Key,
|
||||
g.Where(p => p.Method == PaymentMethod.Cash).Sum(p => p.Amount),
|
||||
g.Where(p => p.Method == PaymentMethod.Cheque).Sum(p => p.Amount),
|
||||
g.Where(p => p.Method == PaymentMethod.Credit).Sum(p => p.Amount)))
|
||||
.ToList();
|
||||
|
||||
var buckets = new (string Name, Func<double, bool> In)[]
|
||||
{
|
||||
("0-30 days", a => a <= 30),
|
||||
("31-60 days", a => a > 30 && a <= 60),
|
||||
("61-90 days", a => a > 60 && a <= 90),
|
||||
("90+ days", a => a > 90),
|
||||
};
|
||||
var aging = buckets.Select(b =>
|
||||
{
|
||||
var rows = open.Where(o => b.In(o.Age)).ToList();
|
||||
return new AgingBucket(b.Name, rows.Count, rows.Sum(r => r.Balance));
|
||||
}).ToList();
|
||||
|
||||
var topOutstanding = open
|
||||
.GroupBy(o => (o.StoreId, o.StoreName))
|
||||
.Select(g => new StoreOutstanding(g.Key.StoreId, g.Key.StoreName, g.Count(),
|
||||
g.Sum(x => x.Balance), g.Where(x => x.Age > OverdueDays).Sum(x => x.Balance)))
|
||||
.OrderByDescending(s => s.Balance)
|
||||
.Take(TopStores)
|
||||
.ToList();
|
||||
|
||||
var byAccount = payments
|
||||
.Where(p => p.Method == PaymentMethod.Cash)
|
||||
.GroupBy(p => (p.AccountId, Name: p.AccountName ?? "Unassigned"))
|
||||
.Select(g => new AccountCollection(g.Key.AccountId, g.Key.Name, g.Count(), g.Sum(p => p.Amount)))
|
||||
.OrderByDescending(a => a.Amount)
|
||||
.ToList();
|
||||
|
||||
var cheques = payments
|
||||
.Where(p => p.Method == PaymentMethod.Cheque)
|
||||
.GroupBy(p => p.ChequeStatus ?? "Unknown")
|
||||
.Select(g => new ChequePipeline(g.Key, g.Count(), g.Sum(p => p.Amount)))
|
||||
.OrderByDescending(c => c.Amount)
|
||||
.ToList();
|
||||
|
||||
return new PaymentAnalyticsResponse(f.From, f.To, summary, byMethod, trend, aging, topOutstanding, byAccount, cheques);
|
||||
}
|
||||
}
|
||||
@@ -60,24 +60,24 @@ public class ProductReportRepository(AppDBContext db) : IProductReportRepository
|
||||
return (rows, total);
|
||||
}
|
||||
|
||||
// Sold = shop PO lines on non-cancelled POs whose invoice is Finalized, dated by Invoice.FinalizedAt.
|
||||
private IQueryable<ShopPOItem> SoldLines(ProductReportFilter f)
|
||||
// Sold = billable (non-removed) invoice lines of Finalized invoices on non-cancelled POs, dated by Invoice.FinalizedAt.
|
||||
private IQueryable<InvoiceItem> SoldLines(ProductReportFilter f)
|
||||
{
|
||||
var lines =
|
||||
from li in db.ShopPOItems.AsNoTracking()
|
||||
join inv in db.Invoices.AsNoTracking() on li.FK_ShopPOId equals inv.FK_ShopPOId
|
||||
where inv.Status == InvoiceStatus.Finalized
|
||||
&& li.ShopPO.Status != ShopPOStatus.Cancelled
|
||||
&& (!f.From.HasValue || inv.FinalizedAt >= f.From.Value)
|
||||
&& (!f.To.HasValue || inv.FinalizedAt < f.To.Value.Date.AddDays(1))
|
||||
from li in db.InvoiceItems.AsNoTracking()
|
||||
where li.IsActive && !li.IsRemoved
|
||||
&& li.Invoice.Status == InvoiceStatus.Finalized
|
||||
&& li.Invoice.ShopPO.Status != ShopPOStatus.Cancelled
|
||||
&& (!f.From.HasValue || li.Invoice.FinalizedAt >= f.From.Value)
|
||||
&& (!f.To.HasValue || li.Invoice.FinalizedAt < f.To.Value.Date.AddDays(1))
|
||||
select li;
|
||||
|
||||
if (f.BrandId.HasValue) lines = lines.Where(l => l.Item.FK_BrandId == f.BrandId.Value);
|
||||
if (f.CategoryId.HasValue) lines = lines.Where(l => l.Item.FK_CategoryId == f.CategoryId.Value);
|
||||
if (f.SubCategoryId.HasValue) lines = lines.Where(l => l.Item.FK_SubCategoryId == f.SubCategoryId.Value);
|
||||
if (f.StoreId.HasValue) lines = lines.Where(l => l.ShopPO.FK_StoreId == f.StoreId.Value);
|
||||
if (f.RouteId.HasValue) lines = lines.Where(l => l.ShopPO.Store.FK_RouteId == f.RouteId.Value);
|
||||
if (f.RegionId.HasValue) lines = lines.Where(l => l.ShopPO.Store.Route.FK_RegionId == f.RegionId.Value);
|
||||
if (f.StoreId.HasValue) lines = lines.Where(l => l.Invoice.ShopPO.FK_StoreId == f.StoreId.Value);
|
||||
if (f.RouteId.HasValue) lines = lines.Where(l => l.Invoice.ShopPO.Store.FK_RouteId == f.RouteId.Value);
|
||||
if (f.RegionId.HasValue) lines = lines.Where(l => l.Invoice.ShopPO.Store.Route.FK_RegionId == f.RegionId.Value);
|
||||
if (!string.IsNullOrWhiteSpace(f.Search))
|
||||
{
|
||||
var s = f.Search.Trim();
|
||||
@@ -93,33 +93,36 @@ public class ProductReportRepository(AppDBContext db) : IProductReportRepository
|
||||
.Select(g => new
|
||||
{
|
||||
g.Key.FK_ItemId, g.Key.Sku, g.Key.Name, g.Key.CostPrice,
|
||||
Orders = g.Select(x => x.FK_ShopPOId).Distinct().Count(),
|
||||
QtyOrdered = g.Sum(x => (decimal)x.Quantity),
|
||||
QtyFulfilled = g.Sum(x => (decimal)x.QuantityFulfilled),
|
||||
SalesValue = g.Sum(x => x.QuantityFulfilled * x.UnitPrice),
|
||||
QtyOrdered = g.Sum(x => (decimal)(x.ShopPOItem != null ? x.ShopPOItem.Quantity : x.Quantity)),
|
||||
QtyFulfilled = g.Sum(x => (decimal)x.Quantity),
|
||||
SalesValue = g.Sum(x => x.Quantity * x.UnitPrice),
|
||||
});
|
||||
|
||||
var total = await query.CountAsync();
|
||||
var page_ = await query.OrderByDescending(r => r.SalesValue).ThenBy(r => r.Name)
|
||||
.Skip((page - 1) * pageSize).Take(pageSize).ToListAsync();
|
||||
|
||||
// Distinct stores per item is counted separately: MySQL can't run the correlated
|
||||
// Distinct orders and stores per item are counted separately: MySQL can't run the correlated
|
||||
// "SELECT DISTINCT ... WHERE outer.col" derived table EF generates inside a GroupBy.
|
||||
var ids = page_.Select(r => r.FK_ItemId).ToList();
|
||||
var storePairs = await SoldLines(f)
|
||||
var pairs = await SoldLines(f)
|
||||
.Where(l => ids.Contains(l.FK_ItemId))
|
||||
.Select(l => new { l.FK_ItemId, l.ShopPO.FK_StoreId })
|
||||
.Select(l => new { l.FK_ItemId, PoId = l.Invoice.FK_ShopPOId, l.Invoice.ShopPO.FK_StoreId })
|
||||
.Distinct()
|
||||
.ToListAsync();
|
||||
var storeCounts = storePairs.GroupBy(p => p.FK_ItemId).ToDictionary(g => g.Key, g => g.Count());
|
||||
var orderCounts = pairs.GroupBy(p => p.FK_ItemId)
|
||||
.ToDictionary(g => g.Key, g => g.Select(p => p.PoId).Distinct().Count());
|
||||
var storeCounts = pairs.GroupBy(p => p.FK_ItemId)
|
||||
.ToDictionary(g => g.Key, g => g.Select(p => p.FK_StoreId).Distinct().Count());
|
||||
|
||||
var rows = page_.Select(r => new SalesAggregate(r.FK_ItemId, r.Sku, r.Name, r.CostPrice,
|
||||
r.Orders, storeCounts.GetValueOrDefault(r.FK_ItemId), r.QtyOrdered, r.QtyFulfilled, r.SalesValue)).ToList();
|
||||
orderCounts.GetValueOrDefault(r.FK_ItemId), storeCounts.GetValueOrDefault(r.FK_ItemId),
|
||||
r.QtyOrdered, r.QtyFulfilled, r.SalesValue)).ToList();
|
||||
return (rows, total);
|
||||
}
|
||||
|
||||
public Task<decimal> GetTotalSalesAsync(ProductReportFilter f) =>
|
||||
SoldLines(f).SumAsync(x => x.QuantityFulfilled * x.UnitPrice);
|
||||
SoldLines(f).SumAsync(x => x.Quantity * x.UnitPrice);
|
||||
|
||||
public async Task<Dictionary<Guid, decimal>> GetSoldQtyAsync(ProductReportFilter f, IEnumerable<Guid> itemIds)
|
||||
{
|
||||
@@ -127,7 +130,7 @@ public class ProductReportRepository(AppDBContext db) : IProductReportRepository
|
||||
return await SoldLines(f with { Search = null })
|
||||
.Where(l => ids.Contains(l.FK_ItemId))
|
||||
.GroupBy(l => l.FK_ItemId)
|
||||
.Select(g => new { g.Key, Qty = g.Sum(x => (decimal)x.QuantityFulfilled) })
|
||||
.Select(g => new { g.Key, Qty = g.Sum(x => (decimal)x.Quantity) })
|
||||
.ToDictionaryAsync(x => x.Key, x => x.Qty);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.ReceivedCheque;
|
||||
|
||||
public interface IReceivedChequeRepository
|
||||
{
|
||||
Task<Models.ReceivedCheque?> GetByIdAsync(Guid id);
|
||||
Task<(List<Models.ReceivedCheque> Items, int TotalCount)> ListAsync(
|
||||
int? companyId, int? branchId, ReceivedChequeStatus? status, ReceivedFromType? receivedFromType,
|
||||
int page, int pageSize);
|
||||
Task AddAsync(Models.ReceivedCheque cheque);
|
||||
void Update(Models.ReceivedCheque cheque);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using Distribution.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.ReceivedCheque;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/received-cheques")]
|
||||
public class ReceivedChequeController(ReceivedChequeService service) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> List(
|
||||
[FromQuery] int? companyId,
|
||||
[FromQuery] int? branchId,
|
||||
[FromQuery] ReceivedChequeStatus? status,
|
||||
[FromQuery] ReceivedFromType? receivedFromType,
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 50)
|
||||
=> Ok(await service.ListAsync(companyId, branchId, status, receivedFromType, page, pageSize));
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create(CreateReceivedChequeRequest request, CancellationToken ct)
|
||||
{
|
||||
try { return Ok(await service.CreateAsync(request, ct)); }
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}/status")]
|
||||
public async Task<IActionResult> UpdateStatus(Guid id, UpdateReceivedChequeStatusRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await service.UpdateStatusAsync(id, request, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using Distribution.Features.ChequeBook;
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.ReceivedCheque;
|
||||
|
||||
public record ReceivedChequeResponse(
|
||||
Guid ReceivedChequeId,
|
||||
int? CompanyId,
|
||||
int? BranchId,
|
||||
ReceivedFromType ReceivedFromType,
|
||||
int? ReceivedFromId,
|
||||
string ReceivedFromName,
|
||||
string? DrawerBankName,
|
||||
string? DrawerBankBranch,
|
||||
string? AccountHolderName,
|
||||
string ChequeNo,
|
||||
DateTime ChequeDate,
|
||||
decimal Amount,
|
||||
DateTime ReceivedDate,
|
||||
string? ReferenceType,
|
||||
int? ReferenceId,
|
||||
string? Notes,
|
||||
string? CreatedBy,
|
||||
ReceivedChequeStatus Status,
|
||||
Guid? DepositBankAccountId,
|
||||
DateTime? DepositDate);
|
||||
|
||||
public record CreateReceivedChequeRequest(
|
||||
int CompanyId,
|
||||
int? BranchId,
|
||||
ReceivedFromType ReceivedFromType,
|
||||
int? ReceivedFromId,
|
||||
string ReceivedFromName,
|
||||
string? DrawerBankName,
|
||||
string? DrawerBankBranch,
|
||||
string? AccountHolderName,
|
||||
string ChequeNo,
|
||||
DateTime ChequeDate,
|
||||
decimal Amount,
|
||||
DateTime ReceivedDate,
|
||||
string? ReferenceType,
|
||||
int? ReferenceId,
|
||||
string? Notes,
|
||||
string? CreatedBy);
|
||||
|
||||
public record UpdateReceivedChequeStatusRequest(
|
||||
ReceivedChequeStatusAction Action,
|
||||
Guid? DepositBankAccountId,
|
||||
DateTime? DepositDate,
|
||||
string? Notes,
|
||||
string? PerformedBy);
|
||||
@@ -0,0 +1,34 @@
|
||||
using Distribution.Data;
|
||||
using Distribution.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.ReceivedCheque;
|
||||
|
||||
public class ReceivedChequeRepository(AppDBContext context) : IReceivedChequeRepository
|
||||
{
|
||||
public async Task<Models.ReceivedCheque?> GetByIdAsync(Guid id) =>
|
||||
await context.ReceivedCheques.FirstOrDefaultAsync(c => c.Id == id);
|
||||
|
||||
public async Task<(List<Models.ReceivedCheque> Items, int TotalCount)> ListAsync(
|
||||
int? companyId, int? branchId, ReceivedChequeStatus? status, ReceivedFromType? receivedFromType,
|
||||
int page, int pageSize)
|
||||
{
|
||||
var query = context.ReceivedCheques.AsQueryable();
|
||||
if (companyId is not null) query = query.Where(c => c.CompanyId == companyId);
|
||||
if (branchId is not null) query = query.Where(c => c.BranchId == branchId);
|
||||
if (status is not null) query = query.Where(c => c.Status == status);
|
||||
if (receivedFromType is not null) query = query.Where(c => c.ReceivedFromType == receivedFromType);
|
||||
|
||||
var total = await query.CountAsync();
|
||||
var items = await query
|
||||
.OrderByDescending(c => c.ReceivedDate)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
return (items, total);
|
||||
}
|
||||
|
||||
public async Task AddAsync(Models.ReceivedCheque cheque) => await context.ReceivedCheques.AddAsync(cheque);
|
||||
|
||||
public void Update(Models.ReceivedCheque cheque) => context.ReceivedCheques.Update(cheque);
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using Distribution.Features.BankAccount;
|
||||
using Distribution.Features.ChequeBook;
|
||||
using Distribution.Infra.UoW;
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.ReceivedCheque;
|
||||
|
||||
public class ReceivedChequeService(
|
||||
IReceivedChequeRepository repo,
|
||||
IBankAccountRepository bankAccountRepo,
|
||||
IUnitOfWork uow)
|
||||
{
|
||||
private static ReceivedChequeResponse ToResponse(Models.ReceivedCheque c) =>
|
||||
new(c.Id, c.CompanyId, c.BranchId, c.ReceivedFromType, c.ReceivedFromId, c.ReceivedFromName,
|
||||
c.DrawerBankName, c.DrawerBankBranch, c.AccountHolderName,
|
||||
c.ChequeNo, c.ChequeDate, c.Amount, c.ReceivedDate,
|
||||
c.ReferenceType, c.ReferenceId, c.Notes, c.CreatedBy,
|
||||
c.Status, c.FK_DepositBankAccountId, c.DepositDate);
|
||||
|
||||
public async Task<PagedResult<ReceivedChequeResponse>> ListAsync(
|
||||
int? companyId, int? branchId, ReceivedChequeStatus? status, ReceivedFromType? receivedFromType,
|
||||
int page, int pageSize)
|
||||
{
|
||||
if (page < 1) page = 1;
|
||||
if (pageSize < 1 || pageSize > 200) pageSize = 50;
|
||||
|
||||
var (items, total) = await repo.ListAsync(companyId, branchId, status, receivedFromType, page, pageSize);
|
||||
return new PagedResult<ReceivedChequeResponse>(
|
||||
items.Select(ToResponse).ToList(), total, page, pageSize);
|
||||
}
|
||||
|
||||
public async Task<ReceivedChequeResponse> CreateAsync(CreateReceivedChequeRequest request, CancellationToken ct = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.ReceivedFromName))
|
||||
throw new InvalidOperationException("ReceivedFromName is required");
|
||||
if (string.IsNullOrWhiteSpace(request.ChequeNo))
|
||||
throw new InvalidOperationException("ChequeNo is required");
|
||||
if (request.Amount <= 0)
|
||||
throw new InvalidOperationException("Amount must be greater than zero");
|
||||
|
||||
var cheque = new Models.ReceivedCheque
|
||||
{
|
||||
CompanyId = request.CompanyId,
|
||||
BranchId = request.BranchId,
|
||||
ReceivedFromType = request.ReceivedFromType,
|
||||
ReceivedFromId = request.ReceivedFromId,
|
||||
ReceivedFromName = request.ReceivedFromName,
|
||||
DrawerBankName = request.DrawerBankName,
|
||||
DrawerBankBranch = request.DrawerBankBranch,
|
||||
AccountHolderName = request.AccountHolderName,
|
||||
ChequeNo = request.ChequeNo,
|
||||
ChequeDate = request.ChequeDate,
|
||||
Amount = request.Amount,
|
||||
ReceivedDate = request.ReceivedDate,
|
||||
ReferenceType = request.ReferenceType,
|
||||
ReferenceId = request.ReferenceId,
|
||||
Notes = request.Notes,
|
||||
CreatedBy = request.CreatedBy,
|
||||
Status = ReceivedChequeStatus.Received
|
||||
};
|
||||
|
||||
await repo.AddAsync(cheque);
|
||||
await uow.CommitAsync(ct);
|
||||
return ToResponse(cheque);
|
||||
}
|
||||
|
||||
public async Task<ReceivedChequeResponse?> UpdateStatusAsync(Guid id, UpdateReceivedChequeStatusRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var cheque = await repo.GetByIdAsync(id);
|
||||
if (cheque is null) return null;
|
||||
|
||||
switch (request.Action)
|
||||
{
|
||||
case ReceivedChequeStatusAction.Deposit:
|
||||
if (cheque.Status != ReceivedChequeStatus.Received)
|
||||
throw new InvalidOperationException("Only received cheques can be deposited");
|
||||
if (request.DepositBankAccountId is null)
|
||||
throw new InvalidOperationException("DepositBankAccountId is required when depositing");
|
||||
if (request.DepositDate is null)
|
||||
throw new InvalidOperationException("DepositDate is required when depositing");
|
||||
var bankAccount = await bankAccountRepo.GetByIdAsync(request.DepositBankAccountId.Value);
|
||||
if (bankAccount is null || bankAccount.AccountType != BankAccountType.Bank)
|
||||
throw new InvalidOperationException("DepositBankAccountId does not reference a valid bank account");
|
||||
cheque.Status = ReceivedChequeStatus.Deposited;
|
||||
cheque.FK_DepositBankAccountId = request.DepositBankAccountId;
|
||||
cheque.DepositDate = request.DepositDate;
|
||||
break;
|
||||
|
||||
case ReceivedChequeStatusAction.Clear:
|
||||
if (cheque.Status != ReceivedChequeStatus.Deposited)
|
||||
throw new InvalidOperationException("Only deposited cheques can be cleared");
|
||||
cheque.Status = ReceivedChequeStatus.Cleared;
|
||||
break;
|
||||
|
||||
case ReceivedChequeStatusAction.Return:
|
||||
if (cheque.Status != ReceivedChequeStatus.Deposited)
|
||||
throw new InvalidOperationException("Only deposited cheques can be returned");
|
||||
cheque.Status = ReceivedChequeStatus.Returned;
|
||||
break;
|
||||
|
||||
case ReceivedChequeStatusAction.Cancel:
|
||||
if (cheque.Status != ReceivedChequeStatus.Received)
|
||||
throw new InvalidOperationException("Only received cheques can be cancelled");
|
||||
cheque.Status = ReceivedChequeStatus.Cancelled;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.Notes))
|
||||
cheque.Notes = request.Notes;
|
||||
|
||||
repo.Update(cheque);
|
||||
await uow.CommitAsync(ct);
|
||||
return ToResponse(cheque);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using Distribution.Features.Stock;
|
||||
using Distribution.Features.DeliveryRide;
|
||||
using Distribution.Features.Notifications;
|
||||
using Distribution.Features.Stock;
|
||||
using Distribution.Features.DriverComplianceConfig;
|
||||
using Distribution.Features.DriverProfile;
|
||||
using Distribution.Infra.UoW;
|
||||
@@ -10,9 +12,14 @@ public class RideService(
|
||||
IRideRepository rideRepo,
|
||||
IDriverProfileRepository driverRepo,
|
||||
IDriverComplianceConfigRepository complianceRepo,
|
||||
IDeliveryTaskRepository taskRepo,
|
||||
StockMover mover,
|
||||
INotificationService notifications,
|
||||
ILogger<RideService> logger,
|
||||
IUnitOfWork uow)
|
||||
{
|
||||
private const string RidesLink = "/operations/rides";
|
||||
|
||||
public async Task<PagedResponse<RideResponse>> GetAllAsync(RideStatus? status, Guid? driverProfileId, Guid? vehicleId, int page, int pageSize)
|
||||
{
|
||||
var total = await rideRepo.GetCountAsync(status, driverProfileId, vehicleId);
|
||||
@@ -72,6 +79,12 @@ public class RideService(
|
||||
try { rideRepo.Update(ride); await uow.CommitAsync(ct); }
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
await notifications.NotifyAsync(
|
||||
NotificationTypes.RideDriverAssigned,
|
||||
[RecipientRule.Users(driver.FK_UserId)],
|
||||
new NotificationArgs(null, "Ride", rideId, RidesLink),
|
||||
ct);
|
||||
|
||||
var saved = await rideRepo.GetByIdAsync(rideId);
|
||||
return ToResponse(saved!);
|
||||
}
|
||||
@@ -125,9 +138,46 @@ public class RideService(
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
await NotifyRideCancelledAsync(ride, request, userId, ct);
|
||||
|
||||
return ToResponse(ride);
|
||||
}
|
||||
|
||||
// A ride has no route of its own; the people to tell are the owners and managers of the routes
|
||||
// its delivery tasks go to. Best-effort: the ride is already cancelled, so nothing here may throw.
|
||||
private async Task NotifyRideCancelledAsync(DeliveryRun ride, CancelRideRequest request, Guid userId, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tasks = await taskRepo.GetByRideIdAsync(ride.Id);
|
||||
var routeIds = tasks
|
||||
.Where(t => t.ShopPO?.Store is not null)
|
||||
.Select(t => t.ShopPO.Store.FK_RouteId)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
var recipients = new List<RecipientRule>();
|
||||
foreach (var routeId in routeIds)
|
||||
{
|
||||
recipients.Add(RecipientRule.RouteOwner(ScopeKind.Route, routeId));
|
||||
recipients.Add(RecipientRule.PermissionInScope("delivery-task.manage", ScopeKind.Route, routeId));
|
||||
}
|
||||
if (recipients.Count == 0) return;
|
||||
|
||||
await notifications.NotifyAsync(
|
||||
NotificationTypes.RideCancelled,
|
||||
recipients,
|
||||
new NotificationArgs(
|
||||
new { CancelType = request.TypeCode, Reason = request.Reason },
|
||||
"Ride", ride.Id, RidesLink, userId),
|
||||
ct);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Failed to build RideCancelled notification for ride {RideId}.", ride.Id);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckDriverComplianceAsync(Guid driverProfileId)
|
||||
{
|
||||
var driver = await driverRepo.GetByIdAsync(driverProfileId)
|
||||
|
||||
@@ -10,7 +10,10 @@ public interface ISchemeRepository
|
||||
|
||||
Task<List<Models.SchemeRedemption>> GetRedemptionsAsync(Guid schemeId);
|
||||
Task AddRedemptionAsync(Models.SchemeRedemption entity);
|
||||
Task<List<Models.SchemeRedemption>> GetRedemptionsByShopPOAsync(Guid shopPOId);
|
||||
void RemoveRedemption(Models.SchemeRedemption entity);
|
||||
Task<bool> HasRedemptionAsync(Guid schemeId, Guid orderId);
|
||||
Task<bool> HasLockedInvoiceAsync(Guid shopPOId);
|
||||
|
||||
Task<Models.SchemeBundleComponent?> GetBundleComponentAsync(Guid schemeId, Guid itemId);
|
||||
Task AddBundleComponentAsync(Models.SchemeBundleComponent entity);
|
||||
|
||||
@@ -112,6 +112,14 @@ public class SchemeController(SchemeService service) : ControllerBase
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost("suggest")]
|
||||
[Authorize("scheme.view")]
|
||||
public async Task<IActionResult> Suggest(SuggestSchemesRequest request)
|
||||
{
|
||||
var result = await service.SuggestAsync(request);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/spend")]
|
||||
[Authorize("scheme.view")]
|
||||
public async Task<IActionResult> GetSpend(Guid id)
|
||||
|
||||
@@ -94,7 +94,13 @@ public record SchemeEligibilityResponse(
|
||||
string? Reason,
|
||||
decimal? DiscountAmount,
|
||||
Guid? FreeItemId,
|
||||
decimal? FreeQty);
|
||||
decimal? FreeQty,
|
||||
string? FreeItemName = null);
|
||||
|
||||
/// <summary>A cart line for scheme suggestion before the Shop PO exists. UnitPrice null = resolve from the price list.</summary>
|
||||
public record SuggestLine(Guid ItemId, int Quantity, decimal? UnitPrice = null, bool IsFree = false);
|
||||
|
||||
public record SuggestSchemesRequest(Guid StoreId, List<SuggestLine> Items);
|
||||
|
||||
public record SchemeRedemptionResponse(
|
||||
Guid Id,
|
||||
|
||||
@@ -49,9 +49,25 @@ public class SchemeRepository(AppDBContext context) : ISchemeRepository
|
||||
public async Task AddRedemptionAsync(Models.SchemeRedemption entity) =>
|
||||
await context.SchemeRedemptions.AddAsync(entity);
|
||||
|
||||
public async Task<List<Models.SchemeRedemption>> GetRedemptionsByShopPOAsync(Guid shopPOId)
|
||||
{
|
||||
var list = await context.SchemeRedemptions.Include(r => r.Scheme).Where(r => r.FK_ShopPOId == shopPOId).ToListAsync();
|
||||
// Rows added earlier in this unit of work are not in the database yet; rows removed are still there.
|
||||
foreach (var added in context.SchemeRedemptions.Local.Where(r => r.FK_ShopPOId == shopPOId && !list.Contains(r)))
|
||||
list.Add(added);
|
||||
return list.Where(r => context.Entry(r).State != EntityState.Deleted).ToList();
|
||||
}
|
||||
|
||||
public void RemoveRedemption(Models.SchemeRedemption entity) =>
|
||||
context.SchemeRedemptions.Remove(entity);
|
||||
|
||||
public async Task<bool> HasRedemptionAsync(Guid schemeId, Guid shopPOId) =>
|
||||
await context.SchemeRedemptions.AnyAsync(r => r.FK_SchemeId == schemeId && r.FK_ShopPOId == shopPOId);
|
||||
|
||||
// An invoice past Draft (printed or finalized) has a fixed amount, so it can no longer take a discount.
|
||||
public async Task<bool> HasLockedInvoiceAsync(Guid shopPOId) =>
|
||||
await context.Invoices.AnyAsync(i => i.FK_ShopPOId == shopPOId && i.IsActive && i.Status != Models.InvoiceStatus.Draft);
|
||||
|
||||
public async Task<Models.SchemeBundleComponent?> GetBundleComponentAsync(Guid schemeId, Guid itemId) =>
|
||||
await context.SchemeBundleComponents
|
||||
.FirstOrDefaultAsync(c => c.FK_SchemeId == schemeId && c.FK_ItemId == itemId);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Text.Json;
|
||||
using Distribution.Features.Invoice;
|
||||
using Distribution.Features.Item;
|
||||
using Distribution.Features.PriceList;
|
||||
using Distribution.Features.ShopPO;
|
||||
using Distribution.Features.Stock;
|
||||
using Distribution.Features.Store;
|
||||
@@ -14,6 +16,8 @@ public class SchemeService(
|
||||
IItemRepository itemRepo,
|
||||
IStoreRepository storeRepo,
|
||||
IStockRepository stockRepo,
|
||||
IInvoiceRepository invoiceRepo,
|
||||
PriceListService priceListService,
|
||||
IUnitOfWork uow)
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
|
||||
@@ -261,12 +265,7 @@ public class SchemeService(
|
||||
|
||||
foreach (var scheme in candidates)
|
||||
{
|
||||
if (scheme.FK_RegionId.HasValue && scheme.FK_RegionId != regionId)
|
||||
continue;
|
||||
if (!string.IsNullOrEmpty(scheme.EligibleSegment) &&
|
||||
!string.Equals(scheme.EligibleSegment, store.Segment, StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
if (shopPO.CreatedAt < scheme.DateRangeFrom || shopPO.CreatedAt > scheme.DateRangeTo)
|
||||
if (!IsInScope(scheme, store.Segment, regionId, shopPO.CreatedAt))
|
||||
continue;
|
||||
|
||||
result.Add(EvaluateRule(scheme, items));
|
||||
@@ -275,6 +274,328 @@ public class SchemeService(
|
||||
return result;
|
||||
}
|
||||
|
||||
private static bool IsInScope(Models.Scheme scheme, string? storeSegment, Guid? storeRegionId, DateTime asOf)
|
||||
{
|
||||
if (scheme.FK_RegionId.HasValue && scheme.FK_RegionId != storeRegionId) return false;
|
||||
if (!string.IsNullOrEmpty(scheme.EligibleSegment) &&
|
||||
!string.Equals(scheme.EligibleSegment, storeSegment, StringComparison.OrdinalIgnoreCase)) return false;
|
||||
return asOf >= scheme.DateRangeFrom && asOf.Date <= scheme.DateRangeTo.Date;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Suggestions for a Shop PO that is still being built (nothing saved yet). Returns eligible schemes,
|
||||
/// plus BuyXGetY/FreeBundle schemes the cart is part-way to, so the UI can nudge ("add 4 more").
|
||||
/// </summary>
|
||||
public async Task<List<SchemeEligibilityResponse>?> SuggestAsync(SuggestSchemesRequest request)
|
||||
{
|
||||
var store = await storeRepo.GetByIdWithRegionAsync(request.StoreId);
|
||||
if (store is null || !store.IsActive) return null;
|
||||
|
||||
var lines = new List<ShopPOItem>();
|
||||
foreach (var l in request.Items.Where(i => !i.IsFree))
|
||||
{
|
||||
var price = l.UnitPrice
|
||||
?? (await priceListService.ResolveByStoreAsync(l.ItemId, request.StoreId, DateTime.Now))?.Price
|
||||
?? 0m;
|
||||
lines.Add(new ShopPOItem { FK_ItemId = l.ItemId, Quantity = l.Quantity, UnitPrice = price });
|
||||
}
|
||||
|
||||
var regionId = store.Route?.FK_RegionId;
|
||||
var inCart = lines.Select(l => l.FK_ItemId).ToHashSet();
|
||||
var result = new List<SchemeEligibilityResponse>();
|
||||
|
||||
foreach (var scheme in (await repo.GetAllAsync()).Where(s => s.IsActive))
|
||||
{
|
||||
if (!IsInScope(scheme, store.Segment, regionId, DateTime.Now)) continue;
|
||||
|
||||
var eval = EvaluateRule(scheme, lines);
|
||||
var relevant = eval.Eligible || scheme.RuleType switch
|
||||
{
|
||||
SchemeRuleType.BuyXGetY => scheme.FK_TriggerItemId.HasValue && inCart.Contains(scheme.FK_TriggerItemId.Value),
|
||||
SchemeRuleType.FreeBundle => scheme.BundleComponents.Any(c => inCart.Contains(c.FK_ItemId)),
|
||||
_ => false
|
||||
};
|
||||
if (!relevant) continue;
|
||||
|
||||
result.Add(eval with { FreeItemName = scheme.FreeItem?.Name });
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks the free lines of a Shop PO that is about to be saved: each must reference an in-scope scheme the
|
||||
/// paid lines still qualify for, be the scheme's free item, stay within the entitled qty, and be in stock.
|
||||
/// Stamps the scheme-name snapshot and forces UnitPrice to 0. Read-only; throws InvalidOperationException.
|
||||
/// </summary>
|
||||
public async Task ValidateFreeLinesAsync(Guid storeId, DateTime asOf, List<ShopPOItem> lines)
|
||||
{
|
||||
var freeLines = lines.Where(l => l.IsFree).ToList();
|
||||
if (freeLines.Count == 0) return;
|
||||
|
||||
if (freeLines.Any(l => l.FK_SchemeId is null))
|
||||
throw new InvalidOperationException("A free-issue line must reference the scheme that granted it.");
|
||||
if (freeLines.Any(l => l.Quantity <= 0))
|
||||
throw new InvalidOperationException("Free-issue quantity must be greater than zero (remove the line instead).");
|
||||
|
||||
var store = await storeRepo.GetByIdWithRegionAsync(storeId)
|
||||
?? throw new InvalidOperationException("Store not found.");
|
||||
var regionId = store.Route?.FK_RegionId;
|
||||
|
||||
foreach (var group in freeLines.GroupBy(l => l.FK_SchemeId!.Value))
|
||||
{
|
||||
var scheme = await repo.GetByIdAsync(group.Key);
|
||||
if (scheme is null || !scheme.IsActive)
|
||||
throw new InvalidOperationException("A free-issue line references a scheme that no longer exists.");
|
||||
if (!IsInScope(scheme, store.Segment, regionId, asOf))
|
||||
throw new InvalidOperationException($"Scheme '{scheme.Name}' is not available for this store or date.");
|
||||
|
||||
var eval = EvaluateRule(scheme, lines);
|
||||
if (!eval.Eligible || eval.FreeItemId is null || eval.FreeQty is null)
|
||||
throw new InvalidOperationException(eval.Reason ?? $"Order no longer qualifies for scheme '{scheme.Name}'.");
|
||||
if (group.Any(l => l.FK_ItemId != eval.FreeItemId))
|
||||
throw new InvalidOperationException($"Scheme '{scheme.Name}' grants a different free item.");
|
||||
|
||||
var qty = group.Sum(l => l.Quantity);
|
||||
if (qty > eval.FreeQty.Value)
|
||||
throw new InvalidOperationException($"Free quantity {qty} exceeds the {eval.FreeQty.Value:0.##} entitled by scheme '{scheme.Name}'.");
|
||||
|
||||
var available = (await stockRepo.GetBalancesByItemAsync(eval.FreeItemId.Value)).Sum(b => b.Quantity);
|
||||
if (available < qty)
|
||||
throw new InvalidOperationException($"Insufficient stock to give {qty} free of scheme '{scheme.Name}'.");
|
||||
|
||||
foreach (var l in group)
|
||||
{
|
||||
l.UnitPrice = 0m;
|
||||
l.SchemeName = scheme.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks the percentage-discount schemes (FlatPercent / SlabDiscount) the user accepted for a Shop PO that is
|
||||
/// about to be saved: one at most, in scope, and the paid lines must still qualify. Read-only; throws.
|
||||
/// </summary>
|
||||
public async Task ValidateDiscountSchemesAsync(Guid storeId, DateTime asOf, List<ShopPOItem> lines, List<Guid>? schemeIds)
|
||||
{
|
||||
if (schemeIds is not { Count: > 0 }) return;
|
||||
if (schemeIds.Distinct().Count() > 1)
|
||||
throw new InvalidOperationException("Only one discount scheme can be applied to an order.");
|
||||
|
||||
var store = await storeRepo.GetByIdWithRegionAsync(storeId)
|
||||
?? throw new InvalidOperationException("Store not found.");
|
||||
|
||||
var scheme = await repo.GetByIdAsync(schemeIds[0]);
|
||||
if (scheme is null || !scheme.IsActive)
|
||||
throw new InvalidOperationException("The selected discount scheme no longer exists.");
|
||||
if (scheme.RuleType is not (SchemeRuleType.FlatPercent or SchemeRuleType.SlabDiscount))
|
||||
throw new InvalidOperationException($"Scheme '{scheme.Name}' is not a discount scheme.");
|
||||
if (!IsInScope(scheme, store.Segment, store.Route?.FK_RegionId, asOf))
|
||||
throw new InvalidOperationException($"Scheme '{scheme.Name}' is not available for this store or date.");
|
||||
|
||||
var eval = EvaluateRule(scheme, lines);
|
||||
if (!eval.Eligible)
|
||||
throw new InvalidOperationException(eval.Reason ?? $"Order does not qualify for scheme '{scheme.Name}'.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Re-evaluates the percentage discounts redeemed against a Shop PO for the given (invoice) lines and returns
|
||||
/// their total. With persist = true the redemption amounts are updated too (the caller commits), so spend/ROI and
|
||||
/// the invoice stay in step; a discount the lines no longer qualify for drops to 0 and comes back if they requalify.
|
||||
/// </summary>
|
||||
public async Task<decimal> CalculateDiscountAsync(Guid shopPOId, IEnumerable<ShopPOItem> lines, bool persist)
|
||||
{
|
||||
var priced = lines.ToList();
|
||||
decimal total = 0m;
|
||||
foreach (var r in (await repo.GetRedemptionsByShopPOAsync(shopPOId)).Where(r => r.DiscountAmount.HasValue))
|
||||
{
|
||||
var scheme = await repo.GetByIdAsync(r.FK_SchemeId);
|
||||
var eval = scheme is null ? null : EvaluateRule(scheme, priced);
|
||||
var amount = eval is { Eligible: true, DiscountAmount: not null } ? eval.DiscountAmount.Value : 0m;
|
||||
if (persist) r.DiscountAmount = amount;
|
||||
total += amount;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
// How many free units each scheme currently entitles for these paid lines (0 when no longer eligible).
|
||||
private async Task<Dictionary<Guid, int>> FreeEntitlementsAsync(IEnumerable<Guid> schemeIds, List<ShopPOItem> paidLines)
|
||||
{
|
||||
var result = new Dictionary<Guid, int>();
|
||||
foreach (var id in schemeIds.Distinct())
|
||||
{
|
||||
var scheme = await repo.GetByIdAsync(id);
|
||||
var eval = scheme is null ? null : EvaluateRule(scheme, paidLines);
|
||||
result[id] = eval is { Eligible: true, FreeQty: not null } ? (int)Math.Floor(eval.FreeQty.Value) : 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Free-issue entitlement per scheme for unsaved invoice lines (editor preview). Lines carry IsFree / scheme id;
|
||||
/// only the paid ones count toward the scheme rules.
|
||||
/// </summary>
|
||||
public async Task<Dictionary<Guid, int>> GetFreeEntitlementsAsync(IEnumerable<Guid> schemeIds, IEnumerable<ShopPOItem> lines) =>
|
||||
await FreeEntitlementsAsync(schemeIds, lines.Where(l => !l.IsFree).ToList());
|
||||
|
||||
/// <summary>
|
||||
/// Keeps an invoice's free-issue lines within what the remaining paid lines still earn: when the paid quantity of the
|
||||
/// qualifying item is lowered (or removed), the free quantity is reduced to match (a line that earns nothing is struck
|
||||
/// out) and the PO's redemption is resized. Stages changes only. Returns true if any free line was changed.
|
||||
/// </summary>
|
||||
public async Task<bool> ReconcileInvoiceFreeLinesAsync(Guid shopPOId, List<InvoiceItem> lines)
|
||||
{
|
||||
var freeLines = lines.Where(l => l.IsActive && l.IsFree && l.FK_SchemeId.HasValue).ToList();
|
||||
if (freeLines.Count == 0) return false;
|
||||
|
||||
var paid = lines.Where(l => l.IsActive && !l.IsRemoved && !l.IsFree)
|
||||
.Select(l => new ShopPOItem { FK_ItemId = l.FK_ItemId, Quantity = l.Quantity, UnitPrice = l.UnitPrice })
|
||||
.ToList();
|
||||
var entitled = await FreeEntitlementsAsync(freeLines.Select(l => l.FK_SchemeId!.Value), paid);
|
||||
var redemptions = await repo.GetRedemptionsByShopPOAsync(shopPOId);
|
||||
var changed = false;
|
||||
|
||||
foreach (var group in freeLines.GroupBy(l => l.FK_SchemeId!.Value))
|
||||
{
|
||||
var remaining = entitled[group.Key];
|
||||
foreach (var line in group.Where(l => !l.IsRemoved).OrderBy(l => l.CreatedAt))
|
||||
{
|
||||
if (line.Quantity <= remaining) { remaining -= line.Quantity; continue; }
|
||||
|
||||
if (remaining == 0) line.IsRemoved = true;
|
||||
else line.Quantity = remaining;
|
||||
remaining = 0;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
var redemption = redemptions.FirstOrDefault(r => r.FK_SchemeId == group.Key && r.FK_FreeItemId.HasValue);
|
||||
if (redemption is not null)
|
||||
redemption.FreeQty = group.Where(l => !l.IsRemoved).Sum(l => l.Quantity);
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Discount the given schemes would give for these lines, without touching any redemption (editor preview).
|
||||
/// </summary>
|
||||
public async Task<decimal> CalculateDiscountForSchemesAsync(IEnumerable<Guid> schemeIds, IEnumerable<ShopPOItem> lines)
|
||||
{
|
||||
var priced = lines.ToList();
|
||||
decimal total = 0m;
|
||||
foreach (var id in schemeIds.Distinct())
|
||||
{
|
||||
var scheme = await repo.GetByIdAsync(id);
|
||||
var eval = scheme is null ? null : EvaluateRule(scheme, priced);
|
||||
if (eval is { Eligible: true, DiscountAmount: not null }) total += eval.DiscountAmount.Value;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the discount choice made on a Draft invoice (the percentage scheme the user accepted, or none) to the
|
||||
/// PO's redemptions: newly chosen schemes are validated and added, unchosen ones removed. The amount itself is
|
||||
/// then set by CalculateDiscountAsync when the totals are re-derived. Stages changes only. Null = leave as is.
|
||||
/// </summary>
|
||||
public async Task ApplyInvoiceDiscountSelectionAsync(Guid shopPOId, Guid storeId, List<InvoiceItem> lines, List<Guid>? schemeIds)
|
||||
{
|
||||
if (schemeIds is null) return;
|
||||
if (schemeIds.Distinct().Count() > 1)
|
||||
throw new InvalidOperationException("Only one discount scheme can be applied to an order.");
|
||||
|
||||
var existing = (await repo.GetRedemptionsByShopPOAsync(shopPOId)).Where(r => r.DiscountAmount.HasValue).ToList();
|
||||
foreach (var r in existing.Where(r => !schemeIds.Contains(r.FK_SchemeId)))
|
||||
repo.RemoveRedemption(r);
|
||||
|
||||
var paid = lines.Where(l => l.IsActive && !l.IsRemoved && !l.IsFree)
|
||||
.Select(l => new ShopPOItem { FK_ItemId = l.FK_ItemId, Quantity = l.Quantity, UnitPrice = l.UnitPrice })
|
||||
.ToList();
|
||||
var added = schemeIds.Where(id => existing.All(r => r.FK_SchemeId != id)).ToList();
|
||||
await ValidateDiscountSchemesAsync(storeId, DateTime.Now, paid, added);
|
||||
|
||||
foreach (var id in added)
|
||||
await repo.AddRedemptionAsync(new SchemeRedemption { FK_SchemeId = id, FK_ShopPOId = shopPOId, DiscountAmount = 0m });
|
||||
}
|
||||
|
||||
/// <summary>Discounts already redeemed against a Shop PO, for display and for editing.</summary>
|
||||
public async Task<List<ShopPODiscountResponse>> GetDiscountsForShopPOAsync(Guid shopPOId) =>
|
||||
(await repo.GetRedemptionsByShopPOAsync(shopPOId))
|
||||
.Where(r => r.DiscountAmount.HasValue)
|
||||
.Select(r => new ShopPODiscountResponse(r.FK_SchemeId, r.Scheme?.Name ?? string.Empty, r.DiscountAmount!.Value))
|
||||
.ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Brings the Shop PO's scheme redemptions in line with its saved lines: free-goods redemptions follow the
|
||||
/// free lines (created, resized or removed), discount redemptions are recomputed or dropped if the order no
|
||||
/// longer qualifies. Stages changes only — the caller owns the commit.
|
||||
/// </summary>
|
||||
public async Task SyncRedemptionsAsync(Guid shopPOId, List<ShopPOItem> lines, List<Guid>? discountSchemeIds = null)
|
||||
{
|
||||
var existing = await repo.GetRedemptionsByShopPOAsync(shopPOId);
|
||||
var freeBySchemes = lines.Where(l => l.IsFree && l.FK_SchemeId.HasValue)
|
||||
.GroupBy(l => l.FK_SchemeId!.Value).ToDictionary(g => g.Key, g => g.ToList());
|
||||
|
||||
foreach (var r in existing)
|
||||
{
|
||||
if (r.FK_FreeItemId.HasValue)
|
||||
{
|
||||
if (!freeBySchemes.ContainsKey(r.FK_SchemeId)) repo.RemoveRedemption(r);
|
||||
}
|
||||
else if (r.DiscountAmount.HasValue)
|
||||
{
|
||||
// A null list means "leave the accepted discounts alone" (just recompute them).
|
||||
if (discountSchemeIds is not null && !discountSchemeIds.Contains(r.FK_SchemeId))
|
||||
{
|
||||
repo.RemoveRedemption(r);
|
||||
continue;
|
||||
}
|
||||
|
||||
var scheme = await repo.GetByIdAsync(r.FK_SchemeId);
|
||||
var eval = scheme is null ? null : EvaluateRule(scheme, lines);
|
||||
if (eval is { Eligible: true, DiscountAmount: not null }) r.DiscountAmount = eval.DiscountAmount;
|
||||
else repo.RemoveRedemption(r);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var schemeId in discountSchemeIds ?? [])
|
||||
{
|
||||
if (existing.Any(r => r.FK_SchemeId == schemeId && r.DiscountAmount.HasValue)) continue;
|
||||
var scheme = await repo.GetByIdAsync(schemeId);
|
||||
var eval = scheme is null ? null : EvaluateRule(scheme, lines);
|
||||
if (eval is { Eligible: true, DiscountAmount: not null })
|
||||
await repo.AddRedemptionAsync(new SchemeRedemption
|
||||
{
|
||||
FK_SchemeId = schemeId,
|
||||
FK_ShopPOId = shopPOId,
|
||||
DiscountAmount = eval.DiscountAmount
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var (schemeId, group) in freeBySchemes)
|
||||
{
|
||||
var freeItem = await itemRepo.GetByIdAsync(group[0].FK_ItemId)
|
||||
?? throw new InvalidOperationException("Free item not found.");
|
||||
var qty = group.Sum(l => l.Quantity);
|
||||
var redemption = existing.FirstOrDefault(r => r.FK_SchemeId == schemeId && r.FK_FreeItemId.HasValue);
|
||||
|
||||
if (redemption is null)
|
||||
await repo.AddRedemptionAsync(new SchemeRedemption
|
||||
{
|
||||
FK_SchemeId = schemeId,
|
||||
FK_ShopPOId = shopPOId,
|
||||
FK_FreeItemId = freeItem.Id,
|
||||
FreeQty = qty,
|
||||
FreeItemCostValue = freeItem.CostPrice ?? 0m
|
||||
});
|
||||
else
|
||||
{
|
||||
redemption.FK_FreeItemId = freeItem.Id;
|
||||
redemption.FreeQty = qty;
|
||||
redemption.FreeItemCostValue = freeItem.CostPrice ?? 0m;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scope-only check (region, segment, date range as of today) with no ShopPO to evaluate line
|
||||
/// items against — used by Store Management's `checkStoreSchemeEligibility(storeId, schemeId)`
|
||||
@@ -299,14 +620,18 @@ public class SchemeService(
|
||||
return (false, $"Scheme is only available to the '{scheme.EligibleSegment}' segment.");
|
||||
|
||||
var today = DateTime.Now;
|
||||
if (today < scheme.DateRangeFrom || today > scheme.DateRangeTo)
|
||||
if (today < scheme.DateRangeFrom || today.Date > scheme.DateRangeTo.Date)
|
||||
return (false, "Scheme is not currently active (outside its date range).");
|
||||
|
||||
return (true, null);
|
||||
}
|
||||
|
||||
private static SchemeEligibilityResponse EvaluateRule(Models.Scheme scheme, List<ShopPOItem> items)
|
||||
private static SchemeEligibilityResponse EvaluateRule(Models.Scheme scheme, List<ShopPOItem> allItems)
|
||||
{
|
||||
// Free-issue lines (added by a previous redemption) must not count toward a trigger/bundle
|
||||
// quantity, otherwise the free units inflate the multiplier.
|
||||
var items = allItems.Where(i => !i.IsFree && i.UnitPrice > 0).ToList();
|
||||
|
||||
switch (scheme.RuleType)
|
||||
{
|
||||
case SchemeRuleType.BuyXGetY:
|
||||
@@ -383,6 +708,9 @@ public class SchemeService(
|
||||
if (await repo.HasRedemptionAsync(schemeId, shopPOId))
|
||||
throw new InvalidOperationException("This scheme has already been redeemed against this ShopPO.");
|
||||
|
||||
if (await repo.HasLockedInvoiceAsync(shopPOId))
|
||||
throw new InvalidOperationException("This order's invoice is already printed or finalized, so a scheme can no longer be applied. Apply schemes before the invoice is printed.");
|
||||
|
||||
var items = await shopPORepo.GetActiveItemsByShopPOIdAsync(shopPOId);
|
||||
var eligibility = EvaluateRule(scheme, items);
|
||||
if (!eligibility.Eligible)
|
||||
@@ -411,7 +739,10 @@ public class SchemeService(
|
||||
FK_ShopPOId = shopPOId,
|
||||
FK_ItemId = freeItem.Id,
|
||||
Quantity = (int)eligibility.FreeQty.Value,
|
||||
UnitPrice = 0m
|
||||
UnitPrice = 0m,
|
||||
IsFree = true,
|
||||
FK_SchemeId = scheme.Id,
|
||||
SchemeName = scheme.Name
|
||||
});
|
||||
|
||||
redemption.FK_FreeItemId = freeItem.Id;
|
||||
@@ -422,7 +753,27 @@ public class SchemeService(
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
if (newItems.Count > 0) await shopPORepo.AddItemsAsync(newItems);
|
||||
if (newItems.Count > 0)
|
||||
{
|
||||
await shopPORepo.AddItemsAsync(newItems);
|
||||
|
||||
// A still-draft invoice was copied from the PO before this line existed, so mirror it there.
|
||||
if (shopPO.InvoiceId is Guid invoiceId &&
|
||||
await invoiceRepo.GetByIdAsync(invoiceId) is { Status: InvoiceStatus.Draft })
|
||||
{
|
||||
await invoiceRepo.AddItemsAsync(newItems.Select(n => new InvoiceItem
|
||||
{
|
||||
FK_InvoiceId = invoiceId,
|
||||
FK_ShopPOItemId = n.Id,
|
||||
FK_ItemId = n.FK_ItemId,
|
||||
Quantity = n.Quantity,
|
||||
UnitPrice = 0m,
|
||||
IsFree = true,
|
||||
FK_SchemeId = n.FK_SchemeId,
|
||||
SchemeName = n.SchemeName
|
||||
}));
|
||||
}
|
||||
}
|
||||
await repo.AddRedemptionAsync(redemption);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
@@ -484,6 +835,8 @@ public class SchemeService(
|
||||
|
||||
if (freeItemId is null || freeQty is null or <= 0)
|
||||
throw new InvalidOperationException("FreeItemId and a positive FreeQty are required.");
|
||||
if (freeQty.Value != decimal.Truncate(freeQty.Value))
|
||||
throw new InvalidOperationException("FreeQty must be a whole number (order lines hold whole units).");
|
||||
if (await itemRepo.GetByIdAsync(freeItemId.Value) is null)
|
||||
throw new InvalidOperationException("Free item not found.");
|
||||
}
|
||||
|
||||
@@ -30,6 +30,16 @@ public class ShopPOController(ShopPOService service, ICurrentUserService current
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("confirmed-without-invoice")]
|
||||
public async Task<IActionResult> GetConfirmedWithoutInvoice(
|
||||
[FromQuery] Guid? agentId,
|
||||
[FromQuery] Guid? routeId,
|
||||
[FromQuery] Guid? shopId)
|
||||
{
|
||||
var result = await service.GetConfirmedWithoutInvoiceAsync(agentId, routeId, shopId);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create(CreateShopPORequest request, CancellationToken ct)
|
||||
{
|
||||
|
||||
@@ -2,13 +2,18 @@
|
||||
|
||||
namespace Distribution.Features.ShopPO;
|
||||
|
||||
public record CreateShopPORequest(Guid StoreId, List<ShopPOItemRequest> Items, Guid? RaisedByUserId = null);
|
||||
public record ShopPOItemRequest(Guid ItemId, int Quantity, decimal? UnitPrice = null);
|
||||
public record UpdateShopPOItemsRequest(List<ShopPOItemRequest> Items);
|
||||
// DiscountSchemeIds: the FlatPercent/SlabDiscount scheme the user accepted (at most one). Null = leave as is.
|
||||
public record CreateShopPORequest(Guid StoreId, List<ShopPOItemRequest> Items, Guid? RaisedByUserId = null, List<Guid>? DiscountSchemeIds = null);
|
||||
// IsFree + SchemeId mark a free-issue line the user accepted from a scheme suggestion; its price is forced to 0.
|
||||
public record ShopPOItemRequest(Guid ItemId, int Quantity, decimal? UnitPrice = null, bool IsFree = false, Guid? SchemeId = null);
|
||||
public record UpdateShopPOItemsRequest(List<ShopPOItemRequest> Items, List<Guid>? DiscountSchemeIds = null);
|
||||
public record ConfirmShopPORequest(Guid? ConfirmedByUserId = null);
|
||||
public record CancelShopPORequest(string Reason, Guid? CancelledByUserId = null);
|
||||
|
||||
public record ShopPOItemResponse(Guid Id, Guid ItemId, string ItemName, int Quantity, int QuantityFulfilled, decimal UnitPrice, bool IsActive);
|
||||
public record ShopPOItemResponse(Guid Id, Guid ItemId, string ItemName, int Quantity, int QuantityFulfilled, decimal UnitPrice, bool IsActive,
|
||||
bool IsFree = false, Guid? SchemeId = null, string? SchemeName = null);
|
||||
|
||||
public record ShopPODiscountResponse(Guid SchemeId, string SchemeName, decimal Amount);
|
||||
|
||||
public record ShopPOResponse(
|
||||
Guid Id,
|
||||
@@ -34,4 +39,5 @@ public record ShopPOResponse(
|
||||
DateTime? CancelledAt = null,
|
||||
string? CancelReason = null,
|
||||
string? CancelledByName = null,
|
||||
string? CancelledByRole = null);
|
||||
string? CancelledByRole = null,
|
||||
List<ShopPODiscountResponse>? Discounts = null);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Distribution.Features.Invoice;
|
||||
using Distribution.Features.Notifications;
|
||||
using Distribution.Features.PriceList;
|
||||
using Distribution.Features.Scheme;
|
||||
using Distribution.Features.Stock;
|
||||
using Distribution.Infra.UoW;
|
||||
using Distribution.Models;
|
||||
@@ -10,16 +12,47 @@ public class ShopPOService(
|
||||
IShopPORepository repo,
|
||||
IInvoiceRepository invoiceRepo,
|
||||
PriceListService priceListService,
|
||||
SchemeService schemeService,
|
||||
INotificationService notifications,
|
||||
IUnitOfWork uow)
|
||||
{
|
||||
private static ShopPOResponse ToResponse(Models.ShopPO p, Dictionary<Guid, (string Name, string Role)>? users = null) =>
|
||||
// Paid lines are priced from the request or the price list; accepted free-issue lines are always priced 0.
|
||||
private async Task<ShopPOItem> BuildLineAsync(ShopPOItemRequest line, Guid storeId)
|
||||
{
|
||||
if (line.IsFree)
|
||||
return new ShopPOItem
|
||||
{
|
||||
FK_ItemId = line.ItemId,
|
||||
Quantity = line.Quantity,
|
||||
UnitPrice = 0m,
|
||||
IsFree = true,
|
||||
FK_SchemeId = line.SchemeId
|
||||
};
|
||||
|
||||
var unitPrice = line.UnitPrice
|
||||
?? (await priceListService.ResolveByStoreAsync(line.ItemId, storeId, DateTime.Now))?.Price
|
||||
?? throw new InvalidOperationException($"No resolvable price for item {line.ItemId}");
|
||||
|
||||
return new ShopPOItem { FK_ItemId = line.ItemId, Quantity = line.Quantity, UnitPrice = unitPrice };
|
||||
}
|
||||
|
||||
// Tells the person who raised the order (and their supervisor) what happened to it, unless they did it themselves.
|
||||
private Task NotifyOrderAsync(string type, Models.ShopPO po, Guid actingUserId, string? reason, CancellationToken ct) =>
|
||||
notifications.NotifyAsync(
|
||||
type,
|
||||
[RecipientRule.Users(po.RaisedById), RecipientRule.ReportsTo(po.RaisedById)],
|
||||
new NotificationArgs(new { StoreName = po.Store?.StoreName, Reason = reason }, "ShopPO", po.Id, "/sales/orders", actingUserId),
|
||||
ct);
|
||||
|
||||
private static ShopPOResponse ToResponse(Models.ShopPO p, Dictionary<Guid, (string Name, string Role)>? users = null, List<ShopPODiscountResponse>? discounts = null) =>
|
||||
new(p.Id, p.FK_StoreId, p.Store?.StoreName ?? string.Empty,
|
||||
p.Store?.StoreAddress, p.Store?.ContactNumber,
|
||||
p.RaisedById, p.InvoiceId, p.TotalAmount, p.Status,
|
||||
p.ConfirmedAt, p.ConfirmedBy, p.FulfilledAt,
|
||||
p.Items.Where(i => i.IsActive).Select(i =>
|
||||
new ShopPOItemResponse(i.Id, i.FK_ItemId, i.Item?.Name ?? string.Empty,
|
||||
i.Quantity, i.QuantityFulfilled, i.UnitPrice, i.IsActive)).ToList(),
|
||||
i.Quantity, i.QuantityFulfilled, i.UnitPrice, i.IsActive,
|
||||
i.IsFree, i.FK_SchemeId, i.SchemeName)).ToList(),
|
||||
p.IsActive, p.CreatedAt,
|
||||
users != null && users.TryGetValue(p.RaisedById, out var u) ? u.Name : null,
|
||||
users != null && users.TryGetValue(p.RaisedById, out var r) ? r.Role : null,
|
||||
@@ -27,7 +60,8 @@ public class ShopPOService(
|
||||
users != null && p.ConfirmedBy is Guid cb2 && users.TryGetValue(cb2, out var c2) ? c2.Role : null,
|
||||
p.CancelledBy, p.CancelledAt, p.CancelReason,
|
||||
users != null && p.CancelledBy is Guid xb && users.TryGetValue(xb, out var x) ? x.Name : null,
|
||||
users != null && p.CancelledBy is Guid xb2 && users.TryGetValue(xb2, out var x2) ? x2.Role : null);
|
||||
users != null && p.CancelledBy is Guid xb2 && users.TryGetValue(xb2, out var x2) ? x2.Role : null,
|
||||
discounts ?? []);
|
||||
|
||||
private static IEnumerable<Guid> UserIds(Models.ShopPO p)
|
||||
{
|
||||
@@ -52,7 +86,14 @@ public class ShopPOService(
|
||||
var po = await repo.GetByIdAsync(id);
|
||||
if (po is null) return null;
|
||||
var users = await repo.GetUserInfoAsync(UserIds(po));
|
||||
return ToResponse(po, users);
|
||||
return ToResponse(po, users, await schemeService.GetDiscountsForShopPOAsync(po.Id));
|
||||
}
|
||||
|
||||
public async Task<List<ShopPOResponse>> GetConfirmedWithoutInvoiceAsync(Guid? agentId, Guid? routeId, Guid? shopId)
|
||||
{
|
||||
var items = await repo.GetConfirmedWithoutInvoiceAsync(agentId, routeId, shopId);
|
||||
var users = await repo.GetUserInfoAsync(items.SelectMany(UserIds));
|
||||
return items.Select(p => ToResponse(p, users)).ToList();
|
||||
}
|
||||
|
||||
public async Task<ShopPOResponse> CreateAsync(CreateShopPORequest request, Guid raisedById, CancellationToken ct = default)
|
||||
@@ -65,20 +106,14 @@ public class ShopPOService(
|
||||
|
||||
foreach (var line in request.Items)
|
||||
{
|
||||
var unitPrice = line.UnitPrice
|
||||
?? (await priceListService.ResolveByStoreAsync(line.ItemId, request.StoreId, DateTime.Now))?.Price
|
||||
?? throw new InvalidOperationException($"No resolvable price for item {line.ItemId}");
|
||||
|
||||
items.Add(new ShopPOItem
|
||||
{
|
||||
FK_ItemId = line.ItemId,
|
||||
Quantity = line.Quantity,
|
||||
QuantityFulfilled = 0,
|
||||
UnitPrice = unitPrice
|
||||
});
|
||||
total += line.Quantity * unitPrice;
|
||||
var item = await BuildLineAsync(line, request.StoreId);
|
||||
items.Add(item);
|
||||
total += item.Quantity * item.UnitPrice;
|
||||
}
|
||||
|
||||
await schemeService.ValidateFreeLinesAsync(request.StoreId, DateTime.Now, items);
|
||||
await schemeService.ValidateDiscountSchemesAsync(request.StoreId, DateTime.Now, items, request.DiscountSchemeIds);
|
||||
|
||||
var po = new Models.ShopPO
|
||||
{
|
||||
FK_StoreId = request.StoreId,
|
||||
@@ -93,6 +128,7 @@ public class ShopPOService(
|
||||
await repo.AddAsync(po);
|
||||
foreach (var item in items) item.FK_ShopPOId = po.Id;
|
||||
await repo.AddItemsAsync(items);
|
||||
await schemeService.SyncRedemptionsAsync(po.Id, items, request.DiscountSchemeIds);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
@@ -120,27 +156,22 @@ public class ShopPOService(
|
||||
|
||||
foreach (var line in request.Items)
|
||||
{
|
||||
var unitPrice = line.UnitPrice
|
||||
?? (await priceListService.ResolveByStoreAsync(line.ItemId, po.FK_StoreId, DateTime.Now))?.Price
|
||||
?? throw new InvalidOperationException($"No resolvable price for item {line.ItemId}");
|
||||
|
||||
newItems.Add(new ShopPOItem
|
||||
{
|
||||
FK_ShopPOId = id,
|
||||
FK_ItemId = line.ItemId,
|
||||
Quantity = line.Quantity,
|
||||
QuantityFulfilled = 0,
|
||||
UnitPrice = unitPrice
|
||||
});
|
||||
total += line.Quantity * unitPrice;
|
||||
var item = await BuildLineAsync(line, po.FK_StoreId);
|
||||
item.FK_ShopPOId = id;
|
||||
newItems.Add(item);
|
||||
total += item.Quantity * item.UnitPrice;
|
||||
}
|
||||
|
||||
await schemeService.ValidateFreeLinesAsync(po.FK_StoreId, po.CreatedAt, newItems);
|
||||
await schemeService.ValidateDiscountSchemesAsync(po.FK_StoreId, po.CreatedAt, newItems, request.DiscountSchemeIds);
|
||||
|
||||
po.TotalAmount = total;
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
await repo.ReplaceItemsAsync(id, newItems);
|
||||
await schemeService.SyncRedemptionsAsync(id, newItems, request.DiscountSchemeIds);
|
||||
repo.Update(po);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
@@ -168,6 +199,8 @@ public class ShopPOService(
|
||||
try { repo.Update(po); await uow.CommitAsync(ct); }
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
await NotifyOrderAsync(NotificationTypes.ShopPOConfirmed, po, confirmedBy, null, ct);
|
||||
|
||||
return await ToResponseWithUserAsync(po);
|
||||
}
|
||||
|
||||
@@ -193,17 +226,25 @@ public class ShopPOService(
|
||||
CreatedBy = userId,
|
||||
TotalAmount = po.TotalAmount
|
||||
};
|
||||
// Start the invoice at the discounted total so it matches what print/finalize will recompute.
|
||||
invoice.DiscountAmount = Math.Min(await invoiceRepo.GetSchemeDiscountAsync(po.Id), po.TotalAmount);
|
||||
invoice.TotalAmount = po.TotalAmount - invoice.DiscountAmount;
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
await invoiceRepo.AddAsync(invoice);
|
||||
var lines = InvoiceService.BuildItemsFromShopPO(po, invoice.Id);
|
||||
await invoiceRepo.AddItemsAsync(lines);
|
||||
await invoiceRepo.AddVersionAsync(InvoiceService.BuildVersion(invoice, lines, 1, InvoiceVersionStage.Created, userId));
|
||||
po.InvoiceId = invoice.Id;
|
||||
repo.Update(po);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
await NotifyOrderAsync(NotificationTypes.ShopPOConfirmed, po, userId, null, ct);
|
||||
|
||||
var saved = await repo.GetByIdAsync(id);
|
||||
return await ToResponseWithUserAsync(saved!);
|
||||
}
|
||||
@@ -231,6 +272,8 @@ public class ShopPOService(
|
||||
try { repo.Update(po); await uow.CommitAsync(ct); }
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
await NotifyOrderAsync(NotificationTypes.ShopPOCancelled, po, cancelledBy, po.CancelReason, ct);
|
||||
|
||||
return await ToResponseWithUserAsync(po);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
using Distribution.Features.Notifications;
|
||||
using Distribution.Features.Scheme;
|
||||
using Distribution.Features.Stock;
|
||||
using Distribution.Features.StoreCredit;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Infra.UoW;
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.Store;
|
||||
|
||||
public class StoreService(IStoreRepository repo, IUnitOfWork uow, ICurrentUserService currentUser, IScopeAccessService scopeAccess, SchemeService schemeService, Route.IRouteRepository routeRepo)
|
||||
public class StoreService(IStoreRepository repo, IUnitOfWork uow, ICurrentUserService currentUser, IScopeAccessService scopeAccess, SchemeService schemeService, Route.IRouteRepository routeRepo, INotificationService notifications, IStoreCreditMovementRepository creditRepo)
|
||||
{
|
||||
private const int GlobalScopeRoleLevel = 50;
|
||||
private const int CreditApproverRoleLevel = 20;
|
||||
@@ -147,6 +149,7 @@ public class StoreService(IStoreRepository repo, IUnitOfWork uow, ICurrentUserSe
|
||||
var store = await repo.GetByIdAsync(id);
|
||||
if (store is null || !store.IsActive) return null;
|
||||
if (!await HasStoreScopeAsync(id)) return null;
|
||||
var wasWithinLimit = store.OutstandingBalance <= store.CreditLimit;
|
||||
store.OutstandingBalance = request.NewBalance;
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
@@ -159,6 +162,18 @@ public class StoreService(IStoreRepository repo, IUnitOfWork uow, ICurrentUserSe
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
|
||||
// Only on the crossing, so repeated updates while over the limit don't re-alert.
|
||||
if (store.CreditLimit > 0 && wasWithinLimit && store.OutstandingBalance > store.CreditLimit)
|
||||
await notifications.NotifyAsync(
|
||||
NotificationTypes.CreditLimitBreached,
|
||||
[
|
||||
RecipientRule.RouteOwner(ScopeKind.Store, store.Id),
|
||||
RecipientRule.PermissionInScope("store.manage", ScopeKind.Store, store.Id),
|
||||
],
|
||||
new NotificationArgs(new { store.StoreName }, "Store", store.Id, $"/stores/{store.Id}", currentUser.UserId),
|
||||
ct);
|
||||
|
||||
return ToResponse(store);
|
||||
}
|
||||
|
||||
@@ -167,7 +182,8 @@ public class StoreService(IStoreRepository repo, IUnitOfWork uow, ICurrentUserSe
|
||||
var store = await repo.GetByIdAsync(id);
|
||||
if (store is null || !store.IsActive) return null;
|
||||
if (!await HasStoreScopeAsync(id)) return null;
|
||||
return new OutstandingBalanceResponse(store.Id, store.CreditLimit, store.OutstandingBalance, store.CreditLimit - store.OutstandingBalance);
|
||||
var outstanding = await creditRepo.GetOutstandingForStoreAsync(id);
|
||||
return new OutstandingBalanceResponse(store.Id, store.CreditLimit, outstanding, store.CreditLimit - outstanding);
|
||||
}
|
||||
public async Task<StoreResponse?> ClassifySegmentAsync(Guid id, ClassifySegmentRequest request, CancellationToken ct = default)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.StoreCredit;
|
||||
|
||||
public interface IStoreCreditMovementRepository
|
||||
{
|
||||
Task<decimal> GetOutstandingForStoreAsync(Guid storeId);
|
||||
Task<List<StoreCreditMovement>> GetByStoreAsync(Guid storeId, int page, int pageSize);
|
||||
Task<int> GetCountByStoreAsync(Guid storeId);
|
||||
Task AddAsync(StoreCreditMovement movement);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using Distribution.Data;
|
||||
using Distribution.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.StoreCredit;
|
||||
|
||||
public class StoreCreditMovementRepository(AppDBContext context) : IStoreCreditMovementRepository
|
||||
{
|
||||
public async Task<decimal> GetOutstandingForStoreAsync(Guid storeId)
|
||||
{
|
||||
var usage = await context.StoreCreditMovements
|
||||
.Where(m => m.IsActive && m.FK_StoreId == storeId && m.Direction == StoreCreditMovementDirection.Usage)
|
||||
.SumAsync(m => (decimal?)m.Amount) ?? 0m;
|
||||
|
||||
var settlements = await context.StoreCreditMovements
|
||||
.Where(m => m.IsActive && m.FK_StoreId == storeId && m.Direction == StoreCreditMovementDirection.Settlement)
|
||||
.SumAsync(m => (decimal?)m.Amount) ?? 0m;
|
||||
|
||||
return usage - settlements;
|
||||
}
|
||||
|
||||
public async Task<List<StoreCreditMovement>> GetByStoreAsync(Guid storeId, int page, int pageSize) =>
|
||||
await context.StoreCreditMovements
|
||||
.Where(m => m.IsActive && m.FK_StoreId == storeId)
|
||||
.OrderByDescending(m => m.OccurredAt)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task<int> GetCountByStoreAsync(Guid storeId) =>
|
||||
await context.StoreCreditMovements
|
||||
.CountAsync(m => m.IsActive && m.FK_StoreId == storeId);
|
||||
|
||||
public async Task AddAsync(StoreCreditMovement movement) =>
|
||||
await context.StoreCreditMovements.AddAsync(movement);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Distribution.Features.Grn;
|
||||
using Distribution.Features.Notifications;
|
||||
using Distribution.Features.Po;
|
||||
using Distribution.Infra.UoW;
|
||||
using Distribution.Models;
|
||||
@@ -9,6 +10,7 @@ public class SupplierClaimService(
|
||||
ISupplierClaimRepository repo,
|
||||
IPoRepository poRepo,
|
||||
IGrnRepository grnRepo,
|
||||
INotificationService notifications,
|
||||
IUnitOfWork uow)
|
||||
{
|
||||
private static SupplierClaimItemResponse MapItem(SupplierClaimItem i) =>
|
||||
@@ -149,9 +151,19 @@ public class SupplierClaimService(
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
await NotifyDecisionAsync(NotificationTypes.SupplierClaimApproved, claim, approverId, ct);
|
||||
|
||||
return Map(claim);
|
||||
}
|
||||
|
||||
// Tells whoever raised the claim how it was decided (never the person who decided it).
|
||||
private Task NotifyDecisionAsync(string type, SupplierClaim claim, Guid decidedBy, CancellationToken ct) =>
|
||||
notifications.NotifyAsync(
|
||||
type,
|
||||
[RecipientRule.Users(claim.RaisedByUserId)],
|
||||
new NotificationArgs(null, "SupplierClaim", claim.Id, "/returns-claims/supplier-claims", decidedBy),
|
||||
ct);
|
||||
|
||||
public async Task<SupplierClaimResponse> RejectAsync(Guid id, RejectSupplierClaimRequest request, Guid approverId, CancellationToken ct = default)
|
||||
{
|
||||
var claim = await repo.GetByIdAsync(id)
|
||||
@@ -172,6 +184,8 @@ public class SupplierClaimService(
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
await NotifyDecisionAsync(NotificationTypes.SupplierClaimRejected, claim, approverId, ct);
|
||||
|
||||
return Map(claim);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.SupplierPayment;
|
||||
|
||||
public interface ISupplierPaymentRepository
|
||||
{
|
||||
Task<Models.SupplierPayment?> GetByIdAsync(Guid id);
|
||||
Task<List<Models.SupplierPayment>> ListBySupplierAsync(Guid supplierId, int page, int pageSize);
|
||||
Task<int> CountBySupplierAsync(Guid supplierId);
|
||||
Task AddAsync(Models.SupplierPayment payment);
|
||||
|
||||
/// <summary>Sum of currently-allocated amounts to a GRN, across all active SupplierPayments.</summary>
|
||||
Task<decimal> GetAllocatedTotalForGrnAsync(Guid goodsReceiptId);
|
||||
|
||||
/// <summary>Load a GRN with its items so the total can be computed.</summary>
|
||||
Task<Models.GoodsReceipt?> GetGrnWithItemsAsync(Guid goodsReceiptId);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.SupplierPayment;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/suppliers/{supplierId:guid}/payments")]
|
||||
public class SupplierPaymentController(SupplierPaymentService service) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> List(Guid supplierId, [FromQuery] int page = 1, [FromQuery] int pageSize = 20)
|
||||
{
|
||||
var result = await service.ListBySupplierAsync(supplierId, page, pageSize);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create(Guid supplierId, CreateSupplierPaymentRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await service.CreateAsync(supplierId, request, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.SupplierPayment;
|
||||
|
||||
public record SupplierPaymentAllocationRequest(
|
||||
Guid GoodsReceiptId,
|
||||
decimal AllocatedAmount);
|
||||
|
||||
public record CreateSupplierPaymentRequest(
|
||||
decimal Amount,
|
||||
PaymentMethod Method,
|
||||
// One or more GRNs this payment covers. Sum of AllocatedAmount must equal Amount.
|
||||
List<SupplierPaymentAllocationRequest> Allocations,
|
||||
// Cash: required.
|
||||
Guid? BankAccountId = null,
|
||||
// Cheque: required — an Unused page from an Active cheque book.
|
||||
Guid? ChequePageId = null,
|
||||
string? Notes = null,
|
||||
Guid? PaidByUserId = null);
|
||||
|
||||
public record SupplierPaymentAllocationResponse(
|
||||
Guid GoodsReceiptId,
|
||||
decimal AllocatedAmount);
|
||||
|
||||
public record SupplierPaymentResponse(
|
||||
Guid Id,
|
||||
Guid SupplierId,
|
||||
decimal Amount,
|
||||
PaymentMethod Method,
|
||||
Guid? BankAccountId,
|
||||
Guid? ChequePageId,
|
||||
string? ChequeNo,
|
||||
DateTime PaidAt,
|
||||
Guid? PaidByUserId,
|
||||
string? Notes,
|
||||
List<SupplierPaymentAllocationResponse> Allocations,
|
||||
DateTime CreatedAt);
|
||||
|
||||
public record PagedSupplierPaymentsResponse(
|
||||
List<SupplierPaymentResponse> Items,
|
||||
int Page,
|
||||
int PageSize,
|
||||
int Total);
|
||||
@@ -0,0 +1,43 @@
|
||||
using Distribution.Data;
|
||||
using Distribution.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.SupplierPayment;
|
||||
|
||||
public class SupplierPaymentRepository(AppDBContext context) : ISupplierPaymentRepository
|
||||
{
|
||||
public async Task<Models.SupplierPayment?> GetByIdAsync(Guid id) =>
|
||||
await context.SupplierPayments
|
||||
.Include(p => p.ChequePage)
|
||||
.Include(p => p.Allocations)
|
||||
.FirstOrDefaultAsync(p => p.Id == id);
|
||||
|
||||
public async Task<List<Models.SupplierPayment>> ListBySupplierAsync(Guid supplierId, int page, int pageSize) =>
|
||||
await context.SupplierPayments
|
||||
.Include(p => p.ChequePage)
|
||||
.Include(p => p.Allocations)
|
||||
.Where(p => p.FK_SupplierId == supplierId && p.IsActive)
|
||||
.OrderByDescending(p => p.PaidAt)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task<int> CountBySupplierAsync(Guid supplierId) =>
|
||||
await context.SupplierPayments
|
||||
.CountAsync(p => p.FK_SupplierId == supplierId && p.IsActive);
|
||||
|
||||
public async Task AddAsync(Models.SupplierPayment payment) =>
|
||||
await context.SupplierPayments.AddAsync(payment);
|
||||
|
||||
public async Task<decimal> GetAllocatedTotalForGrnAsync(Guid goodsReceiptId) =>
|
||||
await context.SupplierPaymentGRNs
|
||||
.Where(a => a.FK_GoodsReceiptId == goodsReceiptId
|
||||
&& a.IsActive
|
||||
&& a.SupplierPayment!.IsActive)
|
||||
.SumAsync(a => (decimal?)a.AllocatedAmount) ?? 0m;
|
||||
|
||||
public async Task<Models.GoodsReceipt?> GetGrnWithItemsAsync(Guid goodsReceiptId) =>
|
||||
await context.GoodsReceipts
|
||||
.Include(g => g.Items)
|
||||
.FirstOrDefaultAsync(g => g.Id == goodsReceiptId && g.IsActive);
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
using Distribution.Data;
|
||||
using Distribution.Features.BankAccount;
|
||||
using Distribution.Features.ChequeBook;
|
||||
using Distribution.Features.Grn;
|
||||
using Distribution.Features.Supplier;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Infra.UoW;
|
||||
using Distribution.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.SupplierPayment;
|
||||
|
||||
public class SupplierPaymentService(
|
||||
AppDBContext context,
|
||||
ISupplierPaymentRepository repo,
|
||||
ISupplierRepository supplierRepo,
|
||||
IBankAccountRepository bankAccountRepo,
|
||||
IChequeBookRepository chequeBookRepo,
|
||||
IUnitOfWork uow,
|
||||
ICurrentUserService currentUser)
|
||||
{
|
||||
private static SupplierPaymentResponse ToResponse(Models.SupplierPayment p) =>
|
||||
new(p.Id, p.FK_SupplierId, p.Amount, p.Method,
|
||||
p.FK_BankAccountId, p.FK_ChequePageId, p.ChequePage?.ChequeNo,
|
||||
p.PaidAt, p.PaidByUserId, p.Notes,
|
||||
p.Allocations
|
||||
.Where(a => a.IsActive)
|
||||
.Select(a => new SupplierPaymentAllocationResponse(a.FK_GoodsReceiptId, a.AllocatedAmount))
|
||||
.ToList(),
|
||||
p.CreatedAt);
|
||||
|
||||
/// <summary>GRN total = sum per line of (gross - discount + NBT + VAT). Rejected qty never costs.</summary>
|
||||
private static decimal GrnTotal(Models.GoodsReceipt grn) =>
|
||||
grn.Items.Where(i => i.IsActive).Sum(i =>
|
||||
GrnPricing.Line(i.AcceptedQuantity, i.UnitCost, i.DiscountPercent, i.NbtPercent, i.VatPercent).Total);
|
||||
|
||||
public async Task<PagedSupplierPaymentsResponse?> ListBySupplierAsync(Guid supplierId, int page, int pageSize)
|
||||
{
|
||||
var supplier = await supplierRepo.GetByIdAsync(supplierId);
|
||||
if (supplier is null) return null;
|
||||
|
||||
if (page < 1) page = 1;
|
||||
if (pageSize < 1) pageSize = 20;
|
||||
if (pageSize > 200) pageSize = 200;
|
||||
|
||||
var payments = await repo.ListBySupplierAsync(supplierId, page, pageSize);
|
||||
var total = await repo.CountBySupplierAsync(supplierId);
|
||||
return new PagedSupplierPaymentsResponse(payments.Select(ToResponse).ToList(), page, pageSize, total);
|
||||
}
|
||||
|
||||
public async Task<SupplierPaymentResponse?> CreateAsync(Guid supplierId, CreateSupplierPaymentRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var supplier = await supplierRepo.GetByIdAsync(supplierId);
|
||||
if (supplier is null) return null;
|
||||
|
||||
if (request.Amount <= 0)
|
||||
throw new InvalidOperationException("Amount must be greater than zero");
|
||||
if (request.Method == PaymentMethod.Credit)
|
||||
throw new InvalidOperationException("Credit is not a valid outgoing payment method");
|
||||
|
||||
if (request.Allocations is null || request.Allocations.Count == 0)
|
||||
throw new InvalidOperationException("At least one GRN allocation is required");
|
||||
|
||||
var duplicateGrn = request.Allocations.GroupBy(a => a.GoodsReceiptId).FirstOrDefault(g => g.Count() > 1);
|
||||
if (duplicateGrn is not null)
|
||||
throw new InvalidOperationException($"GRN {duplicateGrn.Key} is allocated more than once");
|
||||
|
||||
if (request.Allocations.Any(a => a.AllocatedAmount <= 0))
|
||||
throw new InvalidOperationException("Each allocation amount must be greater than zero");
|
||||
|
||||
var allocationSum = request.Allocations.Sum(a => a.AllocatedAmount);
|
||||
if (allocationSum != request.Amount)
|
||||
throw new InvalidOperationException($"Allocations sum ({allocationSum}) must equal Amount ({request.Amount})");
|
||||
|
||||
// Validate each GRN exists, belongs to this supplier, and the new allocation does not push cumulative paid over the GRN total.
|
||||
foreach (var alloc in request.Allocations)
|
||||
{
|
||||
var grn = await repo.GetGrnWithItemsAsync(alloc.GoodsReceiptId)
|
||||
?? throw new InvalidOperationException($"GRN {alloc.GoodsReceiptId} not found");
|
||||
|
||||
var grnSupplierId = grn.FK_SupplierId
|
||||
?? grn.PurchaseOrder?.FK_SupplierId;
|
||||
if (grnSupplierId is null)
|
||||
{
|
||||
// PO-based GRN without the PO loaded — re-query to confirm supplier match.
|
||||
var po = await context.PurchaseOrders.FindAsync([grn.FK_PurchaseOrderId], ct);
|
||||
grnSupplierId = po?.FK_SupplierId;
|
||||
}
|
||||
if (grnSupplierId != supplierId)
|
||||
throw new InvalidOperationException($"GRN {alloc.GoodsReceiptId} does not belong to the specified supplier");
|
||||
|
||||
var grnTotal = GrnTotal(grn);
|
||||
var alreadyAllocated = await repo.GetAllocatedTotalForGrnAsync(grn.Id);
|
||||
if (alreadyAllocated + alloc.AllocatedAmount > grnTotal)
|
||||
throw new InvalidOperationException(
|
||||
$"Allocation to GRN {grn.Id} ({alloc.AllocatedAmount}) exceeds outstanding balance " +
|
||||
$"(GRN total {grnTotal}, already paid {alreadyAllocated})");
|
||||
}
|
||||
|
||||
var payerId = currentUser.UserId ?? request.PaidByUserId;
|
||||
|
||||
var payment = new Models.SupplierPayment
|
||||
{
|
||||
FK_SupplierId = supplierId,
|
||||
Amount = request.Amount,
|
||||
Method = request.Method,
|
||||
PaidAt = DateTime.Now,
|
||||
PaidByUserId = payerId,
|
||||
Notes = request.Notes,
|
||||
Allocations = request.Allocations.Select(a => new Models.SupplierPaymentGRN
|
||||
{
|
||||
FK_GoodsReceiptId = a.GoodsReceiptId,
|
||||
AllocatedAmount = a.AllocatedAmount
|
||||
}).ToList()
|
||||
};
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
if (request.Method == PaymentMethod.Cash)
|
||||
{
|
||||
if (request.BankAccountId is null)
|
||||
throw new InvalidOperationException("BankAccountId is required when Method is Cash");
|
||||
|
||||
var account = await bankAccountRepo.GetByIdAsync(request.BankAccountId.Value);
|
||||
if (account is null)
|
||||
throw new InvalidOperationException("BankAccountId does not reference a valid account");
|
||||
if (account.IsActive is false)
|
||||
throw new InvalidOperationException("The selected account is inactive");
|
||||
|
||||
payment.FK_BankAccountId = account.Id;
|
||||
}
|
||||
else // Cheque
|
||||
{
|
||||
if (request.ChequePageId is null)
|
||||
throw new InvalidOperationException("ChequePageId is required when Method is Cheque");
|
||||
|
||||
var page = await context.ChequePages
|
||||
.Include(p => p.ChequeBook)
|
||||
.FirstOrDefaultAsync(p => p.Id == request.ChequePageId.Value, ct);
|
||||
|
||||
if (page is null)
|
||||
throw new InvalidOperationException("ChequePageId does not reference a valid cheque page");
|
||||
if (page.IssueStatus != ChequePageIssueStatus.Unused)
|
||||
throw new InvalidOperationException($"Cannot issue a cheque in status '{page.IssueStatus}'");
|
||||
if (page.ChequeBook is null || page.ChequeBook.Status != ChequeBookStatus.Active)
|
||||
throw new InvalidOperationException("The cheque book is not active");
|
||||
|
||||
page.IssueStatus = ChequePageIssueStatus.Issued;
|
||||
page.PayeeType = PayeeType.Supplier;
|
||||
page.PayeeName = supplier.Name;
|
||||
page.IssueDate = DateTime.Now;
|
||||
page.Amount = request.Amount;
|
||||
page.CurrencyCode = "LKR";
|
||||
page.PrintedAt = DateTime.Now;
|
||||
|
||||
chequeBookRepo.UpdatePage(page);
|
||||
payment.FK_ChequePageId = page.Id;
|
||||
payment.ChequePage = page;
|
||||
}
|
||||
|
||||
await repo.AddAsync(payment);
|
||||
|
||||
// TODO ledger: when accounting is wired in, post a journal entry here —
|
||||
// Debit Accounts Payable (supplier)
|
||||
// Credit Cash/Bank (BankAccount) OR Cheques-outstanding (for Cheque payments)
|
||||
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return ToResponse(payment);
|
||||
}
|
||||
}
|
||||
+5546
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,926 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Distribution.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddPaymentServiceModule : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "BankAccounts",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
AccountType = table.Column<int>(type: "int", nullable: false),
|
||||
AccountName = table.Column<string>(type: "varchar(200)", maxLength: 200, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
BankName = table.Column<string>(type: "varchar(200)", maxLength: 200, nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
CashAccountTypeName = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
AccountNumber = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
CurrencyCode = table.Column<string>(type: "varchar(10)", maxLength: 10, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
IsDefault = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_BankAccounts", x => x.Id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ChequeBooks",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
ChequeBookNo = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
BranchId = table.Column<int>(type: "int", nullable: false),
|
||||
FK_BankAccountId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
StartChequeNo = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
EndChequeNo = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
TotalLeaves = table.Column<int>(type: "int", nullable: false),
|
||||
ReceivedDate = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
Description = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
CreatedBy = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Status = table.Column<int>(type: "int", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ChequeBooks", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ChequeBooks_BankAccounts_FK_BankAccountId",
|
||||
column: x => x.FK_BankAccountId,
|
||||
principalTable: "BankAccounts",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ReceivedCheques",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
CompanyId = table.Column<int>(type: "int", nullable: false),
|
||||
BranchId = table.Column<int>(type: "int", nullable: true),
|
||||
ReceivedFromType = table.Column<int>(type: "int", nullable: false),
|
||||
ReceivedFromId = table.Column<int>(type: "int", nullable: true),
|
||||
ReceivedFromName = table.Column<string>(type: "varchar(200)", maxLength: 200, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
DrawerBankName = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
DrawerBankBranch = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
AccountHolderName = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
ChequeNo = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
ChequeDate = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
Amount = table.Column<decimal>(type: "decimal(65,30)", nullable: false),
|
||||
ReceivedDate = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
ReferenceType = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
ReferenceId = table.Column<int>(type: "int", nullable: true),
|
||||
Notes = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
CreatedBy = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Status = table.Column<int>(type: "int", nullable: false),
|
||||
FK_DepositBankAccountId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
|
||||
DepositDate = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ReceivedCheques", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ReceivedCheques_BankAccounts_FK_DepositBankAccountId",
|
||||
column: x => x.FK_DepositBankAccountId,
|
||||
principalTable: "BankAccounts",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ChequePages",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
ChequeNo = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
FK_ChequeBookId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
IssueStatus = table.Column<int>(type: "int", nullable: false),
|
||||
PayeeType = table.Column<int>(type: "int", nullable: true),
|
||||
PayeeId = table.Column<int>(type: "int", nullable: true),
|
||||
PayeeName = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
IssueDate = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||
Amount = table.Column<decimal>(type: "decimal(65,30)", nullable: true),
|
||||
CurrencyCode = table.Column<string>(type: "varchar(10)", maxLength: 10, nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
VoucherId = table.Column<int>(type: "int", nullable: true),
|
||||
ReferenceNo = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Purpose = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
IsCrossCheque = table.Column<bool>(type: "tinyint(1)", nullable: true),
|
||||
IsAccountPayee = table.Column<bool>(type: "tinyint(1)", nullable: true),
|
||||
IsPostDated = table.Column<bool>(type: "tinyint(1)", nullable: true),
|
||||
Notes = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
PrintedBy = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
PrintedAt = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||
ClearedDate = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||
ClearedByBank = table.Column<bool>(type: "tinyint(1)", nullable: true),
|
||||
CancelReason = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ChequePages", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ChequePages_ChequeBooks_FK_ChequeBookId",
|
||||
column: x => x.FK_ChequeBookId,
|
||||
principalTable: "ChequeBooks",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000001"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("8c43c584-0f65-496d-a77c-a7df46bb175d"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000002"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("25576a9d-38cb-4ff5-b8ca-08df43d8a5a6"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000003"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("e47016c1-a014-476f-ba87-4b3d438de62a"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000004"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("a24c0e9e-0194-4f96-823c-bdbdcd122d85"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000005"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("e76ef6e2-67ba-462f-a954-b1dbc5a95d8c"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000006"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("bba4fdaa-fd0e-43dc-aedf-331212bd06cd"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000007"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("bc66d450-af6b-426e-9d05-266ceba21fac"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000008"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("9ee2196c-1e4b-452d-aaf6-7c55e3d2be2b"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000009"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("63508590-7a30-4640-9fc9-d3d54978f581"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000010"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("82802a3f-23bc-45db-a531-d7e880dd7970"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000011"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("65ccf5b6-754a-431a-81bf-d8e0587c956e"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000012"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("6c713413-362b-475a-846b-9f3382cf7a35"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000013"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("6dfe68ff-f5a3-41a8-981b-ac108a81408e"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000014"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("09c6b812-dc14-44a2-9362-429dfef3942d"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000015"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("bf29a1bb-234e-47a3-922f-f0fb3a1daba0"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000016"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("0d2f3497-4852-453e-a451-275e945b6c1d"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000017"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("41c13dbc-4f51-4134-891c-b6955f728d65"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000018"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("29bc0291-7a91-4fc5-840b-9420b64b86fc"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000019"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("8ae0c386-d206-4580-890f-c1673c1dfeb2"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000020"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("5db4bd09-9481-4608-9dec-3412ba7f3f69"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000021"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("c1840bd7-88fd-496a-8d88-aef3d886e1ec"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000022"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("af97d659-c040-4fdc-beeb-62fc8334f95c"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000023"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("a6443517-3c2a-4407-a3bd-5497e053b0d6"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000024"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("2f540d56-bff4-41fe-9181-4a423c1da51c"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000025"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("db18cfb8-a56c-46bc-baff-298e6204c455"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000026"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("fe209c12-1189-485b-920e-ce6db3c132b9"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000027"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("fedef395-394d-4237-b5aa-8254603ed304"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000028"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("7cc47626-65b8-44b0-83fb-a5dff96c39f6"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000029"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("cf04aea5-3f47-4717-95aa-8dab547f9cfb"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000030"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("c2906d62-9eea-4d08-b904-86a3f8b04a20"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("7a546bf9-c2f3-4d23-a38e-a068da53dfeb"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("19c916c3-7b17-42e0-b4b6-89b8cb6475f4"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("e6805dbe-a931-4e2a-b08f-83e8760d4968"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000034"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("7d991f5c-16db-46c5-863c-93063c2e7382"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("513c50d0-5d7b-426d-beef-f14bdea626bc"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000036"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("638a6a60-25da-425c-bdec-683431c26d62"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("b00bfae6-92bb-48cb-99d9-f2814baae47d"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000038"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("6562c9b4-0673-4ce0-8805-03734bc889ce"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000039"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("6601e0fe-83dd-4307-be91-38dae998cc9c"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000040"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("c4848449-9678-4fa6-8a00-db4e504b57d6"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000041"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("91c02c24-12a3-48d5-9d19-174c8e764152"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000042"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("0f00477f-5120-40ba-a0e6-0406e47d1c80"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000043"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("db135a10-6335-478d-b2f8-3b5934cd142b"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000044"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("5c219dd9-9ff3-4541-a708-cc7e4bd4dd25"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000045"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("086e7b93-079d-49d0-90e9-95899b53be52"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000046"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("f0b3d172-cb32-4606-8272-00feaf4067a2"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000047"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("75a882c0-b2e9-4efa-a64c-b5d530d42194"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000048"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("03540f63-d811-4436-869f-2f76995bbb19"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000049"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("73894d9f-a823-4b5b-b809-5cc0aa716a70"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000050"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("1ad4bfb8-cde6-4623-a21d-9a78af0026d6"));
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BankAccounts_AccountType_AccountName",
|
||||
table: "BankAccounts",
|
||||
columns: new[] { "AccountType", "AccountName" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ChequeBooks_ChequeBookNo",
|
||||
table: "ChequeBooks",
|
||||
column: "ChequeBookNo",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ChequeBooks_FK_BankAccountId",
|
||||
table: "ChequeBooks",
|
||||
column: "FK_BankAccountId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ChequePages_ChequeNo",
|
||||
table: "ChequePages",
|
||||
column: "ChequeNo",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ChequePages_FK_ChequeBookId",
|
||||
table: "ChequePages",
|
||||
column: "FK_ChequeBookId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ReceivedCheques_ChequeNo",
|
||||
table: "ReceivedCheques",
|
||||
column: "ChequeNo");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ReceivedCheques_FK_DepositBankAccountId",
|
||||
table: "ReceivedCheques",
|
||||
column: "FK_DepositBankAccountId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ChequePages");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ReceivedCheques");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ChequeBooks");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "BankAccounts");
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000001"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("db785a23-5337-4fc8-b460-80c30b707d79"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000002"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("b07b73f9-2c8b-4dff-af5c-42622b026321"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000003"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("2e867ba5-2739-4ce4-b8d7-25b14ed392c1"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000004"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("203d855d-f2d0-43d0-9d4f-9c4d0e8e7566"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000005"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("edd2a0ee-1ca9-47ff-9df6-7254929aef80"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000006"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("644b5ee5-f2c5-4187-a3d4-322cdd5c95d7"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000007"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("70fdf921-f3fd-4e66-bfd2-cb7bb156fa34"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000008"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("e2de5341-4146-4c84-a584-8788774c14df"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000009"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("ece07436-4a0b-435c-888b-a22c14612dc4"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000010"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("b72f8737-7a21-48c0-809a-bc0d3fdd7063"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000011"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("fe1efed0-271f-45e3-a58b-244f1115246f"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000012"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("1b58e809-e4a0-4108-af6e-fd0dac7b3163"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000013"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("881e8e67-e2ee-47be-94f7-77da73a05365"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000014"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("e5d0baa8-9def-4b81-8727-5886b2906eaf"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000015"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("a46ec771-5e79-4595-bb40-91e8a954a792"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000016"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("ef8b1808-3b14-43a8-ba75-30551e643cc5"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000017"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("50142904-8d40-424b-aeb3-53e8cb110445"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000018"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("4bd67f53-7f84-492c-83f9-fd6488fa88b7"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000019"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("d9a0eb59-600e-47e9-8e07-8b7bffc93f8b"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000020"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("e5a9d4da-9497-4e7e-8fb9-796927cbb510"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000021"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("ffa29833-505e-4f56-9a4d-58f5df844af2"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000022"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("4e54994b-7ce1-4b99-aa98-de00c1d205fa"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000023"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("f45b7352-f42a-4bb3-9d1e-d7cbe2665c0c"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000024"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("27744965-334b-4da2-8a2d-5d7433d11b9d"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000025"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("cec1991b-ea93-45b0-b380-01cf0d36fc17"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000026"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("25675bb4-2ab5-437c-bee8-1b1ec1741736"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000027"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("5fc66d40-1960-4028-af48-cf51a80bff04"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000028"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("bd4e1740-a8b6-4a53-8633-a68c9858b3f5"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000029"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("9de97081-77d7-43c3-90b9-af3bc9d4bbef"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000030"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("6ae05e16-95e5-4d43-bfa0-56991393d76f"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("eb096aea-92c9-4556-ba4f-3084da223c7d"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("6c9dac72-200a-46eb-b49e-699b1ca775b5"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("8c8d0c74-40f6-4164-a6a7-6d2374ef162a"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000034"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("4a0d8cea-c33d-45a6-bd42-d8a51ce321c8"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("a0ea0280-8e40-45bc-88e4-c3feb92e36d4"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000036"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("61da4c55-4f19-4ce4-bfc9-3fa9073365a2"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("fbab8f2e-f224-42a0-be16-b7ea4e24eec5"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000038"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("bcf7f883-e6c8-4a97-8a27-6814b0ed9144"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000039"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("13c01d81-198b-45cb-9de9-f25130e16313"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000040"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("667c3022-ea0d-4d1c-8ae4-b64e2f104295"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000041"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("78607299-2f5c-4edb-9143-c4a83395f731"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000042"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("e53313fd-c6d4-4d52-827f-14cfd55401eb"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000043"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("c2521113-60f4-41fa-8ded-6b6a6b91a584"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000044"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("14532efd-d9f9-4a46-a7c3-fbfbe85a6f13"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000045"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("438bbd52-ee61-4afc-9d59-7d0407da4f1d"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000046"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("3e3d0cd9-f95e-4c1c-b829-cd02f1bad987"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000047"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("84daf87e-d3e1-4f4e-9a57-ec9996fff41d"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000048"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("ddcb8caf-106e-4072-b48e-a2542de51211"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000049"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("95d39d28-c5c7-4e82-87eb-40570db1c1f5"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000050"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("b8e7b327-33c9-4420-b4d1-0071c58a5c95"));
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,729 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Distribution.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class SeedDefaultCashAccount : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.InsertData(
|
||||
table: "BankAccounts",
|
||||
columns: new[] { "Id", "AccountName", "AccountNumber", "AccountType", "BankName", "CashAccountTypeName", "CreatedAt", "CurrencyCode", "IsActive", "IsDefault" },
|
||||
values: new object[] { new Guid("11111111-1111-1111-1111-000000000001"), "Petty Cash", null, 0, null, "Petty Cash", new DateTime(2026, 10, 19, 0, 0, 0, 0, DateTimeKind.Unspecified), "LKR", true, true });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000001"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("ec13fdfa-d848-4004-b749-4b455ba0a77e"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000002"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("66f41359-cf33-4bd9-a936-816f03b23da7"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000003"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("369efbe7-f200-4b2e-bc15-858b6d2ca882"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000004"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("adc14999-08af-4b4c-937d-3ecd7c35b709"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000005"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("f1bf4ccb-45c6-4915-b941-6b507eb1e44f"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000006"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("1cbd132a-ea3d-46b2-8150-c5a077b6b36a"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000007"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("362118bd-1d4c-4576-95f6-7da899bf7368"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000008"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("15e94675-d2ae-4f5a-8198-6297a26d7a2c"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000009"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("4af2ab93-d3ba-4bda-b440-167f672cc166"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000010"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("bc5ba5f8-f20a-432a-985b-95b9500ce693"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000011"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("4f43f82b-8e6a-4729-9ded-c78c7dde24a7"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000012"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("7ac3df8c-f5c3-4d57-b537-5d5ca4ec1e11"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000013"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("44ec8d0f-f540-4786-9d9a-9194fa091286"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000014"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("143c4280-21be-4852-adc5-7cd17ab595fd"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000015"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("d00b144e-bf2d-4a04-9c85-0c14544edf48"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000016"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("9a72b7e4-1197-4032-8ec6-ebb3442a267f"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000017"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("5ec2af6e-d1db-4811-9c37-94a260e6ef9b"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000018"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("a71aceba-5199-4824-8b4e-998b108c082c"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000019"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("4bd840bf-da52-46fd-b065-40253f915c7c"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000020"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("8b4d8558-dba9-45bb-b263-f066580a55e9"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000021"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("0ee1a2e0-e4d2-4af3-9cca-ffad05bc1e20"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000022"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("700be5ec-0c11-497b-808a-80e7a854d2b4"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000023"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("b9978e5a-14b1-462a-9b7b-c9c32c4decb9"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000024"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("1e7cc9d4-f65e-434b-90e5-f78b4fda39b8"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000025"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("bfb3f80b-32c3-43b0-b216-f05e55f09360"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000026"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("fcfe2242-93ad-4c6d-8eec-3ec1f1270d0a"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000027"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("8e6eeb90-966f-4417-a1ae-148cdaa64904"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000028"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("6df60aea-63f3-465e-96fb-9a7304ead3c2"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000029"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("c0e21519-a095-4c7f-af42-e26efdcdd31e"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000030"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("496bbf8d-d84d-4c21-b190-bf95a06b4113"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("36f55476-d749-459b-92d9-839919499460"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("d90301fa-8990-49cf-8fa0-228ea7e684bc"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("8da4cd2a-1fd2-4417-bb6d-9e91174193f5"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000034"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("cc9bed46-a230-4611-88b4-d568abca857e"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("4d791413-ac44-4165-87b8-c28dbfa49230"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000036"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("2bb28028-8b12-4937-9dc6-68e35b100007"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("fdc8d3d9-a32d-4322-8144-93ff677beea3"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000038"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("b1ea0901-08e8-4dea-8ac3-15b522a2f274"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000039"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("116ac7bf-741e-4d4f-a313-2e7946a50044"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000040"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("7fd97ca6-a14b-4e42-8474-5471c0ef79a9"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000041"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("e58f35b2-8308-4fbb-b3ca-db9d037347cd"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000042"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("dc6b2750-083b-4653-a986-7af7aaec8136"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000043"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("6629888a-eb6b-433f-bbfe-ee5d947fb5b2"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000044"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("94b97271-7593-4cec-9290-802e9a26f0d8"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000045"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("0dc88aca-4e86-4712-988f-8db6a466531d"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000046"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("c8839187-4358-4218-bf3d-5ea4ed758661"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000047"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("5c731906-8d81-4f85-abad-230050af0bd0"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000048"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("d6e3db40-ca38-4caf-88d8-ce87475b4d87"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000049"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("398f1c4d-5d7d-4af4-9aa7-a88d7abf9a33"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000050"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("0f6c66e6-833c-4b52-ba2c-09da8dbead6f"));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DeleteData(
|
||||
table: "BankAccounts",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("11111111-1111-1111-1111-000000000001"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000001"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("8c43c584-0f65-496d-a77c-a7df46bb175d"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000002"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("25576a9d-38cb-4ff5-b8ca-08df43d8a5a6"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000003"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("e47016c1-a014-476f-ba87-4b3d438de62a"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000004"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("a24c0e9e-0194-4f96-823c-bdbdcd122d85"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000005"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("e76ef6e2-67ba-462f-a954-b1dbc5a95d8c"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000006"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("bba4fdaa-fd0e-43dc-aedf-331212bd06cd"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000007"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("bc66d450-af6b-426e-9d05-266ceba21fac"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000008"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("9ee2196c-1e4b-452d-aaf6-7c55e3d2be2b"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000009"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("63508590-7a30-4640-9fc9-d3d54978f581"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000010"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("82802a3f-23bc-45db-a531-d7e880dd7970"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000011"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("65ccf5b6-754a-431a-81bf-d8e0587c956e"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000012"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("6c713413-362b-475a-846b-9f3382cf7a35"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000013"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("6dfe68ff-f5a3-41a8-981b-ac108a81408e"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000014"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("09c6b812-dc14-44a2-9362-429dfef3942d"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000015"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("bf29a1bb-234e-47a3-922f-f0fb3a1daba0"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000016"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("0d2f3497-4852-453e-a451-275e945b6c1d"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000017"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("41c13dbc-4f51-4134-891c-b6955f728d65"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000018"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("29bc0291-7a91-4fc5-840b-9420b64b86fc"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000019"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("8ae0c386-d206-4580-890f-c1673c1dfeb2"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000020"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("5db4bd09-9481-4608-9dec-3412ba7f3f69"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000021"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("c1840bd7-88fd-496a-8d88-aef3d886e1ec"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000022"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("af97d659-c040-4fdc-beeb-62fc8334f95c"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000023"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("a6443517-3c2a-4407-a3bd-5497e053b0d6"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000024"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("2f540d56-bff4-41fe-9181-4a423c1da51c"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000025"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("db18cfb8-a56c-46bc-baff-298e6204c455"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000026"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("fe209c12-1189-485b-920e-ce6db3c132b9"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000027"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("fedef395-394d-4237-b5aa-8254603ed304"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000028"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("7cc47626-65b8-44b0-83fb-a5dff96c39f6"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000029"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("cf04aea5-3f47-4717-95aa-8dab547f9cfb"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000030"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("c2906d62-9eea-4d08-b904-86a3f8b04a20"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("7a546bf9-c2f3-4d23-a38e-a068da53dfeb"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("19c916c3-7b17-42e0-b4b6-89b8cb6475f4"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("e6805dbe-a931-4e2a-b08f-83e8760d4968"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000034"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("7d991f5c-16db-46c5-863c-93063c2e7382"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("513c50d0-5d7b-426d-beef-f14bdea626bc"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000036"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("638a6a60-25da-425c-bdec-683431c26d62"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("b00bfae6-92bb-48cb-99d9-f2814baae47d"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000038"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("6562c9b4-0673-4ce0-8805-03734bc889ce"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000039"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("6601e0fe-83dd-4307-be91-38dae998cc9c"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000040"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("c4848449-9678-4fa6-8a00-db4e504b57d6"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000041"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("91c02c24-12a3-48d5-9d19-174c8e764152"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000042"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("0f00477f-5120-40ba-a0e6-0406e47d1c80"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000043"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("db135a10-6335-478d-b2f8-3b5934cd142b"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000044"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("5c219dd9-9ff3-4541-a708-cc7e4bd4dd25"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000045"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("086e7b93-079d-49d0-90e9-95899b53be52"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000046"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("f0b3d172-cb32-4606-8272-00feaf4067a2"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000047"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("75a882c0-b2e9-4efa-a64c-b5d530d42194"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000048"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("03540f63-d811-4436-869f-2f76995bbb19"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000049"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("73894d9f-a823-4b5b-b809-5cc0aa716a70"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000050"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("1ad4bfb8-cde6-4623-a21d-9a78af0026d6"));
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,928 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Distribution.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddSupplierPayments : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "CompanyId",
|
||||
table: "ReceivedCheques",
|
||||
type: "int",
|
||||
nullable: true,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int");
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "FK_BankAccountId",
|
||||
table: "Payments",
|
||||
type: "char(36)",
|
||||
nullable: true,
|
||||
collation: "ascii_general_ci");
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "FK_ReceivedChequeId",
|
||||
table: "Payments",
|
||||
type: "char(36)",
|
||||
nullable: true,
|
||||
collation: "ascii_general_ci");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SupplierPayments",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
FK_SupplierId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
Amount = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
Method = table.Column<int>(type: "int", nullable: false),
|
||||
FK_BankAccountId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
|
||||
FK_ChequePageId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
|
||||
PaidAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
PaidByUserId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
|
||||
Notes = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_SupplierPayments", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_SupplierPayments_BankAccounts_FK_BankAccountId",
|
||||
column: x => x.FK_BankAccountId,
|
||||
principalTable: "BankAccounts",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_SupplierPayments_ChequePages_FK_ChequePageId",
|
||||
column: x => x.FK_ChequePageId,
|
||||
principalTable: "ChequePages",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_SupplierPayments_Suppliers_FK_SupplierId",
|
||||
column: x => x.FK_SupplierId,
|
||||
principalTable: "Suppliers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SupplierPaymentGRNs",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
FK_SupplierPaymentId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
FK_GoodsReceiptId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
AllocatedAmount = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_SupplierPaymentGRNs", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_SupplierPaymentGRNs_GoodsReceipts_FK_GoodsReceiptId",
|
||||
column: x => x.FK_GoodsReceiptId,
|
||||
principalTable: "GoodsReceipts",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_SupplierPaymentGRNs_SupplierPayments_FK_SupplierPaymentId",
|
||||
column: x => x.FK_SupplierPaymentId,
|
||||
principalTable: "SupplierPayments",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Payments",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("47000000-0000-0000-0000-000000000001"),
|
||||
columns: new[] { "FK_BankAccountId", "FK_ReceivedChequeId" },
|
||||
values: new object[] { null, null });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Payments",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("47000000-0000-0000-0000-000000000002"),
|
||||
columns: new[] { "FK_BankAccountId", "FK_ReceivedChequeId" },
|
||||
values: new object[] { null, null });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Payments",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("47000000-0000-0000-0000-000000000003"),
|
||||
columns: new[] { "FK_BankAccountId", "FK_ReceivedChequeId" },
|
||||
values: new object[] { null, null });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Payments",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("47000000-0000-0000-0000-000000000004"),
|
||||
columns: new[] { "FK_BankAccountId", "FK_ReceivedChequeId" },
|
||||
values: new object[] { null, null });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000001"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("30f658ac-2c92-4cd8-ad47-10b0def2699c"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000002"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("42ab2c52-eaf8-4a17-b67a-ccd66569e56a"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000003"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("8afec825-27b3-4153-ad06-f8bcdbf93da8"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000004"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("4b427620-e99d-4068-a416-c3788d5bd9b5"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000005"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("361f44ef-1cd1-48a9-be4d-4c068aacef1e"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000006"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("11fc03ae-92ee-4336-ae57-29aac731ad51"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000007"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("0b1f2b73-ef60-4634-864f-22a6c0cd1373"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000008"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("2380abf3-2bcd-4a9f-b447-5f1685df2fc8"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000009"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("240e4d6a-8306-40ba-aaec-8e05cd0347db"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000010"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("cfde0aa6-e859-4f9c-8291-b0d886fcd4c9"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000011"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("dae772c8-26ec-4b5c-8d7a-f3196510bf2c"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000012"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("3512c0a4-a5ae-4002-a779-7d3cc600ab61"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000013"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("f9e2a348-159e-47a2-b7be-a60f0254873f"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000014"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("c8b4c181-5b08-45d2-818b-74bceeab78a8"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000015"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("8fb4ff4c-c5bd-4afd-a08e-02861d63cb7b"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000016"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("edb3b711-d623-439d-811e-afaef8d0680d"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000017"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("f33e4c62-d69f-4a91-8b62-acbd5351ea2a"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000018"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("c7c9d16c-e343-43e9-9e27-f38f903a5d00"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000019"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("f5f4760a-8a45-46ca-8093-743c95b0313f"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000020"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("c5434801-146a-4fd9-9c29-a1145e7f7f00"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000021"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("6c085135-78a5-4e65-a380-ee090b07e2bb"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000022"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("bbde2785-a3df-4f81-bad1-0bc5afe5de57"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000023"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("4421a0fb-bdd4-45a9-8dc0-d9b70f775e97"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000024"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("dba9dcb4-3b94-4ddc-89f3-552cfc109478"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000025"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("d8f74160-c150-4d9c-aba6-e45e628c684e"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000026"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("495fb68b-9f01-4538-9561-fefce0d39558"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000027"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("3bdece9b-a798-4223-a23c-9c9dd10d042d"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000028"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("6fa616eb-99fe-46b9-8305-908765d037fd"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000029"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("7a07148a-1327-4e97-befc-b2045eacad21"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000030"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("852e86d7-aa57-4a77-81c0-054f3fb4d7f7"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("3b767929-c973-41fb-a340-4ee5feb65619"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("63619bbd-0352-41bd-9e1c-a3f7908fb5d2"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("f6b4c8bf-a9b3-4179-9f57-a8584678c0d1"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000034"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("bb47b8ca-f7cf-40fe-8945-fc532a439b53"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("52126271-ae3b-4b58-a9d2-50e2dd894087"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000036"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("981e3358-85bb-47fe-a1f5-3a3a02cf47b1"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("749408da-fcee-458d-9144-8d8dee928ba0"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000038"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("c157857c-fb0e-41fd-a414-c0f57a44b120"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000039"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("3217cdbc-76c9-4505-b9f3-23de91a6212b"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000040"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("63526c52-18ba-40e0-9889-3bd3b7fc8161"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000041"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("260cf2fa-1ca6-4cd5-92ae-8c479a80e2f0"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000042"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("130ab09c-8a36-4705-8235-9d00f75dd684"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000043"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("41794774-e919-4f4e-a42a-5ae408d3407f"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000044"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("2ff23d04-d1f6-4aa3-a48e-1c82bb9ad12a"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000045"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("d36c81c0-8e9b-41ea-8e09-464a327a16d2"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000046"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("6c51dfc1-63e0-46a6-b2bb-27c21c708b51"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000047"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("076333d5-b1f3-4b89-86fd-5a61a4451c88"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000048"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("7e834f3e-1424-4aaa-816e-d62da6df672b"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000049"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("699fa06b-a7ff-4dbd-98a7-a29c2c361665"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000050"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("00df5735-54c6-4cdf-8e2c-a64cd90ede15"));
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Payments_FK_BankAccountId",
|
||||
table: "Payments",
|
||||
column: "FK_BankAccountId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Payments_FK_ReceivedChequeId",
|
||||
table: "Payments",
|
||||
column: "FK_ReceivedChequeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_SupplierPaymentGRNs_FK_GoodsReceiptId",
|
||||
table: "SupplierPaymentGRNs",
|
||||
column: "FK_GoodsReceiptId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_SupplierPaymentGRNs_FK_SupplierPaymentId",
|
||||
table: "SupplierPaymentGRNs",
|
||||
column: "FK_SupplierPaymentId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_SupplierPayments_FK_BankAccountId",
|
||||
table: "SupplierPayments",
|
||||
column: "FK_BankAccountId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_SupplierPayments_FK_ChequePageId",
|
||||
table: "SupplierPayments",
|
||||
column: "FK_ChequePageId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_SupplierPayments_FK_SupplierId",
|
||||
table: "SupplierPayments",
|
||||
column: "FK_SupplierId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Payments_BankAccounts_FK_BankAccountId",
|
||||
table: "Payments",
|
||||
column: "FK_BankAccountId",
|
||||
principalTable: "BankAccounts",
|
||||
principalColumn: "Id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Payments_ReceivedCheques_FK_ReceivedChequeId",
|
||||
table: "Payments",
|
||||
column: "FK_ReceivedChequeId",
|
||||
principalTable: "ReceivedCheques",
|
||||
principalColumn: "Id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Payments_BankAccounts_FK_BankAccountId",
|
||||
table: "Payments");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Payments_ReceivedCheques_FK_ReceivedChequeId",
|
||||
table: "Payments");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "SupplierPaymentGRNs");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "SupplierPayments");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Payments_FK_BankAccountId",
|
||||
table: "Payments");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Payments_FK_ReceivedChequeId",
|
||||
table: "Payments");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FK_BankAccountId",
|
||||
table: "Payments");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FK_ReceivedChequeId",
|
||||
table: "Payments");
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "CompanyId",
|
||||
table: "ReceivedCheques",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 0,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000001"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("ec13fdfa-d848-4004-b749-4b455ba0a77e"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000002"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("66f41359-cf33-4bd9-a936-816f03b23da7"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000003"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("369efbe7-f200-4b2e-bc15-858b6d2ca882"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000004"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("adc14999-08af-4b4c-937d-3ecd7c35b709"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000005"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("f1bf4ccb-45c6-4915-b941-6b507eb1e44f"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000006"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("1cbd132a-ea3d-46b2-8150-c5a077b6b36a"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000007"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("362118bd-1d4c-4576-95f6-7da899bf7368"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000008"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("15e94675-d2ae-4f5a-8198-6297a26d7a2c"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000009"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("4af2ab93-d3ba-4bda-b440-167f672cc166"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000010"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("bc5ba5f8-f20a-432a-985b-95b9500ce693"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000011"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("4f43f82b-8e6a-4729-9ded-c78c7dde24a7"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000012"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("7ac3df8c-f5c3-4d57-b537-5d5ca4ec1e11"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000013"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("44ec8d0f-f540-4786-9d9a-9194fa091286"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000014"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("143c4280-21be-4852-adc5-7cd17ab595fd"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000015"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("d00b144e-bf2d-4a04-9c85-0c14544edf48"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000016"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("9a72b7e4-1197-4032-8ec6-ebb3442a267f"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000017"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("5ec2af6e-d1db-4811-9c37-94a260e6ef9b"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000018"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("a71aceba-5199-4824-8b4e-998b108c082c"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000019"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("4bd840bf-da52-46fd-b065-40253f915c7c"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000020"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("8b4d8558-dba9-45bb-b263-f066580a55e9"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000021"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("0ee1a2e0-e4d2-4af3-9cca-ffad05bc1e20"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000022"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("700be5ec-0c11-497b-808a-80e7a854d2b4"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000023"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("b9978e5a-14b1-462a-9b7b-c9c32c4decb9"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000024"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("1e7cc9d4-f65e-434b-90e5-f78b4fda39b8"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000025"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("bfb3f80b-32c3-43b0-b216-f05e55f09360"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000026"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("fcfe2242-93ad-4c6d-8eec-3ec1f1270d0a"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000027"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("8e6eeb90-966f-4417-a1ae-148cdaa64904"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000028"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("6df60aea-63f3-465e-96fb-9a7304ead3c2"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000029"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("c0e21519-a095-4c7f-af42-e26efdcdd31e"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000030"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("496bbf8d-d84d-4c21-b190-bf95a06b4113"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("36f55476-d749-459b-92d9-839919499460"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("d90301fa-8990-49cf-8fa0-228ea7e684bc"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("8da4cd2a-1fd2-4417-bb6d-9e91174193f5"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000034"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("cc9bed46-a230-4611-88b4-d568abca857e"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("4d791413-ac44-4165-87b8-c28dbfa49230"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000036"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("2bb28028-8b12-4937-9dc6-68e35b100007"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("fdc8d3d9-a32d-4322-8144-93ff677beea3"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000038"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("b1ea0901-08e8-4dea-8ac3-15b522a2f274"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000039"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("116ac7bf-741e-4d4f-a313-2e7946a50044"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000040"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("7fd97ca6-a14b-4e42-8474-5471c0ef79a9"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000041"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("e58f35b2-8308-4fbb-b3ca-db9d037347cd"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000042"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("dc6b2750-083b-4653-a986-7af7aaec8136"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000043"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("6629888a-eb6b-433f-bbfe-ee5d947fb5b2"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000044"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("94b97271-7593-4cec-9290-802e9a26f0d8"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000045"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("0dc88aca-4e86-4712-988f-8db6a466531d"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000046"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("c8839187-4358-4218-bf3d-5ea4ed758661"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000047"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("5c731906-8d81-4f85-abad-230050af0bd0"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000048"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("d6e3db40-ca38-4caf-88d8-ce87475b4d87"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000049"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("398f1c4d-5d7d-4af4-9aa7-a88d7abf9a33"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000050"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("0f6c66e6-833c-4b52-ba2c-09da8dbead6f"));
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,778 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Distribution.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class credittable : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "StoreCreditMovements",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
FK_StoreId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
Direction = table.Column<int>(type: "int", nullable: false),
|
||||
Amount = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
|
||||
FK_InvoiceId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
|
||||
FK_PaymentId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
|
||||
OccurredAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
RecordedBy = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
|
||||
Notes = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_StoreCreditMovements", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_StoreCreditMovements_Invoices_FK_InvoiceId",
|
||||
column: x => x.FK_InvoiceId,
|
||||
principalTable: "Invoices",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_StoreCreditMovements_Payments_FK_PaymentId",
|
||||
column: x => x.FK_PaymentId,
|
||||
principalTable: "Payments",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_StoreCreditMovements_Stores_FK_StoreId",
|
||||
column: x => x.FK_StoreId,
|
||||
principalTable: "Stores",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000001"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("be712eda-98a5-4a30-820a-1737e84d1e4b"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000002"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("47ea9420-d6a0-4376-a2b8-211639d95b27"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000003"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("369eed1f-df4c-4131-a3c6-ebbe63d2dc91"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000004"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("14b6944f-2d4a-4378-a974-c6fc0f9dfb81"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000005"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("14a01e0b-7be5-4c65-ae6a-899f9aa7ad28"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000006"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("5d486ccd-3866-4e7d-8ef1-fb177fb637c1"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000007"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("d830e59b-f451-4bde-9a77-78daea5f9a61"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000008"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("096d3837-a649-4184-8987-2ea0dfa2d4a1"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000009"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("5e43bbde-689b-4d07-baf9-0764078b5586"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000010"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("f317d30c-1644-44f0-b0aa-6bcf1fa66f44"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000011"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("31ffac6f-2aa8-48ee-bc87-213f35933de4"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000012"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("fb525bc4-9f16-455a-bfd5-ca6f3e7ad141"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000013"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("2388918f-6392-4384-b269-0a1469f5bf4c"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000014"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("7159012e-5f25-4881-b5e8-5300e382332e"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000015"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("cc4b2e82-ad42-4440-92ba-9b2100bb5f84"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000016"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("6b15c133-ea0e-473d-b58a-7f140e9f03bf"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000017"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("c2dd52a9-380f-479f-8e5e-6b56bfc4738f"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000018"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("bf5f6c7a-2fde-4a01-83f4-aacf91333c4c"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000019"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("162a0346-c4c0-4c4e-96e9-aa8154d25b39"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000020"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("5cc76807-25d1-45e5-9da3-cfd6d6f3ad7c"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000021"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("51a2653e-4b1a-4f6f-80c2-f533b26109ea"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000022"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("e2a382e2-d712-4a97-a7ec-cd6232d77a78"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000023"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("b60260b5-70b0-4f9e-9ab9-ba4abbbdfb02"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000024"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("a73fe794-383c-4157-89cf-227c4e487a6d"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000025"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("a9743e96-15b2-4c1d-b410-698cd6db0b7b"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000026"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("a94d92ab-66d8-4dd9-8ba3-c242eb450e68"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000027"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("9cb1a028-a98e-492a-aafc-70456fe46c96"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000028"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("5bc99a62-f5d7-48fe-b25c-518e4774614c"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000029"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("eceee678-5cee-4ee9-b16d-8d8d21fbb5ea"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000030"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("3a049ed7-75b7-4b1d-b3bb-402ca77c350f"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("aeaab85c-5b02-4bc2-b0e3-318c0f5b07d3"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("b79800e0-baa6-4add-9374-7d1e2ed650e8"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("0da6bf0b-26ff-46b9-bb8c-f9c641b37a89"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000034"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("ff84aeeb-93c0-465d-9a4a-8b686ba7bc75"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("921f8073-7a66-49bb-ae23-cf8fcde12c9c"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000036"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("bff05501-4988-4c8a-b61d-e32389e795a8"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("2c4f7432-e697-4d65-97df-069f8913a662"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000038"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("18d27746-3178-4c57-afd5-b75f0061637f"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000039"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("a5d1978b-c497-4314-857b-a7e3b8323a53"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000040"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("88300763-b8c3-4bd2-803b-df0e43bc691e"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000041"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("7aaad5a9-4497-429f-b3dc-9b59664e2e19"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000042"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("2db130c1-4b6f-4719-b3bb-7bd8a0662d75"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000043"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("be349d14-38b5-4a13-a152-9ea055de26b1"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000044"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("5292e02d-9a7d-4a18-be14-86c2f3351cac"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000045"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("97021474-1218-4d21-9115-4b9b2726e38b"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000046"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("56f68355-7b1b-49e4-829c-1a4d47fc93de"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000047"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("3017c8cb-f9e4-41c8-ac2e-f03d23fe944a"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000048"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("619a08e5-f6cb-4515-8b4d-a094d19277d8"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000049"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("cb8b4e96-7026-4485-8b52-3145b5289350"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000050"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("f0f57dfd-21a5-4a0b-a77e-e641f00300fa"));
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_StoreCreditMovements_FK_InvoiceId",
|
||||
table: "StoreCreditMovements",
|
||||
column: "FK_InvoiceId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_StoreCreditMovements_FK_PaymentId",
|
||||
table: "StoreCreditMovements",
|
||||
column: "FK_PaymentId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_StoreCreditMovements_FK_StoreId_OccurredAt",
|
||||
table: "StoreCreditMovements",
|
||||
columns: new[] { "FK_StoreId", "OccurredAt" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "StoreCreditMovements");
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000001"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("30f658ac-2c92-4cd8-ad47-10b0def2699c"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000002"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("42ab2c52-eaf8-4a17-b67a-ccd66569e56a"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000003"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("8afec825-27b3-4153-ad06-f8bcdbf93da8"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000004"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("4b427620-e99d-4068-a416-c3788d5bd9b5"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000005"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("361f44ef-1cd1-48a9-be4d-4c068aacef1e"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000006"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("11fc03ae-92ee-4336-ae57-29aac731ad51"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000007"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("0b1f2b73-ef60-4634-864f-22a6c0cd1373"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000008"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("2380abf3-2bcd-4a9f-b447-5f1685df2fc8"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000009"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("240e4d6a-8306-40ba-aaec-8e05cd0347db"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000010"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("cfde0aa6-e859-4f9c-8291-b0d886fcd4c9"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000011"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("dae772c8-26ec-4b5c-8d7a-f3196510bf2c"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000012"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("3512c0a4-a5ae-4002-a779-7d3cc600ab61"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000013"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("f9e2a348-159e-47a2-b7be-a60f0254873f"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000014"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("c8b4c181-5b08-45d2-818b-74bceeab78a8"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000015"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("8fb4ff4c-c5bd-4afd-a08e-02861d63cb7b"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000016"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("edb3b711-d623-439d-811e-afaef8d0680d"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000017"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("f33e4c62-d69f-4a91-8b62-acbd5351ea2a"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000018"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("c7c9d16c-e343-43e9-9e27-f38f903a5d00"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000019"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("f5f4760a-8a45-46ca-8093-743c95b0313f"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000020"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("c5434801-146a-4fd9-9c29-a1145e7f7f00"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000021"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("6c085135-78a5-4e65-a380-ee090b07e2bb"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000022"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("bbde2785-a3df-4f81-bad1-0bc5afe5de57"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000023"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("4421a0fb-bdd4-45a9-8dc0-d9b70f775e97"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000024"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("dba9dcb4-3b94-4ddc-89f3-552cfc109478"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000025"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("d8f74160-c150-4d9c-aba6-e45e628c684e"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000026"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("495fb68b-9f01-4538-9561-fefce0d39558"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000027"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("3bdece9b-a798-4223-a23c-9c9dd10d042d"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000028"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("6fa616eb-99fe-46b9-8305-908765d037fd"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000029"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("7a07148a-1327-4e97-befc-b2045eacad21"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000030"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("852e86d7-aa57-4a77-81c0-054f3fb4d7f7"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("3b767929-c973-41fb-a340-4ee5feb65619"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("63619bbd-0352-41bd-9e1c-a3f7908fb5d2"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("f6b4c8bf-a9b3-4179-9f57-a8584678c0d1"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000034"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("bb47b8ca-f7cf-40fe-8945-fc532a439b53"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("52126271-ae3b-4b58-a9d2-50e2dd894087"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000036"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("981e3358-85bb-47fe-a1f5-3a3a02cf47b1"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("749408da-fcee-458d-9144-8d8dee928ba0"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000038"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("c157857c-fb0e-41fd-a414-c0f57a44b120"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000039"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("3217cdbc-76c9-4505-b9f3-23de91a6212b"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000040"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("63526c52-18ba-40e0-9889-3bd3b7fc8161"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000041"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("260cf2fa-1ca6-4cd5-92ae-8c479a80e2f0"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000042"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("130ab09c-8a36-4705-8235-9d00f75dd684"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000043"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("41794774-e919-4f4e-a42a-5ae408d3407f"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000044"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("2ff23d04-d1f6-4aa3-a48e-1c82bb9ad12a"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000045"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("d36c81c0-8e9b-41ea-8e09-464a327a16d2"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000046"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("6c51dfc1-63e0-46a6-b2bb-27c21c708b51"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000047"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("076333d5-b1f3-4b89-86fd-5a61a4451c88"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000048"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("7e834f3e-1424-4aaa-816e-d62da6df672b"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000049"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("699fa06b-a7ff-4dbd-98a7-a29c2c361665"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000050"), new Guid("10000000-0000-0000-0000-000000000007") },
|
||||
column: "Id",
|
||||
value: new Guid("00df5735-54c6-4cdf-8e2c-a64cd90ede15"));
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Distribution.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddNotifications : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Notifications",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
Type = table.Column<string>(type: "varchar(60)", maxLength: 60, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Severity = table.Column<int>(type: "int", nullable: false),
|
||||
Title = table.Column<string>(type: "varchar(150)", maxLength: 150, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Message = table.Column<string>(type: "varchar(500)", maxLength: 500, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
EntityType = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
EntityId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
Link = table.Column<string>(type: "varchar(200)", maxLength: 200, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
CreatedByUserId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Notifications", x => x.Id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "NotificationRecipients",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
FK_NotificationId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
FK_UserId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
IsRead = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||
ReadAt = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_NotificationRecipients", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_NotificationRecipients_Notifications_FK_NotificationId",
|
||||
column: x => x.FK_NotificationId,
|
||||
principalTable: "Notifications",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_NotificationRecipients_Users_FK_UserId",
|
||||
column: x => x.FK_UserId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_NotificationRecipients_FK_NotificationId_FK_UserId",
|
||||
table: "NotificationRecipients",
|
||||
columns: new[] { "FK_NotificationId", "FK_UserId" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_NotificationRecipients_FK_UserId_IsRead",
|
||||
table: "NotificationRecipients",
|
||||
columns: new[] { "FK_UserId", "IsRead" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Notifications_EntityType_EntityId",
|
||||
table: "Notifications",
|
||||
columns: new[] { "EntityType", "EntityId" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "NotificationRecipients");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Notifications");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Distribution.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddInvoiceItems : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "InvoiceItems",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
FK_InvoiceId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
FK_ShopPOItemId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
FK_ItemId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
Quantity = table.Column<int>(type: "int", nullable: false),
|
||||
UnitPrice = table.Column<decimal>(type: "decimal(65,30)", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_InvoiceItems", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_InvoiceItems_Invoices_FK_InvoiceId",
|
||||
column: x => x.FK_InvoiceId,
|
||||
principalTable: "Invoices",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_InvoiceItems_Items_FK_ItemId",
|
||||
column: x => x.FK_ItemId,
|
||||
principalTable: "Items",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_InvoiceItems_ShopPOItems_FK_ShopPOItemId",
|
||||
column: x => x.FK_ShopPOItemId,
|
||||
principalTable: "ShopPOItems",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_InvoiceItems_FK_InvoiceId",
|
||||
table: "InvoiceItems",
|
||||
column: "FK_InvoiceId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_InvoiceItems_FK_ItemId",
|
||||
table: "InvoiceItems",
|
||||
column: "FK_ItemId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_InvoiceItems_FK_ShopPOItemId",
|
||||
table: "InvoiceItems",
|
||||
column: "FK_ShopPOItemId");
|
||||
|
||||
// Backfill: snapshot each existing invoice's PO lines so older invoices can be edited/finalized.
|
||||
migrationBuilder.Sql(@"
|
||||
INSERT INTO InvoiceItems (Id, FK_InvoiceId, FK_ShopPOItemId, FK_ItemId, Quantity, UnitPrice, CreatedAt, IsActive)
|
||||
SELECT UUID(), inv.Id, poi.Id, poi.FK_ItemId, poi.Quantity, poi.UnitPrice, NOW(6), 1
|
||||
FROM Invoices inv
|
||||
JOIN ShopPOItems poi ON poi.FK_ShopPOId = inv.FK_ShopPOId AND poi.IsActive = 1;");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "InvoiceItems");
|
||||
}
|
||||
}
|
||||
}
|
||||
+6080
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,157 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Distribution.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddInvoiceVersionsAndLineEdits : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "FK_ShopPOItemId",
|
||||
table: "InvoiceItems",
|
||||
type: "char(36)",
|
||||
nullable: true,
|
||||
collation: "ascii_general_ci",
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "char(36)")
|
||||
.OldAnnotation("Relational:Collation", "ascii_general_ci");
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsRemoved",
|
||||
table: "InvoiceItems",
|
||||
type: "tinyint(1)",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "InvoiceVersions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
FK_InvoiceId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
VersionNumber = table.Column<int>(type: "int", nullable: false),
|
||||
Stage = table.Column<int>(type: "int", nullable: false),
|
||||
TotalAmount = table.Column<decimal>(type: "decimal(65,30)", nullable: false),
|
||||
CreatedBy = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_InvoiceVersions", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_InvoiceVersions_Invoices_FK_InvoiceId",
|
||||
column: x => x.FK_InvoiceId,
|
||||
principalTable: "Invoices",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "InvoiceVersionItems",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
FK_InvoiceVersionId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
FK_ShopPOItemId = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
|
||||
FK_ItemId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
Quantity = table.Column<int>(type: "int", nullable: false),
|
||||
UnitPrice = table.Column<decimal>(type: "decimal(65,30)", nullable: false),
|
||||
IsRemoved = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_InvoiceVersionItems", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_InvoiceVersionItems_InvoiceVersions_FK_InvoiceVersionId",
|
||||
column: x => x.FK_InvoiceVersionId,
|
||||
principalTable: "InvoiceVersions",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_InvoiceVersionItems_Items_FK_ItemId",
|
||||
column: x => x.FK_ItemId,
|
||||
principalTable: "Items",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_InvoiceVersionItems_ShopPOItems_FK_ShopPOItemId",
|
||||
column: x => x.FK_ShopPOItemId,
|
||||
principalTable: "ShopPOItems",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_InvoiceVersionItems_FK_InvoiceVersionId",
|
||||
table: "InvoiceVersionItems",
|
||||
column: "FK_InvoiceVersionId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_InvoiceVersionItems_FK_ItemId",
|
||||
table: "InvoiceVersionItems",
|
||||
column: "FK_ItemId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_InvoiceVersionItems_FK_ShopPOItemId",
|
||||
table: "InvoiceVersionItems",
|
||||
column: "FK_ShopPOItemId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_InvoiceVersions_FK_InvoiceId_VersionNumber",
|
||||
table: "InvoiceVersions",
|
||||
columns: new[] { "FK_InvoiceId", "VersionNumber" },
|
||||
unique: true);
|
||||
|
||||
// Backfill: one baseline version per existing invoice from its current lines.
|
||||
migrationBuilder.Sql(@"
|
||||
INSERT INTO InvoiceVersions (Id, FK_InvoiceId, VersionNumber, Stage, TotalAmount, CreatedBy, CreatedAt, IsActive)
|
||||
SELECT UUID(), inv.Id, 1,
|
||||
CASE inv.Status WHEN 0 THEN 0 WHEN 1 THEN 2 ELSE 3 END,
|
||||
inv.TotalAmount, inv.CreatedBy, NOW(6), 1
|
||||
FROM Invoices inv;");
|
||||
|
||||
migrationBuilder.Sql(@"
|
||||
INSERT INTO InvoiceVersionItems (Id, FK_InvoiceVersionId, FK_ShopPOItemId, FK_ItemId, Quantity, UnitPrice, IsRemoved, CreatedAt, IsActive)
|
||||
SELECT UUID(), v.Id, ii.FK_ShopPOItemId, ii.FK_ItemId, ii.Quantity, ii.UnitPrice, ii.IsRemoved, NOW(6), 1
|
||||
FROM InvoiceVersions v
|
||||
JOIN InvoiceItems ii ON ii.FK_InvoiceId = v.FK_InvoiceId AND ii.IsActive = 1
|
||||
WHERE v.VersionNumber = 1;");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "InvoiceVersionItems");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "InvoiceVersions");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IsRemoved",
|
||||
table: "InvoiceItems");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "FK_ShopPOItemId",
|
||||
table: "InvoiceItems",
|
||||
type: "char(36)",
|
||||
nullable: false,
|
||||
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
|
||||
collation: "ascii_general_ci",
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "char(36)",
|
||||
oldNullable: true)
|
||||
.OldAnnotation("Relational:Collation", "ascii_general_ci");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Distribution.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddInvoiceDiscount : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "DiscountAmount",
|
||||
table: "Invoices",
|
||||
type: "decimal(65,30)",
|
||||
nullable: false,
|
||||
defaultValue: 0m);
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Invoices",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("34500000-0000-0000-0000-000000000001"),
|
||||
column: "DiscountAmount",
|
||||
value: 0m);
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Invoices",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("34500000-0000-0000-0000-000000000002"),
|
||||
column: "DiscountAmount",
|
||||
value: 0m);
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DiscountAmount",
|
||||
table: "Invoices");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Distribution.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddFreeIssueLines : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "FK_SchemeId",
|
||||
table: "ShopPOItems",
|
||||
type: "char(36)",
|
||||
nullable: true,
|
||||
collation: "ascii_general_ci");
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsFree",
|
||||
table: "ShopPOItems",
|
||||
type: "tinyint(1)",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "SchemeName",
|
||||
table: "ShopPOItems",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "FK_SchemeId",
|
||||
table: "InvoiceItems",
|
||||
type: "char(36)",
|
||||
nullable: true,
|
||||
collation: "ascii_general_ci");
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsFree",
|
||||
table: "InvoiceItems",
|
||||
type: "tinyint(1)",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "SchemeName",
|
||||
table: "InvoiceItems",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
// Existing free-issue lines were plain zero-priced lines; flag them from their scheme redemption.
|
||||
migrationBuilder.Sql(@"
|
||||
UPDATE ShopPOItems i
|
||||
JOIN SchemeRedemptions r ON r.FK_ShopPOId = i.FK_ShopPOId AND r.FK_FreeItemId = i.FK_ItemId
|
||||
JOIN Schemes s ON s.Id = r.FK_SchemeId
|
||||
SET i.IsFree = 1, i.FK_SchemeId = r.FK_SchemeId, i.SchemeName = s.Name
|
||||
WHERE i.UnitPrice = 0 AND i.IsActive = 1;");
|
||||
|
||||
migrationBuilder.Sql(@"
|
||||
UPDATE InvoiceItems ii
|
||||
JOIN ShopPOItems pi ON ii.FK_ShopPOItemId = pi.Id
|
||||
SET ii.IsFree = pi.IsFree, ii.FK_SchemeId = pi.FK_SchemeId, ii.SchemeName = pi.SchemeName
|
||||
WHERE pi.IsFree = 1;");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FK_SchemeId",
|
||||
table: "ShopPOItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IsFree",
|
||||
table: "ShopPOItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SchemeName",
|
||||
table: "ShopPOItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FK_SchemeId",
|
||||
table: "InvoiceItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IsFree",
|
||||
table: "InvoiceItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SchemeName",
|
||||
table: "InvoiceItems");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public class BankAccount : Template
|
||||
{
|
||||
public BankAccountType AccountType { get; set; }
|
||||
public string AccountName { get; set; } = string.Empty;
|
||||
|
||||
public string? BankName { get; set; }
|
||||
|
||||
public string? CashAccountTypeName { get; set; }
|
||||
|
||||
public string? AccountNumber { get; set; }
|
||||
public string CurrencyCode { get; set; } = "LKR";
|
||||
|
||||
public bool IsDefault { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public class ChequeBook : Template
|
||||
{
|
||||
public string ChequeBookNo { get; set; } = string.Empty;
|
||||
|
||||
public int BranchId { get; set; }
|
||||
|
||||
public Guid FK_BankAccountId { get; set; }
|
||||
[ForeignKey("FK_BankAccountId")]
|
||||
public BankAccount? BankAccount { get; set; }
|
||||
|
||||
public string StartChequeNo { get; set; } = string.Empty;
|
||||
public string EndChequeNo { get; set; } = string.Empty;
|
||||
public int TotalLeaves { get; set; }
|
||||
public DateTime ReceivedDate { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public string? CreatedBy { get; set; }
|
||||
|
||||
public ChequeBookStatus Status { get; set; } = ChequeBookStatus.Active;
|
||||
|
||||
public List<ChequePage> Pages { get; set; } = new();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public class ChequePage : Template
|
||||
{
|
||||
public string ChequeNo { get; set; } = string.Empty;
|
||||
|
||||
public Guid FK_ChequeBookId { get; set; }
|
||||
[ForeignKey("FK_ChequeBookId")]
|
||||
public ChequeBook? ChequeBook { get; set; }
|
||||
|
||||
public ChequePageIssueStatus IssueStatus { get; set; } = ChequePageIssueStatus.Unused;
|
||||
|
||||
public PayeeType? PayeeType { get; set; }
|
||||
public int? PayeeId { get; set; }
|
||||
public string? PayeeName { get; set; }
|
||||
|
||||
public DateTime? IssueDate { get; set; }
|
||||
public decimal? Amount { get; set; }
|
||||
public string? CurrencyCode { get; set; }
|
||||
|
||||
public int? VoucherId { get; set; }
|
||||
public string? ReferenceNo { get; set; }
|
||||
public string? Purpose { get; set; }
|
||||
|
||||
public bool? IsCrossCheque { get; set; }
|
||||
public bool? IsAccountPayee { get; set; }
|
||||
public bool? IsPostDated { get; set; }
|
||||
public string? Notes { get; set; }
|
||||
|
||||
public string? PrintedBy { get; set; }
|
||||
public DateTime? PrintedAt { get; set; }
|
||||
public DateTime? ClearedDate { get; set; }
|
||||
public bool? ClearedByBank { get; set; }
|
||||
public string? CancelReason { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -18,11 +18,14 @@ namespace Distribution.Models
|
||||
public enum ShopPOStatus { Pending, PartiallyFulfilled, Fulfilled, Cancelled }
|
||||
|
||||
public enum InvoiceStatus { Draft, Printed, Finalized }
|
||||
public enum InvoiceVersionStage { Created, DraftEdited, Printed, Finalized }
|
||||
|
||||
public enum PaymentStatus { Pending, PartiallyPaid, Paid }
|
||||
|
||||
public enum PaymentMethod { Cash, Cheque, Credit }
|
||||
|
||||
public enum StoreCreditMovementDirection { Usage, Settlement }
|
||||
|
||||
public enum EntryMode { RealTime, DayEndManual }
|
||||
|
||||
public enum DeliveryTaskStatus { Pending, Delivered, PartiallyDelivered, Failed }
|
||||
@@ -49,4 +52,20 @@ namespace Distribution.Models
|
||||
public enum TaxMode { Disabled, Optional, Mandatory }
|
||||
|
||||
public enum SerialStatus { InStock, Sold, Returned, Damaged }
|
||||
|
||||
public enum BankAccountType { Cash, Bank }
|
||||
|
||||
public enum ChequeBookStatus { Active, Completed, Cancelled }
|
||||
|
||||
public enum ChequePageIssueStatus { Unused, Issued, Cleared, Bounced, Cancelled, Void }
|
||||
|
||||
public enum ChequePageStatusAction { Clear, Bounce, Cancel, Void }
|
||||
|
||||
public enum PayeeType { Supplier, Customer, Employee, Other }
|
||||
|
||||
public enum ReceivedChequeStatus { Received, Deposited, Cleared, Returned, Cancelled }
|
||||
|
||||
public enum ReceivedChequeStatusAction { Deposit, Clear, Return, Cancel }
|
||||
|
||||
public enum ReceivedFromType { Customer, Supplier, Other }
|
||||
}
|
||||
|
||||
@@ -19,8 +19,13 @@ namespace Distribution.Models
|
||||
public DateTime? FinalizedAt { get; set; }
|
||||
public Guid? FinalizedBy { get; set; }
|
||||
|
||||
/// <summary>Scheme discount redeemed against the PO, subtracted from the billable lines to give TotalAmount.</summary>
|
||||
public decimal DiscountAmount { get; set; }
|
||||
|
||||
/// <summary>Payable amount: billable lines minus DiscountAmount.</summary>
|
||||
public decimal TotalAmount { get; set; }
|
||||
|
||||
public ICollection<InvoiceItem> Items { get; set; } = new List<InvoiceItem>();
|
||||
public ICollection<Payment> Payments { get; set; } = new List<Payment>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public class InvoiceItem : Template
|
||||
{
|
||||
public Guid FK_InvoiceId { get; set; }
|
||||
[ForeignKey("FK_InvoiceId")]
|
||||
public Invoice Invoice { get; set; }
|
||||
|
||||
public Guid? FK_ShopPOItemId { get; set; }
|
||||
[ForeignKey("FK_ShopPOItemId")]
|
||||
public ShopPOItem? ShopPOItem { get; set; }
|
||||
|
||||
public Guid FK_ItemId { get; set; }
|
||||
[ForeignKey("FK_ItemId")]
|
||||
public Item Item { get; set; }
|
||||
|
||||
public int Quantity { get; set; }
|
||||
public decimal UnitPrice { get; set; }
|
||||
public bool IsRemoved { get; set; }
|
||||
|
||||
/// <summary>Free issue copied from the Shop PO line (UnitPrice 0).</summary>
|
||||
public bool IsFree { get; set; }
|
||||
public Guid? FK_SchemeId { get; set; }
|
||||
public string? SchemeName { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Distribution.Models
|
||||
{
|
||||
// Immutable snapshot of an invoice's lines at a point in its lifecycle.
|
||||
public class InvoiceVersion : Template
|
||||
{
|
||||
public Guid FK_InvoiceId { get; set; }
|
||||
[ForeignKey("FK_InvoiceId")]
|
||||
public Invoice Invoice { get; set; }
|
||||
|
||||
public int VersionNumber { get; set; }
|
||||
public InvoiceVersionStage Stage { get; set; }
|
||||
public decimal TotalAmount { get; set; }
|
||||
public Guid CreatedBy { get; set; }
|
||||
|
||||
public ICollection<InvoiceVersionItem> Items { get; set; } = new List<InvoiceVersionItem>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public class InvoiceVersionItem : Template
|
||||
{
|
||||
public Guid FK_InvoiceVersionId { get; set; }
|
||||
[ForeignKey("FK_InvoiceVersionId")]
|
||||
public InvoiceVersion InvoiceVersion { get; set; }
|
||||
|
||||
public Guid? FK_ShopPOItemId { get; set; }
|
||||
[ForeignKey("FK_ShopPOItemId")]
|
||||
public ShopPOItem? ShopPOItem { get; set; }
|
||||
|
||||
public Guid FK_ItemId { get; set; }
|
||||
[ForeignKey("FK_ItemId")]
|
||||
public Item Item { get; set; }
|
||||
|
||||
public int Quantity { get; set; }
|
||||
public decimal UnitPrice { get; set; }
|
||||
public bool IsRemoved { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public enum NotificationSeverity { Info, Success, Warning, Critical }
|
||||
|
||||
/// <summary>One row per event. Who it was delivered to lives in <see cref="NotificationRecipient"/>.</summary>
|
||||
public class Notification : Template
|
||||
{
|
||||
[MaxLength(60)]
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
public NotificationSeverity Severity { get; set; }
|
||||
|
||||
[MaxLength(150)]
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
[MaxLength(500)]
|
||||
public string Message { get; set; } = string.Empty;
|
||||
|
||||
[MaxLength(50)]
|
||||
public string EntityType { get; set; } = string.Empty;
|
||||
|
||||
public Guid EntityId { get; set; }
|
||||
|
||||
[MaxLength(200)]
|
||||
public string Link { get; set; } = string.Empty;
|
||||
|
||||
public Guid? CreatedByUserId { get; set; }
|
||||
|
||||
public ICollection<NotificationRecipient> Recipients { get; set; } = new List<NotificationRecipient>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Distribution.Models
|
||||
{
|
||||
/// <summary>One row per user per notification, so read state is per user.</summary>
|
||||
public class NotificationRecipient : Template
|
||||
{
|
||||
public Guid FK_NotificationId { get; set; }
|
||||
[ForeignKey("FK_NotificationId")]
|
||||
public Notification Notification { get; set; }
|
||||
|
||||
public Guid FK_UserId { get; set; }
|
||||
[ForeignKey("FK_UserId")]
|
||||
public User User { get; set; }
|
||||
|
||||
public bool IsRead { get; set; }
|
||||
public DateTime? ReadAt { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -16,5 +16,16 @@ namespace Distribution.Models
|
||||
public DateTime CollectedAt { get; set; }
|
||||
public Guid CollectedBy { get; set; }
|
||||
public Guid? RecordedBy { get; set; }
|
||||
|
||||
// Cash/Bank account that received the funds. Required when Method = Cash;
|
||||
// for Cheque, the FK_ReceivedCheque below carries the deposit account once deposited.
|
||||
public Guid? FK_BankAccountId { get; set; }
|
||||
[ForeignKey("FK_BankAccountId")]
|
||||
public BankAccount? BankAccount { get; set; }
|
||||
|
||||
// Cheque record auto-created when Method = Cheque. Null for Cash/Credit payments.
|
||||
public Guid? FK_ReceivedChequeId { get; set; }
|
||||
[ForeignKey("FK_ReceivedChequeId")]
|
||||
public ReceivedCheque? ReceivedCheque { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public class ReceivedCheque : Template
|
||||
{
|
||||
public int? CompanyId { get; set; }
|
||||
public int? BranchId { get; set; }
|
||||
|
||||
public ReceivedFromType ReceivedFromType { get; set; }
|
||||
public int? ReceivedFromId { get; set; }
|
||||
public string ReceivedFromName { get; set; } = string.Empty;
|
||||
|
||||
public string? DrawerBankName { get; set; }
|
||||
public string? DrawerBankBranch { get; set; }
|
||||
public string? AccountHolderName { get; set; }
|
||||
|
||||
public string ChequeNo { get; set; } = string.Empty;
|
||||
public DateTime ChequeDate { get; set; }
|
||||
public decimal Amount { get; set; }
|
||||
public DateTime ReceivedDate { get; set; }
|
||||
|
||||
public string? ReferenceType { get; set; }
|
||||
public int? ReferenceId { get; set; }
|
||||
public string? Notes { get; set; }
|
||||
public string? CreatedBy { get; set; }
|
||||
|
||||
public ReceivedChequeStatus Status { get; set; } = ReceivedChequeStatus.Received;
|
||||
|
||||
public Guid? FK_DepositBankAccountId { get; set; }
|
||||
[ForeignKey("FK_DepositBankAccountId")]
|
||||
public BankAccount? DepositBankAccount { get; set; }
|
||||
|
||||
public DateTime? DepositDate { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -15,5 +15,11 @@ namespace Distribution.Models
|
||||
public int Quantity { get; set; }
|
||||
public int QuantityFulfilled { get; set; } = 0;
|
||||
public decimal UnitPrice { get; set; }
|
||||
|
||||
/// <summary>Free issue granted by a scheme (UnitPrice 0); shown in the Free Issue column.</summary>
|
||||
public bool IsFree { get; set; }
|
||||
public Guid? FK_SchemeId { get; set; }
|
||||
/// <summary>Scheme name when it was applied, so later scheme edits don't rewrite history.</summary>
|
||||
public string? SchemeName { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public class StoreCreditMovement : Template
|
||||
{
|
||||
public Guid FK_StoreId { get; set; }
|
||||
[ForeignKey("FK_StoreId")]
|
||||
public Store? Store { get; set; }
|
||||
|
||||
public StoreCreditMovementDirection Direction { get; set; }
|
||||
|
||||
public decimal Amount { get; set; }
|
||||
|
||||
public Guid? FK_InvoiceId { get; set; }
|
||||
[ForeignKey("FK_InvoiceId")]
|
||||
public Invoice? Invoice { get; set; }
|
||||
|
||||
public Guid? FK_PaymentId { get; set; }
|
||||
[ForeignKey("FK_PaymentId")]
|
||||
public Payment? Payment { get; set; }
|
||||
|
||||
public DateTime OccurredAt { get; set; } = DateTime.Now;
|
||||
public Guid? RecordedBy { get; set; }
|
||||
public string? Notes { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public class SupplierPayment : Template
|
||||
{
|
||||
public Guid FK_SupplierId { get; set; }
|
||||
[ForeignKey("FK_SupplierId")]
|
||||
public Supplier? Supplier { get; set; }
|
||||
|
||||
public decimal Amount { get; set; }
|
||||
|
||||
// Only Cash or Cheque are valid for outgoing payments. Credit is rejected at the service layer.
|
||||
public PaymentMethod Method { get; set; }
|
||||
|
||||
// Cash: required. Null for Cheque.
|
||||
public Guid? FK_BankAccountId { get; set; }
|
||||
[ForeignKey("FK_BankAccountId")]
|
||||
public BankAccount? BankAccount { get; set; }
|
||||
|
||||
// Cheque: required — the ChequePage this payment issued. Null for Cash.
|
||||
public Guid? FK_ChequePageId { get; set; }
|
||||
[ForeignKey("FK_ChequePageId")]
|
||||
public ChequePage? ChequePage { get; set; }
|
||||
|
||||
public DateTime PaidAt { get; set; }
|
||||
public Guid? PaidByUserId { get; set; }
|
||||
public string? Notes { get; set; }
|
||||
|
||||
public ICollection<SupplierPaymentGRN> Allocations { get; set; } = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public class SupplierPaymentGRN : Template
|
||||
{
|
||||
public Guid FK_SupplierPaymentId { get; set; }
|
||||
[ForeignKey("FK_SupplierPaymentId")]
|
||||
public SupplierPayment? SupplierPayment { get; set; }
|
||||
|
||||
public Guid FK_GoodsReceiptId { get; set; }
|
||||
[ForeignKey("FK_GoodsReceiptId")]
|
||||
public GoodsReceipt? GoodsReceipt { get; set; }
|
||||
|
||||
[Precision(18, 2)]
|
||||
public decimal AllocatedAmount { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,13 @@ using Distribution.Features.Brand;
|
||||
using Distribution.Features.Category;
|
||||
using Distribution.Features.DeliveryRide;
|
||||
using Distribution.Features.Payment;
|
||||
using Distribution.Features.BankAccount;
|
||||
using Distribution.Features.ChequeBook;
|
||||
using Distribution.Features.ReceivedCheque;
|
||||
using Distribution.Features.DriverComplianceConfig;
|
||||
using Distribution.Features.DriverProfile;
|
||||
using Distribution.Features.Grn;
|
||||
using Distribution.Features.Notifications;
|
||||
using Distribution.Features.Item;
|
||||
using Distribution.Features.ShopPO;
|
||||
using Distribution.Features.Invoice;
|
||||
@@ -26,6 +30,7 @@ using Distribution.Features.Route;
|
||||
using Distribution.Features.Scheme;
|
||||
using Distribution.Features.Store;
|
||||
using Distribution.Features.Supplier;
|
||||
using Distribution.Features.SupplierPayment;
|
||||
using Distribution.Features.Sync;
|
||||
using Distribution.Features.Uom;
|
||||
using Distribution.Features.User;
|
||||
@@ -74,6 +79,8 @@ builder.Services.AddScoped<IProductReportRepository, ProductReportRepository>();
|
||||
builder.Services.AddScoped<ProductReportService>();
|
||||
builder.Services.AddScoped<IDashboardReportRepository, DashboardReportRepository>();
|
||||
builder.Services.AddScoped<DashboardReportService>();
|
||||
builder.Services.AddScoped<Distribution.Features.PaymentReport.IPaymentReportRepository, Distribution.Features.PaymentReport.PaymentReportRepository>();
|
||||
builder.Services.AddScoped<Distribution.Features.PaymentReport.PaymentReportService>();
|
||||
builder.Services.AddScoped<IStockReportRepository, StockReportRepository>();
|
||||
builder.Services.AddScoped<StockReportService>();
|
||||
builder.Services.AddScoped<IDriverProfileRepository, DriverProfileRepository>();
|
||||
@@ -92,6 +99,11 @@ builder.Services.AddScoped<ISupplierClaimRepository, SupplierClaimRepository>();
|
||||
builder.Services.AddScoped<ISysConfigRepository, SysConfigRepository>();
|
||||
builder.Services.AddScoped<IDeliveryTaskRepository, DeliveryTaskRepository>();
|
||||
builder.Services.AddScoped<IPaymentRepository, PaymentRepository>();
|
||||
builder.Services.AddScoped<IBankAccountRepository, BankAccountRepository>();
|
||||
builder.Services.AddScoped<IChequeBookRepository, ChequeBookRepository>();
|
||||
builder.Services.AddScoped<IReceivedChequeRepository, ReceivedChequeRepository>();
|
||||
builder.Services.AddScoped<ISupplierPaymentRepository, SupplierPaymentRepository>();
|
||||
builder.Services.AddScoped<Distribution.Features.StoreCredit.IStoreCreditMovementRepository, Distribution.Features.StoreCredit.StoreCreditMovementRepository>();
|
||||
|
||||
builder.Services.AddScoped<RegionService>();
|
||||
builder.Services.AddScoped<RouteService>();
|
||||
@@ -127,8 +139,21 @@ builder.Services.AddScoped<ClaimService>();
|
||||
builder.Services.AddScoped<SupplierClaimService>();
|
||||
builder.Services.AddScoped<DeliveryTaskService>();
|
||||
builder.Services.AddScoped<PaymentService>();
|
||||
builder.Services.AddScoped<BankAccountService>();
|
||||
builder.Services.AddScoped<ChequeBookService>();
|
||||
builder.Services.AddScoped<ReceivedChequeService>();
|
||||
builder.Services.AddScoped<SupplierPaymentService>();
|
||||
builder.Services.AddScoped<SysConfigService>();
|
||||
|
||||
builder.Services.AddScoped<INotificationRepository, NotificationRepository>();
|
||||
builder.Services.AddScoped<NotificationRecipientResolver>();
|
||||
builder.Services.AddScoped<NotificationService>();
|
||||
builder.Services.AddScoped<INotificationService>(sp => sp.GetRequiredService<NotificationService>());
|
||||
builder.Services.AddSingleton<Microsoft.AspNetCore.SignalR.IUserIdProvider, NotificationUserIdProvider>();
|
||||
builder.Services.AddHostedService<NotificationCleanupJob>();
|
||||
builder.Services.AddSignalR()
|
||||
.AddJsonProtocol(options => options.PayloadSerializerOptions.Converters.Add(new JsonStringEnumConverter()));
|
||||
|
||||
builder.Services.AddMemoryCache();
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
builder.Services.AddScoped<ICurrentUserService, CurrentUserService>();
|
||||
@@ -180,5 +205,6 @@ app.UseMiddleware<AuthMiddleware>();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.MapControllers();
|
||||
app.MapHub<NotificationHub>(NotificationHub.Path);
|
||||
|
||||
app.Run();
|
||||
|
||||
@@ -76,7 +76,7 @@ order must contain every component in at least its listed `Qty` before the schem
|
||||
SchemeRedemption
|
||||
├── Id (Guid) — PK
|
||||
├── FK_SchemeId (Guid) — required
|
||||
├── FK_OrderId (Guid?) — nullable (see FieldInvoiceId below)
|
||||
├── FK_ShopPOId (Guid?) — nullable (see FieldInvoiceId below); the Shop PO the scheme was applied to
|
||||
├── FieldInvoiceId (Guid?) — placeholder for the future field-invoice module; no FieldInvoice
|
||||
│ entity exists yet, so this is an unconstrained Guid for now
|
||||
├── DiscountAmount (decimal?) — set for SlabDiscount/FlatPercent redemptions
|
||||
@@ -87,8 +87,8 @@ SchemeRedemption
|
||||
│ later changes
|
||||
├── RedeemedAt (DateTime)
|
||||
```
|
||||
At least one of `DiscountAmount` or `FreeQty` is set per redemption; `Scheme`/`Order` restrict-delete
|
||||
(a scheme or order with redemptions can't be hard-deleted, and soft-delete leaves history intact).
|
||||
At least one of `DiscountAmount` or `FreeQty` is set per redemption; `Scheme`/`ShopPO` restrict-delete
|
||||
(a scheme or Shop PO with redemptions can't be hard-deleted, and soft-delete leaves history intact).
|
||||
|
||||
### Item (extended)
|
||||
```
|
||||
@@ -124,8 +124,10 @@ columns/child table after the first migration had already been applied to the de
|
||||
| — | `DELETE /api/schemes/{id}/free-goods-rule` | Clears it |
|
||||
| — | `PUT /api/schemes/{id}/bundle-components/{itemId}` | Upserts one FreeBundle required-item row |
|
||||
| — | `DELETE /api/schemes/{id}/bundle-components/{itemId}` | Removes one FreeBundle required-item row |
|
||||
| `evaluateSchemeEligibility(storeId, orderId)` | `GET /api/schemes/eligible?storeId=...&orderId=...` | Returns eligibility for every in-scope scheme, not just one |
|
||||
| — | `POST /api/schemes/suggest` | Body `{ storeId, items:[{itemId, quantity, unitPrice?}] }`; scheme suggestions for a Shop PO that is not saved yet |
|
||||
| `evaluateSchemeEligibility(storeId, shopPOId)` | `GET /api/schemes/eligible?storeId=...&orderId=...` (`orderId` is the Shop PO id) | Returns eligibility for every in-scope scheme, not just one |
|
||||
| `applySchemeDiscount(orderId, schemeId)` | `POST /api/orders/{id}/apply-scheme` | Adds a zero-price order line for free-goods payouts |
|
||||
| — | `POST /api/invoices/{id}/preview-totals` | Draft-editor preview of discount, total and free entitlement for unsaved lines |
|
||||
| `trackSchemeSpend(schemeId)` | `GET /api/schemes/{id}/spend` | Discount spend + free-goods cost spend |
|
||||
| `getSchemeROI(schemeId)` | `GET /api/schemes/{id}/roi` | Uses `BaselineExpectedSales` |
|
||||
|
||||
@@ -216,13 +218,18 @@ Use this to add/edit one bundle component after creation.
|
||||
[08_sales_agent_controls.md](./08_sales_agent_controls.md) unless `AgentLimitExempt = true`.
|
||||
(Enforcement against the actual ceiling is deferred — 08_sales_agent_controls.md's discount
|
||||
ceiling isn't implemented in code yet; this is recorded as the target behavior.)
|
||||
2. `evaluateSchemeEligibility` matches a scheme to a store/order only if: the scheme's `RegionId` is
|
||||
2. `evaluateSchemeEligibility` matches a scheme to a store/Shop PO only if: the scheme's `RegionId` is
|
||||
null or matches the store's region; `EligibleSegment` is null or matches `Store.Segment`; and the
|
||||
order's date falls within `[DateRangeFrom, DateRangeTo]`.
|
||||
3. `BuyXGetY` eligibility: the order must contain at least `TriggerQty` of `TriggerItem`; the payout
|
||||
Shop PO's date falls within `[DateRangeFrom, DateRangeTo]`. `DateRangeTo` is inclusive of the
|
||||
whole calendar day (compared by date, so a scheme ending `2026-08-31` still applies on the 31st).
|
||||
3. `BuyXGetY` eligibility: the Shop PO must contain at least `TriggerQty` of `TriggerItem`; the payout
|
||||
scales with how many multiples of `TriggerQty` were bought (`FreeQty × floor(boughtQty / TriggerQty)`).
|
||||
4. `FreeBundle` eligibility: the order must contain every `SchemeBundleComponent` in at least its
|
||||
listed `Qty`; payout is the flat `FreeQty` of `FreeItem` (no multiplier).
|
||||
Only **paid** lines (`UnitPrice > 0`) count toward `boughtQty` — free lines added by an earlier
|
||||
redemption never count toward a trigger, so "buy 10 get 1 of the same item" can't self-inflate.
|
||||
4. `FreeBundle` eligibility: the Shop PO must contain every `SchemeBundleComponent` in at least its
|
||||
listed `Qty` (paid lines only, same as rule 3); payout is the flat `FreeQty` of `FreeItem` (no multiplier).
|
||||
`FreeQty` must be a whole number (validated on create / `PUT .../free-goods-rule`), because
|
||||
Shop PO lines hold whole units.
|
||||
5. `applySchemeDiscount` adds a zero-price `OrderItem` line for the free-goods payout (`UnitPrice = 0`,
|
||||
`Quantity = FreeQty`), rather than only recording a discount amount — the free line still needs to
|
||||
be picked, delivered, and stock-tracked like any other sold line.
|
||||
@@ -240,6 +247,80 @@ Use this to add/edit one bundle component after creation.
|
||||
|
||||
---
|
||||
|
||||
## Offers on the PO and Invoice (implemented)
|
||||
|
||||
Nothing is added silently: the system **suggests**, the user **accepts**, and what was accepted stays **editable**.
|
||||
|
||||
### 1. On the Shop PO (new and edit screens)
|
||||
1. While paid lines are entered, the screen calls `POST /api/schemes/suggest` (`{ storeId, items:[{itemId, quantity, unitPrice?}] }`).
|
||||
The server returns the in-scope schemes (region, segment, date) the lines qualify for, plus Buy & get free /
|
||||
Combo gift schemes the cart is part-way to (`reason` doubles as a nudge, e.g. "Requires 10 of the trigger item;
|
||||
ShopPO has 6").
|
||||
2. The **Offers & Free Issues** panel shows:
|
||||
- **Free goods** (BuyXGetY / FreeBundle): "Qualifies for N free X" with **Add free**. The accepted free line is
|
||||
editable (qty, remove) and is saved as a line with `isFree = true` and the `schemeId`.
|
||||
- **Percentage discount** (FlatPercent / SlabDiscount): "Saves Rs. X" with **Apply discount**. Only one discount
|
||||
per order. The accepted scheme ids are sent as `discountSchemeIds` on create / update-items.
|
||||
3. If a later paid-qty change shrinks what the order earns, the screen trims the free qty / drops the discount and
|
||||
says so. The server enforces the same rules on save: free qty within the entitlement, free item in stock, scheme
|
||||
active and in scope, at most one discount, order still qualifies.
|
||||
4. Saving keeps `SchemeRedemption` in step with the lines: created or resized per free line, removed with its line,
|
||||
and discount redemptions created, recomputed, or dropped if the order stops qualifying.
|
||||
5. `GET /api/shop-pos/{id}` returns `discounts[]` (scheme, amount) so the edit screen starts with the right state and
|
||||
the PO view shows a "Scheme discount" row above the total. `PO.TotalAmount` stays the full lines total.
|
||||
|
||||
### 2. On the invoice
|
||||
- The invoice copies the PO lines, including free lines (`IsFree`, `FK_SchemeId`, `SchemeName`). Free lines show in a
|
||||
**Free Issue** column (a "-" in the cell that does not apply), are listed **after all paid lines**, are zero-priced
|
||||
(price cannot be edited) and can be lowered like any line while the invoice is Draft.
|
||||
- `Invoice.TotalAmount = billable lines - DiscountAmount`. When the invoice is created from a PO it starts at the
|
||||
discounted total.
|
||||
- **Draft invoice editor** (`/sales/invoices/{id}/edit`):
|
||||
- A **Scheme discount** box lists the flat / slab discounts the current lines qualify for with **Apply discount** /
|
||||
**Remove**. This also covers a PO that was confirmed before a discount was applied. `PUT /api/invoices/{id}/items`
|
||||
accepts `discountSchemeIds` (null = leave as is).
|
||||
- `POST /api/invoices/{id}/preview-totals` (`{ items:[{itemId, quantity, unitPrice, isFree, isRemoved, schemeId}], discountSchemeIds? }`)
|
||||
returns the discount, total and per-scheme free entitlement for the unsaved lines; the editor calls it (debounced)
|
||||
so the Scheme discount row and Total update while typing. It changes nothing.
|
||||
- **Recalculation.** On every line change (add, remove, restore, re-price, quantity) and again at print and finalize,
|
||||
`InvoiceService.ApplyTotalsAsync` first reconciles free lines, then re-evaluates the discount against the current lines:
|
||||
- FlatPercent stays the set % of the current lines total; SlabDiscount can move up or down a level, or drop to 0 and
|
||||
return if the lines requalify. Free lines and struck-out lines are ignored. The redemption's `DiscountAmount` is
|
||||
updated with it, so scheme spend / ROI stay correct.
|
||||
- **Free lines only shrink automatically.** If the paid quantity that earned a free issue is lowered or removed, the free
|
||||
quantity is reduced to what is still earned (a line that earns nothing is struck out, keeping its quantity so it can be
|
||||
restored) and the PO's free-goods redemption is resized. Free quantity is never raised automatically; a person raises it,
|
||||
up to what the scheme earns.
|
||||
- The invoice page's Items table ends with **Subtotal / Scheme discount / Total**, and the printed A4 and receipt copies show
|
||||
Subtotal, Scheme discount, Total (Paid / Balance Due use the discounted total).
|
||||
- The older `POST /api/orders/{id}/apply-scheme` endpoint still works for an already-saved PO and flags the line the same way
|
||||
(and mirrors a free line onto a still-Draft invoice).
|
||||
|
||||
### 3. Scheme setup screens
|
||||
`/pricing/schemes/new`, `/pricing/schemes/{id}` (read-only) and `/pricing/schemes/{id}/edit` are full pages (no modal) built on one
|
||||
form. Rule types are shown in plain words: **Buy & get free** (BuyXGetY), **Combo gift** (FreeBundle), **Flat discount**
|
||||
(FlatPercent), **Spend more, save more** (SlabDiscount); the fields adapt to the type (product dropdowns, quantities, % and
|
||||
levels) with a live summary sentence. Required fields come first; Active, then the optional Region / Store segment last. The scheme
|
||||
name is optional (generated from the offer when blank). The rule type cannot change after creation.
|
||||
|
||||
### Data
|
||||
`ShopPOItem` and `InvoiceItem` gained `IsFree`, `FK_SchemeId` (plain Guid, no FK constraint) and a `SchemeName` snapshot. Migration
|
||||
`AddFreeIssueLines` also back-fills existing zero-priced redemption lines.
|
||||
|
||||
### Not done yet
|
||||
`SchemeRedemption.FK_InvoiceId` (`FieldInvoiceId` is still an unused placeholder); invoice-version snapshots do not record `IsFree`;
|
||||
no hint or one-click "add more free units" when the paid quantity rises; scope (date / region) is checked when a discount is first
|
||||
applied, not re-checked on later invoice edits; Slab "close to the next level" hints are not shown.
|
||||
|
||||
## Known Gaps vs. Enterprise Practice
|
||||
|
||||
Scheme code, lifecycle (Draft/Approved/Active/Expired), priority and stackable/exclusive flags;
|
||||
budget and per-store caps; triggers on product groups/brands/categories or order value; tiered
|
||||
free-goods slabs; funding source (company vs supplier — ties into SupplierClaims and GRN
|
||||
`FreeQuantity`); stock reservation for the free item (the current check only sums balances).
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- What counts as "incremental sales" for `getSchemeROI` — **resolved for v1**: a manual
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user