Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e3e331a2bb | |||
| 4e73dd7fb1 | |||
| e88668beca | |||
| c1961ebea2 | |||
| cf9d2c2661 | |||
| 429a80b1ed | |||
| f95fda3806 | |||
| d2390e7b12 |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Server=187.127.102.190;Port=3306;Database=distribution_db;User=kalana;Password=kalana@hexdive"
|
||||
"DefaultConnection": "Server=187.127.102.190;Port=3306;Database=distribution;User=kalana;Password=kalana@hexdive"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
|
||||
@@ -13,7 +13,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("Distribution.Tests")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+397af8fd597bb7dd2d4372bf717d06c8e49552b7")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+aa7ea37ab61a149a38d83283c5dabd76bf5a39b3")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("Distribution.Tests")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("Distribution.Tests")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -1 +1 @@
|
||||
569664876b2d87d1ec65add142012e5eae24d860ed90b6cc736c1cb4bae13b9a
|
||||
5c052f1f25bc37b6a51a3abe0e2b79d99d21422d5ab89151faea450c0ddb9cc0
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -27,6 +27,9 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
|
||||
public DbSet<UserRegionScope> UserRegionScopes { get; set; }
|
||||
public DbSet<UserRouteScope> UserRouteScopes { get; set; }
|
||||
public DbSet<UserStoreScope> UserStoreScopes { get; set; }
|
||||
public DbSet<StockBalance> StockBalances { get; set; }
|
||||
public DbSet<StockMovement> StockMovements { get; set; }
|
||||
public DbSet<StoreKycDocument> StoreKycDocuments { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
@@ -51,6 +54,10 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
|
||||
.HasForeignKey(u => u.FK_ReportsToUserId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
modelBuilder.Entity<StockBalance>()
|
||||
.HasIndex(sb => new { sb.FK_ItemId, sb.LocationId })
|
||||
.IsUnique();
|
||||
|
||||
Seed.RoleAndPermissionSeed.Apply(modelBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ public static class RoleAndPermissionSeed
|
||||
("stores.create", "Create stores"),
|
||||
("stores.update", "Update stores"),
|
||||
("stores.delete", "Delete stores"),
|
||||
("stores.kyc.verify", "Verify or reject store KYC documents"),
|
||||
("stores.credit.update", "Update store credit limit and outstanding balance"),
|
||||
|
||||
("warehouses.view", "View warehouses"),
|
||||
("warehouses.create", "Create warehouses"),
|
||||
@@ -98,14 +100,14 @@ public static class RoleAndPermissionSeed
|
||||
var itemPerms = new[] { "items.view", "items.create", "items.update", "items.delete", "categories.view", "categories.create", "categories.update", "categories.delete" };
|
||||
|
||||
var salesRepPerms = new[] { "orders.view", "orders.create", "orders.update", "stores.view", "routes.view" }.Concat(itemViewPerms).ToArray();
|
||||
var supervisorPerms = salesRepPerms.Concat(["orders.delete", "stores.create", "stores.update"]).ToArray();
|
||||
var areaManagerPerms = supervisorPerms.Concat(["vehicles.view", "warehouses.view"]).Concat(itemManagePerms).Distinct().ToArray();
|
||||
var areaSupervisorPerms = areaManagerPerms.Concat(["routes.create", "routes.update"]).Concat(itemPerms).Distinct().ToArray();
|
||||
var supervisorPerms = salesRepPerms.Concat(["orders.delete", "stores.create", "stores.update", "stores.credit.update"]).ToArray();
|
||||
var areaManagerPerms = supervisorPerms.Concat(["vehicles.view", "warehouses.view", "stores.kyc.verify"]).Concat(itemManagePerms).Distinct().ToArray();
|
||||
var areaSupervisorPerms = areaManagerPerms.Concat(["routes.create", "routes.update", "stores.delete"]).Concat(itemPerms).Distinct().ToArray();
|
||||
|
||||
var regionalManagerPerms = new[]
|
||||
{
|
||||
"orders.view", "orders.create", "orders.update", "orders.delete",
|
||||
"stores.view", "stores.create", "stores.update", "stores.delete",
|
||||
"stores.view", "stores.create", "stores.update", "stores.delete", "stores.kyc.verify", "stores.credit.update",
|
||||
"routes.view", "routes.create", "routes.update", "routes.delete",
|
||||
"vehicles.view", "vehicles.create", "vehicles.update", "vehicles.delete",
|
||||
"warehouses.view", "warehouses.create", "warehouses.update", "warehouses.delete",
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
using Distribution.Features.Store;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Infra.UoW;
|
||||
using Distribution.Models;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Distribution.Features.Order;
|
||||
|
||||
public class OrderService(IOrderRepository repo, IUnitOfWork uow, ICurrentUserService currentUser, IScopeAccessService scopeAccess)
|
||||
public class OrderService(
|
||||
IOrderRepository repo,
|
||||
IStoreRepository storeRepo,
|
||||
IUnitOfWork uow,
|
||||
ICurrentUserService currentUser,
|
||||
IScopeAccessService scopeAccess,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
private const int GlobalScopeRoleLevel = 50;
|
||||
|
||||
@@ -63,6 +72,14 @@ public class OrderService(IOrderRepository repo, IUnitOfWork uow, ICurrentUserSe
|
||||
{
|
||||
if (!await HasStoreScopeAsync(request.StoreId)) return null;
|
||||
|
||||
var store = await storeRepo.GetByIdAsync(request.StoreId);
|
||||
if (store is null || !store.IsActive) return null;
|
||||
if (store.KycStatus != KycStatus.Verified)
|
||||
{
|
||||
var threshold = configuration.GetValue<decimal>("StoreManagement:UnverifiedKycOrderThreshold");
|
||||
if (request.TotalAmount > threshold) return null;
|
||||
}
|
||||
|
||||
var order = new Models.Order
|
||||
{
|
||||
FK_StoreId = request.StoreId,
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.Stock;
|
||||
|
||||
public interface IStockRepository
|
||||
{
|
||||
// Balance
|
||||
Task<StockBalance?> GetBalanceAsync(Guid itemId, Guid locationId);
|
||||
Task<List<StockBalance>> GetBalancesAsync(Guid? locationId, LocationType? locationType, Guid? itemId, int page, int pageSize);
|
||||
Task<int> GetBalancesCountAsync(Guid? locationId, LocationType? locationType, Guid? itemId);
|
||||
Task<List<StockBalance>> GetBalancesByItemAsync(Guid itemId);
|
||||
Task AddBalanceAsync(StockBalance balance);
|
||||
void UpdateBalance(StockBalance balance);
|
||||
|
||||
// Movements
|
||||
Task AddMovementAsync(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);
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Stock;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/stock")]
|
||||
[Authorize]
|
||||
public class StockController(StockService service, ICurrentUserService currentUser) : ControllerBase
|
||||
{
|
||||
// ── Query ──────────────────────────────────────────────────────────────
|
||||
|
||||
[HttpGet("balances")]
|
||||
public async Task<IActionResult> GetBalances(
|
||||
[FromQuery] Guid? locationId,
|
||||
[FromQuery] LocationType? locationType,
|
||||
[FromQuery] Guid? itemId,
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 20)
|
||||
{
|
||||
var result = await service.GetBalancesAsync(locationId, locationType, itemId, page, pageSize);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("balances/{itemId}/total")]
|
||||
public async Task<IActionResult> GetItemTotal(Guid itemId)
|
||||
{
|
||||
var result = await service.GetItemTotalAsync(itemId);
|
||||
if (result is null) return NotFound();
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("movements")]
|
||||
public async Task<IActionResult> GetMovements(
|
||||
[FromQuery] Guid? locationId,
|
||||
[FromQuery] Guid? itemId,
|
||||
[FromQuery] MovementType? type,
|
||||
[FromQuery] DateTime? from,
|
||||
[FromQuery] DateTime? to,
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 20)
|
||||
{
|
||||
var result = await service.GetMovementsAsync(locationId, itemId, type, from, to, page, pageSize);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
// ── Write ──────────────────────────────────────────────────────────────
|
||||
|
||||
[HttpPost("transfer")]
|
||||
public async Task<IActionResult> Transfer(TransferRequest request, CancellationToken ct)
|
||||
{
|
||||
if (currentUser.UserId is null) return Unauthorized();
|
||||
try
|
||||
{
|
||||
var result = await service.TransferAsync(request, currentUser.UserId.Value, ct);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (InvalidOperationException ex) { return BadRequest(ex.Message); }
|
||||
}
|
||||
|
||||
[HttpPost("damage")]
|
||||
public async Task<IActionResult> RecordDamage(DamageRequest request, CancellationToken ct)
|
||||
{
|
||||
if (currentUser.UserId is null) return Unauthorized();
|
||||
try
|
||||
{
|
||||
var result = await service.RecordDamageAsync(request, currentUser.UserId.Value, ct);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (InvalidOperationException ex) { return BadRequest(ex.Message); }
|
||||
}
|
||||
|
||||
[HttpPost("customer-return")]
|
||||
public async Task<IActionResult> RecordCustomerReturn(CustomerReturnRequest request, CancellationToken ct)
|
||||
{
|
||||
if (currentUser.UserId is null) return Unauthorized();
|
||||
try
|
||||
{
|
||||
var result = await service.RecordCustomerReturnAsync(request, currentUser.UserId.Value, ct);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (InvalidOperationException ex) { return BadRequest(ex.Message); }
|
||||
}
|
||||
|
||||
[HttpPost("adjustment")]
|
||||
public async Task<IActionResult> Adjust(AdjustmentRequest request, CancellationToken ct)
|
||||
{
|
||||
if (currentUser.UserId is null) return Unauthorized();
|
||||
try
|
||||
{
|
||||
var result = await service.AdjustAsync(request, currentUser.UserId.Value, ct);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (InvalidOperationException ex) { return BadRequest(ex.Message); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.Stock;
|
||||
|
||||
// Shared pagination wrapper
|
||||
public record PagedResponse<T>(int Page, int PageSize, int Total, List<T> Items);
|
||||
|
||||
// Balance
|
||||
public record StockBalanceResponse(
|
||||
Guid ItemId,
|
||||
string ItemName,
|
||||
Guid LocationId,
|
||||
LocationType LocationType,
|
||||
decimal Quantity,
|
||||
DateTime UpdatedAt);
|
||||
|
||||
public record StockTotalResponse(
|
||||
Guid ItemId,
|
||||
string ItemName,
|
||||
decimal TotalQuantity,
|
||||
List<StockBalanceResponse> Breakdown);
|
||||
|
||||
// Movement
|
||||
public record StockMovementResponse(
|
||||
Guid Id,
|
||||
Guid ItemId,
|
||||
string ItemName,
|
||||
Guid? FromLocationId,
|
||||
LocationType? FromLocationType,
|
||||
Guid? ToLocationId,
|
||||
LocationType? ToLocationType,
|
||||
decimal Quantity,
|
||||
MovementType Type,
|
||||
string? ReasonCode,
|
||||
Guid? ReferenceId,
|
||||
DateTime CreatedAt,
|
||||
Guid CreatedBy);
|
||||
|
||||
// Write requests
|
||||
public record TransferRequest(
|
||||
Guid ItemId,
|
||||
Guid FromLocationId,
|
||||
LocationType FromLocationType,
|
||||
Guid ToLocationId,
|
||||
LocationType ToLocationType,
|
||||
decimal Quantity,
|
||||
Guid? ReferenceId);
|
||||
|
||||
public record DamageRequest(
|
||||
Guid ItemId,
|
||||
Guid LocationId,
|
||||
LocationType LocationType,
|
||||
decimal Quantity,
|
||||
string ReasonCode);
|
||||
|
||||
public record CustomerReturnRequest(
|
||||
Guid ItemId,
|
||||
Guid VehicleId,
|
||||
decimal Quantity,
|
||||
Guid? ReferenceId);
|
||||
|
||||
public record AdjustmentRequest(
|
||||
Guid ItemId,
|
||||
Guid LocationId,
|
||||
LocationType LocationType,
|
||||
decimal Quantity,
|
||||
string ReasonCode);
|
||||
@@ -0,0 +1,70 @@
|
||||
using Distribution.Data;
|
||||
using Distribution.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
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);
|
||||
|
||||
public Task<List<StockBalance>> GetBalancesAsync(Guid? locationId, LocationType? locationType, Guid? itemId, int page, int pageSize)
|
||||
{
|
||||
var query = db.StockBalances.Include(b => b.Item).AsQueryable();
|
||||
if (locationId.HasValue) query = query.Where(b => b.LocationId == locationId.Value);
|
||||
if (locationType.HasValue) query = query.Where(b => b.LocationType == locationType.Value);
|
||||
if (itemId.HasValue) query = query.Where(b => b.FK_ItemId == itemId.Value);
|
||||
return query.Skip((page - 1) * pageSize).Take(pageSize).ToListAsync();
|
||||
}
|
||||
|
||||
public Task<int> GetBalancesCountAsync(Guid? locationId, LocationType? locationType, Guid? itemId)
|
||||
{
|
||||
var query = db.StockBalances.AsQueryable();
|
||||
if (locationId.HasValue) query = query.Where(b => b.LocationId == locationId.Value);
|
||||
if (locationType.HasValue) query = query.Where(b => b.LocationType == locationType.Value);
|
||||
if (itemId.HasValue) query = query.Where(b => b.FK_ItemId == itemId.Value);
|
||||
return query.CountAsync();
|
||||
}
|
||||
|
||||
public Task<List<StockBalance>> GetBalancesByItemAsync(Guid itemId) =>
|
||||
db.StockBalances
|
||||
.Include(b => b.Item)
|
||||
.Where(b => b.FK_ItemId == itemId)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task AddBalanceAsync(StockBalance balance) =>
|
||||
await db.StockBalances.AddAsync(balance);
|
||||
|
||||
public void UpdateBalance(StockBalance balance) =>
|
||||
db.StockBalances.Update(balance);
|
||||
|
||||
public async Task AddMovementAsync(StockMovement movement) =>
|
||||
await db.StockMovements.AddAsync(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)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
using Distribution.Features.Category;
|
||||
using Distribution.Features.Item;
|
||||
using Distribution.Infra.UoW;
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.Stock;
|
||||
|
||||
public class StockService(IStockRepository repo, IItemRepository itemRepo, ICategoryRepository categoryRepo, IUnitOfWork uow)
|
||||
{
|
||||
private static StockBalanceResponse MapBalance(StockBalance b) =>
|
||||
new(b.FK_ItemId, b.Item.Name, b.LocationId, b.LocationType, b.Quantity, b.UpdatedAt);
|
||||
|
||||
private static StockMovementResponse MapMovement(StockMovement m) =>
|
||||
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);
|
||||
|
||||
// ── Queries ────────────────────────────────────────────────────────────
|
||||
|
||||
public async Task<PagedResponse<StockBalanceResponse>> GetBalancesAsync(
|
||||
Guid? locationId, LocationType? locationType, Guid? itemId, int page, int pageSize)
|
||||
{
|
||||
var items = await repo.GetBalancesAsync(locationId, locationType, itemId, page, pageSize);
|
||||
var total = await repo.GetBalancesCountAsync(locationId, locationType, itemId);
|
||||
return new PagedResponse<StockBalanceResponse>(page, pageSize, total, items.Select(MapBalance).ToList());
|
||||
}
|
||||
|
||||
public async Task<StockTotalResponse?> GetItemTotalAsync(Guid itemId)
|
||||
{
|
||||
var item = await itemRepo.GetByIdAsync(itemId);
|
||||
if (item is null || !item.IsActive) return null;
|
||||
|
||||
var balances = await repo.GetBalancesByItemAsync(itemId);
|
||||
var total = balances.Sum(b => b.Quantity);
|
||||
return new StockTotalResponse(itemId, item.Name, total, balances.Select(MapBalance).ToList());
|
||||
}
|
||||
|
||||
public async Task<PagedResponse<StockMovementResponse>> GetMovementsAsync(
|
||||
Guid? locationId, Guid? itemId, MovementType? type, DateTime? from, DateTime? to, int page, int pageSize)
|
||||
{
|
||||
var items = await repo.GetMovementsAsync(locationId, itemId, type, from, to, page, pageSize);
|
||||
var total = await repo.GetMovementsCountAsync(locationId, itemId, type, from, to);
|
||||
return new PagedResponse<StockMovementResponse>(page, pageSize, total, items.Select(MapMovement).ToList());
|
||||
}
|
||||
|
||||
// ── Writes ─────────────────────────────────────────────────────────────
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
await ApplyTransferAsync(request.ItemId, request.FromLocationId, request.ToLocationId, request.ToLocationType, request.Quantity);
|
||||
await repo.AddMovementAsync(movement);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return MapMovement(movement);
|
||||
}
|
||||
|
||||
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,
|
||||
Quantity = request.Quantity,
|
||||
Type = MovementType.Damage,
|
||||
ReasonCode = request.ReasonCode,
|
||||
CreatedBy = userId
|
||||
};
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
await DebitBalanceAsync(request.ItemId, request.LocationId, request.Quantity);
|
||||
await repo.AddMovementAsync(movement);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return MapMovement(movement);
|
||||
}
|
||||
|
||||
public async Task<StockMovementResponse> RecordCustomerReturnAsync(CustomerReturnRequest request, Guid userId, CancellationToken ct = default)
|
||||
{
|
||||
var item = await itemRepo.GetByIdAsync(request.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}'.");
|
||||
|
||||
var movement = new StockMovement
|
||||
{
|
||||
FK_ItemId = request.ItemId,
|
||||
ToLocationId = request.VehicleId,
|
||||
ToLocationType = LocationType.Vehicle,
|
||||
Quantity = request.Quantity,
|
||||
Type = MovementType.CustomerReturn,
|
||||
ReferenceId = request.ReferenceId,
|
||||
CreatedBy = userId
|
||||
};
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
await CreditBalanceAsync(request.ItemId, request.VehicleId, LocationType.Vehicle, request.Quantity);
|
||||
await repo.AddMovementAsync(movement);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return MapMovement(movement);
|
||||
}
|
||||
|
||||
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 : null,
|
||||
FromLocationType = request.Quantity < 0 ? request.LocationType : null,
|
||||
ToLocationId = request.Quantity > 0 ? request.LocationId : null,
|
||||
ToLocationType = request.Quantity > 0 ? request.LocationType : null,
|
||||
Quantity = Math.Abs(request.Quantity),
|
||||
Type = MovementType.Adjustment,
|
||||
ReasonCode = request.ReasonCode,
|
||||
CreatedBy = userId
|
||||
};
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
if (balance is null)
|
||||
{
|
||||
await repo.AddBalanceAsync(new StockBalance
|
||||
{
|
||||
FK_ItemId = request.ItemId,
|
||||
LocationId = request.LocationId,
|
||||
LocationType = request.LocationType,
|
||||
Quantity = request.Quantity
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
balance.Quantity += request.Quantity;
|
||||
balance.UpdatedAt = DateTime.Now;
|
||||
repo.UpdateBalance(balance);
|
||||
}
|
||||
await repo.AddMovementAsync(movement);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return MapMovement(movement);
|
||||
}
|
||||
|
||||
// ── Helpers ────────────────────────────────────────────────────────────
|
||||
|
||||
private async Task EnsureStockAsync(Guid itemId, Guid locationId, decimal quantity)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
private async Task CreditBalanceAsync(Guid itemId, Guid locationId, LocationType locationType, decimal quantity)
|
||||
{
|
||||
var balance = await repo.GetBalanceAsync(itemId, locationId);
|
||||
if (balance is null)
|
||||
{
|
||||
await repo.AddBalanceAsync(new StockBalance
|
||||
{
|
||||
FK_ItemId = itemId,
|
||||
LocationId = locationId,
|
||||
LocationType = locationType,
|
||||
Quantity = quantity
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
balance.Quantity += quantity;
|
||||
balance.UpdatedAt = DateTime.Now;
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -7,4 +7,5 @@ public interface IStoreRepository
|
||||
Task AddAsync(Models.Store entity);
|
||||
void Update(Models.Store entity);
|
||||
void Remove(Models.Store entity);
|
||||
Task<List<Models.Order>> GetOrdersForDashboardAsync(Guid storeId, DateTime periodStart, DateTime periodEndExclusive);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Distribution.Features.Store.Kyc;
|
||||
|
||||
public interface IStoreKycDocumentRepository
|
||||
{
|
||||
Task<List<Models.StoreKycDocument>> GetByStoreIdAsync(Guid storeId);
|
||||
Task AddAsync(Models.StoreKycDocument entity);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Store.Kyc;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/stores/{storeId:guid}/kyc-documents")]
|
||||
[Authorize]
|
||||
public class StoreKycDocumentController(StoreKycDocumentService service) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
[Authorize(Policy = "stores.view")]
|
||||
public async Task<IActionResult> GetByStoreId(Guid storeId)
|
||||
{
|
||||
var result = await service.GetByStoreIdAsync(storeId);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = "stores.update")]
|
||||
public async Task<IActionResult> Upload(Guid storeId, [FromForm] UploadKycDocumentRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.UploadAsync(storeId, request, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace Distribution.Features.Store.Kyc;
|
||||
|
||||
public record UploadKycDocumentRequest(string DocumentType, IFormFile File);
|
||||
|
||||
public record StoreKycDocumentResponse(Guid Id, Guid StoreId, string DocumentType, string FileReference, DateTime UploadedAt);
|
||||
@@ -0,0 +1,13 @@
|
||||
using Distribution.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.Store.Kyc;
|
||||
|
||||
public class StoreKycDocumentRepository(AppDBContext context) : IStoreKycDocumentRepository
|
||||
{
|
||||
public async Task<List<Models.StoreKycDocument>> GetByStoreIdAsync(Guid storeId) =>
|
||||
await context.StoreKycDocuments.Where(d => d.FK_StoreId == storeId && d.IsActive).ToListAsync();
|
||||
|
||||
public async Task AddAsync(Models.StoreKycDocument entity) =>
|
||||
await context.StoreKycDocuments.AddAsync(entity);
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Infra.UoW;
|
||||
|
||||
namespace Distribution.Features.Store.Kyc;
|
||||
|
||||
public class StoreKycDocumentService(
|
||||
IStoreKycDocumentRepository repo,
|
||||
IStoreRepository storeRepo,
|
||||
IUnitOfWork uow,
|
||||
ICurrentUserService currentUser,
|
||||
IScopeAccessService scopeAccess,
|
||||
IWebHostEnvironment env)
|
||||
{
|
||||
private const int GlobalScopeRoleLevel = 50;
|
||||
|
||||
private bool HasGlobalScope => currentUser.RoleLevel >= GlobalScopeRoleLevel;
|
||||
|
||||
private async Task<bool> HasStoreScopeAsync(Guid storeId)
|
||||
{
|
||||
if (HasGlobalScope || currentUser.UserId is null) return HasGlobalScope;
|
||||
return await scopeAccess.HasStoreScopeAsync(currentUser.UserId.Value, storeId);
|
||||
}
|
||||
|
||||
private static StoreKycDocumentResponse ToResponse(Models.StoreKycDocument d) =>
|
||||
new(d.Id, d.FK_StoreId, d.DocumentType, d.FileReference, d.UploadedAt);
|
||||
|
||||
public async Task<List<StoreKycDocumentResponse>?> GetByStoreIdAsync(Guid storeId)
|
||||
{
|
||||
if (!await HasStoreScopeAsync(storeId)) return null;
|
||||
var docs = await repo.GetByStoreIdAsync(storeId);
|
||||
return docs.Select(ToResponse).ToList();
|
||||
}
|
||||
|
||||
public async Task<StoreKycDocumentResponse?> UploadAsync(Guid storeId, UploadKycDocumentRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var store = await storeRepo.GetByIdAsync(storeId);
|
||||
if (store is null || !store.IsActive) return null;
|
||||
if (!await HasStoreScopeAsync(storeId)) return null;
|
||||
|
||||
var uploadsRoot = Path.Combine(env.ContentRootPath, "wwwroot", "uploads", "kyc", storeId.ToString());
|
||||
Directory.CreateDirectory(uploadsRoot);
|
||||
var fileName = $"{Guid.NewGuid()}{Path.GetExtension(request.File.FileName)}";
|
||||
var filePath = Path.Combine(uploadsRoot, fileName);
|
||||
await using (var stream = new FileStream(filePath, FileMode.Create))
|
||||
{
|
||||
await request.File.CopyToAsync(stream, ct);
|
||||
}
|
||||
var fileReference = $"uploads/kyc/{storeId}/{fileName}";
|
||||
|
||||
var doc = new Models.StoreKycDocument
|
||||
{
|
||||
FK_StoreId = storeId,
|
||||
DocumentType = request.DocumentType,
|
||||
FileReference = fileReference,
|
||||
UploadedAt = DateTime.Now
|
||||
};
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
await repo.AddAsync(doc);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return ToResponse(doc);
|
||||
}
|
||||
}
|
||||
@@ -44,4 +44,68 @@ public class StoreController(StoreService service) : ControllerBase
|
||||
var deleted = await service.DeleteAsync(id, ct);
|
||||
return deleted ? NoContent() : NotFound();
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}/credit-limit")]
|
||||
[Authorize(Policy = "stores.credit.update")]
|
||||
public async Task<IActionResult> UpdateCreditLimit(Guid id, UpdateCreditLimitRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.UpdateCreditLimitAsync(id, request, ct);
|
||||
return result is null ? Forbid() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}/outstanding-balance")]
|
||||
[Authorize(Policy = "stores.credit.update")]
|
||||
public async Task<IActionResult> UpdateOutstandingBalance(Guid id, UpdateOutstandingBalanceRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.UpdateOutstandingBalanceAsync(id, request, ct);
|
||||
return result is null ? Forbid() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/outstanding-balance")]
|
||||
[Authorize(Policy = "stores.view")]
|
||||
public async Task<IActionResult> GetOutstandingBalance(Guid id)
|
||||
{
|
||||
var result = await service.GetOutstandingBalanceAsync(id);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}/kyc/verify")]
|
||||
[Authorize(Policy = "stores.kyc.verify")]
|
||||
public async Task<IActionResult> VerifyKyc(Guid id, VerifyKycRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.VerifyKycAsync(id, request, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}/segment")]
|
||||
[Authorize(Policy = "stores.update")]
|
||||
public async Task<IActionResult> ClassifySegment(Guid id, ClassifySegmentRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.ClassifySegmentAsync(id, request, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/visit-history")]
|
||||
[Authorize(Policy = "stores.view")]
|
||||
public async Task<IActionResult> GetVisitHistory(Guid id)
|
||||
{
|
||||
var result = await service.GetVisitHistoryAsync(id);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/schemes/{schemeId:guid}/eligibility")]
|
||||
[Authorize(Policy = "stores.view")]
|
||||
public async Task<IActionResult> CheckSchemeEligibility(Guid id, Guid schemeId)
|
||||
{
|
||||
var result = await service.CheckSchemeEligibilityAsync(id, schemeId);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/dashboard")]
|
||||
[Authorize(Policy = "stores.view")]
|
||||
public async Task<IActionResult> GetDashboard(Guid id, [FromQuery] string period)
|
||||
{
|
||||
var result = await service.GetDashboardAsync(id, period);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.Store;
|
||||
|
||||
public record CreateStoreRequest(
|
||||
@@ -30,5 +32,25 @@ public record StoreResponse(
|
||||
decimal Longitude,
|
||||
decimal OneTimeAmount,
|
||||
decimal CreditAmount,
|
||||
decimal CreditLimit,
|
||||
decimal OutstandingBalance,
|
||||
string? Segment,
|
||||
KycStatus KycStatus,
|
||||
bool IsActive,
|
||||
DateTime CreatedAt);
|
||||
|
||||
public record UpdateCreditLimitRequest(decimal NewLimit, Guid ApproverId);
|
||||
|
||||
public record UpdateOutstandingBalanceRequest(decimal NewBalance);
|
||||
|
||||
public record VerifyKycRequest(KycStatus Status, string? Notes);
|
||||
|
||||
public record ClassifySegmentRequest(string Segment);
|
||||
|
||||
public record OutstandingBalanceResponse(Guid StoreId, decimal CreditLimit, decimal OutstandingBalance, decimal AvailableCredit);
|
||||
|
||||
public record StoreVisitHistoryItem(Guid VisitId, DateTime VisitDate, Guid RepUserId, string Outcome);
|
||||
|
||||
public record SchemeEligibilityResponse(bool Eligible, string? Reason);
|
||||
|
||||
public record StoreDashboardResponse(Guid StoreId, string Period, int OrderCount, decimal TotalOrderValue, DateTime? LastOrderDate);
|
||||
|
||||
@@ -22,4 +22,9 @@ public class StoreRepository(AppDBContext context) : IStoreRepository
|
||||
entity.IsActive = false;
|
||||
context.Stores.Update(entity);
|
||||
}
|
||||
|
||||
public async Task<List<Models.Order>> GetOrdersForDashboardAsync(Guid storeId, DateTime periodStart, DateTime periodEndExclusive) =>
|
||||
await context.Orders
|
||||
.Where(o => o.FK_StoreId == storeId && o.IsActive && o.OrderDate >= periodStart && o.OrderDate < periodEndExclusive)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
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)
|
||||
{
|
||||
private const int GlobalScopeRoleLevel = 50;
|
||||
private const int CreditApproverRoleLevel = 20;
|
||||
|
||||
private bool HasGlobalScope => currentUser.RoleLevel >= GlobalScopeRoleLevel;
|
||||
|
||||
@@ -15,6 +17,10 @@ public class StoreService(IStoreRepository repo, IUnitOfWork uow, ICurrentUserSe
|
||||
return await scopeAccess.HasStoreScopeAsync(currentUser.UserId.Value, storeId);
|
||||
}
|
||||
|
||||
private static StoreResponse ToResponse(Models.Store s) =>
|
||||
new(s.Id, s.FK_RouteId, s.OwnerName, s.StoreName, s.StoreAddress, s.Latitude, s.Longitude, s.OneTimeAmount, s.CreditAmount,
|
||||
s.CreditLimit, s.OutstandingBalance, s.Segment, s.KycStatus, s.IsActive, s.CreatedAt);
|
||||
|
||||
public async Task<List<StoreResponse>> GetAllAsync()
|
||||
{
|
||||
var stores = (await repo.GetAllAsync()).Where(s => s.IsActive).ToList();
|
||||
@@ -23,7 +29,7 @@ public class StoreService(IStoreRepository repo, IUnitOfWork uow, ICurrentUserSe
|
||||
var accessibleStoreIds = (await scopeAccess.GetAccessibleStoreIdsAsync(currentUser.UserId.Value)).ToHashSet();
|
||||
stores = stores.Where(s => accessibleStoreIds.Contains(s.Id)).ToList();
|
||||
}
|
||||
return stores.Select(s => new StoreResponse(s.Id, s.FK_RouteId, s.OwnerName, s.StoreName, s.StoreAddress, s.Latitude, s.Longitude, s.OneTimeAmount, s.CreditAmount, s.IsActive, s.CreatedAt)).ToList();
|
||||
return stores.Select(ToResponse).ToList();
|
||||
}
|
||||
|
||||
public async Task<StoreResponse?> GetByIdAsync(Guid id)
|
||||
@@ -31,7 +37,7 @@ public class StoreService(IStoreRepository repo, IUnitOfWork uow, ICurrentUserSe
|
||||
var store = await repo.GetByIdAsync(id);
|
||||
if (store is null || !store.IsActive) return null;
|
||||
if (!await HasStoreScopeAsync(id)) return null;
|
||||
return new StoreResponse(store.Id, store.FK_RouteId, store.OwnerName, store.StoreName, store.StoreAddress, store.Latitude, store.Longitude, store.OneTimeAmount, store.CreditAmount, store.IsActive, store.CreatedAt);
|
||||
return ToResponse(store);
|
||||
}
|
||||
|
||||
public async Task<StoreResponse> CreateAsync(CreateStoreRequest request, CancellationToken ct = default)
|
||||
@@ -58,7 +64,7 @@ public class StoreService(IStoreRepository repo, IUnitOfWork uow, ICurrentUserSe
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return new StoreResponse(store.Id, store.FK_RouteId, store.OwnerName, store.StoreName, store.StoreAddress, store.Latitude, store.Longitude, store.OneTimeAmount, store.CreditAmount, store.IsActive, store.CreatedAt);
|
||||
return ToResponse(store);
|
||||
}
|
||||
|
||||
public async Task<StoreResponse?> UpdateAsync(Guid id, UpdateStoreRequest request, CancellationToken ct = default)
|
||||
@@ -85,7 +91,7 @@ public class StoreService(IStoreRepository repo, IUnitOfWork uow, ICurrentUserSe
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return new StoreResponse(store.Id, store.FK_RouteId, store.OwnerName, store.StoreName, store.StoreAddress, store.Latitude, store.Longitude, store.OneTimeAmount, store.CreditAmount, store.IsActive, store.CreatedAt);
|
||||
return ToResponse(store);
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAsync(Guid id, CancellationToken ct = default)
|
||||
@@ -106,4 +112,131 @@ public class StoreService(IStoreRepository repo, IUnitOfWork uow, ICurrentUserSe
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<StoreResponse?> UpdateCreditLimitAsync(Guid id, UpdateCreditLimitRequest request, CancellationToken ct = default)
|
||||
{
|
||||
if (currentUser.RoleLevel < CreditApproverRoleLevel) return null;
|
||||
var store = await repo.GetByIdAsync(id);
|
||||
if (store is null || !store.IsActive) return null;
|
||||
if (!await HasStoreScopeAsync(id)) return null;
|
||||
store.CreditLimit = request.NewLimit;
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
repo.Update(store);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return ToResponse(store);
|
||||
}
|
||||
|
||||
public async Task<StoreResponse?> UpdateOutstandingBalanceAsync(Guid id, UpdateOutstandingBalanceRequest request, CancellationToken ct = default)
|
||||
{
|
||||
if (currentUser.RoleLevel < CreditApproverRoleLevel) return null;
|
||||
var store = await repo.GetByIdAsync(id);
|
||||
if (store is null || !store.IsActive) return null;
|
||||
if (!await HasStoreScopeAsync(id)) return null;
|
||||
store.OutstandingBalance = request.NewBalance;
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
repo.Update(store);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return ToResponse(store);
|
||||
}
|
||||
|
||||
public async Task<OutstandingBalanceResponse?> GetOutstandingBalanceAsync(Guid id)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
public async Task<StoreResponse?> VerifyKycAsync(Guid id, VerifyKycRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var store = await repo.GetByIdAsync(id);
|
||||
if (store is null || !store.IsActive) return null;
|
||||
if (!await HasStoreScopeAsync(id)) return null;
|
||||
store.KycStatus = request.Status;
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
repo.Update(store);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return ToResponse(store);
|
||||
}
|
||||
|
||||
public async Task<StoreResponse?> ClassifySegmentAsync(Guid id, ClassifySegmentRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var store = await repo.GetByIdAsync(id);
|
||||
if (store is null || !store.IsActive) return null;
|
||||
if (!await HasStoreScopeAsync(id)) return null;
|
||||
store.Segment = request.Segment;
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
repo.Update(store);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return ToResponse(store);
|
||||
}
|
||||
|
||||
public async Task<List<StoreVisitHistoryItem>?> GetVisitHistoryAsync(Guid id)
|
||||
{
|
||||
var store = await repo.GetByIdAsync(id);
|
||||
if (store is null || !store.IsActive) return null;
|
||||
if (!await HasStoreScopeAsync(id)) return null;
|
||||
// Wired to Step 11 (Route & Sales Management) once ride/visit tracking exists.
|
||||
return [];
|
||||
}
|
||||
|
||||
public async Task<SchemeEligibilityResponse?> CheckSchemeEligibilityAsync(Guid id, Guid schemeId)
|
||||
{
|
||||
var store = await repo.GetByIdAsync(id);
|
||||
if (store is null || !store.IsActive) return null;
|
||||
if (!await HasStoreScopeAsync(id)) return null;
|
||||
// Wired to Step 17 (Promotions & Schemes) once scheme rules exist.
|
||||
return new SchemeEligibilityResponse(true, null);
|
||||
}
|
||||
|
||||
public async Task<StoreDashboardResponse?> GetDashboardAsync(Guid id, string period)
|
||||
{
|
||||
var store = await repo.GetByIdAsync(id);
|
||||
if (store is null || !store.IsActive) return null;
|
||||
if (!await HasStoreScopeAsync(id)) return null;
|
||||
|
||||
if (!DateTime.TryParseExact(period, "yyyy-MM", null, System.Globalization.DateTimeStyles.None, out var periodStart))
|
||||
periodStart = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
|
||||
var periodEndExclusive = periodStart.AddMonths(1);
|
||||
|
||||
var orders = await repo.GetOrdersForDashboardAsync(id, periodStart, periodEndExclusive);
|
||||
return new StoreDashboardResponse(
|
||||
id,
|
||||
periodStart.ToString("yyyy-MM"),
|
||||
orders.Count,
|
||||
orders.Sum(o => o.TotalAmount),
|
||||
orders.Count == 0 ? null : orders.Max(o => o.OrderDate));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,362 +0,0 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Distribution.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Distribution.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDBContext))]
|
||||
[Migration("20260818065939_initialDbCreate")]
|
||||
partial class initialDbCreate
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.DeliveryRun", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("FK_RouteId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("FK_VehicleId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<int>("Meterend")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Meterstart")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("FK_RouteId");
|
||||
|
||||
b.HasIndex("FK_VehicleId");
|
||||
|
||||
b.ToTable("DeliveryRuns");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.Item", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Items");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.Order", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("FK_StoreId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<DateTime>("OrderDate")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<decimal>("TotalAmount")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("FK_StoreId");
|
||||
|
||||
b.ToTable("Orders");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.OrderItem", b =>
|
||||
{
|
||||
b.Property<Guid>("FK_OrderId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("FK_ItemId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<int>("Quantity")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("FK_OrderId", "FK_ItemId");
|
||||
|
||||
b.HasIndex("FK_ItemId");
|
||||
|
||||
b.ToTable("OrderItems");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.Region", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Regions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.Route", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("EndPoint")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<decimal>("Length")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<string>("RouteName")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("StartPoint")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Routes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.SaleRoute", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SaleRoutes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.Store", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<decimal>("CreditAmount")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<decimal>("Latitude")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal>("Longitude")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<decimal>("OneTimeAmount")
|
||||
.HasColumnType("decimal(18,2)");
|
||||
|
||||
b.Property<string>("OwnerName")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("StoreAddress")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("StoreName")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Stores");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.Vehicle", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("NumberPlate")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("RegNumber")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Vehicles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.Warehouse", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("Location")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid?>("VehicleId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("VehicleId");
|
||||
|
||||
b.ToTable("Warehouses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.DeliveryRun", b =>
|
||||
{
|
||||
b.HasOne("Distribution.Models.Route", "Route")
|
||||
.WithMany()
|
||||
.HasForeignKey("FK_RouteId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Distribution.Models.Vehicle", "Vehicle")
|
||||
.WithMany()
|
||||
.HasForeignKey("FK_VehicleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Route");
|
||||
|
||||
b.Navigation("Vehicle");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.Order", b =>
|
||||
{
|
||||
b.HasOne("Distribution.Models.Store", "Store")
|
||||
.WithMany()
|
||||
.HasForeignKey("FK_StoreId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Store");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.OrderItem", b =>
|
||||
{
|
||||
b.HasOne("Distribution.Models.Item", "Item")
|
||||
.WithMany()
|
||||
.HasForeignKey("FK_ItemId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Distribution.Models.Order", "Order")
|
||||
.WithMany()
|
||||
.HasForeignKey("FK_OrderId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Item");
|
||||
|
||||
b.Navigation("Order");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.Warehouse", b =>
|
||||
{
|
||||
b.HasOne("Distribution.Models.Vehicle", "Vehicle")
|
||||
.WithMany()
|
||||
.HasForeignKey("VehicleId");
|
||||
|
||||
b.Navigation("Vehicle");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,281 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Distribution.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class initialDbCreate : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterDatabase()
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Items",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", 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_Items", x => x.Id);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Regions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
Name = table.Column<string>(type: "longtext", 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_Regions", x => x.Id);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Routes",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
RouteName = table.Column<string>(type: "longtext", nullable: false),
|
||||
StartPoint = table.Column<string>(type: "longtext", nullable: false),
|
||||
EndPoint = table.Column<string>(type: "longtext", nullable: false),
|
||||
Length = table.Column<decimal>(type: "decimal(18,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_Routes", x => x.Id);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SaleRoutes",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", 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_SaleRoutes", x => x.Id);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Stores",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
OwnerName = table.Column<string>(type: "longtext", nullable: false),
|
||||
StoreName = table.Column<string>(type: "longtext", nullable: false),
|
||||
StoreAddress = table.Column<string>(type: "longtext", nullable: false),
|
||||
Latitude = table.Column<decimal>(type: "decimal(18,2)", nullable: false),
|
||||
Longitude = table.Column<decimal>(type: "decimal(18,2)", nullable: false),
|
||||
OneTimeAmount = table.Column<decimal>(type: "decimal(18,2)", nullable: false),
|
||||
CreditAmount = table.Column<decimal>(type: "decimal(18,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_Stores", x => x.Id);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Vehicles",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
RegNumber = table.Column<string>(type: "longtext", nullable: false),
|
||||
NumberPlate = table.Column<string>(type: "longtext", nullable: false),
|
||||
Type = 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_Vehicles", x => x.Id);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Orders",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
OrderDate = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
FK_StoreId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
TotalAmount = table.Column<decimal>(type: "decimal(18,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_Orders", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Orders_Stores_FK_StoreId",
|
||||
column: x => x.FK_StoreId,
|
||||
principalTable: "Stores",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "DeliveryRuns",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
FK_VehicleId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
Meterstart = table.Column<int>(type: "int", nullable: false),
|
||||
Meterend = table.Column<int>(type: "int", nullable: false),
|
||||
FK_RouteId = table.Column<Guid>(type: "char(36)", 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_DeliveryRuns", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_DeliveryRuns_Routes_FK_RouteId",
|
||||
column: x => x.FK_RouteId,
|
||||
principalTable: "Routes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_DeliveryRuns_Vehicles_FK_VehicleId",
|
||||
column: x => x.FK_VehicleId,
|
||||
principalTable: "Vehicles",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Warehouses",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
Name = table.Column<string>(type: "longtext", nullable: false),
|
||||
Location = table.Column<string>(type: "longtext", nullable: false),
|
||||
Type = table.Column<int>(type: "int", nullable: false),
|
||||
VehicleId = table.Column<Guid>(type: "char(36)", 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_Warehouses", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Warehouses_Vehicles_VehicleId",
|
||||
column: x => x.VehicleId,
|
||||
principalTable: "Vehicles",
|
||||
principalColumn: "Id");
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "OrderItems",
|
||||
columns: table => new
|
||||
{
|
||||
FK_OrderId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
FK_ItemId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
Quantity = table.Column<int>(type: "int", nullable: false),
|
||||
Id = table.Column<Guid>(type: "char(36)", 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_OrderItems", x => new { x.FK_OrderId, x.FK_ItemId });
|
||||
table.ForeignKey(
|
||||
name: "FK_OrderItems_Items_FK_ItemId",
|
||||
column: x => x.FK_ItemId,
|
||||
principalTable: "Items",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_OrderItems_Orders_FK_OrderId",
|
||||
column: x => x.FK_OrderId,
|
||||
principalTable: "Orders",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DeliveryRuns_FK_RouteId",
|
||||
table: "DeliveryRuns",
|
||||
column: "FK_RouteId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DeliveryRuns_FK_VehicleId",
|
||||
table: "DeliveryRuns",
|
||||
column: "FK_VehicleId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OrderItems_FK_ItemId",
|
||||
table: "OrderItems",
|
||||
column: "FK_ItemId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Orders_FK_StoreId",
|
||||
table: "Orders",
|
||||
column: "FK_StoreId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Warehouses_VehicleId",
|
||||
table: "Warehouses",
|
||||
column: "VehicleId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "DeliveryRuns");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "OrderItems");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Regions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "SaleRoutes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Warehouses");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Routes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Items");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Orders");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Vehicles");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Stores");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,456 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
|
||||
|
||||
namespace Distribution.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddUserManagement : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "FK_RouteId",
|
||||
table: "Stores",
|
||||
type: "char(36)",
|
||||
nullable: false,
|
||||
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"));
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "FK_RegionId",
|
||||
table: "Routes",
|
||||
type: "char(36)",
|
||||
nullable: false,
|
||||
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"));
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Permissions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
Code = table.Column<string>(type: "longtext", nullable: false),
|
||||
Description = table.Column<string>(type: "longtext", 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_Permissions", x => x.Id);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Roles",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
Name = table.Column<string>(type: "longtext", nullable: false),
|
||||
Level = 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_Roles", x => x.Id);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "RolePermissions",
|
||||
columns: table => new
|
||||
{
|
||||
FK_RoleId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
FK_PermissionId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
Id = table.Column<Guid>(type: "char(36)", 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_RolePermissions", x => new { x.FK_RoleId, x.FK_PermissionId });
|
||||
table.ForeignKey(
|
||||
name: "FK_RolePermissions_Permissions_FK_PermissionId",
|
||||
column: x => x.FK_PermissionId,
|
||||
principalTable: "Permissions",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_RolePermissions_Roles_FK_RoleId",
|
||||
column: x => x.FK_RoleId,
|
||||
principalTable: "Roles",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Users",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
Username = table.Column<string>(type: "longtext", nullable: false),
|
||||
PasswordHash = table.Column<string>(type: "longtext", nullable: false),
|
||||
FullName = table.Column<string>(type: "longtext", nullable: false),
|
||||
FK_RoleId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
FK_ReportsToUserId = table.Column<Guid>(type: "char(36)", nullable: true),
|
||||
UpdatedAt = 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_Users", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Users_Roles_FK_RoleId",
|
||||
column: x => x.FK_RoleId,
|
||||
principalTable: "Roles",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_Users_Users_FK_ReportsToUserId",
|
||||
column: x => x.FK_ReportsToUserId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "UserRegionScopes",
|
||||
columns: table => new
|
||||
{
|
||||
FK_UserId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
FK_RegionId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
Id = table.Column<Guid>(type: "char(36)", 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_UserRegionScopes", x => new { x.FK_UserId, x.FK_RegionId });
|
||||
table.ForeignKey(
|
||||
name: "FK_UserRegionScopes_Regions_FK_RegionId",
|
||||
column: x => x.FK_RegionId,
|
||||
principalTable: "Regions",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_UserRegionScopes_Users_FK_UserId",
|
||||
column: x => x.FK_UserId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "UserRouteScopes",
|
||||
columns: table => new
|
||||
{
|
||||
FK_UserId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
FK_RouteId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
Id = table.Column<Guid>(type: "char(36)", 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_UserRouteScopes", x => new { x.FK_UserId, x.FK_RouteId });
|
||||
table.ForeignKey(
|
||||
name: "FK_UserRouteScopes_Routes_FK_RouteId",
|
||||
column: x => x.FK_RouteId,
|
||||
principalTable: "Routes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_UserRouteScopes_Users_FK_UserId",
|
||||
column: x => x.FK_UserId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "UserStoreScopes",
|
||||
columns: table => new
|
||||
{
|
||||
FK_UserId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
FK_StoreId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
Id = table.Column<Guid>(type: "char(36)", 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_UserStoreScopes", x => new { x.FK_UserId, x.FK_StoreId });
|
||||
table.ForeignKey(
|
||||
name: "FK_UserStoreScopes_Stores_FK_StoreId",
|
||||
column: x => x.FK_StoreId,
|
||||
principalTable: "Stores",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_UserStoreScopes_Users_FK_UserId",
|
||||
column: x => x.FK_UserId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "Permissions",
|
||||
columns: new[] { "Id", "Code", "CreatedAt", "Description", "IsActive" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ new Guid("20000000-0000-0000-0000-000000000001"), "regions.view", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "View regions", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000002"), "regions.create", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Create regions", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000003"), "regions.update", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Update regions", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000004"), "regions.delete", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Delete regions", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000005"), "routes.view", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "View routes", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000006"), "routes.create", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Create routes", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000007"), "routes.update", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Update routes", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000008"), "routes.delete", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Delete routes", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000009"), "vehicles.view", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "View vehicles", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000010"), "vehicles.create", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Create vehicles", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000011"), "vehicles.update", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Update vehicles", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000012"), "vehicles.delete", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Delete vehicles", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000013"), "stores.view", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "View stores", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000014"), "stores.create", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Create stores", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000015"), "stores.update", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Update stores", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000016"), "stores.delete", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Delete stores", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000017"), "warehouses.view", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "View warehouses", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000018"), "warehouses.create", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Create warehouses", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000019"), "warehouses.update", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Update warehouses", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000020"), "warehouses.delete", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Delete warehouses", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000021"), "orders.view", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "View orders", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000022"), "orders.create", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Create orders", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000023"), "orders.update", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Update orders", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000024"), "orders.delete", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Delete orders", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000025"), "users.view", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "View users", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000026"), "users.create", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Create users", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000027"), "users.update", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Update users", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000028"), "users.deactivate", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Deactivate users", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000029"), "users.assign_role", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Assign a role to a user", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000030"), "users.assign_scope", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Assign region/route/store scope to a user", true }
|
||||
});
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "Roles",
|
||||
columns: new[] { "Id", "CreatedAt", "IsActive", "Level", "Name" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ new Guid("10000000-0000-0000-0000-000000000001"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), true, 10, "SalesRep" },
|
||||
{ new Guid("10000000-0000-0000-0000-000000000002"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), true, 20, "Supervisor" },
|
||||
{ new Guid("10000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), true, 30, "AreaManager" },
|
||||
{ new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), true, 40, "AreaSupervisor" },
|
||||
{ new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), true, 50, "RegionalManager" },
|
||||
{ new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), true, 60, "RegionalAgency" }
|
||||
});
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "RolePermissions",
|
||||
columns: new[] { "FK_PermissionId", "FK_RoleId", "CreatedAt", "Id", "IsActive" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ new Guid("20000000-0000-0000-0000-000000000005"), new Guid("10000000-0000-0000-0000-000000000001"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("75737fa8-fed0-d97a-8c24-5716758a7222"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000013"), new Guid("10000000-0000-0000-0000-000000000001"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("0e0613fd-d62c-1e87-a5b6-8bd2c3697c2a"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000021"), new Guid("10000000-0000-0000-0000-000000000001"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("85ef4e27-8643-9c43-71f6-da56f66a5c47"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000022"), new Guid("10000000-0000-0000-0000-000000000001"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("1e353909-d682-58f6-df5c-8e57fa2512c9"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000023"), new Guid("10000000-0000-0000-0000-000000000001"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("dfe4bb14-1047-219c-cabe-a2b49b105d86"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000005"), new Guid("10000000-0000-0000-0000-000000000002"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("7ee233b9-3758-e005-7bb2-5ad1edf36e21"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000013"), new Guid("10000000-0000-0000-0000-000000000002"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("eb4ffa50-b09a-f0a5-1994-3fc2ae3a35d0"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000014"), new Guid("10000000-0000-0000-0000-000000000002"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("5adb4d1a-eba8-16cb-8603-c3443d4ee8c8"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000015"), new Guid("10000000-0000-0000-0000-000000000002"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("23105bb1-ef1f-6e19-1a48-d62ccaec1db4"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000021"), new Guid("10000000-0000-0000-0000-000000000002"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("e4648655-7267-141e-51fa-f7d5fe3d2356"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000022"), new Guid("10000000-0000-0000-0000-000000000002"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("d427b9ea-2c9b-d93e-0843-02dfa1ae8887"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000023"), new Guid("10000000-0000-0000-0000-000000000002"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("e1f39dc4-6c33-45f4-8c23-3ad8ae5784a8"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000024"), new Guid("10000000-0000-0000-0000-000000000002"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("357c36de-9b6c-1c9a-4926-c18a9bccf0ca"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000005"), new Guid("10000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("e9c8355a-26a9-4107-504e-d61722f9e7cd"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000009"), new Guid("10000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("7fef12f3-6875-14bb-9554-0c9d82f02a37"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000013"), new Guid("10000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("680b582c-dbc9-cfde-f54b-58c3ad5a57b6"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000014"), new Guid("10000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("0b7d47cf-efd9-9589-682b-78df22062e5a"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000015"), new Guid("10000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("4aaa0f3d-4360-5f11-41c1-5257662428af"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000017"), new Guid("10000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("38ea5531-a29d-a6bb-eebe-a18f3afe066d"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000021"), new Guid("10000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("0dd5c943-3ace-0560-6fbc-54d8e37ce014"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000022"), new Guid("10000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("ca97fb3d-132c-d8a5-4ab8-f7b8fc60f5e5"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000023"), new Guid("10000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("5cee999e-1b77-cb87-5148-6a793af7c44e"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000024"), new Guid("10000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("73e00ce7-c124-4677-4369-1eb9876852c0"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000005"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("c6d74049-3f46-88af-1067-c9ae133132f7"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000006"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("29d71000-6715-de8a-a3af-814938d50dfb"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000007"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("f58f4242-af91-0b8d-5be5-4b07b4367e8c"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000009"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("dafe8f7e-e527-5863-5cde-92599c4ba9eb"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000013"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("76f58da8-09f6-c4e2-01bd-ddd94809c1f5"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000014"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("c99adad6-94e8-0d5b-3d35-086f393207fb"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000015"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("b22d4f78-7a21-da50-5351-553bdf06c9c2"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000017"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("3cb22998-b618-c13f-48cd-9e7416f324fc"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000021"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("5c00e972-a697-d71a-6545-1fd8618826cf"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000022"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("e9b6af8e-7947-7290-9c92-2d9b6239f395"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000023"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("db15215a-0a16-ef1f-5c82-6bb902a2b319"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000024"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("7b4f3482-b8bd-d348-e4c1-ae6dc93a3771"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000001"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("3e3e6f06-72c8-42fa-f7e6-8feeaca36d60"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000005"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("d95e7d5a-b12c-12b8-fee9-39576c905fed"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000006"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("d97abb20-e53d-c970-d337-3764584874fa"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000007"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("a63446ad-14a9-6239-8791-0a1f86813903"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000008"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("7f9502a2-feb4-f0ef-cb71-a62d00570fd0"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000009"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("d1f4316a-72a0-66af-2a8a-1fdbdb4f8c3a"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000010"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("30e94e9b-c70c-2df3-0813-e99ce4511838"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000011"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("94f2f2ec-94b7-b768-76a4-9ee4256d95c1"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000012"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("7201efa2-f829-8035-6607-174214f8fdba"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000013"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("fcc6af61-cc43-066c-5634-2ddc24a8460d"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000014"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("2f994b6b-b055-28c2-2fb1-99a0076a0e3f"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000015"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("55e84a29-428d-bf95-d589-793ecd8bac6e"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000016"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("43ec940a-44d2-9c47-a37d-049f2cfb2615"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000017"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("ee175c87-23bd-5529-ed18-6ce46985c6ed"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000018"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("2ae2eba4-4427-ab1e-cf03-e59a3925ef89"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000019"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("c88131d2-51b2-2bac-ad15-c7c130eb26f8"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000020"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("3a2ccc28-7416-388d-17f2-424d0520049f"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000021"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("08de9913-8cfc-4ab4-2cb9-2bc9217fbb6e"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000022"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("f3c8f315-3b2f-f923-e3cd-f3e575010138"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000023"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("4d682c93-d6df-4214-436d-85f76a3ade93"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000024"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("4b2c3b3b-c27d-410c-c1fc-7e43e280242f"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000025"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("1d227ae7-0eb9-c742-41df-3472718cb077"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000001"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("f64e20e2-f9ca-7ea5-42f4-c1256b0c0798"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000002"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("7b6765bc-1510-cfc6-55ae-ac689caf0dcc"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000003"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("d9e06d2d-82d6-84e8-e9ba-5406e9b4122d"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000004"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("b53e01bb-4e54-3270-87b1-0d54f947e09b"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000005"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("111b7d3c-3640-66e6-0757-b9fb67572943"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000006"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("7ee06ff0-8557-b5bd-02dc-c4ff99e76348"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000007"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("d20bcba0-a5af-de47-919d-a8645a828d27"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000008"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("b3ef35de-afc2-d80a-115f-77ba954f9586"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000009"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("d1b7e0a2-149f-b56b-8c63-bb8d10bfcc1e"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000010"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("51e06fd0-cd20-ceeb-04cc-7dc79388c4a1"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000011"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("25639115-7083-1324-c3bd-3ad90fcfa1a0"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000012"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("51011cb7-5753-14ad-280c-86a6bd33124f"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000013"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("51ce3fad-cdbc-cfad-6cf0-52b47ec3f200"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000014"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("4a080973-c23f-3d1b-9883-6b7e06025a77"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000015"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("3f78910a-76ee-f34b-52d3-af79d7f3b724"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000016"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("812cbb8d-e268-aad3-373e-42b8720f0f45"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000017"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("046cd13e-77f1-78c7-fa52-d49ea7fb9855"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000018"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("96f24adf-386d-14d0-46fe-a22ceff2f79b"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000019"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("9b5f23fb-2396-3f43-1d3e-97551eb4248d"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000020"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("30397d69-3b96-56e7-0ca1-1e612dfb64f1"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000021"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("91260fa1-3754-ad7e-10d8-2a8fd9b208ff"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000022"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("8b993e65-b066-198a-385d-602660ed85ac"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000023"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("248d6092-c2e5-5f8f-5ef0-cd60dc94cbc8"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000024"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("5f6e3c68-27d2-e00f-d5c9-4ce9e2b9587f"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000025"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("96a4ac9e-cfea-6391-8020-75b2980bf2a3"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000026"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("6eb59953-807d-aa61-29e3-1699c0aebda9"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000027"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("1095440c-ce52-235b-b95f-6b28c5d1b3a8"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000028"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("3a56ab5c-f0c5-a37b-0795-de061597628f"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000029"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("104ebcfb-ca82-3f95-d75e-35a943618176"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000030"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("ace990cf-6654-5e68-3dc8-1e62347e0946"), true }
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Stores_FK_RouteId",
|
||||
table: "Stores",
|
||||
column: "FK_RouteId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Routes_FK_RegionId",
|
||||
table: "Routes",
|
||||
column: "FK_RegionId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RolePermissions_FK_PermissionId",
|
||||
table: "RolePermissions",
|
||||
column: "FK_PermissionId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UserRegionScopes_FK_RegionId",
|
||||
table: "UserRegionScopes",
|
||||
column: "FK_RegionId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UserRouteScopes_FK_RouteId",
|
||||
table: "UserRouteScopes",
|
||||
column: "FK_RouteId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Users_FK_ReportsToUserId",
|
||||
table: "Users",
|
||||
column: "FK_ReportsToUserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Users_FK_RoleId",
|
||||
table: "Users",
|
||||
column: "FK_RoleId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UserStoreScopes_FK_StoreId",
|
||||
table: "UserStoreScopes",
|
||||
column: "FK_StoreId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Routes_Regions_FK_RegionId",
|
||||
table: "Routes",
|
||||
column: "FK_RegionId",
|
||||
principalTable: "Regions",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Stores_Routes_FK_RouteId",
|
||||
table: "Stores",
|
||||
column: "FK_RouteId",
|
||||
principalTable: "Routes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Routes_Regions_FK_RegionId",
|
||||
table: "Routes");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Stores_Routes_FK_RouteId",
|
||||
table: "Stores");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "RolePermissions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "UserRegionScopes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "UserRouteScopes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "UserStoreScopes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Permissions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Users");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Roles");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Stores_FK_RouteId",
|
||||
table: "Stores");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Routes_FK_RegionId",
|
||||
table: "Routes");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FK_RouteId",
|
||||
table: "Stores");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FK_RegionId",
|
||||
table: "Routes");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,621 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
|
||||
|
||||
namespace Distribution.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddItemCatalog : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "UnitPrice",
|
||||
table: "OrderItems",
|
||||
type: "decimal(18,2)",
|
||||
nullable: false,
|
||||
defaultValue: 0m);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Description",
|
||||
table: "Items",
|
||||
type: "longtext",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "ExternalErpItemId",
|
||||
table: "Items",
|
||||
type: "int",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "FK_BaseUomId",
|
||||
table: "Items",
|
||||
type: "char(36)",
|
||||
nullable: false,
|
||||
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"));
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "FK_BrandId",
|
||||
table: "Items",
|
||||
type: "char(36)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "FK_CategoryId",
|
||||
table: "Items",
|
||||
type: "char(36)",
|
||||
nullable: false,
|
||||
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"));
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "FK_SubCategoryId",
|
||||
table: "Items",
|
||||
type: "char(36)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "LastSyncedAt",
|
||||
table: "Items",
|
||||
type: "datetime(6)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Name",
|
||||
table: "Items",
|
||||
type: "longtext",
|
||||
nullable: false);
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "SalePrice",
|
||||
table: "Items",
|
||||
type: "decimal(18,2)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Sku",
|
||||
table: "Items",
|
||||
type: "longtext",
|
||||
nullable: false);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "SyncSource",
|
||||
table: "Items",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Brands",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
Name = table.Column<string>(type: "longtext", nullable: false),
|
||||
ExternalErpId = table.Column<int>(type: "int", 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_Brands", x => x.Id);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Categories",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
Name = table.Column<string>(type: "longtext", nullable: false),
|
||||
ExternalErpId = table.Column<int>(type: "int", 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_Categories", x => x.Id);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Uoms",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
Name = table.Column<string>(type: "longtext", nullable: false),
|
||||
ExternalErpId = table.Column<int>(type: "int", 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_Uoms", x => x.Id);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SubCategories",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
Name = table.Column<string>(type: "longtext", nullable: false),
|
||||
FK_CategoryId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
ExternalErpId = table.Column<int>(type: "int", 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_SubCategories", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_SubCategories_Categories_FK_CategoryId",
|
||||
column: x => x.FK_CategoryId,
|
||||
principalTable: "Categories",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "UomConversions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
FK_ItemId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
FK_FromUomId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
FK_ToUomId = table.Column<Guid>(type: "char(36)", nullable: false),
|
||||
Factor = table.Column<decimal>(type: "decimal(18,2)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_UomConversions", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_UomConversions_Items_FK_ItemId",
|
||||
column: x => x.FK_ItemId,
|
||||
principalTable: "Items",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_UomConversions_Uoms_FK_FromUomId",
|
||||
column: x => x.FK_FromUomId,
|
||||
principalTable: "Uoms",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_UomConversions_Uoms_FK_ToUomId",
|
||||
column: x => x.FK_ToUomId,
|
||||
principalTable: "Uoms",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "Permissions",
|
||||
columns: new[] { "Id", "Code", "CreatedAt", "Description", "IsActive" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ new Guid("20000000-0000-0000-0000-000000000031"), "items.view", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "View items, brands and UOMs", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000032"), "items.create", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Create items, brands and UOMs", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000033"), "items.update", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Update items, brands and UOMs", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000034"), "items.delete", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Delete items, brands and UOMs", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000035"), "categories.view", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "View categories", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000036"), "categories.create", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Create categories and subcategories", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000037"), "categories.update", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Update categories and subcategories", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000038"), "categories.delete", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Delete categories and subcategories", true }
|
||||
});
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "RolePermissions",
|
||||
columns: new[] { "FK_PermissionId", "FK_RoleId", "CreatedAt", "Id", "IsActive" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000001"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("7596f703-c6ab-316c-7c80-3f4e3daa174e"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000001"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("aa120c36-488e-34ba-e10a-2ac45e46e119"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000002"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("b7dee5e3-f329-95a5-75c1-d02c3e40e229"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000002"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("e5506b30-527d-08f9-6caa-8a45d3d02999"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("7780d277-9597-84ff-1a1a-f63fc6b4d3cb"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("8083c659-9506-0bd9-b793-3d6148ce9192"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("d49f086e-30f0-6ce8-ae96-4395d5e04472"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("1e7fe1b6-5150-4b99-38ed-088d9495cfd8"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("a701da28-aa75-3c1d-abdb-4317404f8df6"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("2c025310-765f-7d07-ab9a-50524c13d1d3"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("7c392a13-1a57-0d85-dffa-2fb9e892abe9"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000034"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("97708eed-6ce4-7710-abde-01d00c0c879b"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("9afe507e-191e-bdee-0c5a-cfbcbf1efb3e"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000036"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("9c278616-4289-96fb-944d-b8f1495ccb00"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("d1dcef02-d4c3-caba-372f-dab82011e9c1"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000038"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("9cc36229-dfba-49b9-1d6e-8929546e894d"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("69f93a29-1df1-7b20-9d5c-94c264f8c4b8"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("070813f7-4053-2889-0d14-d0d5834208c2"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("0efa7259-aadb-0019-4be5-38d3f8f4cd54"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000034"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("9e60e7d7-8bd6-c1c7-4bf1-4de2c9dcb91c"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("5df82318-bc01-1947-09ef-60508e02b257"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000036"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("34f478f4-6c96-4b31-b68a-28c7942c0789"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("69598e0e-b7ad-477b-c587-7abf6189162d"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000038"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("b36fb2d4-43df-44f8-ff51-de6d88644c94"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("bd209f90-6671-8ea0-eb20-aa151faec6a0"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("4cc32686-8391-0214-5747-db14f53c5b6f"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("26c6d86a-835d-6f0d-3730-66a46ea26b11"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000034"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("e8039d9c-a286-fb74-5abb-b5c6c67f243a"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("d9e038cb-bcff-cce1-5d34-b4e63a9ce694"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000036"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("343e71f0-da6b-2489-5571-f8d7c6378ee7"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("ac894328-a935-8e6e-8427-6c0f36ac75da"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000038"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("1d6273d4-db6a-d70e-4b5f-98cb634d668a"), true }
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Items_FK_BaseUomId",
|
||||
table: "Items",
|
||||
column: "FK_BaseUomId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Items_FK_BrandId",
|
||||
table: "Items",
|
||||
column: "FK_BrandId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Items_FK_CategoryId",
|
||||
table: "Items",
|
||||
column: "FK_CategoryId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Items_FK_SubCategoryId",
|
||||
table: "Items",
|
||||
column: "FK_SubCategoryId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_SubCategories_FK_CategoryId",
|
||||
table: "SubCategories",
|
||||
column: "FK_CategoryId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UomConversions_FK_FromUomId",
|
||||
table: "UomConversions",
|
||||
column: "FK_FromUomId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UomConversions_FK_ItemId",
|
||||
table: "UomConversions",
|
||||
column: "FK_ItemId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UomConversions_FK_ToUomId",
|
||||
table: "UomConversions",
|
||||
column: "FK_ToUomId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Items_Brands_FK_BrandId",
|
||||
table: "Items",
|
||||
column: "FK_BrandId",
|
||||
principalTable: "Brands",
|
||||
principalColumn: "Id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Items_Categories_FK_CategoryId",
|
||||
table: "Items",
|
||||
column: "FK_CategoryId",
|
||||
principalTable: "Categories",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Items_SubCategories_FK_SubCategoryId",
|
||||
table: "Items",
|
||||
column: "FK_SubCategoryId",
|
||||
principalTable: "SubCategories",
|
||||
principalColumn: "Id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Items_Uoms_FK_BaseUomId",
|
||||
table: "Items",
|
||||
column: "FK_BaseUomId",
|
||||
principalTable: "Uoms",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Items_Brands_FK_BrandId",
|
||||
table: "Items");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Items_Categories_FK_CategoryId",
|
||||
table: "Items");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Items_SubCategories_FK_SubCategoryId",
|
||||
table: "Items");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Items_Uoms_FK_BaseUomId",
|
||||
table: "Items");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Brands");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "SubCategories");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "UomConversions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Categories");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Uoms");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Items_FK_BaseUomId",
|
||||
table: "Items");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Items_FK_BrandId",
|
||||
table: "Items");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Items_FK_CategoryId",
|
||||
table: "Items");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Items_FK_SubCategoryId",
|
||||
table: "Items");
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000001") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000001") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000002") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000002") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000003") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000003") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000003") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000003") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000004") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000004") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000004") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000034"), new Guid("10000000-0000-0000-0000-000000000004") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000004") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000036"), new Guid("10000000-0000-0000-0000-000000000004") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000004") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000038"), new Guid("10000000-0000-0000-0000-000000000004") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000005") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000005") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000005") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000034"), new Guid("10000000-0000-0000-0000-000000000005") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000005") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000036"), new Guid("10000000-0000-0000-0000-000000000005") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000005") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000038"), new Guid("10000000-0000-0000-0000-000000000005") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000006") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000006") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000006") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000034"), new Guid("10000000-0000-0000-0000-000000000006") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000006") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000036"), new Guid("10000000-0000-0000-0000-000000000006") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000006") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000038"), new Guid("10000000-0000-0000-0000-000000000006") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000031"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000032"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000033"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000034"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000035"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000036"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000037"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000038"));
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "UnitPrice",
|
||||
table: "OrderItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Description",
|
||||
table: "Items");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ExternalErpItemId",
|
||||
table: "Items");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FK_BaseUomId",
|
||||
table: "Items");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FK_BrandId",
|
||||
table: "Items");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FK_CategoryId",
|
||||
table: "Items");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FK_SubCategoryId",
|
||||
table: "Items");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LastSyncedAt",
|
||||
table: "Items");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Name",
|
||||
table: "Items");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SalePrice",
|
||||
table: "Items");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Sku",
|
||||
table: "Items");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SyncSource",
|
||||
table: "Items");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Distribution.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class item_catalog : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<decimal>(
|
||||
name: "Factor",
|
||||
table: "UomConversions",
|
||||
type: "decimal(65,30)",
|
||||
nullable: false,
|
||||
oldClrType: typeof(decimal),
|
||||
oldType: "decimal(18,2)");
|
||||
|
||||
migrationBuilder.AlterColumn<decimal>(
|
||||
name: "OneTimeAmount",
|
||||
table: "Stores",
|
||||
type: "decimal(65,30)",
|
||||
nullable: false,
|
||||
oldClrType: typeof(decimal),
|
||||
oldType: "decimal(18,2)");
|
||||
|
||||
migrationBuilder.AlterColumn<decimal>(
|
||||
name: "Longitude",
|
||||
table: "Stores",
|
||||
type: "decimal(65,30)",
|
||||
nullable: false,
|
||||
oldClrType: typeof(decimal),
|
||||
oldType: "decimal(18,2)");
|
||||
|
||||
migrationBuilder.AlterColumn<decimal>(
|
||||
name: "Latitude",
|
||||
table: "Stores",
|
||||
type: "decimal(65,30)",
|
||||
nullable: false,
|
||||
oldClrType: typeof(decimal),
|
||||
oldType: "decimal(18,2)");
|
||||
|
||||
migrationBuilder.AlterColumn<decimal>(
|
||||
name: "CreditAmount",
|
||||
table: "Stores",
|
||||
type: "decimal(65,30)",
|
||||
nullable: false,
|
||||
oldClrType: typeof(decimal),
|
||||
oldType: "decimal(18,2)");
|
||||
|
||||
migrationBuilder.AlterColumn<decimal>(
|
||||
name: "Length",
|
||||
table: "Routes",
|
||||
type: "decimal(65,30)",
|
||||
nullable: false,
|
||||
oldClrType: typeof(decimal),
|
||||
oldType: "decimal(18,2)");
|
||||
|
||||
migrationBuilder.AlterColumn<decimal>(
|
||||
name: "TotalAmount",
|
||||
table: "Orders",
|
||||
type: "decimal(65,30)",
|
||||
nullable: false,
|
||||
oldClrType: typeof(decimal),
|
||||
oldType: "decimal(18,2)");
|
||||
|
||||
migrationBuilder.AlterColumn<decimal>(
|
||||
name: "UnitPrice",
|
||||
table: "OrderItems",
|
||||
type: "decimal(65,30)",
|
||||
nullable: false,
|
||||
oldClrType: typeof(decimal),
|
||||
oldType: "decimal(18,2)");
|
||||
|
||||
migrationBuilder.AlterColumn<decimal>(
|
||||
name: "SalePrice",
|
||||
table: "Items",
|
||||
type: "decimal(65,30)",
|
||||
nullable: true,
|
||||
oldClrType: typeof(decimal),
|
||||
oldType: "decimal(18,2)",
|
||||
oldNullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<decimal>(
|
||||
name: "Factor",
|
||||
table: "UomConversions",
|
||||
type: "decimal(18,2)",
|
||||
nullable: false,
|
||||
oldClrType: typeof(decimal),
|
||||
oldType: "decimal(65,30)");
|
||||
|
||||
migrationBuilder.AlterColumn<decimal>(
|
||||
name: "OneTimeAmount",
|
||||
table: "Stores",
|
||||
type: "decimal(18,2)",
|
||||
nullable: false,
|
||||
oldClrType: typeof(decimal),
|
||||
oldType: "decimal(65,30)");
|
||||
|
||||
migrationBuilder.AlterColumn<decimal>(
|
||||
name: "Longitude",
|
||||
table: "Stores",
|
||||
type: "decimal(18,2)",
|
||||
nullable: false,
|
||||
oldClrType: typeof(decimal),
|
||||
oldType: "decimal(65,30)");
|
||||
|
||||
migrationBuilder.AlterColumn<decimal>(
|
||||
name: "Latitude",
|
||||
table: "Stores",
|
||||
type: "decimal(18,2)",
|
||||
nullable: false,
|
||||
oldClrType: typeof(decimal),
|
||||
oldType: "decimal(65,30)");
|
||||
|
||||
migrationBuilder.AlterColumn<decimal>(
|
||||
name: "CreditAmount",
|
||||
table: "Stores",
|
||||
type: "decimal(18,2)",
|
||||
nullable: false,
|
||||
oldClrType: typeof(decimal),
|
||||
oldType: "decimal(65,30)");
|
||||
|
||||
migrationBuilder.AlterColumn<decimal>(
|
||||
name: "Length",
|
||||
table: "Routes",
|
||||
type: "decimal(18,2)",
|
||||
nullable: false,
|
||||
oldClrType: typeof(decimal),
|
||||
oldType: "decimal(65,30)");
|
||||
|
||||
migrationBuilder.AlterColumn<decimal>(
|
||||
name: "TotalAmount",
|
||||
table: "Orders",
|
||||
type: "decimal(18,2)",
|
||||
nullable: false,
|
||||
oldClrType: typeof(decimal),
|
||||
oldType: "decimal(65,30)");
|
||||
|
||||
migrationBuilder.AlterColumn<decimal>(
|
||||
name: "UnitPrice",
|
||||
table: "OrderItems",
|
||||
type: "decimal(18,2)",
|
||||
nullable: false,
|
||||
oldClrType: typeof(decimal),
|
||||
oldType: "decimal(65,30)");
|
||||
|
||||
migrationBuilder.AlterColumn<decimal>(
|
||||
name: "SalePrice",
|
||||
table: "Items",
|
||||
type: "decimal(18,2)",
|
||||
nullable: true,
|
||||
oldClrType: typeof(decimal),
|
||||
oldType: "decimal(65,30)",
|
||||
oldNullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
+102
-2
@@ -12,8 +12,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
namespace Distribution.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDBContext))]
|
||||
[Migration("20260819074545_item_catalog")]
|
||||
partial class item_catalog
|
||||
[Migration("20260819163125_init")]
|
||||
partial class init
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
@@ -55,6 +55,9 @@ namespace Distribution.Migrations
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<bool>("AllowCustomerReturn")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
@@ -1682,6 +1685,81 @@ namespace Distribution.Migrations
|
||||
b.ToTable("SaleRoutes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.StockBalance", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("FK_ItemId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("LocationId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<int>("LocationType")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<decimal>("Quantity")
|
||||
.HasColumnType("decimal(65,30)");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("FK_ItemId", "LocationId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("StockBalances");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.StockMovement", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("CreatedBy")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("FK_ItemId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid?>("FromLocationId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<int?>("FromLocationType")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<decimal>("Quantity")
|
||||
.HasColumnType("decimal(65,30)");
|
||||
|
||||
b.Property<string>("ReasonCode")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<Guid?>("ReferenceId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid?>("ToLocationId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<int?>("ToLocationType")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("FK_ItemId");
|
||||
|
||||
b.ToTable("StockMovements");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.Store", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -2095,6 +2173,28 @@ namespace Distribution.Migrations
|
||||
b.Navigation("Region");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.StockBalance", b =>
|
||||
{
|
||||
b.HasOne("Distribution.Models.Item", "Item")
|
||||
.WithMany()
|
||||
.HasForeignKey("FK_ItemId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Item");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.StockMovement", b =>
|
||||
{
|
||||
b.HasOne("Distribution.Models.Item", "Item")
|
||||
.WithMany()
|
||||
.HasForeignKey("FK_ItemId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Item");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.Store", b =>
|
||||
{
|
||||
b.HasOne("Distribution.Models.Route", "Route")
|
||||
File diff suppressed because it is too large
Load Diff
+427
-172
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,716 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
|
||||
|
||||
namespace Distribution.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddStoreCreditAndKyc : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000021"), new Guid("10000000-0000-0000-0000-000000000001") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000022"), new Guid("10000000-0000-0000-0000-000000000001") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000001") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000001") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000021"), new Guid("10000000-0000-0000-0000-000000000002") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000022"), new Guid("10000000-0000-0000-0000-000000000002") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000002") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000002") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000021"), new Guid("10000000-0000-0000-0000-000000000003") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000022"), new Guid("10000000-0000-0000-0000-000000000003") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000003") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000003") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000021"), new Guid("10000000-0000-0000-0000-000000000004") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000022"), new Guid("10000000-0000-0000-0000-000000000004") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000004") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000004") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000005") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000005") });
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "CreditLimit",
|
||||
table: "Stores",
|
||||
type: "decimal(65,30)",
|
||||
nullable: false,
|
||||
defaultValue: 0m);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "KycStatus",
|
||||
table: "Stores",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "OutstandingBalance",
|
||||
table: "Stores",
|
||||
type: "decimal(65,30)",
|
||||
nullable: false,
|
||||
defaultValue: 0m);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Segment",
|
||||
table: "Stores",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "StoreKycDocuments",
|
||||
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"),
|
||||
DocumentType = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
FileReference = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
UploadedAt = table.Column<DateTime>(type: "datetime(6)", 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_StoreKycDocuments", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_StoreKycDocuments_Stores_FK_StoreId",
|
||||
column: x => x.FK_StoreId,
|
||||
principalTable: "Stores",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000017"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "stores.kyc.verify", "Verify or reject store KYC documents" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000018"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "stores.credit.update", "Update store credit limit and outstanding balance" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000019"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "warehouses.view", "View warehouses" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000020"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "warehouses.create", "Create warehouses" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000021"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "warehouses.update", "Update warehouses" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000022"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "warehouses.delete", "Delete warehouses" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000023"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "orders.view", "View orders" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000024"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "orders.create", "Create orders" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000025"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "orders.update", "Update orders" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000026"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "orders.delete", "Delete orders" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000027"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "users.view", "View users" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000028"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "users.create", "Create users" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000029"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "users.update", "Update users" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000030"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "users.deactivate", "Deactivate users" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000031"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "users.assign_role", "Assign a role to a user" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000032"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "users.assign_scope", "Assign region/route/store scope to a user" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000033"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "items.view", "View items, brands and UOMs" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000034"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "items.create", "Create items, brands and UOMs" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000035"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "items.update", "Update items, brands and UOMs" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000036"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "items.delete", "Delete items, brands and UOMs" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000037"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "categories.view", "View categories" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000038"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "categories.create", "Create categories and subcategories" });
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "Permissions",
|
||||
columns: new[] { "Id", "Code", "CreatedAt", "Description", "IsActive" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ new Guid("20000000-0000-0000-0000-000000000039"), "categories.update", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Update categories and subcategories", true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000040"), "categories.delete", new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), "Delete categories and subcategories", true }
|
||||
});
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "RolePermissions",
|
||||
columns: new[] { "FK_PermissionId", "FK_RoleId", "CreatedAt", "Id", "IsActive" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ new Guid("20000000-0000-0000-0000-000000000024"), new Guid("10000000-0000-0000-0000-000000000001"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("c0739b84-50a2-0971-429b-02bab8354c51"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000025"), new Guid("10000000-0000-0000-0000-000000000001"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("c268a531-44eb-0f29-82ba-18394ac2dcb5"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000001"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("ddb2d04d-19b0-20b1-2639-b157d12a9eec"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000001"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("320ba3fb-666c-ce57-3e61-95c9824c4048"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000018"), new Guid("10000000-0000-0000-0000-000000000002"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("f2c14d55-d557-5cd2-a70a-4a6f58933436"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000025"), new Guid("10000000-0000-0000-0000-000000000002"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("1b126bb0-e582-3e84-4728-a7ab5628fe7b"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000026"), new Guid("10000000-0000-0000-0000-000000000002"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("f50b201c-6c8d-0df8-85ad-08cb137bc213"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000002"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("0f291826-e169-c06a-0735-bd06cbb2062d"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000002"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("10dd0249-ce04-c5d9-8f94-ccb348c1d979"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000018"), new Guid("10000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("d92d572f-f1bf-9e7d-1d4d-7c7b0d575c8e"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000019"), new Guid("10000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("f367f6f6-3d26-7a6c-888b-da1a240b59af"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000025"), new Guid("10000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("929950da-d51b-ad5d-c4e5-55175908fb43"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000026"), new Guid("10000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("a9a49749-f99d-3166-5d92-b7e380fcabb2"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000034"), new Guid("10000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("b17df3df-022c-c903-6ce0-e6423e648dc8"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("a41f94ba-c24d-93dc-9f25-c462df19785a"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000016"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("e0b27435-f505-43f5-7ed9-4711f2248495"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000018"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("5e10d61c-2d7a-3388-722f-45232d2cfb71"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000019"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("d8257363-d838-fe7e-57a5-51b626dbfc83"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000025"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("04ece77e-b36f-8ed0-4ef9-2f6962b6f9c6"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000026"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("bf2a2f96-04ed-905d-5a8a-9047d814aa3e"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000026"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("4d287b19-ba40-7d4d-3d39-291be64197f8"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000027"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("43a4c7b4-a50f-aa37-b147-a30636d9d65f"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000039"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("a66f3854-5a8f-95c2-cb1c-b2b2e67ee226"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000040"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("14bcecd1-b1a2-6da3-155a-233611484467"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000039"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("aeb29e0e-e819-1446-6cc6-1fb64b5ecc6b"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000040"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("e18a6eca-1009-bcc6-35e9-ead943c1649f"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000039"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("3f32cdac-6925-23f2-064e-8226d781d4a3"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000040"), new Guid("10000000-0000-0000-0000-000000000006"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("dc3dd9ff-b123-a8d4-dcea-b4f5f8ff171e"), true }
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_StoreKycDocuments_FK_StoreId",
|
||||
table: "StoreKycDocuments",
|
||||
column: "FK_StoreId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "StoreKycDocuments");
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000024"), new Guid("10000000-0000-0000-0000-000000000001") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000025"), new Guid("10000000-0000-0000-0000-000000000001") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000001") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000001") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000018"), new Guid("10000000-0000-0000-0000-000000000002") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000025"), new Guid("10000000-0000-0000-0000-000000000002") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000026"), new Guid("10000000-0000-0000-0000-000000000002") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000033"), new Guid("10000000-0000-0000-0000-000000000002") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000002") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000018"), new Guid("10000000-0000-0000-0000-000000000003") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000019"), new Guid("10000000-0000-0000-0000-000000000003") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000025"), new Guid("10000000-0000-0000-0000-000000000003") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000026"), new Guid("10000000-0000-0000-0000-000000000003") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000034"), new Guid("10000000-0000-0000-0000-000000000003") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000037"), new Guid("10000000-0000-0000-0000-000000000003") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000016"), new Guid("10000000-0000-0000-0000-000000000004") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000018"), new Guid("10000000-0000-0000-0000-000000000004") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000019"), new Guid("10000000-0000-0000-0000-000000000004") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000025"), new Guid("10000000-0000-0000-0000-000000000004") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000026"), new Guid("10000000-0000-0000-0000-000000000004") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000039"), new Guid("10000000-0000-0000-0000-000000000004") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000040"), new Guid("10000000-0000-0000-0000-000000000004") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000026"), new Guid("10000000-0000-0000-0000-000000000005") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000027"), new Guid("10000000-0000-0000-0000-000000000005") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000039"), new Guid("10000000-0000-0000-0000-000000000005") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000040"), new Guid("10000000-0000-0000-0000-000000000005") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000039"), new Guid("10000000-0000-0000-0000-000000000006") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "RolePermissions",
|
||||
keyColumns: new[] { "FK_PermissionId", "FK_RoleId" },
|
||||
keyValues: new object[] { new Guid("20000000-0000-0000-0000-000000000040"), new Guid("10000000-0000-0000-0000-000000000006") });
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000039"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000040"));
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CreditLimit",
|
||||
table: "Stores");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "KycStatus",
|
||||
table: "Stores");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "OutstandingBalance",
|
||||
table: "Stores");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Segment",
|
||||
table: "Stores");
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000017"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "warehouses.view", "View warehouses" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000018"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "warehouses.create", "Create warehouses" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000019"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "warehouses.update", "Update warehouses" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000020"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "warehouses.delete", "Delete warehouses" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000021"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "orders.view", "View orders" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000022"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "orders.create", "Create orders" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000023"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "orders.update", "Update orders" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000024"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "orders.delete", "Delete orders" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000025"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "users.view", "View users" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000026"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "users.create", "Create users" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000027"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "users.update", "Update users" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000028"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "users.deactivate", "Deactivate users" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000029"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "users.assign_role", "Assign a role to a user" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000030"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "users.assign_scope", "Assign region/route/store scope to a user" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000031"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "items.view", "View items, brands and UOMs" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000032"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "items.create", "Create items, brands and UOMs" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000033"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "items.update", "Update items, brands and UOMs" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000034"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "items.delete", "Delete items, brands and UOMs" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000035"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "categories.view", "View categories" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000036"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "categories.create", "Create categories and subcategories" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000037"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "categories.update", "Update categories and subcategories" });
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "Permissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("20000000-0000-0000-0000-000000000038"),
|
||||
columns: new[] { "Code", "Description" },
|
||||
values: new object[] { "categories.delete", "Delete categories and subcategories" });
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "RolePermissions",
|
||||
columns: new[] { "FK_PermissionId", "FK_RoleId", "CreatedAt", "Id", "IsActive" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ new Guid("20000000-0000-0000-0000-000000000021"), new Guid("10000000-0000-0000-0000-000000000001"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("85ef4e27-8643-9c43-71f6-da56f66a5c47"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000022"), new Guid("10000000-0000-0000-0000-000000000001"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("1e353909-d682-58f6-df5c-8e57fa2512c9"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000001"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("7596f703-c6ab-316c-7c80-3f4e3daa174e"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000001"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("aa120c36-488e-34ba-e10a-2ac45e46e119"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000021"), new Guid("10000000-0000-0000-0000-000000000002"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("e4648655-7267-141e-51fa-f7d5fe3d2356"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000022"), new Guid("10000000-0000-0000-0000-000000000002"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("d427b9ea-2c9b-d93e-0843-02dfa1ae8887"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000002"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("b7dee5e3-f329-95a5-75c1-d02c3e40e229"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000035"), new Guid("10000000-0000-0000-0000-000000000002"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("e5506b30-527d-08f9-6caa-8a45d3d02999"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000021"), new Guid("10000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("0dd5c943-3ace-0560-6fbc-54d8e37ce014"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000022"), new Guid("10000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("ca97fb3d-132c-d8a5-4ab8-f7b8fc60f5e5"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("7780d277-9597-84ff-1a1a-f63fc6b4d3cb"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("8083c659-9506-0bd9-b793-3d6148ce9192"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000021"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("5c00e972-a697-d71a-6545-1fd8618826cf"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000022"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("e9b6af8e-7947-7290-9c92-2d9b6239f395"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("a701da28-aa75-3c1d-abdb-4317404f8df6"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("2c025310-765f-7d07-ab9a-50524c13d1d3"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000031"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("69f93a29-1df1-7b20-9d5c-94c264f8c4b8"), true },
|
||||
{ new Guid("20000000-0000-0000-0000-000000000032"), new Guid("10000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified), new Guid("070813f7-4053-2889-0d14-d0d5834208c2"), true }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,9 @@ namespace Distribution.Migrations
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<bool>("AllowCustomerReturn")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
@@ -377,6 +380,22 @@ namespace Distribution.Migrations
|
||||
new
|
||||
{
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000017"),
|
||||
Code = "stores.kyc.verify",
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Description = "Verify or reject store KYC documents",
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000018"),
|
||||
Code = "stores.credit.update",
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Description = "Update store credit limit and outstanding balance",
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000019"),
|
||||
Code = "warehouses.view",
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Description = "View warehouses",
|
||||
@@ -384,7 +403,7 @@ namespace Distribution.Migrations
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000018"),
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000020"),
|
||||
Code = "warehouses.create",
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Description = "Create warehouses",
|
||||
@@ -392,7 +411,7 @@ namespace Distribution.Migrations
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000019"),
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000021"),
|
||||
Code = "warehouses.update",
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Description = "Update warehouses",
|
||||
@@ -400,7 +419,7 @@ namespace Distribution.Migrations
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000020"),
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000022"),
|
||||
Code = "warehouses.delete",
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Description = "Delete warehouses",
|
||||
@@ -408,7 +427,7 @@ namespace Distribution.Migrations
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000021"),
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000023"),
|
||||
Code = "orders.view",
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Description = "View orders",
|
||||
@@ -416,7 +435,7 @@ namespace Distribution.Migrations
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000022"),
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000024"),
|
||||
Code = "orders.create",
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Description = "Create orders",
|
||||
@@ -424,7 +443,7 @@ namespace Distribution.Migrations
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000023"),
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000025"),
|
||||
Code = "orders.update",
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Description = "Update orders",
|
||||
@@ -432,7 +451,7 @@ namespace Distribution.Migrations
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000024"),
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000026"),
|
||||
Code = "orders.delete",
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Description = "Delete orders",
|
||||
@@ -440,7 +459,7 @@ namespace Distribution.Migrations
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000025"),
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000027"),
|
||||
Code = "users.view",
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Description = "View users",
|
||||
@@ -448,7 +467,7 @@ namespace Distribution.Migrations
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000026"),
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000028"),
|
||||
Code = "users.create",
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Description = "Create users",
|
||||
@@ -456,7 +475,7 @@ namespace Distribution.Migrations
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000027"),
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000029"),
|
||||
Code = "users.update",
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Description = "Update users",
|
||||
@@ -464,7 +483,7 @@ namespace Distribution.Migrations
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000028"),
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000030"),
|
||||
Code = "users.deactivate",
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Description = "Deactivate users",
|
||||
@@ -472,7 +491,7 @@ namespace Distribution.Migrations
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000029"),
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000031"),
|
||||
Code = "users.assign_role",
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Description = "Assign a role to a user",
|
||||
@@ -480,7 +499,7 @@ namespace Distribution.Migrations
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000030"),
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000032"),
|
||||
Code = "users.assign_scope",
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Description = "Assign region/route/store scope to a user",
|
||||
@@ -488,7 +507,7 @@ namespace Distribution.Migrations
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000031"),
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000033"),
|
||||
Code = "items.view",
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Description = "View items, brands and UOMs",
|
||||
@@ -496,7 +515,7 @@ namespace Distribution.Migrations
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000032"),
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000034"),
|
||||
Code = "items.create",
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Description = "Create items, brands and UOMs",
|
||||
@@ -504,7 +523,7 @@ namespace Distribution.Migrations
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000033"),
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000035"),
|
||||
Code = "items.update",
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Description = "Update items, brands and UOMs",
|
||||
@@ -512,7 +531,7 @@ namespace Distribution.Migrations
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000034"),
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000036"),
|
||||
Code = "items.delete",
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Description = "Delete items, brands and UOMs",
|
||||
@@ -520,7 +539,7 @@ namespace Distribution.Migrations
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000035"),
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000037"),
|
||||
Code = "categories.view",
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Description = "View categories",
|
||||
@@ -528,7 +547,7 @@ namespace Distribution.Migrations
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000036"),
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000038"),
|
||||
Code = "categories.create",
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Description = "Create categories and subcategories",
|
||||
@@ -536,7 +555,7 @@ namespace Distribution.Migrations
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000037"),
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000039"),
|
||||
Code = "categories.update",
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Description = "Update categories and subcategories",
|
||||
@@ -544,7 +563,7 @@ namespace Distribution.Migrations
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000038"),
|
||||
Id = new Guid("20000000-0000-0000-0000-000000000040"),
|
||||
Code = "categories.delete",
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Description = "Delete categories and subcategories",
|
||||
@@ -671,22 +690,6 @@ namespace Distribution.Migrations
|
||||
b.ToTable("RolePermissions");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000001"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000021"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("85ef4e27-8643-9c43-71f6-da56f66a5c47"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000001"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000022"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("1e353909-d682-58f6-df5c-8e57fa2512c9"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000001"),
|
||||
@@ -696,6 +699,22 @@ namespace Distribution.Migrations
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000001"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000024"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("c0739b84-50a2-0971-429b-02bab8354c51"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000001"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000025"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("c268a531-44eb-0f29-82ba-18394ac2dcb5"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000001"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000013"),
|
||||
@@ -714,33 +733,17 @@ namespace Distribution.Migrations
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000001"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000031"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000033"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("7596f703-c6ab-316c-7c80-3f4e3daa174e"),
|
||||
Id = new Guid("ddb2d04d-19b0-20b1-2639-b157d12a9eec"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000001"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000035"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000037"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("aa120c36-488e-34ba-e10a-2ac45e46e119"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000002"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000021"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("e4648655-7267-141e-51fa-f7d5fe3d2356"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000002"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000022"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("d427b9ea-2c9b-d93e-0843-02dfa1ae8887"),
|
||||
Id = new Guid("320ba3fb-666c-ce57-3e61-95c9824c4048"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
@@ -752,6 +755,22 @@ namespace Distribution.Migrations
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000002"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000024"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("357c36de-9b6c-1c9a-4926-c18a9bccf0ca"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000002"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000025"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("1b126bb0-e582-3e84-4728-a7ab5628fe7b"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000002"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000013"),
|
||||
@@ -770,25 +789,25 @@ namespace Distribution.Migrations
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000002"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000031"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000033"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("b7dee5e3-f329-95a5-75c1-d02c3e40e229"),
|
||||
Id = new Guid("0f291826-e169-c06a-0735-bd06cbb2062d"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000002"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000035"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000037"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("e5506b30-527d-08f9-6caa-8a45d3d02999"),
|
||||
Id = new Guid("10dd0249-ce04-c5d9-8f94-ccb348c1d979"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000002"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000024"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000026"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("357c36de-9b6c-1c9a-4926-c18a9bccf0ca"),
|
||||
Id = new Guid("f50b201c-6c8d-0df8-85ad-08cb137bc213"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
@@ -809,18 +828,10 @@ namespace Distribution.Migrations
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000003"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000021"),
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000002"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000018"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("0dd5c943-3ace-0560-6fbc-54d8e37ce014"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000003"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000022"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("ca97fb3d-132c-d8a5-4ab8-f7b8fc60f5e5"),
|
||||
Id = new Guid("f2c14d55-d557-5cd2-a70a-4a6f58933436"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
@@ -832,6 +843,22 @@ namespace Distribution.Migrations
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000003"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000024"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("73e00ce7-c124-4677-4369-1eb9876852c0"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000003"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000025"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("929950da-d51b-ad5d-c4e5-55175908fb43"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000003"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000013"),
|
||||
@@ -850,25 +877,25 @@ namespace Distribution.Migrations
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000003"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000031"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000033"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("7780d277-9597-84ff-1a1a-f63fc6b4d3cb"),
|
||||
Id = new Guid("d49f086e-30f0-6ce8-ae96-4395d5e04472"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000003"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000035"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000037"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("1e7fe1b6-5150-4b99-38ed-088d9495cfd8"),
|
||||
Id = new Guid("a41f94ba-c24d-93dc-9f25-c462df19785a"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000003"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000024"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000026"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("73e00ce7-c124-4677-4369-1eb9876852c0"),
|
||||
Id = new Guid("a9a49749-f99d-3166-5d92-b7e380fcabb2"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
@@ -888,6 +915,14 @@ namespace Distribution.Migrations
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000003"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000018"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("d92d572f-f1bf-9e7d-1d4d-7c7b0d575c8e"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000003"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000009"),
|
||||
@@ -896,6 +931,14 @@ namespace Distribution.Migrations
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000003"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000019"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("f367f6f6-3d26-7a6c-888b-da1a240b59af"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000003"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000017"),
|
||||
@@ -906,33 +949,17 @@ namespace Distribution.Migrations
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000003"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000032"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000034"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("8083c659-9506-0bd9-b793-3d6148ce9192"),
|
||||
Id = new Guid("b17df3df-022c-c903-6ce0-e6423e648dc8"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000003"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000033"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000035"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("d49f086e-30f0-6ce8-ae96-4395d5e04472"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000004"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000021"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("5c00e972-a697-d71a-6545-1fd8618826cf"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000004"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000022"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("e9b6af8e-7947-7290-9c92-2d9b6239f395"),
|
||||
Id = new Guid("1e7fe1b6-5150-4b99-38ed-088d9495cfd8"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
@@ -944,6 +971,22 @@ namespace Distribution.Migrations
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000004"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000024"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("7b4f3482-b8bd-d348-e4c1-ae6dc93a3771"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000004"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000025"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("04ece77e-b36f-8ed0-4ef9-2f6962b6f9c6"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000004"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000013"),
|
||||
@@ -962,25 +1005,25 @@ namespace Distribution.Migrations
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000004"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000031"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000033"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("a701da28-aa75-3c1d-abdb-4317404f8df6"),
|
||||
Id = new Guid("7c392a13-1a57-0d85-dffa-2fb9e892abe9"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000004"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000035"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000037"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("9afe507e-191e-bdee-0c5a-cfbcbf1efb3e"),
|
||||
Id = new Guid("d1dcef02-d4c3-caba-372f-dab82011e9c1"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000004"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000024"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000026"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("7b4f3482-b8bd-d348-e4c1-ae6dc93a3771"),
|
||||
Id = new Guid("bf2a2f96-04ed-905d-5a8a-9047d814aa3e"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
@@ -1000,6 +1043,14 @@ namespace Distribution.Migrations
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000004"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000018"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("5e10d61c-2d7a-3388-722f-45232d2cfb71"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000004"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000009"),
|
||||
@@ -1008,6 +1059,14 @@ namespace Distribution.Migrations
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000004"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000019"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("d8257363-d838-fe7e-57a5-51b626dbfc83"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000004"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000017"),
|
||||
@@ -1018,17 +1077,17 @@ namespace Distribution.Migrations
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000004"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000032"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000034"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("2c025310-765f-7d07-ab9a-50524c13d1d3"),
|
||||
Id = new Guid("97708eed-6ce4-7710-abde-01d00c0c879b"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000004"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000033"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000035"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("7c392a13-1a57-0d85-dffa-2fb9e892abe9"),
|
||||
Id = new Guid("9afe507e-191e-bdee-0c5a-cfbcbf1efb3e"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
@@ -1050,9 +1109,9 @@ namespace Distribution.Migrations
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000004"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000034"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000016"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("97708eed-6ce4-7710-abde-01d00c0c879b"),
|
||||
Id = new Guid("e0b27435-f505-43f5-7ed9-4711f2248495"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
@@ -1064,14 +1123,6 @@ namespace Distribution.Migrations
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000004"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000037"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("d1dcef02-d4c3-caba-372f-dab82011e9c1"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000004"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000038"),
|
||||
@@ -1081,18 +1132,18 @@ namespace Distribution.Migrations
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000021"),
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000004"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000039"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("08de9913-8cfc-4ab4-2cb9-2bc9217fbb6e"),
|
||||
Id = new Guid("a66f3854-5a8f-95c2-cb1c-b2b2e67ee226"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000022"),
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000004"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000040"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("f3c8f315-3b2f-f923-e3cd-f3e575010138"),
|
||||
Id = new Guid("14bcecd1-b1a2-6da3-155a-233611484467"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
@@ -1112,6 +1163,22 @@ namespace Distribution.Migrations
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000025"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("1d227ae7-0eb9-c742-41df-3472718cb077"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000026"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("4d287b19-ba40-7d4d-3d39-291be64197f8"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000013"),
|
||||
@@ -1144,6 +1211,22 @@ namespace Distribution.Migrations
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000017"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("ee175c87-23bd-5529-ed18-6ce46985c6ed"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000018"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("2ae2eba4-4427-ab1e-cf03-e59a3925ef89"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000005"),
|
||||
@@ -1208,22 +1291,6 @@ namespace Distribution.Migrations
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000017"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("ee175c87-23bd-5529-ed18-6ce46985c6ed"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000018"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("2ae2eba4-4427-ab1e-cf03-e59a3925ef89"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000019"),
|
||||
@@ -1240,6 +1307,22 @@ namespace Distribution.Migrations
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000021"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("08de9913-8cfc-4ab4-2cb9-2bc9217fbb6e"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000022"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("f3c8f315-3b2f-f923-e3cd-f3e575010138"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000001"),
|
||||
@@ -1250,25 +1333,9 @@ namespace Distribution.Migrations
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000025"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000027"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("1d227ae7-0eb9-c742-41df-3472718cb077"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000031"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("69f93a29-1df1-7b20-9d5c-94c264f8c4b8"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000032"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("070813f7-4053-2889-0d14-d0d5834208c2"),
|
||||
Id = new Guid("43a4c7b4-a50f-aa37-b147-a30636d9d65f"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
@@ -1320,6 +1387,22 @@ namespace Distribution.Migrations
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000039"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("aeb29e0e-e819-1446-6cc6-1fb64b5ecc6b"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000005"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000040"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("e18a6eca-1009-bcc6-35e9-ead943c1649f"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000006"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000001"),
|
||||
@@ -1622,6 +1705,22 @@ namespace Distribution.Migrations
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("1d6273d4-db6a-d70e-4b5f-98cb634d668a"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000006"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000039"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("3f32cdac-6925-23f2-064e-8226d781d4a3"),
|
||||
IsActive = true
|
||||
},
|
||||
new
|
||||
{
|
||||
FK_RoleId = new Guid("10000000-0000-0000-0000-000000000006"),
|
||||
FK_PermissionId = new Guid("20000000-0000-0000-0000-000000000040"),
|
||||
CreatedAt = new DateTime(2026, 8, 18, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
Id = new Guid("dc3dd9ff-b123-a8d4-dcea-b4f5f8ff171e"),
|
||||
IsActive = true
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1679,6 +1778,81 @@ namespace Distribution.Migrations
|
||||
b.ToTable("SaleRoutes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.StockBalance", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("FK_ItemId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("LocationId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<int>("LocationType")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<decimal>("Quantity")
|
||||
.HasColumnType("decimal(65,30)");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("FK_ItemId", "LocationId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("StockBalances");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.StockMovement", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("CreatedBy")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("FK_ItemId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid?>("FromLocationId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<int?>("FromLocationType")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<decimal>("Quantity")
|
||||
.HasColumnType("decimal(65,30)");
|
||||
|
||||
b.Property<string>("ReasonCode")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<Guid?>("ReferenceId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid?>("ToLocationId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<int?>("ToLocationType")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("FK_ItemId");
|
||||
|
||||
b.ToTable("StockMovements");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.Store", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -1691,12 +1865,18 @@ namespace Distribution.Migrations
|
||||
b.Property<decimal>("CreditAmount")
|
||||
.HasColumnType("decimal(65,30)");
|
||||
|
||||
b.Property<decimal>("CreditLimit")
|
||||
.HasColumnType("decimal(65,30)");
|
||||
|
||||
b.Property<Guid>("FK_RouteId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<int>("KycStatus")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<decimal>("Latitude")
|
||||
.HasColumnType("decimal(65,30)");
|
||||
|
||||
@@ -1706,10 +1886,16 @@ namespace Distribution.Migrations
|
||||
b.Property<decimal>("OneTimeAmount")
|
||||
.HasColumnType("decimal(65,30)");
|
||||
|
||||
b.Property<decimal>("OutstandingBalance")
|
||||
.HasColumnType("decimal(65,30)");
|
||||
|
||||
b.Property<string>("OwnerName")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Segment")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("StoreAddress")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
@@ -1725,6 +1911,39 @@ namespace Distribution.Migrations
|
||||
b.ToTable("Stores");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.StoreKycDocument", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("DocumentType")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<Guid>("FK_StoreId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<string>("FileReference")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<DateTime>("UploadedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("FK_StoreId");
|
||||
|
||||
b.ToTable("StoreKycDocuments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.SubCategory", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -2092,6 +2311,28 @@ namespace Distribution.Migrations
|
||||
b.Navigation("Region");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.StockBalance", b =>
|
||||
{
|
||||
b.HasOne("Distribution.Models.Item", "Item")
|
||||
.WithMany()
|
||||
.HasForeignKey("FK_ItemId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Item");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.StockMovement", b =>
|
||||
{
|
||||
b.HasOne("Distribution.Models.Item", "Item")
|
||||
.WithMany()
|
||||
.HasForeignKey("FK_ItemId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Item");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.Store", b =>
|
||||
{
|
||||
b.HasOne("Distribution.Models.Route", "Route")
|
||||
@@ -2103,6 +2344,17 @@ namespace Distribution.Migrations
|
||||
b.Navigation("Route");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.StoreKycDocument", b =>
|
||||
{
|
||||
b.HasOne("Distribution.Models.Store", "Store")
|
||||
.WithMany()
|
||||
.HasForeignKey("FK_StoreId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Store");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.SubCategory", b =>
|
||||
{
|
||||
b.HasOne("Distribution.Models.Category", "Category")
|
||||
|
||||
@@ -4,5 +4,6 @@ namespace Distribution.Models
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public int? ExternalErpId { get; set; }
|
||||
public bool AllowCustomerReturn { get; set; } = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public enum LocationType
|
||||
{
|
||||
Warehouse,
|
||||
Vehicle
|
||||
}
|
||||
|
||||
public enum KycStatus
|
||||
{
|
||||
Pending,
|
||||
Verified,
|
||||
Rejected }
|
||||
|
||||
public enum MovementType
|
||||
{
|
||||
Sale,
|
||||
CustomerReturn,
|
||||
Transfer,
|
||||
Damage,
|
||||
Adjustment
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public class StockBalance
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
public Guid FK_ItemId { get; set; }
|
||||
[ForeignKey("FK_ItemId")]
|
||||
public Item Item { get; set; }
|
||||
|
||||
public Guid LocationId { get; set; }
|
||||
public LocationType LocationType { get; set; }
|
||||
|
||||
public decimal Quantity { get; set; }
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.Now;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public class StockMovement
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
public Guid FK_ItemId { get; set; }
|
||||
[ForeignKey("FK_ItemId")]
|
||||
public Item Item { get; set; }
|
||||
|
||||
public Guid? FromLocationId { get; set; }
|
||||
public LocationType? FromLocationType { get; set; }
|
||||
|
||||
public Guid? ToLocationId { get; set; }
|
||||
public LocationType? ToLocationType { get; set; }
|
||||
|
||||
public decimal Quantity { get; set; }
|
||||
public MovementType Type { get; set; }
|
||||
|
||||
public string? ReasonCode { get; set; }
|
||||
public Guid? ReferenceId { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.Now;
|
||||
public Guid CreatedBy { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,10 @@ namespace Distribution.Models
|
||||
public decimal Longitude { get; set; } = 0;
|
||||
public decimal OneTimeAmount { get; set; } = 0;
|
||||
public decimal CreditAmount { get; set; } = 0;
|
||||
public decimal CreditLimit { get; set; } = 0;
|
||||
public decimal OutstandingBalance { get; set; } = 0;
|
||||
public String? Segment { get; set; } //Gold ,silver,key Account,high risk
|
||||
public KycStatus KycStatus { get; set; } = KycStatus.Pending;
|
||||
|
||||
public Guid FK_RouteId { get; set; }
|
||||
[ForeignKey("FK_RouteId")]
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public class StoreKycDocument : Template
|
||||
{
|
||||
public Guid FK_StoreId { get; set; }
|
||||
[ForeignKey("FK_StoreId")]
|
||||
public Store Store { get; set; }
|
||||
public String DocumentType { get; set; }
|
||||
public String FileReference { get; set; }
|
||||
public DateTime UploadedAt { get; set; } = DateTime.Now;
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,12 @@ using Distribution.Features.Region;
|
||||
using Distribution.Features.Role;
|
||||
using Distribution.Features.Route;
|
||||
using Distribution.Features.Store;
|
||||
using Distribution.Features.Store.Kyc;
|
||||
using Distribution.Features.Sync;
|
||||
using Distribution.Features.Uom;
|
||||
using Distribution.Features.User;
|
||||
using Distribution.Features.Vehicle;
|
||||
using Distribution.Features.Stock;
|
||||
using Distribution.Features.Warehouse;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Infra.UoW;
|
||||
@@ -44,6 +46,8 @@ builder.Services.AddScoped<ISubCategoryRepository, SubCategoryRepository>();
|
||||
builder.Services.AddScoped<IBrandRepository, BrandRepository>();
|
||||
builder.Services.AddScoped<IUomRepository, UomRepository>();
|
||||
builder.Services.AddScoped<IItemRepository, ItemRepository>();
|
||||
builder.Services.AddScoped<IStockRepository, StockRepository>();
|
||||
builder.Services.AddScoped<IStoreKycDocumentRepository, StoreKycDocumentRepository>();
|
||||
|
||||
builder.Services.AddScoped<RegionService>();
|
||||
builder.Services.AddScoped<RouteService>();
|
||||
@@ -59,6 +63,8 @@ builder.Services.AddScoped<BrandService>();
|
||||
builder.Services.AddScoped<UomService>();
|
||||
builder.Services.AddScoped<ItemService>();
|
||||
builder.Services.AddScoped<SyncService>();
|
||||
builder.Services.AddScoped<StockService>();
|
||||
builder.Services.AddScoped<StoreKycDocumentService>();
|
||||
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
builder.Services.AddScoped<ICurrentUserService, CurrentUserService>();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Server=187.127.102.190;Port=3306;Database=distribution_db;User=kalana;Password=kalana@hexdive"
|
||||
"DefaultConnection": "Server=187.127.102.190;Port=3306;Database=distribution;User=kalana;Password=kalana@hexdive"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
@@ -14,5 +14,8 @@
|
||||
"Issuer": "Distribution.Api",
|
||||
"Audience": "Distribution.Api",
|
||||
"ExpiryMinutes": 120
|
||||
},
|
||||
"StoreManagement": {
|
||||
"UnverifiedKycOrderThreshold": 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,11 @@
|
||||
|
||||
## Goal
|
||||
|
||||
|
||||
Close every hole identified in the currently-implemented modules (Region, Route, Vehicle, Store,
|
||||
Warehouse, Order, User/Role/Permission) before any feature from [mainPlan.md](./mainPlan.md)
|
||||
is built on top of them. This file is the running list of "Partially implemented" and foundational
|
||||
"Not implemented" items pulled from that reference doc that block later steps, not just cosmetic
|
||||
cleanup.
|
||||
|
||||
---
|
||||
|
||||
@@ -15,45 +19,94 @@
|
||||
|
||||
### 1. Route DTO — Missing RegionId
|
||||
|
||||
|
||||
`RouteDTOs.cs` does not expose `RegionId` on create/update, so a Route cannot be linked to a
|
||||
Region via the API even though `Route.FK_RegionId` exists on the model. Blocks §2/§3 of the
|
||||
function reference (`assignAreaToRegion`, `getRegionHierarchy`) and any region-scoped authorization.
|
||||
|
||||
### 2. Store DTO — Missing RouteId
|
||||
|
||||
|
||||
`StoreDTOs.cs` does not expose `RouteId` on create/update, so a Store cannot be linked to a Route
|
||||
via the API even though `Store.FK_RouteId` exists on the model. Blocks `assignOutletsToRoute` (§3)
|
||||
and route/region-scoped authorization for Stores and Orders.
|
||||
|
||||
### 3. Role Controller — Missing Authorization
|
||||
|
||||
|
||||
`RoleController`'s GET endpoints carry only `[Authorize]`, with no `[Authorize(Policy = "...")]`
|
||||
per action like every other controller. Any authenticated user — including a SalesRep — can read
|
||||
every role and its full permission list today.
|
||||
|
||||
### 4. Secrets & Configuration Management
|
||||
|
||||
|
||||
JWT signing key and DB connection string currently live in `appsettings.json`. Move both to
|
||||
environment variables or user secrets before any non-local environment is stood up.
|
||||
|
||||
### 5. Input Validation
|
||||
|
||||
|
||||
No DTO in any feature carries validation attributes (`[Required]`, `[MaxLength]`, range checks,
|
||||
etc.). Bad data (empty names, negative levels, malformed emails) can enter the system silently
|
||||
through any existing POST/PUT endpoint.
|
||||
|
||||
### 6. Item Model — Missing Properties
|
||||
|
||||
`Item : Template` has no properties (no SKU, name, unit of measure, category). `OrderItem`
|
||||
references `Item`, but there's nothing there to reference. Fully scoped as its own step in
|
||||
[01_item_catalog.md](./01_item_catalog.md) — tracked here only as the blocking dependency.
|
||||
|
||||
### 7. Order Has No Status / State Machine
|
||||
|
||||
`Order` has no status field, so `updateOrderStatus`, `approveOrder`/`rejectOrder`, and
|
||||
`convertOrderToInvoice` (§1 of the function reference) have nowhere to write to. Every order-status
|
||||
consumer downstream (delivery ride execution, sale ride execution, reconciliation, agent controls)
|
||||
depends on this existing first.
|
||||
|
||||
### 8. No Credit Limit / Segment Fields on Store
|
||||
|
||||
`validateCreditLimit`, `updateStoreCreditLimit`, `getStoreOutstandingBalance`, and `classifyStore`
|
||||
(§1, §4) all need fields that don't exist on `Store` today (credit limit, outstanding balance,
|
||||
segment). Not scoped to a single feature file yet — flagged here so it isn't silently assumed by
|
||||
a later step.
|
||||
|
||||
### 9. No Area Entity Between Region and Route
|
||||
|
||||
`assignAreaToRegion`, `createRoute(name, areaId)` (§2, §3) assume an `Area` level between Region
|
||||
and Route that doesn't exist in the schema — only `Region → Route → Store` exists. Confirm with
|
||||
the user whether `Area` is a real requirement or whether Region/Route is the intended two-level
|
||||
hierarchy before building anything that assumes a third level.
|
||||
|
||||
### 10. No Driver Entity
|
||||
|
||||
`DeliveryRun.FK_DriverId`/`Driver` navigation is commented out in the model. `assignDriverToVehicle`,
|
||||
`checkDriverCompliance` (§5) and any ride that needs a named driver depend on this existing.
|
||||
Blocks [03_ride_lifecycle.md](./03_ride_lifecycle.md).
|
||||
|
||||
---
|
||||
|
||||
## Scope (What This Step Does NOT Include)
|
||||
|
||||
|
||||
- No new feature modules (Procurement, Finance, Promotions, etc.) — those are separate steps,
|
||||
see [mainPlan.md](./mainPlan.md) for the full list and their own
|
||||
numbered files below.
|
||||
- No UI/client changes.
|
||||
- No changes to authentication (JWT) — only authorization policy coverage on `RoleController`.
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
|
||||
- A gap fix must not change the shape of an already-shipped response DTO in a breaking way without
|
||||
flagging it — additive fields only unless explicitly agreed.
|
||||
- Soft-delete (`IsActive`) pattern must be preserved on every entity touched.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
|
||||
- Is `Area` a real business concept (a level between Region and Route), or should
|
||||
`createRoute`/`assignAreaToRegion` in the function reference be reinterpreted as Region-only?
|
||||
- Should `Store.CreditLimit` and `Store.OutstandingBalance` be added now as part of gap-fixing, or
|
||||
deferred to a dedicated Finance step (§13)?
|
||||
- Does `Order.Status` need a fixed enum now (`pending/approved/picked/dispatched/delivered/invoiced`
|
||||
per §1), or a smaller interim set until the fuller order lifecycle is built?
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,62 +1,258 @@
|
||||
# 02 — Stock Ledger
|
||||
|
||||
**Status**: Not Started
|
||||
**Status**: Done
|
||||
**Depends On**: 01 — Item Catalog
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
|
||||
Introduce stock tracking per location. Every warehouse and vehicle holds a quantity of each item.
|
||||
Stock is never edited directly — all changes happen through movement transactions.
|
||||
The current balance is always the materialized sum kept in sync with the transaction log.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
Two tables work together:
|
||||
|
||||
- **StockBalance** — one row per (ItemId, LocationId). Updated atomically on every movement. Used for fast reads and enforcing stock limits (e.g. driver can't sell more than loaded).
|
||||
- **StockMovement** — append-only transaction log. Source of truth. Used for audit, reconciliation at ride close (Step 07), and re-deriving balance if drift is ever suspected.
|
||||
|
||||
Both are updated in the same UoW commit. They must always agree.
|
||||
|
||||
**Location abstraction:** A single `LocationId` (+ `LocationType` enum) covers both warehouses and vehicles. Same query pattern regardless of location type.
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Stock Balance (per location)
|
||||
### New Enum — LocationType
|
||||
|
||||
```csharp
|
||||
public enum LocationType { Warehouse, Vehicle }
|
||||
```
|
||||
|
||||
### New Enum — MovementType
|
||||
|
||||
### Stock Movement Transaction
|
||||
```csharp
|
||||
public enum MovementType
|
||||
{
|
||||
Sale,
|
||||
CustomerReturn,
|
||||
Transfer, // covers: warehouse→vehicle (load), vehicle→warehouse (driver return), warehouse→warehouse
|
||||
Damage,
|
||||
Adjustment
|
||||
}
|
||||
```
|
||||
|
||||
### New Entity — StockBalance
|
||||
|
||||
One row per (ItemId, LocationId). Updated on every movement.
|
||||
|
||||
### Location Abstraction (Warehouse vs Vehicle)
|
||||
| Column | Type | Notes |
|
||||
|---|---|---|
|
||||
| Id | Guid | PK |
|
||||
| FK_ItemId | Guid | FK → Item |
|
||||
| LocationId | Guid | FK → Warehouse or Vehicle |
|
||||
| LocationType | LocationType | Warehouse or Vehicle |
|
||||
| Quantity | decimal | Current balance. Never goes below 0 |
|
||||
| UpdatedAt | DateTime | Last movement time |
|
||||
|
||||
No `IsActive` / no soft delete — balance rows are permanent once created.
|
||||
Composite unique index on `(FK_ItemId, LocationId)`.
|
||||
|
||||
### New Entity — StockMovement
|
||||
|
||||
Append-only. Never updated or deleted.
|
||||
|
||||
| Column | Type | Notes |
|
||||
|---|---|---|
|
||||
| Id | Guid | PK |
|
||||
| FK_ItemId | Guid | FK → Item |
|
||||
| FromLocationId | Guid? | Null for Sale, Damage, Adjustment (single-location) |
|
||||
| FromLocationType | LocationType? | Null when FromLocationId is null |
|
||||
| ToLocationId | Guid? | Null for Sale, Damage (stock leaves system) |
|
||||
| ToLocationType | LocationType? | Null when ToLocationId is null |
|
||||
| Quantity | decimal | Always positive — direction implied by movement type |
|
||||
| Type | MovementType | |
|
||||
| ReasonCode | string? | Required for Adjustment and Damage |
|
||||
| ReferenceId | Guid? | Links to the originating business event (OrderId, RideId, etc.) |
|
||||
| CreatedAt | DateTime | |
|
||||
| CreatedBy | Guid | UserId of who triggered this |
|
||||
|
||||
---
|
||||
|
||||
## Category Configuration
|
||||
|
||||
`Category` gets one new flag to support configurable customer returns:
|
||||
|
||||
| Column | Type | Notes |
|
||||
|---|---|---|
|
||||
| AllowCustomerReturn | bool | Default false. If false, CustomerReturn movements are rejected for items in this category |
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
Base route: `/api/stock`
|
||||
|
||||
### Query
|
||||
|
||||
| Method | Route | Description |
|
||||
|---|---|---|
|
||||
| GET | `/balances` | Get all stock balances (paginated). Filter by `locationId`, `locationType`, `itemId` |
|
||||
| GET | `/balances/{itemId}/total` | Total stock of one item across all locations |
|
||||
| GET | `/movements` | Get movement history (paginated). Filter by `locationId`, `itemId`, `type`, `from`, `to` |
|
||||
|
||||
### Write
|
||||
|
||||
| Method | Route | Description |
|
||||
|---|---|---|
|
||||
| POST | `/transfer` | Move stock between two locations |
|
||||
| POST | `/damage` | Record damaged stock at a location |
|
||||
| POST | `/customer-return` | Record a customer return to a vehicle |
|
||||
| POST | `/adjustment` | Manual correction at a location (requires reason code) |
|
||||
|
||||
---
|
||||
|
||||
## Request / Response Shapes
|
||||
|
||||
### GET `/balances` response (paginated)
|
||||
|
||||
```json
|
||||
{
|
||||
"page": 1,
|
||||
"pageSize": 20,
|
||||
"total": 85,
|
||||
"items": [
|
||||
{
|
||||
"itemId": "...",
|
||||
"itemName": "Sprite 330ml",
|
||||
"locationId": "...",
|
||||
"locationType": "Warehouse",
|
||||
"quantity": 120,
|
||||
"updatedAt": "2026-08-19T10:00:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### GET `/balances/{itemId}/total` response
|
||||
|
||||
```json
|
||||
{
|
||||
"itemId": "...",
|
||||
"itemName": "Sprite 330ml",
|
||||
"totalQuantity": 340,
|
||||
"breakdown": [
|
||||
{ "locationId": "...", "locationType": "Warehouse", "quantity": 200 },
|
||||
{ "locationId": "...", "locationType": "Vehicle", "quantity": 140 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### GET `/movements` response (paginated)
|
||||
|
||||
```json
|
||||
{
|
||||
"page": 1,
|
||||
"pageSize": 20,
|
||||
"total": 412,
|
||||
"items": [
|
||||
{
|
||||
"id": "...",
|
||||
"itemId": "...",
|
||||
"itemName": "Sprite 330ml",
|
||||
"fromLocationId": "...",
|
||||
"fromLocationType": "Warehouse",
|
||||
"toLocationId": "...",
|
||||
"toLocationType": "Vehicle",
|
||||
"quantity": 50,
|
||||
"type": "Transfer",
|
||||
"reasonCode": null,
|
||||
"referenceId": "...",
|
||||
"createdAt": "2026-08-19T08:00:00",
|
||||
"createdBy": "..."
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### POST `/transfer` request
|
||||
|
||||
```json
|
||||
{
|
||||
"itemId": "...",
|
||||
"fromLocationId": "...",
|
||||
"fromLocationType": "Warehouse",
|
||||
"toLocationId": "...",
|
||||
"toLocationType": "Vehicle",
|
||||
"quantity": 50,
|
||||
"referenceId": "..."
|
||||
}
|
||||
```
|
||||
|
||||
### POST `/damage` request
|
||||
|
||||
```json
|
||||
{
|
||||
"itemId": "...",
|
||||
"locationId": "...",
|
||||
"locationType": "Warehouse",
|
||||
"quantity": 3,
|
||||
"reasonCode": "Broken during handling"
|
||||
}
|
||||
```
|
||||
|
||||
### POST `/customer-return` request
|
||||
|
||||
```json
|
||||
{
|
||||
"itemId": "...",
|
||||
"vehicleId": "...",
|
||||
"quantity": 2,
|
||||
"referenceId": "..."
|
||||
}
|
||||
```
|
||||
|
||||
### POST `/adjustment` request
|
||||
|
||||
```json
|
||||
{
|
||||
"itemId": "...",
|
||||
"locationId": "...",
|
||||
"locationType": "Warehouse",
|
||||
"quantity": -2,
|
||||
"reasonCode": "Counting error corrected after audit"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Fraud / Control Considerations
|
||||
|
||||
|
||||
1. `StockBalance.Quantity` must never go below 0. Any movement that would result in negative stock is rejected with a 400.
|
||||
2. `Adjustment` and `Damage` require a non-empty `ReasonCode`.
|
||||
3. `CustomerReturn` is rejected if `Category.AllowCustomerReturn = false` for the item's category.
|
||||
4. `Transfer` debits `FromLocation` and credits `ToLocation` atomically.
|
||||
5. Both `StockBalance` update and `StockMovement` insert happen in the same transaction — never one without the other.
|
||||
6. `StockMovement` rows are never updated or deleted. Wrong entries are corrected by a counter-movement.
|
||||
7. All list endpoints return paginated responses.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
|
||||
None — all design decisions resolved in planning discussion.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
**Feature folder:** `Features/Stock/`
|
||||
**Entities:** `StockBalance.cs`, `StockMovement.cs` in `Models/`
|
||||
**Migration:** Add after entities and DbContext are updated
|
||||
**Service layer:** One `StockService` handles all movement types. Each write method: begins transaction → validates stock → inserts movement → updates balance → commits.
|
||||
**Category change:** Add `AllowCustomerReturn` column to existing `Category` entity and migration.
|
||||
|
||||
@@ -7,13 +7,20 @@
|
||||
|
||||
## Goal
|
||||
|
||||
|
||||
Turn the currently-unused `DeliveryRun` model into a working ride: a session that turns a vehicle
|
||||
into a temporary warehouse. Covers `assignVehicleToRoute`, `assignDriverToVehicle`,
|
||||
`checkDriverCompliance` from [mainPlan.md §5](./mainPlan.md#5-vehicle--fleet-management)
|
||||
and `planDeliveryRoute` from §3.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
|
||||
`Models/DeliveryRun.cs` exists (`FK_VehicleId`, `FK_RouteId`, `Meterstart`, `Meterend`) but has no
|
||||
repository, service, or controller — it is completely unwired today, per [mainPlan.md](./mainPlan.md)'s
|
||||
"Known Gaps" table. `FK_DriverId`/`Driver` navigation is commented out because no `Driver` entity
|
||||
exists yet (gap #10 in [00_existing_gaps.md](./00_existing_gaps.md)) — this step is blocked on that
|
||||
being resolved first if driver assignment is required at ride-open time.
|
||||
|
||||
---
|
||||
|
||||
@@ -21,39 +28,52 @@
|
||||
|
||||
### Ride (extending DeliveryRun)
|
||||
|
||||
|
||||
Add a `Status` field to `DeliveryRun` for the state machine below, and a `RideType` field
|
||||
(`Delivery` | `Sale`).
|
||||
|
||||
### Ride State Machine
|
||||
|
||||
|
||||
`Draft → Open → Active → Closed`. A vehicle can only be on one `Open`/`Active` ride at a time —
|
||||
enforced in the service layer, not the database.
|
||||
|
||||
### Ride Type (Delivery vs Sale)
|
||||
|
||||
|
||||
`Delivery` rides fulfill pre-confirmed orders (see [05_delivery_ride.md](./05_delivery_ride.md));
|
||||
`Sale` rides create invoices on the spot with no pre-orders (see [06_sale_ride.md](./06_sale_ride.md)).
|
||||
Both share the same ride lifecycle and loading event.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `assignVehicleToRoute(vehicleId, routeId)` (§5) | Implicit in `POST /api/rides` — a ride binds a vehicle to a route for its duration |
|
||||
| `assignDriverToVehicle(vehicleId, driverId)` (§5) | `PUT /api/rides/{id}/driver` — blocked on Driver entity, see gap #10 |
|
||||
| `checkDriverCompliance(driverId)` (§5) | Called internally before a ride can move `Draft → Open` |
|
||||
| `planDeliveryRoute(warehouseId, orderIds[])` (§3) | `POST /api/rides` (for `RideType = Delivery`) |
|
||||
| — | `POST /api/rides/{id}/open`, `POST /api/rides/{id}/close` — state transitions |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
|
||||
- A ride cannot open without a compliant driver (once Driver entity exists) and a vehicle not
|
||||
already on an open ride.
|
||||
- Opening a ride creates the vehicle's stock ledger location (see [02_stock_ledger.md](./02_stock_ledger.md)).
|
||||
|
||||
---
|
||||
|
||||
## Fraud / Control Considerations
|
||||
|
||||
|
||||
- `checkDriverCompliance` failures must block ride open, not just warn.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
|
||||
- Is a `Driver` a new entity distinct from `User`, or is "driver" just a `User` with a specific
|
||||
`Role`? Affects whether gap #10 needs a new table or just a role addition.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -7,13 +7,18 @@
|
||||
|
||||
## Goal
|
||||
|
||||
|
||||
Move stock from a source warehouse onto an opened ride's vehicle, with two-party confirmation.
|
||||
Covers the dispatch half of [mainPlan.md §6](./mainPlan.md#6-warehouse-management):
|
||||
`generatePickList`, `confirmPicking`, `packOrder`, `buildDispatchLoad`, and `calculateLoadPlan`
|
||||
from §5.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
|
||||
This is the first place the [02_stock_ledger.md](./02_stock_ledger.md) movement model gets
|
||||
exercised end-to-end: a `Load` movement debits the warehouse and credits the vehicle location
|
||||
created when the ride opened ([03_ride_lifecycle.md](./03_ride_lifecycle.md)).
|
||||
|
||||
---
|
||||
|
||||
@@ -21,39 +26,55 @@
|
||||
|
||||
### Loading Sheet
|
||||
|
||||
|
||||
Header record: `RideId`, `SourceWarehouseId`, `Status` (`Pending`, `DispatchConfirmed`,
|
||||
`ReceiptConfirmed`, `DiscrepancyFlagged`, `Resolved`).
|
||||
|
||||
### Loading Line Item
|
||||
|
||||
|
||||
`LoadingSheetId`, `ItemId`, `QuantityDispatched` (set by warehouse manager), `QuantityReceived`
|
||||
(set by driver) — a mismatch between the two is a discrepancy.
|
||||
|
||||
### Two-Party Confirmation
|
||||
|
||||
|
||||
Warehouse manager confirms what was dispatched (`generatePickList` → `confirmPicking` →
|
||||
`packOrder`); driver separately confirms what was received onto the vehicle. Both confirmations are
|
||||
required before the ride can move `Open → Active`.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `generatePickList(orderIds[])` (§6) | `POST /api/loading-sheets` |
|
||||
| `confirmPicking(pickListId, pickedItems[])` (§6) | `POST /api/loading-sheets/{id}/confirm-dispatch` |
|
||||
| `packOrder(orderId, packageDetails)` (§6) | `POST /api/loading-sheets/{id}/pack` |
|
||||
| `buildDispatchLoad(vehicleId, orderIds[])` (§6) | Groups multiple orders into one loading sheet |
|
||||
| `calculateLoadPlan(vehicleId, orderIds[])` (§5) | Validates total volume/weight against vehicle capacity before dispatch confirmation |
|
||||
| — | `POST /api/loading-sheets/{id}/confirm-receipt` — driver-side confirmation |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
|
||||
- `QuantityDispatched != QuantityReceived` on any line flags the sheet `DiscrepancyFlagged` and
|
||||
blocks ride activation until resolved.
|
||||
- `calculateLoadPlan` must reject a load exceeding vehicle capacity before dispatch is confirmed,
|
||||
not after.
|
||||
|
||||
---
|
||||
|
||||
## Fraud / Control Considerations
|
||||
|
||||
|
||||
- Discrepancies must be immediately visible, not silently averaged into stock adjustments — see
|
||||
[09_fraud_controls.md](./09_fraud_controls.md).
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
|
||||
- Does `Vehicle` need a `Capacity` field added now (volume/weight), or is `calculateLoadPlan`
|
||||
deferred until that's available?
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -7,13 +7,18 @@
|
||||
|
||||
## Goal
|
||||
|
||||
|
||||
Execute pre-confirmed orders against a loaded, active delivery ride. Covers the order-status half
|
||||
of [mainPlan.md §1](./mainPlan.md#1-order-management):
|
||||
`updateOrderStatus`, `convertOrderToInvoice`, `createBackorder`. Requires `Order.Status` to exist
|
||||
first — see gap #7 in [00_existing_gaps.md](./00_existing_gaps.md).
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
|
||||
Today `Order` has no status and no link to a ride. This step is where the order lifecycle
|
||||
(`pending → approved → picked → dispatched → delivered → invoiced`) actually gets driven, once the
|
||||
order has been picked/loaded ([04_loading_event.md](./04_loading_event.md)).
|
||||
|
||||
---
|
||||
|
||||
@@ -21,39 +26,51 @@
|
||||
|
||||
### Delivery Task (per order on ride)
|
||||
|
||||
|
||||
`RideId`, `OrderId`, `Sequence` (visit order), `Status` (`Pending`, `Delivered`, `PartiallyDelivered`,
|
||||
`Failed`).
|
||||
|
||||
### Delivery Outcome
|
||||
|
||||
|
||||
Per delivery task: quantities actually delivered per line, vs. ordered — feeds `createBackorder`
|
||||
for the shortfall.
|
||||
|
||||
### Customer Confirmation
|
||||
|
||||
|
||||
Signature or OTP captured against the delivery task before it can be marked complete.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `updateOrderStatus(orderId, status)` (§1) | `PUT /api/rides/{rideId}/tasks/{orderId}/status` |
|
||||
| `convertOrderToInvoice(orderId)` (§1) | Triggered automatically on `Delivered`/`PartiallyDelivered` — see [19_finance_accounting.md](./19_finance_accounting.md) |
|
||||
| `createBackorder(orderId, unfulfilledItems[])` (§1) | Triggered automatically on `PartiallyDelivered` |
|
||||
| — | `POST /api/rides/{rideId}/tasks/{orderId}/confirm` — captures customer signature/OTP |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
|
||||
- A delivery task cannot be marked `Delivered` without a customer confirmation.
|
||||
- `PartiallyDelivered` always creates a `createBackorder` entry and a credit note for the
|
||||
undelivered quantity (see [18_returns_claims.md](./18_returns_claims.md) for credit note shape).
|
||||
- Each confirmed delivery task creates a stock movement (`Sale`/consumption) against the vehicle
|
||||
location per [02_stock_ledger.md](./02_stock_ledger.md).
|
||||
|
||||
---
|
||||
|
||||
## Fraud / Control Considerations
|
||||
|
||||
|
||||
- OTP/signature capture must be tamper-evident (timestamped, tied to the delivery task, not
|
||||
editable after submission).
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
|
||||
- Signature capture (image) or OTP (SMS code) — one, or both, in v1?
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -7,13 +7,18 @@
|
||||
|
||||
## Goal
|
||||
|
||||
|
||||
Support on-the-spot van sales with no pre-order: driver picks items/quantities, an invoice is
|
||||
generated immediately, and payment is captured at point of sale. Covers `createInvoice`,
|
||||
`recordPayment` from [mainPlan.md §13](./mainPlan.md#13-finance--accounting),
|
||||
and reuses `applyPricingAndDiscounts` (§1) plus `evaluateSchemeEligibility`/`applySchemeDiscount`
|
||||
(§11) if promotions are live by the time this ships.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
|
||||
Distinct from [05_delivery_ride.md](./05_delivery_ride.md): there is no pre-existing `Order` row —
|
||||
the field invoice *is* the order, created and settled in one step.
|
||||
|
||||
---
|
||||
|
||||
@@ -21,39 +26,52 @@
|
||||
|
||||
### Field Invoice
|
||||
|
||||
|
||||
`RideId`, `StoreId`, `CreatedByUserId`, `TotalAmount`, `PaymentStatus` (`Paid`, `PartiallyPaid`,
|
||||
`OnCredit`).
|
||||
|
||||
### Field Invoice Line Item
|
||||
|
||||
|
||||
`FieldInvoiceId`, `ItemId`, `Quantity`, `UnitPrice` (subject to the discount-limit rules in
|
||||
[08_sales_agent_controls.md](./08_sales_agent_controls.md)).
|
||||
|
||||
### Payment Record
|
||||
|
||||
|
||||
`FieldInvoiceId`, `Amount`, `Method` (`Cash`, `Credit`), `CollectedAt`.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `createInvoice(orderId)` (§13) | `POST /api/rides/{rideId}/field-invoices` — orderId is the field invoice itself here |
|
||||
| `recordPayment(invoiceId, amount, method)` (§13) | `POST /api/field-invoices/{id}/payments` |
|
||||
| `applyPricingAndDiscounts(orderId)` (§1) | Applied at line-item entry time, within agent discount limits |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
|
||||
- Driver cannot sell more than what is loaded on the vehicle — every line item is checked against
|
||||
the vehicle's current stock balance ([02_stock_ledger.md](./02_stock_ledger.md)) before the
|
||||
invoice is finalized.
|
||||
- Each confirmed sale line creates a `Sale` stock movement against the vehicle location,
|
||||
immediately (not batched at ride close).
|
||||
- Discount applied per line must not exceed the agent's role-level limit (§8).
|
||||
|
||||
---
|
||||
|
||||
## Fraud / Control Considerations
|
||||
|
||||
|
||||
- Cash collected must reconcile against invoiced totals at ride close — see
|
||||
[07_ride_reconciliation.md](./07_ride_reconciliation.md).
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
|
||||
- Are field invoices numbered/sequenced separately from warehouse-issued invoices, or share one
|
||||
sequence?
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -7,13 +7,18 @@
|
||||
|
||||
## Goal
|
||||
|
||||
|
||||
Physically count what's left on the vehicle at ride close and reconcile it against the stock
|
||||
ledger and cash collected. Covers `runCycleCount` and `reconcileStock` from
|
||||
[mainPlan.md §6](./mainPlan.md#6-warehouse-management), applied to a
|
||||
vehicle location instead of a warehouse zone.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
|
||||
This is the control point that catches loading discrepancies ([04_loading_event.md](./04_loading_event.md))
|
||||
that went unresolved, and any shortfall between what was sold/delivered and what's physically on
|
||||
the vehicle.
|
||||
|
||||
---
|
||||
|
||||
@@ -21,45 +26,61 @@
|
||||
|
||||
### Ride Close Summary
|
||||
|
||||
|
||||
`RideId`, `ClosedAt`, `ClosedByUserId`, computed totals for the reconciliation equation below.
|
||||
|
||||
### Variance Record
|
||||
|
||||
|
||||
`RideId`, `ItemId` (or `Cash` for the monetary variance), `ExpectedQuantity`/`ExpectedAmount`,
|
||||
`ActualQuantity`/`ActualAmount`, `Variance`, `ReasonCode`, `Status` (`Pending`, `Approved`,
|
||||
`Rejected`).
|
||||
|
||||
### Supervisor Approval
|
||||
|
||||
|
||||
Every non-zero variance requires a supervisor-level approval (`Role.Level >= 20`) before the ride
|
||||
can transition to `Closed`.
|
||||
|
||||
---
|
||||
|
||||
## The Reconciliation Equation
|
||||
|
||||
|
||||
```
|
||||
Opening Stock − Sales − Damages + Returns = Closing Stock (physical count) [runCycleCount / reconcileStock]
|
||||
Cash Collected + Credit Extended = Total Sales − Returns
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `runCycleCount(warehouseId, zoneId)` (§6) | `POST /api/rides/{id}/cycle-count` — `zoneId` maps to the ride's vehicle location |
|
||||
| `reconcileStock(warehouseId, countResults)` (§6) | `POST /api/rides/{id}/reconcile` |
|
||||
| — | `POST /api/rides/{id}/variances/{id}/approve` — supervisor sign-off |
|
||||
| — | `POST /api/rides/{id}/close` — only succeeds once every variance is `Approved` |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
|
||||
- A ride cannot close with an unexplained variance — every `Variance` row must be `Approved` or
|
||||
`Rejected` (with the discrepancy converted to a stock `Adjustment` movement) before close.
|
||||
- `reconcileStock` writes an `Adjustment` movement per variance line, never edits the running
|
||||
balance directly (see [02_stock_ledger.md](./02_stock_ledger.md)).
|
||||
|
||||
---
|
||||
|
||||
## Fraud / Control Considerations
|
||||
|
||||
|
||||
- Unresolved/high-frequency discrepancies are logged permanently against the agent's record — feeds
|
||||
the anomaly detection in [09_fraud_controls.md](./09_fraud_controls.md).
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
|
||||
- Is there a variance threshold below which supervisor approval is auto-granted (e.g. rounding-
|
||||
level cash differences), or does every non-zero variance require explicit sign-off?
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -7,13 +7,21 @@
|
||||
|
||||
## Goal
|
||||
|
||||
|
||||
Wire role levels to concrete business limits and approval chains. Covers the approval-routing half
|
||||
of [mainPlan.md §1](./mainPlan.md#1-order-management)
|
||||
(`submitOrderForApproval`, `approveOrder`, `rejectOrder`) and the performance-tracking functions
|
||||
from [§10 SFA/CRM](./mainPlan.md#10-sales-force-automation-sfa--crm)
|
||||
(`trackTargetVsAchievement`, `calculateIncentive`). Lead/interaction functions from §10
|
||||
(`logCustomerInteraction`, `createLead`, `convertLeadToStore`) are out of scope here — see
|
||||
[16_sfa_crm.md](./16_sfa_crm.md).
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
|
||||
`Role.Level` already exists and drives the permission seed
|
||||
(`Data/Seed/RoleAndPermissionSeed.cs`), but nothing today turns a level into a discount limit,
|
||||
credit limit, or approval threshold — it only gates which API endpoints a role can call.
|
||||
|
||||
---
|
||||
|
||||
@@ -21,39 +29,56 @@
|
||||
|
||||
### Agent Limit Configuration (per role level)
|
||||
|
||||
|
||||
`RoleId`, `MaxDiscountPercent`, `MaxSingleTransactionValue` — read by both
|
||||
[06_sale_ride.md](./06_sale_ride.md) (line-item discount check) and order approval routing here.
|
||||
|
||||
### Override Log
|
||||
|
||||
|
||||
`OverrideById` (the supervisor), `TargetType`/`TargetId` (e.g. `Order`, `FieldInvoice`), `OldValue`,
|
||||
`NewValue`, `Reason`, `CreatedAt` — a one-time exception, logged against the supervisor's account.
|
||||
|
||||
### Customer Credit Profile
|
||||
|
||||
|
||||
Reuses `Store.CreditLimit`/`Store.OutstandingBalance` once added (gap #8 in
|
||||
[00_existing_gaps.md](./00_existing_gaps.md)) rather than a separate entity.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `submitOrderForApproval(orderId)` (§1) | Triggered automatically when an order exceeds its agent's limit |
|
||||
| `approveOrder(orderId, approverId)` / `rejectOrder(orderId, reason)` (§1) | `POST /api/orders/{id}/approve`, `POST /api/orders/{id}/reject` |
|
||||
| `trackTargetVsAchievement(userId, period)` (§10) | `GET /api/agents/{userId}/performance?period=...` |
|
||||
| `calculateIncentive(userId, period)` (§10) | `GET /api/agents/{userId}/incentive?period=...` |
|
||||
| — | `POST /api/orders/{id}/override` — supervisor one-time exception, writes an Override Log entry |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
|
||||
- An order/invoice whose value or discount exceeds the agent's `Agent Limit Configuration` cannot
|
||||
proceed without either supervisor approval or a logged override — no silent pass-through.
|
||||
- `calculateIncentive` reads only settled/closed rides (post-[07_ride_reconciliation.md](./07_ride_reconciliation.md)),
|
||||
never in-flight ones.
|
||||
|
||||
---
|
||||
|
||||
## Fraud / Control Considerations
|
||||
|
||||
|
||||
- Every `Override Log` entry is permanent and immutable once written — feeds
|
||||
[09_fraud_controls.md](./09_fraud_controls.md)'s anomaly detection (agents with unusually
|
||||
frequent overrides).
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
|
||||
- Are discount/transaction limits configured per `Role`, or can an individual `User` have a
|
||||
tighter/looser override of their role's default?
|
||||
- What sales target source feeds `trackTargetVsAchievement` — is there a target-setting endpoint
|
||||
needed here, or does it come from §2's `setRegionalTarget` rolled down to the individual?
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -7,13 +7,18 @@
|
||||
|
||||
## Goal
|
||||
|
||||
|
||||
A dedicated, immutable audit log plus automated anomaly detection over everything built in steps
|
||||
01–08. Covers [mainPlan.md §18](./mainPlan.md#18-compliance--audit)
|
||||
(`logAuditEvent`, `checkLicenseCompliance`, `generateComplianceReport`) and the fraud-relevant piece
|
||||
of §15 (`sendRouteDeviationAlert`) and §12 (return-without-prior-sale detection).
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
|
||||
Every prior step in this plan references "logged" or "flagged" behavior (override logs, loading
|
||||
discrepancies, reconciliation variances) — this step is where that logging actually becomes a
|
||||
queryable, immutable audit trail instead of scattered per-feature notes.
|
||||
|
||||
---
|
||||
|
||||
@@ -21,45 +26,69 @@
|
||||
|
||||
### Audit Log Entry
|
||||
|
||||
|
||||
`UserId`, `Action`, `EntityType`, `EntityId`, `OldValue`/`NewValue` (JSON snapshot), `CreatedAt`,
|
||||
`SourceIp`/`RequestId` if available. Append-only — no update or delete path exposed anywhere.
|
||||
|
||||
### Anomaly Flag
|
||||
|
||||
|
||||
`Type` (`ReturnWithoutSale`, `RouteDeviation`, `HighVarianceRate`, `CashShortfall`), `EntityType`/
|
||||
`EntityId`, `RaisedAt`, `Status` (`Open`, `Reviewed`, `Dismissed`).
|
||||
|
||||
### Return Approval Record
|
||||
|
||||
|
||||
Reuses the `approveReturn` flow from [18_returns_claims.md](./18_returns_claims.md) — this step
|
||||
adds the automated pre-check (`ReturnWithoutSale`) that runs before a human approver sees it.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `logAuditEvent(userId, action, entityType, entityId)` (§18) | Internal — called by every mutating service, not directly exposed |
|
||||
| `checkLicenseCompliance(entityId, licenseType)` (§18) | `GET /api/compliance/{entityType}/{entityId}/license-check` |
|
||||
| `generateComplianceReport(period)` (§18) | `GET /api/compliance/report?period=...` |
|
||||
| `sendRouteDeviationAlert(routeId, salesRepId)` (§15) | Triggered from route check-in/out data (§3 of the function reference; not yet in any numbered step — see [11_route_sales_management.md](./11_route_sales_management.md)) |
|
||||
| — | `GET /api/audit-log?entityType=...&entityId=...` |
|
||||
| — | `GET /api/anomaly-flags?status=Open` |
|
||||
|
||||
---
|
||||
|
||||
## Automated Checks & Flags
|
||||
|
||||
|
||||
- Returns without a matching prior sale (`ReturnWithoutSale`).
|
||||
- Sales/deliveries outside the geofenced route (`RouteDeviation`) — depends on GPS check-in/out
|
||||
data from [11_route_sales_management.md](./11_route_sales_management.md).
|
||||
- Agents with consistently high variance rates across closed rides (`HighVarianceRate`) — reads
|
||||
`Variance Record` history from [07_ride_reconciliation.md](./07_ride_reconciliation.md).
|
||||
- Cash collected below invoiced amount (`CashShortfall`) — reads `Payment Record` from
|
||||
[06_sale_ride.md](./06_sale_ride.md).
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
|
||||
- Every state-changing action across every feature must call `logAuditEvent` — this is the one
|
||||
cross-cutting requirement every other numbered step inherits once this lands.
|
||||
- Return approval requires supervisor sign-off before stock is credited back
|
||||
([18_returns_claims.md](./18_returns_claims.md)), regardless of whether an anomaly flag fired.
|
||||
|
||||
---
|
||||
|
||||
## Fraud / Control Considerations
|
||||
|
||||
|
||||
- Anomaly reports are surfaced to area managers on a scheduled basis — batch job or on-demand
|
||||
endpoint, not real-time push, in v1.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
|
||||
- Should `logAuditEvent` be wired in as a cross-cutting concern (e.g. an EF `SaveChanges`
|
||||
interceptor) rather than an explicit call in every service, to guarantee nothing is missed?
|
||||
- What licenses does `checkLicenseCompliance` actually need to validate for this business — is
|
||||
regulated goods handling in scope at all, or is this function speculative until a real
|
||||
requirement shows up?
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
# 10 — Region Management (Beyond CRUD)
|
||||
|
||||
**Status**: Not Started
|
||||
**Depends On**: 00 — Existing Gap Fixes
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Region CRUD already exists (`Features/Region`). This step covers everything in
|
||||
[mainPlan.md §2](./mainPlan.md#2-region-management) that isn't plain
|
||||
CRUD: targets, budgets, agency mapping, tax rules, hierarchy, and dashboarding.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
`Region` today has only a `Name`. Nothing points to it except `Route.FK_RegionId`. There is no
|
||||
`Area` level (see Open Question in [00_existing_gaps.md](./00_existing_gaps.md#9-no-area-entity-between-region-and-route)).
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Regional Target / Budget
|
||||
|
||||
`RegionId`, `Period` (e.g. `2026-08`), `TargetValue`, `BudgetAmount`.
|
||||
|
||||
### Agency-Region Map
|
||||
|
||||
`RegionId`, `AgencyId` — depends on whether "Regional Agency" becomes a distinct entity or stays a
|
||||
`Role` (see Open Questions).
|
||||
|
||||
### Regional Tax Rule
|
||||
|
||||
`RegionId`, `RuleConfig` (JSON) — deferred to whenever tax/pricing rule overrides are prioritized.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `assignAreaToRegion(areaId, regionId)` | Blocked — no `Area` entity exists (see [00_existing_gaps.md](./00_existing_gaps.md#9-no-area-entity-between-region-and-route)) |
|
||||
| `setRegionalTarget(regionId, period, targetValue)` | `PUT /api/regions/{id}/targets/{period}` |
|
||||
| `setRegionalBudget(regionId, period, budgetAmount)` | `PUT /api/regions/{id}/budgets/{period}` |
|
||||
| `mapAgencyToRegion(agencyId, regionId)` | `POST /api/regions/{id}/agencies` |
|
||||
| `setRegionalTaxRule(regionId, ruleConfig)` | `PUT /api/regions/{id}/tax-rule` |
|
||||
| `getRegionHierarchy(regionId)` | `GET /api/regions/{id}/hierarchy` |
|
||||
| `getRegionDashboard(regionId, period)` | `GET /api/regions/{id}/dashboard?period=...` |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
- Only `RegionalManager` and above (per role level in `Data/Seed/RoleAndPermissionSeed.cs`) can
|
||||
set targets/budgets for their own region; only `RegionalAgency` can set them across regions.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Is "Regional Agency" a business entity distinct from the `RegionalAgency` role (e.g. a contracted
|
||||
third party with its own record), or is the role sufficient?
|
||||
- Does `getRegionDashboard` need real-time aggregation, or a scheduled/cached rollup?
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
@@ -0,0 +1,71 @@
|
||||
# 11 — Route & Sales Visit Management
|
||||
|
||||
**Status**: Not Started
|
||||
**Depends On**: 00 — Existing Gap Fixes
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Route CRUD already exists (`Features/Route`). This step covers the sales-visit half of
|
||||
[mainPlan.md §3](./mainPlan.md#3-route-management) — the part that is
|
||||
about a SalesRep's daily visit schedule, not the ride/dispatch route from
|
||||
[03_ride_lifecycle.md](./03_ride_lifecycle.md)'s `planDeliveryRoute`.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
Today a `Store` has a single `FK_RouteId` (bulk-assign not yet supported via API — see gap #2 in
|
||||
[00_existing_gaps.md](./00_existing_gaps.md)), and route access is only granted via `UserRouteScope`
|
||||
(a many-to-many grant), not single ownership. There is no visit check-in/out data anywhere.
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Route Ownership
|
||||
|
||||
`RouteId`, `OwnerUserId` — single-owner assignment, distinct from the broader `UserRouteScope`
|
||||
access grant which may cover multiple users (e.g. a Supervisor overseeing several reps' routes).
|
||||
|
||||
### Route Visit Log
|
||||
|
||||
`RouteId`, `StoreId`, `UserId`, `CheckInAt`, `CheckInGps`, `CheckOutAt` — one row per planned visit.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `assignOutletsToRoute(routeId, outletIds[])` | `POST /api/routes/{id}/stores` — bulk version of the existing single `Store.RouteId` field |
|
||||
| `assignRouteToSalesRep(routeId, salesRepId)` | `PUT /api/routes/{id}/owner` |
|
||||
| `optimizeRouteSequence(routeId)` | `POST /api/routes/{id}/optimize` |
|
||||
| `recordRouteCheckIn(routeId, outletId, gpsCoords, timestamp)` | `POST /api/routes/{id}/visits/check-in` |
|
||||
| `recordRouteCheckOut(routeId, outletId, timestamp)` | `POST /api/routes/{id}/visits/check-out` |
|
||||
| `getRouteAdherence(routeId, date)` | `GET /api/routes/{id}/adherence?date=...` |
|
||||
| `reassignRoute(routeId, newSalesRepId)` | `PUT /api/routes/{id}/owner` (same as assign, different actor) |
|
||||
| `getRoutePerformance(routeId, period)` | `GET /api/routes/{id}/performance?period=...` |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
- Check-in `gpsCoords` feeds `sendRouteDeviationAlert` in
|
||||
[09_fraud_controls.md](./09_fraud_controls.md) — a check-in materially outside the store's
|
||||
registered location should flag, not silently accept.
|
||||
- `getStoreVisitHistory` (§4) reads directly from `Route Visit Log` — no separate storage.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Is route sequence optimization (`optimizeRouteSequence`) a manual reorder endpoint, or does it
|
||||
need actual distance/priority-based computation (external mapping API)?
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
@@ -0,0 +1,257 @@
|
||||
# 12 — Store Management (Beyond CRUD)
|
||||
|
||||
**Status**: Done — migration applied to the database
|
||||
**Depends On**: 00 — Existing Gap Fixes, 10 — Region Management, 11 — Route & Sales Management
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Store CRUD already exists (`Features/Store`). This step adds everything in
|
||||
[mainPlan.md §4](./mainPlan.md#4-store-outlet-management) that isn't plain CRUD: credit control,
|
||||
KYC verification, segmentation, outstanding balance, visit history, scheme eligibility, and a
|
||||
sales dashboard.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
`Store` currently has no credit limit, outstanding balance, segment, or KYC fields — flagged as
|
||||
gap #8 in [00_existing_gaps.md](./00_existing_gaps.md). Credit fields exist informally today as
|
||||
`OneTimeAmount` / `CreditAmount`, which stay as-is; this step adds the real credit-limit +
|
||||
outstanding-balance + KYC-gated-ordering flow on top.
|
||||
|
||||
`getStoreVisitHistory` and `checkStoreSchemeEligibility` are read-only integration points into
|
||||
modules that don't exist yet ([11_route_sales_management.md](./11_route_sales_management.md) for
|
||||
visits, [17_promotions_schemes.md](./17_promotions_schemes.md) for schemes). Implement them now
|
||||
with the query shape defined below; wire the actual join once those modules land — return an empty
|
||||
list / `eligible: true` (no active schemes to check) until then, guarded by a `NotImplementedYet`
|
||||
style short-circuit, not a broken query.
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Store — New Fields
|
||||
|
||||
```
|
||||
Store (existing, add columns)
|
||||
├── CreditLimit (decimal) — default 0. Max outstanding allowed before new orders are blocked.
|
||||
├── OutstandingBalance (decimal) — default 0. Cached/denormalized. Updated by Finance module
|
||||
│ (step 19) on invoice/payment events via UpdateOutstandingBalanceAsync.
|
||||
│ Until Finance exists, only moved by manual adjustment endpoint.
|
||||
├── Segment (string?) — free text, e.g. "Gold", "Silver", "Key Account", "High Risk".
|
||||
├── KycStatus (enum) — Pending | Verified | Rejected. Default Pending.
|
||||
```
|
||||
|
||||
### New Enum — KycStatus
|
||||
|
||||
```csharp
|
||||
public enum KycStatus { Pending, Verified, Rejected }
|
||||
```
|
||||
|
||||
### New Entity — StoreKycDocument
|
||||
|
||||
```
|
||||
StoreKycDocument
|
||||
├── Id (Guid) — PK, from Template
|
||||
├── FK_StoreId (Guid) — required
|
||||
├── DocumentType (string) — free text, e.g. "GST Certificate", "PAN Card", "Shop License,NIC"
|
||||
├── FileReference (string) — relative path under wwwroot/uploads/kyc/{storeId}/
|
||||
├── UploadedAt (DateTime)
|
||||
├── IsActive, CreatedAt — from Template
|
||||
```
|
||||
|
||||
### AppSettings — New Config Key
|
||||
|
||||
```
|
||||
StoreManagement:UnverifiedKycOrderThreshold (decimal) — default 0.
|
||||
Max single-order value a store with KycStatus != Verified may place.
|
||||
0 disables ordering entirely for unverified stores; set higher to allow small orders through.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migrations
|
||||
|
||||
One migration needed:
|
||||
1. **AddStoreCreditAndKyc** — adds `CreditLimit`, `OutstandingBalance`, `Segment`, `KycStatus`
|
||||
columns to `Stores`; creates `StoreKycDocuments` table.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Existing (unchanged)
|
||||
| Method | Route | Permission | Notes |
|
||||
|---|---|---|---|
|
||||
| GET | /api/stores | stores.view | |
|
||||
| GET | /api/stores/{id} | stores.view | |
|
||||
| POST | /api/stores | stores.create | |
|
||||
| PUT | /api/stores/{id} | stores.update | |
|
||||
| DELETE | /api/stores/{id} | stores.delete | Soft delete |
|
||||
|
||||
### New
|
||||
| Method | Route | Permission | Notes |
|
||||
|---|---|---|---|
|
||||
| POST | /api/stores/{id}/kyc-documents | stores.update | Multipart upload, appends a KYC document |
|
||||
| GET | /api/stores/{id}/kyc-documents | stores.view | Lists documents for a store |
|
||||
| PUT | /api/stores/{id}/kyc/verify | stores.kyc.verify | Sets KycStatus, body carries `status` (Verified/Rejected) + optional `notes` |
|
||||
| PUT | /api/stores/{id}/credit-limit | stores.credit.update | Requires Supervisor (roleLevel ≥ 20) or above |
|
||||
| PUT | /api/stores/{id}/outstanding-balance | stores.credit.update | Manual adjustment until Finance owns this; same permission as credit-limit |
|
||||
| GET | /api/stores/{id}/outstanding-balance | stores.view | Returns cached value + CreditLimit + available credit |
|
||||
| PUT | /api/stores/{id}/segment | stores.update | Body: `{ "segment": "Gold" }` |
|
||||
| GET | /api/stores/{id}/visit-history | stores.view | Paginated. Empty list until Step 11 wiring lands |
|
||||
| GET | /api/stores/{id}/schemes/{schemeId}/eligibility | stores.view | Returns `{ eligible: true, reason: null }` until Step 17 wiring lands |
|
||||
| GET | /api/stores/{id}/dashboard?period=... | stores.view | Aggregates order count/value for the period from existing Order tables |
|
||||
|
||||
---
|
||||
|
||||
## Request / Response Shapes
|
||||
|
||||
### POST `/api/stores/{id}/kyc-documents` (multipart/form-data)
|
||||
Fields: `documentType` (string), `file` (binary).
|
||||
|
||||
Response:
|
||||
```json
|
||||
{ "id": "...", "storeId": "...", "documentType": "GST Certificate", "fileReference": "uploads/kyc/{storeId}/{guid}.pdf", "uploadedAt": "2026-08-20T10:00:00" }
|
||||
```
|
||||
|
||||
### PUT `/api/stores/{id}/kyc/verify` request
|
||||
```json
|
||||
{ "status": "Verified", "notes": "GST + PAN checked against ERP records" }
|
||||
```
|
||||
|
||||
### PUT `/api/stores/{id}/credit-limit` request
|
||||
```json
|
||||
{ "newLimit": 50000, "approverId": "..." }
|
||||
```
|
||||
`approverId` must resolve to a user with `RoleLevel >= 20` (Supervisor or above); enforced
|
||||
server-side from the authenticated caller, not trusted from the body — the field is echoed back
|
||||
for audit display only.
|
||||
|
||||
### GET `/api/stores/{id}/outstanding-balance` response
|
||||
```json
|
||||
{ "storeId": "...", "creditLimit": 50000, "outstandingBalance": 32000, "availableCredit": 18000 }
|
||||
```
|
||||
|
||||
### GET `/api/stores/{id}/dashboard` response
|
||||
```json
|
||||
{
|
||||
"storeId": "...",
|
||||
"period": "2026-08",
|
||||
"orderCount": 12,
|
||||
"totalOrderValue": 145000,
|
||||
"lastOrderDate": "2026-08-18T09:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
1. `updateStoreCreditLimit` requires the authenticated caller to have `RoleLevel >= 20`
|
||||
(Supervisor or above) — enforced by the `stores.credit.update` policy plus a role-level check
|
||||
in the service, same pattern as `StoreService.HasGlobalScope`.
|
||||
2. A store with `KycStatus != Verified` may only place orders up to
|
||||
`StoreManagement:UnverifiedKycOrderThreshold`. `OrderService` must check this at order creation
|
||||
time; orders exceeding the threshold are rejected with a 400. This mirrors the stock-ledger
|
||||
pattern of rejecting at the point of the write, not after.
|
||||
3. `deactivateStore` (existing `DELETE /api/stores/{id}`) blocks new orders against the store,
|
||||
matching the existing `IsActive` soft-delete filter already used in `OrderService`. No separate
|
||||
reason field is added — soft delete already covers it via the existing `Template.IsActive` flag.
|
||||
4. `OutstandingBalance` is never edited directly by client-supplied arbitrary values outside the
|
||||
dedicated endpoint — always through `UpdateOutstandingBalanceAsync`, so Finance (step 19) has a
|
||||
single integration point to call into later.
|
||||
5. KYC documents are append-only from the API's perspective — no update endpoint. To replace a
|
||||
document, upload a new one; old ones stay for audit trail (soft-deletable via `IsActive` if
|
||||
ever needed).
|
||||
6. Scope rules for all new endpoints follow the existing `StoreService` pattern: global-scope roles
|
||||
(`RoleLevel >= 50`) see everything, others are checked via `IScopeAccessService.HasStoreScopeAsync`.
|
||||
|
||||
---
|
||||
|
||||
## Permissions to Add to Seed
|
||||
|
||||
New permission codes to add to the existing seed:
|
||||
```
|
||||
stores.kyc.verify, stores.credit.update
|
||||
```
|
||||
(`stores.view`, `stores.create`, `stores.update`, `stores.delete` already exist.)
|
||||
|
||||
Role assignments:
|
||||
- **SalesRep (10)**: stores.view (unchanged)
|
||||
- **Supervisor (20)**: stores.view, stores.update, stores.credit.update
|
||||
- **AreaManager (30)**: stores.view, stores.update, stores.credit.update, stores.kyc.verify
|
||||
- **AreaSupervisor (40)**: all store permissions
|
||||
- **RegionalManager (50)**: all store permissions
|
||||
- **RegionalAgency (60)**: all store permissions
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- None. Resolved in planning discussion (2026-08-20): OutstandingBalance is cached/denormalized;
|
||||
KYC document type is free text with local disk storage; unverified stores are gated by a
|
||||
configurable order-value threshold rather than a hard block.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
### File structure created / touched
|
||||
```
|
||||
Features/
|
||||
Store/
|
||||
StoreController.cs (extended — credit-limit, balance, kyc/verify, segment,
|
||||
visit-history, scheme-eligibility, dashboard endpoints)
|
||||
StoreDTOs.cs (extended — new request/response records)
|
||||
StoreService.cs (extended — new methods, RoleLevel >= 20 credit-approver check)
|
||||
IStoreRepository.cs (extended — GetOrdersForDashboardAsync)
|
||||
StoreRepository.cs (extended)
|
||||
Kyc/
|
||||
StoreKycDocumentController.cs
|
||||
StoreKycDocumentDTOs.cs
|
||||
StoreKycDocumentService.cs
|
||||
IStoreKycDocumentRepository.cs
|
||||
StoreKycDocumentRepository.cs
|
||||
Order/
|
||||
OrderService.cs (extended — KycStatus + UnverifiedKycOrderThreshold gate in CreateAsync)
|
||||
|
||||
Models/
|
||||
Store.cs (add CreditLimit, OutstandingBalance, Segment, KycStatus columns)
|
||||
StoreKycDocument.cs (new)
|
||||
Enums.cs (add KycStatus enum)
|
||||
|
||||
Migrations/
|
||||
AddStoreCreditAndKyc
|
||||
|
||||
appsettings.json
|
||||
add "StoreManagement": { "UnverifiedKycOrderThreshold": 0 }
|
||||
|
||||
Program.cs
|
||||
register IStoreKycDocumentRepository, StoreKycDocumentRepository, StoreKycDocumentService
|
||||
```
|
||||
|
||||
KYC documents live as a sub-namespace `Features/Store/Kyc/` rather than a top-level feature,
|
||||
matching the one-feature-per-folder convention (`Features/Store`, `Features/Stock`) — they only
|
||||
ever belong to a Store and have no independent lifecycle.
|
||||
|
||||
### KYC upload logic (pseudo)
|
||||
```
|
||||
POST /api/stores/{id}/kyc-documents — multipart (documentType, file):
|
||||
1. Load Store by id, reject if missing/inactive or caller lacks store scope
|
||||
2. Ensure wwwroot/uploads/kyc/{storeId}/ exists
|
||||
3. Save file as {guid}.{ext}, record FileReference = "uploads/kyc/{storeId}/{guid}.{ext}"
|
||||
4. Insert StoreKycDocument row, commit in the same UoW transaction
|
||||
```
|
||||
|
||||
### Order-creation KYC gate (pseudo)
|
||||
```
|
||||
OrderService.CreateAsync — before inserting the order:
|
||||
1. Load Store by request.StoreId
|
||||
2. If Store.KycStatus != Verified:
|
||||
threshold = config["StoreManagement:UnverifiedKycOrderThreshold"]
|
||||
if request.TotalAmount > threshold: reject (return null → 403 at controller)
|
||||
```
|
||||
</content>
|
||||
@@ -0,0 +1,78 @@
|
||||
# 13 — Fleet Management (Beyond CRUD)
|
||||
|
||||
**Status**: Not Started
|
||||
**Depends On**: 00 — Existing Gap Fixes
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Vehicle CRUD already exists (`Features/Vehicle`). This step covers everything in
|
||||
[mainPlan.md §5](./mainPlan.md#5-vehicle--fleet-management) that isn't
|
||||
plain CRUD or already scoped into [03_ride_lifecycle.md](./03_ride_lifecycle.md)/
|
||||
[04_loading_event.md](./04_loading_event.md): maintenance, fuel, telemetry, and utilization.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
`Vehicle` today only has `RegNumber`/`NumberPlate`. No capacity, ownership, maintenance, or fuel
|
||||
data exists. There is no `Driver` entity — see gap #10 in
|
||||
[00_existing_gaps.md](./00_existing_gaps.md).
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Vehicle — New Fields
|
||||
|
||||
`CapacityVolume`, `CapacityWeight` (needed by `calculateLoadPlan` in
|
||||
[04_loading_event.md](./04_loading_event.md)), `OwnershipType` (`Owned`, `Leased`, `Contracted`).
|
||||
|
||||
### Maintenance Task
|
||||
|
||||
`VehicleId`, `MaintenanceType`, `DueAt` (date or mileage-based), `CompletedAt`.
|
||||
|
||||
### Fuel Log
|
||||
|
||||
`VehicleId`, `Amount`, `Liters`, `Date`.
|
||||
|
||||
### Telemetry Record
|
||||
|
||||
`VehicleId`, `GpsCoords`, `Speed`, `Timestamp` — high-volume; likely a separate store/retention
|
||||
policy from the rest of the schema (see Open Questions).
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `scheduleMaintenance(vehicleId, maintenanceType, dueAt)` | `POST /api/vehicles/{id}/maintenance` |
|
||||
| `logFuelExpense(vehicleId, amount, liters, date)` | `POST /api/vehicles/{id}/fuel-logs` |
|
||||
| `recordVehicleTelemetry(vehicleId, gpsCoords, speed, timestamp)` | `POST /api/vehicles/{id}/telemetry` — likely ingested via [22_integration_api.md](./22_integration_api.md) rather than direct client calls |
|
||||
| `getVehicleUtilization(vehicleId, period)` | `GET /api/vehicles/{id}/utilization?period=...` |
|
||||
| `retireVehicle(vehicleId, reason)` | Extends existing `DELETE /api/vehicles/{id}` with a reason field |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
- `retireVehicle` must be blocked while the vehicle is on an open/active ride
|
||||
([03_ride_lifecycle.md](./03_ride_lifecycle.md)).
|
||||
- Overdue `Maintenance Task`s should block a vehicle from being assigned to a new ride — exact
|
||||
enforcement level (hard block vs. warning) to confirm.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Is real-time telemetry (`recordVehicleTelemetry`) actually in scope, or is odometer
|
||||
start/end (`DeliveryRun.Meterstart`/`Meterend`, already modeled) sufficient for this business?
|
||||
- What third-party telematics provider, if any, is being integrated (affects [22_integration_api.md](./22_integration_api.md))?
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
@@ -0,0 +1,70 @@
|
||||
# 14 — Pricing & Supplier Master Data
|
||||
|
||||
**Status**: Not Started
|
||||
**Depends On**: 01 — Item Catalog
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Covers the remainder of [mainPlan.md §7](./mainPlan.md#7-master-data-management)
|
||||
not already handled by [01_item_catalog.md](./01_item_catalog.md): price lists and supplier master
|
||||
records. `createProduct`/`deactivateSku` live in 01; this step is everything else in §7.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
`Item.BasePrice` (from [01_item_catalog.md](./01_item_catalog.md)) is a single flat price. There is
|
||||
no concept of a region- or date-scoped price list, and no `Supplier` entity anywhere in the schema.
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Price List
|
||||
|
||||
`Name`, `RegionId` (nullable — null means global), `EffectiveFrom`, `EffectiveTo`.
|
||||
|
||||
### Price List Item
|
||||
|
||||
`PriceListId`, `ItemId`, `Price` — overrides `Item.BasePrice` when an applicable, currently-
|
||||
effective price list exists for the order's region.
|
||||
|
||||
### Supplier
|
||||
|
||||
`Name`, `ContactInfo`, `PerformanceScore` (nullable, populated by
|
||||
[15_procurement.md](./15_procurement.md)'s `evaluateSupplier`).
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `createPriceList(name, region, effectiveDates)` | `POST /api/price-lists` |
|
||||
| `updateProductPricing(sku, priceListId, price)` | `PUT /api/price-lists/{id}/items/{sku}` |
|
||||
| `createSupplierMaster(supplierData)` | `POST /api/suppliers` |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
- `applyPricingAndDiscounts` (§1, used in [01_item_catalog.md](./01_item_catalog.md) and
|
||||
[06_sale_ride.md](./06_sale_ride.md)) resolves price in this order: region-scoped active price
|
||||
list → global active price list → `Item.BasePrice`.
|
||||
- Two price lists cannot have overlapping `EffectiveFrom`/`EffectiveTo` ranges for the same
|
||||
`(ItemId, RegionId)` pair.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Are price lists customer/segment-specific as well as region-specific (ties into `Store.Segment`
|
||||
from [12_store_management.md](./12_store_management.md)), or region-only in v1?
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
@@ -0,0 +1,74 @@
|
||||
# 15 — Procurement / Purchasing
|
||||
|
||||
**Status**: Not Started
|
||||
**Depends On**: 01 — Item Catalog, 14 — Pricing & Supplier Master Data
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Covers [mainPlan.md §9](./mainPlan.md#9-procurement--purchasing) in full
|
||||
— an entirely new subsystem, nothing in this area exists in the current schema.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
Inbound stock (`createGoodsReceipt` in [04_loading_event.md](./04_loading_event.md)/
|
||||
[mainPlan.md §6](./mainPlan.md#6-warehouse-management)) currently has no
|
||||
purchase order to receive against — this step is what creates that PO in the first place.
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Purchase Requisition
|
||||
|
||||
`RequestedByUserId`, `Status` (`Draft`, `Submitted`, `Approved`, `Rejected`), line items
|
||||
(`ItemId`, `Quantity`).
|
||||
|
||||
### Purchase Order
|
||||
|
||||
`SupplierId`, `RequisitionId` (nullable — a PO can be raised without a prior requisition), `Status`
|
||||
(`Issued`, `PartiallyReceived`, `Received`, `Closed`), line items (`ItemId`, `Quantity`, `UnitCost`).
|
||||
|
||||
### Purchase Return
|
||||
|
||||
`PurchaseOrderId`, reason, line items — goods sent back to the supplier post-receipt.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `createPurchaseRequisition(items[], requestedBy)` | `POST /api/purchase-requisitions` |
|
||||
| `createPurchaseOrder(supplierId, items[])` | `POST /api/purchase-orders` |
|
||||
| `matchInvoiceToPO(invoiceId, poId, grnId)` | `POST /api/purchase-orders/{id}/match-invoice` |
|
||||
| `evaluateSupplier(supplierId, criteria)` | `POST /api/suppliers/{id}/evaluate` |
|
||||
| `createPurchaseReturn(poId, items[], reason)` | `POST /api/purchase-orders/{id}/returns` |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
- `createGoodsReceipt` (§6, [04_loading_event.md](./04_loading_event.md)'s warehouse-inbound
|
||||
counterpart) must reference an `Issued`/`PartiallyReceived` Purchase Order — no receipts against
|
||||
a closed or nonexistent PO.
|
||||
- `matchInvoiceToPO` (3-way match) must block payment posting in
|
||||
[19_finance_accounting.md](./19_finance_accounting.md) until PO, GRN, and invoice quantities/
|
||||
amounts reconcile within a configurable tolerance.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Is procurement in scope for this project at all right now, or is inbound stock assumed to be
|
||||
seeded/manual for the foreseeable future? Confirm before investing in this step — it's the
|
||||
largest net-new subsystem in the reference doc.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
@@ -0,0 +1,68 @@
|
||||
# 16 — Sales Force Automation (SFA) / CRM
|
||||
|
||||
**Status**: Not Started
|
||||
**Depends On**: 12 — Store Management
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Covers the lead/interaction half of
|
||||
[mainPlan.md §10](./mainPlan.md#10-sales-force-automation-sfa--crm):
|
||||
`logCustomerInteraction`, `createLead`, `convertLeadToStore`. Performance/incentive functions
|
||||
(`trackTargetVsAchievement`, `calculateIncentive`) are already scoped in
|
||||
[08_sales_agent_controls.md](./08_sales_agent_controls.md) — not repeated here.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
There is no `Lead` entity or interaction log anywhere in the schema today — new outlet onboarding
|
||||
currently goes straight to `POST /api/stores` with no pre-qualification step.
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Lead
|
||||
|
||||
`ProspectName`, `ContactInfo`, `Notes`, `Status` (`New`, `Qualified`, `Converted`, `Rejected`),
|
||||
`AssignedToUserId`.
|
||||
|
||||
### Customer Interaction
|
||||
|
||||
`StoreId` (nullable — can be logged against a `Lead` instead), `LeadId` (nullable), `UserId`,
|
||||
`Notes`, `CreatedAt`.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `logCustomerInteraction(storeId, notes)` | `POST /api/stores/{id}/interactions` |
|
||||
| `createLead(prospectData)` | `POST /api/leads` |
|
||||
| `convertLeadToStore(leadId)` | `POST /api/leads/{id}/convert` — creates a `Store` via the existing `POST /api/stores` path internally |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
- `convertLeadToStore` reuses `createStore` (§4, [12_store_management.md](./12_store_management.md))
|
||||
rather than duplicating store-creation logic — a `Lead` conversion is not a separate creation
|
||||
path.
|
||||
- Exactly one of `StoreId`/`LeadId` must be set on a `Customer Interaction` row, never both, never
|
||||
neither.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Does a `Lead` need its own scope/authorization model (region/route), or does it inherit the
|
||||
creating user's scope with no further restriction until converted?
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
@@ -0,0 +1,70 @@
|
||||
# 17 — Promotions & Trade Scheme Management
|
||||
|
||||
**Status**: Not Started
|
||||
**Depends On**: 01 — Item Catalog, 14 — Pricing & Supplier Master Data
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Covers [mainPlan.md §11](./mainPlan.md#11-promotions--trade-scheme-management)
|
||||
in full — an entirely new subsystem. Once built, `applyPricingAndDiscounts` (§1) and
|
||||
`checkStoreSchemeEligibility` (§4) both read from it.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
No scheme/promotion entity exists today. Every reference to "eligible discount" elsewhere in this
|
||||
doc set (order pricing, field invoices, agent discount limits) currently has no scheme data source
|
||||
— it only enforces the flat per-role discount ceiling from
|
||||
[08_sales_agent_controls.md](./08_sales_agent_controls.md).
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Scheme
|
||||
|
||||
`Name`, `RuleType` (`BuyXGetY`, `SlabDiscount`, `FlatPercent`), `RuleConfig` (JSON), `Eligibility`
|
||||
(store segment / region scope), `DateRangeFrom`, `DateRangeTo`.
|
||||
|
||||
### Scheme Redemption
|
||||
|
||||
`SchemeId`, `OrderId` (or `FieldInvoiceId`), `DiscountAmount`, `RedeemedAt` — feeds
|
||||
`trackSchemeSpend` and `getSchemeROI` without re-scanning every order.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `createScheme(schemeRules, eligibility, dateRange)` | `POST /api/schemes` |
|
||||
| `evaluateSchemeEligibility(storeId, orderId)` | `GET /api/schemes/eligible?storeId=...&orderId=...` |
|
||||
| `applySchemeDiscount(orderId, schemeId)` | `POST /api/orders/{id}/apply-scheme` |
|
||||
| `trackSchemeSpend(schemeId)` | `GET /api/schemes/{id}/spend` |
|
||||
| `getSchemeROI(schemeId)` | `GET /api/schemes/{id}/roi` |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
- A scheme discount stacks with, but does not bypass, the per-role discount ceiling in
|
||||
[08_sales_agent_controls.md](./08_sales_agent_controls.md) unless explicitly flagged as an
|
||||
agent-limit-exempt scheme.
|
||||
- `getSchemeROI` requires a baseline (pre-scheme expected sales) to compare against — confirm data
|
||||
source before implementing (see Open Questions).
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- What counts as "incremental sales" for `getSchemeROI` — a manual baseline entered when the scheme
|
||||
is created, or a trailing-period average computed automatically?
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
@@ -0,0 +1,74 @@
|
||||
# 18 — Returns & Claims Management
|
||||
|
||||
**Status**: Not Started
|
||||
**Depends On**: 05 — Delivery Ride Execution, 06 — Sale Ride Execution
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Covers [mainPlan.md §12](./mainPlan.md#12-returns--claims-management) in
|
||||
full. [05_delivery_ride.md](./05_delivery_ride.md) and [09_fraud_controls.md](./09_fraud_controls.md)
|
||||
both reference this step's credit note and approval shapes — this is where those are actually
|
||||
defined.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
No return, credit note, or claim entity exists today. `createBackorder` (§1,
|
||||
[05_delivery_ride.md](./05_delivery_ride.md)) needs a credit note target once this step lands.
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Return Request
|
||||
|
||||
`OrderId`, `Items[]` (`ItemId`, `Quantity`, `ReasonCode`), `Status` (`Pending`, `Approved`,
|
||||
`Rejected`), `RequestedByUserId`.
|
||||
|
||||
### Credit Note
|
||||
|
||||
`ReturnRequestId`, `InvoiceId`, `Amount`, `IssuedAt`.
|
||||
|
||||
### Claim
|
||||
|
||||
`AgencyId`, `Details`, `Status` (`Open`, `Settled`), `SettlementAmount` (nullable until settled).
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `createReturnRequest(orderId, items[], reasonCode)` | `POST /api/returns` |
|
||||
| `approveReturn(returnId, approverId)` | `POST /api/returns/{id}/approve` |
|
||||
| `generateCreditNote(returnId)` | `POST /api/returns/{id}/credit-note` — auto-triggered on approval |
|
||||
| `createClaim(agencyId, claimDetails)` | `POST /api/claims` |
|
||||
| `settleClaim(claimId, settlementAmount)` | `POST /api/claims/{id}/settle` |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
- `approveReturn` requires supervisor sign-off before stock is credited back — matches the rule
|
||||
already stated in [09_fraud_controls.md](./09_fraud_controls.md).
|
||||
- A `Return Request` must reference a real prior sale/delivery line — the absence of one is exactly
|
||||
the `ReturnWithoutSale` anomaly flagged automatically in
|
||||
[09_fraud_controls.md](./09_fraud_controls.md).
|
||||
- Approving a return creates a `Return` stock movement crediting the relevant location, per
|
||||
[02_stock_ledger.md](./02_stock_ledger.md).
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Are `Claim`s only against Regional Agencies (as the reference doc frames them), or can a claim
|
||||
also be raised against an internal warehouse/supplier discrepancy?
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
@@ -0,0 +1,75 @@
|
||||
# 19 — Finance & Accounting
|
||||
|
||||
**Status**: Not Started
|
||||
**Depends On**: 05 — Delivery Ride Execution, 06 — Sale Ride Execution, 18 — Returns & Claims Management
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Covers [mainPlan.md §13](./mainPlan.md#13-finance--accounting) beyond
|
||||
what [06_sale_ride.md](./06_sale_ride.md) already scoped for field invoices
|
||||
(`createInvoice`/`recordPayment` there are the van-sale-specific case; this step is the general
|
||||
warehouse-fulfilled-order invoicing path plus GL posting and reporting).
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
`convertOrderToInvoice` is referenced from [05_delivery_ride.md](./05_delivery_ride.md) but has no
|
||||
home until this step. There is no invoice, payment, or GL entity for warehouse-fulfilled
|
||||
(non-field-sale) orders today.
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Invoice
|
||||
|
||||
`OrderId` (nullable if it originates from a field invoice instead), `StoreId`, `TotalAmount`,
|
||||
`TaxAmount`, `Status` (`Issued`, `PartiallyPaid`, `Paid`), `IssuedAt`.
|
||||
|
||||
### Payment
|
||||
|
||||
`InvoiceId`, `Amount`, `Method`, `CollectedAt` — same shape as the `Payment Record` in
|
||||
[06_sale_ride.md](./06_sale_ride.md); consider whether to unify into one table keyed by a generic
|
||||
`InvoiceId` covering both field invoices and warehouse invoices.
|
||||
|
||||
### GL Posting
|
||||
|
||||
`TransactionId`, `Account`, `DebitAmount`, `CreditAmount`, `PostedAt`.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `createInvoice(orderId)` | `POST /api/orders/{id}/invoice` — the warehouse-fulfilled counterpart to [06_sale_ride.md](./06_sale_ride.md)'s field invoice creation |
|
||||
| `recordPayment(invoiceId, amount, method)` | `POST /api/invoices/{id}/payments` |
|
||||
| `postToGeneralLedger(transactionId)` | Internal — triggered on invoice/payment/credit-note events |
|
||||
| `generateTaxInvoice(invoiceId, jurisdiction)` | `GET /api/invoices/{id}/tax-invoice?jurisdiction=...` |
|
||||
| `getFinancialReport(entityId, period, reportType)` | `GET /api/finance/reports?entityId=...&period=...&reportType=...` |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
- `Store.OutstandingBalance` (from [12_store_management.md](./12_store_management.md)) is
|
||||
maintained by this step's invoice/payment events, not edited directly.
|
||||
- `postToGeneralLedger` must be idempotent per `TransactionId` — replays must not double-post.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Should field invoices ([06_sale_ride.md](./06_sale_ride.md)) and warehouse invoices share one
|
||||
`Invoice` table, or stay as separate entities that both feed the same GL posting logic?
|
||||
- Is e-invoicing (`generateTaxInvoice` for a specific jurisdiction like GST/VAT) an actual near-term
|
||||
requirement, or speculative until a specific market is targeted?
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
@@ -0,0 +1,65 @@
|
||||
# 20 — Reporting, Analytics & BI
|
||||
|
||||
**Status**: Not Started
|
||||
**Depends On**: Every prior numbered step whose data it reports on (this step is a read-only
|
||||
aggregation layer, not a new source of truth)
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Covers [mainPlan.md §14](./mainPlan.md#14-reporting-analytics--bi) in
|
||||
full. This step deliberately reads data other steps already own — it does not introduce new
|
||||
mutable entities.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
Individual dashboards are already referenced piecemeal elsewhere (`getRegionDashboard` in
|
||||
[10_region_management.md](./10_region_management.md), `getStoreSalesDashboard` in
|
||||
[12_store_management.md](./12_store_management.md), `getWarehouseKpis` in
|
||||
[04_loading_event.md](./04_loading_event.md)/[07_ride_reconciliation.md](./07_ride_reconciliation.md)
|
||||
context). This step is the role-aware, cross-cutting dashboard/report layer that sits above all of
|
||||
them.
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
None expected — this step reads existing tables. If a specific report proves too expensive to
|
||||
compute live, a cached/materialized summary table may be added per-report, decided when that report
|
||||
is actually built.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `getDashboardData(role, scope)` | `GET /api/dashboard?scope=...` — role taken from the authenticated user, not a request param |
|
||||
| `generateKpiReport(kpiType, filters)` | `GET /api/reports/{kpiType}?filters=...` |
|
||||
| `getPredictiveAlert(type, scope)` | `GET /api/alerts/predictive?type=...&scope=...` — explicitly flagged below as the highest-risk item in this step |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
- `getDashboardData` must respect the same scope rules (`IScopeAccessService`) as every other
|
||||
endpoint — a dashboard is not an exception to region/route/store scoping.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- `getPredictiveAlert` implies a forecasting/ML component that doesn't exist anywhere in this
|
||||
codebase today — is this in scope at all for the near term, or should it be dropped from this
|
||||
step until a concrete predictive use case and data volume exist to justify it?
|
||||
- Which specific KPIs matter first (fill rate, OOS%, credit risk are mentioned in the reference —
|
||||
confirm the actual priority list with the user before building `generateKpiReport`)?
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
@@ -0,0 +1,67 @@
|
||||
# 21 — Notifications & Alerts
|
||||
|
||||
**Status**: Not Started
|
||||
**Depends On**: 02 — Stock Ledger, 12 — Store Management, 08 — Sales Agent Controls
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Covers [mainPlan.md §15](./mainPlan.md#15-notifications--alerts) in full
|
||||
— a cross-cutting delivery mechanism (email/SMS/push) triggered by events other steps already
|
||||
produce.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
No notification channel exists in this codebase today. This step is purely the delivery mechanism
|
||||
and trigger wiring; the underlying conditions it watches for are already defined elsewhere
|
||||
(reorder point in [02_stock_ledger.md](./02_stock_ledger.md), credit limit in
|
||||
[12_store_management.md](./12_store_management.md), route deviation logic in
|
||||
[09_fraud_controls.md](./09_fraud_controls.md)).
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Notification
|
||||
|
||||
`RecipientUserId`, `Type`, `Payload` (JSON), `Channel` (`Email`, `SMS`, `Push`, `InApp`), `SentAt`,
|
||||
`ReadAt` (nullable).
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `sendLowStockAlert(warehouseId, sku)` | Internal — triggered when a stock movement crosses the reorder point set in [02_stock_ledger.md](./02_stock_ledger.md) |
|
||||
| `sendCreditLimitBreachAlert(storeId)` | Internal — triggered on order/invoice creation when `Store.OutstandingBalance` exceeds `Store.CreditLimit` |
|
||||
| `sendApprovalPendingNotification(approverId, requestId)` | Internal — triggered by `submitOrderForApproval` ([08_sales_agent_controls.md](./08_sales_agent_controls.md)) and return approval ([18_returns_claims.md](./18_returns_claims.md)) |
|
||||
| `sendRouteDeviationAlert(routeId, salesRepId)` | Internal — triggered from [09_fraud_controls.md](./09_fraud_controls.md)'s anomaly flag |
|
||||
| — | `GET /api/notifications` — recipient's own notification inbox |
|
||||
| — | `PUT /api/notifications/{id}/read` |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
- Every `send*` function in §15 is a trigger, not a user-facing endpoint — the only client-facing
|
||||
endpoints are inbox read/list.
|
||||
- Notification delivery failures (e.g. SMS provider down) must not roll back the business
|
||||
transaction that triggered them — fire-and-forget, logged separately.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Which channels are actually needed at launch — in-app only, or SMS/email/push from day one? This
|
||||
significantly changes the infrastructure needed (third-party provider integration, see
|
||||
[22_integration_api.md](./22_integration_api.md)).
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
@@ -0,0 +1,64 @@
|
||||
# 22 — Integration & API Layer
|
||||
|
||||
**Status**: Not Started
|
||||
**Depends On**: 19 — Finance & Accounting, 13 — Fleet Management
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Covers [mainPlan.md §16](./mainPlan.md#16-integration--api-layer) in full
|
||||
— outbound/inbound integration with third-party systems.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
No external system integration exists today. Each function here depends on a specific named
|
||||
third-party provider (accounting platform, telematics provider, B2B portal) that hasn't been
|
||||
chosen yet — this step cannot be scoped precisely until that choice is made per Open Questions.
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Integration Sync Log
|
||||
|
||||
`TargetSystem`, `EntityType`, `EntityId`, `Status` (`Success`, `Failed`, `Retrying`), `AttemptedAt`,
|
||||
`ErrorDetail` (nullable).
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `syncToAccountingSystem(transactionId, targetSystem)` | Internal/background job — triggered on GL posting ([19_finance_accounting.md](./19_finance_accounting.md)) |
|
||||
| `syncTelematicsData(vehicleId, provider)` | Internal/background job — feeds `recordVehicleTelemetry` ([13_fleet_management.md](./13_fleet_management.md)) |
|
||||
| `pushOrderToDistributorPortal(orderId)` | Internal/background job — triggered on order status change ([05_delivery_ride.md](./05_delivery_ride.md)) |
|
||||
| — | `GET /api/integrations/sync-log?targetSystem=...` — for support/debugging |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
- Every outbound sync must be retryable and idempotent — a failed sync logs to `Integration Sync
|
||||
Log` and retries rather than silently dropping.
|
||||
- No integration should be able to block the originating business transaction (order creation, GL
|
||||
posting) — sync happens after commit, asynchronously.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Which accounting platform (QuickBooks/SAP/Xero — reference doc names all three as examples), and
|
||||
which telematics provider — is either actually contracted yet, or is this step purely
|
||||
speculative until a vendor is chosen?
|
||||
- Does a "distributor-facing B2B portal" already exist to push orders to, or is this describing a
|
||||
future/external system not yet built?
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
@@ -0,0 +1,66 @@
|
||||
# 23 — Mobile & Offline Capability
|
||||
|
||||
**Status**: Not Started
|
||||
**Depends On**: 06 — Sale Ride Execution, 05 — Delivery Ride Execution
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Covers [mainPlan.md §17](./mainPlan.md#17-mobile--offline-capability) in
|
||||
full — offline-first support for field agents (drivers/sales reps) whose connectivity can't be
|
||||
assumed during a ride.
|
||||
|
||||
---
|
||||
|
||||
## Background / Context
|
||||
|
||||
There is no mobile client in this repository — this step is the server-side contract a future
|
||||
mobile client would sync against (queueing, conflict resolution, barcode resolution), not a client
|
||||
implementation itself.
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Offline Action Queue Entry
|
||||
|
||||
`DeviceId`, `UserId`, `ActionType` (e.g. `CreateFieldInvoice`, `RecordDelivery`), `Payload` (JSON),
|
||||
`ClientCreatedAt`, `SyncedAt` (nullable), `ConflictStatus` (`None`, `Resolved`, `Unresolved`).
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Function reference | Endpoint intent |
|
||||
|---|---|
|
||||
| `queueOfflineOrder(orderData)` | Client-side only — server sees it as a normal write once synced |
|
||||
| `syncOfflineQueue(deviceId)` | `POST /api/sync?deviceId=...` — batch endpoint accepting queued actions, applying them in order, returning per-action success/conflict |
|
||||
| `scanBarcode(barcodeValue)` | `GET /api/items/by-barcode/{barcodeValue}` — resolves to an `Item` (needs a `Barcode` field added to `Item`, see [01_item_catalog.md](./01_item_catalog.md)) |
|
||||
|
||||
---
|
||||
|
||||
## Business Rules
|
||||
|
||||
- Every action type accepted by `syncOfflineQueue` must be safely replayable (idempotent by a
|
||||
client-generated id) — a duplicate sync of the same queued action must not double-apply (e.g.
|
||||
double-decrement stock).
|
||||
- A sync conflict (e.g. stock sold twice from two devices while offline) must not silently pick a
|
||||
winner — flagged `Unresolved` and surfaced to a supervisor, consistent with the reconciliation
|
||||
approach in [07_ride_reconciliation.md](./07_ride_reconciliation.md).
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Is a mobile client actually planned/being built by this team, or is offline support scoped for a
|
||||
hypothetical future client? This changes whether the sync contract needs to be finalized now or
|
||||
can wait.
|
||||
- Does `Item` need a `Barcode` field added now (extends [01_item_catalog.md](./01_item_catalog.md)),
|
||||
or is barcode scanning deferred with the rest of this step?
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
> Fill this section in during planning discussion before writing any code.
|
||||
@@ -36,7 +36,7 @@ They must be resolved before new feature steps begin because later steps depend
|
||||
|---|---|---|---|---|
|
||||
| 00 | Existing Gap Fixes | Done | — | [00_existing_gaps.md](./00_existing_gaps.md) |
|
||||
| 01 | Item Catalog | Done | 00 | [01_item_catalog.md](./01_item_catalog.md) |
|
||||
| 02 | Stock Ledger | Not Started | 01 | [02_stock_ledger.md](./02_stock_ledger.md) |
|
||||
| 02 | Stock Ledger | Done | 01 | [02_stock_ledger.md](./02_stock_ledger.md) |
|
||||
| 03 | Ride Lifecycle | Not Started | 02 | [03_ride_lifecycle.md](./03_ride_lifecycle.md) |
|
||||
| 04 | Loading Event | Not Started | 03 | [04_loading_event.md](./04_loading_event.md) |
|
||||
| 05 | Delivery Ride Execution | Not Started | 04 | [05_delivery_ride.md](./05_delivery_ride.md) |
|
||||
@@ -44,6 +44,239 @@ They must be resolved before new feature steps begin because later steps depend
|
||||
| 07 | Ride Close & Reconciliation | Not Started | 05, 06 | [07_ride_reconciliation.md](./07_ride_reconciliation.md) |
|
||||
| 08 | Sales Agent Controls | Not Started | 07 | [08_sales_agent_controls.md](./08_sales_agent_controls.md) |
|
||||
| 09 | Fraud Controls & Audit Trail | Not Started | 08 | [09_fraud_controls.md](./09_fraud_controls.md) |
|
||||
| 10 | Region Management (Beyond CRUD) | Not Started | 00 | [10_region_management.md](./10_region_management.md) |
|
||||
| 11 | Route & Sales Visit Management | Not Started | 00 | [11_route_sales_management.md](./11_route_sales_management.md) |
|
||||
| 12 | Store Management (Beyond CRUD) | Not Started | 00 | [12_store_management.md](./12_store_management.md) |
|
||||
| 13 | Fleet Management (Beyond CRUD) | Not Started | 00 | [13_fleet_management.md](./13_fleet_management.md) |
|
||||
| 14 | Pricing & Supplier Master Data | Not Started | 01 | [14_pricing_and_suppliers.md](./14_pricing_and_suppliers.md) |
|
||||
| 15 | Procurement / Purchasing | Not Started | 01, 14 | [15_procurement.md](./15_procurement.md) |
|
||||
| 16 | Sales Force Automation / CRM | Not Started | 12 | [16_sfa_crm.md](./16_sfa_crm.md) |
|
||||
| 17 | Promotions & Trade Scheme Management | Not Started | 01, 14 | [17_promotions_schemes.md](./17_promotions_schemes.md) |
|
||||
| 18 | Returns & Claims Management | Not Started | 05, 06 | [18_returns_claims.md](./18_returns_claims.md) |
|
||||
| 19 | Finance & Accounting | Not Started | 05, 06, 18 | [19_finance_accounting.md](./19_finance_accounting.md) |
|
||||
| 20 | Reporting, Analytics & BI | Not Started | all prior | [20_reporting_analytics.md](./20_reporting_analytics.md) |
|
||||
| 21 | Notifications & Alerts | Not Started | 02, 12, 08 | [21_notifications_alerts.md](./21_notifications_alerts.md) |
|
||||
| 22 | Integration & API Layer | Not Started | 19, 13 | [22_integration_api.md](./22_integration_api.md) |
|
||||
| 23 | Mobile & Offline Capability | Not Started | 05, 06 | [23_mobile_offline.md](./23_mobile_offline.md) |
|
||||
|
||||
Steps 00–09 are the original ride/stock-ledger roadmap and keep their strict dependency order.
|
||||
Steps 10–23 fill in every remaining area from the [Function Reference by Feature](#function-reference-by-feature)
|
||||
section above that wasn't already covered by 00–09; they depend only on the specific earlier steps
|
||||
noted above, not on the full 00–09 chain, and can be resequenced relative to each other as
|
||||
priorities become clearer.
|
||||
|
||||
---
|
||||
|
||||
## Function Reference by Feature
|
||||
|
||||
Reference only, not a build plan — background material to consult while planning/implementing the
|
||||
steps above and below. Service-layer function names and what each should do when implemented.
|
||||
Names are illustrative (camelCase) — adapt to your language/framework conventions. None of the
|
||||
functions below are implemented yet unless noted. User Management/RBAC functions aren't repeated
|
||||
here — see `SRS_User_Management_Module.pdf`.
|
||||
|
||||
### 1. Order Management
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `createOrder(storeId, items[], salesRepId)` | Validates stock/credit, creates a new order in `pending` state. | Partially implemented — `OrderService.CreateAsync` exists but has no credit/stock validation or state machine yet. |
|
||||
| `validateCreditLimit(storeId, orderAmount)` | Checks store's outstanding balance + new order value against its credit limit. | Not implemented — no credit limit field on `Store` yet. |
|
||||
| `checkStockAvailability(items[])` | Confirms requested SKUs/quantities are available at the fulfilling warehouse. | Not implemented — depends on [02_stock_ledger.md](./02_stock_ledger.md). |
|
||||
| `applyPricingAndDiscounts(orderId)` | Runs the active price list and eligible scheme rules against order lines. | Not implemented — depends on §7 Master Data and §11 Promotions below. |
|
||||
| `submitOrderForApproval(orderId)` | Routes the order to the correct approver based on its value/discount threshold. | Not implemented — depends on [08_sales_agent_controls.md](./08_sales_agent_controls.md). |
|
||||
| `approveOrder(orderId, approverId)` / `rejectOrder(orderId, reason)` | Moves order to `approved` or `rejected`, logging the actor. | Not implemented. |
|
||||
| `updateOrderStatus(orderId, status)` | Transitions order through `pending → approved → picked → dispatched → delivered → invoiced`. | Not implemented — `Order` has no status field or state machine today. |
|
||||
| `convertOrderToInvoice(orderId)` | Generates the invoice once the order is dispatched/delivered. | Not implemented — depends on §13 Finance & Accounting. |
|
||||
| `createBackorder(orderId, unfulfilledItems[])` | Splits unfulfillable quantity into a linked backorder. | Not implemented. |
|
||||
| `cancelOrder(orderId, reason)` | Cancels an order pre-dispatch and releases any reserved stock. | Not implemented — `OrderController` supports soft delete only, no reason code or stock release. |
|
||||
| `getOrdersByStore(storeId)` / `getOrdersBySalesRep(salesRepId)` | Scoped order listings. | `getOrdersByStore` implemented (`GET /api/orders/store/{storeId}`). `getOrdersBySalesRep` not implemented. |
|
||||
| `getOrderAnalytics(filters)` | Returns fill rate, average order value, rejection rate over a period. | Not implemented. |
|
||||
|
||||
### 2. Region Management
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `createRegion(name, boundaries)` | Creates a new region record. | Partially implemented — `RegionController` supports create with `Name` only, no `boundaries` field. |
|
||||
| `assignAreaToRegion(areaId, regionId)` | Links an existing area under a region. | Not implemented — no `Area` entity exists in the schema. |
|
||||
| `setRegionalTarget(regionId, period, targetValue)` | Sets the sales target for a region for a given period. | Not implemented. |
|
||||
| `setRegionalBudget(regionId, period, budgetAmount)` | Allocates budget at the regional level. | Not implemented. |
|
||||
| `mapAgencyToRegion(agencyId, regionId)` | Assigns a Regional Agency's contract scope to a region. | Not implemented — `RegionalAgency` is currently a `Role`, not a distinct agency entity; scope is via `UserRegionScope`. |
|
||||
| `setRegionalTaxRule(regionId, ruleConfig)` | Overrides tax/pricing rules for a specific region. | Not implemented. |
|
||||
| `getRegionHierarchy(regionId)` | Returns all areas/territories nested under a region. | Not implemented — see [user_management_PLAN.md](../user_management_PLAN.md) territory hierarchy prerequisite. |
|
||||
| `getRegionDashboard(regionId, period)` | Aggregated KPIs: sales, fill rate, agency performance for the region. | Not implemented. |
|
||||
|
||||
### 3. Route Management
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `createRoute(name, areaId)` | Creates a new sales visit route within an area. | Partially implemented — `RouteController` supports create, but by `RegionId` not `areaId` (no `Area` entity exists). |
|
||||
| `assignOutletsToRoute(routeId, outletIds[])` | Maps a set of stores to a route. | Partially implemented — a `Store` has a single `FK_RouteId`, set via `StoreController` create/update; no bulk-assign endpoint. |
|
||||
| `assignRouteToSalesRep(routeId, salesRepId)` | Assigns ownership of a route to an SR. | Not implemented — only `UserRouteScope` exists (scope grant), not a single-owner assignment. |
|
||||
| `optimizeRouteSequence(routeId)` | Reorders the visit sequence for efficiency (distance/priority-based). | Not implemented. |
|
||||
| `recordRouteCheckIn(routeId, outletId, gpsCoords, timestamp)` | Logs an SR's arrival at an outlet. | Not implemented. |
|
||||
| `recordRouteCheckOut(routeId, outletId, timestamp)` | Logs departure and closes out the visit. | Not implemented. |
|
||||
| `getRouteAdherence(routeId, date)` | Compares planned vs. actual visits for a route/day. | Not implemented — depends on check-in/check-out above. |
|
||||
| `reassignRoute(routeId, newSalesRepId)` | Moves a route to a different SR. | Not implemented. |
|
||||
| `getRoutePerformance(routeId, period)` | Sales per route, strike rate, missed-visit count. | Not implemented. |
|
||||
| `planDeliveryRoute(warehouseId, orderIds[])` | Builds the warehouse-to-outlet dispatch route (separate from the sales visit route). | Not implemented — this is the `DeliveryRun` concept; see [03_ride_lifecycle.md](./03_ride_lifecycle.md). |
|
||||
|
||||
### 4. Store (Outlet) Management
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `createStore(storeData, kycDocs)` | Onboards a new outlet with required KYC. | Partially implemented — `StoreController` create exists, no KYC document handling. |
|
||||
| `verifyStoreKyc(storeId)` | Marks KYC as verified/rejected by an approver. | Not implemented. |
|
||||
| `updateStoreCreditLimit(storeId, newLimit, approverId)` | Changes a store's credit limit, subject to approval authority. | Not implemented — no credit limit field on `Store`. |
|
||||
| `getStoreOutstandingBalance(storeId)` | Returns current receivables against the store. | Not implemented — depends on §13 Finance & Accounting. |
|
||||
| `classifyStore(storeId, segment)` | Sets/updates the store's segment (key account, general trade, etc.). | Not implemented — no segment field on `Store`. |
|
||||
| `deactivateStore(storeId, reason)` | Soft-deletes a store, blocking new orders against it. | Partially implemented — `DELETE /api/stores/{id}` soft-deletes via `IsActive`, no reason code captured. |
|
||||
| `getStoreVisitHistory(storeId)` | Returns visit compliance history from Route Management. | Not implemented — depends on route check-in/check-out (§3). |
|
||||
| `checkStoreSchemeEligibility(storeId, schemeId)` | Evaluates whether a store qualifies for a given promotion. | Not implemented — depends on §11 Promotions. |
|
||||
| `getStoreSalesDashboard(storeId, period)` | Store-level sales and outstanding summary. | Not implemented. |
|
||||
|
||||
### 5. Vehicle / Fleet Management
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `registerVehicle(vehicleData)` | Creates the vehicle master record (registration, capacity, ownership). | Partially implemented — `VehicleController` create exists with `RegNumber`/`NumberPlate` only, no capacity/ownership fields. |
|
||||
| `assignVehicleToRoute(vehicleId, routeId)` | Assigns a vehicle to a delivery/sales route. | Not implemented — `Vehicle` has no route link; only `DeliveryRun.FK_RouteId`/`FK_VehicleId` model this, and `DeliveryRun` is unwired (see [03_ride_lifecycle.md](./03_ride_lifecycle.md)). |
|
||||
| `assignDriverToVehicle(vehicleId, driverId)` | Links a driver to a vehicle, checking license validity. | Not implemented — no `Driver` entity exists yet (`DeliveryRun.FK_DriverId` is commented out in the model). |
|
||||
| `scheduleMaintenance(vehicleId, maintenanceType, dueAt)` | Sets up a preventive maintenance task (mileage/calendar-based). | Not implemented. |
|
||||
| `logFuelExpense(vehicleId, amount, liters, date)` | Records a fuel transaction against the vehicle. | Not implemented. |
|
||||
| `recordVehicleTelemetry(vehicleId, gpsCoords, speed, timestamp)` | Ingests telematics data (location, driver behavior). | Not implemented — see §16 Integration. |
|
||||
| `checkDriverCompliance(driverId)` | Validates license/permit status before dispatch. | Not implemented — depends on a `Driver` entity. |
|
||||
| `calculateLoadPlan(vehicleId, orderIds[])` | Matches order volume/weight to available vehicle capacity. | Not implemented — depends on [04_loading_event.md](./04_loading_event.md). |
|
||||
| `getVehicleUtilization(vehicleId, period)` | Utilization and cost-per-delivery metrics. | Not implemented. |
|
||||
| `retireVehicle(vehicleId, reason)` | Removes a vehicle from active fleet, retaining history. | Partially implemented — `DELETE /api/vehicles/{id}` soft-deletes via `IsActive`, no reason code captured. |
|
||||
|
||||
### 6. Warehouse Management
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `createGoodsReceipt(poId, receivedItems[])` | Records inbound stock against a purchase order (GRN). | Not implemented — depends on §9 Procurement. |
|
||||
| `inspectGoodsReceipt(grnId, inspectionResult)` | Logs quality-check outcome before putaway. | Not implemented. |
|
||||
| `putawayStock(grnId, locationAssignments[])` | Assigns received stock to warehouse locations (zone/ABC-based). | Not implemented — depends on [02_stock_ledger.md](./02_stock_ledger.md). |
|
||||
| `getStockByLocation(warehouseId, sku)` | Returns real-time quantity and location for a SKU. | Not implemented — depends on [02_stock_ledger.md](./02_stock_ledger.md). |
|
||||
| `generatePickList(orderIds[])` | Creates a pick list/task for one or more orders. | Not implemented — overlaps with [04_loading_event.md](./04_loading_event.md). |
|
||||
| `confirmPicking(pickListId, pickedItems[])` | Marks items as picked, decrementing available stock. | Not implemented. |
|
||||
| `packOrder(orderId, packageDetails)` | Records packing completion and package details. | Not implemented. |
|
||||
| `createCrossDockTransfer(inboundId, outboundOrderId)` | Routes inbound stock directly to an outbound order, skipping storage. | Not implemented. |
|
||||
| `runCycleCount(warehouseId, zoneId)` | Initiates a scheduled partial stock count. | Not implemented — overlaps with [07_ride_reconciliation.md](./07_ride_reconciliation.md) physical count concept. |
|
||||
| `reconcileStock(warehouseId, countResults)` | Adjusts system stock to match physical count results. | Not implemented — see [07_ride_reconciliation.md](./07_ride_reconciliation.md). |
|
||||
| `buildDispatchLoad(vehicleId, orderIds[])` | Groups orders into a dispatch load for a specific vehicle/route. | Not implemented — see [04_loading_event.md](./04_loading_event.md). |
|
||||
| `receiveReturn(returnId, items[], condition)` | Processes returned goods back into the warehouse. | Not implemented — see §12 Returns & Claims. |
|
||||
| `getWarehouseKpis(warehouseId, period)` | Dock-to-stock time, pick accuracy, turnover rate. | Not implemented. |
|
||||
|
||||
### 7. Master Data Management
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `createProduct(skuData)` | Creates a SKU with category, UOM, barcode, pack hierarchy. | Not implemented — `Item` model exists but has no properties; see [01_item_catalog.md](./01_item_catalog.md). |
|
||||
| `deactivateSku(sku, reason)` | Retires a SKU from active ordering. | Not implemented. |
|
||||
| `createPriceList(name, region, effectiveDates)` | Defines a new price list scoped to a region/date range. | Not implemented — no price list entity exists. |
|
||||
| `updateProductPricing(sku, priceListId, price)` | Sets/updates the price of a SKU within a price list. | Not implemented. |
|
||||
| `createSupplierMaster(supplierData)` | Registers a new supplier/vendor. | Not implemented — no `Supplier` entity exists. |
|
||||
|
||||
### 8. Enterprise-wide Inventory & Stock
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `getMultiLocationStock(sku)` | Returns stock levels for a SKU across all warehouses/agencies. | Not implemented — see [02_stock_ledger.md](./02_stock_ledger.md). |
|
||||
| `setReorderPoint(sku, warehouseId, threshold)` | Configures the auto-replenishment trigger level. | Not implemented. |
|
||||
| `createStockTransfer(fromWarehouseId, toWarehouseId, items[])` | Initiates an inter-warehouse stock movement. | Not implemented. |
|
||||
| `trackBatchExpiry(sku, batchNo, expiryDate)` | Registers batch/expiry data for FEFO enforcement. | Not implemented. |
|
||||
| `calculateStockValuation(warehouseId, method)` | Computes stock value using FIFO/weighted-average. | Not implemented. |
|
||||
|
||||
### 9. Procurement / Purchasing
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `createPurchaseRequisition(items[], requestedBy)` | Raises an internal request to purchase stock. | Not implemented — no procurement entities exist. |
|
||||
| `createPurchaseOrder(supplierId, items[])` | Issues a PO to a supplier. | Not implemented. |
|
||||
| `matchInvoiceToPO(invoiceId, poId, grnId)` | Performs the 3-way match (PO–GRN–Invoice) before payment. | Not implemented. |
|
||||
| `evaluateSupplier(supplierId, criteria)` | Scores a supplier on delivery/quality/price performance. | Not implemented. |
|
||||
| `createPurchaseReturn(poId, items[], reason)` | Initiates a return of received goods to the supplier. | Not implemented. |
|
||||
|
||||
### 10. Sales Force Automation (SFA) / CRM
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `logCustomerInteraction(storeId, notes)` | Records a visit note or customer interaction. | Not implemented. |
|
||||
| `trackTargetVsAchievement(userId, period)` | Compares a user's sales target to actuals. | Not implemented. |
|
||||
| `calculateIncentive(userId, period)` | Computes commission/incentive payout based on performance. | Not implemented. |
|
||||
| `createLead(prospectData)` | Captures a prospective new outlet. | Not implemented — no `Lead` entity exists. |
|
||||
| `convertLeadToStore(leadId)` | Converts a qualified lead into an active store record. | Not implemented. |
|
||||
|
||||
### 11. Promotions & Trade Scheme Management
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `createScheme(schemeRules, eligibility, dateRange)` | Defines a promotion (buy-X-get-Y, slab discount, etc.). | Not implemented — no scheme entity exists. |
|
||||
| `evaluateSchemeEligibility(storeId, orderId)` | Checks whether an order qualifies for a scheme. | Not implemented. |
|
||||
| `applySchemeDiscount(orderId, schemeId)` | Applies the scheme's discount to order pricing. | Not implemented. |
|
||||
| `trackSchemeSpend(schemeId)` | Aggregates total discount value given under a scheme. | Not implemented. |
|
||||
| `getSchemeROI(schemeId)` | Compares scheme spend against incremental sales generated. | Not implemented. |
|
||||
|
||||
### 12. Returns & Claims Management
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `createReturnRequest(orderId, items[], reasonCode)` | Initiates a return against a delivered order. | Not implemented — see [09_fraud_controls.md](./09_fraud_controls.md) for the fraud-control angle on returns. |
|
||||
| `approveReturn(returnId, approverId)` | Approves/rejects the return request. | Not implemented. |
|
||||
| `generateCreditNote(returnId)` | Issues a credit note against the original invoice. | Not implemented. |
|
||||
| `createClaim(agencyId, claimDetails)` | Raises a claim against a Regional Agency (e.g., damaged stock). | Not implemented. |
|
||||
| `settleClaim(claimId, settlementAmount)` | Closes out a claim with a final settlement amount. | Not implemented. |
|
||||
|
||||
### 13. Finance & Accounting
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `createInvoice(orderId)` | Generates a formal invoice from a fulfilled order. | Not implemented — no invoice entity exists. |
|
||||
| `recordPayment(invoiceId, amount, method)` | Logs a payment/collection against an invoice. | Not implemented. |
|
||||
| `postToGeneralLedger(transactionId)` | Posts a financial transaction to the GL. | Not implemented. |
|
||||
| `generateTaxInvoice(invoiceId, jurisdiction)` | Produces a tax-compliant invoice (GST/VAT e-invoicing). | Not implemented. |
|
||||
| `getFinancialReport(entityId, period, reportType)` | Returns P&L/cash-flow reports scoped to a region/area. | Not implemented. |
|
||||
|
||||
### 14. Reporting, Analytics & BI
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `getDashboardData(role, scope)` | Returns the role-appropriate dashboard payload. | Not implemented. |
|
||||
| `generateKpiReport(kpiType, filters)` | Produces a specific KPI report (fill rate, OOS%, credit risk, etc.). | Not implemented. |
|
||||
| `getPredictiveAlert(type, scope)` | Returns AI-flagged issues, e.g. a region's declining fill rate. | Not implemented. |
|
||||
|
||||
### 15. Notifications & Alerts
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `sendLowStockAlert(warehouseId, sku)` | Notifies relevant roles when stock drops below the reorder point. | Not implemented. |
|
||||
| `sendCreditLimitBreachAlert(storeId)` | Notifies approvers when a store exceeds its credit limit. | Not implemented. |
|
||||
| `sendApprovalPendingNotification(approverId, requestId)` | Notifies an approver of a pending action. | Not implemented. |
|
||||
| `sendRouteDeviationAlert(routeId, salesRepId)` | Flags when an SR deviates materially from the planned route. | Not implemented — see [09_fraud_controls.md](./09_fraud_controls.md) geofencing check. |
|
||||
|
||||
### 16. Integration & API Layer
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `syncToAccountingSystem(transactionId, targetSystem)` | Pushes a transaction to an external accounting platform (QuickBooks/SAP/Xero). | Not implemented. |
|
||||
| `syncTelematicsData(vehicleId, provider)` | Pulls GPS/telematics data from a third-party provider. | Not implemented. |
|
||||
| `pushOrderToDistributorPortal(orderId)` | Syncs order status to a distributor-facing B2B portal. | Not implemented. |
|
||||
|
||||
### 17. Mobile & Offline Capability
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `queueOfflineOrder(orderData)` | Stores an order locally on the device when offline. | Not implemented — no mobile client in this repository. |
|
||||
| `syncOfflineQueue(deviceId)` | Pushes queued offline actions to the server once connectivity returns. | Not implemented. |
|
||||
| `scanBarcode(barcodeValue)` | Resolves a scanned barcode to a SKU or warehouse location. | Not implemented. |
|
||||
|
||||
### 18. Compliance & Audit
|
||||
|
||||
| Function | What it does | Status |
|
||||
|---|---|---|
|
||||
| `logAuditEvent(userId, action, entityType, entityId)` | Writes an immutable audit trail entry for a state-changing action. | Not implemented — see [09_fraud_controls.md](./09_fraud_controls.md), which scopes an audit log as part of Fraud Controls. |
|
||||
| `checkLicenseCompliance(entityId, licenseType)` | Validates a required license/permit is current (e.g., regulated goods). | Not implemented. |
|
||||
| `generateComplianceReport(period)` | Produces a report of compliance status across the org for a period. | Not implemented. |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user