Add SetActiveStatusAsync, and update DTOs for active status management.

This commit is contained in:
2026-09-04 11:48:53 +05:30
parent d0bf5581c5
commit 1645eae694
5 changed files with 78 additions and 15 deletions
@@ -1,3 +1,5 @@
using Distribution.Models;
namespace Distribution.Features.Store; namespace Distribution.Features.Store;
public interface IStoreRepository public interface IStoreRepository
@@ -5,6 +7,8 @@ public interface IStoreRepository
Task<Models.Store?> GetByIdAsync(Guid id); Task<Models.Store?> GetByIdAsync(Guid id);
Task<Models.Store?> GetByIdWithRegionAsync(Guid id); Task<Models.Store?> GetByIdWithRegionAsync(Guid id);
Task<List<Models.Store>> GetAllAsync(); Task<List<Models.Store>> GetAllAsync();
Task<List<Models.Store>> GetAllAsync(Guid? routeId, KycStatus? kycStatus, string? search, int page, int pageSize);
Task<int> GetCountAsync(Guid? routeId, KycStatus? kycStatus, string? search);
Task AddAsync(Models.Store entity); Task AddAsync(Models.Store entity);
void Update(Models.Store entity); void Update(Models.Store entity);
void Remove(Models.Store entity); void Remove(Models.Store entity);
+16 -3
View File
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc; using Distribution.Models;
using Microsoft.AspNetCore.Mvc;
namespace Distribution.Features.Store; namespace Distribution.Features.Store;
@@ -7,8 +8,13 @@ namespace Distribution.Features.Store;
public class StoreController(StoreService service) : ControllerBase public class StoreController(StoreService service) : ControllerBase
{ {
[HttpGet] [HttpGet]
public async Task<IActionResult> GetAll() => public async Task<IActionResult> GetAll(
Ok(await service.GetAllAsync()); [FromQuery] Guid? routeId,
[FromQuery] KycStatus? kycStatus,
[FromQuery] string? search,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20) =>
Ok(await service.GetAllAsync(routeId, kycStatus, search, page, pageSize));
[HttpGet("{id:guid}")] [HttpGet("{id:guid}")]
public async Task<IActionResult> GetById(Guid id) public async Task<IActionResult> GetById(Guid id)
@@ -100,4 +106,11 @@ public class StoreController(StoreService service) : ControllerBase
var result = await service.VerifyKycAsync(id, new VerifyKycRequest(request.KycStatus, null), ct); var result = await service.VerifyKycAsync(id, new VerifyKycRequest(request.KycStatus, null), ct);
return result is null ? NotFound() : Ok(result); return result is null ? NotFound() : Ok(result);
} }
[HttpPatch("{id:guid}/status")]
public async Task<IActionResult> PatchStatus(Guid id, PatchStoreStatusRequest request, CancellationToken ct)
{
var result = await service.SetActiveStatusAsync(id, request.IsActive, ct);
return result is null ? NotFound() : Ok(result);
}
} }
+4 -1
View File
@@ -20,7 +20,8 @@ public record UpdateStoreRequest(
decimal Latitude, decimal Latitude,
decimal Longitude, decimal Longitude,
decimal OneTimeAmount, decimal OneTimeAmount,
decimal CreditAmount); decimal CreditAmount,
bool IsActive = true);
public record StoreResponse( public record StoreResponse(
Guid Id, Guid Id,
@@ -55,4 +56,6 @@ public record SchemeEligibilityResponse(bool Eligible, string? Reason);
public record PatchKycStatusRequest(KycStatus KycStatus); public record PatchKycStatusRequest(KycStatus KycStatus);
public record PatchStoreStatusRequest(bool IsActive);
public record StoreDashboardResponse(Guid StoreId, string Period, int OrderCount, decimal TotalOrderValue, DateTime? LastOrderDate); public record StoreDashboardResponse(Guid StoreId, string Period, int OrderCount, decimal TotalOrderValue, DateTime? LastOrderDate);
@@ -1,4 +1,5 @@
using Distribution.Data; using Distribution.Data;
using Distribution.Models;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Distribution.Features.Store; namespace Distribution.Features.Store;
@@ -16,6 +17,25 @@ public class StoreRepository(AppDBContext context) : IStoreRepository
public async Task<List<Models.Store>> GetAllAsync() => public async Task<List<Models.Store>> GetAllAsync() =>
await context.Stores.ToListAsync(); await context.Stores.ToListAsync();
private static IQueryable<Models.Store> ApplyFilters(
IQueryable<Models.Store> query, Guid? routeId, KycStatus? kycStatus, string? search) =>
query
.Where(s => routeId == null || s.FK_RouteId == routeId)
.Where(s => kycStatus == null || s.KycStatus == kycStatus)
.Where(s => string.IsNullOrWhiteSpace(search)
|| s.StoreName.Contains(search)
|| s.OwnerName.Contains(search));
public async Task<List<Models.Store>> GetAllAsync(Guid? routeId, KycStatus? kycStatus, string? search, int page, int pageSize) =>
await ApplyFilters(context.Stores, routeId, kycStatus, search)
.OrderBy(s => s.StoreName)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
public async Task<int> GetCountAsync(Guid? routeId, KycStatus? kycStatus, string? search) =>
await ApplyFilters(context.Stores, routeId, kycStatus, search).CountAsync();
public async Task AddAsync(Models.Store entity) => public async Task AddAsync(Models.Store entity) =>
await context.Stores.AddAsync(entity); await context.Stores.AddAsync(entity);
+34 -11
View File
@@ -1,4 +1,5 @@
using Distribution.Features.Scheme; using Distribution.Features.Scheme;
using Distribution.Features.Stock;
using Distribution.Infra.Authorization; using Distribution.Infra.Authorization;
using Distribution.Infra.UoW; using Distribution.Infra.UoW;
using Distribution.Models; using Distribution.Models;
@@ -23,21 +24,20 @@ public class StoreService(IStoreRepository repo, IUnitOfWork uow, ICurrentUserSe
new(s.Id, s.FK_RouteId, s.OwnerName, s.StoreName, s.StoreAddress, s.Latitude, s.Longitude, s.OneTimeAmount, s.CreditAmount, new(s.Id, s.FK_RouteId, s.OwnerName, s.StoreName, s.StoreAddress, s.Latitude, s.Longitude, s.OneTimeAmount, s.CreditAmount,
s.CreditLimit, s.OutstandingBalance, s.Segment, s.KycStatus, s.IsActive, s.CreatedAt); s.CreditLimit, s.OutstandingBalance, s.Segment, s.KycStatus, s.IsActive, s.CreatedAt);
public async Task<List<StoreResponse>> GetAllAsync() public async Task<PagedResponse<StoreResponse>> GetAllAsync(
Guid? routeId, KycStatus? kycStatus, string? search, int page, int pageSize)
{ {
var stores = (await repo.GetAllAsync()).Where(s => s.IsActive).ToList(); // Deactivated stores stay visible in the list showing Inactive — deactivate never
if (!HasGlobalScope && currentUser.UserId is not null) // removes the row, only marks it (same rule as Orders, see testbug.md #42).
{ var total = await repo.GetCountAsync(routeId, kycStatus, search);
var accessibleStoreIds = (await scopeAccess.GetAccessibleStoreIdsAsync(currentUser.UserId.Value)).ToHashSet(); var stores = await repo.GetAllAsync(routeId, kycStatus, search, page, pageSize);
stores = stores.Where(s => accessibleStoreIds.Contains(s.Id)).ToList(); return new PagedResponse<StoreResponse>(page, pageSize, total, stores.Select(ToResponse).ToList());
}
return stores.Select(ToResponse).ToList();
} }
public async Task<StoreResponse?> GetByIdAsync(Guid id) public async Task<StoreResponse?> GetByIdAsync(Guid id)
{ {
var store = await repo.GetByIdAsync(id); var store = await repo.GetByIdAsync(id);
if (store is null || !store.IsActive) return null; if (store is null) return null;
//if (!await HasStoreScopeAsync(id)) return null; //if (!await HasStoreScopeAsync(id)) return null;
return ToResponse(store); return ToResponse(store);
} }
@@ -72,7 +72,7 @@ public class StoreService(IStoreRepository repo, IUnitOfWork uow, ICurrentUserSe
public async Task<StoreResponse?> UpdateAsync(Guid id, UpdateStoreRequest request, CancellationToken ct = default) public async Task<StoreResponse?> UpdateAsync(Guid id, UpdateStoreRequest request, CancellationToken ct = default)
{ {
var store = await repo.GetByIdAsync(id); var store = await repo.GetByIdAsync(id);
if (store is null || !store.IsActive) return null; if (store is null) return null;
if (!await HasStoreScopeAsync(id)) return null; if (!await HasStoreScopeAsync(id)) return null;
store.FK_RouteId = request.RouteId; store.FK_RouteId = request.RouteId;
store.OwnerName = request.OwnerName; store.OwnerName = request.OwnerName;
@@ -82,6 +82,7 @@ public class StoreService(IStoreRepository repo, IUnitOfWork uow, ICurrentUserSe
store.Longitude = request.Longitude; store.Longitude = request.Longitude;
store.OneTimeAmount = request.OneTimeAmount; store.OneTimeAmount = request.OneTimeAmount;
store.CreditAmount = request.CreditAmount; store.CreditAmount = request.CreditAmount;
store.IsActive = request.IsActive;
await uow.BeginAsync(ct); await uow.BeginAsync(ct);
try try
{ {
@@ -205,10 +206,32 @@ public class StoreService(IStoreRepository repo, IUnitOfWork uow, ICurrentUserSe
return ToResponse(store); return ToResponse(store);
} }
public async Task<StoreResponse?> SetActiveStatusAsync(Guid id, bool isActive, CancellationToken ct = default)
{
var store = await repo.GetByIdAsync(id);
if (store is null) return null;
if (!await HasStoreScopeAsync(id)) return null;
store.IsActive = isActive;
await uow.BeginAsync(ct);
try
{
repo.Update(store);
await uow.CommitAsync(ct);
}
catch
{
await uow.RollbackAsync(ct);
throw;
}
return ToResponse(store);
}
public async Task<List<StoreVisitHistoryItem>?> GetVisitHistoryAsync(Guid id) public async Task<List<StoreVisitHistoryItem>?> GetVisitHistoryAsync(Guid id)
{ {
var store = await repo.GetByIdAsync(id); var store = await repo.GetByIdAsync(id);
if (store is null || !store.IsActive) return null; if (store is null) return null;
if (!await HasStoreScopeAsync(id)) return null; if (!await HasStoreScopeAsync(id)) return null;
var visits = await routeRepo.GetVisitsByStoreIdAsync(id); var visits = await routeRepo.GetVisitsByStoreIdAsync(id);