Dev #18
@@ -60,6 +60,7 @@ public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(op
|
||||
public DbSet<RegionalBudget> RegionalBudgets { get; set; }
|
||||
public DbSet<RegionAgencyMap> RegionAgencyMaps { get; set; }
|
||||
public DbSet<RegionalTaxRule> RegionalTaxRules { get; set; }
|
||||
public DbSet<RouteVisitLog> RouteVisitLogs { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
|
||||
@@ -7,4 +7,14 @@ public interface IRouteRepository
|
||||
Task AddAsync(Models.Route entity);
|
||||
void Update(Models.Route entity);
|
||||
void Remove(Models.Route entity);
|
||||
|
||||
Task<Models.User?> GetActiveUserAsync(Guid userId);
|
||||
Task<List<Models.Store>> GetStoresByIdsAsync(List<Guid> storeIds);
|
||||
Task<Models.Store?> GetStoreByIdAsync(Guid storeId);
|
||||
void UpdateStore(Models.Store store);
|
||||
|
||||
Task<Models.RouteVisitLog?> GetOpenVisitAsync(Guid routeId, Guid storeId);
|
||||
Task AddVisitLogAsync(Models.RouteVisitLog log);
|
||||
void UpdateVisitLog(Models.RouteVisitLog log);
|
||||
Task<List<Models.RouteVisitLog>> GetVisitsByStoreIdAsync(Guid storeId);
|
||||
}
|
||||
|
||||
@@ -44,4 +44,36 @@ public class RouteController(RouteService service) : ControllerBase
|
||||
var deleted = await service.DeleteAsync(id, ct);
|
||||
return deleted ? NoContent() : NotFound();
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/stores")]
|
||||
public async Task<IActionResult> AssignStores(Guid id, AssignStoresToRouteRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.AssignStoresToRouteAsync(id, request, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}/owner")]
|
||||
public async Task<IActionResult> AssignOwner(Guid id, AssignRouteOwnerRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.AssignOwnerAsync(id, request, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/optimize")]
|
||||
public IActionResult OptimizeSequence(Guid id) =>
|
||||
StatusCode(StatusCodes.Status501NotImplemented);
|
||||
|
||||
[HttpPost("{id:guid}/visits/check-in")]
|
||||
public async Task<IActionResult> CheckIn(Guid id, RecordCheckInRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.RecordCheckInAsync(id, request, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/visits/check-out")]
|
||||
public async Task<IActionResult> CheckOut(Guid id, RecordCheckOutRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.RecordCheckOutAsync(id, request, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,16 @@ public record CreateRouteRequest(Guid RegionId, string RouteName, string StartPo
|
||||
|
||||
public record UpdateRouteRequest(Guid RegionId, string RouteName, string StartPoint, string EndPoint, decimal Length);
|
||||
|
||||
public record RouteResponse(Guid Id, Guid RegionId, string RouteName, string StartPoint, string EndPoint, decimal Length, bool IsActive, DateTime CreatedAt);
|
||||
public record RouteResponse(Guid Id, Guid RegionId, string RouteName, string StartPoint, string EndPoint, decimal Length, Guid? OwnerUserId, bool IsActive, DateTime CreatedAt);
|
||||
|
||||
public record AssignRouteToRegionRequest(Guid RegionId);
|
||||
|
||||
public record AssignStoresToRouteRequest(List<Guid> StoreIds);
|
||||
|
||||
public record AssignRouteOwnerRequest(Guid SalesRepId);
|
||||
|
||||
public record RecordCheckInRequest(Guid StoreId, decimal Latitude, decimal Longitude, DateTime? Timestamp);
|
||||
|
||||
public record RecordCheckOutRequest(Guid StoreId, DateTime? Timestamp);
|
||||
|
||||
public record RouteVisitResponse(Guid Id, Guid RouteId, Guid StoreId, Guid UserId, DateTime CheckInAt, bool IsDeviationFlagged, DateTime? CheckOutAt);
|
||||
|
||||
@@ -22,4 +22,31 @@ public class RouteRepository(AppDBContext context) : IRouteRepository
|
||||
entity.IsActive = false;
|
||||
context.Routes.Update(entity);
|
||||
}
|
||||
|
||||
public async Task<Models.User?> GetActiveUserAsync(Guid userId) =>
|
||||
await context.Users.FirstOrDefaultAsync(u => u.Id == userId && u.IsActive);
|
||||
|
||||
public async Task<List<Models.Store>> GetStoresByIdsAsync(List<Guid> storeIds) =>
|
||||
await context.Stores.Where(s => storeIds.Contains(s.Id)).ToListAsync();
|
||||
|
||||
public async Task<Models.Store?> GetStoreByIdAsync(Guid storeId) =>
|
||||
await context.Stores.FindAsync(storeId);
|
||||
|
||||
public void UpdateStore(Models.Store store) =>
|
||||
context.Stores.Update(store);
|
||||
|
||||
public async Task<Models.RouteVisitLog?> GetOpenVisitAsync(Guid routeId, Guid storeId) =>
|
||||
await context.RouteVisitLogs
|
||||
.Where(v => v.FK_RouteId == routeId && v.FK_StoreId == storeId && v.CheckOutAt == null)
|
||||
.OrderByDescending(v => v.CheckInAt)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
public async Task AddVisitLogAsync(Models.RouteVisitLog log) =>
|
||||
await context.RouteVisitLogs.AddAsync(log);
|
||||
|
||||
public void UpdateVisitLog(Models.RouteVisitLog log) =>
|
||||
context.RouteVisitLogs.Update(log);
|
||||
|
||||
public async Task<List<Models.RouteVisitLog>> GetVisitsByStoreIdAsync(Guid storeId) =>
|
||||
await context.RouteVisitLogs.Where(v => v.FK_StoreId == storeId).ToListAsync();
|
||||
}
|
||||
|
||||
@@ -1,20 +1,50 @@
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Infra.UoW;
|
||||
|
||||
namespace Distribution.Features.Route;
|
||||
|
||||
public class RouteService(IRouteRepository repo, Region.IRegionRepository regionRepo, IUnitOfWork uow)
|
||||
public class RouteService(IRouteRepository repo, Region.IRegionRepository regionRepo, IUnitOfWork uow, ICurrentUserService currentUser, IScopeAccessService scopeAccess)
|
||||
{
|
||||
private const int RouteManagerRoleLevel = 20;
|
||||
private const double DeviationThresholdMeters = 500;
|
||||
|
||||
private async Task<bool> CanManageRouteAsync(Guid routeId)
|
||||
{
|
||||
if (currentUser.RoleLevel < RouteManagerRoleLevel || currentUser.UserId is null) return false;
|
||||
return await scopeAccess.HasRouteScopeAsync(currentUser.UserId.Value, routeId);
|
||||
}
|
||||
|
||||
private static double DistanceMeters(decimal lat1, decimal lon1, decimal lat2, decimal lon2)
|
||||
{
|
||||
const double earthRadiusMeters = 6371000;
|
||||
var dLat = ToRadians((double)(lat2 - lat1));
|
||||
var dLon = ToRadians((double)(lon2 - lon1));
|
||||
var a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) +
|
||||
Math.Cos(ToRadians((double)lat1)) * Math.Cos(ToRadians((double)lat2)) *
|
||||
Math.Sin(dLon / 2) * Math.Sin(dLon / 2);
|
||||
var c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));
|
||||
return earthRadiusMeters * c;
|
||||
}
|
||||
|
||||
private static double ToRadians(double degrees) => degrees * Math.PI / 180;
|
||||
|
||||
private static RouteResponse ToResponse(Models.Route route) =>
|
||||
new(route.Id, route.FK_RegionId, route.RouteName, route.StartPoint, route.EndPoint, route.Length, route.FK_OwnerUserId, route.IsActive, route.CreatedAt);
|
||||
|
||||
private static RouteVisitResponse ToVisitResponse(Models.RouteVisitLog log) =>
|
||||
new(log.Id, log.FK_RouteId, log.FK_StoreId, log.FK_UserId, log.CheckInAt, log.IsDeviationFlagged, log.CheckOutAt);
|
||||
|
||||
public async Task<List<RouteResponse>> GetAllAsync() =>
|
||||
(await repo.GetAllAsync())
|
||||
.Where(r => r.IsActive)
|
||||
.Select(r => new RouteResponse(r.Id, r.FK_RegionId, r.RouteName, r.StartPoint, r.EndPoint, r.Length, r.IsActive, r.CreatedAt))
|
||||
.Select(r => new RouteResponse(r.Id, r.FK_RegionId, r.RouteName, r.StartPoint, r.EndPoint, r.Length, r.FK_OwnerUserId, r.IsActive, r.CreatedAt))
|
||||
.ToList();
|
||||
|
||||
public async Task<RouteResponse?> GetByIdAsync(Guid id)
|
||||
{
|
||||
var route = await repo.GetByIdAsync(id);
|
||||
if (route is null || !route.IsActive) return null;
|
||||
return new RouteResponse(route.Id, route.FK_RegionId, route.RouteName, route.StartPoint, route.EndPoint, route.Length, route.IsActive, route.CreatedAt);
|
||||
return new RouteResponse(route.Id, route.FK_RegionId, route.RouteName, route.StartPoint, route.EndPoint, route.Length, route.FK_OwnerUserId, route.IsActive, route.CreatedAt);
|
||||
}
|
||||
|
||||
public async Task<RouteResponse> CreateAsync(CreateRouteRequest request, CancellationToken ct = default)
|
||||
@@ -38,7 +68,7 @@ public class RouteService(IRouteRepository repo, Region.IRegionRepository region
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return new RouteResponse(route.Id, route.FK_RegionId, route.RouteName, route.StartPoint, route.EndPoint, route.Length, route.IsActive, route.CreatedAt);
|
||||
return ToResponse(route);
|
||||
}
|
||||
|
||||
public async Task<RouteResponse?> UpdateAsync(Guid id, UpdateRouteRequest request, CancellationToken ct = default)
|
||||
@@ -61,7 +91,7 @@ public class RouteService(IRouteRepository repo, Region.IRegionRepository region
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return new RouteResponse(route.Id, route.FK_RegionId, route.RouteName, route.StartPoint, route.EndPoint, route.Length, route.IsActive, route.CreatedAt);
|
||||
return ToResponse(route);
|
||||
}
|
||||
|
||||
public async Task<RouteResponse?> AssignToRegionAsync(Guid id, AssignRouteToRegionRequest request, CancellationToken ct = default)
|
||||
@@ -84,7 +114,7 @@ public class RouteService(IRouteRepository repo, Region.IRegionRepository region
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return new RouteResponse(route.Id, route.FK_RegionId, route.RouteName, route.StartPoint, route.EndPoint, route.Length, route.IsActive, route.CreatedAt);
|
||||
return ToResponse(route);
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAsync(Guid id, CancellationToken ct = default)
|
||||
@@ -104,4 +134,119 @@ public class RouteService(IRouteRepository repo, Region.IRegionRepository region
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<RouteResponse?> AssignStoresToRouteAsync(Guid id, AssignStoresToRouteRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var route = await repo.GetByIdAsync(id);
|
||||
if (route is null || !route.IsActive) return null;
|
||||
if (!await CanManageRouteAsync(id)) return null;
|
||||
|
||||
var stores = await repo.GetStoresByIdsAsync(request.StoreIds);
|
||||
if (stores.Count != request.StoreIds.Distinct().Count()) return null;
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
foreach (var store in stores)
|
||||
{
|
||||
store.FK_RouteId = id;
|
||||
repo.UpdateStore(store);
|
||||
}
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return ToResponse(route);
|
||||
}
|
||||
|
||||
public async Task<RouteResponse?> AssignOwnerAsync(Guid id, AssignRouteOwnerRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var route = await repo.GetByIdAsync(id);
|
||||
if (route is null || !route.IsActive) return null;
|
||||
if (!await CanManageRouteAsync(id)) return null;
|
||||
|
||||
var salesRep = await repo.GetActiveUserAsync(request.SalesRepId);
|
||||
if (salesRep is null) return null;
|
||||
|
||||
route.FK_OwnerUserId = request.SalesRepId;
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
repo.Update(route);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return ToResponse(route);
|
||||
}
|
||||
|
||||
public async Task<RouteVisitResponse?> RecordCheckInAsync(Guid id, RecordCheckInRequest request, CancellationToken ct = default)
|
||||
{
|
||||
if (currentUser.UserId is null) return null;
|
||||
|
||||
var route = await repo.GetByIdAsync(id);
|
||||
if (route is null || !route.IsActive) return null;
|
||||
if (!await scopeAccess.HasRouteScopeAsync(currentUser.UserId.Value, id)) return null;
|
||||
|
||||
var store = await repo.GetStoreByIdAsync(request.StoreId);
|
||||
if (store is null || !store.IsActive || store.FK_RouteId != id) return null;
|
||||
|
||||
var distance = DistanceMeters(request.Latitude, request.Longitude, store.Latitude, store.Longitude);
|
||||
|
||||
var log = new Models.RouteVisitLog
|
||||
{
|
||||
FK_RouteId = id,
|
||||
FK_StoreId = request.StoreId,
|
||||
FK_UserId = currentUser.UserId.Value,
|
||||
CheckInAt = request.Timestamp ?? DateTime.Now,
|
||||
CheckInLatitude = request.Latitude,
|
||||
CheckInLongitude = request.Longitude,
|
||||
IsDeviationFlagged = distance > DeviationThresholdMeters
|
||||
};
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
await repo.AddVisitLogAsync(log);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return ToVisitResponse(log);
|
||||
}
|
||||
|
||||
public async Task<RouteVisitResponse?> RecordCheckOutAsync(Guid id, RecordCheckOutRequest request, CancellationToken ct = default)
|
||||
{
|
||||
if (currentUser.UserId is null) return null;
|
||||
|
||||
var route = await repo.GetByIdAsync(id);
|
||||
if (route is null || !route.IsActive) return null;
|
||||
if (!await scopeAccess.HasRouteScopeAsync(currentUser.UserId.Value, id)) return null;
|
||||
|
||||
var log = await repo.GetOpenVisitAsync(id, request.StoreId);
|
||||
if (log is null) return null;
|
||||
|
||||
log.CheckOutAt = request.Timestamp ?? DateTime.Now;
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
repo.UpdateVisitLog(log);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return ToVisitResponse(log);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.Store;
|
||||
|
||||
public class StoreService(IStoreRepository repo, IUnitOfWork uow, ICurrentUserService currentUser, IScopeAccessService scopeAccess, SchemeService schemeService)
|
||||
public class StoreService(IStoreRepository repo, IUnitOfWork uow, ICurrentUserService currentUser, IScopeAccessService scopeAccess, SchemeService schemeService, Route.IRouteRepository routeRepo)
|
||||
{
|
||||
private const int GlobalScopeRoleLevel = 50;
|
||||
private const int CreditApproverRoleLevel = 20;
|
||||
@@ -209,8 +209,12 @@ 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;
|
||||
// Wired to Step 11 (Route & Sales Management) once ride/visit tracking exists.
|
||||
return [];
|
||||
|
||||
var visits = await routeRepo.GetVisitsByStoreIdAsync(id);
|
||||
return visits
|
||||
.OrderByDescending(v => v.CheckInAt)
|
||||
.Select(v => new StoreVisitHistoryItem(v.Id, v.CheckInAt, v.FK_UserId, v.CheckOutAt is null ? "In Progress" : "Completed"))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public async Task<SchemeEligibilityResponse?> CheckSchemeEligibilityAsync(Guid id, Guid schemeId)
|
||||
|
||||
+2895
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,108 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Distribution.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddRouteVisitManagement : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "FK_OwnerUserId",
|
||||
table: "Routes",
|
||||
type: "char(36)",
|
||||
nullable: true,
|
||||
collation: "ascii_general_ci");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "RouteVisitLogs",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
FK_RouteId = 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"),
|
||||
FK_UserId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
CheckInAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
CheckInLatitude = table.Column<decimal>(type: "decimal(65,30)", nullable: false),
|
||||
CheckInLongitude = table.Column<decimal>(type: "decimal(65,30)", nullable: false),
|
||||
IsDeviationFlagged = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||
CheckOutAt = 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_RouteVisitLogs", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_RouteVisitLogs_Routes_FK_RouteId",
|
||||
column: x => x.FK_RouteId,
|
||||
principalTable: "Routes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_RouteVisitLogs_Stores_FK_StoreId",
|
||||
column: x => x.FK_StoreId,
|
||||
principalTable: "Stores",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_RouteVisitLogs_Users_FK_UserId",
|
||||
column: x => x.FK_UserId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Routes_FK_OwnerUserId",
|
||||
table: "Routes",
|
||||
column: "FK_OwnerUserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RouteVisitLogs_FK_RouteId",
|
||||
table: "RouteVisitLogs",
|
||||
column: "FK_RouteId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RouteVisitLogs_FK_StoreId",
|
||||
table: "RouteVisitLogs",
|
||||
column: "FK_StoreId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RouteVisitLogs_FK_UserId",
|
||||
table: "RouteVisitLogs",
|
||||
column: "FK_UserId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Routes_Users_FK_OwnerUserId",
|
||||
table: "Routes",
|
||||
column: "FK_OwnerUserId",
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Routes_Users_FK_OwnerUserId",
|
||||
table: "Routes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "RouteVisitLogs");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Routes_FK_OwnerUserId",
|
||||
table: "Routes");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FK_OwnerUserId",
|
||||
table: "Routes");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1115,6 +1115,9 @@ namespace Distribution.Migrations
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<Guid?>("FK_OwnerUserId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("FK_RegionId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
@@ -1134,11 +1137,60 @@ namespace Distribution.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("FK_OwnerUserId");
|
||||
|
||||
b.HasIndex("FK_RegionId");
|
||||
|
||||
b.ToTable("Routes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.RouteVisitLog", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("CheckInAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<decimal>("CheckInLatitude")
|
||||
.HasColumnType("decimal(65,30)");
|
||||
|
||||
b.Property<decimal>("CheckInLongitude")
|
||||
.HasColumnType("decimal(65,30)");
|
||||
|
||||
b.Property<DateTime?>("CheckOutAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("FK_RouteId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("FK_StoreId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid>("FK_UserId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<bool>("IsDeviationFlagged")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("FK_RouteId");
|
||||
|
||||
b.HasIndex("FK_StoreId");
|
||||
|
||||
b.HasIndex("FK_UserId");
|
||||
|
||||
b.ToTable("RouteVisitLogs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.Sale", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -2384,15 +2436,48 @@ namespace Distribution.Migrations
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.Route", b =>
|
||||
{
|
||||
b.HasOne("Distribution.Models.User", "OwnerUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("FK_OwnerUserId");
|
||||
|
||||
b.HasOne("Distribution.Models.Region", "Region")
|
||||
.WithMany()
|
||||
.HasForeignKey("FK_RegionId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("OwnerUser");
|
||||
|
||||
b.Navigation("Region");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.RouteVisitLog", b =>
|
||||
{
|
||||
b.HasOne("Distribution.Models.Route", "Route")
|
||||
.WithMany()
|
||||
.HasForeignKey("FK_RouteId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Distribution.Models.Store", "Store")
|
||||
.WithMany()
|
||||
.HasForeignKey("FK_StoreId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Distribution.Models.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("FK_UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Route");
|
||||
|
||||
b.Navigation("Store");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Distribution.Models.Sale", b =>
|
||||
{
|
||||
b.HasOne("Distribution.Models.User", "CreatedByUser")
|
||||
|
||||
@@ -12,5 +12,9 @@ namespace Distribution.Models
|
||||
public Guid FK_RegionId { get; set; }
|
||||
[ForeignKey("FK_RegionId")]
|
||||
public Region Region { get; set; }
|
||||
|
||||
public Guid? FK_OwnerUserId { get; set; }
|
||||
[ForeignKey("FK_OwnerUserId")]
|
||||
public User? OwnerUser { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public class RouteVisitLog : Template
|
||||
{
|
||||
public Guid FK_RouteId { get; set; }
|
||||
[ForeignKey("FK_RouteId")]
|
||||
public Route Route { get; set; }
|
||||
|
||||
public Guid FK_StoreId { get; set; }
|
||||
[ForeignKey("FK_StoreId")]
|
||||
public Store Store { get; set; }
|
||||
|
||||
public Guid FK_UserId { get; set; }
|
||||
[ForeignKey("FK_UserId")]
|
||||
public User User { get; set; }
|
||||
|
||||
public DateTime CheckInAt { get; set; }
|
||||
public decimal CheckInLatitude { get; set; }
|
||||
public decimal CheckInLongitude { get; set; }
|
||||
public bool IsDeviationFlagged { get; set; } = false;
|
||||
|
||||
public DateTime? CheckOutAt { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user