Initial project setup with domain, UoW, and API structure
Added .gitignore, solution, and project files for .NET 10. Introduced AGENTS.md and PLAN.md for guidelines and planning. Implemented appsettings.json, launchSettings.json, and MySQL config. Created AppDBContext and all domain models with composite keys. Added IUnitOfWork and EFUnitOfWork. Structured feature folders for Region, Route, Vehicle, Store, Warehouse, and Order with DTOs, repositories, services, and controllers. Added initial EF Core migrations and sample HTTP request. Registered dependencies and enabled OpenAPI in development.
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
## Build outputs
|
||||
bin/
|
||||
obj/
|
||||
|
||||
## Visual Studio
|
||||
.vs/
|
||||
*.user
|
||||
*.suo
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
## NuGet
|
||||
*.nupkg
|
||||
*.snupkg
|
||||
**/[Pp]ackages/
|
||||
!**/[Pp]ackages/build/
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
## ASP.NET Core
|
||||
**/wwwroot/lib/
|
||||
|
||||
## Secrets & environment-specific config
|
||||
appsettings.Development.json
|
||||
appsettings.Local.json
|
||||
appsettings.*.local.json
|
||||
secrets.json
|
||||
*.pfx
|
||||
*.p12
|
||||
|
||||
## Entity Framework
|
||||
# Keep Migrations — they are source code
|
||||
# Add this only if you intentionally exclude migrations:
|
||||
# **/Migrations/
|
||||
|
||||
## Logs
|
||||
logs/
|
||||
*.log
|
||||
*.txt.log
|
||||
|
||||
## OS
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
.DS_Store
|
||||
*.lnk
|
||||
|
||||
## JetBrains Rider / ReSharper
|
||||
.idea/
|
||||
*.DotSettings.user
|
||||
_ReSharper*/
|
||||
|
||||
## Publish output
|
||||
publish/
|
||||
**/publish/
|
||||
|
||||
## Test results
|
||||
TestResults/
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
## Temporary files
|
||||
*.tmp
|
||||
*.temp
|
||||
*.bak
|
||||
*.orig
|
||||
*.rej
|
||||
@@ -0,0 +1,67 @@
|
||||
# Repository Guidelines
|
||||
|
||||
## Hard Rules — Always Enforced
|
||||
|
||||
1. **Never call `uow.SaveChangesAsync()` in any repository or service.** Only `uow.CommitAsync()` (or `BeginAsync`/`CommitAsync`/`RollbackAsync` for transactions) may be called, and only from the service layer.
|
||||
2. **Never use `DateTime.UtcNow`.** Use `DateTime.Now` (server time) everywhere.
|
||||
3. **Never build a feature that was not explicitly requested.** Confirm with the user before creating any new entity, endpoint, service, or infrastructure component.
|
||||
|
||||
---
|
||||
|
||||
## Project Structure & Module Organization
|
||||
The repository contains one solution file, `Distribution.slnx`, and one ASP.NET Core 10 Web API project in `Distribution/`. The intended folder layout is:
|
||||
|
||||
```
|
||||
Distribution/
|
||||
├── Controllers/ # Thin HTTP endpoints — no business logic, no UoW calls
|
||||
├── Data/
|
||||
│ └── AppDBContext.cs # EF Core DbContext — add DbSets here
|
||||
├── Domain/
|
||||
│ └── Entities/ # Plain POCO entities; one file per entity
|
||||
├── Infra/
|
||||
│ ├── UoW/ # IUnitOfWork + EFUnitOfWork (already implemented)
|
||||
│ └── Repositories/ # IRepository<T>, EFRepository<T>, and per-aggregate repos
|
||||
├── Application/
|
||||
│ └── Services/ # Business logic; owns the uow.CommitAsync() call
|
||||
└── DTOs/ # Request/Response DTOs per module
|
||||
```
|
||||
|
||||
Core entrypoint: `Distribution/Program.cs`. Configuration: `appsettings.json` / `appsettings.Development.json`. Launch profiles: `Distribution/Properties/launchSettings.json`. Do not commit build output from `Distribution/bin/` or `Distribution/obj/`.
|
||||
|
||||
## UoW Call-Chain Rule
|
||||
|
||||
```
|
||||
Controller → Service → Repository (EF change tracking only)
|
||||
↓
|
||||
uow.CommitAsync() ← only the service calls this
|
||||
```
|
||||
|
||||
- Repositories call `AddAsync`, `Update`, `Remove` on the DbSet — nothing more.
|
||||
- Services call `_uow.CommitAsync(ct)` to flush and commit.
|
||||
- For multi-step operations, services call `BeginAsync` → work → `CommitAsync` (or `RollbackAsync` in catch).
|
||||
- `uow.SaveChangesAsync()` exists on the interface but must never appear in repo or service code.
|
||||
|
||||
---
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
Run commands from the repository root unless noted otherwise.
|
||||
|
||||
- `dotnet restore Distribution.slnx`: restore NuGet packages.
|
||||
- `dotnet build Distribution.slnx`: compile the solution.
|
||||
- `dotnet run --project Distribution/Distribution.csproj`: start the API locally.
|
||||
- `dotnet watch run --project Distribution/Distribution.csproj`: run with hot reload during development.
|
||||
- `dotnet test`: run automated tests once a test project is added.
|
||||
|
||||
The default development URLs are defined in `launchSettings.json`, currently `http://localhost:5089` and `https://localhost:7025`.
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
Use standard C# conventions: 4-space indentation, PascalCase for public types and members, camelCase for local variables and parameters, and one class per file. Name controllers with the `*Controller.cs` pattern and keep route names explicit. Nullable reference types are enabled, so handle nullability deliberately rather than suppressing warnings.
|
||||
|
||||
## Testing Guidelines
|
||||
There is no test project in the repository yet. Add new tests in a sibling project such as `Distribution.Tests/` and include it in the solution. Prefer xUnit for .NET API coverage. Name test files after the subject under test, for example `OrdersControllerTests.cs`, and use method names like `Get_ReturnsNotFound_WhenIdIsMissing`.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
The current branch has no commit history yet, so use a clean convention from the start: short imperative subjects such as `Add orders controller` or `Configure OpenAPI in development`. Keep commits focused on one change. Pull requests should include a brief summary, impacted endpoints or config files, test evidence (`dotnet build`, `dotnet test`), and sample request or response details when API behavior changes.
|
||||
|
||||
## Configuration Notes
|
||||
Keep secrets out of `appsettings.json`; use environment variables or user secrets for local development. Restrict `appsettings.Development.json` to non-sensitive machine-local defaults.
|
||||
@@ -0,0 +1,24 @@
|
||||
using Distribution.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Data;
|
||||
|
||||
public class AppDBContext(DbContextOptions<AppDBContext> options) : DbContext(options)
|
||||
{
|
||||
public DbSet<Region> Regions { get; set; }
|
||||
public DbSet<Models.Route> Routes { get; set; }
|
||||
public DbSet<Vehicle> Vehicles { get; set; }
|
||||
public DbSet<Store> Stores { get; set; }
|
||||
public DbSet<Warehouse> Warehouses { get; set; }
|
||||
public DbSet<SaleRoute> SaleRoutes { get; set; }
|
||||
public DbSet<Order> Orders { get; set; }
|
||||
public DbSet<OrderItem> OrderItems { get; set; }
|
||||
public DbSet<DeliveryRun> DeliveryRuns { get; set; }
|
||||
public DbSet<Item> Items { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<OrderItem>()
|
||||
.HasKey(oi => new { oi.FK_OrderId, oi.FK_ItemId });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.10" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.7">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.OpenApi" Version="2.11.0" />
|
||||
<PackageReference Include="MySql.EntityFrameworkCore" Version="10.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,6 @@
|
||||
@Distribution_HostAddress = http://localhost:5089
|
||||
|
||||
GET {{Distribution_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
@@ -0,0 +1,3 @@
|
||||
<Solution>
|
||||
<Project Path="Distribution.csproj" />
|
||||
</Solution>
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Distribution.Features.Order;
|
||||
|
||||
public interface IOrderRepository
|
||||
{
|
||||
Task<Models.Order?> GetByIdAsync(Guid id);
|
||||
Task<List<Models.Order>> GetAllAsync();
|
||||
Task AddAsync(Models.Order entity);
|
||||
void Update(Models.Order entity);
|
||||
void Remove(Models.Order entity);
|
||||
Task<List<Models.OrderItem>> GetItemsByOrderIdAsync(Guid orderId);
|
||||
Task<List<Models.Order>> GetByStoreIdAsync(Guid storeId);
|
||||
Task AddItemsAsync(IEnumerable<Models.OrderItem> items);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Order;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/orders")]
|
||||
public class OrderController(OrderService service) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAll() =>
|
||||
Ok(await service.GetAllAsync());
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var result = await service.GetByIdAsync(id);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("store/{storeId:guid}")]
|
||||
public async Task<IActionResult> GetByStore(Guid storeId) =>
|
||||
Ok(await service.GetByStoreIdAsync(storeId));
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create(CreateOrderRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.CreateAsync(request, ct);
|
||||
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
public async Task<IActionResult> Update(Guid id, UpdateOrderRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.UpdateAsync(id, request, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
||||
{
|
||||
var deleted = await service.DeleteAsync(id, ct);
|
||||
return deleted ? NoContent() : NotFound();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace Distribution.Features.Order;
|
||||
|
||||
public record OrderItemRequest(Guid ItemId, int Quantity);
|
||||
|
||||
public record CreateOrderRequest(Guid StoreId, DateTime OrderDate, decimal TotalAmount, List<OrderItemRequest> Items);
|
||||
|
||||
public record UpdateOrderRequest(DateTime OrderDate, decimal TotalAmount);
|
||||
|
||||
public record OrderItemResponse(Guid ItemId, int Quantity);
|
||||
|
||||
public record OrderResponse(
|
||||
Guid Id,
|
||||
DateTime OrderDate,
|
||||
Guid StoreId,
|
||||
decimal TotalAmount,
|
||||
List<OrderItemResponse> Items,
|
||||
bool IsActive,
|
||||
DateTime CreatedAt);
|
||||
@@ -0,0 +1,34 @@
|
||||
using Distribution.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.Order;
|
||||
|
||||
public class OrderRepository(AppDBContext context) : IOrderRepository
|
||||
{
|
||||
public async Task<Models.Order?> GetByIdAsync(Guid id) =>
|
||||
await context.Orders.FindAsync(id);
|
||||
|
||||
public async Task<List<Models.Order>> GetAllAsync() =>
|
||||
await context.Orders.ToListAsync();
|
||||
|
||||
public async Task AddAsync(Models.Order entity) =>
|
||||
await context.Orders.AddAsync(entity);
|
||||
|
||||
public void Update(Models.Order entity) =>
|
||||
context.Orders.Update(entity);
|
||||
|
||||
public void Remove(Models.Order entity)
|
||||
{
|
||||
entity.IsActive = false;
|
||||
context.Orders.Update(entity);
|
||||
}
|
||||
|
||||
public async Task<List<Models.OrderItem>> GetItemsByOrderIdAsync(Guid orderId) =>
|
||||
await context.OrderItems.Where(oi => oi.FK_OrderId == orderId).ToListAsync();
|
||||
|
||||
public async Task<List<Models.Order>> GetByStoreIdAsync(Guid storeId) =>
|
||||
await context.Orders.Where(o => o.FK_StoreId == storeId && o.IsActive).ToListAsync();
|
||||
|
||||
public async Task AddItemsAsync(IEnumerable<Models.OrderItem> items) =>
|
||||
await context.OrderItems.AddRangeAsync(items);
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
using Distribution.Infra.UoW;
|
||||
|
||||
namespace Distribution.Features.Order;
|
||||
|
||||
public class OrderService(IOrderRepository repo, IUnitOfWork uow)
|
||||
{
|
||||
private static OrderResponse ToResponse(Models.Order o, List<Models.OrderItem> items) =>
|
||||
new(o.Id, o.OrderDate, o.FK_StoreId, o.TotalAmount,
|
||||
items.Select(i => new OrderItemResponse(i.FK_ItemId, i.Quantity)).ToList(),
|
||||
o.IsActive, o.CreatedAt);
|
||||
|
||||
public async Task<List<OrderResponse>> GetAllAsync()
|
||||
{
|
||||
var orders = (await repo.GetAllAsync()).Where(o => o.IsActive).ToList();
|
||||
var result = new List<OrderResponse>();
|
||||
foreach (var order in orders)
|
||||
{
|
||||
var items = await repo.GetItemsByOrderIdAsync(order.Id);
|
||||
result.Add(ToResponse(order, items));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<OrderResponse?> GetByIdAsync(Guid id)
|
||||
{
|
||||
var order = await repo.GetByIdAsync(id);
|
||||
if (order is null || !order.IsActive) return null;
|
||||
var items = await repo.GetItemsByOrderIdAsync(id);
|
||||
return ToResponse(order, items);
|
||||
}
|
||||
|
||||
public async Task<List<OrderResponse>> GetByStoreIdAsync(Guid storeId)
|
||||
{
|
||||
var orders = await repo.GetByStoreIdAsync(storeId);
|
||||
var result = new List<OrderResponse>();
|
||||
foreach (var order in orders)
|
||||
{
|
||||
var items = await repo.GetItemsByOrderIdAsync(order.Id);
|
||||
result.Add(ToResponse(order, items));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<OrderResponse> CreateAsync(CreateOrderRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var order = new Models.Order
|
||||
{
|
||||
FK_StoreId = request.StoreId,
|
||||
OrderDate = request.OrderDate,
|
||||
TotalAmount = request.TotalAmount
|
||||
};
|
||||
var orderItems = request.Items.Select(i => new Models.OrderItem
|
||||
{
|
||||
FK_OrderId = order.Id,
|
||||
FK_ItemId = i.ItemId,
|
||||
Quantity = i.Quantity
|
||||
}).ToList();
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
await repo.AddAsync(order);
|
||||
await repo.AddItemsAsync(orderItems);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return ToResponse(order, orderItems);
|
||||
}
|
||||
|
||||
public async Task<OrderResponse?> UpdateAsync(Guid id, UpdateOrderRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var order = await repo.GetByIdAsync(id);
|
||||
if (order is null || !order.IsActive) return null;
|
||||
order.OrderDate = request.OrderDate;
|
||||
order.TotalAmount = request.TotalAmount;
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
repo.Update(order);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
var items = await repo.GetItemsByOrderIdAsync(id);
|
||||
return ToResponse(order, items);
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAsync(Guid id, CancellationToken ct = default)
|
||||
{
|
||||
var order = await repo.GetByIdAsync(id);
|
||||
if (order is null || !order.IsActive) return false;
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
repo.Remove(order);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Distribution.Features.Region;
|
||||
|
||||
public interface IRegionRepository
|
||||
{
|
||||
Task<Models.Region?> GetByIdAsync(Guid id);
|
||||
Task<List<Models.Region>> GetAllAsync();
|
||||
Task AddAsync(Models.Region entity);
|
||||
void Update(Models.Region entity);
|
||||
void Remove(Models.Region entity);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Region;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/regions")]
|
||||
public class RegionController(RegionService service) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAll() =>
|
||||
Ok(await service.GetAllAsync());
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var result = await service.GetByIdAsync(id);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create(CreateRegionRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.CreateAsync(request, ct);
|
||||
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
public async Task<IActionResult> Update(Guid id, UpdateRegionRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.UpdateAsync(id, request, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
||||
{
|
||||
var deleted = await service.DeleteAsync(id, ct);
|
||||
return deleted ? NoContent() : NotFound();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Distribution.Features.Region;
|
||||
|
||||
public record CreateRegionRequest(string Name);
|
||||
|
||||
public record UpdateRegionRequest(string Name);
|
||||
|
||||
public record RegionResponse(Guid Id, string Name, bool IsActive, DateTime CreatedAt);
|
||||
@@ -0,0 +1,25 @@
|
||||
using Distribution.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.Region;
|
||||
|
||||
public class RegionRepository(AppDBContext context) : IRegionRepository
|
||||
{
|
||||
public async Task<Models.Region?> GetByIdAsync(Guid id) =>
|
||||
await context.Regions.FindAsync(id);
|
||||
|
||||
public async Task<List<Models.Region>> GetAllAsync() =>
|
||||
await context.Regions.ToListAsync();
|
||||
|
||||
public async Task AddAsync(Models.Region entity) =>
|
||||
await context.Regions.AddAsync(entity);
|
||||
|
||||
public void Update(Models.Region entity) =>
|
||||
context.Regions.Update(entity);
|
||||
|
||||
public void Remove(Models.Region entity)
|
||||
{
|
||||
entity.IsActive = false;
|
||||
context.Regions.Update(entity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using Distribution.Infra.UoW;
|
||||
|
||||
namespace Distribution.Features.Region;
|
||||
|
||||
public class RegionService(IRegionRepository repo, IUnitOfWork uow)
|
||||
{
|
||||
public async Task<List<RegionResponse>> GetAllAsync() =>
|
||||
(await repo.GetAllAsync())
|
||||
.Where(r => r.IsActive)
|
||||
.Select(r => new RegionResponse(r.Id, r.Name, r.IsActive, r.CreatedAt))
|
||||
.ToList();
|
||||
|
||||
public async Task<RegionResponse?> GetByIdAsync(Guid id)
|
||||
{
|
||||
var region = await repo.GetByIdAsync(id);
|
||||
if (region is null || !region.IsActive) return null;
|
||||
return new RegionResponse(region.Id, region.Name, region.IsActive, region.CreatedAt);
|
||||
}
|
||||
|
||||
public async Task<RegionResponse> CreateAsync(CreateRegionRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var region = new Models.Region { Name = request.Name };
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
await repo.AddAsync(region);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return new RegionResponse(region.Id, region.Name, region.IsActive, region.CreatedAt);
|
||||
}
|
||||
|
||||
public async Task<RegionResponse?> UpdateAsync(Guid id, UpdateRegionRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var region = await repo.GetByIdAsync(id);
|
||||
if (region is null || !region.IsActive) return null;
|
||||
region.Name = request.Name;
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
repo.Update(region);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return new RegionResponse(region.Id, region.Name, region.IsActive, region.CreatedAt);
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAsync(Guid id, CancellationToken ct = default)
|
||||
{
|
||||
var region = await repo.GetByIdAsync(id);
|
||||
if (region is null || !region.IsActive) return false;
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
repo.Remove(region);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Distribution.Features.Route;
|
||||
|
||||
public interface IRouteRepository
|
||||
{
|
||||
Task<Models.Route?> GetByIdAsync(Guid id);
|
||||
Task<List<Models.Route>> GetAllAsync();
|
||||
Task AddAsync(Models.Route entity);
|
||||
void Update(Models.Route entity);
|
||||
void Remove(Models.Route entity);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Route;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/routes")]
|
||||
public class RouteController(RouteService service) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAll() =>
|
||||
Ok(await service.GetAllAsync());
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var result = await service.GetByIdAsync(id);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create(CreateRouteRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.CreateAsync(request, ct);
|
||||
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
public async Task<IActionResult> Update(Guid id, UpdateRouteRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.UpdateAsync(id, request, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
||||
{
|
||||
var deleted = await service.DeleteAsync(id, ct);
|
||||
return deleted ? NoContent() : NotFound();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Distribution.Features.Route;
|
||||
|
||||
public record CreateRouteRequest(string RouteName, string StartPoint, string EndPoint, decimal Length);
|
||||
|
||||
public record UpdateRouteRequest(string RouteName, string StartPoint, string EndPoint, decimal Length);
|
||||
|
||||
public record RouteResponse(Guid Id, string RouteName, string StartPoint, string EndPoint, decimal Length, bool IsActive, DateTime CreatedAt);
|
||||
@@ -0,0 +1,25 @@
|
||||
using Distribution.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.Route;
|
||||
|
||||
public class RouteRepository(AppDBContext context) : IRouteRepository
|
||||
{
|
||||
public async Task<Models.Route?> GetByIdAsync(Guid id) =>
|
||||
await context.Routes.FindAsync(id);
|
||||
|
||||
public async Task<List<Models.Route>> GetAllAsync() =>
|
||||
await context.Routes.ToListAsync();
|
||||
|
||||
public async Task AddAsync(Models.Route entity) =>
|
||||
await context.Routes.AddAsync(entity);
|
||||
|
||||
public void Update(Models.Route entity) =>
|
||||
context.Routes.Update(entity);
|
||||
|
||||
public void Remove(Models.Route entity)
|
||||
{
|
||||
entity.IsActive = false;
|
||||
context.Routes.Update(entity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using Distribution.Infra.UoW;
|
||||
|
||||
namespace Distribution.Features.Route;
|
||||
|
||||
public class RouteService(IRouteRepository repo, IUnitOfWork uow)
|
||||
{
|
||||
public async Task<List<RouteResponse>> GetAllAsync() =>
|
||||
(await repo.GetAllAsync())
|
||||
.Where(r => r.IsActive)
|
||||
.Select(r => new RouteResponse(r.Id, r.RouteName, r.StartPoint, r.EndPoint, r.Length, 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.RouteName, route.StartPoint, route.EndPoint, route.Length, route.IsActive, route.CreatedAt);
|
||||
}
|
||||
|
||||
public async Task<RouteResponse> CreateAsync(CreateRouteRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var route = new Models.Route
|
||||
{
|
||||
RouteName = request.RouteName,
|
||||
StartPoint = request.StartPoint,
|
||||
EndPoint = request.EndPoint,
|
||||
Length = request.Length
|
||||
};
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
await repo.AddAsync(route);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return new RouteResponse(route.Id, route.RouteName, route.StartPoint, route.EndPoint, route.Length, route.IsActive, route.CreatedAt);
|
||||
}
|
||||
|
||||
public async Task<RouteResponse?> UpdateAsync(Guid id, UpdateRouteRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var route = await repo.GetByIdAsync(id);
|
||||
if (route is null || !route.IsActive) return null;
|
||||
route.RouteName = request.RouteName;
|
||||
route.StartPoint = request.StartPoint;
|
||||
route.EndPoint = request.EndPoint;
|
||||
route.Length = request.Length;
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
repo.Update(route);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return new RouteResponse(route.Id, route.RouteName, route.StartPoint, route.EndPoint, route.Length, route.IsActive, route.CreatedAt);
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAsync(Guid id, CancellationToken ct = default)
|
||||
{
|
||||
var route = await repo.GetByIdAsync(id);
|
||||
if (route is null || !route.IsActive) return false;
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
repo.Remove(route);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Distribution.Features.Store;
|
||||
|
||||
public interface IStoreRepository
|
||||
{
|
||||
Task<Models.Store?> GetByIdAsync(Guid id);
|
||||
Task<List<Models.Store>> GetAllAsync();
|
||||
Task AddAsync(Models.Store entity);
|
||||
void Update(Models.Store entity);
|
||||
void Remove(Models.Store entity);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Store;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/stores")]
|
||||
public class StoreController(StoreService service) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAll() =>
|
||||
Ok(await service.GetAllAsync());
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var result = await service.GetByIdAsync(id);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create(CreateStoreRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.CreateAsync(request, ct);
|
||||
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
public async Task<IActionResult> Update(Guid id, UpdateStoreRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.UpdateAsync(id, request, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
||||
{
|
||||
var deleted = await service.DeleteAsync(id, ct);
|
||||
return deleted ? NoContent() : NotFound();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
namespace Distribution.Features.Store;
|
||||
|
||||
public record CreateStoreRequest(
|
||||
string OwnerName,
|
||||
string StoreName,
|
||||
string StoreAddress,
|
||||
decimal Latitude,
|
||||
decimal Longitude,
|
||||
decimal OneTimeAmount,
|
||||
decimal CreditAmount);
|
||||
|
||||
public record UpdateStoreRequest(
|
||||
string OwnerName,
|
||||
string StoreName,
|
||||
string StoreAddress,
|
||||
decimal Latitude,
|
||||
decimal Longitude,
|
||||
decimal OneTimeAmount,
|
||||
decimal CreditAmount);
|
||||
|
||||
public record StoreResponse(
|
||||
Guid Id,
|
||||
string OwnerName,
|
||||
string StoreName,
|
||||
string StoreAddress,
|
||||
decimal Latitude,
|
||||
decimal Longitude,
|
||||
decimal OneTimeAmount,
|
||||
decimal CreditAmount,
|
||||
bool IsActive,
|
||||
DateTime CreatedAt);
|
||||
@@ -0,0 +1,25 @@
|
||||
using Distribution.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.Store;
|
||||
|
||||
public class StoreRepository(AppDBContext context) : IStoreRepository
|
||||
{
|
||||
public async Task<Models.Store?> GetByIdAsync(Guid id) =>
|
||||
await context.Stores.FindAsync(id);
|
||||
|
||||
public async Task<List<Models.Store>> GetAllAsync() =>
|
||||
await context.Stores.ToListAsync();
|
||||
|
||||
public async Task AddAsync(Models.Store entity) =>
|
||||
await context.Stores.AddAsync(entity);
|
||||
|
||||
public void Update(Models.Store entity) =>
|
||||
context.Stores.Update(entity);
|
||||
|
||||
public void Remove(Models.Store entity)
|
||||
{
|
||||
entity.IsActive = false;
|
||||
context.Stores.Update(entity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
using Distribution.Infra.UoW;
|
||||
|
||||
namespace Distribution.Features.Store;
|
||||
|
||||
public class StoreService(IStoreRepository repo, IUnitOfWork uow)
|
||||
{
|
||||
private static StoreResponse ToResponse(Models.Store s) =>
|
||||
new(s.Id, s.OwnerName, s.StoreName, s.StoreAddress, s.Latitude, s.Longitude, s.OneTimeAmount, s.CreditAmount, s.IsActive, s.CreatedAt);
|
||||
|
||||
public async Task<List<StoreResponse>> GetAllAsync() =>
|
||||
(await repo.GetAllAsync())
|
||||
.Where(s => s.IsActive)
|
||||
.Select(ToResponse)
|
||||
.ToList();
|
||||
|
||||
public async Task<StoreResponse?> GetByIdAsync(Guid id)
|
||||
{
|
||||
var store = await repo.GetByIdAsync(id);
|
||||
if (store is null || !store.IsActive) return null;
|
||||
return ToResponse(store);
|
||||
}
|
||||
|
||||
public async Task<StoreResponse> CreateAsync(CreateStoreRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var store = new Models.Store
|
||||
{
|
||||
OwnerName = request.OwnerName,
|
||||
StoreName = request.StoreName,
|
||||
StoreAddress = request.StoreAddress,
|
||||
Latitude = request.Latitude,
|
||||
Longitude = request.Longitude,
|
||||
OneTimeAmount = request.OneTimeAmount,
|
||||
CreditAmount = request.CreditAmount
|
||||
};
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
await repo.AddAsync(store);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return ToResponse(store);
|
||||
}
|
||||
|
||||
public async Task<StoreResponse?> UpdateAsync(Guid id, UpdateStoreRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var store = await repo.GetByIdAsync(id);
|
||||
if (store is null || !store.IsActive) return null;
|
||||
store.OwnerName = request.OwnerName;
|
||||
store.StoreName = request.StoreName;
|
||||
store.StoreAddress = request.StoreAddress;
|
||||
store.Latitude = request.Latitude;
|
||||
store.Longitude = request.Longitude;
|
||||
store.OneTimeAmount = request.OneTimeAmount;
|
||||
store.CreditAmount = request.CreditAmount;
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
repo.Update(store);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return ToResponse(store);
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAsync(Guid id, CancellationToken ct = default)
|
||||
{
|
||||
var store = await repo.GetByIdAsync(id);
|
||||
if (store is null || !store.IsActive) return false;
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
repo.Remove(store);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Distribution.Features.Vehicle;
|
||||
|
||||
public interface IVehicleRepository
|
||||
{
|
||||
Task<Models.Vehicle?> GetByIdAsync(Guid id);
|
||||
Task<List<Models.Vehicle>> GetAllAsync();
|
||||
Task AddAsync(Models.Vehicle entity);
|
||||
void Update(Models.Vehicle entity);
|
||||
void Remove(Models.Vehicle entity);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Vehicle;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/vehicles")]
|
||||
public class VehicleController(VehicleService service) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAll() =>
|
||||
Ok(await service.GetAllAsync());
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var result = await service.GetByIdAsync(id);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create(CreateVehicleRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.CreateAsync(request, ct);
|
||||
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
public async Task<IActionResult> Update(Guid id, UpdateVehicleRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.UpdateAsync(id, request, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
||||
{
|
||||
var deleted = await service.DeleteAsync(id, ct);
|
||||
return deleted ? NoContent() : NotFound();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.Vehicle;
|
||||
|
||||
public record CreateVehicleRequest(string RegNumber, string NumberPlate, VehicleType Type);
|
||||
|
||||
public record UpdateVehicleRequest(string RegNumber, string NumberPlate, VehicleType Type);
|
||||
|
||||
public record VehicleResponse(Guid Id, string RegNumber, string NumberPlate, VehicleType Type, bool IsActive, DateTime CreatedAt);
|
||||
@@ -0,0 +1,25 @@
|
||||
using Distribution.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.Vehicle;
|
||||
|
||||
public class VehicleRepository(AppDBContext context) : IVehicleRepository
|
||||
{
|
||||
public async Task<Models.Vehicle?> GetByIdAsync(Guid id) =>
|
||||
await context.Vehicles.FindAsync(id);
|
||||
|
||||
public async Task<List<Models.Vehicle>> GetAllAsync() =>
|
||||
await context.Vehicles.ToListAsync();
|
||||
|
||||
public async Task AddAsync(Models.Vehicle entity) =>
|
||||
await context.Vehicles.AddAsync(entity);
|
||||
|
||||
public void Update(Models.Vehicle entity) =>
|
||||
context.Vehicles.Update(entity);
|
||||
|
||||
public void Remove(Models.Vehicle entity)
|
||||
{
|
||||
entity.IsActive = false;
|
||||
context.Vehicles.Update(entity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using Distribution.Infra.UoW;
|
||||
|
||||
namespace Distribution.Features.Vehicle;
|
||||
|
||||
public class VehicleService(IVehicleRepository repo, IUnitOfWork uow)
|
||||
{
|
||||
public async Task<List<VehicleResponse>> GetAllAsync() =>
|
||||
(await repo.GetAllAsync())
|
||||
.Where(v => v.IsActive)
|
||||
.Select(v => new VehicleResponse(v.Id, v.RegNumber, v.NumberPlate, v.Type, v.IsActive, v.CreatedAt))
|
||||
.ToList();
|
||||
|
||||
public async Task<VehicleResponse?> GetByIdAsync(Guid id)
|
||||
{
|
||||
var vehicle = await repo.GetByIdAsync(id);
|
||||
if (vehicle is null || !vehicle.IsActive) return null;
|
||||
return new VehicleResponse(vehicle.Id, vehicle.RegNumber, vehicle.NumberPlate, vehicle.Type, vehicle.IsActive, vehicle.CreatedAt);
|
||||
}
|
||||
|
||||
public async Task<VehicleResponse> CreateAsync(CreateVehicleRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var vehicle = new Models.Vehicle
|
||||
{
|
||||
RegNumber = request.RegNumber,
|
||||
NumberPlate = request.NumberPlate,
|
||||
Type = request.Type
|
||||
};
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
await repo.AddAsync(vehicle);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return new VehicleResponse(vehicle.Id, vehicle.RegNumber, vehicle.NumberPlate, vehicle.Type, vehicle.IsActive, vehicle.CreatedAt);
|
||||
}
|
||||
|
||||
public async Task<VehicleResponse?> UpdateAsync(Guid id, UpdateVehicleRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var vehicle = await repo.GetByIdAsync(id);
|
||||
if (vehicle is null || !vehicle.IsActive) return null;
|
||||
vehicle.RegNumber = request.RegNumber;
|
||||
vehicle.NumberPlate = request.NumberPlate;
|
||||
vehicle.Type = request.Type;
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
repo.Update(vehicle);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return new VehicleResponse(vehicle.Id, vehicle.RegNumber, vehicle.NumberPlate, vehicle.Type, vehicle.IsActive, vehicle.CreatedAt);
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAsync(Guid id, CancellationToken ct = default)
|
||||
{
|
||||
var vehicle = await repo.GetByIdAsync(id);
|
||||
if (vehicle is null || !vehicle.IsActive) return false;
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
repo.Remove(vehicle);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Distribution.Features.Warehouse;
|
||||
|
||||
public interface IWarehouseRepository
|
||||
{
|
||||
Task<Models.Warehouse?> GetByIdAsync(Guid id);
|
||||
Task<List<Models.Warehouse>> GetAllAsync();
|
||||
Task AddAsync(Models.Warehouse entity);
|
||||
void Update(Models.Warehouse entity);
|
||||
void Remove(Models.Warehouse entity);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Warehouse;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/warehouses")]
|
||||
public class WarehouseController(WarehouseService service) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAll() =>
|
||||
Ok(await service.GetAllAsync());
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var result = await service.GetByIdAsync(id);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create(CreateWarehouseRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.CreateAsync(request, ct);
|
||||
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
public async Task<IActionResult> Update(Guid id, UpdateWarehouseRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.UpdateAsync(id, request, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
||||
{
|
||||
var deleted = await service.DeleteAsync(id, ct);
|
||||
return deleted ? NoContent() : NotFound();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.Warehouse;
|
||||
|
||||
public record CreateWarehouseRequest(string Name, string Location, WareHouseType Type, Guid? VehicleId);
|
||||
|
||||
public record UpdateWarehouseRequest(string Name, string Location, WareHouseType Type, Guid? VehicleId);
|
||||
|
||||
public record WarehouseResponse(Guid Id, string Name, string Location, WareHouseType Type, Guid? VehicleId, bool IsActive, DateTime CreatedAt);
|
||||
@@ -0,0 +1,25 @@
|
||||
using Distribution.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.Warehouse;
|
||||
|
||||
public class WarehouseRepository(AppDBContext context) : IWarehouseRepository
|
||||
{
|
||||
public async Task<Models.Warehouse?> GetByIdAsync(Guid id) =>
|
||||
await context.Warehouses.FindAsync(id);
|
||||
|
||||
public async Task<List<Models.Warehouse>> GetAllAsync() =>
|
||||
await context.Warehouses.ToListAsync();
|
||||
|
||||
public async Task AddAsync(Models.Warehouse entity) =>
|
||||
await context.Warehouses.AddAsync(entity);
|
||||
|
||||
public void Update(Models.Warehouse entity) =>
|
||||
context.Warehouses.Update(entity);
|
||||
|
||||
public void Remove(Models.Warehouse entity)
|
||||
{
|
||||
entity.IsActive = false;
|
||||
context.Warehouses.Update(entity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using Distribution.Infra.UoW;
|
||||
|
||||
namespace Distribution.Features.Warehouse;
|
||||
|
||||
public class WarehouseService(IWarehouseRepository repo, IUnitOfWork uow)
|
||||
{
|
||||
private static WarehouseResponse ToResponse(Models.Warehouse w) =>
|
||||
new(w.Id, w.Name, w.Location, w.Type, w.VehicleId, w.IsActive, w.CreatedAt);
|
||||
|
||||
public async Task<List<WarehouseResponse>> GetAllAsync() =>
|
||||
(await repo.GetAllAsync())
|
||||
.Where(w => w.IsActive)
|
||||
.Select(ToResponse)
|
||||
.ToList();
|
||||
|
||||
public async Task<WarehouseResponse?> GetByIdAsync(Guid id)
|
||||
{
|
||||
var warehouse = await repo.GetByIdAsync(id);
|
||||
if (warehouse is null || !warehouse.IsActive) return null;
|
||||
return ToResponse(warehouse);
|
||||
}
|
||||
|
||||
public async Task<WarehouseResponse> CreateAsync(CreateWarehouseRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var warehouse = new Models.Warehouse
|
||||
{
|
||||
Name = request.Name,
|
||||
Location = request.Location,
|
||||
Type = request.Type,
|
||||
VehicleId = request.VehicleId
|
||||
};
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
await repo.AddAsync(warehouse);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return ToResponse(warehouse);
|
||||
}
|
||||
|
||||
public async Task<WarehouseResponse?> UpdateAsync(Guid id, UpdateWarehouseRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var warehouse = await repo.GetByIdAsync(id);
|
||||
if (warehouse is null || !warehouse.IsActive) return null;
|
||||
warehouse.Name = request.Name;
|
||||
warehouse.Location = request.Location;
|
||||
warehouse.Type = request.Type;
|
||||
warehouse.VehicleId = request.VehicleId;
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
repo.Update(warehouse);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return ToResponse(warehouse);
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAsync(Guid id, CancellationToken ct = default)
|
||||
{
|
||||
var warehouse = await repo.GetByIdAsync(id);
|
||||
if (warehouse is null || !warehouse.IsActive) return false;
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
repo.Remove(warehouse);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using Microsoft.EntityFrameworkCore.Storage;
|
||||
using Distribution.Data;
|
||||
|
||||
namespace Distribution.Infra.UoW
|
||||
{
|
||||
public class EFUnitOfWork : IUnitOfWork
|
||||
{
|
||||
private readonly AppDBContext _dbContext;
|
||||
private IDbContextTransaction? _transaction;
|
||||
|
||||
public EFUnitOfWork(AppDBContext dbContext)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
}
|
||||
|
||||
public async Task BeginAsync(CancellationToken ct = default)
|
||||
{
|
||||
if (_transaction == null)
|
||||
_transaction = await _dbContext.Database.BeginTransactionAsync(ct);
|
||||
}
|
||||
|
||||
public async Task SaveChangesAsync(CancellationToken ct = default)
|
||||
{
|
||||
await _dbContext.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
public async Task CommitAsync(CancellationToken ct = default)
|
||||
{
|
||||
await _dbContext.SaveChangesAsync(ct);
|
||||
|
||||
if (_transaction != null)
|
||||
{
|
||||
await _transaction.CommitAsync(ct);
|
||||
await _transaction.DisposeAsync();
|
||||
_transaction = null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RollbackAsync(CancellationToken ct = default)
|
||||
{
|
||||
if (_transaction != null)
|
||||
{
|
||||
await _transaction.RollbackAsync(ct);
|
||||
await _transaction.DisposeAsync();
|
||||
_transaction = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Distribution.Infra.UoW
|
||||
{
|
||||
public interface IUnitOfWork
|
||||
{
|
||||
Task BeginAsync(CancellationToken ct = default);
|
||||
Task CommitAsync(CancellationToken ct = default);
|
||||
Task RollbackAsync(CancellationToken ct = default);
|
||||
Task SaveChangesAsync(CancellationToken ct = default);
|
||||
}
|
||||
}
|
||||
+353
@@ -0,0 +1,353 @@
|
||||
// <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("20260812090735_init")]
|
||||
partial class init
|
||||
{
|
||||
/// <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<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<Guid?>("VehicleId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<int>("type")
|
||||
.HasColumnType("int");
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Distribution.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class init : 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)
|
||||
},
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Distribution.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Distribution.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDBContext))]
|
||||
partial class AppDBContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(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<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<Guid?>("VehicleId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<int>("type")
|
||||
.HasColumnType("int");
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public class DeliveryRun : Template
|
||||
{
|
||||
public Guid FK_VehicleId { get; set; }
|
||||
[ForeignKey("FK_VehicleId")]
|
||||
public Vehicle Vehicle { get; set; }
|
||||
//public Guid FK_DriverId { get; set; }
|
||||
//[ForeignKey("FK_DriverId")]
|
||||
//public Driver Driver { get; set; }
|
||||
public int Meterstart { get; set; }
|
||||
public int Meterend { get; set; }
|
||||
public Guid FK_RouteId { get; set; }
|
||||
[ForeignKey("FK_RouteId")]
|
||||
public Route Route { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public class Item : Template
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public class Order : Template
|
||||
{
|
||||
public DateTime OrderDate { get; set; }
|
||||
public Guid FK_StoreId { get; set; }
|
||||
[ForeignKey("FK_StoreId")]
|
||||
public Store Store { get; set; }
|
||||
public decimal TotalAmount { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public class OrderItem : Template
|
||||
{
|
||||
public Guid FK_OrderId { get; set; }
|
||||
[ForeignKey("FK_OrderId")]
|
||||
public Order Order { get; set; }
|
||||
public Guid FK_ItemId { get; set; }
|
||||
[ForeignKey("FK_ItemId")]
|
||||
public Item Item { get; set; }
|
||||
public int Quantity { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public class Region : Template
|
||||
{
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public class Route : Template
|
||||
{
|
||||
public String RouteName { get; set; }
|
||||
public String StartPoint { get; set; }
|
||||
public String EndPoint { get; set; }
|
||||
public decimal Length { get; set; } = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public class SaleRoute : Template
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public class Store : Template
|
||||
{
|
||||
public String OwnerName { get; set; }
|
||||
public String StoreName { get; set; }
|
||||
public String StoreAddress { get; set; }
|
||||
public decimal Latitude { get; set; } = 0;
|
||||
public decimal Longitude { get; set; } = 0;
|
||||
public decimal OneTimeAmount { get; set; } = 0;
|
||||
public decimal CreditAmount { get; set; } = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public class Template
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public DateTime CreatedAt { get; set; } = DateTime.Now;
|
||||
public bool IsActive { get; set; } = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public class Vehicle : Template
|
||||
{
|
||||
public string RegNumber { get; set; }
|
||||
public string NumberPlate { get; set; }
|
||||
public VehicleType Type { get; set; }
|
||||
}
|
||||
public enum VehicleType
|
||||
{
|
||||
Car,
|
||||
Truck,
|
||||
Van,
|
||||
Motorcycle,
|
||||
Bicycle
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Distribution.Models
|
||||
{
|
||||
public class Warehouse : Template
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Location { get; set; }
|
||||
public WareHouseType Type { get; set; }
|
||||
public Guid? VehicleId { get; set; }
|
||||
[ForeignKey("VehicleId")]
|
||||
public Vehicle Vehicle { get; set; }
|
||||
}
|
||||
|
||||
public enum WareHouseType
|
||||
{
|
||||
DistributionCenter,
|
||||
FulfillmentCenter,
|
||||
CrossDocking,
|
||||
ColdStorage,
|
||||
BondedWarehouse,
|
||||
AutomatedWarehouse
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
# Phase Plan — Route & Store Management
|
||||
|
||||
## Scope
|
||||
|
||||
Build the full vertical-slice stack for: **Region, Route, Vehicle, Store, Warehouse, Order, OrderItem**.
|
||||
Item model is sourced from ERP Core — excluded from this phase.
|
||||
SaleRoute shape is TBD — excluded until clarified.
|
||||
|
||||
---
|
||||
|
||||
## Project Structure (target)
|
||||
|
||||
```
|
||||
Distribution/
|
||||
├── Models/ # All entity POCOs — unchanged
|
||||
│ ├── Template.cs
|
||||
│ ├── Region.cs
|
||||
│ ├── Route.cs
|
||||
│ ├── Vehicle.cs
|
||||
│ ├── Store.cs
|
||||
│ ├── Warehouse.cs
|
||||
│ ├── SaleRoute.cs # TBD
|
||||
│ ├── Order.cs
|
||||
│ ├── OrderItem.cs
|
||||
│ ├── DeliveryRun.cs
|
||||
│ └── Item.cs # Sourced from ERP Core
|
||||
├── Data/
|
||||
│ └── AppDBContext.cs # DbSets + Fluent API config
|
||||
├── Infra/
|
||||
│ ├── UoW/ # Already implemented
|
||||
│ │ ├── IUnitOfWork.cs
|
||||
│ │ └── EFUnitOfWork.cs
|
||||
│ └── Repositories/ # Shared generic base only
|
||||
│ ├── IGenericRepo.cs
|
||||
│ └── GenericRepo.cs
|
||||
└── Features/
|
||||
├── Region/
|
||||
│ ├── RegionDTOs.cs
|
||||
│ ├── IRegionRepository.cs
|
||||
│ ├── RegionRepository.cs
|
||||
│ ├── RegionService.cs
|
||||
│ └── RegionController.cs
|
||||
├── Route/
|
||||
│ ├── RouteDTOs.cs
|
||||
│ ├── IRouteRepository.cs
|
||||
│ ├── RouteRepository.cs
|
||||
│ ├── RouteService.cs
|
||||
│ └── RouteController.cs
|
||||
├── Vehicle/
|
||||
│ ├── VehicleDTOs.cs
|
||||
│ ├── IVehicleRepository.cs
|
||||
│ ├── VehicleRepository.cs
|
||||
│ ├── VehicleService.cs
|
||||
│ └── VehicleController.cs
|
||||
├── Store/
|
||||
│ ├── StoreDTOs.cs
|
||||
│ ├── IStoreRepository.cs
|
||||
│ ├── StoreRepository.cs
|
||||
│ ├── StoreService.cs
|
||||
│ └── StoreController.cs
|
||||
├── Warehouse/
|
||||
│ ├── WarehouseDTOs.cs
|
||||
│ ├── IWarehouseRepository.cs
|
||||
│ ├── WarehouseRepository.cs
|
||||
│ ├── WarehouseService.cs
|
||||
│ └── WarehouseController.cs
|
||||
└── Order/
|
||||
├── OrderDTOs.cs
|
||||
├── IOrderRepository.cs
|
||||
├── OrderRepository.cs
|
||||
├── OrderService.cs
|
||||
└── OrderController.cs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Hard Rules (from AGENTS.md)
|
||||
|
||||
- Only `uow.CommitAsync()` in services — never in repositories.
|
||||
- Use `DateTime.Now` — never `DateTime.UtcNow`.
|
||||
- Never build anything not explicitly requested.
|
||||
|
||||
---
|
||||
|
||||
## Step 2 — Complete AppDBContext
|
||||
|
||||
File: `Data/AppDBContext.cs`
|
||||
|
||||
Add DbSets and Fluent API configuration:
|
||||
|
||||
```csharp
|
||||
public DbSet<Region> Regions { get; set; }
|
||||
public DbSet<Route> Routes { get; set; }
|
||||
public DbSet<Vehicle> Vehicles { get; set; }
|
||||
public DbSet<Store> Stores { get; set; }
|
||||
public DbSet<Warehouse> Warehouses { get; set; }
|
||||
public DbSet<SaleRoute> SaleRoutes { get; set; }
|
||||
public DbSet<Order> Orders { get; set; }
|
||||
public DbSet<OrderItem> OrderItems { get; set; }
|
||||
public DbSet<DeliveryRun> DeliveryRuns { get; set; }
|
||||
public DbSet<Item> Items { get; set; }
|
||||
|
||||
// OnModelCreating
|
||||
modelBuilder.Entity<OrderItem>()
|
||||
.HasKey(oi => new { oi.FK_OrderId, oi.FK_ItemId });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3 — Initial Migration
|
||||
|
||||
```powershell
|
||||
dotnet ef migrations add InitialCreate --project Distribution
|
||||
dotnet ef database update --project Distribution
|
||||
```
|
||||
|
||||
Each developer applies this locally. No migration files are shared.
|
||||
|
||||
---
|
||||
|
||||
## Step 4 — Generic Repository (Infra/Repositories)
|
||||
|
||||
**`IGenericRepo<T>`**
|
||||
```
|
||||
GetByIdAsync(Guid id)
|
||||
GetAllAsync()
|
||||
AddAsync(T entity)
|
||||
Update(T entity)
|
||||
Remove(T entity)
|
||||
```
|
||||
|
||||
**`GenericRepo<T>`** — implements above using DbContext. No `SaveChanges` calls.
|
||||
|
||||
---
|
||||
|
||||
## Step 5 — Feature Implementation Order
|
||||
|
||||
Build each feature in this dependency order:
|
||||
|
||||
```
|
||||
1. Region → no dependencies
|
||||
2. Route → no dependencies
|
||||
3. Vehicle → no dependencies
|
||||
4. Store → no dependencies
|
||||
5. Warehouse → depends on Vehicle (FK)
|
||||
6. Order → depends on Store (FK)
|
||||
7. OrderItem → depends on Order + Item (composite FK)
|
||||
```
|
||||
|
||||
### Per-feature deliverables
|
||||
|
||||
#### DTOs (`{Feature}DTOs.cs`)
|
||||
- `CreateRequest` — fields accepted on POST
|
||||
- `UpdateRequest` — fields accepted on PUT
|
||||
- `Response` — shape returned to client (never expose the entity directly)
|
||||
|
||||
#### Repository interface (`I{Feature}Repository.cs`)
|
||||
Extends `IGenericRepo<T>`. Add feature-specific queries (e.g., `GetByStoreId`).
|
||||
|
||||
#### Repository implementation (`{Feature}Repository.cs`)
|
||||
Inherits `GenericRepo<T>`. Implements feature-specific queries. No `CommitAsync` calls.
|
||||
|
||||
#### Service (`{Feature}Service.cs`)
|
||||
- Contains all business logic.
|
||||
- Calls `_uow.CommitAsync(ct)` after write operations.
|
||||
- For multi-step writes: `BeginAsync → work → CommitAsync` (catch → `RollbackAsync`).
|
||||
|
||||
#### Controller (`{Feature}Controller.cs`)
|
||||
Thin — maps HTTP → service call → returns result. No logic, no UoW calls.
|
||||
|
||||
---
|
||||
|
||||
## Step 6 — Endpoints
|
||||
|
||||
### Region — `/api/regions`
|
||||
| Method | Route | Description |
|
||||
|---|---|---|
|
||||
| GET | `/` | Get all active regions |
|
||||
| GET | `/{id}` | Get region by id |
|
||||
| POST | `/` | Create region |
|
||||
| PUT | `/{id}` | Update region |
|
||||
| DELETE | `/{id}` | Soft delete (set IsActive = false) |
|
||||
|
||||
### Route — `/api/routes`
|
||||
| Method | Route | Description |
|
||||
|---|---|---|
|
||||
| GET | `/` | Get all active routes |
|
||||
| GET | `/{id}` | Get route by id |
|
||||
| POST | `/` | Create route |
|
||||
| PUT | `/{id}` | Update route |
|
||||
| DELETE | `/{id}` | Soft delete |
|
||||
|
||||
### Vehicle — `/api/vehicles`
|
||||
| Method | Route | Description |
|
||||
|---|---|---|
|
||||
| GET | `/` | Get all active vehicles |
|
||||
| GET | `/{id}` | Get vehicle by id |
|
||||
| POST | `/` | Create vehicle |
|
||||
| PUT | `/{id}` | Update vehicle |
|
||||
| DELETE | `/{id}` | Soft delete |
|
||||
|
||||
### Store — `/api/stores`
|
||||
| Method | Route | Description |
|
||||
|---|---|---|
|
||||
| GET | `/` | Get all active stores |
|
||||
| GET | `/{id}` | Get store by id |
|
||||
| POST | `/` | Create store |
|
||||
| PUT | `/{id}` | Update store |
|
||||
| DELETE | `/{id}` | Soft delete |
|
||||
|
||||
### Warehouse — `/api/warehouses`
|
||||
| Method | Route | Description |
|
||||
|---|---|---|
|
||||
| GET | `/` | Get all active warehouses |
|
||||
| GET | `/{id}` | Get warehouse by id |
|
||||
| POST | `/` | Create warehouse |
|
||||
| PUT | `/{id}` | Update warehouse |
|
||||
| DELETE | `/{id}` | Soft delete |
|
||||
|
||||
### Order — `/api/orders`
|
||||
| Method | Route | Description |
|
||||
|---|---|---|
|
||||
| GET | `/` | Get all active orders |
|
||||
| GET | `/{id}` | Get order with items |
|
||||
| GET | `/store/{storeId}` | Get orders by store |
|
||||
| POST | `/` | Create order with items |
|
||||
| PUT | `/{id}` | Update order |
|
||||
| DELETE | `/{id}` | Soft delete |
|
||||
|
||||
---
|
||||
|
||||
## Step 7 — DI Registration (Program.cs)
|
||||
|
||||
Register all feature repositories and services:
|
||||
|
||||
```csharp
|
||||
// Generic base
|
||||
builder.Services.AddScoped(typeof(IGenericRepo<>), typeof(GenericRepo<>));
|
||||
|
||||
// Feature repositories
|
||||
builder.Services.AddScoped<IRegionRepository, RegionRepository>();
|
||||
builder.Services.AddScoped<IRouteRepository, RouteRepository>();
|
||||
builder.Services.AddScoped<IVehicleRepository, VehicleRepository>();
|
||||
builder.Services.AddScoped<IStoreRepository, StoreRepository>();
|
||||
builder.Services.AddScoped<IWarehouseRepository, WarehouseRepository>();
|
||||
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
|
||||
|
||||
// Feature services
|
||||
builder.Services.AddScoped<RegionService>();
|
||||
builder.Services.AddScoped<RouteService>();
|
||||
builder.Services.AddScoped<VehicleService>();
|
||||
builder.Services.AddScoped<StoreService>();
|
||||
builder.Services.AddScoped<WarehouseService>();
|
||||
builder.Services.AddScoped<OrderService>();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deferred
|
||||
|
||||
| Item | Reason |
|
||||
|---|---|
|
||||
| `SaleRoute` | Shape undefined — implement after clarification |
|
||||
| `Item` model | Sourced from ERP Core — user handles integration |
|
||||
| `DeliveryRun` | Not in scope for this phase |
|
||||
| Driver FK on DeliveryRun | Commented out — depends on HR/Driver module |
|
||||
| Tests | No test project yet — add after endpoints stabilize |
|
||||
@@ -0,0 +1,50 @@
|
||||
using Distribution.Data;
|
||||
using Distribution.Features.Order;
|
||||
using Distribution.Features.Region;
|
||||
using Distribution.Features.Route;
|
||||
using Distribution.Features.Store;
|
||||
using Distribution.Features.Vehicle;
|
||||
using Distribution.Features.Warehouse;
|
||||
using Distribution.Infra.UoW;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MySql.EntityFrameworkCore.Extensions;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
|
||||
?? throw new InvalidOperationException("Connection string 'DefaultConnection' was not found.");
|
||||
|
||||
builder.Services.AddDbContext<AppDBContext>(options =>
|
||||
options.UseMySQL(connectionString));
|
||||
|
||||
builder.Services.AddScoped<IUnitOfWork, EFUnitOfWork>();
|
||||
|
||||
builder.Services.AddScoped<IRegionRepository, RegionRepository>();
|
||||
builder.Services.AddScoped<IRouteRepository, RouteRepository>();
|
||||
builder.Services.AddScoped<IVehicleRepository, VehicleRepository>();
|
||||
builder.Services.AddScoped<IStoreRepository, StoreRepository>();
|
||||
builder.Services.AddScoped<IWarehouseRepository, WarehouseRepository>();
|
||||
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
|
||||
|
||||
builder.Services.AddScoped<RegionService>();
|
||||
builder.Services.AddScoped<RouteService>();
|
||||
builder.Services.AddScoped<VehicleService>();
|
||||
builder.Services.AddScoped<StoreService>();
|
||||
builder.Services.AddScoped<WarehouseService>();
|
||||
builder.Services.AddScoped<OrderService>();
|
||||
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddOpenApi();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.MapOpenApi();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
app.UseAuthorization();
|
||||
app.MapControllers();
|
||||
|
||||
app.Run();
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "http://localhost:5089",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "https://localhost:7025;http://localhost:5089",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Server=localhost;Port=3306;Database=distribution_db;User=root;Password=root"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
Reference in New Issue
Block a user