Merge branch 'dev' of https://gitea.hexdive.com/New_REP_SYSTEM/ERP_Distribution into dev
CI/CD - ERP Distribution / Build & Deploy Frontend (pull_request) Failing after 11m11s
CI/CD - ERP Distribution / Build & Deploy Backend (pull_request) Failing after 12m39s

This commit is contained in:
Kalana B. Thilakarathna
2026-09-20 16:03:00 +05:30
28 changed files with 12769 additions and 184 deletions
-1
View File
@@ -37,7 +37,6 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
public DbSet<Supplier> Suppliers { get; set; }
public DbSet<DriverProfile> DriverProfiles { get; set; }
public DbSet<DriverComplianceConfig> DriverComplianceConfigs { get; set; }
public DbSet<RideCancelRequest> RideCancelRequests { get; set; }
public DbSet<Scheme> Schemes { get; set; }
public DbSet<SchemeRedemption> SchemeRedemptions { get; set; }
public DbSet<SchemeBundleComponent> SchemeBundleComponents { get; set; }
+7 -1
View File
@@ -16,6 +16,9 @@ public static class CommerceSeed
private static readonly Guid SchemeRedemption1Id = new("50000000-0000-0000-0000-000000000001");
private static readonly Guid Payment1Id = new("47000000-0000-0000-0000-000000000001");
private static readonly Guid Payment2Id = new("47000000-0000-0000-0000-000000000002");
private static readonly Guid Payment3Id = new("47000000-0000-0000-0000-000000000003");
private static readonly Guid Payment4Id = new("47000000-0000-0000-0000-000000000004");
private static readonly Guid SaleReturn1Id = new("51000000-0000-0000-0000-000000000001");
private static readonly Guid SaleReturnItem1Id = new("52000000-0000-0000-0000-000000000001");
@@ -84,7 +87,10 @@ public static class CommerceSeed
);
modelBuilder.Entity<Payment>().HasData(
new Payment { Id = Payment1Id, FK_InvoiceId = OperationsDataSeed.Invoice1Id, Amount = 3000m, Method = PaymentMethod.Cash, CollectedAt = new DateTime(2026, 8, 25, 9, 30, 0), CollectedBy = MasterDataSeed.SalesRep1UserId, CreatedAt = new DateTime(2026, 8, 25, 9, 30, 0), IsActive = true }
new Payment { Id = Payment1Id, FK_InvoiceId = OperationsDataSeed.Invoice1Id, Amount = 3000m, Method = PaymentMethod.Cash, EntryMode = EntryMode.RealTime, CollectedAt = new DateTime(2026, 8, 25, 9, 30, 0), CollectedBy = MasterDataSeed.SalesRep1UserId, CreatedAt = new DateTime(2026, 8, 25, 9, 30, 0), IsActive = true },
new Payment { Id = Payment2Id, FK_InvoiceId = OperationsDataSeed.Invoice2Id, Amount = 1000m, Method = PaymentMethod.Cash, EntryMode = EntryMode.RealTime, CollectedAt = new DateTime(2026, 8, 31, 9, 20, 0), CollectedBy = MasterDataSeed.SalesRep2UserId, CreatedAt = new DateTime(2026, 8, 31, 9, 20, 0), IsActive = true },
new Payment { Id = Payment3Id, FK_InvoiceId = OperationsDataSeed.Invoice2Id, Amount = 800m, Method = PaymentMethod.Cheque, EntryMode = EntryMode.DayEndManual, ChequeNumber = "000456", ChequeDate = new DateTime(2026, 8, 31), CollectedAt = new DateTime(2026, 8, 31, 11, 0, 0), CollectedBy = MasterDataSeed.SalesRep2UserId, RecordedBy = MasterDataSeed.Supervisor1UserId, CreatedAt = new DateTime(2026, 8, 31, 18, 0, 0), IsActive = true },
new Payment { Id = Payment4Id, FK_InvoiceId = OperationsDataSeed.Invoice2Id, Amount = 300m, Method = PaymentMethod.Credit, EntryMode = EntryMode.RealTime, CollectedAt = new DateTime(2026, 8, 31, 11, 5, 0), CollectedBy = MasterDataSeed.SalesRep2UserId, CreatedAt = new DateTime(2026, 8, 31, 11, 5, 0), IsActive = true }
);
modelBuilder.Entity<SaleReturn>().HasData(
+34 -1
View File
@@ -13,12 +13,15 @@ public static class OperationsDataSeed
internal static readonly Guid ShopPO1Id = new("33000000-0000-0000-0000-000000000001");
internal static readonly Guid ShopPO2Id = new("33000000-0000-0000-0000-000000000002");
internal static readonly Guid ShopPO3Id = new("33000000-0000-0000-0000-000000000003");
internal static readonly Guid ShopPOItem1Id = new("34000000-0000-0000-0000-000000000001");
internal static readonly Guid ShopPOItem2Id = new("34000000-0000-0000-0000-000000000002");
internal static readonly Guid ShopPOItem3Id = new("34000000-0000-0000-0000-000000000003");
internal static readonly Guid ShopPOItem4Id = new("34000000-0000-0000-0000-000000000004");
internal static readonly Guid Invoice1Id = new("34500000-0000-0000-0000-000000000001");
internal static readonly Guid Invoice2Id = new("34500000-0000-0000-0000-000000000002");
internal static readonly Guid LoadingSheet1Id = new("35000000-0000-0000-0000-000000000001");
@@ -86,13 +89,28 @@ public static class OperationsDataSeed
FulfilledAt = null,
CreatedAt = new DateTime(2026, 8, 30, 10, 0, 0),
IsActive = true
},
new ShopPO
{
Id = ShopPO3Id,
FK_StoreId = MasterDataSeed.KandyStoreId,
RaisedById = MasterDataSeed.SalesRep2UserId,
InvoiceId = Invoice2Id,
TotalAmount = 2400m,
Status = ShopPOStatus.Fulfilled,
ConfirmedAt = new DateTime(2026, 8, 31, 8, 30, 0),
ConfirmedBy = MasterDataSeed.Supervisor1UserId,
FulfilledAt = new DateTime(2026, 8, 31, 13, 0, 0),
CreatedAt = new DateTime(2026, 8, 31, 9, 0, 0),
IsActive = true
}
);
modelBuilder.Entity<ShopPOItem>().HasData(
new ShopPOItem { Id = ShopPOItem1Id, FK_ShopPOId = ShopPO1Id, FK_ItemId = MasterDataSeed.Coke500ItemId, Quantity = 30, QuantityFulfilled = 30, UnitPrice = 60m, CreatedAt = new DateTime(2026, 8, 25, 9, 0, 0), IsActive = true },
new ShopPOItem { Id = ShopPOItem2Id, FK_ShopPOId = ShopPO1Id, FK_ItemId = MasterDataSeed.LaysClassicItemId, Quantity = 24, QuantityFulfilled = 24, UnitPrice = 50m, CreatedAt = new DateTime(2026, 8, 25, 9, 0, 0), IsActive = true },
new ShopPOItem { Id = ShopPOItem3Id, FK_ShopPOId = ShopPO2Id, FK_ItemId = MasterDataSeed.Coke1500ItemId, Quantity = 10, QuantityFulfilled = 0, UnitPrice = 120m, CreatedAt = new DateTime(2026, 8, 30, 10, 0, 0), IsActive = true }
new ShopPOItem { Id = ShopPOItem3Id, FK_ShopPOId = ShopPO2Id, FK_ItemId = MasterDataSeed.Coke1500ItemId, Quantity = 10, QuantityFulfilled = 0, UnitPrice = 120m, CreatedAt = new DateTime(2026, 8, 30, 10, 0, 0), IsActive = true },
new ShopPOItem { Id = ShopPOItem4Id, FK_ShopPOId = ShopPO3Id, FK_ItemId = MasterDataSeed.Coke1500ItemId, Quantity = 20, QuantityFulfilled = 20, UnitPrice = 120m, CreatedAt = new DateTime(2026, 8, 31, 9, 0, 0), IsActive = true }
);
modelBuilder.Entity<Invoice>().HasData(
@@ -110,6 +128,21 @@ public static class OperationsDataSeed
TotalAmount = 3000m,
CreatedAt = new DateTime(2026, 8, 25, 8, 30, 0),
IsActive = true
},
new Invoice
{
Id = Invoice2Id,
FK_ShopPOId = ShopPO3Id,
Status = InvoiceStatus.Printed,
PaymentStatus = PaymentStatus.PartiallyPaid,
CreatedBy = MasterDataSeed.SalesRep2UserId,
PrintedAt = new DateTime(2026, 8, 31, 9, 15, 0),
PrintedBy = MasterDataSeed.SalesRep2UserId,
FinalizedAt = null,
FinalizedBy = null,
TotalAmount = 2400m,
CreatedAt = new DateTime(2026, 8, 31, 9, 0, 0),
IsActive = true
}
);
@@ -32,8 +32,6 @@ public static class OperationsExtrasSeed
private static readonly Guid DriverComplianceConfig1Id = new("64000000-0000-0000-0000-000000000001");
private static readonly Guid RideCancelRequest1Id = new("65000000-0000-0000-0000-000000000001");
private static readonly Guid RouteVisitLog1Id = new("66000000-0000-0000-0000-000000000001");
private static readonly Guid RouteVisitLog2Id = new("66000000-0000-0000-0000-000000000002");
@@ -95,21 +93,6 @@ public static class OperationsExtrasSeed
new DriverComplianceConfig { Id = DriverComplianceConfig1Id, EnforceLicenseExpiry = true, EnforceMedicalClearance = true, UpdatedAt = new DateTime(2026, 8, 20), UpdatedBy = MasterDataSeed.Supervisor1UserId }
);
modelBuilder.Entity<RideCancelRequest>().HasData(
new RideCancelRequest
{
Id = RideCancelRequest1Id,
FK_RideId = OperationsDataSeed.ActiveDeliveryRunId,
TypeCode = CancelRequestType.VehicleBreakdown,
Reason = "Reported minor engine issue, resolved after inspection",
RequestedBy = MasterDataSeed.Driver2UserId,
RequestedAt = new DateTime(2026, 8, 30, 9, 10, 0),
Status = CancelRequestStatus.Rejected,
ApprovedBy = MasterDataSeed.Supervisor1UserId,
ApprovedAt = new DateTime(2026, 8, 30, 9, 15, 0)
}
);
modelBuilder.Entity<RouteVisitLog>().HasData(
new RouteVisitLog { Id = RouteVisitLog1Id, FK_RouteId = MasterDataSeed.ColomboRouteId, FK_StoreId = MasterDataSeed.FortStoreId, FK_UserId = MasterDataSeed.SalesRep1UserId, CheckInAt = new DateTime(2026, 8, 25, 9, 0, 0), CheckInLatitude = 6.9344m, CheckInLongitude = 79.8428m, IsDeviationFlagged = false, CheckOutAt = new DateTime(2026, 8, 25, 9, 25, 0), CreatedAt = new DateTime(2026, 8, 25, 9, 0, 0), IsActive = true },
new RouteVisitLog { Id = RouteVisitLog2Id, FK_RouteId = MasterDataSeed.ColomboRouteId, FK_StoreId = MasterDataSeed.DehiwalaStoreId, FK_UserId = MasterDataSeed.SalesRep1UserId, CheckInAt = new DateTime(2026, 8, 25, 10, 0, 0), CheckInLatitude = 6.9000m, CheckInLongitude = 79.8654m, IsDeviationFlagged = true, CheckOutAt = null, CreatedAt = new DateTime(2026, 8, 25, 10, 0, 0), IsActive = true }
@@ -5,6 +5,9 @@ namespace Distribution.Features.Payment;
public record CreatePaymentRequest(
decimal Amount,
PaymentMethod Method,
EntryMode EntryMode = EntryMode.RealTime,
string? ChequeNumber = null,
DateTime? ChequeDate = null,
Guid? CollectedByUserId = null);
public record PaymentResponse(
@@ -12,6 +15,10 @@ public record PaymentResponse(
Guid InvoiceId,
decimal Amount,
PaymentMethod Method,
EntryMode EntryMode,
string? ChequeNumber,
DateTime? ChequeDate,
DateTime CollectedAt,
Guid CollectedBy,
Guid? RecordedBy,
DateTime CreatedAt);
@@ -12,7 +12,8 @@ public class PaymentService(
ICurrentUserService currentUser)
{
private static PaymentResponse ToResponse(Models.Payment p) =>
new(p.Id, p.FK_InvoiceId, p.Amount, p.Method, p.CollectedAt, p.CollectedBy, p.CreatedAt);
new(p.Id, p.FK_InvoiceId, p.Amount, p.Method, p.EntryMode, p.ChequeNumber, p.ChequeDate,
p.CollectedAt, p.CollectedBy, p.RecordedBy, p.CreatedAt);
public async Task<List<PaymentResponse>?> GetByInvoiceIdAsync(Guid invoiceId)
{
@@ -30,16 +31,28 @@ public class PaymentService(
if (invoice.Status == InvoiceStatus.Finalized)
throw new InvalidOperationException("Cannot record a payment against a finalized invoice");
var userId = currentUser.UserId ?? request.CollectedByUserId
if (request.Method == PaymentMethod.Cheque &&
(string.IsNullOrWhiteSpace(request.ChequeNumber) || request.ChequeDate is null))
throw new InvalidOperationException("ChequeNumber and ChequeDate are required when Method is Cheque");
var recorderId = currentUser.UserId ?? request.CollectedByUserId
?? throw new InvalidOperationException("CollectedByUserId is required while auth is unavailable");
var collectedBy = request.CollectedByUserId ?? recorderId;
var recordedBy = request.EntryMode == EntryMode.DayEndManual && collectedBy != recorderId
? recorderId
: (Guid?)null;
var payment = new Models.Payment
{
FK_InvoiceId = invoiceId,
Amount = request.Amount,
Method = request.Method,
EntryMode = request.EntryMode,
ChequeNumber = request.Method == PaymentMethod.Cheque ? request.ChequeNumber : null,
ChequeDate = request.Method == PaymentMethod.Cheque ? request.ChequeDate : null,
CollectedAt = DateTime.Now,
CollectedBy = userId
CollectedBy = collectedBy,
RecordedBy = recordedBy
};
await uow.BeginAsync(ct);
@@ -1,14 +0,0 @@
using Distribution.Models;
namespace Distribution.Features.Ride;
public interface IRideCancelRequestRepository
{
Task<RideCancelRequest?> GetByIdAsync(Guid id);
Task<RideCancelRequest?> GetByRideIdAsync(Guid rideId);
Task<RideCancelRequest?> GetPendingByRideIdAsync(Guid rideId);
Task<List<RideCancelRequest>> GetAllAsync(CancelRequestStatus? status, int page, int pageSize);
Task<int> GetCountAsync(CancelRequestStatus? status);
Task AddAsync(RideCancelRequest request);
void Update(RideCancelRequest request);
}
@@ -1,39 +0,0 @@
using Distribution.Data;
using Distribution.Models;
using Microsoft.EntityFrameworkCore;
namespace Distribution.Features.Ride;
public class RideCancelRequestRepository(AppDBContext context) : IRideCancelRequestRepository
{
public async Task<RideCancelRequest?> GetByIdAsync(Guid id) =>
await context.RideCancelRequests.FirstOrDefaultAsync(r => r.Id == id);
public async Task<RideCancelRequest?> GetByRideIdAsync(Guid rideId) =>
await context.RideCancelRequests
.Where(r => r.FK_RideId == rideId)
.OrderByDescending(r => r.RequestedAt)
.FirstOrDefaultAsync();
public async Task<RideCancelRequest?> GetPendingByRideIdAsync(Guid rideId) =>
await context.RideCancelRequests
.FirstOrDefaultAsync(r => r.FK_RideId == rideId && r.Status == CancelRequestStatus.Pending);
public async Task<List<RideCancelRequest>> GetAllAsync(CancelRequestStatus? status, int page, int pageSize) =>
await context.RideCancelRequests
.Where(r => status == null || r.Status == status)
.OrderByDescending(r => r.RequestedAt)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
public async Task<int> GetCountAsync(CancelRequestStatus? status) =>
await context.RideCancelRequests
.CountAsync(r => status == null || r.Status == status);
public async Task AddAsync(RideCancelRequest request) =>
await context.RideCancelRequests.AddAsync(request);
public void Update(RideCancelRequest request) =>
context.RideCancelRequests.Update(request);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,59 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Distribution.Migrations
{
/// <inheritdoc />
public partial class RemoveRideCancelRequest : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "RideCancelRequests");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "RideCancelRequests",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
FK_RideId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
ApprovedAt = table.Column<DateTime>(type: "datetime(6)", nullable: true),
ApprovedBy = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
Reason = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
RequestedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
RequestedBy = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
Status = table.Column<int>(type: "int", nullable: false),
TypeCode = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_RideCancelRequests", x => x.Id);
table.ForeignKey(
name: "FK_RideCancelRequests_DeliveryRuns_FK_RideId",
column: x => x.FK_RideId,
principalTable: "DeliveryRuns",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.InsertData(
table: "RideCancelRequests",
columns: new[] { "Id", "ApprovedAt", "ApprovedBy", "FK_RideId", "Reason", "RequestedAt", "RequestedBy", "Status", "TypeCode" },
values: new object[] { new Guid("65000000-0000-0000-0000-000000000001"), new DateTime(2026, 8, 30, 9, 15, 0, 0, DateTimeKind.Unspecified), new Guid("29000000-0000-0000-0000-000000000001"), new Guid("31000000-0000-0000-0000-000000000002"), "Reported minor engine issue, resolved after inspection", new DateTime(2026, 8, 30, 9, 10, 0, 0, DateTimeKind.Unspecified), new Guid("29000000-0000-0000-0000-000000000005"), 2, 0 });
migrationBuilder.CreateIndex(
name: "IX_RideCancelRequests_FK_RideId",
table: "RideCancelRequests",
column: "FK_RideId");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,69 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Distribution.Migrations
{
/// <inheritdoc />
public partial class AddPaymentChequeAndEntryMode : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "ChequeDate",
table: "Payments",
type: "datetime(6)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "ChequeNumber",
table: "Payments",
type: "longtext",
nullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AddColumn<int>(
name: "EntryMode",
table: "Payments",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<Guid>(
name: "RecordedBy",
table: "Payments",
type: "char(36)",
nullable: true,
collation: "ascii_general_ci");
migrationBuilder.UpdateData(
table: "Payments",
keyColumn: "Id",
keyValue: new Guid("47000000-0000-0000-0000-000000000001"),
columns: new[] { "ChequeDate", "ChequeNumber", "EntryMode", "RecordedBy" },
values: new object[] { null, null, 0, null });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ChequeDate",
table: "Payments");
migrationBuilder.DropColumn(
name: "ChequeNumber",
table: "Payments");
migrationBuilder.DropColumn(
name: "EntryMode",
table: "Payments");
migrationBuilder.DropColumn(
name: "RecordedBy",
table: "Payments");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,76 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
namespace Distribution.Migrations
{
/// <inheritdoc />
public partial class SeedInvoice2AndMorePayments : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.InsertData(
table: "ShopPOs",
columns: new[] { "Id", "ConfirmedAt", "ConfirmedBy", "CreatedAt", "FK_StoreId", "FulfilledAt", "InvoiceId", "IsActive", "RaisedById", "Status", "TotalAmount" },
values: new object[] { new Guid("33000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 31, 8, 30, 0, 0, DateTimeKind.Unspecified), new Guid("29000000-0000-0000-0000-000000000001"), new DateTime(2026, 8, 31, 9, 0, 0, 0, DateTimeKind.Unspecified), new Guid("26000000-0000-0000-0000-000000000005"), new DateTime(2026, 8, 31, 13, 0, 0, 0, DateTimeKind.Unspecified), new Guid("34500000-0000-0000-0000-000000000002"), true, new Guid("29000000-0000-0000-0000-000000000003"), 2, 2400m });
migrationBuilder.InsertData(
table: "Invoices",
columns: new[] { "Id", "CreatedAt", "CreatedBy", "FK_ShopPOId", "FinalizedAt", "FinalizedBy", "IsActive", "PaymentStatus", "PrintedAt", "PrintedBy", "Status", "TotalAmount" },
values: new object[] { new Guid("34500000-0000-0000-0000-000000000002"), new DateTime(2026, 8, 31, 9, 0, 0, 0, DateTimeKind.Unspecified), new Guid("29000000-0000-0000-0000-000000000003"), new Guid("33000000-0000-0000-0000-000000000003"), null, null, true, 1, new DateTime(2026, 8, 31, 9, 15, 0, 0, DateTimeKind.Unspecified), new Guid("29000000-0000-0000-0000-000000000003"), 1, 2400m });
migrationBuilder.InsertData(
table: "ShopPOItems",
columns: new[] { "Id", "CreatedAt", "FK_ItemId", "FK_ShopPOId", "IsActive", "Quantity", "QuantityFulfilled", "UnitPrice" },
values: new object[] { new Guid("34000000-0000-0000-0000-000000000004"), new DateTime(2026, 8, 31, 9, 0, 0, 0, DateTimeKind.Unspecified), new Guid("23000000-0000-0000-0000-000000000002"), new Guid("33000000-0000-0000-0000-000000000003"), true, 20, 20, 120m });
migrationBuilder.InsertData(
table: "Payments",
columns: new[] { "Id", "Amount", "ChequeDate", "ChequeNumber", "CollectedAt", "CollectedBy", "CreatedAt", "EntryMode", "FK_InvoiceId", "IsActive", "Method", "RecordedBy" },
values: new object[,]
{
{ new Guid("47000000-0000-0000-0000-000000000002"), 1000m, null, null, new DateTime(2026, 8, 31, 9, 20, 0, 0, DateTimeKind.Unspecified), new Guid("29000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 31, 9, 20, 0, 0, DateTimeKind.Unspecified), 0, new Guid("34500000-0000-0000-0000-000000000002"), true, 0, null },
{ new Guid("47000000-0000-0000-0000-000000000003"), 800m, new DateTime(2026, 8, 31, 0, 0, 0, 0, DateTimeKind.Unspecified), "000456", new DateTime(2026, 8, 31, 11, 0, 0, 0, DateTimeKind.Unspecified), new Guid("29000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 31, 18, 0, 0, 0, DateTimeKind.Unspecified), 1, new Guid("34500000-0000-0000-0000-000000000002"), true, 1, new Guid("29000000-0000-0000-0000-000000000001") },
{ new Guid("47000000-0000-0000-0000-000000000004"), 300m, null, null, new DateTime(2026, 8, 31, 11, 5, 0, 0, DateTimeKind.Unspecified), new Guid("29000000-0000-0000-0000-000000000003"), new DateTime(2026, 8, 31, 11, 5, 0, 0, DateTimeKind.Unspecified), 0, new Guid("34500000-0000-0000-0000-000000000002"), true, 2, null }
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "Payments",
keyColumn: "Id",
keyValue: new Guid("47000000-0000-0000-0000-000000000002"));
migrationBuilder.DeleteData(
table: "Payments",
keyColumn: "Id",
keyValue: new Guid("47000000-0000-0000-0000-000000000003"));
migrationBuilder.DeleteData(
table: "Payments",
keyColumn: "Id",
keyValue: new Guid("47000000-0000-0000-0000-000000000004"));
migrationBuilder.DeleteData(
table: "ShopPOItems",
keyColumn: "Id",
keyValue: new Guid("34000000-0000-0000-0000-000000000004"));
migrationBuilder.DeleteData(
table: "Invoices",
keyColumn: "Id",
keyValue: new Guid("34500000-0000-0000-0000-000000000002"));
migrationBuilder.DeleteData(
table: "ShopPOs",
keyColumn: "Id",
keyValue: new Guid("33000000-0000-0000-0000-000000000003"));
}
}
}
@@ -572,6 +572,19 @@ namespace Distribution.Migrations
PrintedBy = new Guid("29000000-0000-0000-0000-000000000002"),
Status = 2,
TotalAmount = 3000m
},
new
{
Id = new Guid("34500000-0000-0000-0000-000000000002"),
CreatedAt = new DateTime(2026, 8, 31, 9, 0, 0, 0, DateTimeKind.Unspecified),
CreatedBy = new Guid("29000000-0000-0000-0000-000000000003"),
FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000003"),
IsActive = true,
PaymentStatus = 1,
PrintedAt = new DateTime(2026, 8, 31, 9, 15, 0, 0, DateTimeKind.Unspecified),
PrintedBy = new Guid("29000000-0000-0000-0000-000000000003"),
Status = 1,
TotalAmount = 2400m
});
});
@@ -833,6 +846,12 @@ namespace Distribution.Migrations
b.Property<decimal>("Amount")
.HasColumnType("decimal(65,30)");
b.Property<DateTime?>("ChequeDate")
.HasColumnType("datetime(6)");
b.Property<string>("ChequeNumber")
.HasColumnType("longtext");
b.Property<DateTime>("CollectedAt")
.HasColumnType("datetime(6)");
@@ -842,6 +861,9 @@ namespace Distribution.Migrations
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<int>("EntryMode")
.HasColumnType("int");
b.Property<Guid>("FK_InvoiceId")
.HasColumnType("char(36)");
@@ -851,6 +873,9 @@ namespace Distribution.Migrations
b.Property<int>("Method")
.HasColumnType("int");
b.Property<Guid?>("RecordedBy")
.HasColumnType("char(36)");
b.HasKey("Id");
b.HasIndex("FK_InvoiceId");
@@ -865,9 +890,49 @@ namespace Distribution.Migrations
CollectedAt = new DateTime(2026, 8, 25, 9, 30, 0, 0, DateTimeKind.Unspecified),
CollectedBy = new Guid("29000000-0000-0000-0000-000000000002"),
CreatedAt = new DateTime(2026, 8, 25, 9, 30, 0, 0, DateTimeKind.Unspecified),
EntryMode = 0,
FK_InvoiceId = new Guid("34500000-0000-0000-0000-000000000001"),
IsActive = true,
Method = 0
},
new
{
Id = new Guid("47000000-0000-0000-0000-000000000002"),
Amount = 1000m,
CollectedAt = new DateTime(2026, 8, 31, 9, 20, 0, 0, DateTimeKind.Unspecified),
CollectedBy = new Guid("29000000-0000-0000-0000-000000000003"),
CreatedAt = new DateTime(2026, 8, 31, 9, 20, 0, 0, DateTimeKind.Unspecified),
EntryMode = 0,
FK_InvoiceId = new Guid("34500000-0000-0000-0000-000000000002"),
IsActive = true,
Method = 0
},
new
{
Id = new Guid("47000000-0000-0000-0000-000000000003"),
Amount = 800m,
ChequeDate = new DateTime(2026, 8, 31, 0, 0, 0, 0, DateTimeKind.Unspecified),
ChequeNumber = "000456",
CollectedAt = new DateTime(2026, 8, 31, 11, 0, 0, 0, DateTimeKind.Unspecified),
CollectedBy = new Guid("29000000-0000-0000-0000-000000000003"),
CreatedAt = new DateTime(2026, 8, 31, 18, 0, 0, 0, DateTimeKind.Unspecified),
EntryMode = 1,
FK_InvoiceId = new Guid("34500000-0000-0000-0000-000000000002"),
IsActive = true,
Method = 1,
RecordedBy = new Guid("29000000-0000-0000-0000-000000000001")
},
new
{
Id = new Guid("47000000-0000-0000-0000-000000000004"),
Amount = 300m,
CollectedAt = new DateTime(2026, 8, 31, 11, 5, 0, 0, DateTimeKind.Unspecified),
CollectedBy = new Guid("29000000-0000-0000-0000-000000000003"),
CreatedAt = new DateTime(2026, 8, 31, 11, 5, 0, 0, DateTimeKind.Unspecified),
EntryMode = 0,
FK_InvoiceId = new Guid("34500000-0000-0000-0000-000000000002"),
IsActive = true,
Method = 2
});
});
@@ -1407,58 +1472,6 @@ namespace Distribution.Migrations
});
});
modelBuilder.Entity("Distribution.Models.RideCancelRequest", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<DateTime?>("ApprovedAt")
.HasColumnType("datetime(6)");
b.Property<Guid?>("ApprovedBy")
.HasColumnType("char(36)");
b.Property<Guid>("FK_RideId")
.HasColumnType("char(36)");
b.Property<string>("Reason")
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTime>("RequestedAt")
.HasColumnType("datetime(6)");
b.Property<Guid>("RequestedBy")
.HasColumnType("char(36)");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<int>("TypeCode")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("FK_RideId");
b.ToTable("RideCancelRequests");
b.HasData(
new
{
Id = new Guid("65000000-0000-0000-0000-000000000001"),
ApprovedAt = new DateTime(2026, 8, 30, 9, 15, 0, 0, DateTimeKind.Unspecified),
ApprovedBy = new Guid("29000000-0000-0000-0000-000000000001"),
FK_RideId = new Guid("31000000-0000-0000-0000-000000000002"),
Reason = "Reported minor engine issue, resolved after inspection",
RequestedAt = new DateTime(2026, 8, 30, 9, 10, 0, 0, DateTimeKind.Unspecified),
RequestedBy = new Guid("29000000-0000-0000-0000-000000000005"),
Status = 2,
TypeCode = 0
});
});
modelBuilder.Entity("Distribution.Models.Role", b =>
{
b.Property<Guid>("Id")
@@ -2104,6 +2117,20 @@ namespace Distribution.Migrations
RaisedById = new Guid("29000000-0000-0000-0000-000000000003"),
Status = 0,
TotalAmount = 1200m
},
new
{
Id = new Guid("33000000-0000-0000-0000-000000000003"),
ConfirmedAt = new DateTime(2026, 8, 31, 8, 30, 0, 0, DateTimeKind.Unspecified),
ConfirmedBy = new Guid("29000000-0000-0000-0000-000000000001"),
CreatedAt = new DateTime(2026, 8, 31, 9, 0, 0, 0, DateTimeKind.Unspecified),
FK_StoreId = new Guid("26000000-0000-0000-0000-000000000005"),
FulfilledAt = new DateTime(2026, 8, 31, 13, 0, 0, 0, DateTimeKind.Unspecified),
InvoiceId = new Guid("34500000-0000-0000-0000-000000000002"),
IsActive = true,
RaisedById = new Guid("29000000-0000-0000-0000-000000000003"),
Status = 2,
TotalAmount = 2400m
});
});
@@ -2175,6 +2202,17 @@ namespace Distribution.Migrations
Quantity = 10,
QuantityFulfilled = 0,
UnitPrice = 120m
},
new
{
Id = new Guid("34000000-0000-0000-0000-000000000004"),
CreatedAt = new DateTime(2026, 8, 31, 9, 0, 0, 0, DateTimeKind.Unspecified),
FK_ItemId = new Guid("23000000-0000-0000-0000-000000000002"),
FK_ShopPOId = new Guid("33000000-0000-0000-0000-000000000003"),
IsActive = true,
Quantity = 20,
QuantityFulfilled = 20,
UnitPrice = 120m
});
});
@@ -3553,17 +3591,6 @@ namespace Distribution.Migrations
b.Navigation("Region");
});
modelBuilder.Entity("Distribution.Models.RideCancelRequest", b =>
{
b.HasOne("Distribution.Models.DeliveryRun", "Ride")
.WithMany()
.HasForeignKey("FK_RideId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Ride");
});
modelBuilder.Entity("Distribution.Models.RolePermission", b =>
{
b.HasOne("Distribution.Models.Permission", "Permission")
+3 -3
View File
@@ -14,8 +14,6 @@ namespace Distribution.Models
public enum CancelRequestType { VehicleBreakdown, DriverUnavailable, RouteIssue, Other }
public enum CancelRequestStatus { Pending, Approved, Rejected }
public enum PurchaseOrderStatus { Issued, PartiallyReceived, Received, Closed, Cancelled }
public enum ShopPOStatus { Pending, PartiallyFulfilled, Fulfilled, Cancelled }
@@ -24,7 +22,9 @@ namespace Distribution.Models
public enum PaymentStatus { Pending, PartiallyPaid, Paid }
public enum PaymentMethod { Cash, Credit }
public enum PaymentMethod { Cash, Cheque, Credit }
public enum EntryMode { RealTime, DayEndManual }
public enum DeliveryTaskStatus { Pending, Delivered, PartiallyDelivered, Failed }
+4
View File
@@ -10,7 +10,11 @@ namespace Distribution.Models
public decimal Amount { get; set; }
public PaymentMethod Method { get; set; }
public EntryMode EntryMode { get; set; } = EntryMode.RealTime;
public string? ChequeNumber { get; set; }
public DateTime? ChequeDate { get; set; }
public DateTime CollectedAt { get; set; }
public Guid CollectedBy { get; set; }
public Guid? RecordedBy { get; set; }
}
}
-23
View File
@@ -1,23 +0,0 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Distribution.Models
{
public class RideCancelRequest
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid FK_RideId { get; set; }
[ForeignKey("FK_RideId")]
public DeliveryRun Ride { get; set; }
public CancelRequestType TypeCode { get; set; }
public string Reason { get; set; }
public Guid RequestedBy { get; set; }
public DateTime RequestedAt { get; set; } = DateTime.Now;
public CancelRequestStatus Status { get; set; } = CancelRequestStatus.Pending;
public Guid? ApprovedBy { get; set; }
public DateTime? ApprovedAt { get; set; }
}
}
@@ -1,11 +1,36 @@
# [Refactor] Ride Lifecycle — Route-Free, Type-Free Rides
**Status**: Proposed (design only — no code changes yet)
**Status**: Mostly implemented — see [Implementation Status](#implementation-status-2026-09-17)
**Refactors**: [../03_ride_lifecycle.md](../03_ride_lifecycle.md) (kept as-is, this is the target design)
**Depends On**: 02 — Stock Ledger
---
## Implementation Status (2026-09-17)
**Done**, verified against `Models/DeliveryRun.cs`, `Features/Ride/*`:
- `RideStatus` collapsed to `Open`/`Cancelled`/`Closed` — no Draft/Active stage anywhere.
- `FK_RouteId`, `RideType`, `ActivatedAt`, `FK_SourceWarehouseId` are all gone from `DeliveryRun`.
- `POST /api/rides` creates directly into `Open` with `StartedAt` set; no `/open`/`/activate` routes.
- `POST /api/rides/{id}/cancel` cancels an `Open` ride directly — no approval step, no
`CancelRequested` stage.
- Driver compliance check (`CheckDriverComplianceAsync`) and the "vehicle can't have two Open rides"
guard are both wired into `RideService.CreateAsync`.
- Dead `RideCancelRequest` model/repository/table removed (2026-09-17) — see Migration Notes.
**Not done / diverges from this doc:**
1. **`GET /api/rides/breakdown-assist/{rideId}`** — not implemented at all (no route in
`RideController.cs`), not even the trimmed "vehicle stock levels only" version this doc describes.
2. **Impact item 1 (delivery task generation) confirmed still broken, honestly**:
`DeliveryTaskService.GenerateAsync` is a one-line `throw new NotImplementedException("... route-
based dispatch not yet implemented.")` — matches this doc's prediction exactly, not yet designed.
3. **Impact item 4 (stock reconciliation at Close/Cancel without a source warehouse)** — still
unaddressed; `CloseAsync`/`CancelAsync` do not touch `StockBalance` at all.
4. **Impact item 2 (loading sheet auto-population)** — resolved differently than either this doc or
`loading_event.md` predicted; see that document's own Implementation Status section.
---
## Goal
Ride should not require a `Route`, should not carry a `RideType`, should not be gated by a warehouse's `Type`, and should not even pin down a source warehouse on the ride record itself. Per direction from the business owner, the ride record carries only what a real dispatch needs to track — **"a ride only carries vehicle, driver assign, and meter reading from start to end."** Everything route-shaped (which stores get visited, which orders/items travel on this trip) and everything warehouse-shaped (where stock loads from/returns to) moves out of the Ride entity's responsibility entirely; neither is replaced by another matching mechanism here.
@@ -215,9 +240,10 @@ Not part of this docs-only pass, but captured for the eventual implementation st
`DeliveryRun`/`Ride` and their Fluent/EF configuration. `Status` stays, re-typed to the narrowed
`RideStatus` (`Open`/`Cancelled`/`Closed`). Add `CancelTypeCode`, `CancelReason`, `CancelledAt`
columns to `DeliveryRun`.
- Drop the `RideCancelRequest` table, its Fluent/EF configuration, and
`RideCancelRequestRepository`/`IRideCancelRequestRepository` — the request/approve/reject flow is
gone.
- ~~Drop the `RideCancelRequest` table, its Fluent/EF configuration, and
`RideCancelRequestRepository`/`IRideCancelRequestRepository`~~ — **Done (2026-09-17)**: model,
repository, `DbSet`, and seed data removed; `CancelRequestStatus` enum removed as unused;
migration `RemoveRideCancelRequest` applied, dropping the table.
- Remove `RouteId`, `RideType`, and `SourceWarehouseId`/`SourceWarehouseName` from
`CreateRideRequest`, `RideResponse`, and `BreakdownAssistRideResponse` in `RideDTOs.cs`. Remove the
open-orders list from `BreakdownAssistResponse`. Remove the `RaiseCancelRequest`/cancel-request