Compare commits
76 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ca81e9bc42 | |||
| cc33ecdd54 | |||
| 5707bd4c31 | |||
| f77db46a02 | |||
| 28c465f59b | |||
| 21b38b5276 | |||
| 267144f7a1 | |||
| 992cd292fa | |||
| efb5657b6b | |||
| 7b6c9ac92a | |||
| 622b75e187 | |||
| eb0b042f0c | |||
| 293df41d4e | |||
| a980e636f5 | |||
| b992d07689 | |||
| 5e81fd77b1 | |||
| a4d58f3377 | |||
| 94db2a311c | |||
| 6b608ad2ef | |||
| a1fd5f1a08 | |||
| 87c807c16c | |||
| 3c75f9786e | |||
| 2d6723a040 | |||
| 2fe2ba007c | |||
| f3f2b71bd4 | |||
| bc72426495 | |||
| da29492798 | |||
| ce21aa6588 | |||
| f44cbf711b | |||
| aaac42b481 | |||
| 7a21cde217 | |||
| 531b3a7fef | |||
| a81e32bbe2 | |||
| 4ef63420b7 | |||
| 9fdf5aa8e3 | |||
| d9c339949e | |||
| 5d18aba1f9 | |||
| 5828879ddc | |||
| d75a43d45f | |||
| 728f2a8b17 | |||
| 1b66d5d767 | |||
| c5a0992b5d | |||
| 09d86835a2 | |||
| d1d2d55352 | |||
| c626f56cdb | |||
| 263e2ea9c0 | |||
| 5c37f02f19 | |||
| 6b3f28d3ae | |||
| e5f6aa15b9 | |||
| 4e12b03e4b | |||
| f5967497be | |||
| 4110e2fcfc | |||
| c52eaabec4 | |||
| 37f4a88014 | |||
| 2d365cd906 | |||
| c68c6e1ab9 | |||
| ddc3ecd3e6 | |||
| d7b6f29200 | |||
| d2277f626c | |||
| 3149902574 | |||
| 37fb72376a | |||
| 52a9a3fde7 | |||
| 1b310c86b3 | |||
| e398ee9d6b | |||
| a3c0211654 | |||
| e4e12aa05a | |||
| 3103fb66ef | |||
| 93355d34f1 | |||
| 9151f47d15 | |||
| 661b522b7d | |||
| e0bb17d0ff | |||
| 0f29413375 | |||
| ef17f7da0a | |||
| d9031f8e73 | |||
| 75578ea7f6 | |||
| 4f02fa303d |
@@ -65,6 +65,14 @@ 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<GlPostingLog> GlPostingLogs { 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<StoreCreditMovement> StoreCreditMovements { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
@@ -575,6 +583,16 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
|
||||
.HasIndex(r => r.FK_RegionId)
|
||||
.IsUnique();
|
||||
|
||||
// --- GL posting log ---
|
||||
|
||||
modelBuilder.Entity<GlPostingLog>()
|
||||
.HasIndex(l => new { l.Event, l.SourceId })
|
||||
.IsUnique();
|
||||
|
||||
modelBuilder.Entity<GlPostingLog>()
|
||||
.Property(l => l.Event)
|
||||
.HasMaxLength(60);
|
||||
|
||||
// --- SysConfig ---
|
||||
|
||||
modelBuilder.Entity<SysConfig>()
|
||||
@@ -594,6 +612,141 @@ 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);
|
||||
|
||||
// --- 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);
|
||||
|
||||
@@ -68,6 +68,8 @@ public static class RoleAndPermissionSeed
|
||||
private static readonly Guid PermSupplierClaimManage= new("20000000-0000-0000-0000-000000000048");
|
||||
private static readonly Guid PermSysConfigView = new("20000000-0000-0000-0000-000000000049");
|
||||
private static readonly Guid PermSysConfigManage = new("20000000-0000-0000-0000-000000000050");
|
||||
private static readonly Guid PermFinanceReportView = new("20000000-0000-0000-0000-000000000051");
|
||||
private static readonly Guid PermGlManage = new("20000000-0000-0000-0000-000000000052");
|
||||
|
||||
public static void Apply(ModelBuilder modelBuilder)
|
||||
{
|
||||
@@ -134,7 +136,9 @@ public static class RoleAndPermissionSeed
|
||||
new Permission { Id = PermSupplierClaimView, Code = "supplier-claim.view", Description = "View supplier claims", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermSupplierClaimManage,Code = "supplier-claim.manage", Description = "Create and resolve supplier claims", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermSysConfigView, Code = "sys-config.view", Description = "View system configuration settings", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermSysConfigManage, Code = "sys-config.manage", Description = "Update system configuration settings", CreatedAt = SeedDate, IsActive = true }
|
||||
new Permission { Id = PermSysConfigManage, Code = "sys-config.manage", Description = "Update system configuration settings", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermFinanceReportView, Code = "finance-reports.view", Description = "View financial reports (trial balance, P&L, balance sheet, cash flow...)", CreatedAt = SeedDate, IsActive = true },
|
||||
new Permission { Id = PermGlManage, Code = "gl.manage", Description = "Set up the General Ledger link and retry failed postings", CreatedAt = SeedDate, IsActive = true }
|
||||
);
|
||||
|
||||
// SuperAdmin gets every permission
|
||||
@@ -191,7 +195,9 @@ public static class RoleAndPermissionSeed
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermSupplierClaimView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermSupplierClaimManage,CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermSysConfigView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermSysConfigManage, CreatedAt = SeedDate, IsActive = true }
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermSysConfigManage, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermFinanceReportView, CreatedAt = SeedDate, IsActive = true },
|
||||
new RolePermission { FK_RoleId = SuperAdminRoleId, FK_PermissionId = PermGlManage, CreatedAt = SeedDate, IsActive = true }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,179 @@
|
||||
using Distribution.Features.BankAccount;
|
||||
using Distribution.Features.GeneralLedger;
|
||||
using Distribution.Infra.UoW;
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.ChequeBook;
|
||||
|
||||
public class ChequeBookService(
|
||||
IChequeBookRepository repo,
|
||||
IBankAccountRepository bankAccountRepo,
|
||||
IUnitOfWork uow,
|
||||
GlPostingService gl)
|
||||
{
|
||||
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);
|
||||
await gl.PostIssuedChequeAsync(page, request.Action, 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);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.DashboardReport;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/reports/dashboard")]
|
||||
public class DashboardReportController(DashboardReportService service) : ControllerBase
|
||||
{
|
||||
[HttpGet("sales")]
|
||||
public async Task<IActionResult> Sales(
|
||||
[FromQuery] DateTime? from, [FromQuery] DateTime? to,
|
||||
[FromQuery] Guid? regionId, [FromQuery] Guid? categoryId)
|
||||
{
|
||||
var f = DashboardReportService.BuildFilter(from, to, regionId, categoryId, null);
|
||||
var error = DashboardReportService.Validate(f);
|
||||
return error is null ? Ok(await service.GetSalesSummaryAsync(f)) : BadRequest(new { message = error });
|
||||
}
|
||||
|
||||
[HttpGet("inventory")]
|
||||
public async Task<IActionResult> Inventory(
|
||||
[FromQuery] DateTime? from, [FromQuery] DateTime? to,
|
||||
[FromQuery] Guid? warehouseId, [FromQuery] Guid? categoryId)
|
||||
{
|
||||
var f = DashboardReportService.BuildFilter(from, to, null, categoryId, warehouseId);
|
||||
var error = DashboardReportService.Validate(f);
|
||||
return error is null ? Ok(await service.GetInventorySummaryAsync(f)) : BadRequest(new { message = error });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
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 SalesKpis(
|
||||
decimal GrossSales, decimal ReturnValue, decimal NetSales, int Orders, decimal AverageOrderValue,
|
||||
decimal GrossProfit, decimal? GrossProfitMarginPercent);
|
||||
|
||||
public record SalesKpiComparison(SalesKpis Current, SalesKpis Previous, DateTime PreviousFrom, DateTime PreviousTo);
|
||||
|
||||
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,
|
||||
List<NamedAmount> ByCategory, List<NamedAmount> ByRegion, List<NamedAmount> ByRep);
|
||||
|
||||
public record PeriodAmount(DateTime Date, decimal Amount);
|
||||
|
||||
public record MovementPoint(string Period, decimal Received, decimal Issued, decimal Net);
|
||||
|
||||
public record WarehouseStockRow(Guid WarehouseId, string Name, decimal Quantity, decimal Value);
|
||||
|
||||
public record CategoryValueRow(string Category, decimal Quantity, decimal Value);
|
||||
|
||||
public record InventorySummaryResponse(
|
||||
DateTime From, DateTime To, string Granularity,
|
||||
List<CategoryValueRow> ValueByCategory, List<WarehouseStockRow> ByWarehouse, List<MovementPoint> MovementTrend);
|
||||
@@ -0,0 +1,171 @@
|
||||
using Distribution.Data;
|
||||
using Distribution.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.DashboardReport;
|
||||
|
||||
public class DashboardReportRepository(AppDBContext db) : IDashboardReportRepository
|
||||
{
|
||||
private sealed class SoldLine
|
||||
{
|
||||
public DateTime Date { get; set; }
|
||||
public Guid PoId { get; set; }
|
||||
public decimal Value { get; set; }
|
||||
public decimal? Cost { get; set; }
|
||||
public Guid CategoryId { get; set; }
|
||||
public string CategoryName { get; set; } = "";
|
||||
public Guid RegionId { get; set; }
|
||||
public string RegionName { get; set; } = "";
|
||||
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).
|
||||
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
|
||||
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,
|
||||
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,
|
||||
};
|
||||
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);
|
||||
return q;
|
||||
}
|
||||
|
||||
private IQueryable<SaleReturnItem> ReturnLines(DashboardFilter f)
|
||||
{
|
||||
var start = f.From.Date;
|
||||
var end = f.To.Date.AddDays(1);
|
||||
var q = db.SaleReturnItems.AsNoTracking()
|
||||
.Where(r => r.SaleReturn.CreatedAt >= start && r.SaleReturn.CreatedAt < end);
|
||||
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;
|
||||
}
|
||||
|
||||
public async Task<SalesTotals> GetSalesTotalsAsync(DashboardFilter f)
|
||||
{
|
||||
var lines = SoldLines(f);
|
||||
var gross = await lines.SumAsync(l => (decimal?)l.Value) ?? 0m;
|
||||
var orders = await lines.Select(l => l.PoId).Distinct().CountAsync();
|
||||
var costed = lines.Where(l => l.Cost != null);
|
||||
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);
|
||||
}
|
||||
|
||||
public async Task<List<PeriodAmount>> GetDailySalesAsync(DashboardFilter f) =>
|
||||
(await SoldLines(f).GroupBy(l => l.Date)
|
||||
.Select(g => new { Date = g.Key, Amount = g.Sum(x => x.Value) })
|
||||
.ToListAsync())
|
||||
.Select(x => new PeriodAmount(x.Date, x.Amount)).ToList();
|
||||
|
||||
public async Task<List<PeriodAmount>> GetDailyReturnsAsync(DashboardFilter f) =>
|
||||
(await ReturnLines(f).GroupBy(r => r.SaleReturn.CreatedAt.Date)
|
||||
.Select(g => new { Date = g.Key, Amount = g.Sum(x => x.Quantity * x.UnitPrice) })
|
||||
.ToListAsync())
|
||||
.Select(x => new PeriodAmount(x.Date, x.Amount)).ToList();
|
||||
|
||||
public async Task<List<GroupAggregate>> GetSalesByCategoryAsync(DashboardFilter f) =>
|
||||
(await SoldLines(f).GroupBy(l => new { l.CategoryId, l.CategoryName })
|
||||
.Select(g => new { g.Key.CategoryId, g.Key.CategoryName, Amount = g.Sum(x => x.Value) })
|
||||
.OrderByDescending(x => x.Amount).ToListAsync())
|
||||
.Select(x => new GroupAggregate(x.CategoryId, x.CategoryName, x.Amount)).ToList();
|
||||
|
||||
public async Task<List<GroupAggregate>> GetSalesByRegionAsync(DashboardFilter f) =>
|
||||
(await SoldLines(f).GroupBy(l => new { l.RegionId, l.RegionName })
|
||||
.Select(g => new { g.Key.RegionId, g.Key.RegionName, Amount = g.Sum(x => x.Value) })
|
||||
.OrderByDescending(x => x.Amount).ToListAsync())
|
||||
.Select(x => new GroupAggregate(x.RegionId, x.RegionName, x.Amount)).ToList();
|
||||
|
||||
public async Task<List<GroupAggregate>> GetSalesByRepAsync(DashboardFilter f)
|
||||
{
|
||||
var rows = await SoldLines(f).GroupBy(l => l.RepId)
|
||||
.Select(g => new { RepId = g.Key, Amount = g.Sum(x => x.Value) })
|
||||
.OrderByDescending(x => x.Amount).Take(20).ToListAsync();
|
||||
var ids = rows.Select(r => r.RepId).ToList();
|
||||
var names = await db.Users.AsNoTracking().Where(u => ids.Contains(u.Id))
|
||||
.ToDictionaryAsync(u => u.Id, u => u.FullName);
|
||||
return rows.Select(r => new GroupAggregate(r.RepId, names.GetValueOrDefault(r.RepId, "Unknown"), r.Amount)).ToList();
|
||||
}
|
||||
|
||||
private IQueryable<StockBalance> Balances(DashboardFilter f)
|
||||
{
|
||||
var q = db.StockBalances.AsNoTracking().AsQueryable();
|
||||
if (f.WarehouseId.HasValue)
|
||||
q = q.Where(b => b.LocationType == LocationType.Warehouse && b.LocationId == f.WarehouseId.Value);
|
||||
if (f.CategoryId.HasValue) q = q.Where(b => b.Item.FK_CategoryId == f.CategoryId.Value);
|
||||
return q;
|
||||
}
|
||||
|
||||
// Value = quantity x cost price; items without a cost price contribute quantity only.
|
||||
public async Task<List<CategoryValueRow>> GetStockValueByCategoryAsync(DashboardFilter f) =>
|
||||
(await Balances(f).GroupBy(b => b.Item.Category.Name)
|
||||
.Select(g => new
|
||||
{
|
||||
Category = g.Key,
|
||||
Quantity = g.Sum(x => x.Quantity),
|
||||
Value = g.Sum(x => x.Quantity * (x.Item.CostPrice ?? 0m)),
|
||||
})
|
||||
.OrderByDescending(x => x.Value).ToListAsync())
|
||||
.Select(x => new CategoryValueRow(x.Category, x.Quantity, x.Value)).ToList();
|
||||
|
||||
public async Task<List<WarehouseStockRow>> GetStockByWarehouseAsync(DashboardFilter f)
|
||||
{
|
||||
var rows = await Balances(f).Where(b => b.LocationType == LocationType.Warehouse)
|
||||
.GroupBy(b => b.LocationId)
|
||||
.Select(g => new
|
||||
{
|
||||
Id = g.Key,
|
||||
Quantity = g.Sum(x => x.Quantity),
|
||||
Value = g.Sum(x => x.Quantity * (x.Item.CostPrice ?? 0m)),
|
||||
})
|
||||
.ToListAsync();
|
||||
var ids = rows.Select(r => r.Id).ToList();
|
||||
var names = await db.Warehouses.AsNoTracking().Where(w => ids.Contains(w.Id))
|
||||
.ToDictionaryAsync(w => w.Id, w => w.Name);
|
||||
return rows.Select(r => new WarehouseStockRow(r.Id, names.GetValueOrDefault(r.Id, "Unknown"), r.Quantity, r.Value))
|
||||
.OrderByDescending(r => r.Value).ToList();
|
||||
}
|
||||
|
||||
// Received = purchase receipts + customer returns; Issued = sales + purchase returns + damages.
|
||||
// Transfers, loads and adjustments are internal/signed and excluded from this trend.
|
||||
public async Task<List<(DateTime Date, decimal Received, decimal Issued)>> GetDailyMovementsAsync(DashboardFilter f)
|
||||
{
|
||||
var start = f.From.Date;
|
||||
var end = f.To.Date.AddDays(1);
|
||||
var q = db.StockMovements.AsNoTracking()
|
||||
.Where(m => m.CreatedAt >= start && m.CreatedAt < end
|
||||
&& (m.Type == MovementType.PurchaseReceipt || m.Type == MovementType.CustomerReturn
|
||||
|| m.Type == MovementType.Sale || m.Type == MovementType.PurchaseReturn || m.Type == MovementType.Damage));
|
||||
if (f.CategoryId.HasValue) q = q.Where(m => m.Item.FK_CategoryId == f.CategoryId.Value);
|
||||
if (f.WarehouseId.HasValue)
|
||||
q = q.Where(m => m.ToLocationId == f.WarehouseId.Value || m.FromLocationId == f.WarehouseId.Value);
|
||||
|
||||
var rows = await q.GroupBy(m => new { m.CreatedAt.Date, m.Type })
|
||||
.Select(g => new { g.Key.Date, g.Key.Type, Qty = g.Sum(x => x.Quantity) })
|
||||
.ToListAsync();
|
||||
return rows.GroupBy(r => r.Date)
|
||||
.Select(g => (g.Key,
|
||||
g.Where(r => r.Type is MovementType.PurchaseReceipt or MovementType.CustomerReturn).Sum(r => r.Qty),
|
||||
g.Where(r => r.Type is MovementType.Sale or MovementType.PurchaseReturn or MovementType.Damage).Sum(r => r.Qty)))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
namespace Distribution.Features.DashboardReport;
|
||||
|
||||
public class DashboardReportService(IDashboardReportRepository repo)
|
||||
{
|
||||
public const int MaxRangeDays = 366;
|
||||
private const int DailyGranularityMaxDays = 62;
|
||||
|
||||
public static DashboardFilter BuildFilter(DateTime? from, DateTime? to, Guid? regionId, Guid? categoryId, Guid? warehouseId)
|
||||
{
|
||||
var end = (to ?? DateTime.Now).Date;
|
||||
var start = (from ?? end.AddDays(-29)).Date;
|
||||
return new DashboardFilter(start, end, regionId, categoryId, warehouseId);
|
||||
}
|
||||
|
||||
public static string? Validate(DashboardFilter 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 bool Daily(DashboardFilter f) => (f.To - f.From).TotalDays <= DailyGranularityMaxDays;
|
||||
|
||||
private static string Bucket(DateTime d, bool daily) => daily ? d.ToString("yyyy-MM-dd") : d.ToString("yyyy-MM");
|
||||
|
||||
private static SalesKpis ToKpis(SalesTotals t)
|
||||
{
|
||||
var net = t.GrossSales - t.ReturnValue;
|
||||
var gp = t.CostedSales - t.CostOfSales;
|
||||
return new SalesKpis(
|
||||
t.GrossSales, t.ReturnValue, 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);
|
||||
}
|
||||
|
||||
public async Task<SalesSummaryResponse> GetSalesSummaryAsync(DashboardFilter f)
|
||||
{
|
||||
var days = (f.To - f.From).Days + 1;
|
||||
var prev = f with { From = f.From.AddDays(-days), To = f.From.AddDays(-1) };
|
||||
var daily = Daily(f);
|
||||
|
||||
var current = await repo.GetSalesTotalsAsync(f);
|
||||
var previous = await repo.GetSalesTotalsAsync(prev);
|
||||
var sales = await repo.GetDailySalesAsync(f);
|
||||
var returns = await repo.GetDailyReturnsAsync(f);
|
||||
|
||||
var trend = sales.Select(s => (s.Date, Sales: s.Amount, Returns: 0m))
|
||||
.Concat(returns.Select(r => (r.Date, Sales: 0m, Returns: r.Amount)))
|
||||
.GroupBy(x => Bucket(x.Date, daily))
|
||||
.OrderBy(g => g.Key)
|
||||
.Select(g => new TrendPoint(g.Key, g.Sum(x => x.Sales), g.Sum(x => x.Returns)))
|
||||
.ToList();
|
||||
|
||||
static NamedAmount Map(GroupAggregate g) => new(g.Key, g.Name, g.Amount);
|
||||
return new SalesSummaryResponse(
|
||||
f.From, f.To, daily ? "day" : "month",
|
||||
new SalesKpiComparison(ToKpis(current), ToKpis(previous), prev.From, prev.To),
|
||||
trend,
|
||||
(await repo.GetSalesByCategoryAsync(f)).Select(Map).ToList(),
|
||||
(await repo.GetSalesByRegionAsync(f)).Select(Map).ToList(),
|
||||
(await repo.GetSalesByRepAsync(f)).Select(Map).ToList());
|
||||
}
|
||||
|
||||
public async Task<InventorySummaryResponse> GetInventorySummaryAsync(DashboardFilter f)
|
||||
{
|
||||
var daily = Daily(f);
|
||||
var movements = (await repo.GetDailyMovementsAsync(f))
|
||||
.GroupBy(m => Bucket(m.Date, daily))
|
||||
.OrderBy(g => g.Key)
|
||||
.Select(g =>
|
||||
{
|
||||
var rec = g.Sum(x => x.Received);
|
||||
var iss = g.Sum(x => x.Issued);
|
||||
return new MovementPoint(g.Key, rec, iss, rec - iss);
|
||||
}).ToList();
|
||||
|
||||
return new InventorySummaryResponse(
|
||||
f.From, f.To, daily ? "day" : "month",
|
||||
await repo.GetStockValueByCategoryAsync(f),
|
||||
await repo.GetStockByWarehouseAsync(f),
|
||||
movements);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace Distribution.Features.DashboardReport;
|
||||
|
||||
public record GroupAggregate(Guid? Key, string Name, decimal Amount);
|
||||
|
||||
public interface IDashboardReportRepository
|
||||
{
|
||||
Task<SalesTotals> GetSalesTotalsAsync(DashboardFilter f);
|
||||
Task<List<PeriodAmount>> GetDailySalesAsync(DashboardFilter f);
|
||||
Task<List<PeriodAmount>> GetDailyReturnsAsync(DashboardFilter f);
|
||||
Task<List<GroupAggregate>> GetSalesByCategoryAsync(DashboardFilter f);
|
||||
Task<List<GroupAggregate>> GetSalesByRegionAsync(DashboardFilter f);
|
||||
Task<List<GroupAggregate>> GetSalesByRepAsync(DashboardFilter f);
|
||||
Task<List<CategoryValueRow>> GetStockValueByCategoryAsync(DashboardFilter f);
|
||||
Task<List<WarehouseStockRow>> GetStockByWarehouseAsync(DashboardFilter f);
|
||||
Task<List<(DateTime Date, decimal Received, decimal Issued)>> GetDailyMovementsAsync(DashboardFilter f);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -6,7 +7,7 @@ namespace Distribution.Features.DeliveryRide;
|
||||
|
||||
[ApiController]
|
||||
[Route("api")]
|
||||
public class DeliveryTaskController(DeliveryTaskService service) : ControllerBase
|
||||
public class DeliveryTaskController(DeliveryTaskService service, ICurrentUserService currentUser) : ControllerBase
|
||||
{
|
||||
[HttpPost("rides/{rideId:guid}/tasks/generate")]
|
||||
[Authorize("delivery-task.manage")]
|
||||
@@ -68,7 +69,7 @@ public class DeliveryTaskController(DeliveryTaskService service) : ControllerBas
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await service.ConfirmAsync(id, ct);
|
||||
var result = await service.ConfirmAsync(id, currentUser.UserId ?? Guid.Empty, ct);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (KeyNotFoundException) { return NotFound(); }
|
||||
|
||||
@@ -12,7 +12,7 @@ public class DeliveryTaskService(
|
||||
IRideRepository rideRepo,
|
||||
IShopPORepository shopPORepo,
|
||||
ILoadingSheetRepository loadingSheetRepo,
|
||||
IStockRepository stockRepo,
|
||||
StockMover mover,
|
||||
IUnitOfWork uow)
|
||||
{
|
||||
private static DeliveryTaskResponse ToResponse(DeliveryTask t) =>
|
||||
@@ -134,7 +134,7 @@ public class DeliveryTaskService(
|
||||
return ToResponse(saved!);
|
||||
}
|
||||
|
||||
public async Task<DeliveryTaskResponse> ConfirmAsync(Guid taskId, CancellationToken ct = default)
|
||||
public async Task<DeliveryTaskResponse> ConfirmAsync(Guid taskId, Guid userId, CancellationToken ct = default)
|
||||
{
|
||||
var task = await repo.GetByIdAsync(taskId)
|
||||
?? throw new KeyNotFoundException("Delivery task not found");
|
||||
@@ -166,29 +166,12 @@ public class DeliveryTaskService(
|
||||
{
|
||||
foreach (var item in task.ShopPO.Items.Where(i => i.QuantityFulfilled > 0))
|
||||
{
|
||||
var vehicleBalance = await stockRepo.GetBalanceAsync(item.FK_ItemId, vehicleId);
|
||||
var availableQuantity = vehicleBalance?.Quantity ?? 0;
|
||||
var itemName = item.Item?.Name ?? item.FK_ItemId.ToString();
|
||||
|
||||
if (availableQuantity < item.QuantityFulfilled)
|
||||
throw new InvalidOperationException(
|
||||
$"Insufficient vehicle stock for item '{itemName}': available {availableQuantity}, required {item.QuantityFulfilled}");
|
||||
|
||||
vehicleBalance!.Quantity -= item.QuantityFulfilled;
|
||||
vehicleBalance.UpdatedAt = DateTime.Now;
|
||||
stockRepo.UpdateBalance(vehicleBalance);
|
||||
|
||||
await stockRepo.AddMovementAsync(new StockMovement
|
||||
{
|
||||
FK_ItemId = item.FK_ItemId,
|
||||
FromLocationId = vehicleId,
|
||||
FromLocationType = LocationType.Vehicle,
|
||||
ToLocationId = StockService.ExternalLocationId,
|
||||
ToLocationType = LocationType.External,
|
||||
Quantity = item.QuantityFulfilled,
|
||||
Type = MovementType.Sale,
|
||||
ReferenceId = task.FK_ShopPOId
|
||||
});
|
||||
await mover.MoveAsync(item.FK_ItemId, itemName,
|
||||
vehicleId, LocationType.Vehicle,
|
||||
StockService.ExternalLocationId, LocationType.External,
|
||||
item.QuantityFulfilled, MovementType.Sale, task.FK_ShopPOId, userId);
|
||||
}
|
||||
|
||||
repo.Update(task);
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Distribution.Features.GeneralLedger;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.FinanceReport;
|
||||
|
||||
/// <summary>
|
||||
/// Proxies the General Ledger service's reports (TrialBalance, GeneralLedger, BalanceSheet, ProfitAndLoss, CashFlow,
|
||||
/// BudgetVsActual, TaxSummary). Query parameters are forwarded as-is; Json is returned as the GL data payload and
|
||||
/// Pdf/Csv are returned as downloadable files.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/reports/finance")]
|
||||
public class FinanceReportController(IGeneralLedgerClient gl) : ControllerBase
|
||||
{
|
||||
/// <summary>Active GL accounts (code + name) for the report filters' account dropdown.</summary>
|
||||
[HttpGet("accounts")]
|
||||
[Authorize("finance-reports.view")]
|
||||
public async Task<IActionResult> Accounts(CancellationToken ct)
|
||||
{
|
||||
GeneralLedgerResult result;
|
||||
try { result = await gl.SendAsync(HttpMethod.Get, "v1/accounts?isActive=true", null, ct); }
|
||||
catch (HttpRequestException) { return StatusCode(502, new { error = "The General Ledger service is unreachable." }); }
|
||||
|
||||
if (!result.Success || GlJson.Data(result) is not { } data)
|
||||
return StatusCode(502, new { error = GlJson.Message(result) ?? "Could not load GL accounts." });
|
||||
|
||||
var items = GlJson.Get(data, "items");
|
||||
var accounts = items is { ValueKind: JsonValueKind.Array }
|
||||
? items.Value.EnumerateArray()
|
||||
.Select(a => new { accountCode = GlJson.GetString(a, "accountCode") ?? "", accountName = GlJson.GetString(a, "accountName") ?? "" })
|
||||
.OrderBy(a => a.accountCode, StringComparer.Ordinal)
|
||||
.ToList()
|
||||
: [];
|
||||
return Ok(accounts);
|
||||
}
|
||||
|
||||
[HttpGet("{reportType}")]
|
||||
[Authorize("finance-reports.view")]
|
||||
public async Task<IActionResult> Get(string reportType, [FromQuery] string outputFormat = "Json", CancellationToken ct = default)
|
||||
{
|
||||
var query = new StringBuilder($"v1/reports?reportType={Uri.EscapeDataString(reportType)}&outputFormat={Uri.EscapeDataString(outputFormat)}");
|
||||
foreach (var (key, values) in Request.Query)
|
||||
{
|
||||
if (key is "reportType" or "outputFormat") continue;
|
||||
foreach (var v in values)
|
||||
query.Append('&').Append(Uri.EscapeDataString(key)).Append('=').Append(Uri.EscapeDataString(v ?? ""));
|
||||
}
|
||||
|
||||
GeneralLedgerResult result;
|
||||
try { result = await gl.SendAsync(HttpMethod.Get, query.ToString(), null, ct); }
|
||||
catch (HttpRequestException) { return StatusCode(502, new { error = "The General Ledger service is unreachable." }); }
|
||||
|
||||
if (!result.Success)
|
||||
return StatusCode(result.StatusCode is >= 400 and < 500 ? result.StatusCode : 502,
|
||||
new { error = GlJson.Message(result) ?? "The General Ledger service rejected the request." });
|
||||
|
||||
var data = GlJson.Data(result);
|
||||
if (data is null) return StatusCode(502, new { error = "The General Ledger service returned no data." });
|
||||
|
||||
if (!outputFormat.Equals("Json", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var base64 = GlJson.GetString(data.Value, "contentBase64");
|
||||
if (base64 is null) return StatusCode(502, new { error = "The General Ledger service returned no file." });
|
||||
return File(Convert.FromBase64String(base64),
|
||||
GlJson.GetString(data.Value, "contentType") ?? "application/octet-stream",
|
||||
GlJson.GetString(data.Value, "fileName"));
|
||||
}
|
||||
|
||||
return Content(data.Value.GetRawText(), "application/json");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Distribution.Features.GeneralLedger;
|
||||
|
||||
public record GeneralLedgerResult(int StatusCode, bool Success, JsonElement? Body);
|
||||
|
||||
public interface IGeneralLedgerClient
|
||||
{
|
||||
Task<bool> IsHealthyAsync(CancellationToken ct = default);
|
||||
Task<GeneralLedgerResult> SendAsync(HttpMethod method, string path, object? payload = null, CancellationToken ct = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Service-to-service client for the General Ledger microservice. The GL service is not user-facing:
|
||||
/// every request carries the shared X-Api-Key (GeneralLedger:ApiKey, must equal the GL service's ApiKey:Value).
|
||||
/// <paramref name="path"/> is relative to GeneralLedger:BaseUrl and carries the API version (e.g. "v1/accounts", "v1/journal-entries"), so moving a call to v2 is a one-path change.
|
||||
/// </summary>
|
||||
public class GeneralLedgerClient(HttpClient http) : IGeneralLedgerClient
|
||||
{
|
||||
public async Task<bool> IsHealthyAsync(CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var response = await http.GetAsync("/health", ct);
|
||||
return response.IsSuccessStatusCode;
|
||||
}
|
||||
catch (HttpRequestException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<GeneralLedgerResult> SendAsync(HttpMethod method, string path, object? payload = null, CancellationToken ct = default)
|
||||
{
|
||||
using var request = new HttpRequestMessage(method, path.TrimStart('/'));
|
||||
if (payload is not null) request.Content = JsonContent.Create(payload);
|
||||
|
||||
using var response = await http.SendAsync(request, ct);
|
||||
JsonElement? body = null;
|
||||
if (response.Content.Headers.ContentLength != 0)
|
||||
{
|
||||
try { body = await response.Content.ReadFromJsonAsync<JsonElement>(cancellationToken: ct); }
|
||||
catch (JsonException) { }
|
||||
}
|
||||
return new GeneralLedgerResult((int)response.StatusCode, response.IsSuccessStatusCode, body);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using Distribution.Data;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.GeneralLedger;
|
||||
|
||||
public record GlSetupRequest(int? Year);
|
||||
|
||||
public record YearEndCloseRequest(int Year);
|
||||
|
||||
public record OpeningBalanceRequest(DateOnly Date, string? Narration, List<OpeningBalanceLine> Lines);
|
||||
|
||||
[ApiController]
|
||||
[Route("api/gl")]
|
||||
public class GlController(
|
||||
GlSetupService setup,
|
||||
GlPostingService posting,
|
||||
IGeneralLedgerClient gl,
|
||||
Distribution.Features.SysConfig.ISysConfigRepository configRepo,
|
||||
AppDBContext context,
|
||||
ICurrentUserService currentUser) : ControllerBase
|
||||
{
|
||||
[HttpGet("status")]
|
||||
[Authorize("gl.manage")]
|
||||
public async Task<IActionResult> Status()
|
||||
{
|
||||
var mapping = new Dictionary<string, string?>();
|
||||
foreach (var key in GlKeys.AllAccountKeys)
|
||||
mapping[key] = (await configRepo.GetByKeyAsync(key))?.Value;
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
glReachable = await gl.IsHealthyAsync(),
|
||||
setupDone = (await configRepo.GetByKeyAsync(GlKeys.SetupDone))?.Value == "true",
|
||||
openingBalancePosted = await context.GlPostingLogs.AnyAsync(l => l.Event == "OpeningBalance" && l.Status == GlPostingStatus.Posted),
|
||||
closedYears = await posting.GetClosedYearsAsync(),
|
||||
mapping,
|
||||
posted = await context.GlPostingLogs.CountAsync(l => l.Status == GlPostingStatus.Posted),
|
||||
failed = await context.GlPostingLogs.CountAsync(l => l.Status != GlPostingStatus.Posted)
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("setup")]
|
||||
[Authorize("gl.manage")]
|
||||
public async Task<IActionResult> Setup(GlSetupRequest request, CancellationToken ct)
|
||||
{
|
||||
try { return Ok(await setup.RunAsync(request.Year, currentUser.UserId, ct)); }
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPost("opening-balances")]
|
||||
[Authorize("gl.manage")]
|
||||
public async Task<IActionResult> OpeningBalances(OpeningBalanceRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var row = await posting.PostOpeningBalanceAsync(request.Date, request.Narration, request.Lines, ct);
|
||||
return row.Status == GlPostingStatus.Posted
|
||||
? Ok(new { status = row.Status, glJournalNo = row.GlJournalNo })
|
||||
: StatusCode(502, new { error = row.Error ?? "The General Ledger service did not accept the entry. It was saved and can be retried." });
|
||||
}
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpGet("year-end/preview")]
|
||||
[Authorize("gl.manage")]
|
||||
public async Task<IActionResult> YearEndPreview([FromQuery] int year, CancellationToken ct)
|
||||
{
|
||||
try { return Ok(await posting.GetYearEndPreviewAsync(year, ct)); }
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPost("year-end/close")]
|
||||
[Authorize("gl.manage")]
|
||||
public async Task<IActionResult> YearEndClose(YearEndCloseRequest request, CancellationToken ct)
|
||||
{
|
||||
try { return Ok(await posting.CloseYearAsync(request.Year, ct)); }
|
||||
catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpGet("postings")]
|
||||
[Authorize("gl.manage")]
|
||||
public async Task<IActionResult> Postings([FromQuery] GlPostingStatus? status, [FromQuery] int take = 100)
|
||||
{
|
||||
var query = context.GlPostingLogs.AsQueryable();
|
||||
if (status is not null) query = query.Where(l => l.Status == status);
|
||||
var rows = await query.OrderByDescending(l => l.CreatedAt).Take(Math.Clamp(take, 1, 500))
|
||||
.Select(l => new { l.Id, l.Event, l.SourceId, l.Reference, l.Status, l.GlJournalNo, l.Error, l.Attempts, l.LastAttemptAt, l.CreatedAt })
|
||||
.ToListAsync();
|
||||
return Ok(rows);
|
||||
}
|
||||
|
||||
[HttpPost("postings/retry")]
|
||||
[Authorize("gl.manage")]
|
||||
public async Task<IActionResult> Retry(CancellationToken ct) =>
|
||||
Ok(new { posted = await posting.RetryFailedAsync(ct) });
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Distribution.Features.GeneralLedger;
|
||||
|
||||
/// <summary>Small readers for the GL service's ApiResponse envelope ({statusCode, success, message, data}).</summary>
|
||||
public static class GlJson
|
||||
{
|
||||
public static JsonElement? Data(GeneralLedgerResult result) =>
|
||||
result.Body is { ValueKind: JsonValueKind.Object } body && Get(body, "data") is { } data ? data : null;
|
||||
|
||||
public static string? Message(GeneralLedgerResult result) =>
|
||||
result.Body is { ValueKind: JsonValueKind.Object } body ? Get(body, "message")?.GetString()
|
||||
?? Get(body, "Message")?.GetString() : null;
|
||||
|
||||
/// <summary>Case-insensitive property lookup (GL success bodies are camelCase, its error bodies PascalCase).</summary>
|
||||
public static JsonElement? Get(JsonElement element, string name)
|
||||
{
|
||||
if (element.ValueKind != JsonValueKind.Object) return null;
|
||||
foreach (var p in element.EnumerateObject())
|
||||
if (string.Equals(p.Name, name, StringComparison.OrdinalIgnoreCase)) return p.Value;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string? GetString(JsonElement element, string name)
|
||||
{
|
||||
var v = Get(element, name);
|
||||
return v is null || v.Value.ValueKind == JsonValueKind.Null ? null
|
||||
: v.Value.ValueKind == JsonValueKind.String ? v.Value.GetString() : v.Value.ToString();
|
||||
}
|
||||
|
||||
public static long? GetLong(JsonElement element, string name) =>
|
||||
long.TryParse(GetString(element, name), out var n) ? n : null;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
namespace Distribution.Features.GeneralLedger;
|
||||
|
||||
/// <summary>SysConfig keys holding the GL account codes Distribution posts to (created by GlSetupService).</summary>
|
||||
public static class GlKeys
|
||||
{
|
||||
public const string SetupDone = "Gl.Setup.Done";
|
||||
|
||||
public const string CashEquivalents = "Gl.Account.CashEquivalents"; // parent of Cash in Hand + Bank Accounts (GL cash root)
|
||||
public const string CashInHand = "Gl.Account.CashInHand"; // parent of every Distribution Cash account
|
||||
public const string BankAccounts = "Gl.Account.BankAccounts"; // parent of every Distribution Bank account
|
||||
public const string ChequesInHand = "Gl.Account.ChequesInHand";
|
||||
public const string Receivable = "Gl.Account.Receivable";
|
||||
public const string Inventory = "Gl.Account.Inventory";
|
||||
public const string InputVat = "Gl.Account.InputVat";
|
||||
public const string InputNbt = "Gl.Account.InputNbt";
|
||||
public const string Payable = "Gl.Account.Payable";
|
||||
public const string ChequesIssued = "Gl.Account.ChequesIssued";
|
||||
public const string Equity = "Gl.Account.Equity"; // header; parent of the equity accounts below
|
||||
public const string ShareCapital = "Gl.Account.ShareCapital";
|
||||
public const string RetainedEarnings = "Gl.Account.RetainedEarnings";
|
||||
public const string Drawings = "Gl.Account.Drawings";
|
||||
public const string GeneralReserve = "Gl.Account.GeneralReserve";
|
||||
public const string RevaluationReserve = "Gl.Account.RevaluationReserve";
|
||||
public const string Sales = "Gl.Account.Sales";
|
||||
|
||||
public static readonly string[] AllAccountKeys =
|
||||
[
|
||||
CashEquivalents, CashInHand, BankAccounts, ChequesInHand, Receivable, Inventory,
|
||||
InputVat, InputNbt, Payable, ChequesIssued, Equity, ShareCapital, RetainedEarnings, Drawings,
|
||||
GeneralReserve, RevaluationReserve, Sales
|
||||
];
|
||||
|
||||
public static bool IsGlKey(string key) => key == SetupDone || AllAccountKeys.Contains(key);
|
||||
}
|
||||
@@ -0,0 +1,449 @@
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using Distribution.Data;
|
||||
using Distribution.Features.Grn;
|
||||
using Distribution.Features.SysConfig;
|
||||
using Distribution.Infra.UoW;
|
||||
using Distribution.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.GeneralLedger;
|
||||
|
||||
public record GlLine(string AccountCode, decimal Debit, decimal Credit);
|
||||
|
||||
/// <summary>What closing a financial year would post. Closable is false with a Reason when it cannot be closed.</summary>
|
||||
public record YearEndPreview(int Year, string StartDate, string EndDate, decimal TotalIncome, decimal TotalExpenses,
|
||||
decimal NetProfit, bool Closable, string? Reason, bool AlreadyClosed);
|
||||
|
||||
/// <summary>
|
||||
/// One opening balance: Account is a key from OpeningAccounts (e.g. "Inventory", "ShareCapital") or "BankAccount"
|
||||
/// with BankAccountId. Amount is the account's normal balance (>= 0); the debit/credit side comes from the account.
|
||||
/// </summary>
|
||||
public record OpeningBalanceLine(string Account, Guid? BankAccountId, decimal Amount);
|
||||
|
||||
/// <summary>
|
||||
/// Mirrors Distribution business events into the General Ledger as journal entries. Every public Post* method is
|
||||
/// called AFTER the business transaction has committed and never throws: a GL problem is recorded in GlPostingLogs
|
||||
/// (status Failed, payload kept) and can be retried later, so it can never block a sale, payment or cheque action.
|
||||
/// </summary>
|
||||
public class GlPostingService(
|
||||
IGeneralLedgerClient gl,
|
||||
ISysConfigRepository configRepo,
|
||||
GlSetupService setup,
|
||||
AppDBContext context,
|
||||
IUnitOfWork uow,
|
||||
ILogger<GlPostingService> logger)
|
||||
{
|
||||
private record Payload(string EntryDate, string JournalType, string SourceModule, string Reference, string Narration,
|
||||
string PreparedBy, List<PayloadLine> Lines);
|
||||
|
||||
private record PayloadLine(string AccountCode, decimal DebitAmount, decimal CreditAmount);
|
||||
|
||||
// ── Events ───────────────────────────────────────────────────────────────
|
||||
|
||||
public Task PostInvoiceFinalizedAsync(Models.Invoice invoice, CancellationToken ct = default) =>
|
||||
PostAsync("InvoiceFinalized", invoice.Id, invoice.FinalizedAt ?? DateTime.Now, "INVOICE", $"Invoice {invoice.Id} finalized",
|
||||
async () => invoice.TotalAmount <= 0 ? null :
|
||||
[
|
||||
new GlLine(await Acct(GlKeys.Receivable), invoice.TotalAmount, 0),
|
||||
new GlLine(await Acct(GlKeys.Sales), 0, invoice.TotalAmount)
|
||||
], ct);
|
||||
|
||||
public Task PostCustomerPaymentAsync(Models.Payment payment, CancellationToken ct = default) =>
|
||||
PostAsync("CustomerPayment", payment.Id, payment.CollectedAt, "PAYMENT", $"Customer payment for invoice {payment.FK_InvoiceId}",
|
||||
async () =>
|
||||
{
|
||||
if (payment.Amount <= 0 || payment.Method == PaymentMethod.Credit) return null;
|
||||
var debit = payment.Method == PaymentMethod.Cash
|
||||
? await BankCode(payment.FK_BankAccountId)
|
||||
: await Acct(GlKeys.ChequesInHand);
|
||||
return [new GlLine(debit, payment.Amount, 0), new GlLine(await Acct(GlKeys.Receivable), 0, payment.Amount)];
|
||||
}, ct);
|
||||
|
||||
/// <summary>Deposit / Return / Cancel of a received cheque that came from a customer payment.</summary>
|
||||
public Task PostReceivedChequeAsync(Models.ReceivedCheque cheque, ReceivedChequeStatusAction action, CancellationToken ct = default)
|
||||
{
|
||||
if (action is not (ReceivedChequeStatusAction.Deposit or ReceivedChequeStatusAction.Return or ReceivedChequeStatusAction.Cancel))
|
||||
return Task.CompletedTask;
|
||||
|
||||
var date = action == ReceivedChequeStatusAction.Deposit ? cheque.DepositDate ?? DateTime.Now : DateTime.Now;
|
||||
return PostAsync($"ReceivedCheque{action}", cheque.Id, date, "CHEQUE", $"Received cheque {cheque.ChequeNo} {action}",
|
||||
async () =>
|
||||
{
|
||||
// Only cheques created by a customer payment were posted to Cheques-in-hand, so only they can move.
|
||||
if (!await context.Payments.AnyAsync(p => p.FK_ReceivedChequeId == cheque.Id, ct)) return null;
|
||||
var inHand = await Acct(GlKeys.ChequesInHand);
|
||||
return action switch
|
||||
{
|
||||
ReceivedChequeStatusAction.Deposit =>
|
||||
[new GlLine(await BankCode(cheque.FK_DepositBankAccountId), cheque.Amount, 0), new GlLine(inHand, 0, cheque.Amount)],
|
||||
ReceivedChequeStatusAction.Return =>
|
||||
[new GlLine(inHand, cheque.Amount, 0), new GlLine(await BankCode(cheque.FK_DepositBankAccountId), 0, cheque.Amount)],
|
||||
_ => [new GlLine(await Acct(GlKeys.Receivable), cheque.Amount, 0), new GlLine(inHand, 0, cheque.Amount)]
|
||||
};
|
||||
}, ct);
|
||||
}
|
||||
|
||||
public Task PostGrnAsync(Models.GoodsReceipt grn, CancellationToken ct = default) =>
|
||||
PostAsync("Grn", grn.Id, grn.ReceivedAt, "GRN", $"Goods received {grn.Id}",
|
||||
async () =>
|
||||
{
|
||||
decimal net = 0, nbt = 0, vat = 0;
|
||||
foreach (var i in grn.Items.Where(i => i.IsActive))
|
||||
{
|
||||
var a = GrnPricing.Line(i.AcceptedQuantity, i.UnitCost, i.DiscountPercent, i.NbtPercent, i.VatPercent);
|
||||
net += a.Gross - a.Discount;
|
||||
nbt += a.Nbt;
|
||||
vat += a.Vat;
|
||||
}
|
||||
var total = net + nbt + vat;
|
||||
if (total <= 0) return null;
|
||||
|
||||
var lines = new List<GlLine> { new(await Acct(GlKeys.Inventory), net, 0) };
|
||||
if (nbt > 0) lines.Add(new GlLine(await Acct(GlKeys.InputNbt), nbt, 0));
|
||||
if (vat > 0) lines.Add(new GlLine(await Acct(GlKeys.InputVat), vat, 0));
|
||||
lines.Add(new GlLine(await Acct(GlKeys.Payable), 0, total));
|
||||
return lines;
|
||||
}, ct);
|
||||
|
||||
public Task PostSupplierPaymentAsync(Models.SupplierPayment payment, CancellationToken ct = default) =>
|
||||
PostAsync("SupplierPayment", payment.Id, payment.PaidAt, "SUPPLIER_PAYMENT", $"Supplier payment {payment.Id}",
|
||||
async () =>
|
||||
{
|
||||
if (payment.Amount <= 0) return null;
|
||||
var credit = payment.Method == PaymentMethod.Cash
|
||||
? await BankCode(payment.FK_BankAccountId)
|
||||
: await Acct(GlKeys.ChequesIssued);
|
||||
return [new GlLine(await Acct(GlKeys.Payable), payment.Amount, 0), new GlLine(credit, 0, payment.Amount)];
|
||||
}, ct);
|
||||
|
||||
/// <summary>Clear / Bounce / Cancel of a cheque issued through a supplier payment.</summary>
|
||||
public Task PostIssuedChequeAsync(Models.ChequePage page, ChequePageStatusAction action, CancellationToken ct = default)
|
||||
{
|
||||
if (action == ChequePageStatusAction.Void) return Task.CompletedTask;
|
||||
|
||||
var date = action == ChequePageStatusAction.Clear ? page.ClearedDate ?? DateTime.Now : DateTime.Now;
|
||||
return PostAsync($"IssuedCheque{action}", page.Id, date, "CHEQUE", $"Issued cheque {page.ChequeNo} {action}",
|
||||
async () =>
|
||||
{
|
||||
var amount = page.Amount ?? 0;
|
||||
if (amount <= 0 || !await context.SupplierPayments.AnyAsync(p => p.FK_ChequePageId == page.Id, ct)) return null;
|
||||
var issued = await Acct(GlKeys.ChequesIssued);
|
||||
if (action == ChequePageStatusAction.Clear)
|
||||
{
|
||||
var bookBankId = await context.ChequeBooks.Where(b => b.Id == page.FK_ChequeBookId)
|
||||
.Select(b => (Guid?)b.FK_BankAccountId).FirstOrDefaultAsync(ct);
|
||||
return [new GlLine(issued, amount, 0), new GlLine(await BankCode(bookBankId), 0, amount)];
|
||||
}
|
||||
// Bounce / Cancel: the cheque no longer settles the payable, so put it back.
|
||||
return [new GlLine(issued, amount, 0), new GlLine(await Acct(GlKeys.Payable), 0, amount)];
|
||||
}, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Posts the opening balances (asset side debited, liability/equity side credited). Zero amounts are skipped, so the
|
||||
/// form can list every account with a default of 0. Reports the outcome so the caller can show a failure.
|
||||
/// </summary>
|
||||
public async Task<GlPostingLog> PostOpeningBalanceAsync(DateOnly date, string? narration, List<OpeningBalanceLine> input, CancellationToken ct = default)
|
||||
{
|
||||
if ((await configRepo.GetByKeyAsync(GlKeys.SetupDone))?.Value != "true")
|
||||
throw new InvalidOperationException("Run the General Ledger setup first.");
|
||||
|
||||
var lines = new List<GlLine>();
|
||||
foreach (var l in input ?? [])
|
||||
{
|
||||
if (l.Amount < 0) throw new InvalidOperationException($"'{l.Account}': the amount cannot be negative.");
|
||||
if (l.Amount == 0) continue;
|
||||
|
||||
if (l.Account == "BankAccount")
|
||||
{
|
||||
lines.Add(new GlLine(await BankCode(l.BankAccountId), l.Amount, 0));
|
||||
continue;
|
||||
}
|
||||
if (!OpeningAccounts.TryGetValue(l.Account, out var def))
|
||||
throw new InvalidOperationException($"Unknown account '{l.Account}'.");
|
||||
var code = await Acct(def.Key);
|
||||
lines.Add(def.DebitNormal ? new GlLine(code, l.Amount, 0) : new GlLine(code, 0, l.Amount));
|
||||
}
|
||||
|
||||
if (lines.Count == 0)
|
||||
throw new InvalidOperationException("Enter at least one opening balance greater than zero.");
|
||||
|
||||
var debit = lines.Sum(x => x.Debit);
|
||||
var credit = lines.Sum(x => x.Credit);
|
||||
if (debit != credit)
|
||||
throw new InvalidOperationException(
|
||||
$"The opening balances do not balance: assets (debits) total {debit:N2} but liabilities + equity (credits) total {credit:N2}. " +
|
||||
"Adjust Share Capital / Retained Earnings so they match.");
|
||||
|
||||
var sourceId = Guid.NewGuid();
|
||||
await PostAsync("OpeningBalance", sourceId, date.ToDateTime(TimeOnly.MinValue), "OPENING_BALANCE",
|
||||
string.IsNullOrWhiteSpace(narration) ? "Opening balances" : narration.Trim(), () => Task.FromResult<List<GlLine>?>(lines), ct);
|
||||
|
||||
return await context.GlPostingLogs.FirstOrDefaultAsync(l => l.Event == "OpeningBalance" && l.SourceId == sourceId, ct)
|
||||
?? throw new InvalidOperationException("The opening balances could not be prepared. Check the server log.");
|
||||
}
|
||||
|
||||
/// <summary>Accounts that can carry an opening balance (besides individual cash/bank accounts) and their normal side.</summary>
|
||||
public static readonly Dictionary<string, (string Key, bool DebitNormal)> OpeningAccounts = new()
|
||||
{
|
||||
["ChequesInHand"] = (GlKeys.ChequesInHand, true),
|
||||
["Receivable"] = (GlKeys.Receivable, true),
|
||||
["Inventory"] = (GlKeys.Inventory, true),
|
||||
["InputVat"] = (GlKeys.InputVat, true),
|
||||
["InputNbt"] = (GlKeys.InputNbt, true),
|
||||
["Drawings"] = (GlKeys.Drawings, true),
|
||||
["Payable"] = (GlKeys.Payable, false),
|
||||
["ChequesIssued"] = (GlKeys.ChequesIssued, false),
|
||||
["ShareCapital"] = (GlKeys.ShareCapital, false),
|
||||
["RetainedEarnings"] = (GlKeys.RetainedEarnings, false),
|
||||
["GeneralReserve"] = (GlKeys.GeneralReserve, false),
|
||||
["RevaluationReserve"] = (GlKeys.RevaluationReserve, false),
|
||||
};
|
||||
|
||||
// ── Year-end close ───────────────────────────────────────────────────────
|
||||
|
||||
private const string YearEndEvent = "YearEndClose";
|
||||
|
||||
private static Guid YearEndSourceId(int year) =>
|
||||
new(System.Security.Cryptography.MD5.HashData(System.Text.Encoding.UTF8.GetBytes($"{YearEndEvent}:{year}")));
|
||||
|
||||
/// <summary>Years that have a posted closing journal.</summary>
|
||||
public async Task<List<int>> GetClosedYearsAsync(CancellationToken ct = default) =>
|
||||
(await context.GlPostingLogs
|
||||
.Where(l => l.Event == YearEndEvent && l.Status == GlPostingStatus.Posted)
|
||||
.Select(l => l.Reference)
|
||||
.ToListAsync(ct))
|
||||
.Select(r => int.TryParse(r.Split(':').Last(), out var y) ? y : 0)
|
||||
.Where(y => y > 0).OrderBy(y => y).ToList();
|
||||
|
||||
private record CloseBuild(List<GlLine> Lines, decimal Income, decimal Expenses);
|
||||
|
||||
/// <summary>Reads the year's own P&L from GL (which excludes earlier closing journals) and builds the closing lines.</summary>
|
||||
private async Task<CloseBuild> BuildYearEndCloseAsync(int year, CancellationToken ct)
|
||||
{
|
||||
var result = await gl.SendAsync(HttpMethod.Get,
|
||||
$"v1/reports?reportType=ProfitAndLoss&outputFormat=Json&periodStart={year}-01-01&periodEnd={year}-12-31", null, ct);
|
||||
if (!result.Success || GlJson.Data(result) is not { } pl)
|
||||
throw new InvalidOperationException($"Could not read the {year} profit and loss from the General Ledger: {GlJson.Message(result) ?? result.StatusCode.ToString()}");
|
||||
|
||||
// Net CREDIT balance per account: income sections carry their amount as is, expense sections are reported
|
||||
// as positive debits (so flip), unclassified rows are already credit - debit.
|
||||
var netCredit = new Dictionary<string, decimal>();
|
||||
decimal income = 0, expenses = 0;
|
||||
void Collect(string section, int sign)
|
||||
{
|
||||
if (GlJson.Get(pl, section) is not { } s || GlJson.Get(s, "lines") is not { ValueKind: JsonValueKind.Array } sectionLines) return;
|
||||
foreach (var l in sectionLines.EnumerateArray())
|
||||
{
|
||||
var code = GlJson.GetString(l, "accountCode");
|
||||
if (code is null || !decimal.TryParse(GlJson.GetString(l, "amount"), NumberStyles.Any, CultureInfo.InvariantCulture, out var amount)) continue;
|
||||
var credit = sign * amount;
|
||||
netCredit[code] = netCredit.GetValueOrDefault(code) + credit;
|
||||
if (credit > 0) income += credit; else expenses += -credit;
|
||||
}
|
||||
}
|
||||
Collect("sales", 1); Collect("otherIncome", 1);
|
||||
Collect("costOfSales", -1); Collect("distributionExpenses", -1); Collect("administrationExpenses", -1);
|
||||
Collect("otherExpenses", -1); Collect("financialExpenses", -1);
|
||||
Collect("unclassified", 1);
|
||||
|
||||
// Closing: an account with a net credit balance is debited, one with a net debit balance is credited.
|
||||
var lines = netCredit.Where(kv => kv.Value != 0)
|
||||
.Select(kv => kv.Value > 0 ? new GlLine(kv.Key, kv.Value, 0) : new GlLine(kv.Key, 0, -kv.Value)).ToList();
|
||||
var debit = lines.Sum(x => x.Debit);
|
||||
var credit2 = lines.Sum(x => x.Credit);
|
||||
if (debit != credit2)
|
||||
{
|
||||
var retained = await Acct(GlKeys.RetainedEarnings);
|
||||
lines.Add(debit > credit2 ? new GlLine(retained, 0, debit - credit2) : new GlLine(retained, credit2 - debit, 0));
|
||||
}
|
||||
return new CloseBuild(lines, income, expenses);
|
||||
}
|
||||
|
||||
public async Task<YearEndPreview> GetYearEndPreviewAsync(int year, CancellationToken ct = default)
|
||||
{
|
||||
var start = $"{year}-01-01";
|
||||
var end = $"{year}-12-31";
|
||||
|
||||
if ((await configRepo.GetByKeyAsync(GlKeys.SetupDone))?.Value != "true")
|
||||
return new(year, start, end, 0, 0, 0, false, "Run the General Ledger setup first.", false);
|
||||
|
||||
var sourceId = YearEndSourceId(year);
|
||||
var existing = await context.GlPostingLogs.FirstOrDefaultAsync(l => l.Event == YearEndEvent && l.SourceId == sourceId, ct);
|
||||
if (existing?.Status == GlPostingStatus.Posted)
|
||||
return new(year, start, end, 0, 0, 0, false, $"{year} has already been closed.", true);
|
||||
|
||||
var build = await BuildYearEndCloseAsync(year, ct);
|
||||
var net = build.Income - build.Expenses;
|
||||
string? reason = null;
|
||||
if (new DateOnly(year, 12, 31) > DateOnly.FromDateTime(DateTime.Now))
|
||||
reason = $"{year} has not ended yet. A year can only be closed after 31 December.";
|
||||
else if (await context.GlPostingLogs.AnyAsync(l => l.Status != GlPostingStatus.Posted && !(l.Event == YearEndEvent && l.SourceId == sourceId), ct))
|
||||
reason = "There are failed or pending General Ledger postings. Retry them first so the year's figures are complete.";
|
||||
else if (build.Lines.Count == 0)
|
||||
reason = $"There is no income or expense activity in {year} to close.";
|
||||
|
||||
return new(year, start, end, build.Income, build.Expenses, net, reason is null, reason, false);
|
||||
}
|
||||
|
||||
/// <summary>Posts the closing journal for the year (income/expense into Retained Earnings), then locks the year's periods.</summary>
|
||||
public async Task<object> CloseYearAsync(int year, CancellationToken ct = default)
|
||||
{
|
||||
var preview = await GetYearEndPreviewAsync(year, ct);
|
||||
if (!preview.Closable) throw new InvalidOperationException(preview.Reason ?? "This year cannot be closed.");
|
||||
|
||||
var build = await BuildYearEndCloseAsync(year, ct);
|
||||
var sourceId = YearEndSourceId(year);
|
||||
await PostAsync(YearEndEvent, sourceId, new DateTime(year, 12, 31), "YEAR_END_CLOSE", $"Year-end closing entry for {year}",
|
||||
() => Task.FromResult<List<GlLine>?>(build.Lines), ct, journalType: "CLOSING", reference: $"{YearEndEvent}:{year}");
|
||||
|
||||
var row = await context.GlPostingLogs.FirstOrDefaultAsync(l => l.Event == YearEndEvent && l.SourceId == sourceId, ct)
|
||||
?? throw new InvalidOperationException("The closing entry could not be prepared. Check the server log.");
|
||||
if (row.Status != GlPostingStatus.Posted)
|
||||
throw new InvalidOperationException((row.Error ?? "The General Ledger did not accept the closing entry.") + " Fix the cause and press Close again.");
|
||||
|
||||
var locked = await LockYearPeriodsAsync(year, ct);
|
||||
return new { year, glJournalNo = row.GlJournalNo, netProfit = preview.NetProfit, periodsLocked = locked };
|
||||
}
|
||||
|
||||
private async Task<int> LockYearPeriodsAsync(int year, CancellationToken ct)
|
||||
{
|
||||
var ids = new HashSet<long>();
|
||||
for (var month = 1; month <= 12; month++)
|
||||
{
|
||||
var p = await gl.SendAsync(HttpMethod.Get, $"v1/fiscal-years/periods/by-date?date={year}-{month:00}-15", null, ct);
|
||||
if (p.Success && GlJson.Data(p) is { } pd && GlJson.GetLong(pd, "periodId") is { } id) ids.Add(id);
|
||||
}
|
||||
|
||||
var locked = 0;
|
||||
foreach (var id in ids)
|
||||
{
|
||||
var r = await gl.SendAsync(HttpMethod.Put, $"v1/periods/{id}/lock", new { action = "Close", reason = $"Year-end close {year}" }, ct);
|
||||
if (r.Success) locked++;
|
||||
else logger.LogWarning("Could not lock GL period {PeriodId} after closing {Year}: {Message}", id, year, GlJson.Message(r));
|
||||
}
|
||||
return locked;
|
||||
}
|
||||
|
||||
// ── Retry ────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Re-sends every Failed/Pending posting. Returns how many are now Posted.</summary>
|
||||
public async Task<int> RetryFailedAsync(CancellationToken ct = default)
|
||||
{
|
||||
var rows = await context.GlPostingLogs
|
||||
.Where(l => l.Status != GlPostingStatus.Posted)
|
||||
.OrderBy(l => l.CreatedAt)
|
||||
.ToListAsync(ct);
|
||||
|
||||
var posted = 0;
|
||||
foreach (var row in rows)
|
||||
{
|
||||
await SendAsync(row, ct);
|
||||
if (row.Status == GlPostingStatus.Posted) posted++;
|
||||
}
|
||||
return posted;
|
||||
}
|
||||
|
||||
// ── Core ─────────────────────────────────────────────────────────────────
|
||||
|
||||
private async Task PostAsync(string evt, Guid sourceId, DateTime date, string sourceModule, string narration,
|
||||
Func<Task<List<GlLine>?>> buildLines, CancellationToken ct, string journalType = "GENERAL", string? reference = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if ((await configRepo.GetByKeyAsync(GlKeys.SetupDone))?.Value != "true") return; // GL not set up yet
|
||||
|
||||
var row = await context.GlPostingLogs.FirstOrDefaultAsync(l => l.Event == evt && l.SourceId == sourceId, ct);
|
||||
if (row is not null && row.Status == GlPostingStatus.Posted) return;
|
||||
|
||||
if (row is null)
|
||||
{
|
||||
var lines = await buildLines();
|
||||
if (lines is null || lines.Count == 0) return;
|
||||
|
||||
reference ??= $"{evt}:{sourceId}";
|
||||
var payload = new Payload(date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), journalType, sourceModule, reference,
|
||||
narration, "Distribution", lines.Select(l => new PayloadLine(l.AccountCode, l.Debit, l.Credit)).ToList());
|
||||
|
||||
row = new GlPostingLog
|
||||
{
|
||||
Event = evt,
|
||||
SourceId = sourceId,
|
||||
Reference = reference,
|
||||
PayloadJson = JsonSerializer.Serialize(payload, JsonOptions)
|
||||
};
|
||||
await uow.BeginAsync(ct);
|
||||
try { await context.GlPostingLogs.AddAsync(row, ct); await uow.CommitAsync(ct); }
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
}
|
||||
|
||||
await SendAsync(row, ct);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "GL posting {Event}:{SourceId} failed before it could be sent", evt, sourceId);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendAsync(GlPostingLog row, CancellationToken ct)
|
||||
{
|
||||
string? journalNo = null;
|
||||
string? error = null;
|
||||
try
|
||||
{
|
||||
var payload = JsonSerializer.Deserialize<JsonElement>(row.PayloadJson);
|
||||
var entryDate = payload.GetProperty("entryDate").GetString()!;
|
||||
|
||||
var period = await gl.SendAsync(HttpMethod.Get, $"v1/fiscal-years/periods/by-date?date={entryDate}", null, ct);
|
||||
var periodId = period.Success && GlJson.Data(period) is { } pd ? GlJson.GetLong(pd, "periodId") : null;
|
||||
if (periodId is null)
|
||||
{
|
||||
error = $"No accounting period in GL covers {entryDate}. Run GL setup for that year.";
|
||||
}
|
||||
else
|
||||
{
|
||||
var body = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(row.PayloadJson, JsonOptions)!;
|
||||
body["periodId"] = JsonSerializer.SerializeToElement(periodId.Value);
|
||||
var result = await gl.SendAsync(HttpMethod.Post, "v1/journal-entries", body, ct);
|
||||
if (result.Success)
|
||||
journalNo = GlJson.Data(result) is { } d ? GlJson.GetString(d, "journalNo") ?? "" : "";
|
||||
else
|
||||
error = GlJson.Message(result) ?? $"GL returned HTTP {result.StatusCode}";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = ex.Message;
|
||||
logger.LogWarning(ex, "GL posting {Reference} failed", row.Reference);
|
||||
}
|
||||
|
||||
row.Attempts++;
|
||||
row.LastAttemptAt = DateTime.Now;
|
||||
row.Status = error is null ? GlPostingStatus.Posted : GlPostingStatus.Failed;
|
||||
row.GlJournalNo = journalNo;
|
||||
row.Error = error is null ? null : error.Length > 1000 ? error[..1000] : error;
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try { context.GlPostingLogs.Update(row); await uow.CommitAsync(ct); }
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
}
|
||||
|
||||
// ── Account lookups ──────────────────────────────────────────────────────
|
||||
|
||||
private async Task<string> Acct(string key) =>
|
||||
(await configRepo.GetByKeyAsync(key))?.Value is { Length: > 0 } code
|
||||
? code
|
||||
: throw new InvalidOperationException($"GL account mapping '{key}' is missing. Run GL setup.");
|
||||
|
||||
private async Task<string> BankCode(Guid? bankAccountId)
|
||||
{
|
||||
if (bankAccountId is null) throw new InvalidOperationException("No cash/bank account on this transaction.");
|
||||
var account = await context.BankAccounts.FirstOrDefaultAsync(a => a.Id == bankAccountId.Value)
|
||||
?? throw new InvalidOperationException("Cash/bank account not found.");
|
||||
return await setup.EnsureBankAccountAsync(account)
|
||||
?? throw new InvalidOperationException("Could not map the cash/bank account to a GL account. Run GL setup.");
|
||||
}
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
using System.Text.Json;
|
||||
using Distribution.Data;
|
||||
using Distribution.Features.SysConfig;
|
||||
using Distribution.Infra.UoW;
|
||||
using Distribution.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.GeneralLedger;
|
||||
|
||||
public record GlSetupResult(bool Done, List<string> Actions);
|
||||
|
||||
/// <summary>
|
||||
/// One-time (re-runnable) bootstrap of the General Ledger service for Distribution: creates the chart of accounts
|
||||
/// Distribution posts to, a fiscal year with its periods, the GL cash-root config, and a GL account for every
|
||||
/// Distribution cash/bank account. The resulting GL account codes are stored in SysConfig (see GlKeys).
|
||||
/// </summary>
|
||||
public class GlSetupService(
|
||||
IGeneralLedgerClient gl,
|
||||
ISysConfigRepository configRepo,
|
||||
AppDBContext context,
|
||||
IUnitOfWork uow)
|
||||
{
|
||||
private const short Asset = 1, Liability = 2, Equity = 3, Income = 4;
|
||||
|
||||
// key, GL account name, account type, parent key, postable, tag name, tag value.
|
||||
// Operating = working-capital account for the Cash Flow report (cashFlowCategory OPERATING).
|
||||
private record AccountDef(string Key, string Name, short TypeId, string? ParentKey, bool Postable, string? TagField, string? TagValue, bool Operating = false);
|
||||
|
||||
private static readonly AccountDef[] Chart =
|
||||
[
|
||||
new(GlKeys.CashEquivalents, "Cash and Cash Equivalents", Asset, null, false, "balanceSheetClassification", "CURRENT_ASSET"),
|
||||
new(GlKeys.CashInHand, "Cash in Hand", Asset, GlKeys.CashEquivalents, false, null, null),
|
||||
new(GlKeys.BankAccounts, "Bank Accounts", Asset, GlKeys.CashEquivalents, false, null, null),
|
||||
new(GlKeys.ChequesInHand, "Cheques in Hand", Asset, null, true, "balanceSheetClassification", "CURRENT_ASSET", true),
|
||||
new(GlKeys.Receivable, "Trade Receivables", Asset, null, true, "balanceSheetClassification", "CURRENT_ASSET", true),
|
||||
new(GlKeys.Inventory, "Inventory", Asset, null, true, "balanceSheetClassification", "CURRENT_ASSET", true),
|
||||
new(GlKeys.InputVat, "Input VAT", Asset, null, true, "balanceSheetClassification", "CURRENT_ASSET", true),
|
||||
new(GlKeys.InputNbt, "Input NBT", Asset, null, true, "balanceSheetClassification", "CURRENT_ASSET", true),
|
||||
new(GlKeys.Payable, "Trade Payables", Liability, null, true, "balanceSheetClassification", "CURRENT_LIABILITY", true),
|
||||
new(GlKeys.ChequesIssued, "Cheques Issued - Uncleared", Liability, null, true, "balanceSheetClassification", "CURRENT_LIABILITY", true),
|
||||
new(GlKeys.Equity, "Owner's Equity", Equity, null, false, null, null),
|
||||
new(GlKeys.ShareCapital, "Share Capital / Owner's Capital", Equity, GlKeys.Equity, true, null, null),
|
||||
new(GlKeys.RetainedEarnings, "Retained Earnings", Equity, GlKeys.Equity, true, null, null),
|
||||
new(GlKeys.Drawings, "Drawings and Dividends", Equity, GlKeys.Equity, true, null, null),
|
||||
new(GlKeys.GeneralReserve, "General Reserve", Equity, GlKeys.Equity, true, null, null),
|
||||
new(GlKeys.RevaluationReserve, "Revaluation Reserve", Equity, GlKeys.Equity, true, null, null),
|
||||
new(GlKeys.Sales, "Sales Revenue", Income, null, true, "incomeStatementSection", "SALES"),
|
||||
];
|
||||
|
||||
public async Task<GlSetupResult> RunAsync(int? year, Guid? userId, CancellationToken ct = default)
|
||||
{
|
||||
if (!await gl.IsHealthyAsync(ct))
|
||||
throw new InvalidOperationException("The General Ledger service is not reachable. Check GeneralLedger:BaseUrl and that the service is running.");
|
||||
|
||||
var actions = new List<string>();
|
||||
var codes = new Dictionary<string, string>();
|
||||
|
||||
foreach (var key in GlKeys.AllAccountKeys)
|
||||
{
|
||||
var existing = (await configRepo.GetByKeyAsync(key))?.Value;
|
||||
if (!string.IsNullOrWhiteSpace(existing)) codes[key] = existing;
|
||||
}
|
||||
|
||||
// 1. Chart of accounts (skips accounts whose code is already stored and still exists in GL).
|
||||
foreach (var def in Chart)
|
||||
{
|
||||
if (codes.TryGetValue(def.Key, out var code) && await AccountIdAsync(code, ct) is not null)
|
||||
continue;
|
||||
|
||||
long? parentId = null;
|
||||
if (def.ParentKey is not null)
|
||||
parentId = await AccountIdAsync(codes[def.ParentKey], ct);
|
||||
|
||||
var created = await CreateAccountAsync(def.Name, def.TypeId, parentId, def.Postable, ct);
|
||||
codes[def.Key] = created.Code;
|
||||
actions.Add($"Created GL account {created.Code} - {def.Name}");
|
||||
}
|
||||
|
||||
// Tags are applied on every run (idempotent), so accounts created by an earlier run are corrected too.
|
||||
foreach (var def in Chart)
|
||||
{
|
||||
var tags = new Dictionary<string, object?>();
|
||||
if (def.TagField is not null) tags[def.TagField] = def.TagValue;
|
||||
if (def.Operating) tags["cashFlowCategory"] = "OPERATING";
|
||||
if (!def.Postable) tags["isPostable"] = false; // header accounts (re-applied so older runs are corrected)
|
||||
if (tags.Count > 0) await UpdateAccountAsync(codes[def.Key], tags, ct);
|
||||
}
|
||||
|
||||
// 2. Fiscal year + periods for the requested year (default: current year).
|
||||
var fy = year ?? DateTime.Now.Year;
|
||||
var probe = await gl.SendAsync(HttpMethod.Get, $"v1/fiscal-years/periods/by-date?date={fy}-06-30", null, ct);
|
||||
if (!probe.Success)
|
||||
{
|
||||
var fyResult = await gl.SendAsync(HttpMethod.Post, "v1/fiscal-years", new
|
||||
{
|
||||
yearName = $"FY{fy}",
|
||||
startDate = $"{fy}-01-01",
|
||||
endDate = $"{fy}-12-31",
|
||||
autoGeneratePeriods = true,
|
||||
periodCount = 12
|
||||
}, ct);
|
||||
if (!fyResult.Success)
|
||||
throw new InvalidOperationException($"Could not create fiscal year {fy} in GL: {GlJson.Message(fyResult) ?? fyResult.StatusCode.ToString()}");
|
||||
actions.Add($"Created fiscal year FY{fy} with 12 periods");
|
||||
}
|
||||
|
||||
// 3. GL cash root (GL locks this key after one edit, so only set it when empty).
|
||||
var cashRootId = await AccountIdAsync(codes[GlKeys.CashEquivalents], ct);
|
||||
var cfgResult = await gl.SendAsync(HttpMethod.Get, "v1/system-config", null, ct);
|
||||
var currentRoot = ReadGlConfigValue(cfgResult, "CASH_ACCOUNT_ROOT_ID");
|
||||
if (string.IsNullOrWhiteSpace(currentRoot) && cashRootId is not null)
|
||||
{
|
||||
var set = await gl.SendAsync(HttpMethod.Put, "v1/system-config/CASH_ACCOUNT_ROOT_ID",
|
||||
new { configValue = cashRootId.Value.ToString() }, ct);
|
||||
if (!set.Success)
|
||||
throw new InvalidOperationException($"Could not set CASH_ACCOUNT_ROOT_ID in GL: {GlJson.Message(set) ?? set.StatusCode.ToString()}");
|
||||
actions.Add("Set GL CASH_ACCOUNT_ROOT_ID");
|
||||
}
|
||||
|
||||
// 4. Persist the mapping, then mirror every existing Distribution cash/bank account.
|
||||
await SaveConfigAsync(codes, userId, ct);
|
||||
|
||||
foreach (var account in await context.BankAccounts.Where(a => a.GlAccountCode == null).ToListAsync(ct))
|
||||
{
|
||||
var code = await EnsureBankAccountAsync(account, ct);
|
||||
if (code is not null) actions.Add($"Mirrored {account.AccountName} as GL account {code}");
|
||||
}
|
||||
|
||||
await SaveConfigAsync(new Dictionary<string, string> { [GlKeys.SetupDone] = "true" }, userId, ct);
|
||||
return new GlSetupResult(true, actions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the GL account code for a Distribution cash/bank account, creating the GL child account on first use.
|
||||
/// Saves the code on the BankAccount. Returns null if GL setup has not been run.
|
||||
/// </summary>
|
||||
public async Task<string?> EnsureBankAccountAsync(Models.BankAccount account, CancellationToken ct = default)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(account.GlAccountCode)) return account.GlAccountCode;
|
||||
|
||||
var parentKey = account.AccountType == BankAccountType.Bank ? GlKeys.BankAccounts : GlKeys.CashInHand;
|
||||
var parentCode = (await configRepo.GetByKeyAsync(parentKey))?.Value;
|
||||
if (string.IsNullOrWhiteSpace(parentCode)) return null;
|
||||
|
||||
var parentId = await AccountIdAsync(parentCode, ct);
|
||||
if (parentId is null) return null;
|
||||
|
||||
var name = account.AccountType == BankAccountType.Bank && !string.IsNullOrWhiteSpace(account.BankName)
|
||||
? $"{account.BankName} - {account.AccountName}"
|
||||
: account.AccountName;
|
||||
|
||||
var created = await CreateAccountAsync(name, Asset, parentId, true, ct);
|
||||
|
||||
account.GlAccountCode = created.Code;
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
context.BankAccounts.Update(account);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return created.Code;
|
||||
}
|
||||
|
||||
private async Task SaveConfigAsync(Dictionary<string, string> values, Guid? userId, CancellationToken ct)
|
||||
{
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
foreach (var (key, value) in values)
|
||||
await configRepo.UpsertAsync(new Models.SysConfig { Key = key, Value = value, UpdatedAt = DateTime.Now, UpdatedBy = userId });
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
}
|
||||
|
||||
private async Task<long?> AccountIdAsync(string code, CancellationToken ct)
|
||||
{
|
||||
var result = await gl.SendAsync(HttpMethod.Get, $"v1/accounts/{Uri.EscapeDataString(code)}", null, ct);
|
||||
if (!result.Success) return null;
|
||||
var data = GlJson.Data(result);
|
||||
if (data is null) return null;
|
||||
// GET /accounts/{code} wraps the account: { account: {...}, ... }
|
||||
var account = GlJson.Get(data.Value, "account") ?? data.Value;
|
||||
return GlJson.GetLong(account, "accountId");
|
||||
}
|
||||
|
||||
private async Task<(long Id, string Code)> CreateAccountAsync(string name, short typeId, long? parentId, bool postable, CancellationToken ct)
|
||||
{
|
||||
var result = await gl.SendAsync(HttpMethod.Post, "v1/accounts", new
|
||||
{
|
||||
accountName = name,
|
||||
parentAccountId = parentId,
|
||||
accountTypeId = typeId,
|
||||
isControlAccount = false,
|
||||
isPostable = postable
|
||||
}, ct);
|
||||
if (!result.Success)
|
||||
throw new InvalidOperationException($"Could not create GL account '{name}': {GlJson.Message(result) ?? result.StatusCode.ToString()}");
|
||||
|
||||
var data = GlJson.Data(result) ?? throw new InvalidOperationException($"GL returned no data when creating account '{name}'.");
|
||||
var id = GlJson.GetLong(data, "accountId");
|
||||
var code = GlJson.GetString(data, "accountCode");
|
||||
if (id is null || code is null)
|
||||
throw new InvalidOperationException($"GL returned an unexpected body when creating account '{name}'.");
|
||||
return (id.Value, code);
|
||||
}
|
||||
|
||||
private async Task UpdateAccountAsync(string code, Dictionary<string, object?> fields, CancellationToken ct)
|
||||
{
|
||||
var result = await gl.SendAsync(HttpMethod.Put, $"v1/accounts/{Uri.EscapeDataString(code)}", fields, ct);
|
||||
if (!result.Success)
|
||||
throw new InvalidOperationException($"Could not update GL account {code}: {GlJson.Message(result) ?? result.StatusCode.ToString()}");
|
||||
}
|
||||
|
||||
private static string? ReadGlConfigValue(GeneralLedgerResult result, string key)
|
||||
{
|
||||
if (!result.Success || GlJson.Data(result) is not { } data) return null;
|
||||
var items = data.ValueKind == JsonValueKind.Array ? data : GlJson.Get(data, "items");
|
||||
if (items is not { ValueKind: JsonValueKind.Array }) return null;
|
||||
foreach (var item in items.Value.EnumerateArray())
|
||||
if (GlJson.GetString(item, "configKey") == key) return GlJson.GetString(item, "configValue");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using Distribution.Features.GeneralLedger;
|
||||
using Distribution.Features.Po;
|
||||
using Distribution.Features.Stock;
|
||||
using Distribution.Features.SysConfig;
|
||||
@@ -13,7 +14,8 @@ public class GrnService(
|
||||
IUnitOfWork uow,
|
||||
Distribution.Features.Item.IItemRepository itemRepo,
|
||||
Distribution.Features.Supplier.ISupplierRepository supplierRepo,
|
||||
Distribution.Features.SysConfig.SysConfigService sysConfig)
|
||||
Distribution.Features.SysConfig.SysConfigService sysConfig,
|
||||
GlPostingService gl)
|
||||
{
|
||||
private static GrnItemResponse MapItem(Models.GoodsReceiptItem i)
|
||||
{
|
||||
@@ -482,6 +484,7 @@ public class GrnService(
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
var saved = await repo.GetByIdAsync(goodsReceipt.Id);
|
||||
await gl.PostGrnAsync(saved!, ct);
|
||||
return Map(saved!);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ public record PrintInvoiceRequest(Guid? PrintedByUserId = null);
|
||||
public record InvoiceResponse(
|
||||
Guid Id,
|
||||
Guid ShopPOId,
|
||||
Guid StoreId,
|
||||
string StoreName,
|
||||
InvoiceStatus Status,
|
||||
PaymentStatus PaymentStatus,
|
||||
@@ -19,6 +20,7 @@ public record InvoiceResponse(
|
||||
DateTime? FinalizedAt,
|
||||
Guid? FinalizedBy,
|
||||
decimal TotalAmount,
|
||||
decimal PaidAmount,
|
||||
bool IsActive,
|
||||
DateTime CreatedAt);
|
||||
|
||||
|
||||
@@ -21,6 +21,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)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Distribution.Features.Stock;
|
||||
using Distribution.Features.GeneralLedger;
|
||||
using Distribution.Features.Stock;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Infra.UoW;
|
||||
using Distribution.Models;
|
||||
@@ -8,15 +9,19 @@ namespace Distribution.Features.Invoice;
|
||||
public class InvoiceService(
|
||||
IInvoiceRepository repo,
|
||||
IUnitOfWork uow,
|
||||
ICurrentUserService currentUser)
|
||||
ICurrentUserService currentUser,
|
||||
GlPostingService gl)
|
||||
{
|
||||
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.Payments?.Where(p => p.IsActive).Sum(p => p.Amount) ?? 0m,
|
||||
i.IsActive, i.CreatedAt);
|
||||
|
||||
public async Task<InvoiceResponse?> GetByIdAsync(Guid id)
|
||||
{
|
||||
@@ -93,6 +98,8 @@ public class InvoiceService(
|
||||
try { repo.Update(invoice); await uow.CommitAsync(ct); }
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
await gl.PostInvoiceFinalizedAsync(invoice, ct);
|
||||
|
||||
return ToResponse(invoice);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Distribution.Features.LoadingEvent;
|
||||
public class LoadingSheetService(
|
||||
ILoadingSheetRepository repo,
|
||||
IRideRepository rideRepo,
|
||||
IStockRepository stockRepo,
|
||||
StockMover mover,
|
||||
IUnitOfWork uow)
|
||||
{
|
||||
public async Task<PagedResponse<LoadingSheetResponse>> GetAllAsync(Guid? rideId, LoadingSheetStatus? status, Guid? routeId, Guid? regionId, int page, int pageSize)
|
||||
@@ -154,59 +154,14 @@ public class LoadingSheetService(
|
||||
|
||||
private async Task ApplyLoadMovementsAsync(Guid sheetId, Guid vehicleId, List<LoadingSheetItem> items, Guid userId)
|
||||
{
|
||||
// Vehicle balances created during this confirm are only tracked, not yet saved, so a
|
||||
// later row for the same item would not find them via the repository and would try to
|
||||
// insert a duplicate (item, vehicle) balance.
|
||||
var createdVehicleBalances = new Dictionary<Guid, StockBalance>();
|
||||
|
||||
foreach (var item in items.Where(i => i.QuantityDispatched > 0))
|
||||
{
|
||||
var warehouseBalance = await stockRepo.GetBalanceAsync(item.FK_ItemId, item.FK_WarehouseId)
|
||||
?? throw new InvalidOperationException(
|
||||
$"No stock found for '{item.Item?.Name ?? item.FK_ItemId.ToString()}' in warehouse '{item.Warehouse?.Name ?? item.FK_WarehouseId.ToString()}'");
|
||||
var label = $"{item.Item?.Name ?? item.FK_ItemId.ToString()} (warehouse {item.Warehouse?.Name ?? item.FK_WarehouseId.ToString()})";
|
||||
|
||||
if (warehouseBalance.Quantity < item.QuantityDispatched)
|
||||
throw new InvalidOperationException(
|
||||
$"Insufficient stock for '{item.Item?.Name ?? item.FK_ItemId.ToString()}' in warehouse '{item.Warehouse?.Name ?? item.FK_WarehouseId.ToString()}' " +
|
||||
$"(available {warehouseBalance.Quantity}, needed {item.QuantityDispatched})");
|
||||
|
||||
warehouseBalance.Quantity -= item.QuantityDispatched;
|
||||
warehouseBalance.UpdatedAt = DateTime.Now;
|
||||
stockRepo.UpdateBalance(warehouseBalance);
|
||||
|
||||
var vehicleBalance = createdVehicleBalances.GetValueOrDefault(item.FK_ItemId)
|
||||
?? await stockRepo.GetBalanceAsync(item.FK_ItemId, vehicleId);
|
||||
if (vehicleBalance is null)
|
||||
{
|
||||
var created = new StockBalance
|
||||
{
|
||||
FK_ItemId = item.FK_ItemId,
|
||||
LocationId = vehicleId,
|
||||
LocationType = LocationType.Vehicle,
|
||||
Quantity = item.QuantityDispatched
|
||||
};
|
||||
createdVehicleBalances[item.FK_ItemId] = created;
|
||||
await stockRepo.AddBalanceAsync(created);
|
||||
}
|
||||
else
|
||||
{
|
||||
vehicleBalance.Quantity += item.QuantityDispatched;
|
||||
vehicleBalance.UpdatedAt = DateTime.Now;
|
||||
stockRepo.UpdateBalance(vehicleBalance);
|
||||
}
|
||||
|
||||
await stockRepo.AddMovementAsync(new StockMovement
|
||||
{
|
||||
FK_ItemId = item.FK_ItemId,
|
||||
FromLocationId = item.FK_WarehouseId,
|
||||
FromLocationType = LocationType.Warehouse,
|
||||
ToLocationId = vehicleId,
|
||||
ToLocationType = LocationType.Vehicle,
|
||||
Quantity = item.QuantityDispatched,
|
||||
Type = MovementType.Load,
|
||||
ReferenceId = sheetId,
|
||||
CreatedBy = userId
|
||||
});
|
||||
await mover.MoveAsync(item.FK_ItemId, label,
|
||||
item.FK_WarehouseId, LocationType.Warehouse,
|
||||
vehicleId, LocationType.Vehicle,
|
||||
item.QuantityDispatched, MovementType.Load, sheetId, userId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,8 @@
|
||||
using Distribution.Features.Invoice;
|
||||
using Distribution.Features.BankAccount;
|
||||
using Distribution.Features.GeneralLedger;
|
||||
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 +12,17 @@ namespace Distribution.Features.Payment;
|
||||
public class PaymentService(
|
||||
IPaymentRepository repo,
|
||||
IInvoiceRepository invoiceRepo,
|
||||
IBankAccountRepository bankAccountRepo,
|
||||
IReceivedChequeRepository receivedChequeRepo,
|
||||
IStoreCreditMovementRepository creditRepo,
|
||||
IUnitOfWork uow,
|
||||
ICurrentUserService currentUser)
|
||||
ICurrentUserService currentUser,
|
||||
GlPostingService gl)
|
||||
{
|
||||
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 +38,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 +93,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 +102,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,10 +151,14 @@ public class PaymentService(
|
||||
: PaymentStatus.Paid;
|
||||
|
||||
invoiceRepo.Update(invoice);
|
||||
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
// Mirrors to the General Ledger after commit: Dr Cash/Bank (or Cheques-in-hand) / Cr Receivable. Never throws.
|
||||
await gl.PostCustomerPaymentAsync(payment, ct);
|
||||
|
||||
return ToResponse(payment);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ public class PurchaseReturnService(
|
||||
IPurchaseReturnRepository repo,
|
||||
IPoRepository poRepo,
|
||||
IGrnRepository goodsReceiptRepo,
|
||||
IStockRepository stockRepo,
|
||||
StockMover mover,
|
||||
IUnitOfWork uow)
|
||||
{
|
||||
private static PurchaseReturnItemResponse MapItem(Models.PurchaseReturnItem i) =>
|
||||
@@ -89,29 +89,12 @@ public class PurchaseReturnService(
|
||||
|
||||
foreach (var line in purchaseReturn.Items)
|
||||
{
|
||||
var balance = await stockRepo.GetBalanceAsync(line.FK_ItemId, warehouseId)
|
||||
?? throw new InvalidOperationException($"No stock balance found for item {line.FK_ItemId} at the receiving warehouse.");
|
||||
var itemName = po.Items.FirstOrDefault(i => i.FK_ItemId == line.FK_ItemId)?.Item?.Name ?? line.FK_ItemId.ToString();
|
||||
|
||||
if (balance.Quantity < line.Quantity)
|
||||
throw new InvalidOperationException($"Insufficient warehouse stock for item {line.FK_ItemId} to return.");
|
||||
|
||||
balance.Quantity -= line.Quantity;
|
||||
balance.UpdatedAt = DateTime.Now;
|
||||
stockRepo.UpdateBalance(balance);
|
||||
|
||||
await stockRepo.AddMovementAsync(new StockMovement
|
||||
{
|
||||
FK_ItemId = line.FK_ItemId,
|
||||
FromLocationId = warehouseId,
|
||||
FromLocationType = LocationType.Warehouse,
|
||||
ToLocationId = StockService.ExternalLocationId,
|
||||
ToLocationType = LocationType.External,
|
||||
Quantity = line.Quantity,
|
||||
Type = MovementType.PurchaseReturn,
|
||||
ReasonCode = request.Reason,
|
||||
ReferenceId = purchaseReturn.Id,
|
||||
CreatedBy = createdByUserId
|
||||
});
|
||||
await mover.MoveAsync(line.FK_ItemId, itemName,
|
||||
warehouseId, LocationType.Warehouse,
|
||||
StockService.ExternalLocationId, LocationType.External,
|
||||
line.Quantity, MovementType.PurchaseReturn, purchaseReturn.Id, createdByUserId, request.Reason);
|
||||
}
|
||||
|
||||
await uow.CommitAsync(ct);
|
||||
|
||||
@@ -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,118 @@
|
||||
using Distribution.Features.BankAccount;
|
||||
using Distribution.Features.ChequeBook;
|
||||
using Distribution.Features.GeneralLedger;
|
||||
using Distribution.Infra.UoW;
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.ReceivedCheque;
|
||||
|
||||
public class ReceivedChequeService(
|
||||
IReceivedChequeRepository repo,
|
||||
IBankAccountRepository bankAccountRepo,
|
||||
IUnitOfWork uow,
|
||||
GlPostingService gl)
|
||||
{
|
||||
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);
|
||||
await gl.PostReceivedChequeAsync(cheque, request.Action, ct);
|
||||
return ToResponse(cheque);
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ public class ReturnController(ReturnService service, ICurrentUserService current
|
||||
var result = await service.CreateAsync(id, request, userId.Value, ct);
|
||||
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
||||
}
|
||||
catch (NotImplementedException ex) { return StatusCode(501, new { error = ex.Message }); }
|
||||
|
||||
catch (InvalidOperationException ex) { return Conflict(new { error = ex.Message }); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Distribution.Features.Category;
|
||||
using Distribution.Features.Item;
|
||||
using Distribution.Features.ShopPO;
|
||||
using Distribution.Features.Stock;
|
||||
@@ -10,7 +11,8 @@ public class ReturnService(
|
||||
IReturnRepository repo,
|
||||
IShopPORepository shopPORepo,
|
||||
IItemRepository itemRepo,
|
||||
IStockRepository stockRepo,
|
||||
ICategoryRepository categoryRepo,
|
||||
StockMover mover,
|
||||
IUnitOfWork uow)
|
||||
{
|
||||
private static SaleReturnItemResponse MapItem(SaleReturnItem i) =>
|
||||
@@ -57,6 +59,72 @@ public class ReturnService(
|
||||
public async Task<List<SaleReturnResponse>> GetByShopPOIdAsync(Guid shopPOId) =>
|
||||
(await repo.GetByShopPOIdAsync(shopPOId)).Select(Map).ToList();
|
||||
|
||||
public Task<SaleReturnResponse> CreateAsync(Guid shopPOId, CreateSaleReturnRequest request, Guid createdByUserId, CancellationToken ct = default) =>
|
||||
throw new NotImplementedException("Return flow is being redesigned — not yet implemented.");
|
||||
/// <summary>
|
||||
/// Stock-only: records the return and puts the goods back on the vehicle that delivered them (restoring the
|
||||
/// batches/serials that were sold). Credit/exchange money handling is not applied here, so TotalCreditAmount stays 0.
|
||||
/// </summary>
|
||||
public async Task<SaleReturnResponse> CreateAsync(Guid shopPOId, CreateSaleReturnRequest request, Guid createdByUserId, CancellationToken ct = default)
|
||||
{
|
||||
var shopPO = await shopPORepo.GetByIdAsync(shopPOId);
|
||||
if (shopPO is null || !shopPO.IsActive)
|
||||
throw new InvalidOperationException("Order not found.");
|
||||
|
||||
if (request.Items is null || request.Items.Count == 0)
|
||||
throw new InvalidOperationException("A return must have at least one line item.");
|
||||
|
||||
if (request.Items.GroupBy(i => i.ItemId).Any(g => g.Count() > 1))
|
||||
throw new InvalidOperationException("Each item can appear only once on a return.");
|
||||
|
||||
var saleReturn = new SaleReturn
|
||||
{
|
||||
FK_ShopPOId = shopPOId,
|
||||
Mode = request.Mode,
|
||||
TotalCreditAmount = 0,
|
||||
CreatedByUserId = createdByUserId
|
||||
};
|
||||
|
||||
foreach (var line in request.Items)
|
||||
{
|
||||
if (line.Quantity <= 0)
|
||||
throw new InvalidOperationException("Return quantity must be greater than zero.");
|
||||
|
||||
var shopPOItem = shopPO.Items.FirstOrDefault(i => i.FK_ItemId == line.ItemId && i.IsActive)
|
||||
?? throw new InvalidOperationException($"Item {line.ItemId} is not on this order.");
|
||||
|
||||
var item = await itemRepo.GetByIdAsync(line.ItemId)
|
||||
?? throw new InvalidOperationException("Item not found.");
|
||||
|
||||
var category = await categoryRepo.GetByIdAsync(item.FK_CategoryId)
|
||||
?? throw new InvalidOperationException("Category not found.");
|
||||
|
||||
if (!category.AllowCustomerReturn)
|
||||
throw new InvalidOperationException($"Customer returns are not allowed for category '{category.Name}'.");
|
||||
|
||||
saleReturn.Items.Add(new SaleReturnItem
|
||||
{
|
||||
FK_ItemId = line.ItemId,
|
||||
Quantity = line.Quantity,
|
||||
ReasonCode = line.ReasonCode,
|
||||
UnitPrice = shopPOItem.UnitPrice
|
||||
});
|
||||
}
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
await repo.AddAsync(saleReturn);
|
||||
|
||||
foreach (var line in saleReturn.Items)
|
||||
{
|
||||
var itemName = shopPO.Items.First(i => i.FK_ItemId == line.FK_ItemId).Item?.Name ?? line.FK_ItemId.ToString();
|
||||
await mover.ReturnToVehicleAsync(line.FK_ItemId, itemName, line.Quantity, shopPOId, createdByUserId, line.ReasonCode.ToString());
|
||||
}
|
||||
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
var saved = await repo.GetByIdAsync(saleReturn.Id);
|
||||
return Map(saved!);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
using Distribution.Models;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Ride;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/rides")]
|
||||
public class RideController(RideService service) : ControllerBase
|
||||
public class RideController(RideService service, ICurrentUserService currentUser) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAll(
|
||||
@@ -54,7 +55,7 @@ public class RideController(RideService service) : ControllerBase
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await service.CloseAsync(id, request, ct);
|
||||
var result = await service.CloseAsync(id, request, currentUser.UserId ?? Guid.Empty, ct);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (KeyNotFoundException) { return NotFound(); }
|
||||
@@ -66,7 +67,7 @@ public class RideController(RideService service) : ControllerBase
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await service.CancelAsync(id, request, ct);
|
||||
var result = await service.CancelAsync(id, request, currentUser.UserId ?? Guid.Empty, ct);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (KeyNotFoundException) { return NotFound(); }
|
||||
|
||||
@@ -10,7 +10,7 @@ public class RideService(
|
||||
IRideRepository rideRepo,
|
||||
IDriverProfileRepository driverRepo,
|
||||
IDriverComplianceConfigRepository complianceRepo,
|
||||
IStockRepository stockRepo,
|
||||
StockMover mover,
|
||||
IUnitOfWork uow)
|
||||
{
|
||||
public async Task<PagedResponse<RideResponse>> GetAllAsync(RideStatus? status, Guid? driverProfileId, Guid? vehicleId, int page, int pageSize)
|
||||
@@ -76,7 +76,7 @@ public class RideService(
|
||||
return ToResponse(saved!);
|
||||
}
|
||||
|
||||
public async Task<RideResponse> CloseAsync(Guid rideId, CloseRideRequest request, CancellationToken ct = default)
|
||||
public async Task<RideResponse> CloseAsync(Guid rideId, CloseRideRequest request, Guid userId, CancellationToken ct = default)
|
||||
{
|
||||
var ride = await rideRepo.GetByIdAsync(rideId)
|
||||
?? throw new KeyNotFoundException("Ride not found");
|
||||
@@ -87,20 +87,24 @@ public class RideService(
|
||||
if (request.Meterend < ride.Meterstart)
|
||||
throw new InvalidOperationException($"Meter end ({request.Meterend}) cannot be less than meter start ({ride.Meterstart})");
|
||||
|
||||
await EnsureVehicleStockClearedAsync(ride.FK_VehicleId);
|
||||
|
||||
ride.Status = RideStatus.Closed;
|
||||
ride.ClosedAt = DateTime.Now;
|
||||
ride.Meterend = request.Meterend;
|
||||
|
||||
// Whatever wasn't delivered goes back to the warehouse it was loaded from.
|
||||
await uow.BeginAsync(ct);
|
||||
try { rideRepo.Update(ride); await uow.CommitAsync(ct); }
|
||||
try
|
||||
{
|
||||
await mover.UnloadVehicleAsync(ride.FK_VehicleId, ride.Id, userId, "RideClosed");
|
||||
rideRepo.Update(ride);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return ToResponse(ride);
|
||||
}
|
||||
|
||||
public async Task<RideResponse> CancelAsync(Guid rideId, CancelRideRequest request, CancellationToken ct = default)
|
||||
public async Task<RideResponse> CancelAsync(Guid rideId, CancelRideRequest request, Guid userId, CancellationToken ct = default)
|
||||
{
|
||||
var ride = await rideRepo.GetByIdAsync(rideId)
|
||||
?? throw new KeyNotFoundException("Ride not found");
|
||||
@@ -113,7 +117,12 @@ public class RideService(
|
||||
ride.CancelReason = request.Reason;
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try { rideRepo.Update(ride); await uow.CommitAsync(ct); }
|
||||
try
|
||||
{
|
||||
await mover.UnloadVehicleAsync(ride.FK_VehicleId, ride.Id, userId, "RideCancelled");
|
||||
rideRepo.Update(ride);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return ToResponse(ride);
|
||||
@@ -139,13 +148,6 @@ public class RideService(
|
||||
throw new InvalidOperationException("Driver medical clearance has expired");
|
||||
}
|
||||
|
||||
private async Task EnsureVehicleStockClearedAsync(Guid vehicleId)
|
||||
{
|
||||
var balances = await stockRepo.GetBalancesByLocationAsync(vehicleId, LocationType.Vehicle);
|
||||
if (balances.Any(b => b.Quantity > 0))
|
||||
throw new InvalidOperationException("All vehicle stock must be returned before this ride can be completed");
|
||||
}
|
||||
|
||||
private static RideResponse ToResponse(DeliveryRun r) =>
|
||||
new(r.Id, r.Status,
|
||||
r.FK_VehicleId, r.Vehicle?.NumberPlate ?? string.Empty,
|
||||
|
||||
@@ -7,6 +7,8 @@ public interface IStockRepository
|
||||
// Balance
|
||||
Task<StockBalance?> GetBalanceAsync(Guid itemId, Guid locationId);
|
||||
Task<StockBalance?> GetBalanceAsync(Guid itemId, Guid locationId, Guid? itemBatchId, Guid? itemSerialNumberId);
|
||||
/// <summary>Positive balances for the item at the location in pick order: batches by earliest expiry, serials oldest first, untracked last.</summary>
|
||||
Task<List<StockBalance>> GetAllocatableBalancesAsync(Guid itemId, Guid locationId);
|
||||
Task<List<StockBalance>> GetBalancesByLocationAsync(Guid locationId, LocationType locationType);
|
||||
Task<List<StockBalance>> GetBalancesAsync(Guid? locationId, LocationType? locationType, Guid? itemId, int page, int pageSize);
|
||||
Task<int> GetBalancesCountAsync(Guid? locationId, LocationType? locationType, Guid? itemId);
|
||||
@@ -19,14 +21,21 @@ public interface IStockRepository
|
||||
Task AddMovementAsync(StockMovement movement);
|
||||
Task<StockMovement?> GetMovementByIdAsync(Guid id);
|
||||
void UpdateMovement(StockMovement movement);
|
||||
Task<List<StockMovement>> GetMovementsAsync(Guid? locationId, Guid? itemId, MovementType? type, DateTime? from, DateTime? to, int page, int pageSize);
|
||||
Task<int> GetMovementsCountAsync(Guid? locationId, Guid? itemId, MovementType? type, DateTime? from, DateTime? to);
|
||||
Task<List<StockMovement>> GetMovementsAsync(MovementFilter filter, int page, int pageSize);
|
||||
Task<int> GetMovementsCountAsync(MovementFilter filter);
|
||||
/// <summary>Opening balance, quantity in/out within [from, toExclusive), closing balance, and the live balance to reconcile against.</summary>
|
||||
Task<MovementSummary> GetMovementSummaryAsync(Guid itemId, Guid locationId, DateTime from, DateTime toExclusive);
|
||||
|
||||
Task<List<StockMovement>> GetMovementsByReferenceAsync(Guid referenceId, Guid itemId, MovementType type);
|
||||
/// <summary>The warehouse the item most recently arrived at <paramref name="vehicleId"/> from, or null if it never did.</summary>
|
||||
Task<Guid?> GetLastWarehouseSourceAsync(Guid vehicleId, Guid itemId);
|
||||
|
||||
// Batches — a null batchNumber always creates a fresh expiry-only lot (no dedup possible on null).
|
||||
Task<ItemBatch?> FindBatchAsync(Guid itemId, string batchNumber);
|
||||
Task AddItemBatchAsync(ItemBatch batch);
|
||||
|
||||
// Serial numbers
|
||||
Task<ItemSerialNumber?> GetSerialAsync(Guid serialId);
|
||||
Task<bool> SerialNumberExistsAsync(Guid itemId, string serialNumber);
|
||||
/// <summary>One past the highest numeric suffix among the item's existing serials that start with <paramref name="prefix"/> (1 if none).</summary>
|
||||
Task<int> GetNextSerialSequenceAsync(Guid itemId, string prefix);
|
||||
|
||||
@@ -58,10 +58,28 @@ public class StockController(StockService service, ICurrentUserService currentUs
|
||||
[FromQuery] MovementType? type,
|
||||
[FromQuery] DateTime? from,
|
||||
[FromQuery] DateTime? to,
|
||||
[FromQuery] Guid? referenceId,
|
||||
[FromQuery] Guid? batchId,
|
||||
[FromQuery] Guid? serialId,
|
||||
[FromQuery] Guid? createdBy,
|
||||
[FromQuery] string? reasonCode,
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 20)
|
||||
{
|
||||
var result = await service.GetMovementsAsync(locationId, itemId, type, from, to, page, pageSize);
|
||||
var filter = new MovementFilter(locationId, itemId, type, from, to, referenceId, batchId, serialId, createdBy, reasonCode);
|
||||
var result = await service.GetMovementsAsync(filter, page, pageSize);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>Opening / in / out / closing quantity for one item at one location over a date range.</summary>
|
||||
[HttpGet("movements/summary")]
|
||||
public async Task<IActionResult> GetMovementSummary(
|
||||
[FromQuery] Guid itemId,
|
||||
[FromQuery] Guid locationId,
|
||||
[FromQuery] DateTime from,
|
||||
[FromQuery] DateTime to)
|
||||
{
|
||||
var result = await service.GetMovementSummaryAsync(itemId, locationId, from, to);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,12 @@ public record StockMovementResponse(
|
||||
DateTime CreatedAt,
|
||||
Guid CreatedBy,
|
||||
string? FromLocationName,
|
||||
string? ToLocationName);
|
||||
string? ToLocationName,
|
||||
Guid? BatchId = null,
|
||||
string? BatchNumber = null,
|
||||
DateTime? BatchExpiryDate = null,
|
||||
Guid? SerialId = null,
|
||||
string? SerialNumber = null);
|
||||
|
||||
// Write requests
|
||||
public record TransferRequest(
|
||||
@@ -82,3 +87,23 @@ public record AdjustmentRequest(
|
||||
// Movements are an immutable ledger — quantity, locations, and type can never change once recorded
|
||||
// (the running stock balance is the sum of all movements). Only the free-text reason/note is editable.
|
||||
public record UpdateMovementReasonRequest(string ReasonCode);
|
||||
|
||||
// Movement report filters
|
||||
public record MovementFilter(
|
||||
Guid? LocationId = null,
|
||||
Guid? ItemId = null,
|
||||
MovementType? Type = null,
|
||||
DateTime? From = null,
|
||||
DateTime? To = null,
|
||||
Guid? ReferenceId = null,
|
||||
Guid? BatchId = null,
|
||||
Guid? SerialId = null,
|
||||
Guid? CreatedBy = null,
|
||||
string? ReasonCode = null);
|
||||
|
||||
public record MovementSummary(
|
||||
decimal OpeningQuantity,
|
||||
decimal QuantityIn,
|
||||
decimal QuantityOut,
|
||||
decimal ClosingQuantity,
|
||||
decimal CurrentBalance);
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
using Distribution.Features.SysConfig;
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.Stock;
|
||||
|
||||
/// <summary>
|
||||
/// Applies a stock movement end to end: picks which batch/serial balances to take from (FEFO / FIFO),
|
||||
/// updates the source and destination balances, keeps serial status/location in step, and writes one
|
||||
/// StockMovement per slice so the ledger always carries the batch/serial that actually moved.
|
||||
/// Tracks changes only — the calling service owns the unit of work.
|
||||
/// </summary>
|
||||
public class StockMover(IStockRepository repo, SysConfigService sysConfig)
|
||||
{
|
||||
private static readonly Guid External = StockService.ExternalLocationId;
|
||||
|
||||
private readonly record struct StockKey(Guid LocationId, Guid? BatchId, Guid? SerialId);
|
||||
|
||||
public async Task<List<StockMovement>> MoveAsync(
|
||||
Guid itemId, string itemName,
|
||||
Guid fromId, LocationType fromType,
|
||||
Guid toId, LocationType toType,
|
||||
decimal quantity, MovementType type,
|
||||
Guid? referenceId, Guid userId, string? reasonCode = null)
|
||||
{
|
||||
if (quantity <= 0)
|
||||
throw new InvalidOperationException("Quantity must be greater than zero.");
|
||||
|
||||
var balances = await repo.GetAllocatableBalancesAsync(itemId, fromId);
|
||||
var available = balances.Sum(b => b.Quantity);
|
||||
if (available < quantity)
|
||||
throw new InvalidOperationException(
|
||||
$"Insufficient stock for '{itemName}': available {available}, required {quantity}");
|
||||
|
||||
var created = new List<StockMovement>();
|
||||
var remaining = quantity;
|
||||
foreach (var balance in balances)
|
||||
{
|
||||
if (remaining <= 0) break;
|
||||
var take = Math.Min(balance.Quantity, remaining);
|
||||
|
||||
balance.Quantity -= take;
|
||||
balance.UpdatedAt = DateTime.Now;
|
||||
repo.UpdateBalance(balance);
|
||||
|
||||
if (toType != LocationType.External)
|
||||
await CreditAsync(itemId, toId, toType, balance.FK_ItemBatchId, balance.FK_ItemSerialNumberId, take);
|
||||
|
||||
if (balance.ItemSerialNumber is not null)
|
||||
await ApplySerialAsync(balance.ItemSerialNumber, balance.Item, toId, toType, type);
|
||||
|
||||
var movement = new StockMovement
|
||||
{
|
||||
FK_ItemId = itemId,
|
||||
FromLocationId = fromId,
|
||||
FromLocationType = fromType,
|
||||
ToLocationId = toId,
|
||||
ToLocationType = toType,
|
||||
FK_ItemBatchId = balance.FK_ItemBatchId,
|
||||
FK_ItemSerialNumberId = balance.FK_ItemSerialNumberId,
|
||||
Quantity = take,
|
||||
Type = type,
|
||||
ReasonCode = reasonCode,
|
||||
ReferenceId = referenceId,
|
||||
CreatedBy = userId
|
||||
};
|
||||
await repo.AddMovementAsync(movement);
|
||||
created.Add(movement);
|
||||
|
||||
remaining -= take;
|
||||
}
|
||||
return created;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Puts returned goods back on the vehicle that delivered them, restoring the same batches/serials that
|
||||
/// were sold on that ShopPO. Quantity is capped at what was delivered and not yet returned.
|
||||
/// </summary>
|
||||
public async Task<List<StockMovement>> ReturnToVehicleAsync(
|
||||
Guid itemId, string itemName, decimal quantity, Guid shopPoId, Guid userId, string? reasonCode = null)
|
||||
{
|
||||
if (quantity <= 0)
|
||||
throw new InvalidOperationException("Quantity must be greater than zero.");
|
||||
|
||||
var sold = await repo.GetMovementsByReferenceAsync(shopPoId, itemId, MovementType.Sale);
|
||||
if (sold.Count == 0)
|
||||
throw new InvalidOperationException($"'{itemName}' was not delivered on this order, so it cannot be returned.");
|
||||
|
||||
var returned = await repo.GetMovementsByReferenceAsync(shopPoId, itemId, MovementType.CustomerReturn);
|
||||
|
||||
var outstanding = new Dictionary<StockKey, decimal>();
|
||||
foreach (var m in sold)
|
||||
{
|
||||
var key = new StockKey(m.FromLocationId!.Value, m.FK_ItemBatchId, m.FK_ItemSerialNumberId);
|
||||
outstanding[key] = outstanding.GetValueOrDefault(key) + m.Quantity;
|
||||
}
|
||||
foreach (var m in returned)
|
||||
{
|
||||
var key = new StockKey(m.ToLocationId!.Value, m.FK_ItemBatchId, m.FK_ItemSerialNumberId);
|
||||
outstanding[key] = outstanding.GetValueOrDefault(key) - m.Quantity;
|
||||
}
|
||||
|
||||
var returnable = outstanding.Where(kv => kv.Value > 0).Sum(kv => kv.Value);
|
||||
if (quantity > returnable)
|
||||
throw new InvalidOperationException(
|
||||
$"Return quantity for '{itemName}' ({quantity}) exceeds the delivered-but-not-yet-returned quantity ({returnable}).");
|
||||
|
||||
var created = new List<StockMovement>();
|
||||
var remaining = quantity;
|
||||
foreach (var (key, qty) in outstanding.Where(kv => kv.Value > 0))
|
||||
{
|
||||
if (remaining <= 0) break;
|
||||
var take = Math.Min(qty, remaining);
|
||||
|
||||
await CreditAsync(itemId, key.LocationId, LocationType.Vehicle, key.BatchId, key.SerialId, take);
|
||||
|
||||
if (key.SerialId is Guid serialId)
|
||||
{
|
||||
var serial = await repo.GetSerialAsync(serialId);
|
||||
if (serial is not null)
|
||||
await ApplySerialAsync(serial, null, key.LocationId, LocationType.Vehicle, MovementType.CustomerReturn);
|
||||
}
|
||||
|
||||
var movement = new StockMovement
|
||||
{
|
||||
FK_ItemId = itemId,
|
||||
FromLocationId = External,
|
||||
FromLocationType = LocationType.External,
|
||||
ToLocationId = key.LocationId,
|
||||
ToLocationType = LocationType.Vehicle,
|
||||
FK_ItemBatchId = key.BatchId,
|
||||
FK_ItemSerialNumberId = key.SerialId,
|
||||
Quantity = take,
|
||||
Type = MovementType.CustomerReturn,
|
||||
ReasonCode = reasonCode,
|
||||
ReferenceId = shopPoId,
|
||||
CreatedBy = userId
|
||||
};
|
||||
await repo.AddMovementAsync(movement);
|
||||
created.Add(movement);
|
||||
|
||||
remaining -= take;
|
||||
}
|
||||
return created;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends everything still on the vehicle back to the warehouse it was loaded from.
|
||||
/// </summary>
|
||||
public async Task UnloadVehicleAsync(Guid vehicleId, Guid referenceId, Guid userId, string reasonCode)
|
||||
{
|
||||
var onVehicle = (await repo.GetBalancesByLocationAsync(vehicleId, LocationType.Vehicle))
|
||||
.Where(b => b.Quantity > 0)
|
||||
.GroupBy(b => b.FK_ItemId);
|
||||
|
||||
foreach (var group in onVehicle)
|
||||
{
|
||||
var item = group.First().Item;
|
||||
var warehouseId = await repo.GetLastWarehouseSourceAsync(vehicleId, group.Key)
|
||||
?? throw new InvalidOperationException(
|
||||
$"Cannot unload '{item.Name}': no source warehouse found for the stock on this vehicle.");
|
||||
|
||||
await MoveAsync(group.Key, item.Name, vehicleId, LocationType.Vehicle,
|
||||
warehouseId, LocationType.Warehouse, group.Sum(b => b.Quantity),
|
||||
MovementType.Transfer, referenceId, userId, reasonCode);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CreditAsync(Guid itemId, Guid locationId, LocationType locationType,
|
||||
Guid? batchId, Guid? serialId, decimal quantity)
|
||||
{
|
||||
var balance = await repo.GetBalanceAsync(itemId, locationId, batchId, serialId);
|
||||
if (balance is null)
|
||||
{
|
||||
await repo.AddBalanceAsync(new StockBalance
|
||||
{
|
||||
FK_ItemId = itemId,
|
||||
LocationId = locationId,
|
||||
LocationType = locationType,
|
||||
FK_ItemBatchId = batchId,
|
||||
FK_ItemSerialNumberId = serialId,
|
||||
Quantity = quantity
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
balance.Quantity += quantity;
|
||||
balance.UpdatedAt = DateTime.Now;
|
||||
repo.UpdateBalance(balance);
|
||||
}
|
||||
|
||||
private async Task ApplySerialAsync(ItemSerialNumber serial, Models.Item? item, Guid toId, LocationType toType, MovementType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case MovementType.Sale:
|
||||
serial.Status = SerialStatus.Sold;
|
||||
serial.CurrentLocationId = null;
|
||||
serial.CurrentLocationType = null;
|
||||
// Warranty clock starts at sale, not receipt.
|
||||
if (item is { HasWarranty: true } && await sysConfig.GetWarrantyTrackingEnabledAsync())
|
||||
{
|
||||
var days = serial.WarrantyPeriodDaysOverride ?? await sysConfig.GetDefaultWarrantyPeriodDaysAsync();
|
||||
serial.WarrantyExpiresAt = DateTime.Now.AddDays(days);
|
||||
}
|
||||
break;
|
||||
|
||||
case MovementType.Damage:
|
||||
serial.Status = SerialStatus.Damaged;
|
||||
serial.CurrentLocationId = null;
|
||||
serial.CurrentLocationType = null;
|
||||
break;
|
||||
|
||||
case MovementType.CustomerReturn:
|
||||
serial.Status = SerialStatus.Returned;
|
||||
serial.CurrentLocationId = toId;
|
||||
serial.CurrentLocationType = toType;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (toType == LocationType.External)
|
||||
{
|
||||
serial.CurrentLocationId = null;
|
||||
serial.CurrentLocationType = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (type == MovementType.Load) serial.Status = SerialStatus.InStock;
|
||||
serial.CurrentLocationId = toId;
|
||||
serial.CurrentLocationType = toType;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,16 +7,39 @@ namespace Distribution.Features.Stock;
|
||||
public class StockRepository(AppDBContext db) : IStockRepository
|
||||
{
|
||||
public Task<StockBalance?> GetBalanceAsync(Guid itemId, Guid locationId) =>
|
||||
db.StockBalances
|
||||
.Include(b => b.Item)
|
||||
.FirstOrDefaultAsync(b => b.FK_ItemId == itemId && b.LocationId == locationId
|
||||
&& b.FK_ItemBatchId == null && b.FK_ItemSerialNumberId == null);
|
||||
GetBalanceAsync(itemId, locationId, null, null);
|
||||
|
||||
public Task<StockBalance?> GetBalanceAsync(Guid itemId, Guid locationId, Guid? itemBatchId, Guid? itemSerialNumberId) =>
|
||||
db.StockBalances
|
||||
// Looks at rows added earlier in the same unit of work first — they aren't in the database yet,
|
||||
// so a second movement for the same (item, location, batch, serial) would otherwise insert a duplicate.
|
||||
public async Task<StockBalance?> GetBalanceAsync(Guid itemId, Guid locationId, Guid? itemBatchId, Guid? itemSerialNumberId)
|
||||
{
|
||||
var pending = db.StockBalances.Local.FirstOrDefault(b => b.FK_ItemId == itemId && b.LocationId == locationId
|
||||
&& b.FK_ItemBatchId == itemBatchId && b.FK_ItemSerialNumberId == itemSerialNumberId);
|
||||
if (pending is not null) return pending;
|
||||
|
||||
return await db.StockBalances
|
||||
.Include(b => b.Item)
|
||||
.FirstOrDefaultAsync(b => b.FK_ItemId == itemId && b.LocationId == locationId
|
||||
&& b.FK_ItemBatchId == itemBatchId && b.FK_ItemSerialNumberId == itemSerialNumberId);
|
||||
}
|
||||
|
||||
public async Task<List<StockBalance>> GetAllocatableBalancesAsync(Guid itemId, Guid locationId)
|
||||
{
|
||||
var balances = await db.StockBalances
|
||||
.Include(b => b.Item)
|
||||
.Include(b => b.ItemBatch)
|
||||
.Include(b => b.ItemSerialNumber)
|
||||
.Where(b => b.FK_ItemId == itemId && b.LocationId == locationId && b.Quantity > 0)
|
||||
.ToListAsync();
|
||||
|
||||
// Tracked stock first: earliest expiry (FEFO), then oldest received serial (FIFO); untracked rows last.
|
||||
return balances
|
||||
.OrderBy(b => b.FK_ItemBatchId == null && b.FK_ItemSerialNumberId == null ? 1 : 0)
|
||||
.ThenBy(b => b.ItemBatch?.ExpiryDate ?? DateTime.MaxValue)
|
||||
.ThenBy(b => b.ItemSerialNumber?.ReceivedAt ?? DateTime.MaxValue)
|
||||
.ThenBy(b => b.UpdatedAt)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public Task<List<StockBalance>> GetBalancesByLocationAsync(Guid locationId, LocationType locationType) =>
|
||||
db.StockBalances
|
||||
@@ -69,41 +92,88 @@ public class StockRepository(AppDBContext db) : IStockRepository
|
||||
await db.StockMovements.AddAsync(movement);
|
||||
|
||||
public Task<StockMovement?> GetMovementByIdAsync(Guid id) =>
|
||||
db.StockMovements.Include(m => m.Item).FirstOrDefaultAsync(m => m.Id == id);
|
||||
db.StockMovements.Include(m => m.Item).Include(m => m.ItemBatch).Include(m => m.ItemSerialNumber).FirstOrDefaultAsync(m => m.Id == id);
|
||||
|
||||
public void UpdateMovement(StockMovement movement) =>
|
||||
db.StockMovements.Update(movement);
|
||||
|
||||
public Task<List<StockMovement>> GetMovementsAsync(Guid? locationId, Guid? itemId, MovementType? type, DateTime? from, DateTime? to, int page, int pageSize)
|
||||
{
|
||||
var query = db.StockMovements.Include(m => m.Item).AsQueryable();
|
||||
if (locationId.HasValue)
|
||||
query = query.Where(m => m.FromLocationId == locationId.Value || m.ToLocationId == locationId.Value);
|
||||
if (itemId.HasValue) query = query.Where(m => m.FK_ItemId == itemId.Value);
|
||||
if (type.HasValue) query = query.Where(m => m.Type == type.Value);
|
||||
if (from.HasValue) query = query.Where(m => m.CreatedAt >= from.Value);
|
||||
if (to.HasValue) query = query.Where(m => m.CreatedAt <= to.Value);
|
||||
return query.OrderByDescending(m => m.CreatedAt).Skip((page - 1) * pageSize).Take(pageSize).ToListAsync();
|
||||
}
|
||||
|
||||
public Task<int> GetMovementsCountAsync(Guid? locationId, Guid? itemId, MovementType? type, DateTime? from, DateTime? to)
|
||||
private IQueryable<StockMovement> FilterMovements(MovementFilter f)
|
||||
{
|
||||
var query = db.StockMovements.AsQueryable();
|
||||
if (locationId.HasValue)
|
||||
query = query.Where(m => m.FromLocationId == locationId.Value || m.ToLocationId == locationId.Value);
|
||||
if (itemId.HasValue) query = query.Where(m => m.FK_ItemId == itemId.Value);
|
||||
if (type.HasValue) query = query.Where(m => m.Type == type.Value);
|
||||
if (from.HasValue) query = query.Where(m => m.CreatedAt >= from.Value);
|
||||
if (to.HasValue) query = query.Where(m => m.CreatedAt <= to.Value);
|
||||
return query.CountAsync();
|
||||
if (f.LocationId.HasValue)
|
||||
query = query.Where(m => m.FromLocationId == f.LocationId.Value || m.ToLocationId == f.LocationId.Value);
|
||||
if (f.ItemId.HasValue) query = query.Where(m => m.FK_ItemId == f.ItemId.Value);
|
||||
if (f.Type.HasValue) query = query.Where(m => m.Type == f.Type.Value);
|
||||
if (f.ReferenceId.HasValue) query = query.Where(m => m.ReferenceId == f.ReferenceId.Value);
|
||||
if (f.BatchId.HasValue) query = query.Where(m => m.FK_ItemBatchId == f.BatchId.Value);
|
||||
if (f.SerialId.HasValue) query = query.Where(m => m.FK_ItemSerialNumberId == f.SerialId.Value);
|
||||
if (f.CreatedBy.HasValue) query = query.Where(m => m.CreatedBy == f.CreatedBy.Value);
|
||||
if (!string.IsNullOrWhiteSpace(f.ReasonCode)) query = query.Where(m => m.ReasonCode == f.ReasonCode);
|
||||
if (f.From.HasValue) query = query.Where(m => m.CreatedAt >= f.From.Value);
|
||||
if (f.To.HasValue)
|
||||
{
|
||||
// A bare date (midnight) means "through the end of that day".
|
||||
var to = f.To.Value;
|
||||
var toExclusive = to.TimeOfDay == TimeSpan.Zero ? to.AddDays(1) : to;
|
||||
query = query.Where(m => m.CreatedAt < toExclusive);
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
public Task<List<StockMovement>> GetMovementsAsync(MovementFilter filter, int page, int pageSize) =>
|
||||
FilterMovements(filter)
|
||||
.Include(m => m.Item)
|
||||
.Include(m => m.ItemBatch)
|
||||
.Include(m => m.ItemSerialNumber)
|
||||
.OrderByDescending(m => m.CreatedAt)
|
||||
.Skip((page - 1) * pageSize).Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
public Task<int> GetMovementsCountAsync(MovementFilter filter) =>
|
||||
FilterMovements(filter).CountAsync();
|
||||
|
||||
public async Task<MovementSummary> GetMovementSummaryAsync(Guid itemId, Guid locationId, DateTime from, DateTime toExclusive)
|
||||
{
|
||||
var rows = db.StockMovements.AsNoTracking()
|
||||
.Where(m => m.FK_ItemId == itemId && (m.FromLocationId == locationId || m.ToLocationId == locationId));
|
||||
|
||||
var before = await rows.Where(m => m.CreatedAt < from)
|
||||
.GroupBy(_ => 1)
|
||||
.Select(g => g.Sum(m => m.ToLocationId == locationId ? m.Quantity : 0)
|
||||
- g.Sum(m => m.FromLocationId == locationId ? m.Quantity : 0))
|
||||
.FirstOrDefaultAsync();
|
||||
var inRange = rows.Where(m => m.CreatedAt >= from && m.CreatedAt < toExclusive);
|
||||
var received = await inRange.Where(m => m.ToLocationId == locationId).SumAsync(m => (decimal?)m.Quantity) ?? 0;
|
||||
var issued = await inRange.Where(m => m.FromLocationId == locationId).SumAsync(m => (decimal?)m.Quantity) ?? 0;
|
||||
var current = await db.StockBalances.AsNoTracking()
|
||||
.Where(b => b.FK_ItemId == itemId && b.LocationId == locationId)
|
||||
.SumAsync(b => (decimal?)b.Quantity) ?? 0;
|
||||
|
||||
return new MovementSummary(before, received, issued, before + received - issued, current);
|
||||
}
|
||||
|
||||
public Task<List<StockMovement>> GetMovementsByReferenceAsync(Guid referenceId, Guid itemId, MovementType type) =>
|
||||
db.StockMovements.AsNoTracking()
|
||||
.Where(m => m.ReferenceId == referenceId && m.FK_ItemId == itemId && m.Type == type)
|
||||
.ToListAsync();
|
||||
|
||||
public Task<Guid?> GetLastWarehouseSourceAsync(Guid vehicleId, Guid itemId) =>
|
||||
db.StockMovements.AsNoTracking()
|
||||
.Where(m => m.ToLocationId == vehicleId && m.FK_ItemId == itemId
|
||||
&& m.FromLocationType == LocationType.Warehouse)
|
||||
.OrderByDescending(m => m.CreatedAt)
|
||||
.Select(m => m.FromLocationId)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
public Task<ItemBatch?> FindBatchAsync(Guid itemId, string batchNumber) =>
|
||||
db.ItemBatches.FirstOrDefaultAsync(b => b.FK_ItemId == itemId && b.BatchNumber == batchNumber);
|
||||
|
||||
public async Task AddItemBatchAsync(ItemBatch batch) =>
|
||||
await db.ItemBatches.AddAsync(batch);
|
||||
|
||||
public Task<ItemSerialNumber?> GetSerialAsync(Guid serialId) =>
|
||||
db.ItemSerialNumbers.FirstOrDefaultAsync(s => s.Id == serialId);
|
||||
|
||||
public Task<bool> SerialNumberExistsAsync(Guid itemId, string serialNumber) =>
|
||||
db.ItemSerialNumbers.AnyAsync(s => s.FK_ItemId == itemId && s.SerialNumber == serialNumber);
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ public class StockService(
|
||||
ICategoryRepository categoryRepo,
|
||||
IWarehouseRepository warehouseRepo,
|
||||
IVehicleRepository vehicleRepo,
|
||||
StockMover mover,
|
||||
IUnitOfWork uow)
|
||||
{
|
||||
private static StockBalanceResponse MapBalance(StockBalance b, string locationName) =>
|
||||
@@ -37,7 +38,9 @@ public class StockService(
|
||||
private static StockMovementResponse MapMovement(StockMovement m, string? fromLocationName, string? toLocationName) =>
|
||||
new(m.Id, m.FK_ItemId, m.Item.Name, m.FromLocationId, m.FromLocationType,
|
||||
m.ToLocationId, m.ToLocationType, m.Quantity, m.Type, m.ReasonCode,
|
||||
m.ReferenceId, m.CreatedAt, m.CreatedBy, fromLocationName, toLocationName);
|
||||
m.ReferenceId, m.CreatedAt, m.CreatedBy, fromLocationName, toLocationName,
|
||||
m.FK_ItemBatchId, m.ItemBatch?.BatchNumber, m.ItemBatch?.ExpiryDate,
|
||||
m.FK_ItemSerialNumberId, m.ItemSerialNumber?.SerialNumber);
|
||||
|
||||
private async Task<StockMovementResponse> MapMovementAsync(StockMovement m)
|
||||
{
|
||||
@@ -112,11 +115,16 @@ public class StockService(
|
||||
balances.Select(b => MapBalance(b, names.GetValueOrDefault(b.LocationId, "—"))).ToList());
|
||||
}
|
||||
|
||||
public async Task<PagedResponse<StockMovementResponse>> GetMovementsAsync(
|
||||
Guid? locationId, Guid? itemId, MovementType? type, DateTime? from, DateTime? to, int page, int pageSize)
|
||||
public async Task<MovementSummary> GetMovementSummaryAsync(Guid itemId, Guid locationId, DateTime from, DateTime to)
|
||||
{
|
||||
var items = await repo.GetMovementsAsync(locationId, itemId, type, from, to, page, pageSize);
|
||||
var total = await repo.GetMovementsCountAsync(locationId, itemId, type, from, to);
|
||||
var toExclusive = to.TimeOfDay == TimeSpan.Zero ? to.AddDays(1) : to;
|
||||
return await repo.GetMovementSummaryAsync(itemId, locationId, from, toExclusive);
|
||||
}
|
||||
|
||||
public async Task<PagedResponse<StockMovementResponse>> GetMovementsAsync(MovementFilter filter, int page, int pageSize)
|
||||
{
|
||||
var items = await repo.GetMovementsAsync(filter, page, pageSize);
|
||||
var total = await repo.GetMovementsCountAsync(filter);
|
||||
|
||||
var locations = new List<(Guid, LocationType)>();
|
||||
foreach (var m in items)
|
||||
@@ -137,60 +145,42 @@ public class StockService(
|
||||
|
||||
public async Task<StockMovementResponse> TransferAsync(TransferRequest request, Guid userId, CancellationToken ct = default)
|
||||
{
|
||||
await EnsureStockAsync(request.ItemId, request.FromLocationId, request.Quantity);
|
||||
|
||||
var movement = new StockMovement
|
||||
{
|
||||
FK_ItemId = request.ItemId,
|
||||
FromLocationId = request.FromLocationId,
|
||||
FromLocationType = request.FromLocationType,
|
||||
ToLocationId = request.ToLocationId,
|
||||
ToLocationType = request.ToLocationType,
|
||||
Quantity = request.Quantity,
|
||||
Type = MovementType.Transfer,
|
||||
ReferenceId = request.ReferenceId,
|
||||
CreatedBy = userId
|
||||
};
|
||||
var item = await itemRepo.GetByIdAsync(request.ItemId)
|
||||
?? throw new InvalidOperationException("Item not found.");
|
||||
|
||||
List<StockMovement> movements;
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
await ApplyTransferAsync(request.ItemId, request.FromLocationId, request.ToLocationId, request.ToLocationType, request.Quantity);
|
||||
await repo.AddMovementAsync(movement);
|
||||
movements = await mover.MoveAsync(request.ItemId, item.Name,
|
||||
request.FromLocationId, request.FromLocationType,
|
||||
request.ToLocationId, request.ToLocationType,
|
||||
request.Quantity, MovementType.Transfer, request.ReferenceId, userId);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return await MapMovementAsync(movement);
|
||||
return await MapMovementsAsync(movements, item, request.Quantity);
|
||||
}
|
||||
|
||||
public async Task<StockMovementResponse> RecordDamageAsync(DamageRequest request, Guid userId, CancellationToken ct = default)
|
||||
{
|
||||
await EnsureStockAsync(request.ItemId, request.LocationId, request.Quantity);
|
||||
|
||||
var movement = new StockMovement
|
||||
{
|
||||
FK_ItemId = request.ItemId,
|
||||
FromLocationId = request.LocationId,
|
||||
FromLocationType = request.LocationType,
|
||||
ToLocationId = ExternalLocationId,
|
||||
ToLocationType = LocationType.External,
|
||||
Quantity = request.Quantity,
|
||||
Type = MovementType.Damage,
|
||||
ReasonCode = request.ReasonCode,
|
||||
CreatedBy = userId
|
||||
};
|
||||
var item = await itemRepo.GetByIdAsync(request.ItemId)
|
||||
?? throw new InvalidOperationException("Item not found.");
|
||||
|
||||
List<StockMovement> movements;
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
await DebitBalanceAsync(request.ItemId, request.LocationId, request.Quantity);
|
||||
await repo.AddMovementAsync(movement);
|
||||
movements = await mover.MoveAsync(request.ItemId, item.Name,
|
||||
request.LocationId, request.LocationType,
|
||||
ExternalLocationId, LocationType.External,
|
||||
request.Quantity, MovementType.Damage, null, userId, request.ReasonCode);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return await MapMovementAsync(movement);
|
||||
return await MapMovementsAsync(movements, item, request.Quantity);
|
||||
}
|
||||
|
||||
public async Task<StockMovementResponse> RecordCustomerReturnAsync(CustomerReturnRequest request, Guid userId, CancellationToken ct = default)
|
||||
@@ -204,75 +194,89 @@ public class StockService(
|
||||
if (!category.AllowCustomerReturn)
|
||||
throw new InvalidOperationException($"Customer returns are not allowed for category '{category.Name}'.");
|
||||
|
||||
var movement = new StockMovement
|
||||
{
|
||||
FK_ItemId = request.ItemId,
|
||||
FromLocationId = ExternalLocationId,
|
||||
FromLocationType = LocationType.External,
|
||||
ToLocationId = request.VehicleId,
|
||||
ToLocationType = LocationType.Vehicle,
|
||||
Quantity = request.Quantity,
|
||||
Type = MovementType.CustomerReturn,
|
||||
ReferenceId = request.ReferenceId,
|
||||
CreatedBy = userId
|
||||
};
|
||||
var tracked = item.TracksSerialNumbers || item.TracksBatches || item.TracksExpiryDate;
|
||||
if (tracked && request.ReferenceId is null)
|
||||
throw new InvalidOperationException(
|
||||
$"'{item.Name}' is batch/serial tracked — a customer return must reference the ShopPO it was delivered on.");
|
||||
|
||||
List<StockMovement> movements;
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
await CreditBalanceAsync(request.ItemId, request.VehicleId, LocationType.Vehicle, request.Quantity);
|
||||
await repo.AddMovementAsync(movement);
|
||||
if (tracked)
|
||||
{
|
||||
movements = await mover.ReturnToVehicleAsync(request.ItemId, item.Name, request.Quantity,
|
||||
request.ReferenceId!.Value, userId);
|
||||
}
|
||||
else
|
||||
{
|
||||
var movement = new StockMovement
|
||||
{
|
||||
FK_ItemId = request.ItemId,
|
||||
FromLocationId = ExternalLocationId,
|
||||
FromLocationType = LocationType.External,
|
||||
ToLocationId = request.VehicleId,
|
||||
ToLocationType = LocationType.Vehicle,
|
||||
Quantity = request.Quantity,
|
||||
Type = MovementType.CustomerReturn,
|
||||
ReferenceId = request.ReferenceId,
|
||||
CreatedBy = userId
|
||||
};
|
||||
await CreditBalanceAsync(request.ItemId, request.VehicleId, LocationType.Vehicle, request.Quantity);
|
||||
await repo.AddMovementAsync(movement);
|
||||
movements = [movement];
|
||||
}
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return await MapMovementAsync(movement);
|
||||
return await MapMovementsAsync(movements, item, request.Quantity);
|
||||
}
|
||||
|
||||
public async Task<StockMovementResponse> AdjustAsync(AdjustmentRequest request, Guid userId, CancellationToken ct = default)
|
||||
{
|
||||
var balance = await repo.GetBalanceAsync(request.ItemId, request.LocationId);
|
||||
if (balance is not null && balance.Quantity + request.Quantity < 0)
|
||||
throw new InvalidOperationException("Adjustment would result in negative stock.");
|
||||
|
||||
var movement = new StockMovement
|
||||
{
|
||||
FK_ItemId = request.ItemId,
|
||||
FromLocationId = request.Quantity < 0 ? request.LocationId : ExternalLocationId,
|
||||
FromLocationType = request.Quantity < 0 ? request.LocationType : LocationType.External,
|
||||
ToLocationId = request.Quantity > 0 ? request.LocationId : ExternalLocationId,
|
||||
ToLocationType = request.Quantity > 0 ? request.LocationType : LocationType.External,
|
||||
Quantity = Math.Abs(request.Quantity),
|
||||
Type = MovementType.Adjustment,
|
||||
ReasonCode = request.ReasonCode,
|
||||
CreatedBy = userId
|
||||
};
|
||||
var item = await itemRepo.GetByIdAsync(request.ItemId)
|
||||
?? throw new InvalidOperationException("Item not found.");
|
||||
|
||||
List<StockMovement> movements;
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
if (balance is null)
|
||||
if (request.Quantity < 0)
|
||||
{
|
||||
await repo.AddBalanceAsync(new StockBalance
|
||||
{
|
||||
FK_ItemId = request.ItemId,
|
||||
LocationId = request.LocationId,
|
||||
LocationType = request.LocationType,
|
||||
Quantity = request.Quantity
|
||||
});
|
||||
movements = await mover.MoveAsync(request.ItemId, item.Name,
|
||||
request.LocationId, request.LocationType,
|
||||
ExternalLocationId, LocationType.External,
|
||||
-request.Quantity, MovementType.Adjustment, null, userId, request.ReasonCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
balance.Quantity += request.Quantity;
|
||||
balance.UpdatedAt = DateTime.Now;
|
||||
repo.UpdateBalance(balance);
|
||||
// A positive adjustment can't say which batch/serial it adds — those enter stock through a GRN.
|
||||
if (item.TracksSerialNumbers || item.TracksBatches || item.TracksExpiryDate)
|
||||
throw new InvalidOperationException(
|
||||
$"'{item.Name}' is batch/serial tracked — increase its stock by receiving a GRN, not by adjustment.");
|
||||
|
||||
var movement = new StockMovement
|
||||
{
|
||||
FK_ItemId = request.ItemId,
|
||||
FromLocationId = ExternalLocationId,
|
||||
FromLocationType = LocationType.External,
|
||||
ToLocationId = request.LocationId,
|
||||
ToLocationType = request.LocationType,
|
||||
Quantity = request.Quantity,
|
||||
Type = MovementType.Adjustment,
|
||||
ReasonCode = request.ReasonCode,
|
||||
CreatedBy = userId
|
||||
};
|
||||
await CreditBalanceAsync(request.ItemId, request.LocationId, request.LocationType, request.Quantity);
|
||||
await repo.AddMovementAsync(movement);
|
||||
movements = [movement];
|
||||
}
|
||||
await repo.AddMovementAsync(movement);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return await MapMovementAsync(movement);
|
||||
return await MapMovementsAsync(movements, item, Math.Abs(request.Quantity));
|
||||
}
|
||||
|
||||
// Movements are an immutable ledger — only the reason/note can be edited, never quantity/locations/type.
|
||||
@@ -282,28 +286,29 @@ public class StockService(
|
||||
?? throw new InvalidOperationException("Movement not found.");
|
||||
|
||||
movement.ReasonCode = request.ReasonCode;
|
||||
repo.UpdateMovement(movement);
|
||||
await uow.SaveChangesAsync(ct);
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
repo.UpdateMovement(movement);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return await MapMovementAsync(movement);
|
||||
}
|
||||
|
||||
// ── Helpers ────────────────────────────────────────────────────────────
|
||||
|
||||
private async Task EnsureStockAsync(Guid itemId, Guid locationId, decimal quantity)
|
||||
/// <summary>
|
||||
/// One logical operation can produce several movements (one per batch/serial slice). The caller gets a
|
||||
/// single response for the operation: the first slice's locations, with the total quantity moved.
|
||||
/// </summary>
|
||||
private async Task<StockMovementResponse> MapMovementsAsync(List<StockMovement> movements, Models.Item item, decimal totalQuantity)
|
||||
{
|
||||
var balance = await repo.GetBalanceAsync(itemId, locationId);
|
||||
if (balance is null || balance.Quantity < quantity)
|
||||
throw new InvalidOperationException("Insufficient stock at the specified location.");
|
||||
}
|
||||
|
||||
private async Task DebitBalanceAsync(Guid itemId, Guid locationId, decimal quantity)
|
||||
{
|
||||
var balance = await repo.GetBalanceAsync(itemId, locationId)
|
||||
?? throw new InvalidOperationException("No stock balance found.");
|
||||
balance.Quantity -= quantity;
|
||||
balance.UpdatedAt = DateTime.Now;
|
||||
repo.UpdateBalance(balance);
|
||||
var first = movements[0];
|
||||
first.Item ??= item;
|
||||
return (await MapMovementAsync(first)) with { Quantity = totalQuantity };
|
||||
}
|
||||
|
||||
private async Task CreditBalanceAsync(Guid itemId, Guid locationId, LocationType locationType, decimal quantity)
|
||||
@@ -326,10 +331,4 @@ public class StockService(
|
||||
repo.UpdateBalance(balance);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ApplyTransferAsync(Guid itemId, Guid fromLocationId, Guid toLocationId, LocationType toLocationType, decimal quantity)
|
||||
{
|
||||
await DebitBalanceAsync(itemId, fromLocationId, quantity);
|
||||
await CreditBalanceAsync(itemId, toLocationId, toLocationType, quantity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace Distribution.Features.StockReport;
|
||||
|
||||
public record LedgerAggregate(
|
||||
Guid ItemId, string Sku, string Name, string? Brand, string Category, string Uom,
|
||||
decimal? CostPrice, decimal? ReorderLevel,
|
||||
decimal Opening, decimal In, decimal Out, decimal Current, decimal WarehouseQty);
|
||||
|
||||
public record ExpiryAggregate(
|
||||
Guid ItemId, string Sku, string Name, Guid BatchId, string? BatchNumber, DateTime ExpiryDate,
|
||||
decimal Quantity, decimal? CostPrice, Guid LocationId, string LocationType, string LocationName);
|
||||
|
||||
public record OverviewAggregate(
|
||||
int ActiveItems, int InStockItems, int LowStockItems, decimal TotalQty, decimal TotalValue,
|
||||
int ExpiringSoonBatches, int ExpiredBatches);
|
||||
|
||||
public interface IStockReportRepository
|
||||
{
|
||||
Task<bool> IsWarehouseAsync(Guid locationId);
|
||||
Task<OverviewAggregate> GetOverviewAsync(Guid? locationId, bool includeWarehouseLow, DateTime today, int expiryDays);
|
||||
Task<(List<LedgerAggregate> Rows, int Total)> GetLedgerAsync(StockReportFilter f, bool includeWarehouseLow, int page, int pageSize);
|
||||
Task<(List<ExpiryAggregate> Rows, int Total)> GetExpiryAsync(Guid? locationId, string? search, DateTime cutoff, int page, int pageSize);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.StockReport;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/reports/stock")]
|
||||
public class StockReportController(StockReportService service) : ControllerBase
|
||||
{
|
||||
[HttpGet("overview")]
|
||||
public async Task<IActionResult> Overview([FromQuery] Guid? locationId, [FromQuery] int expiryDays = 30)
|
||||
{
|
||||
var error = StockReportService.ValidateExpiry(expiryDays, 1, 1);
|
||||
return error is null ? Ok(await service.GetOverviewAsync(locationId, expiryDays)) : BadRequest(new { message = error });
|
||||
}
|
||||
|
||||
[HttpGet("ledger")]
|
||||
public async Task<IActionResult> Ledger(
|
||||
[FromQuery] Guid? locationId, [FromQuery] Guid? categoryId, [FromQuery] Guid? brandId,
|
||||
[FromQuery] DateTime? from, [FromQuery] DateTime? to, [FromQuery] string? status = null,
|
||||
[FromQuery] bool activeOnly = true, [FromQuery] string? search = null,
|
||||
[FromQuery] int page = 1, [FromQuery] int pageSize = 50)
|
||||
{
|
||||
var end = (to ?? DateTime.Now).Date;
|
||||
var start = (from ?? end.AddDays(-30)).Date;
|
||||
var f = new StockReportFilter(locationId, categoryId, brandId, search, start, end, status, activeOnly);
|
||||
var error = StockReportService.Validate(f, page, pageSize);
|
||||
return error is null ? Ok(await service.GetLedgerAsync(f, page, pageSize)) : BadRequest(new { message = error });
|
||||
}
|
||||
|
||||
[HttpGet("expiry")]
|
||||
public async Task<IActionResult> Expiry(
|
||||
[FromQuery] Guid? locationId, [FromQuery] string? search = null, [FromQuery] int days = 30,
|
||||
[FromQuery] int page = 1, [FromQuery] int pageSize = 50)
|
||||
{
|
||||
var error = StockReportService.ValidateExpiry(days, page, pageSize);
|
||||
return error is null
|
||||
? Ok(await service.GetExpiryAsync(locationId, search, days, page, pageSize))
|
||||
: BadRequest(new { message = error });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace Distribution.Features.StockReport;
|
||||
|
||||
public static class StockStatus
|
||||
{
|
||||
public const string Ok = "OK";
|
||||
public const string Low = "Low";
|
||||
public const string Out = "Out";
|
||||
}
|
||||
|
||||
public record StockReportFilter(
|
||||
Guid? LocationId, Guid? CategoryId, Guid? BrandId, string? Search,
|
||||
DateTime From, DateTime To, string? Status, bool ActiveOnly);
|
||||
|
||||
public record StockOverview(
|
||||
int TotalSkus, decimal TotalQty, decimal TotalValue,
|
||||
int LowStockCount, int OutOfStockCount, int ExpiringSoonCount, int ExpiredCount);
|
||||
|
||||
public record StockLedgerRow(
|
||||
Guid ItemId, string Sku, string Name, string? Brand, string Category, string Uom,
|
||||
decimal OpeningQty, decimal QtyIn, decimal QtyOut, decimal ClosingQty, decimal CurrentQty,
|
||||
decimal? CostPrice, decimal StockValue, decimal? ReorderLevel, string Status);
|
||||
|
||||
public record StockExpiryRow(
|
||||
Guid ItemId, string Sku, string Name, Guid BatchId, string? BatchNumber, DateTime ExpiryDate, int DaysLeft,
|
||||
decimal Quantity, decimal? CostPrice, decimal Value, Guid LocationId, string LocationType, string LocationName);
|
||||
@@ -0,0 +1,182 @@
|
||||
using System.Linq.Expressions;
|
||||
using Distribution.Data;
|
||||
using Distribution.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.StockReport;
|
||||
|
||||
public class StockReportRepository(AppDBContext db) : IStockReportRepository
|
||||
{
|
||||
// One location when picked, otherwise every internal location (warehouses + vehicles; External is not tracked).
|
||||
private IQueryable<StockBalance> ScopedBalances(Guid? locationId)
|
||||
{
|
||||
var q = db.StockBalances.AsNoTracking().AsQueryable();
|
||||
return locationId.HasValue
|
||||
? q.Where(b => b.LocationId == locationId.Value)
|
||||
: q.Where(b => b.LocationType == LocationType.Warehouse || b.LocationType == LocationType.Vehicle);
|
||||
}
|
||||
|
||||
public Task<bool> IsWarehouseAsync(Guid locationId) =>
|
||||
db.Warehouses.AsNoTracking().AnyAsync(w => w.Id == locationId);
|
||||
|
||||
public async Task<OverviewAggregate> GetOverviewAsync(Guid? locationId, bool includeWarehouseLow, DateTime today, int expiryDays)
|
||||
{
|
||||
var balances = ScopedBalances(locationId);
|
||||
var whBalances = balances.Where(b => b.LocationType == LocationType.Warehouse);
|
||||
|
||||
var perItem = db.Items.AsNoTracking().Where(i => i.IsActive).Select(i => new
|
||||
{
|
||||
i.ReorderLevel,
|
||||
i.CostPrice,
|
||||
Qty = balances.Where(b => b.FK_ItemId == i.Id).Sum(b => (decimal?)b.Quantity) ?? 0m,
|
||||
Wh = whBalances.Where(b => b.FK_ItemId == i.Id).Sum(b => (decimal?)b.Quantity) ?? 0m,
|
||||
});
|
||||
|
||||
var activeItems = await perItem.CountAsync();
|
||||
var inStockItems = await perItem.CountAsync(x => x.Qty > 0);
|
||||
var lowItems = includeWarehouseLow
|
||||
? await perItem.CountAsync(x => x.Qty > 0 && x.ReorderLevel != null && x.Wh <= x.ReorderLevel)
|
||||
: 0;
|
||||
var totalQty = await perItem.SumAsync(x => (decimal?)x.Qty) ?? 0m;
|
||||
var totalValue = await perItem.SumAsync(x => (decimal?)(x.Qty * (x.CostPrice ?? 0m))) ?? 0m;
|
||||
|
||||
var soonEnd = today.AddDays(expiryDays + 1);
|
||||
var batchBalances = balances.Where(b => b.FK_ItemBatchId != null && b.Quantity > 0
|
||||
&& b.ItemBatch!.ExpiryDate != null);
|
||||
var expired = await batchBalances.Where(b => b.ItemBatch!.ExpiryDate < today)
|
||||
.Select(b => b.FK_ItemBatchId).Distinct().CountAsync();
|
||||
var soon = await batchBalances.Where(b => b.ItemBatch!.ExpiryDate >= today && b.ItemBatch.ExpiryDate < soonEnd)
|
||||
.Select(b => b.FK_ItemBatchId).Distinct().CountAsync();
|
||||
|
||||
return new OverviewAggregate(activeItems, inStockItems, lowItems, totalQty, totalValue, soon, expired);
|
||||
}
|
||||
|
||||
public async Task<(List<LedgerAggregate> Rows, int Total)> GetLedgerAsync(
|
||||
StockReportFilter f, bool includeWarehouseLow, int page, int pageSize)
|
||||
{
|
||||
var balances = ScopedBalances(f.LocationId);
|
||||
var whBalances = balances.Where(b => b.LocationType == LocationType.Warehouse);
|
||||
|
||||
var items = db.Items.AsNoTracking().AsQueryable();
|
||||
if (f.ActiveOnly) items = items.Where(i => i.IsActive);
|
||||
if (f.CategoryId.HasValue) items = items.Where(i => i.FK_CategoryId == f.CategoryId.Value);
|
||||
if (f.BrandId.HasValue) items = items.Where(i => i.FK_BrandId == f.BrandId.Value);
|
||||
if (!string.IsNullOrWhiteSpace(f.Search))
|
||||
{
|
||||
var s = f.Search.Trim();
|
||||
items = items.Where(i => i.Name.Contains(s) || i.Sku.Contains(s));
|
||||
}
|
||||
|
||||
var withQty = items.Select(i => new
|
||||
{
|
||||
Item = i,
|
||||
Qty = balances.Where(b => b.FK_ItemId == i.Id).Sum(b => (decimal?)b.Quantity) ?? 0m,
|
||||
Wh = whBalances.Where(b => b.FK_ItemId == i.Id).Sum(b => (decimal?)b.Quantity) ?? 0m,
|
||||
});
|
||||
|
||||
if (f.Status == "out")
|
||||
withQty = withQty.Where(x => x.Qty <= 0);
|
||||
else if (f.Status == "low")
|
||||
withQty = includeWarehouseLow
|
||||
? withQty.Where(x => x.Qty > 0 && x.Item.ReorderLevel != null && x.Wh <= x.Item.ReorderLevel)
|
||||
: withQty.Where(x => false);
|
||||
|
||||
var total = await withQty.CountAsync();
|
||||
var pageRows = await withQty
|
||||
.OrderBy(x => x.Item.Name)
|
||||
.Skip((page - 1) * pageSize).Take(pageSize)
|
||||
.Select(x => new
|
||||
{
|
||||
x.Item.Id, x.Item.Sku, x.Item.Name,
|
||||
Brand = x.Item.Brand != null ? x.Item.Brand.Name : null,
|
||||
Category = x.Item.Category.Name,
|
||||
Uom = x.Item.BaseUom.Name,
|
||||
x.Item.CostPrice, x.Item.ReorderLevel,
|
||||
x.Qty, x.Wh,
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
var ids = pageRows.Select(r => r.Id).ToList();
|
||||
var toExclusive = f.To.TimeOfDay == TimeSpan.Zero ? f.To.AddDays(1) : f.To;
|
||||
var from = f.From;
|
||||
|
||||
Expression<Func<StockMovement, bool>> isIn, isOut;
|
||||
if (f.LocationId.HasValue)
|
||||
{
|
||||
var loc = f.LocationId.Value;
|
||||
isIn = m => m.ToLocationId == loc;
|
||||
isOut = m => m.FromLocationId == loc;
|
||||
}
|
||||
else
|
||||
{
|
||||
// All locations: only movements that cross the boundary of the internal network count;
|
||||
// warehouse <-> vehicle transfers net to zero and are left out of in/out.
|
||||
isIn = m => m.ToLocationId != null && m.ToLocationType != LocationType.External
|
||||
&& (m.FromLocationId == null || m.FromLocationType == LocationType.External);
|
||||
isOut = m => m.FromLocationId != null && m.FromLocationType != LocationType.External
|
||||
&& (m.ToLocationId == null || m.ToLocationType == LocationType.External);
|
||||
}
|
||||
|
||||
var moves = db.StockMovements.AsNoTracking().Where(m => ids.Contains(m.FK_ItemId) && m.CreatedAt < toExclusive);
|
||||
var before = moves.Where(m => m.CreatedAt < from);
|
||||
var inRange = moves.Where(m => m.CreatedAt >= from);
|
||||
|
||||
var openingIn = await SumByItem(before, isIn);
|
||||
var openingOut = await SumByItem(before, isOut);
|
||||
var rangeIn = await SumByItem(inRange, isIn);
|
||||
var rangeOut = await SumByItem(inRange, isOut);
|
||||
|
||||
var rows = pageRows.Select(r => new LedgerAggregate(
|
||||
r.Id, r.Sku, r.Name, r.Brand, r.Category, r.Uom, r.CostPrice, r.ReorderLevel,
|
||||
openingIn.GetValueOrDefault(r.Id) - openingOut.GetValueOrDefault(r.Id),
|
||||
rangeIn.GetValueOrDefault(r.Id), rangeOut.GetValueOrDefault(r.Id),
|
||||
r.Qty, r.Wh)).ToList();
|
||||
return (rows, total);
|
||||
}
|
||||
|
||||
private static async Task<Dictionary<Guid, decimal>> SumByItem(
|
||||
IQueryable<StockMovement> q, Expression<Func<StockMovement, bool>> predicate) =>
|
||||
await q.Where(predicate)
|
||||
.GroupBy(m => m.FK_ItemId)
|
||||
.Select(g => new { ItemId = g.Key, Qty = g.Sum(m => m.Quantity) })
|
||||
.ToDictionaryAsync(x => x.ItemId, x => x.Qty);
|
||||
|
||||
public async Task<(List<ExpiryAggregate> Rows, int Total)> GetExpiryAsync(
|
||||
Guid? locationId, string? search, DateTime cutoffExclusive, int page, int pageSize)
|
||||
{
|
||||
var q = ScopedBalances(locationId)
|
||||
.Where(b => b.FK_ItemBatchId != null && b.Quantity > 0
|
||||
&& b.ItemBatch!.ExpiryDate != null && b.ItemBatch.ExpiryDate < cutoffExclusive);
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
{
|
||||
var s = search.Trim();
|
||||
q = q.Where(b => b.Item.Name.Contains(s) || b.Item.Sku.Contains(s));
|
||||
}
|
||||
|
||||
var total = await q.CountAsync();
|
||||
var page_ = await q
|
||||
.OrderBy(b => b.ItemBatch!.ExpiryDate).ThenBy(b => b.Item.Name)
|
||||
.Skip((page - 1) * pageSize).Take(pageSize)
|
||||
.Select(b => new
|
||||
{
|
||||
b.FK_ItemId, b.Item.Sku, b.Item.Name,
|
||||
BatchId = b.FK_ItemBatchId!.Value,
|
||||
b.ItemBatch!.BatchNumber,
|
||||
ExpiryDate = b.ItemBatch.ExpiryDate!.Value,
|
||||
b.Quantity, b.Item.CostPrice, b.LocationId, b.LocationType,
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
var locIds = page_.Select(r => r.LocationId).Distinct().ToList();
|
||||
var names = await db.Warehouses.AsNoTracking().Where(w => locIds.Contains(w.Id))
|
||||
.Select(w => new { w.Id, w.Name }).ToDictionaryAsync(w => w.Id, w => w.Name);
|
||||
var plates = await db.Vehicles.AsNoTracking().Where(v => locIds.Contains(v.Id))
|
||||
.Select(v => new { v.Id, v.NumberPlate }).ToDictionaryAsync(v => v.Id, v => v.NumberPlate);
|
||||
|
||||
var rows = page_.Select(r => new ExpiryAggregate(
|
||||
r.FK_ItemId, r.Sku, r.Name, r.BatchId, r.BatchNumber, r.ExpiryDate,
|
||||
r.Quantity, r.CostPrice, r.LocationId, r.LocationType.ToString(),
|
||||
names.GetValueOrDefault(r.LocationId) ?? plates.GetValueOrDefault(r.LocationId) ?? "—")).ToList();
|
||||
return (rows, total);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using Distribution.Features.Stock;
|
||||
|
||||
namespace Distribution.Features.StockReport;
|
||||
|
||||
public class StockReportService(IStockReportRepository repo)
|
||||
{
|
||||
public const int MaxRangeDays = 366;
|
||||
public const int MaxExpiryDays = 3650;
|
||||
|
||||
public static string? Validate(StockReportFilter f, int page, int pageSize)
|
||||
{
|
||||
if (page < 1 || pageSize < 1 || pageSize > 200) return "page must be >= 1 and pageSize between 1 and 200.";
|
||||
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.";
|
||||
if (f.Status is not (null or "low" or "out")) return "status must be 'low' or 'out'.";
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string? ValidateExpiry(int days, int page, int pageSize)
|
||||
{
|
||||
if (page < 1 || pageSize < 1 || pageSize > 200) return "page must be >= 1 and pageSize between 1 and 200.";
|
||||
if (days < 0 || days > MaxExpiryDays) return $"days must be between 0 and {MaxExpiryDays}.";
|
||||
return null;
|
||||
}
|
||||
|
||||
// Low-stock is judged on warehouse quantity, so it only applies when the scope includes warehouses.
|
||||
private async Task<bool> IncludesWarehouses(Guid? locationId) =>
|
||||
!locationId.HasValue || await repo.IsWarehouseAsync(locationId.Value);
|
||||
|
||||
public async Task<StockOverview> GetOverviewAsync(Guid? locationId, int expiryDays)
|
||||
{
|
||||
var agg = await repo.GetOverviewAsync(locationId, await IncludesWarehouses(locationId), DateTime.Now.Date, expiryDays);
|
||||
return new StockOverview(agg.InStockItems, agg.TotalQty, agg.TotalValue, agg.LowStockItems,
|
||||
agg.ActiveItems - agg.InStockItems, agg.ExpiringSoonBatches, agg.ExpiredBatches);
|
||||
}
|
||||
|
||||
public async Task<PagedResponse<StockLedgerRow>> GetLedgerAsync(StockReportFilter f, int page, int pageSize)
|
||||
{
|
||||
var includeLow = await IncludesWarehouses(f.LocationId);
|
||||
var (rows, total) = await repo.GetLedgerAsync(f, includeLow, page, pageSize);
|
||||
var mapped = rows.Select(r =>
|
||||
{
|
||||
var status = r.Current <= 0 ? StockStatus.Out
|
||||
: includeLow && r.ReorderLevel != null && r.WarehouseQty <= r.ReorderLevel ? StockStatus.Low
|
||||
: StockStatus.Ok;
|
||||
return new StockLedgerRow(r.ItemId, r.Sku, r.Name, r.Brand, r.Category, r.Uom,
|
||||
r.Opening, r.In, r.Out, r.Opening + r.In - r.Out, r.Current,
|
||||
r.CostPrice, r.Current * (r.CostPrice ?? 0m), r.ReorderLevel, status);
|
||||
}).ToList();
|
||||
return new PagedResponse<StockLedgerRow>(page, pageSize, total, mapped);
|
||||
}
|
||||
|
||||
public async Task<PagedResponse<StockExpiryRow>> GetExpiryAsync(
|
||||
Guid? locationId, string? search, int days, int page, int pageSize)
|
||||
{
|
||||
var today = DateTime.Now.Date;
|
||||
var (rows, total) = await repo.GetExpiryAsync(locationId, search, today.AddDays(days + 1), page, pageSize);
|
||||
var mapped = rows.Select(r => new StockExpiryRow(
|
||||
r.ItemId, r.Sku, r.Name, r.BatchId, r.BatchNumber, r.ExpiryDate,
|
||||
(int)(r.ExpiryDate.Date - today).TotalDays,
|
||||
r.Quantity, r.CostPrice, r.Quantity * (r.CostPrice ?? 0m),
|
||||
r.LocationId, r.LocationType, r.LocationName)).ToList();
|
||||
return new PagedResponse<StockExpiryRow>(page, pageSize, total, mapped);
|
||||
}
|
||||
}
|
||||
@@ -13,4 +13,5 @@ public interface IStoreRepository
|
||||
void Update(Models.Store entity);
|
||||
void Remove(Models.Store entity);
|
||||
Task<List<Models.ShopPO>> GetOrdersForDashboardAsync(Guid storeId, DateTime periodStart, DateTime periodEndExclusive);
|
||||
Task<List<Models.Invoice>> GetInvoicesForAnalyticsAsync(Guid storeId);
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@ public class StoreController(
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var userId = currentUser.UserId;
|
||||
if (userId.HasValue && !await scopeAccess.HasStoreScopeAsync(userId.Value, id))
|
||||
return StatusCode(403);
|
||||
//if (userId.HasValue && !await scopeAccess.HasStoreScopeAsync(userId.Value, id))
|
||||
// return StatusCode(403);
|
||||
var result = await service.GetByIdAsync(id);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
@@ -112,6 +112,13 @@ public class StoreController(
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/analytics")]
|
||||
public async Task<IActionResult> GetAnalytics(Guid id, [FromQuery] int months = 12, [FromQuery] int overdueDays = 30)
|
||||
{
|
||||
var result = await service.GetAnalyticsAsync(id, months, overdueDays);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPatch("{id:guid}/status")]
|
||||
[Authorize("store.manage")]
|
||||
public async Task<IActionResult> PatchStatus(Guid id, PatchStoreStatusRequest request, CancellationToken ct)
|
||||
|
||||
@@ -59,3 +59,40 @@ public record SchemeEligibilityResponse(bool Eligible, string? Reason);
|
||||
public record PatchStoreStatusRequest(bool IsActive);
|
||||
|
||||
public record StoreDashboardResponse(Guid StoreId, string Period, int OrderCount, decimal TotalOrderValue, DateTime? LastOrderDate);
|
||||
|
||||
public record StoreAnalyticsSummary(
|
||||
decimal TotalSales,
|
||||
decimal TotalCollected,
|
||||
decimal Outstanding,
|
||||
decimal OverdueAmount,
|
||||
int OverdueInvoiceCount,
|
||||
int InvoiceCount,
|
||||
decimal AverageOrderValue,
|
||||
DateTime? LastSaleDate,
|
||||
decimal CreditLimit,
|
||||
decimal CreditUtilizationPercent);
|
||||
|
||||
public record StoreMonthlySales(string Month, decimal Invoiced, decimal Collected, decimal Outstanding);
|
||||
|
||||
public record StoreTopProduct(Guid ItemId, string Name, int Quantity, decimal Revenue, DateTime LastSoldAt);
|
||||
|
||||
public record StoreAgeingBucket(string Label, decimal Amount, int InvoiceCount);
|
||||
|
||||
public record StoreAnalyticsInvoice(
|
||||
Guid InvoiceId,
|
||||
DateTime InvoiceDate,
|
||||
DateTime DueDate,
|
||||
decimal TotalAmount,
|
||||
decimal PaidAmount,
|
||||
decimal Balance,
|
||||
string PaymentStatus,
|
||||
int DaysOverdue);
|
||||
|
||||
public record StoreAnalyticsResponse(
|
||||
Guid StoreId,
|
||||
int OverdueDays,
|
||||
StoreAnalyticsSummary Summary,
|
||||
List<StoreMonthlySales> MonthlySales,
|
||||
List<StoreTopProduct> TopProducts,
|
||||
List<StoreAgeingBucket> Ageing,
|
||||
List<StoreAnalyticsInvoice> Invoices);
|
||||
|
||||
@@ -51,4 +51,12 @@ public class StoreRepository(AppDBContext context) : IStoreRepository
|
||||
await context.ShopPOs
|
||||
.Where(o => o.FK_StoreId == storeId && o.IsActive && o.CreatedAt >= periodStart && o.CreatedAt < periodEndExclusive)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task<List<Models.Invoice>> GetInvoicesForAnalyticsAsync(Guid storeId) =>
|
||||
await context.Invoices
|
||||
.AsNoTracking()
|
||||
.Include(i => i.Payments)
|
||||
.Include(i => i.ShopPO).ThenInclude(p => p.Items).ThenInclude(x => x.Item)
|
||||
.Where(i => i.IsActive && i.Status != InvoiceStatus.Draft && i.ShopPO.FK_StoreId == storeId)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
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, IStoreCreditMovementRepository creditRepo)
|
||||
{
|
||||
private const int GlobalScopeRoleLevel = 50;
|
||||
private const int CreditApproverRoleLevel = 20;
|
||||
@@ -167,7 +168,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)
|
||||
{
|
||||
@@ -253,4 +255,92 @@ public class StoreService(IStoreRepository repo, IUnitOfWork uow, ICurrentUserSe
|
||||
orders.Sum(o => o.TotalAmount),
|
||||
orders.Count == 0 ? null : orders.Max(o => o.CreatedAt));
|
||||
}
|
||||
|
||||
public async Task<StoreAnalyticsResponse?> GetAnalyticsAsync(Guid id, int months, int overdueDays)
|
||||
{
|
||||
var store = await repo.GetByIdAsync(id);
|
||||
if (store is null) return null;
|
||||
if (!await HasStoreScopeAsync(id)) return null;
|
||||
|
||||
months = Math.Clamp(months, 1, 36);
|
||||
overdueDays = Math.Clamp(overdueDays, 0, 365);
|
||||
var now = DateTime.Now;
|
||||
var from = new DateTime(now.Year, now.Month, 1).AddMonths(-(months - 1));
|
||||
|
||||
var invoices = (await repo.GetInvoicesForAnalyticsAsync(id))
|
||||
.Select(i =>
|
||||
{
|
||||
var date = i.FinalizedAt ?? i.CreatedAt;
|
||||
var paid = i.Payments.Where(p => p.IsActive).Sum(p => p.Amount);
|
||||
var balance = Math.Max(0, i.TotalAmount - paid);
|
||||
var due = date.AddDays(overdueDays);
|
||||
var daysOverdue = balance > 0 && now > due ? (int)(now - due).TotalDays : 0;
|
||||
return (Invoice: i, Date: date, Due: due, Paid: paid, Balance: balance, DaysOverdue: daysOverdue);
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var inRange = invoices.Where(x => x.Date >= from).ToList();
|
||||
var totalSales = inRange.Sum(x => x.Invoice.TotalAmount);
|
||||
var overdue = invoices.Where(x => x.DaysOverdue > 0).ToList();
|
||||
|
||||
var summary = new StoreAnalyticsSummary(
|
||||
totalSales,
|
||||
inRange.Sum(x => x.Paid),
|
||||
invoices.Sum(x => x.Balance),
|
||||
overdue.Sum(x => x.Balance),
|
||||
overdue.Count,
|
||||
inRange.Count,
|
||||
inRange.Count == 0 ? 0 : Math.Round(totalSales / inRange.Count, 2),
|
||||
invoices.Count == 0 ? null : invoices.Max(x => x.Date),
|
||||
store.CreditLimit,
|
||||
store.CreditLimit > 0 ? Math.Round(store.OutstandingBalance / store.CreditLimit * 100, 1) : 0);
|
||||
|
||||
var monthly = Enumerable.Range(0, months)
|
||||
.Select(n => from.AddMonths(n))
|
||||
.Select(m => new StoreMonthlySales(
|
||||
m.ToString("yyyy-MM"),
|
||||
inRange.Where(x => x.Date.Year == m.Year && x.Date.Month == m.Month).Sum(x => x.Invoice.TotalAmount),
|
||||
inRange.Where(x => x.Date.Year == m.Year && x.Date.Month == m.Month).Sum(x => x.Paid),
|
||||
invoices
|
||||
.Where(x => x.Date < m.AddMonths(1))
|
||||
.Sum(x => Math.Max(0, x.Invoice.TotalAmount - x.Invoice.Payments.Where(p => p.IsActive && p.CollectedAt < m.AddMonths(1)).Sum(p => p.Amount)))))
|
||||
.ToList();
|
||||
|
||||
var topProducts = inRange
|
||||
.SelectMany(x => x.Invoice.ShopPO.Items.Select(it => (it, x.Date)))
|
||||
.GroupBy(t => t.it.FK_ItemId)
|
||||
.Select(g => new StoreTopProduct(
|
||||
g.Key,
|
||||
g.First().it.Item?.Name ?? "Unknown",
|
||||
g.Sum(t => t.it.QuantityFulfilled > 0 ? t.it.QuantityFulfilled : t.it.Quantity),
|
||||
g.Sum(t => (t.it.QuantityFulfilled > 0 ? t.it.QuantityFulfilled : t.it.Quantity) * t.it.UnitPrice),
|
||||
g.Max(t => t.Date)))
|
||||
.OrderByDescending(p => p.Revenue)
|
||||
.Take(10)
|
||||
.ToList();
|
||||
|
||||
var unpaid = invoices.Where(x => x.Balance > 0).ToList();
|
||||
StoreAgeingBucket Bucket(string label, Func<int, bool> match)
|
||||
{
|
||||
var items = unpaid.Where(x => match(x.DaysOverdue)).ToList();
|
||||
return new StoreAgeingBucket(label, items.Sum(x => x.Balance), items.Count);
|
||||
}
|
||||
var ageing = new List<StoreAgeingBucket>
|
||||
{
|
||||
Bucket("Current", d => d <= 0),
|
||||
Bucket("1-30", d => d is >= 1 and <= 30),
|
||||
Bucket("31-60", d => d is >= 31 and <= 60),
|
||||
Bucket("61-90", d => d is >= 61 and <= 90),
|
||||
Bucket("90+", d => d > 90)
|
||||
};
|
||||
|
||||
var list = invoices
|
||||
.OrderByDescending(x => x.Date)
|
||||
.Select(x => new StoreAnalyticsInvoice(
|
||||
x.Invoice.Id, x.Date, x.Due, x.Invoice.TotalAmount, x.Paid, x.Balance,
|
||||
x.Invoice.PaymentStatus.ToString(), x.DaysOverdue))
|
||||
.ToList();
|
||||
|
||||
return new StoreAnalyticsResponse(id, overdueDays, summary, monthly, topProducts, ageing, list);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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,175 @@
|
||||
using Distribution.Data;
|
||||
using Distribution.Features.BankAccount;
|
||||
using Distribution.Features.ChequeBook;
|
||||
using Distribution.Features.GeneralLedger;
|
||||
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,
|
||||
GlPostingService gl)
|
||||
{
|
||||
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);
|
||||
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
// Mirrors to the General Ledger after commit: Dr Payable / Cr Cash/Bank (or Cheques-issued). Never throws.
|
||||
await gl.PostSupplierPaymentAsync(payment, ct);
|
||||
|
||||
return ToResponse(payment);
|
||||
}
|
||||
}
|
||||
@@ -31,13 +31,6 @@ public class UserController(UserService service) : ControllerBase
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}/role")]
|
||||
public async Task<IActionResult> AssignRole(Guid id, AssignUserRoleRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.AssignRoleAsync(id, request, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}/scope")]
|
||||
public async Task<IActionResult> AssignScope(Guid id, AssignUserScopeRequest request, CancellationToken ct)
|
||||
{
|
||||
|
||||
@@ -9,7 +9,12 @@ public record CreateUserRequest(
|
||||
Guid RoleId,
|
||||
Guid? ReportsToUserId);
|
||||
|
||||
public record UpdateUserRequest(string FullName);
|
||||
public record UpdateUserRequest(
|
||||
string FullName,
|
||||
string? Username = null,
|
||||
Guid? RoleId = null,
|
||||
string? CurrentPassword = null,
|
||||
string? NewPassword = null);
|
||||
|
||||
public record AssignUserRoleRequest(Guid RoleId);
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ public class UserService(IUserRepository repo, IUnitOfWork uow, IAuthHexClient a
|
||||
email = request.Username,
|
||||
fullname = request.FullName,
|
||||
password = request.Password,
|
||||
roleId = request.RoleId,
|
||||
roleId = request.RoleId.ToString(),
|
||||
userTypeId = "00000001-0000-0000-0000-200000000000",
|
||||
sendCredentialsEmail = false
|
||||
}, reference: user.Id.ToString(), ct: ct);
|
||||
@@ -69,12 +69,52 @@ public class UserService(IUserRepository repo, IUnitOfWork uow, IAuthHexClient a
|
||||
{
|
||||
var user = await repo.GetByIdAsync(id);
|
||||
if (user is null || !user.IsActive) return null;
|
||||
|
||||
user.FullName = request.FullName;
|
||||
if (!string.IsNullOrWhiteSpace(request.Username))
|
||||
user.Username = request.Username;
|
||||
if (request.RoleId.HasValue)
|
||||
user.FK_RoleId = request.RoleId.Value;
|
||||
user.UpdatedAt = DateTime.Now;
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
repo.Update(user);
|
||||
|
||||
var hasUsernameChange = !string.IsNullOrWhiteSpace(request.Username);
|
||||
var hasRoleChange = request.RoleId.HasValue;
|
||||
|
||||
if (hasUsernameChange || hasRoleChange)
|
||||
{
|
||||
var payload = new Dictionary<string, object> { ["userId"] = user.Id.ToString() };
|
||||
if (hasUsernameChange) payload["userName"] = request.Username!;
|
||||
if (hasRoleChange) payload["roleCode"] = request.RoleId!.Value.ToString();
|
||||
|
||||
var authResult = await authHex.CallAsync("UpdateUser", payload, reference: user.Id.ToString(), ct: ct);
|
||||
if (!authResult.Success)
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw new InvalidOperationException($"AuthHex update failed: {authResult.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.CurrentPassword) && !string.IsNullOrWhiteSpace(request.NewPassword))
|
||||
{
|
||||
var authResult = await authHex.CallAsync("ChangeUserPassword", new
|
||||
{
|
||||
userId = user.Id.ToString(),
|
||||
currentPassword = request.CurrentPassword,
|
||||
newPassword = request.NewPassword
|
||||
}, reference: user.Id.ToString(), ct: ct);
|
||||
|
||||
if (!authResult.Success)
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw new InvalidOperationException($"AuthHex password change failed: {authResult.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
@@ -87,22 +127,9 @@ public class UserService(IUserRepository repo, IUnitOfWork uow, IAuthHexClient a
|
||||
|
||||
public async Task<UserResponse?> AssignRoleAsync(Guid id, AssignUserRoleRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var user = await repo.GetByIdAsync(id);
|
||||
if (user is null || !user.IsActive) return null;
|
||||
user.FK_RoleId = request.RoleId;
|
||||
user.UpdatedAt = DateTime.Now;
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
repo.Update(user);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return ToResponse(user);
|
||||
return await UpdateAsync(id, new UpdateUserRequest(
|
||||
FullName: (await repo.GetByIdAsync(id))?.FullName ?? string.Empty,
|
||||
RoleId: request.RoleId), ct);
|
||||
}
|
||||
|
||||
public async Task<bool> AssignScopeAsync(Guid id, AssignUserScopeRequest request, CancellationToken ct = default)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace Distribution.Features.Vehicle;
|
||||
|
||||
public interface IVehicleDashboardRepository
|
||||
{
|
||||
Task<List<VehicleBalanceRow>> GetStockOnBoardAsync(Guid vehicleId);
|
||||
Task<List<VehicleMovementRow>> GetMovementsAsync(Guid vehicleId, DateTime from, DateTime toExclusive);
|
||||
|
||||
Task<List<VehicleRideRow>> GetRidesAsync(Guid vehicleId, DateTime from, DateTime toExclusive);
|
||||
Task<VehicleRideRow?> GetOpenRideAsync(Guid vehicleId);
|
||||
Task<List<VehicleTaskRow>> GetTasksAsync(Guid vehicleId, DateTime from, DateTime toExclusive);
|
||||
|
||||
Task<List<VehicleCollectionRow>> GetCollectionsAsync(Guid vehicleId, DateTime from, DateTime toExclusive);
|
||||
Task<VehicleReturnTotals> GetReturnTotalsAsync(Guid vehicleId, DateTime from, DateTime toExclusive);
|
||||
|
||||
Task<List<VehicleDriverRow>> GetDriversAsync(IEnumerable<Guid> driverProfileIds);
|
||||
Task<VehicleNamedRef?> GetWarehouseRefAsync(Guid? warehouseId);
|
||||
Task<VehicleNamedRef?> GetMobileWarehouseAsync(Guid vehicleId);
|
||||
|
||||
Task<Dictionary<Guid, string>> GetLocationNamesAsync(IEnumerable<Guid> ids);
|
||||
Task<Dictionary<Guid, string>> GetUserNamesAsync(IEnumerable<Guid> ids);
|
||||
}
|
||||
@@ -4,7 +4,7 @@ namespace Distribution.Features.Vehicle;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/vehicles")]
|
||||
public class VehicleController(VehicleService service) : ControllerBase
|
||||
public class VehicleController(VehicleService service, VehicleDashboardService dashboardService) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAll() =>
|
||||
@@ -17,6 +17,15 @@ public class VehicleController(VehicleService service) : ControllerBase
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/dashboard")]
|
||||
public async Task<IActionResult> Dashboard(Guid id, [FromQuery] DateTime? from, [FromQuery] DateTime? to)
|
||||
{
|
||||
var error = VehicleDashboardService.Validate(from, to);
|
||||
if (error is not null) return BadRequest(new { message = error });
|
||||
var result = await dashboardService.GetAsync(id, from, to);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create(CreateVehicleRequest request, CancellationToken ct)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.Vehicle;
|
||||
|
||||
// ── Repository projections ─────────────────────────────────────────────────
|
||||
|
||||
public record VehicleBalanceRow(
|
||||
Guid ItemId, string Sku, string Name, string Category, string Uom,
|
||||
decimal? SalePrice, decimal Quantity, DateTime UpdatedAt);
|
||||
|
||||
public record VehicleMovementRow(
|
||||
Guid Id, Guid ItemId, string ItemName, string Sku,
|
||||
Guid? FromLocationId, LocationType? FromLocationType,
|
||||
Guid? ToLocationId, LocationType? ToLocationType,
|
||||
decimal Quantity, MovementType Type, string? ReasonCode, Guid? ReferenceId,
|
||||
DateTime CreatedAt, Guid CreatedBy);
|
||||
|
||||
public record VehicleRideRow(
|
||||
Guid Id, RideStatus Status, Guid DriverProfileId, string DriverName,
|
||||
int MeterStart, int MeterEnd, DateTime? StartedAt, DateTime? ClosedAt, DateTime CreatedAt,
|
||||
CancelRequestType? CancelType, string? CancelReason,
|
||||
int Tasks, int Delivered, int Failed);
|
||||
|
||||
public record VehicleTaskRow(
|
||||
Guid RideId, DeliveryTaskStatus Status, Guid StoreId, string StoreName,
|
||||
decimal DeliveredValue, DateTime Date);
|
||||
|
||||
public record VehicleCollectionRow(PaymentMethod Method, decimal Amount, int Count);
|
||||
|
||||
public record VehicleReturnTotals(int ReturnCount, decimal CreditValue, decimal Units);
|
||||
|
||||
public record VehicleDriverRow(
|
||||
Guid Id, string Name, string LicenseNumber, DateTime LicenseExpiry, DateTime MedicalClearanceExpiry, bool IsActive);
|
||||
|
||||
public record VehicleNamedRef(Guid Id, string Name);
|
||||
|
||||
// ── Response ───────────────────────────────────────────────────────────────
|
||||
|
||||
public record VehicleDashboardPeriod(DateTime From, DateTime To);
|
||||
|
||||
public record VehicleDashboardKpis(
|
||||
int Rides, int ClosedRides, int CancelledRides, decimal DistanceKm,
|
||||
int Deliveries, int DeliveredCount, int FailedCount, int PendingCount, decimal SuccessRatePercent,
|
||||
decimal DeliveredValue, decimal Collections,
|
||||
decimal StockOnBoardUnits, decimal StockOnBoardValue,
|
||||
decimal UnitsLoaded, decimal UnitsSold);
|
||||
|
||||
public record VehicleDashboardStockRow(
|
||||
Guid ItemId, string Sku, string Name, string Category, string Uom,
|
||||
decimal Quantity, decimal Value, DateTime UpdatedAt);
|
||||
|
||||
public record VehicleDashboardRide(
|
||||
Guid Id, RideStatus Status, string DriverName, DateTime? StartedAt, DateTime? ClosedAt, DateTime CreatedAt,
|
||||
decimal DistanceKm, int Tasks, int Delivered, int Failed, CancelRequestType? CancelType, string? CancelReason);
|
||||
|
||||
public record VehicleDashboardDailyDelivery(string Date, int Delivered, int Failed);
|
||||
|
||||
public record VehicleDashboardDailyMovement(string Date, decimal In, decimal Out);
|
||||
|
||||
public record VehicleDashboardMovementType(MovementType Type, string Direction, decimal Units, int Count);
|
||||
|
||||
public record VehicleDashboardLedgerRow(
|
||||
Guid Id, DateTime CreatedAt, Guid ItemId, string ItemName, string Sku,
|
||||
MovementType Type, string Direction, decimal Quantity,
|
||||
string FromName, string ToName, Guid? ReferenceId, string? ReferenceKind,
|
||||
string? ReasonCode, string CreatedByName);
|
||||
|
||||
public record VehicleDashboardCancelReason(CancelRequestType Type, int Count);
|
||||
|
||||
public record VehicleDashboardCollection(PaymentMethod Method, decimal Amount, int Count);
|
||||
|
||||
public record VehicleDashboardReturns(int ReturnCount, decimal CreditValue, decimal ReturnedUnits, decimal CustomerReturnUnitsOnVehicle);
|
||||
|
||||
public record VehicleDashboardTopStore(Guid StoreId, string StoreName, int Deliveries, decimal Value);
|
||||
|
||||
public record VehicleDashboardDriver(
|
||||
Guid Id, string Name, string LicenseNumber, DateTime LicenseExpiry, DateTime MedicalClearanceExpiry,
|
||||
bool IsActive, int Rides, decimal DistanceKm, DateTime? LastRideAt);
|
||||
|
||||
public record VehicleDashboardCurrentRide(
|
||||
Guid Id, string DriverName, DateTime? StartedAt, int MeterStart, int Tasks, int Delivered, int Failed);
|
||||
|
||||
public record VehicleDashboardResponse(
|
||||
VehicleResponse Vehicle,
|
||||
VehicleNamedRef? DefaultWarehouse,
|
||||
VehicleNamedRef? MobileWarehouse,
|
||||
VehicleDashboardCurrentRide? CurrentRide,
|
||||
VehicleDashboardPeriod Period,
|
||||
VehicleDashboardKpis Kpis,
|
||||
List<VehicleDashboardStockRow> StockOnBoard,
|
||||
List<VehicleDashboardRide> Rides,
|
||||
List<VehicleDashboardDailyDelivery> DailyDeliveries,
|
||||
List<VehicleDashboardDailyMovement> DailyMovement,
|
||||
List<VehicleDashboardMovementType> MovementByType,
|
||||
List<VehicleDashboardLedgerRow> Ledger,
|
||||
List<VehicleDashboardCancelReason> CancelReasons,
|
||||
List<VehicleDashboardCollection> CollectionsByMethod,
|
||||
VehicleDashboardReturns Returns,
|
||||
List<VehicleDashboardTopStore> TopStores,
|
||||
List<VehicleDashboardDriver> Drivers);
|
||||
@@ -0,0 +1,130 @@
|
||||
using Distribution.Data;
|
||||
using Distribution.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.Vehicle;
|
||||
|
||||
public class VehicleDashboardRepository(AppDBContext db) : IVehicleDashboardRepository
|
||||
{
|
||||
public Task<List<VehicleBalanceRow>> GetStockOnBoardAsync(Guid vehicleId) =>
|
||||
db.StockBalances.AsNoTracking()
|
||||
.Where(b => b.LocationType == LocationType.Vehicle && b.LocationId == vehicleId && b.Quantity > 0)
|
||||
.Select(b => new VehicleBalanceRow(
|
||||
b.FK_ItemId, b.Item.Sku, b.Item.Name, b.Item.Category.Name, b.Item.BaseUom.Name,
|
||||
b.Item.SalePrice, b.Quantity, b.UpdatedAt))
|
||||
.ToListAsync();
|
||||
|
||||
public Task<List<VehicleMovementRow>> GetMovementsAsync(Guid vehicleId, DateTime from, DateTime toExclusive) =>
|
||||
db.StockMovements.AsNoTracking()
|
||||
.Where(m => (m.FromLocationId == vehicleId || m.ToLocationId == vehicleId)
|
||||
&& m.CreatedAt >= from && m.CreatedAt < toExclusive)
|
||||
.OrderByDescending(m => m.CreatedAt)
|
||||
.Select(m => new VehicleMovementRow(
|
||||
m.Id, m.FK_ItemId, m.Item.Name, m.Item.Sku,
|
||||
m.FromLocationId, m.FromLocationType, m.ToLocationId, m.ToLocationType,
|
||||
m.Quantity, m.Type, m.ReasonCode, m.ReferenceId, m.CreatedAt, m.CreatedBy))
|
||||
.ToListAsync();
|
||||
|
||||
public Task<List<VehicleRideRow>> GetRidesAsync(Guid vehicleId, DateTime from, DateTime toExclusive) =>
|
||||
db.DeliveryRuns.AsNoTracking()
|
||||
.Where(r => r.FK_VehicleId == vehicleId
|
||||
&& (r.StartedAt ?? r.CreatedAt) >= from && (r.StartedAt ?? r.CreatedAt) < toExclusive)
|
||||
.OrderByDescending(r => r.StartedAt ?? r.CreatedAt)
|
||||
.Select(r => new VehicleRideRow(
|
||||
r.Id, r.Status, r.FK_DriverProfileId, r.DriverProfile.User.FullName,
|
||||
r.Meterstart, r.Meterend, r.StartedAt, r.ClosedAt, r.CreatedAt,
|
||||
r.CancelTypeCode, r.CancelReason,
|
||||
db.DeliveryTasks.Count(t => t.FK_RideId == r.Id),
|
||||
db.DeliveryTasks.Count(t => t.FK_RideId == r.Id
|
||||
&& (t.Status == DeliveryTaskStatus.Delivered || t.Status == DeliveryTaskStatus.PartiallyDelivered)),
|
||||
db.DeliveryTasks.Count(t => t.FK_RideId == r.Id && t.Status == DeliveryTaskStatus.Failed)))
|
||||
.ToListAsync();
|
||||
|
||||
public Task<VehicleRideRow?> GetOpenRideAsync(Guid vehicleId) =>
|
||||
db.DeliveryRuns.AsNoTracking()
|
||||
.Where(r => r.FK_VehicleId == vehicleId && r.Status == RideStatus.Open)
|
||||
.Select(r => new VehicleRideRow(
|
||||
r.Id, r.Status, r.FK_DriverProfileId, r.DriverProfile.User.FullName,
|
||||
r.Meterstart, r.Meterend, r.StartedAt, r.ClosedAt, r.CreatedAt,
|
||||
r.CancelTypeCode, r.CancelReason,
|
||||
db.DeliveryTasks.Count(t => t.FK_RideId == r.Id),
|
||||
db.DeliveryTasks.Count(t => t.FK_RideId == r.Id
|
||||
&& (t.Status == DeliveryTaskStatus.Delivered || t.Status == DeliveryTaskStatus.PartiallyDelivered)),
|
||||
db.DeliveryTasks.Count(t => t.FK_RideId == r.Id && t.Status == DeliveryTaskStatus.Failed)))
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
public Task<List<VehicleTaskRow>> GetTasksAsync(Guid vehicleId, DateTime from, DateTime toExclusive) =>
|
||||
db.DeliveryTasks.AsNoTracking()
|
||||
.Where(t => t.Ride.FK_VehicleId == vehicleId
|
||||
&& (t.Ride.StartedAt ?? t.Ride.CreatedAt) >= from && (t.Ride.StartedAt ?? t.Ride.CreatedAt) < toExclusive)
|
||||
.Select(t => new VehicleTaskRow(
|
||||
t.FK_RideId, t.Status, t.ShopPO.FK_StoreId, t.ShopPO.Store.StoreName,
|
||||
t.ShopPO.Items.Sum(i => i.QuantityFulfilled * i.UnitPrice),
|
||||
t.Ride.StartedAt ?? t.Ride.CreatedAt))
|
||||
.ToListAsync();
|
||||
|
||||
public Task<List<VehicleCollectionRow>> GetCollectionsAsync(Guid vehicleId, DateTime from, DateTime toExclusive) =>
|
||||
db.Payments.AsNoTracking()
|
||||
.Where(p => p.CollectedAt >= from && p.CollectedAt < toExclusive
|
||||
&& db.DeliveryTasks.Any(t => t.Ride.FK_VehicleId == vehicleId && t.FK_ShopPOId == p.Invoice.FK_ShopPOId))
|
||||
.GroupBy(p => p.Method)
|
||||
.Select(g => new VehicleCollectionRow(g.Key, g.Sum(p => p.Amount), g.Count()))
|
||||
.ToListAsync();
|
||||
|
||||
public async Task<VehicleReturnTotals> GetReturnTotalsAsync(Guid vehicleId, DateTime from, DateTime toExclusive)
|
||||
{
|
||||
var returns = db.SaleReturns.AsNoTracking()
|
||||
.Where(r => r.CreatedAt >= from && r.CreatedAt < toExclusive
|
||||
&& db.DeliveryTasks.Any(t => t.Ride.FK_VehicleId == vehicleId && t.FK_ShopPOId == r.FK_ShopPOId));
|
||||
var count = await returns.CountAsync();
|
||||
var credit = await returns.SumAsync(r => (decimal?)r.TotalCreditAmount) ?? 0m;
|
||||
var units = await returns.SelectMany(r => r.Items).SumAsync(i => (decimal?)i.Quantity) ?? 0m;
|
||||
return new VehicleReturnTotals(count, credit, units);
|
||||
}
|
||||
|
||||
public Task<List<VehicleDriverRow>> GetDriversAsync(IEnumerable<Guid> driverProfileIds)
|
||||
{
|
||||
var ids = driverProfileIds.Distinct().ToList();
|
||||
return db.DriverProfiles.AsNoTracking()
|
||||
.Where(d => ids.Contains(d.Id))
|
||||
.Select(d => new VehicleDriverRow(
|
||||
d.Id, d.User.FullName, d.LicenseNumber, d.LicenseExpiry, d.MedicalClearanceExpiry, d.IsActive))
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public Task<VehicleNamedRef?> GetWarehouseRefAsync(Guid? warehouseId) =>
|
||||
warehouseId is null
|
||||
? Task.FromResult<VehicleNamedRef?>(null)
|
||||
: db.Warehouses.AsNoTracking()
|
||||
.Where(w => w.Id == warehouseId)
|
||||
.Select(w => new VehicleNamedRef(w.Id, w.Name))
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
public Task<VehicleNamedRef?> GetMobileWarehouseAsync(Guid vehicleId) =>
|
||||
db.Warehouses.AsNoTracking()
|
||||
.Where(w => w.VehicleId == vehicleId)
|
||||
.Select(w => new VehicleNamedRef(w.Id, w.Name))
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
public async Task<Dictionary<Guid, string>> GetLocationNamesAsync(IEnumerable<Guid> ids)
|
||||
{
|
||||
var idList = ids.Distinct().ToList();
|
||||
var names = new Dictionary<Guid, string>();
|
||||
if (idList.Count == 0) return names;
|
||||
|
||||
foreach (var w in await db.Warehouses.AsNoTracking().Where(w => idList.Contains(w.Id)).Select(w => new { w.Id, w.Name }).ToListAsync())
|
||||
names[w.Id] = w.Name;
|
||||
foreach (var v in await db.Vehicles.AsNoTracking().Where(v => idList.Contains(v.Id)).Select(v => new { v.Id, v.RegNumber }).ToListAsync())
|
||||
names[v.Id] = v.RegNumber;
|
||||
return names;
|
||||
}
|
||||
|
||||
public async Task<Dictionary<Guid, string>> GetUserNamesAsync(IEnumerable<Guid> ids)
|
||||
{
|
||||
var idList = ids.Distinct().ToList();
|
||||
if (idList.Count == 0) return [];
|
||||
return await db.Users.AsNoTracking()
|
||||
.Where(u => idList.Contains(u.Id))
|
||||
.ToDictionaryAsync(u => u.Id, u => u.FullName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.Vehicle;
|
||||
|
||||
public class VehicleDashboardService(IVehicleRepository vehicleRepo, IVehicleDashboardRepository repo)
|
||||
{
|
||||
private const int LedgerSize = 50;
|
||||
private const int RideListSize = 15;
|
||||
private const int TopListSize = 5;
|
||||
private const int MaxPeriodDays = 366;
|
||||
|
||||
public static string? Validate(DateTime? from, DateTime? to)
|
||||
{
|
||||
if (from.HasValue && to.HasValue)
|
||||
{
|
||||
if (from.Value.Date > to.Value.Date) return "'from' must not be after 'to'.";
|
||||
if ((to.Value.Date - from.Value.Date).TotalDays >= MaxPeriodDays) return $"The period cannot exceed {MaxPeriodDays} days.";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>Unlike the standard vehicle lookup, this also serves inactive vehicles so their history stays viewable.</summary>
|
||||
public async Task<VehicleDashboardResponse?> GetAsync(Guid id, DateTime? from, DateTime? to)
|
||||
{
|
||||
var vehicle = await vehicleRepo.GetByIdAsync(id);
|
||||
if (vehicle is null) return null;
|
||||
|
||||
var toDate = (to ?? DateTime.Now).Date;
|
||||
var fromDate = (from ?? toDate.AddDays(-29)).Date;
|
||||
var toExclusive = toDate.AddDays(1);
|
||||
|
||||
var balances = await repo.GetStockOnBoardAsync(id);
|
||||
var movements = (await repo.GetMovementsAsync(id, fromDate, toExclusive))
|
||||
.Select(m => (Row: m, Direction: DirectionOf(m, id)))
|
||||
.Where(x => x.Direction is not null)
|
||||
.ToList();
|
||||
var rides = await repo.GetRidesAsync(id, fromDate, toExclusive);
|
||||
var tasks = await repo.GetTasksAsync(id, fromDate, toExclusive);
|
||||
var collections = await repo.GetCollectionsAsync(id, fromDate, toExclusive);
|
||||
var returnTotals = await repo.GetReturnTotalsAsync(id, fromDate, toExclusive);
|
||||
var openRide = await repo.GetOpenRideAsync(id);
|
||||
|
||||
// ── Stock on board ─────────────────────────────────────────────────
|
||||
var stock = balances
|
||||
.GroupBy(b => b.ItemId)
|
||||
.Select(g =>
|
||||
{
|
||||
var f = g.First();
|
||||
var qty = g.Sum(x => x.Quantity);
|
||||
return new VehicleDashboardStockRow(
|
||||
f.ItemId, f.Sku, f.Name, f.Category, f.Uom, qty, qty * (f.SalePrice ?? 0m), g.Max(x => x.UpdatedAt));
|
||||
})
|
||||
.OrderBy(r => r.Name)
|
||||
.ToList();
|
||||
|
||||
// ── Rides and deliveries ───────────────────────────────────────────
|
||||
static decimal Km(VehicleRideRow r) =>
|
||||
r.Status == RideStatus.Closed && r.MeterEnd > r.MeterStart ? r.MeterEnd - r.MeterStart : 0m;
|
||||
|
||||
var rideRows = rides.Take(RideListSize)
|
||||
.Select(r => new VehicleDashboardRide(
|
||||
r.Id, r.Status, r.DriverName, r.StartedAt, r.ClosedAt, r.CreatedAt,
|
||||
Km(r), r.Tasks, r.Delivered, r.Failed, r.CancelType, r.CancelReason))
|
||||
.ToList();
|
||||
|
||||
bool IsDelivered(VehicleTaskRow t) =>
|
||||
t.Status is DeliveryTaskStatus.Delivered or DeliveryTaskStatus.PartiallyDelivered;
|
||||
var delivered = tasks.Count(IsDelivered);
|
||||
var failed = tasks.Count(t => t.Status == DeliveryTaskStatus.Failed);
|
||||
var pending = tasks.Count(t => t.Status == DeliveryTaskStatus.Pending);
|
||||
var deliveredValue = tasks.Where(IsDelivered).Sum(t => t.DeliveredValue);
|
||||
|
||||
var dailyDeliveries = new List<VehicleDashboardDailyDelivery>();
|
||||
var dailyMovement = new List<VehicleDashboardDailyMovement>();
|
||||
var tasksByDay = tasks.GroupBy(t => t.Date.Date).ToDictionary(g => g.Key);
|
||||
var movesByDay = movements.GroupBy(x => x.Row.CreatedAt.Date).ToDictionary(g => g.Key);
|
||||
for (var d = fromDate; d <= toDate; d = d.AddDays(1))
|
||||
{
|
||||
var key = d.ToString("yyyy-MM-dd");
|
||||
tasksByDay.TryGetValue(d, out var dayTasks);
|
||||
movesByDay.TryGetValue(d, out var dayMoves);
|
||||
dailyDeliveries.Add(new VehicleDashboardDailyDelivery(
|
||||
key, dayTasks?.Count(IsDelivered) ?? 0, dayTasks?.Count(t => t.Status == DeliveryTaskStatus.Failed) ?? 0));
|
||||
dailyMovement.Add(new VehicleDashboardDailyMovement(
|
||||
key,
|
||||
dayMoves?.Where(x => x.Direction == "In").Sum(x => x.Row.Quantity) ?? 0m,
|
||||
dayMoves?.Where(x => x.Direction == "Out").Sum(x => x.Row.Quantity) ?? 0m));
|
||||
}
|
||||
|
||||
var cancelReasons = rides
|
||||
.Where(r => r.Status == RideStatus.Cancelled && r.CancelType.HasValue)
|
||||
.GroupBy(r => r.CancelType!.Value)
|
||||
.Select(g => new VehicleDashboardCancelReason(g.Key, g.Count()))
|
||||
.OrderByDescending(c => c.Count)
|
||||
.ToList();
|
||||
|
||||
var topStores = tasks
|
||||
.Where(IsDelivered)
|
||||
.GroupBy(t => (t.StoreId, t.StoreName))
|
||||
.Select(g => new VehicleDashboardTopStore(g.Key.StoreId, g.Key.StoreName, g.Count(), g.Sum(t => t.DeliveredValue)))
|
||||
.OrderByDescending(s => s.Value)
|
||||
.Take(TopListSize)
|
||||
.ToList();
|
||||
|
||||
// ── Movements ──────────────────────────────────────────────────────
|
||||
var byType = movements
|
||||
.GroupBy(x => (x.Row.Type, x.Direction))
|
||||
.Select(g => new VehicleDashboardMovementType(g.Key.Type, g.Key.Direction!, g.Sum(x => x.Row.Quantity), g.Count()))
|
||||
.OrderByDescending(r => r.Units)
|
||||
.ToList();
|
||||
|
||||
var ledgerSource = movements.Take(LedgerSize).ToList();
|
||||
var locationIds = ledgerSource
|
||||
.SelectMany(x => new[]
|
||||
{
|
||||
(x.Row.FromLocationId, x.Row.FromLocationType),
|
||||
(x.Row.ToLocationId, x.Row.ToLocationType),
|
||||
})
|
||||
.Where(l => l.Item1.HasValue && l.Item2 is LocationType t && t != LocationType.External)
|
||||
.Select(l => l.Item1!.Value);
|
||||
var locationNames = await repo.GetLocationNamesAsync(locationIds);
|
||||
var userNames = await repo.GetUserNamesAsync(ledgerSource.Select(x => x.Row.CreatedBy));
|
||||
|
||||
var ledger = ledgerSource.Select(x => new VehicleDashboardLedgerRow(
|
||||
x.Row.Id, x.Row.CreatedAt, x.Row.ItemId, x.Row.ItemName, x.Row.Sku,
|
||||
x.Row.Type, x.Direction!, x.Row.Quantity,
|
||||
SideName(x.Row.Type, x.Row.FromLocationId, x.Row.FromLocationType, locationNames, isFrom: true),
|
||||
SideName(x.Row.Type, x.Row.ToLocationId, x.Row.ToLocationType, locationNames, isFrom: false),
|
||||
x.Row.ReferenceId, ReferenceKind(x.Row.Type),
|
||||
x.Row.ReasonCode, userNames.GetValueOrDefault(x.Row.CreatedBy, "—"))).ToList();
|
||||
|
||||
// ── Drivers ────────────────────────────────────────────────────────
|
||||
var driverProfiles = (await repo.GetDriversAsync(rides.Select(r => r.DriverProfileId)))
|
||||
.ToDictionary(d => d.Id);
|
||||
var drivers = rides
|
||||
.GroupBy(r => r.DriverProfileId)
|
||||
.Where(g => driverProfiles.ContainsKey(g.Key))
|
||||
.Select(g =>
|
||||
{
|
||||
var d = driverProfiles[g.Key];
|
||||
return new VehicleDashboardDriver(
|
||||
d.Id, d.Name, d.LicenseNumber, d.LicenseExpiry, d.MedicalClearanceExpiry, d.IsActive,
|
||||
g.Count(), g.Sum(Km), g.Max(r => r.StartedAt ?? r.CreatedAt));
|
||||
})
|
||||
.OrderByDescending(d => d.Rides)
|
||||
.ToList();
|
||||
|
||||
// ── Assemble ───────────────────────────────────────────────────────
|
||||
var settled = delivered + failed;
|
||||
var kpis = new VehicleDashboardKpis(
|
||||
rides.Count, rides.Count(r => r.Status == RideStatus.Closed), rides.Count(r => r.Status == RideStatus.Cancelled),
|
||||
rides.Sum(Km),
|
||||
tasks.Count, delivered, failed, pending,
|
||||
settled == 0 ? 0m : Math.Round(delivered * 100m / settled, 1),
|
||||
deliveredValue, collections.Sum(c => c.Amount),
|
||||
stock.Sum(r => r.Quantity), stock.Sum(r => r.Value),
|
||||
movements.Where(x => x.Row.Type == MovementType.Load && x.Direction == "In").Sum(x => x.Row.Quantity),
|
||||
movements.Where(x => x.Row.Type == MovementType.Sale && x.Direction == "Out").Sum(x => x.Row.Quantity));
|
||||
|
||||
var customerReturnUnits = movements
|
||||
.Where(x => x.Row.Type == MovementType.CustomerReturn && x.Direction == "In")
|
||||
.Sum(x => x.Row.Quantity);
|
||||
|
||||
return new VehicleDashboardResponse(
|
||||
new VehicleResponse(vehicle.Id, vehicle.RegNumber, vehicle.NumberPlate, vehicle.Type, vehicle.IsActive, vehicle.CreatedAt),
|
||||
await repo.GetWarehouseRefAsync(vehicle.DefaultWarehouseId),
|
||||
await repo.GetMobileWarehouseAsync(id),
|
||||
openRide is null ? null : new VehicleDashboardCurrentRide(
|
||||
openRide.Id, openRide.DriverName, openRide.StartedAt, openRide.MeterStart,
|
||||
openRide.Tasks, openRide.Delivered, openRide.Failed),
|
||||
new VehicleDashboardPeriod(fromDate, toDate),
|
||||
kpis, stock, rideRows, dailyDeliveries, dailyMovement, byType, ledger, cancelReasons,
|
||||
collections.Select(c => new VehicleDashboardCollection(c.Method, c.Amount, c.Count))
|
||||
.OrderByDescending(c => c.Amount).ToList(),
|
||||
new VehicleDashboardReturns(returnTotals.ReturnCount, returnTotals.CreditValue, returnTotals.Units, customerReturnUnits),
|
||||
topStores, drivers);
|
||||
}
|
||||
|
||||
private static string? DirectionOf(VehicleMovementRow m, Guid vehicleId) =>
|
||||
m.ToLocationId == vehicleId && m.FromLocationId != vehicleId ? "In"
|
||||
: m.FromLocationId == vehicleId && m.ToLocationId != vehicleId ? "Out"
|
||||
: null;
|
||||
|
||||
private static string? ReferenceKind(MovementType type) => type switch
|
||||
{
|
||||
MovementType.Load => "Loading Sheet",
|
||||
MovementType.Sale => "Order",
|
||||
MovementType.CustomerReturn => "Return",
|
||||
_ => null,
|
||||
};
|
||||
|
||||
private static string SideName(
|
||||
MovementType type, Guid? id, LocationType? locationType, Dictionary<Guid, string> names, bool isFrom)
|
||||
{
|
||||
if (id is not Guid locId || locationType is not LocationType lt) return "—";
|
||||
if (lt != LocationType.External) return names.GetValueOrDefault(locId, "—");
|
||||
return type switch
|
||||
{
|
||||
MovementType.Sale or MovementType.CustomerReturn => "Customer",
|
||||
MovementType.PurchaseReceipt or MovementType.PurchaseReturn => "Supplier",
|
||||
MovementType.Damage => "Written Off",
|
||||
MovementType.Adjustment => "Stock Adjustment",
|
||||
_ => isFrom ? "External Source" : "External Destination",
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace Distribution.Features.Warehouse;
|
||||
|
||||
public interface IWarehouseDashboardRepository
|
||||
{
|
||||
Task<List<WarehouseBalanceRow>> GetBalanceRowsAsync(Guid warehouseId);
|
||||
Task<List<WarehouseMovementRow>> GetMovementsAsync(Guid warehouseId, DateTime from, DateTime toExclusive);
|
||||
Task<List<WarehouseLastOutboundRow>> GetLastOutboundAsync(Guid warehouseId);
|
||||
|
||||
Task<WarehouseGrnTotals> GetGrnTotalsAsync(Guid warehouseId, DateTime from, DateTime toExclusive);
|
||||
Task<List<DashboardGrnRow>> GetRecentGrnsAsync(Guid warehouseId, DateTime from, DateTime toExclusive, int take);
|
||||
|
||||
Task<List<DashboardLoadingSheetRow>> GetRecentLoadingSheetsAsync(Guid warehouseId, DateTime from, DateTime toExclusive, int take);
|
||||
Task<decimal> GetPendingDispatchUnitsAsync(Guid warehouseId);
|
||||
|
||||
Task<List<WarehouseVehicleStockRow>> GetVehicleStockAsync(Guid warehouseId, Guid? primaryVehicleId);
|
||||
Task<List<DashboardLinkedVehicle>> GetDefaultVehiclesAsync(Guid warehouseId, Guid? primaryVehicleId);
|
||||
|
||||
Task<int> GetOpenClaimCountAsync(Guid warehouseId);
|
||||
Task<List<DashboardClaimRow>> GetOpenClaimsAsync(Guid warehouseId, int take);
|
||||
|
||||
Task<Dictionary<Guid, string>> GetLocationNamesAsync(IEnumerable<Guid> ids);
|
||||
Task<Dictionary<Guid, string>> GetUserNamesAsync(IEnumerable<Guid> ids);
|
||||
}
|
||||
@@ -4,7 +4,7 @@ namespace Distribution.Features.Warehouse;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/warehouses")]
|
||||
public class WarehouseController(WarehouseService service) : ControllerBase
|
||||
public class WarehouseController(WarehouseService service, WarehouseDashboardService dashboardService) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAll() =>
|
||||
@@ -17,6 +17,16 @@ public class WarehouseController(WarehouseService service) : ControllerBase
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/dashboard")]
|
||||
public async Task<IActionResult> Dashboard(
|
||||
Guid id, [FromQuery] DateTime? from, [FromQuery] DateTime? to, [FromQuery] int deadStockDays = 30)
|
||||
{
|
||||
var error = WarehouseDashboardService.Validate(from, to, deadStockDays);
|
||||
if (error is not null) return BadRequest(new { message = error });
|
||||
var result = await dashboardService.GetAsync(id, from, to, deadStockDays);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create(CreateWarehouseRequest request, CancellationToken ct)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.Warehouse;
|
||||
|
||||
// ── Repository projections ─────────────────────────────────────────────────
|
||||
|
||||
public record WarehouseBalanceRow(
|
||||
Guid ItemId, string Sku, string Name, string Category, string? Brand, string Uom,
|
||||
decimal? ReorderLevel, decimal? CostPrice, decimal? SalePrice,
|
||||
Guid? BatchId, string? BatchNumber, DateTime? ExpiryDate,
|
||||
decimal Quantity, DateTime UpdatedAt);
|
||||
|
||||
public record WarehouseMovementRow(
|
||||
Guid Id, Guid ItemId, string ItemName, string Sku,
|
||||
Guid? FromLocationId, LocationType? FromLocationType,
|
||||
Guid? ToLocationId, LocationType? ToLocationType,
|
||||
decimal Quantity, MovementType Type, string? ReasonCode, Guid? ReferenceId,
|
||||
DateTime CreatedAt, Guid CreatedBy);
|
||||
|
||||
public record WarehouseLastOutboundRow(Guid ItemId, DateTime LastOutboundAt);
|
||||
|
||||
public record WarehouseGrnTotals(int Count, int Accepted, int Rejected);
|
||||
|
||||
public record WarehouseVehicleStockRow(
|
||||
Guid VehicleId, string RegNumber, string NumberPlate, bool IsLinked, decimal Units, int Skus);
|
||||
|
||||
// ── Response ───────────────────────────────────────────────────────────────
|
||||
|
||||
public record DashboardPeriod(DateTime From, DateTime To);
|
||||
|
||||
public record DashboardKpis(
|
||||
decimal StockValueCost, decimal StockValueSale, decimal TotalUnits, int SkuCount,
|
||||
int LowStockCount, int OutOfStockCount, int NearExpiryCount, int ExpiredCount,
|
||||
decimal UnitsIn, decimal UnitsOut);
|
||||
|
||||
public record DashboardBatch(Guid BatchId, string? BatchNumber, DateTime? ExpiryDate, decimal Quantity);
|
||||
|
||||
public record DashboardStockRow(
|
||||
Guid ItemId, string Sku, string Name, string Category, string? Brand, string Uom,
|
||||
decimal Quantity, decimal? ReorderLevel, string Status,
|
||||
decimal CostValue, decimal SaleValue, DateTime UpdatedAt, List<DashboardBatch> Batches);
|
||||
|
||||
public record DashboardCategoryStock(string Category, decimal Units, decimal Value);
|
||||
|
||||
public record DashboardExpiringBatch(
|
||||
Guid ItemId, string ItemName, string? BatchNumber, DateTime ExpiryDate, decimal Quantity, string Status);
|
||||
|
||||
public record DashboardDailyPoint(string Date, decimal In, decimal Out);
|
||||
|
||||
public record DashboardMovementTypeRow(MovementType Type, string Direction, decimal Units, int Count);
|
||||
|
||||
public record DashboardLedgerRow(
|
||||
Guid Id, DateTime CreatedAt, Guid ItemId, string ItemName, string Sku,
|
||||
MovementType Type, string Direction, decimal Quantity,
|
||||
string FromName, string ToName, Guid? ReferenceId, string? ReferenceKind,
|
||||
string? ReasonCode, string CreatedByName);
|
||||
|
||||
public record DashboardGrnRow(
|
||||
Guid Id, DateTime ReceivedAt, Guid? PurchaseOrderId, string? SupplierName,
|
||||
int AcceptedQuantity, int RejectedQuantity, decimal CostValue);
|
||||
|
||||
public record DashboardInbound(
|
||||
int GrnCount, int AcceptedUnits, int RejectedUnits, decimal RejectionRatePercent, List<DashboardGrnRow> RecentGrns);
|
||||
|
||||
public record DashboardLoadingSheetRow(
|
||||
Guid Id, Guid RideId, string VehiclePlate, LoadingSheetStatus Status,
|
||||
DateTime PopulatedAt, DateTime? ConfirmedAt, int Lines, decimal Units);
|
||||
|
||||
public record DashboardOutbound(
|
||||
decimal UnitsDispatched, decimal PendingDispatchUnits, List<DashboardLoadingSheetRow> RecentLoadingSheets);
|
||||
|
||||
public record DashboardVehicle(
|
||||
Guid VehicleId, string RegNumber, string NumberPlate, bool IsLinked, decimal Units, int Skus);
|
||||
|
||||
public record DashboardClaimRow(Guid Id, DateTime CreatedAt, string? SupplierName, string Details, decimal Units);
|
||||
|
||||
public record DashboardLosses(
|
||||
decimal PurchaseReturnUnits, decimal DamageUnits, decimal NegativeAdjustmentUnits,
|
||||
decimal PositiveAdjustmentUnits, int OpenClaimCount, List<DashboardClaimRow> OpenClaims);
|
||||
|
||||
public record DashboardTopMover(Guid ItemId, string Sku, string Name, decimal UnitsOut);
|
||||
|
||||
public record DashboardDeadStock(
|
||||
Guid ItemId, string Sku, string Name, decimal Quantity, decimal CostValue, DateTime? LastOutboundAt);
|
||||
|
||||
public record DashboardLinkedVehicle(Guid Id, string RegNumber, string NumberPlate, bool IsPrimary);
|
||||
|
||||
public record WarehouseDashboardResponse(
|
||||
WarehouseResponse Warehouse,
|
||||
List<DashboardLinkedVehicle> LinkedVehicles,
|
||||
DashboardPeriod Period,
|
||||
int DeadStockDays,
|
||||
DashboardKpis Kpis,
|
||||
List<DashboardStockRow> Stock,
|
||||
List<DashboardCategoryStock> StockByCategory,
|
||||
List<DashboardExpiringBatch> ExpiringBatches,
|
||||
List<DashboardDailyPoint> DailyMovement,
|
||||
List<DashboardMovementTypeRow> MovementByType,
|
||||
List<DashboardLedgerRow> Ledger,
|
||||
DashboardInbound Inbound,
|
||||
DashboardOutbound Outbound,
|
||||
List<DashboardVehicle> Vehicles,
|
||||
DashboardLosses Losses,
|
||||
List<DashboardTopMover> TopMovers,
|
||||
List<DashboardDeadStock> DeadStock);
|
||||
@@ -0,0 +1,160 @@
|
||||
using Distribution.Data;
|
||||
using Distribution.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.Warehouse;
|
||||
|
||||
public class WarehouseDashboardRepository(AppDBContext db) : IWarehouseDashboardRepository
|
||||
{
|
||||
public Task<List<WarehouseBalanceRow>> GetBalanceRowsAsync(Guid warehouseId) =>
|
||||
db.StockBalances.AsNoTracking()
|
||||
.Where(b => b.LocationType == LocationType.Warehouse && b.LocationId == warehouseId)
|
||||
.Select(b => new WarehouseBalanceRow(
|
||||
b.FK_ItemId, b.Item.Sku, b.Item.Name, b.Item.Category.Name,
|
||||
b.Item.Brand != null ? b.Item.Brand.Name : null, b.Item.BaseUom.Name,
|
||||
b.Item.ReorderLevel, b.Item.CostPrice, b.Item.SalePrice,
|
||||
b.FK_ItemBatchId,
|
||||
b.ItemBatch != null ? b.ItemBatch.BatchNumber : null,
|
||||
b.ItemBatch != null ? b.ItemBatch.ExpiryDate : null,
|
||||
b.Quantity, b.UpdatedAt))
|
||||
.ToListAsync();
|
||||
|
||||
public Task<List<WarehouseMovementRow>> GetMovementsAsync(Guid warehouseId, DateTime from, DateTime toExclusive) =>
|
||||
db.StockMovements.AsNoTracking()
|
||||
.Where(m => (m.FromLocationId == warehouseId || m.ToLocationId == warehouseId)
|
||||
&& m.CreatedAt >= from && m.CreatedAt < toExclusive)
|
||||
.OrderByDescending(m => m.CreatedAt)
|
||||
.Select(m => new WarehouseMovementRow(
|
||||
m.Id, m.FK_ItemId, m.Item.Name, m.Item.Sku,
|
||||
m.FromLocationId, m.FromLocationType, m.ToLocationId, m.ToLocationType,
|
||||
m.Quantity, m.Type, m.ReasonCode, m.ReferenceId, m.CreatedAt, m.CreatedBy))
|
||||
.ToListAsync();
|
||||
|
||||
public Task<List<WarehouseLastOutboundRow>> GetLastOutboundAsync(Guid warehouseId) =>
|
||||
db.StockMovements.AsNoTracking()
|
||||
.Where(m => m.FromLocationId == warehouseId && m.ToLocationId != warehouseId)
|
||||
.GroupBy(m => m.FK_ItemId)
|
||||
.Select(g => new WarehouseLastOutboundRow(g.Key, g.Max(x => x.CreatedAt)))
|
||||
.ToListAsync();
|
||||
|
||||
public async Task<WarehouseGrnTotals> GetGrnTotalsAsync(Guid warehouseId, DateTime from, DateTime toExclusive)
|
||||
{
|
||||
var receipts = db.GoodsReceipts.AsNoTracking()
|
||||
.Where(g => g.FK_WarehouseId == warehouseId && g.ReceivedAt >= from && g.ReceivedAt < toExclusive);
|
||||
var count = await receipts.CountAsync();
|
||||
var accepted = await receipts.SelectMany(g => g.Items).SumAsync(i => (int?)i.AcceptedQuantity) ?? 0;
|
||||
var rejected = await receipts.SelectMany(g => g.Items).SumAsync(i => (int?)i.RejectedQuantity) ?? 0;
|
||||
return new WarehouseGrnTotals(count, accepted, rejected);
|
||||
}
|
||||
|
||||
public Task<List<DashboardGrnRow>> GetRecentGrnsAsync(Guid warehouseId, DateTime from, DateTime toExclusive, int take) =>
|
||||
db.GoodsReceipts.AsNoTracking()
|
||||
.Where(g => g.FK_WarehouseId == warehouseId && g.ReceivedAt >= from && g.ReceivedAt < toExclusive)
|
||||
.OrderByDescending(g => g.ReceivedAt)
|
||||
.Take(take)
|
||||
.Select(g => new DashboardGrnRow(
|
||||
g.Id, g.ReceivedAt, g.FK_PurchaseOrderId,
|
||||
g.PurchaseOrder != null ? g.PurchaseOrder.Supplier.Name : (g.Supplier != null ? g.Supplier.Name : null),
|
||||
g.Items.Sum(i => i.AcceptedQuantity),
|
||||
g.Items.Sum(i => i.RejectedQuantity),
|
||||
g.Items.Sum(i => i.AcceptedQuantity * i.UnitCost)))
|
||||
.ToListAsync();
|
||||
|
||||
public Task<List<DashboardLoadingSheetRow>> GetRecentLoadingSheetsAsync(Guid warehouseId, DateTime from, DateTime toExclusive, int take) =>
|
||||
db.LoadingSheets.AsNoTracking()
|
||||
.Where(s => s.PopulatedAt >= from && s.PopulatedAt < toExclusive
|
||||
&& s.Items.Any(i => i.FK_WarehouseId == warehouseId))
|
||||
.OrderByDescending(s => s.PopulatedAt)
|
||||
.Take(take)
|
||||
.Select(s => new DashboardLoadingSheetRow(
|
||||
s.Id, s.FK_RideId, s.Ride.Vehicle.NumberPlate, s.Status, s.PopulatedAt, s.ConfirmedAt,
|
||||
s.Items.Count(i => i.FK_WarehouseId == warehouseId),
|
||||
s.Items.Where(i => i.FK_WarehouseId == warehouseId).Sum(i => i.QuantityDispatched)))
|
||||
.ToListAsync();
|
||||
|
||||
public async Task<decimal> GetPendingDispatchUnitsAsync(Guid warehouseId) =>
|
||||
await db.LoadingSheetItems.AsNoTracking()
|
||||
.Where(i => i.FK_WarehouseId == warehouseId && i.LoadingSheet.Status == LoadingSheetStatus.Planned)
|
||||
.SumAsync(i => (decimal?)i.QuantityDispatched) ?? 0m;
|
||||
|
||||
public async Task<List<WarehouseVehicleStockRow>> GetVehicleStockAsync(Guid warehouseId, Guid? primaryVehicleId)
|
||||
{
|
||||
var loadedFromHere = db.LoadingSheetItems
|
||||
.Where(i => i.FK_WarehouseId == warehouseId)
|
||||
.Select(i => i.LoadingSheet.Ride.FK_VehicleId);
|
||||
|
||||
var vehicles = await db.Vehicles.AsNoTracking()
|
||||
.Where(v => v.IsActive
|
||||
&& (v.DefaultWarehouseId == warehouseId
|
||||
|| (primaryVehicleId != null && v.Id == primaryVehicleId)
|
||||
|| loadedFromHere.Contains(v.Id)))
|
||||
.Select(v => new
|
||||
{
|
||||
v.Id, v.RegNumber, v.NumberPlate,
|
||||
IsLinked = v.DefaultWarehouseId == warehouseId || (primaryVehicleId != null && v.Id == primaryVehicleId),
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
var ids = vehicles.Select(v => v.Id).ToList();
|
||||
var balances = await db.StockBalances.AsNoTracking()
|
||||
.Where(b => b.LocationType == LocationType.Vehicle && ids.Contains(b.LocationId) && b.Quantity > 0)
|
||||
.GroupBy(b => b.LocationId)
|
||||
.Select(g => new { VehicleId = g.Key, Units = g.Sum(x => x.Quantity), Skus = g.Select(x => x.FK_ItemId).Distinct().Count() })
|
||||
.ToListAsync();
|
||||
|
||||
return vehicles
|
||||
.Select(v =>
|
||||
{
|
||||
var bal = balances.FirstOrDefault(b => b.VehicleId == v.Id);
|
||||
return new WarehouseVehicleStockRow(v.Id, v.RegNumber, v.NumberPlate, v.IsLinked, bal?.Units ?? 0m, bal?.Skus ?? 0);
|
||||
})
|
||||
.OrderByDescending(v => v.Units)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public Task<List<DashboardLinkedVehicle>> GetDefaultVehiclesAsync(Guid warehouseId, Guid? primaryVehicleId) =>
|
||||
db.Vehicles.AsNoTracking()
|
||||
.Where(v => v.IsActive
|
||||
&& (v.DefaultWarehouseId == warehouseId || (primaryVehicleId != null && v.Id == primaryVehicleId)))
|
||||
.Select(v => new DashboardLinkedVehicle(
|
||||
v.Id, v.RegNumber, v.NumberPlate, primaryVehicleId != null && v.Id == primaryVehicleId))
|
||||
.ToListAsync();
|
||||
|
||||
public Task<int> GetOpenClaimCountAsync(Guid warehouseId) =>
|
||||
db.SupplierClaims.AsNoTracking()
|
||||
.CountAsync(c => c.Status == SupplierClaimStatus.Open
|
||||
&& c.GoodsReceipt != null && c.GoodsReceipt.FK_WarehouseId == warehouseId);
|
||||
|
||||
public Task<List<DashboardClaimRow>> GetOpenClaimsAsync(Guid warehouseId, int take) =>
|
||||
db.SupplierClaims.AsNoTracking()
|
||||
.Where(c => c.Status == SupplierClaimStatus.Open
|
||||
&& c.GoodsReceipt != null && c.GoodsReceipt.FK_WarehouseId == warehouseId)
|
||||
.OrderByDescending(c => c.CreatedAt)
|
||||
.Take(take)
|
||||
.Select(c => new DashboardClaimRow(
|
||||
c.Id, c.CreatedAt, c.PurchaseOrder.Supplier.Name, c.Details,
|
||||
c.Items.Sum(i => i.Quantity)))
|
||||
.ToListAsync();
|
||||
|
||||
public async Task<Dictionary<Guid, string>> GetLocationNamesAsync(IEnumerable<Guid> ids)
|
||||
{
|
||||
var idList = ids.Distinct().ToList();
|
||||
var names = new Dictionary<Guid, string>();
|
||||
if (idList.Count == 0) return names;
|
||||
|
||||
foreach (var w in await db.Warehouses.AsNoTracking().Where(w => idList.Contains(w.Id)).Select(w => new { w.Id, w.Name }).ToListAsync())
|
||||
names[w.Id] = w.Name;
|
||||
foreach (var v in await db.Vehicles.AsNoTracking().Where(v => idList.Contains(v.Id)).Select(v => new { v.Id, v.RegNumber }).ToListAsync())
|
||||
names[v.Id] = v.RegNumber;
|
||||
return names;
|
||||
}
|
||||
|
||||
public async Task<Dictionary<Guid, string>> GetUserNamesAsync(IEnumerable<Guid> ids)
|
||||
{
|
||||
var idList = ids.Distinct().ToList();
|
||||
if (idList.Count == 0) return [];
|
||||
return await db.Users.AsNoTracking()
|
||||
.Where(u => idList.Contains(u.Id))
|
||||
.ToDictionaryAsync(u => u.Id, u => u.FullName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.Warehouse;
|
||||
|
||||
public class WarehouseDashboardService(IWarehouseRepository warehouseRepo, IWarehouseDashboardRepository repo)
|
||||
{
|
||||
private const int NearExpiryDays = 30;
|
||||
private const int LedgerSize = 50;
|
||||
private const int RecentDocumentCount = 8;
|
||||
private const int TopListSize = 5;
|
||||
private const int DeadStockListSize = 10;
|
||||
private const int MaxPeriodDays = 366;
|
||||
|
||||
public static string? Validate(DateTime? from, DateTime? to, int deadStockDays)
|
||||
{
|
||||
if (deadStockDays < 1 || deadStockDays > 365) return "deadStockDays must be between 1 and 365.";
|
||||
if (from.HasValue && to.HasValue)
|
||||
{
|
||||
if (from.Value.Date > to.Value.Date) return "'from' must not be after 'to'.";
|
||||
if ((to.Value.Date - from.Value.Date).TotalDays >= MaxPeriodDays) return $"The period cannot exceed {MaxPeriodDays} days.";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>Unlike the standard warehouse lookup, this also serves inactive warehouses so their history stays viewable.</summary>
|
||||
public async Task<WarehouseDashboardResponse?> GetAsync(Guid id, DateTime? from, DateTime? to, int deadStockDays)
|
||||
{
|
||||
var warehouse = await warehouseRepo.GetByIdAsync(id);
|
||||
if (warehouse is null) return null;
|
||||
|
||||
var now = DateTime.Now;
|
||||
var today = now.Date;
|
||||
var toDate = (to ?? today).Date;
|
||||
var fromDate = (from ?? toDate.AddDays(-29)).Date;
|
||||
var toExclusive = toDate.AddDays(1);
|
||||
|
||||
var balances = await repo.GetBalanceRowsAsync(id);
|
||||
var movements = (await repo.GetMovementsAsync(id, fromDate, toExclusive))
|
||||
.Select(m => (Row: m, Direction: DirectionOf(m, id)))
|
||||
.Where(x => x.Direction is not null)
|
||||
.ToList();
|
||||
var lastOutbound = (await repo.GetLastOutboundAsync(id)).ToDictionary(r => r.ItemId, r => r.LastOutboundAt);
|
||||
|
||||
// ── Stock on hand ──────────────────────────────────────────────────
|
||||
var stock = balances
|
||||
.GroupBy(b => b.ItemId)
|
||||
.Select(g => BuildStockRow(g.ToList()))
|
||||
.OrderBy(r => r.Name)
|
||||
.ToList();
|
||||
|
||||
var stockByCategory = stock
|
||||
.Where(r => r.Quantity > 0)
|
||||
.GroupBy(r => r.Category)
|
||||
.Select(g => new DashboardCategoryStock(g.Key, g.Sum(r => r.Quantity), g.Sum(r => r.CostValue)))
|
||||
.OrderByDescending(c => c.Value)
|
||||
.ToList();
|
||||
|
||||
var itemNames = stock.ToDictionary(r => r.ItemId, r => r.Name);
|
||||
var expiring = balances
|
||||
.Where(b => b.Quantity > 0 && b.ExpiryDate.HasValue && b.BatchId.HasValue
|
||||
&& b.ExpiryDate.Value.Date <= today.AddDays(NearExpiryDays))
|
||||
.GroupBy(b => (b.ItemId, b.BatchId))
|
||||
.Select(g =>
|
||||
{
|
||||
var first = g.First();
|
||||
var expiry = first.ExpiryDate!.Value;
|
||||
return new DashboardExpiringBatch(
|
||||
first.ItemId, itemNames.GetValueOrDefault(first.ItemId, first.Name), first.BatchNumber,
|
||||
expiry, g.Sum(x => x.Quantity), expiry.Date < today ? "Expired" : "NearExpiry");
|
||||
})
|
||||
.OrderBy(b => b.ExpiryDate)
|
||||
.ToList();
|
||||
|
||||
// ── Movements ──────────────────────────────────────────────────────
|
||||
var unitsIn = movements.Where(x => x.Direction == "In").Sum(x => x.Row.Quantity);
|
||||
var unitsOut = movements.Where(x => x.Direction == "Out").Sum(x => x.Row.Quantity);
|
||||
|
||||
var perDay = movements
|
||||
.GroupBy(x => x.Row.CreatedAt.Date)
|
||||
.ToDictionary(g => g.Key, g => (
|
||||
In: g.Where(x => x.Direction == "In").Sum(x => x.Row.Quantity),
|
||||
Out: g.Where(x => x.Direction == "Out").Sum(x => x.Row.Quantity)));
|
||||
var daily = new List<DashboardDailyPoint>();
|
||||
for (var d = fromDate; d <= toDate; d = d.AddDays(1))
|
||||
{
|
||||
perDay.TryGetValue(d, out var p);
|
||||
daily.Add(new DashboardDailyPoint(d.ToString("yyyy-MM-dd"), p.In, p.Out));
|
||||
}
|
||||
|
||||
var byType = movements
|
||||
.GroupBy(x => (x.Row.Type, x.Direction))
|
||||
.Select(g => new DashboardMovementTypeRow(g.Key.Type, g.Key.Direction!, g.Sum(x => x.Row.Quantity), g.Count()))
|
||||
.OrderByDescending(r => r.Units)
|
||||
.ToList();
|
||||
|
||||
var ledgerSource = movements.Take(LedgerSize).ToList();
|
||||
var locationIds = ledgerSource
|
||||
.SelectMany(x => new[]
|
||||
{
|
||||
(x.Row.FromLocationId, x.Row.FromLocationType),
|
||||
(x.Row.ToLocationId, x.Row.ToLocationType),
|
||||
})
|
||||
.Where(l => l.Item1.HasValue && l.Item2 is LocationType t && t != LocationType.External)
|
||||
.Select(l => l.Item1!.Value);
|
||||
var locationNames = await repo.GetLocationNamesAsync(locationIds);
|
||||
var userNames = await repo.GetUserNamesAsync(ledgerSource.Select(x => x.Row.CreatedBy));
|
||||
|
||||
var ledger = ledgerSource.Select(x => new DashboardLedgerRow(
|
||||
x.Row.Id, x.Row.CreatedAt, x.Row.ItemId, x.Row.ItemName, x.Row.Sku,
|
||||
x.Row.Type, x.Direction!, x.Row.Quantity,
|
||||
SideName(x.Row.Type, x.Row.FromLocationId, x.Row.FromLocationType, locationNames, isFrom: true),
|
||||
SideName(x.Row.Type, x.Row.ToLocationId, x.Row.ToLocationType, locationNames, isFrom: false),
|
||||
x.Row.ReferenceId, ReferenceKind(x.Row.Type),
|
||||
x.Row.ReasonCode, userNames.GetValueOrDefault(x.Row.CreatedBy, "—"))).ToList();
|
||||
|
||||
// ── Inbound (GRN) ──────────────────────────────────────────────────
|
||||
var grnTotals = await repo.GetGrnTotalsAsync(id, fromDate, toExclusive);
|
||||
var received = grnTotals.Accepted + grnTotals.Rejected;
|
||||
var inbound = new DashboardInbound(
|
||||
grnTotals.Count, grnTotals.Accepted, grnTotals.Rejected,
|
||||
received == 0 ? 0m : Math.Round(grnTotals.Rejected * 100m / received, 1),
|
||||
await repo.GetRecentGrnsAsync(id, fromDate, toExclusive, RecentDocumentCount));
|
||||
|
||||
// ── Outbound (loading sheets) ──────────────────────────────────────
|
||||
var outbound = new DashboardOutbound(
|
||||
movements.Where(x => x.Row.Type == MovementType.Load && x.Direction == "Out").Sum(x => x.Row.Quantity),
|
||||
await repo.GetPendingDispatchUnitsAsync(id),
|
||||
await repo.GetRecentLoadingSheetsAsync(id, fromDate, toExclusive, RecentDocumentCount));
|
||||
|
||||
// ── Vehicles ───────────────────────────────────────────────────────
|
||||
var vehicleStock = (await repo.GetVehicleStockAsync(id, warehouse.VehicleId))
|
||||
.Select(v => new DashboardVehicle(v.VehicleId, v.RegNumber, v.NumberPlate, v.IsLinked, v.Units, v.Skus))
|
||||
.ToList();
|
||||
var linkedVehicles = await repo.GetDefaultVehiclesAsync(id, warehouse.VehicleId);
|
||||
|
||||
// ── Returns and losses ─────────────────────────────────────────────
|
||||
decimal Units(MovementType type, string direction) =>
|
||||
movements.Where(x => x.Row.Type == type && x.Direction == direction).Sum(x => x.Row.Quantity);
|
||||
var losses = new DashboardLosses(
|
||||
Units(MovementType.PurchaseReturn, "Out"), Units(MovementType.Damage, "Out"),
|
||||
Units(MovementType.Adjustment, "Out"), Units(MovementType.Adjustment, "In"),
|
||||
await repo.GetOpenClaimCountAsync(id),
|
||||
await repo.GetOpenClaimsAsync(id, RecentDocumentCount));
|
||||
|
||||
// ── Top movers and dead stock ──────────────────────────────────────
|
||||
var topMovers = movements
|
||||
.Where(x => x.Row.Type == MovementType.Load && x.Direction == "Out")
|
||||
.GroupBy(x => x.Row.ItemId)
|
||||
.Select(g => new DashboardTopMover(g.Key, g.First().Row.Sku, g.First().Row.ItemName, g.Sum(x => x.Row.Quantity)))
|
||||
.OrderByDescending(m => m.UnitsOut)
|
||||
.Take(TopListSize)
|
||||
.ToList();
|
||||
|
||||
var deadCutoff = now.AddDays(-deadStockDays);
|
||||
var deadStock = stock
|
||||
.Where(r => r.Quantity > 0 && r.UpdatedAt < deadCutoff
|
||||
&& (!lastOutbound.TryGetValue(r.ItemId, out var last) || last < deadCutoff))
|
||||
.OrderByDescending(r => r.CostValue)
|
||||
.Take(DeadStockListSize)
|
||||
.Select(r => new DashboardDeadStock(
|
||||
r.ItemId, r.Sku, r.Name, r.Quantity, r.CostValue,
|
||||
lastOutbound.TryGetValue(r.ItemId, out var last) ? last : null))
|
||||
.ToList();
|
||||
|
||||
var kpis = new DashboardKpis(
|
||||
stock.Sum(r => r.CostValue), stock.Sum(r => r.SaleValue),
|
||||
stock.Where(r => r.Quantity > 0).Sum(r => r.Quantity), stock.Count(r => r.Quantity > 0),
|
||||
stock.Count(r => r.Status == "Low"), stock.Count(r => r.Status == "Out"),
|
||||
expiring.Count(b => b.Status == "NearExpiry"), expiring.Count(b => b.Status == "Expired"),
|
||||
unitsIn, unitsOut);
|
||||
|
||||
return new WarehouseDashboardResponse(
|
||||
new WarehouseResponse(warehouse.Id, warehouse.Name, warehouse.Location, warehouse.Type,
|
||||
warehouse.VehicleId, warehouse.IsActive, warehouse.CreatedAt),
|
||||
linkedVehicles, new DashboardPeriod(fromDate, toDate), deadStockDays, kpis,
|
||||
stock, stockByCategory, expiring, daily, byType, ledger,
|
||||
inbound, outbound, vehicleStock, losses, topMovers, deadStock);
|
||||
}
|
||||
|
||||
private static DashboardStockRow BuildStockRow(List<WarehouseBalanceRow> rows)
|
||||
{
|
||||
var first = rows[0];
|
||||
var quantity = rows.Sum(r => r.Quantity);
|
||||
var billable = Math.Max(quantity, 0m);
|
||||
var status = quantity <= 0 ? "Out"
|
||||
: first.ReorderLevel.HasValue && quantity < first.ReorderLevel.Value ? "Low"
|
||||
: "OK";
|
||||
var batches = rows
|
||||
.Where(r => r.BatchId.HasValue && r.Quantity > 0)
|
||||
.GroupBy(r => r.BatchId!.Value)
|
||||
.Select(g => new DashboardBatch(g.Key, g.First().BatchNumber, g.First().ExpiryDate, g.Sum(r => r.Quantity)))
|
||||
.OrderBy(b => b.ExpiryDate ?? DateTime.MaxValue)
|
||||
.ToList();
|
||||
|
||||
return new DashboardStockRow(
|
||||
first.ItemId, first.Sku, first.Name, first.Category, first.Brand, first.Uom,
|
||||
quantity, first.ReorderLevel, status,
|
||||
billable * (first.CostPrice ?? 0m), billable * (first.SalePrice ?? 0m),
|
||||
rows.Max(r => r.UpdatedAt), batches);
|
||||
}
|
||||
|
||||
private static string? DirectionOf(WarehouseMovementRow m, Guid warehouseId) =>
|
||||
m.ToLocationId == warehouseId && m.FromLocationId != warehouseId ? "In"
|
||||
: m.FromLocationId == warehouseId && m.ToLocationId != warehouseId ? "Out"
|
||||
: null;
|
||||
|
||||
private static string? ReferenceKind(MovementType type) => type switch
|
||||
{
|
||||
MovementType.PurchaseReceipt => "GRN",
|
||||
MovementType.Load => "Loading Sheet",
|
||||
MovementType.PurchaseReturn => "Purchase Return",
|
||||
_ => null,
|
||||
};
|
||||
|
||||
private static string SideName(
|
||||
MovementType type, Guid? id, LocationType? locationType, Dictionary<Guid, string> names, bool isFrom)
|
||||
{
|
||||
if (id is not Guid locId || locationType is not LocationType lt) return "—";
|
||||
if (lt != LocationType.External) return names.GetValueOrDefault(locId, "—");
|
||||
return type switch
|
||||
{
|
||||
MovementType.Sale or MovementType.CustomerReturn => "Customer",
|
||||
MovementType.PurchaseReceipt or MovementType.PurchaseReturn => "Supplier",
|
||||
MovementType.Damage => "Written Off",
|
||||
MovementType.Adjustment => "Stock Adjustment",
|
||||
_ => isFrom ? "External Source" : "External Destination",
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -98,6 +98,12 @@ namespace Distribution.Migrations
|
||||
table: "DeliveryTasks",
|
||||
newName: "FK_ShopPOId");
|
||||
|
||||
// MariaDB drops the ascii collation on CHANGE COLUMN; restore it so the FK to ShopPOs.Id can be created.
|
||||
migrationBuilder.Sql("ALTER TABLE `DeliveryTasks` MODIFY `FK_ShopPOId` char(36) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL;");
|
||||
migrationBuilder.Sql("ALTER TABLE `SchemeRedemptions` MODIFY `FK_ShopPOId` char(36) CHARACTER SET ascii COLLATE ascii_general_ci NULL;");
|
||||
migrationBuilder.Sql("ALTER TABLE `SaleReturns` MODIFY `FK_ShopPOId` char(36) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL;");
|
||||
migrationBuilder.Sql("ALTER TABLE `Payments` MODIFY `FK_InvoiceId` char(36) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL;");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "IX_DeliveryTasks_FK_OrderId",
|
||||
table: "DeliveryTasks",
|
||||
|
||||
+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,814 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
|
||||
|
||||
namespace Distribution.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class GlIntegration : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "GlAccountCode",
|
||||
table: "BankAccounts",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "GlPostingLogs",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
Event = table.Column<string>(type: "varchar(60)", maxLength: 60, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
SourceId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
Reference = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Status = table.Column<int>(type: "int", nullable: false),
|
||||
PayloadJson = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
GlJournalNo = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Error = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Attempts = table.Column<int>(type: "int", nullable: false),
|
||||
LastAttemptAt = 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_GlPostingLogs", x => x.Id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "BankAccounts",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("11111111-1111-1111-1111-000000000001"),
|
||||
column: "GlAccountCode",
|
||||
value: null);
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "Permissions",
|
||||
columns: new[] { "Id", "Code", "CreatedAt", "Description", "IsActive" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ new Guid("20000000-0000-0000-0000-000000000051"), "finance-reports.view", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "View financial reports (trial balance, P&L, balance sheet, cash flow...)", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000052"), "gl.manage", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Set up the General Ledger link and retry failed postings", 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("52dedbb8-6251-4131-82de-41c2064a663b"));
|
||||
|
||||
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("47450af3-cf95-4725-a208-eeca124022b7"));
|
||||
|
||||
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("44deed45-fc6f-420c-a026-f4483efab782"));
|
||||
|
||||
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("12ae0586-2ff1-4333-92be-9e4660d0f34d"));
|
||||
|
||||
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("cd258515-3abd-448f-9972-1b9ae9a953d8"));
|
||||
|
||||
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("9d2dcbbe-cf49-4842-bbcd-1ed0c3bf5f05"));
|
||||
|
||||
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("16ad892c-b785-4872-a426-1015f32c90b9"));
|
||||
|
||||
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("3ac5579e-2ba0-44e2-b194-e8a34c91d831"));
|
||||
|
||||
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("498230b5-7d98-4826-afaf-944123658f69"));
|
||||
|
||||
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("e960268d-ad0e-4cdc-a387-e2b4fa4ede47"));
|
||||
|
||||
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("61b598f0-1138-4733-8dac-b84911b1ac18"));
|
||||
|
||||
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("91ab95eb-c25b-44f9-be90-9b0ffeba4e7a"));
|
||||
|
||||
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("96ffe8c1-2d62-4d56-8933-02bc4f79972e"));
|
||||
|
||||
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("1f38d01e-e333-40cd-811c-cc1bcaaaa97b"));
|
||||
|
||||
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("8c768b0a-0920-4b28-b48a-3f62ba8a16d5"));
|
||||
|
||||
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("7a87ddf5-e185-4978-9390-09388ab1b264"));
|
||||
|
||||
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("86229744-f68d-4e7e-9b07-2aad58584331"));
|
||||
|
||||
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("646c3d0c-801a-4382-8a50-3a8d57d3225e"));
|
||||
|
||||
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("e2b513a0-5fae-4642-8bf5-c2e3dc734aca"));
|
||||
|
||||
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("59ce3bc1-2466-4090-97fe-9ae81d718a83"));
|
||||
|
||||
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("48772797-f181-423d-bfb3-f6226a9abe11"));
|
||||
|
||||
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("e99740a5-b681-4b6b-9a42-754ca3114e00"));
|
||||
|
||||
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("a1b180a1-fbc5-4f18-aeaa-82b443a4fc3f"));
|
||||
|
||||
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("83070521-f8f2-4b54-ae59-8cd2972de2a1"));
|
||||
|
||||
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("132b8143-0836-4a3b-b5b3-90cdbe6df12d"));
|
||||
|
||||
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("9ad2e440-e355-41c8-9e9e-00a78de66abb"));
|
||||
|
||||
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("b76e221a-fb92-43a3-a723-dcd5fa3327f6"));
|
||||
|
||||
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("8f63c426-38bb-456d-adff-75e477857607"));
|
||||
|
||||
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("e4a559d1-bc00-438b-b636-a85803a7ef02"));
|
||||
|
||||
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("d0a1ab21-0809-4605-a1d2-29e546cb4ae8"));
|
||||
|
||||
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("1e3fb35b-0de5-4d15-860f-51e0c9ec1bb1"));
|
||||
|
||||
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("12e98296-456e-40a3-b11c-61d3393b335e"));
|
||||
|
||||
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("1d47dc32-9faa-418e-8241-a0dc8a32c974"));
|
||||
|
||||
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("5e701ad5-f462-4642-86d1-303148323a32"));
|
||||
|
||||
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("52dfa802-8a12-4554-b7ab-1e286998b3b9"));
|
||||
|
||||
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("7944f075-4eb3-4ccd-98bf-584f79f9f4d5"));
|
||||
|
||||
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("6ce15356-7240-4a79-bfc8-340c8f31e270"));
|
||||
|
||||
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("ac248603-3c71-4c1b-84dc-174f25cade65"));
|
||||
|
||||
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("7805ec5e-8b99-4b45-b47a-be01405a2151"));
|
||||
|
||||
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("7ea67b95-2918-48e9-b569-71123adb82a0"));
|
||||
|
||||
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("a2515783-f099-4332-a07c-be81f2e85160"));
|
||||
|
||||
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("2bff0498-d66e-4564-b0ec-5924cbab96a3"));
|
||||
|
||||
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("5fa95034-9d7f-4350-b754-9a16b03804fe"));
|
||||
|
||||
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("f1a82d54-68b8-4992-a4ba-28bd58dd5281"));
|
||||
|
||||
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("fa183267-8eb0-4831-8f5f-ba1af4d5a78d"));
|
||||
|
||||
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("fd75e8b3-8751-4b67-870b-6370098a5e1e"));
|
||||
|
||||
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("c6632177-d567-4cce-98dd-80d735c2e5db"));
|
||||
|
||||
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("b463deda-4bb9-4d86-a0fc-513d10fc57b1"));
|
||||
|
||||
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("b44bfcb4-ff1d-4848-9098-1ae54bace236"));
|
||||
|
||||
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("93c036e4-d811-4915-b73c-1cb358f17065"));
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "RolePermissions",
|
||||
columns: new[] { "FK_PermissionId", "FK_RoleId", "CreatedAt", "Id", "IsActive" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ new Guid("20000000-0000-0000-0000-000000000051"), new Guid("10000000-0000-0000-0000-000000000007"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("9c27d325-1915-41c6-9b62-d85a12f102e1"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000052"), new Guid("10000000-0000-0000-0000-000000000007"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("1f48dd7b-2f14-451d-b80d-71579d5625e6"), true }
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_GlPostingLogs_Event_SourceId",
|
||||
table: "GlPostingLogs",
|
||||
columns: new[] { "Event", "SourceId" },
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "GlPostingLogs");
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000051"), new Guid("10000000-0000-0000-0000-000000000007") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000052"), new Guid("10000000-0000-0000-0000-000000000007") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000051"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000052"));
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "GlAccountCode",
|
||||
table: "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("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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,20 @@
|
||||
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; }
|
||||
|
||||
/// <summary>Code of the mirrored General Ledger account (child of GL Cash in Hand / Bank Accounts). Null until synced.</summary>
|
||||
public string? GlAccountCode { 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; }
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,8 @@ namespace Distribution.Models
|
||||
|
||||
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 +51,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 }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public enum GlPostingStatus { Pending, Posted, Failed }
|
||||
|
||||
/// <summary>
|
||||
/// One row per journal entry Distribution sends to the General Ledger service. (Event, SourceId) is unique,
|
||||
/// so a business event can never be posted twice; Failed rows keep their payload and can be retried.
|
||||
/// </summary>
|
||||
public class GlPostingLog : Template
|
||||
{
|
||||
public string Event { get; set; } = string.Empty;
|
||||
public Guid SourceId { get; set; }
|
||||
public string Reference { get; set; } = string.Empty;
|
||||
public GlPostingStatus Status { get; set; } = GlPostingStatus.Pending;
|
||||
|
||||
/// <summary>The journal entry body (without periodId, which is resolved from EntryDate at send time).</summary>
|
||||
public string PayloadJson { get; set; } = string.Empty;
|
||||
|
||||
public string? GlJournalNo { get; set; }
|
||||
public string? Error { get; set; }
|
||||
public int Attempts { get; set; }
|
||||
public DateTime? LastAttemptAt { 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; }
|
||||
}
|
||||
}
|
||||
@@ -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; } = [];
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user