conflict fixed
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using Distribution.Features.User;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -6,32 +8,93 @@ namespace Distribution.Features.Auth;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/auth")]
|
||||
public class AuthController(AuthService service, ICurrentUserService currentUser) : ControllerBase
|
||||
public class AuthController(AuthService service, ICurrentUserService currentUser, IUserRepository userRepo) : ControllerBase
|
||||
{
|
||||
private static IActionResult ToActionResult(AuthHexResponse result) =>
|
||||
result.Success ? new OkObjectResult(result) : new ObjectResult(result) { StatusCode = result.StatusCode is >= 400 and < 600 ? result.StatusCode : 401 };
|
||||
|
||||
[HttpPost("login")]
|
||||
public async Task<IActionResult> Login(LoginRequest request, CancellationToken ct) =>
|
||||
ToActionResult(await service.LoginAsync(request, ct));
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> Login(LoginRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.LoginAsync(request, ct);
|
||||
|
||||
if (!result.Success || result.Data is not { } data)
|
||||
return ToActionResult(result);
|
||||
|
||||
string? token = null;
|
||||
TimeSpan maxAge = TimeSpan.FromHours(1);
|
||||
|
||||
if (data.TryGetProperty("accessToken", out var tokenEl))
|
||||
token = tokenEl.GetString();
|
||||
|
||||
if (data.TryGetProperty("expiresIn", out var expEl))
|
||||
maxAge = TimeSpan.FromSeconds(expEl.GetInt32());
|
||||
|
||||
if (!string.IsNullOrEmpty(token))
|
||||
{
|
||||
Response.Cookies.Append("access-token", token, new CookieOptions
|
||||
{
|
||||
HttpOnly = true,
|
||||
SameSite = SameSiteMode.Lax,
|
||||
Path = "/",
|
||||
MaxAge = maxAge,
|
||||
Secure = Request.IsHttps,
|
||||
});
|
||||
}
|
||||
|
||||
MeResponse? profile = null;
|
||||
if (!string.IsNullOrEmpty(token))
|
||||
{
|
||||
var jwt = new JwtSecurityTokenHandler().ReadJwtToken(token);
|
||||
var userIdStr = jwt.Claims.FirstOrDefault(c => c.Type == "UserId")?.Value;
|
||||
if (Guid.TryParse(userIdStr, out var userId))
|
||||
{
|
||||
var user = await userRepo.GetByIdAsync(userId);
|
||||
if (user is not null)
|
||||
profile = new MeResponse(
|
||||
user.Id,
|
||||
user.Username,
|
||||
user.FK_RoleId,
|
||||
user.Role?.Name ?? string.Empty,
|
||||
user.Role?.Level ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(new { result.StatusCode, result.Success, result.Message, result.Data, profile });
|
||||
}
|
||||
|
||||
[HttpPost("register")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> Register(RegisterRequest request, CancellationToken ct) =>
|
||||
ToActionResult(await service.RegisterAsync(request, ct));
|
||||
|
||||
[HttpGet("me")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Me(CancellationToken ct)
|
||||
public async Task<IActionResult> Me()
|
||||
{
|
||||
var userId = currentUser.UserId ?? throw new UnauthorizedAccessException();
|
||||
return ToActionResult(await service.GetUserDetailsAsync(userId, ct));
|
||||
if (currentUser.UserId is null) return Unauthorized();
|
||||
var user = await userRepo.GetByIdAsync(currentUser.UserId.Value);
|
||||
if (user is null) return Unauthorized();
|
||||
return Ok(new MeResponse(
|
||||
user.Id,
|
||||
user.Username,
|
||||
user.FK_RoleId,
|
||||
user.Role?.Name ?? string.Empty,
|
||||
user.Role?.Level ?? 0));
|
||||
}
|
||||
|
||||
[HttpPost("logout")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Logout(CancellationToken ct)
|
||||
[AllowAnonymous]
|
||||
public IActionResult Logout()
|
||||
{
|
||||
var token = Request.Headers.Authorization.ToString().Replace("Bearer ", "", StringComparison.OrdinalIgnoreCase);
|
||||
return ToActionResult(await service.LogoutAsync(token, ct));
|
||||
Response.Cookies.Delete("access-token", new CookieOptions
|
||||
{
|
||||
HttpOnly = true,
|
||||
SameSite = SameSiteMode.Lax,
|
||||
Path = "/",
|
||||
Secure = Request.IsHttps,
|
||||
});
|
||||
return Ok(new { success = true });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
namespace Distribution.Features.Auth;
|
||||
|
||||
public record LoginRequest(string Username, string Password);
|
||||
public record LoginRequest(
|
||||
string Identifier,
|
||||
string Password,
|
||||
Guid? UserTypeId = null,
|
||||
string DeviceName = "Unknown Device");
|
||||
|
||||
public record RegisterRequest(string Username, string Password, string? FullName);
|
||||
public record RegisterRequest(string Username, string email, string Password, string? FullName);
|
||||
|
||||
public record MeResponse(
|
||||
Guid UserId,
|
||||
string Username,
|
||||
Guid RoleId,
|
||||
string RoleCode,
|
||||
int RoleLevel);
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Net.Http.Json;
|
||||
|
||||
namespace Distribution.Features.Auth;
|
||||
|
||||
/// <summary>
|
||||
/// Proxies own-user auth calls (login/register/getUserDetails/logout) to the external AuthHex
|
||||
/// identity service. AuthHex owns credentials and issues the RS256-signed JWT this app validates;
|
||||
/// this app never sees or stores a plaintext/hashed password.
|
||||
/// </summary>
|
||||
public class AuthHexClient(HttpClient http) : IAuthHexClient
|
||||
{
|
||||
public async Task<AuthHexResponse> CallAsync(string functionName, object? payload, string? reference = null, CancellationToken ct = default)
|
||||
{
|
||||
var body = new { functionName, payload, reference };
|
||||
var response = await http.PostAsJsonAsync("/api/user", body, ct);
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<AuthHexResponse>(cancellationToken: ct);
|
||||
return result ?? new AuthHexResponse((int)response.StatusCode, false, "Empty response from AuthHex.", null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Distribution.Features.Auth;
|
||||
|
||||
/// <summary>Raw response shape returned by AuthHex's `/api/user` endpoint for every functionName.</summary>
|
||||
public record AuthHexResponse(
|
||||
[property: JsonPropertyName("statusCode")] int StatusCode,
|
||||
[property: JsonPropertyName("success")] bool Success,
|
||||
[property: JsonPropertyName("message")] string? Message,
|
||||
[property: JsonPropertyName("data")] JsonElement? Data);
|
||||
@@ -1,17 +1,21 @@
|
||||
using Distribution.Infra.Authorization;
|
||||
|
||||
namespace Distribution.Features.Auth;
|
||||
|
||||
/// <summary>
|
||||
/// Own-user auth is fully delegated to AuthHex — this service only proxies the four functions
|
||||
/// AuthHex exposes (loginUser/registerUser/getUserDetails/LogoutUser). No local password check or
|
||||
/// token issuance happens here; AuthHex owns credentials and signs the JWT this app validates
|
||||
/// (see Program.cs's JwtBearer registration + OnTokenValidated role mapping).
|
||||
/// token issuance happens here; AuthHex owns credentials and signs the JWT this app validates.
|
||||
/// </summary>
|
||||
public class AuthService(IAuthHexClient authHex)
|
||||
{
|
||||
public Task<AuthHexResponse> LoginAsync(LoginRequest request, CancellationToken ct = default) =>
|
||||
authHex.CallAsync("loginUser", request, reference: Guid.NewGuid().ToString(), ct: ct);
|
||||
authHex.CallAsync("loginUser", new
|
||||
{
|
||||
identifier = request.Identifier,
|
||||
password = request.Password,
|
||||
userTypeId = request.UserTypeId,
|
||||
deviceName = request.DeviceName
|
||||
}, reference: Guid.NewGuid().ToString(), ct: ct);
|
||||
|
||||
public Task<AuthHexResponse> RegisterAsync(RegisterRequest request, CancellationToken ct = default) =>
|
||||
authHex.CallAsync("registerUser", request, reference: Guid.NewGuid().ToString(), ct: ct);
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
namespace Distribution.Features.Auth;
|
||||
|
||||
/// <summary>
|
||||
/// Typed HttpClient that proxies requests to the external Auth Service.
|
||||
/// Registered via AddHttpClient<AuthServiceClient> in Program.cs.
|
||||
/// BaseAddress is configured from AuthService:BaseUrl in appsettings.json.
|
||||
///
|
||||
/// TODO: Implement methods once Auth Service contract is agreed.
|
||||
/// </summary>
|
||||
public class AuthServiceClient(HttpClient http) : IAuthServiceClient
|
||||
{
|
||||
// TODO: implement proxy methods, e.g.:
|
||||
//
|
||||
// public async Task<AuthServiceResponse?> LoginAsync(LoginRequest request, CancellationToken ct)
|
||||
// {
|
||||
// var response = await http.PostAsJsonAsync("/TODO_AUTH_SERVICE_LOGIN_PATH", request, ct);
|
||||
// if (!response.IsSuccessStatusCode) return null;
|
||||
// return await response.Content.ReadFromJsonAsync<AuthServiceResponse>(ct);
|
||||
// }
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
namespace Distribution.Features.Auth;
|
||||
|
||||
public class AuthServiceOptions
|
||||
{
|
||||
public const string SectionName = "AuthService";
|
||||
|
||||
/// <summary>Base URL of the external Auth Service, e.g. "https://auth.internal".</summary>
|
||||
public string BaseUrl { get; set; } = string.Empty;
|
||||
|
||||
// TODO: add other config (API keys, timeout, etc.) once Auth Service is defined.
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Distribution.Features.Auth;
|
||||
|
||||
public interface IAuthHexClient
|
||||
{
|
||||
Task<AuthHexResponse> CallAsync(string functionName, object? payload, string? reference = null, CancellationToken ct = default);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
namespace Distribution.Features.Auth;
|
||||
|
||||
/// <summary>
|
||||
/// Connector to the external Auth Service. This backend proxies all auth
|
||||
/// traffic from the Next.js frontend through to the Auth Service — the
|
||||
/// frontend never calls the Auth Service directly.
|
||||
///
|
||||
/// TODO: Fill in the method signatures once the Auth Service contract is agreed.
|
||||
/// </summary>
|
||||
public interface IAuthServiceClient
|
||||
{
|
||||
// TODO: define methods based on Auth Service contract, e.g.:
|
||||
// Task<AuthServiceResponse?> LoginAsync(LoginRequest request, CancellationToken ct);
|
||||
// Task<AuthServiceResponse?> RefreshAsync(string refreshToken, CancellationToken ct);
|
||||
// Task LogoutAsync(string token, CancellationToken ct);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Claims;
|
||||
@@ -7,10 +8,12 @@ namespace Distribution.Features.Claims;
|
||||
public class ClaimController(ClaimService service, ICurrentUserService currentUser) : ControllerBase
|
||||
{
|
||||
[HttpGet("api/claims")]
|
||||
[Authorize("claim.view")]
|
||||
public async Task<IActionResult> GetAll() =>
|
||||
Ok(await service.GetAllAsync());
|
||||
|
||||
[HttpGet("api/claims/{id:guid}")]
|
||||
[Authorize("claim.view")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var result = await service.GetByIdAsync(id);
|
||||
@@ -18,6 +21,7 @@ public class ClaimController(ClaimService service, ICurrentUserService currentUs
|
||||
}
|
||||
|
||||
[HttpPost("api/claims")]
|
||||
[Authorize("claim.manage")]
|
||||
public async Task<IActionResult> Create(CreateClaimRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
@@ -29,6 +33,7 @@ public class ClaimController(ClaimService service, ICurrentUserService currentUs
|
||||
}
|
||||
|
||||
[HttpPut("api/claims/{id:guid}")]
|
||||
[Authorize("claim.manage")]
|
||||
public async Task<IActionResult> Update(Guid id, UpdateClaimRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
@@ -40,6 +45,7 @@ public class ClaimController(ClaimService service, ICurrentUserService currentUs
|
||||
}
|
||||
|
||||
[HttpPost("api/claims/{id:guid}/settle")]
|
||||
[Authorize("claim.manage")]
|
||||
public async Task<IActionResult> Settle(Guid id, SettleClaimRequest request, CancellationToken ct)
|
||||
{
|
||||
var userId = currentUser.UserId ?? request.SettledByUserId;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Distribution.Models;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.DeliveryRide;
|
||||
@@ -9,6 +9,7 @@ namespace Distribution.Features.DeliveryRide;
|
||||
public class DeliveryTaskController(DeliveryTaskService service, ICurrentUserService currentUser) : ControllerBase
|
||||
{
|
||||
[HttpPost("rides/{rideId:guid}/tasks/generate")]
|
||||
[Authorize("delivery-task.manage")]
|
||||
public async Task<IActionResult> Generate(Guid rideId, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
@@ -22,6 +23,7 @@ public class DeliveryTaskController(DeliveryTaskService service, ICurrentUserSer
|
||||
|
||||
[HttpGet("rides/{rideId:guid}/tasks")]
|
||||
[HttpGet("rides/{rideId:guid}/delivery-tasks")]
|
||||
[Authorize("delivery-task.view")]
|
||||
public async Task<IActionResult> GetByRide(Guid rideId)
|
||||
{
|
||||
var result = await service.GetByRideIdAsync(rideId);
|
||||
@@ -29,6 +31,7 @@ public class DeliveryTaskController(DeliveryTaskService service, ICurrentUserSer
|
||||
}
|
||||
|
||||
[HttpGet("delivery-tasks")]
|
||||
//[Authorize("delivery-task.view")]
|
||||
public async Task<IActionResult> GetAll(
|
||||
[FromQuery] DeliveryTaskStatus? status,
|
||||
[FromQuery] int page = 1,
|
||||
@@ -39,6 +42,7 @@ public class DeliveryTaskController(DeliveryTaskService service, ICurrentUserSer
|
||||
}
|
||||
|
||||
[HttpGet("delivery-tasks/{id:guid}")]
|
||||
[Authorize("delivery-task.view")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var result = await service.GetByIdAsync(id);
|
||||
@@ -46,6 +50,7 @@ public class DeliveryTaskController(DeliveryTaskService service, ICurrentUserSer
|
||||
}
|
||||
|
||||
[HttpPut("delivery-tasks/{id:guid}/quantities")]
|
||||
[Authorize("delivery-task.manage")]
|
||||
public async Task<IActionResult> SetQuantities(Guid id, SetQuantitiesRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
@@ -58,6 +63,7 @@ public class DeliveryTaskController(DeliveryTaskService service, ICurrentUserSer
|
||||
}
|
||||
|
||||
[HttpPost("delivery-tasks/{id:guid}/confirm")]
|
||||
[Authorize("delivery-task.manage")]
|
||||
public async Task<IActionResult> Confirm(Guid id, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
@@ -70,6 +76,7 @@ public class DeliveryTaskController(DeliveryTaskService service, ICurrentUserSer
|
||||
}
|
||||
|
||||
[HttpPost("delivery-tasks/{id:guid}/fail")]
|
||||
[Authorize("delivery-task.manage")]
|
||||
public async Task<IActionResult> Fail(Guid id, FailTaskRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Distribution.Features.DriverComplianceConfig;
|
||||
using Distribution.Features.DriverComplianceConfig;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.DriverProfile;
|
||||
@@ -12,6 +13,7 @@ public class DriverProfileController(
|
||||
ICurrentUserService currentUser) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
[Authorize("driver.view")]
|
||||
public async Task<IActionResult> GetAll([FromQuery] int page = 1, [FromQuery] int pageSize = 20)
|
||||
{
|
||||
var result = await service.GetAllAsync(page, pageSize);
|
||||
@@ -19,6 +21,7 @@ public class DriverProfileController(
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
[Authorize("driver.view")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var result = await service.GetByIdAsync(id);
|
||||
@@ -26,6 +29,7 @@ public class DriverProfileController(
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize("driver.manage")]
|
||||
public async Task<IActionResult> Create(CreateDriverProfileRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
@@ -44,6 +48,7 @@ public class DriverProfileController(
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
[Authorize("driver.manage")]
|
||||
public async Task<IActionResult> Update(Guid id, UpdateDriverProfileRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.UpdateAsync(id, request, ct);
|
||||
@@ -51,6 +56,7 @@ public class DriverProfileController(
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
[Authorize("driver.manage")]
|
||||
public async Task<IActionResult> Deactivate(Guid id, CancellationToken ct)
|
||||
{
|
||||
var success = await service.DeactivateAsync(id, ct);
|
||||
@@ -58,6 +64,7 @@ public class DriverProfileController(
|
||||
}
|
||||
|
||||
[HttpGet("compliance")]
|
||||
[Authorize("driver.view")]
|
||||
public async Task<IActionResult> GetCompliance()
|
||||
{
|
||||
var result = await complianceService.GetAsync();
|
||||
@@ -65,6 +72,7 @@ public class DriverProfileController(
|
||||
}
|
||||
|
||||
[HttpPut("compliance")]
|
||||
[Authorize("driver.manage")]
|
||||
public async Task<IActionResult> UpsertCompliance(UpdateDriverComplianceConfigRequest request, CancellationToken ct)
|
||||
{
|
||||
var userId = currentUser.UserId ?? throw new UnauthorizedAccessException();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Grn;
|
||||
@@ -7,6 +8,7 @@ namespace Distribution.Features.Grn;
|
||||
public class GrnController(GrnService service, ICurrentUserService currentUser) : ControllerBase
|
||||
{
|
||||
[HttpGet("api/goods-receipts")]
|
||||
[Authorize("grn.view")]
|
||||
public async Task<IActionResult> GetAll(
|
||||
[FromQuery] Guid? purchaseOrderId,
|
||||
[FromQuery] int page = 1,
|
||||
@@ -17,6 +19,7 @@ public class GrnController(GrnService service, ICurrentUserService currentUser)
|
||||
}
|
||||
|
||||
[HttpGet("api/goods-receipts/{id:guid}")]
|
||||
[Authorize("grn.view")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var result = await service.GetByIdAsync(id);
|
||||
@@ -24,6 +27,7 @@ public class GrnController(GrnService service, ICurrentUserService currentUser)
|
||||
}
|
||||
|
||||
[HttpPost("api/goods-receipts")]
|
||||
[Authorize("grn.manage")]
|
||||
public async Task<IActionResult> CreateFlat(CreateFlatGrnRequest request, CancellationToken ct)
|
||||
{
|
||||
// TEMPORARY: currentUser.UserId is always unset due to the broken auth middleware
|
||||
@@ -42,6 +46,7 @@ public class GrnController(GrnService service, ICurrentUserService currentUser)
|
||||
|
||||
/// <summary>Goods received without a purchase order.</summary>
|
||||
[HttpPost("api/goods-receipts/direct")]
|
||||
[Authorize("grn.manage")]
|
||||
public async Task<IActionResult> CreateDirect(CreateDirectGrnRequest request, CancellationToken ct)
|
||||
{
|
||||
var userId = currentUser.UserId ?? request.ReceivedByUserId;
|
||||
@@ -55,10 +60,12 @@ public class GrnController(GrnService service, ICurrentUserService currentUser)
|
||||
}
|
||||
|
||||
[HttpGet("api/purchase-orders/{id:guid}/goods-receipts")]
|
||||
[Authorize("grn.view")]
|
||||
public async Task<IActionResult> GetByPurchaseOrder(Guid id) =>
|
||||
Ok(await service.GetByPurchaseOrderIdAsync(id));
|
||||
|
||||
[HttpPost("api/purchase-orders/{id:guid}/goods-receipts")]
|
||||
[Authorize("grn.manage")]
|
||||
public async Task<IActionResult> Create(Guid id, CreateGrnRequest request, CancellationToken ct)
|
||||
{
|
||||
var userId = currentUser.UserId ?? request.ReceivedByUserId;
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
using Distribution.Models;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.LoadingEvent;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/loading-sheets")]
|
||||
public class LoadingSheetController(LoadingSheetService service, ICurrentUserService currentUser) : ControllerBase
|
||||
public class LoadingSheetController(
|
||||
LoadingSheetService service,
|
||||
IScopeAccessService scopeAccess,
|
||||
ICurrentUserService currentUser) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
//[Authorize("loading-sheet.view")]
|
||||
public async Task<IActionResult> GetAll(
|
||||
[FromQuery] Guid? rideId,
|
||||
[FromQuery] LoadingSheetStatus? status,
|
||||
@@ -17,11 +22,26 @@ public class LoadingSheetController(LoadingSheetService service, ICurrentUserSer
|
||||
[FromQuery] int page = 1,
|
||||
[FromQuery] int pageSize = 20)
|
||||
{
|
||||
if (routeId.HasValue)
|
||||
{
|
||||
var userId = currentUser.UserId;
|
||||
if (userId.HasValue && !await scopeAccess.HasRouteScopeAsync(userId.Value, routeId.Value))
|
||||
return StatusCode(403);
|
||||
}
|
||||
|
||||
if (regionId.HasValue)
|
||||
{
|
||||
var userId = currentUser.UserId;
|
||||
if (userId.HasValue && !await scopeAccess.HasRegionScopeAsync(userId.Value, regionId.Value))
|
||||
return StatusCode(403);
|
||||
}
|
||||
|
||||
var result = await service.GetAllAsync(rideId, status, routeId, regionId, page, pageSize);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
[Authorize("loading-sheet.view")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var result = await service.GetByIdAsync(id);
|
||||
@@ -29,6 +49,7 @@ public class LoadingSheetController(LoadingSheetService service, ICurrentUserSer
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize("loading-sheet.manage")]
|
||||
public async Task<IActionResult> Create(CreateLoadingSheetRequest request, CancellationToken ct)
|
||||
{
|
||||
// TEMPORARY: currentUser.UserId is always unset due to the broken auth middleware
|
||||
@@ -45,6 +66,7 @@ public class LoadingSheetController(LoadingSheetService service, ICurrentUserSer
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}/items")]
|
||||
[Authorize("loading-sheet.manage")]
|
||||
public async Task<IActionResult> SetItems(Guid id, SetItemsRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
@@ -57,6 +79,7 @@ public class LoadingSheetController(LoadingSheetService service, ICurrentUserSer
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/confirm")]
|
||||
[Authorize("loading-sheet.manage")]
|
||||
public async Task<IActionResult> Confirm(Guid id, ConfirmLoadingSheetRequest request, CancellationToken ct)
|
||||
{
|
||||
// TEMPORARY: currentUser.UserId is always unset due to the broken auth middleware
|
||||
|
||||
@@ -1,16 +1,31 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.PriceList;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/price-lists")]
|
||||
public class PriceListController(PriceListService service) : ControllerBase
|
||||
public class PriceListController(
|
||||
PriceListService service,
|
||||
IScopeAccessService scopeAccess,
|
||||
ICurrentUserService currentUser) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetAll([FromQuery] Guid? regionId) =>
|
||||
Ok(await service.GetAllAsync(regionId));
|
||||
[Authorize("price-list.view")]
|
||||
public async Task<IActionResult> GetAll([FromQuery] Guid? regionId)
|
||||
{
|
||||
if (regionId.HasValue)
|
||||
{
|
||||
var userId = currentUser.UserId;
|
||||
if (userId.HasValue && !await scopeAccess.HasRegionScopeAsync(userId.Value, regionId.Value))
|
||||
return StatusCode(403);
|
||||
}
|
||||
return Ok(await service.GetAllAsync(regionId));
|
||||
}
|
||||
|
||||
[HttpGet("resolve")]
|
||||
[Authorize("price-list.view")]
|
||||
public async Task<IActionResult> Resolve([FromQuery] Guid itemId, [FromQuery] Guid storeId, [FromQuery] DateTime saleDate)
|
||||
{
|
||||
var result = await service.ResolveByStoreAsync(itemId, storeId, saleDate);
|
||||
@@ -18,6 +33,7 @@ public class PriceListController(PriceListService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
[Authorize("price-list.view")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var result = await service.GetByIdAsync(id);
|
||||
@@ -25,6 +41,7 @@ public class PriceListController(PriceListService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize("price-list.manage")]
|
||||
public async Task<IActionResult> Create(CreatePriceListRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.CreateAsync(request, ct);
|
||||
@@ -32,6 +49,7 @@ public class PriceListController(PriceListService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
[Authorize("price-list.manage")]
|
||||
public async Task<IActionResult> Update(Guid id, UpdatePriceListRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
@@ -46,6 +64,7 @@ public class PriceListController(PriceListService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
[Authorize("price-list.manage")]
|
||||
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
||||
{
|
||||
var deleted = await service.DeleteAsync(id, ct);
|
||||
@@ -53,6 +72,7 @@ public class PriceListController(PriceListService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}/items/{sku}")]
|
||||
[Authorize("price-list.manage")]
|
||||
public async Task<IActionResult> UpsertItem(Guid id, string sku, UpsertPriceListItemRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
@@ -67,6 +87,7 @@ public class PriceListController(PriceListService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}/items/{sku}")]
|
||||
[Authorize("price-list.manage")]
|
||||
public async Task<IActionResult> RemoveItem(Guid id, string sku, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Po;
|
||||
@@ -9,10 +10,12 @@ namespace Distribution.Features.Po;
|
||||
public class PoController(PoService service, ICurrentUserService currentUser) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
[Authorize("purchase-order.view")]
|
||||
public async Task<IActionResult> GetAll([FromQuery] Guid? supplierId, [FromQuery] PurchaseOrderStatus? status) =>
|
||||
Ok(await service.GetAllAsync(supplierId, status));
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
[Authorize("purchase-order.view")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var result = await service.GetByIdAsync(id);
|
||||
@@ -20,6 +23,7 @@ public class PoController(PoService service, ICurrentUserService currentUser) :
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize("purchase-order.manage")]
|
||||
public async Task<IActionResult> Create(CreatePoRequest request, CancellationToken ct)
|
||||
{
|
||||
// TEMPORARY: currentUser.UserId is always unset due to the broken auth middleware
|
||||
@@ -35,6 +39,7 @@ public class PoController(PoService service, ICurrentUserService currentUser) :
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
[Authorize("purchase-order.manage")]
|
||||
public async Task<IActionResult> Update(Guid id, UpdatePoRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
@@ -46,6 +51,7 @@ public class PoController(PoService service, ICurrentUserService currentUser) :
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/close")]
|
||||
[Authorize("purchase-order.manage")]
|
||||
public async Task<IActionResult> Close(Guid id, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
@@ -57,6 +63,7 @@ public class PoController(PoService service, ICurrentUserService currentUser) :
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/cancel")]
|
||||
[Authorize("purchase-order.manage")]
|
||||
public async Task<IActionResult> Cancel(Guid id, CancelPoRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
@@ -68,6 +75,7 @@ public class PoController(PoService service, ICurrentUserService currentUser) :
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/items/{itemId:guid}/cancel")]
|
||||
[Authorize("purchase-order.manage")]
|
||||
public async Task<IActionResult> CancelLine(Guid id, Guid itemId, CancelPoLineRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
@@ -79,6 +87,7 @@ public class PoController(PoService service, ICurrentUserService currentUser) :
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/match-invoice")]
|
||||
[Authorize("purchase-order.view")]
|
||||
public async Task<IActionResult> MatchInvoice(Guid id, [FromQuery] Guid? invoiceId, [FromQuery] Guid? grnId)
|
||||
{
|
||||
var result = await service.MatchInvoiceToPoAsync(id, invoiceId, grnId);
|
||||
@@ -86,6 +95,7 @@ public class PoController(PoService service, ICurrentUserService currentUser) :
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/resolve-variance")]
|
||||
[Authorize("purchase-order.manage")]
|
||||
public async Task<IActionResult> ResolveVariance(Guid id, ResolveInvoiceVarianceRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.PurchaseReturn;
|
||||
@@ -7,6 +8,7 @@ namespace Distribution.Features.PurchaseReturn;
|
||||
public class PurchaseReturnController(PurchaseReturnService service, ICurrentUserService currentUser) : ControllerBase
|
||||
{
|
||||
[HttpGet("api/purchase-returns/{id:guid}")]
|
||||
[Authorize("purchase-order.view")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var result = await service.GetByIdAsync(id);
|
||||
@@ -14,10 +16,12 @@ public class PurchaseReturnController(PurchaseReturnService service, ICurrentUse
|
||||
}
|
||||
|
||||
[HttpGet("api/purchase-orders/{id:guid}/returns")]
|
||||
[Authorize("purchase-order.view")]
|
||||
public async Task<IActionResult> GetByPurchaseOrder(Guid id) =>
|
||||
Ok(await service.GetByPurchaseOrderIdAsync(id));
|
||||
|
||||
[HttpPost("api/purchase-orders/{id:guid}/returns")]
|
||||
[Authorize("purchase-return.manage")]
|
||||
public async Task<IActionResult> Create(Guid id, CreatePurchaseReturnRequest request, CancellationToken ct)
|
||||
{
|
||||
// TEMPORARY: currentUser.UserId is always unset due to the broken auth middleware
|
||||
|
||||
@@ -1,28 +1,39 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Region;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/regions")]
|
||||
public class RegionController(RegionService service) : ControllerBase
|
||||
public class RegionController(
|
||||
RegionService service,
|
||||
IScopeAccessService scopeAccess,
|
||||
ICurrentUserService currentUser) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
[Authorize("region.view")]
|
||||
public async Task<IActionResult> GetAll() =>
|
||||
Ok(await service.GetAllAsync());
|
||||
|
||||
// Suggested code for the Add Region form; the user may override it.
|
||||
[HttpGet("next-code")]
|
||||
[Authorize("region.view")]
|
||||
public async Task<IActionResult> GetNextCode() =>
|
||||
Ok(await service.GetNextCodeAsync());
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
[Authorize("region.view")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var userId = currentUser.UserId;
|
||||
if (userId.HasValue && !await scopeAccess.HasRegionScopeAsync(userId.Value, id))
|
||||
return StatusCode(403);
|
||||
var result = await service.GetByIdAsync(id);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize("region.manage")]
|
||||
public async Task<IActionResult> Create(CreateRegionRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.CreateAsync(request, ct);
|
||||
@@ -30,6 +41,7 @@ public class RegionController(RegionService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
[Authorize("region.manage")]
|
||||
public async Task<IActionResult> Update(Guid id, UpdateRegionRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.UpdateAsync(id, request, ct);
|
||||
@@ -37,6 +49,7 @@ public class RegionController(RegionService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
[Authorize("region.manage")]
|
||||
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
||||
{
|
||||
var deleted = await service.DeleteAsync(id, ct);
|
||||
@@ -44,6 +57,7 @@ public class RegionController(RegionService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/manager")]
|
||||
[Authorize("region.view")]
|
||||
public async Task<IActionResult> GetManager(Guid id)
|
||||
{
|
||||
var result = await service.GetManagerAsync(id);
|
||||
@@ -51,6 +65,7 @@ public class RegionController(RegionService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}/manager")]
|
||||
[Authorize("region.manage")]
|
||||
public async Task<IActionResult> SetManager(Guid id, SetRegionManagerRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
@@ -65,14 +80,15 @@ public class RegionController(RegionService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}/manager")]
|
||||
[Authorize("region.manage")]
|
||||
public async Task<IActionResult> RemoveManager(Guid id, CancellationToken ct)
|
||||
{
|
||||
var removed = await service.RemoveManagerAsync(id, ct);
|
||||
return removed ? NoContent() : NotFound();
|
||||
}
|
||||
|
||||
//targets/budgets/agencies/tax-rule.
|
||||
[HttpPut("{id:guid}/manage")]
|
||||
[Authorize("region.manage")]
|
||||
public async Task<IActionResult> Manage(Guid id, ManageRegionRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.ManageAsync(id, request, ct);
|
||||
@@ -80,6 +96,7 @@ public class RegionController(RegionService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/hierarchy")]
|
||||
[Authorize("region.view")]
|
||||
public async Task<IActionResult> GetHierarchy(Guid id)
|
||||
{
|
||||
var result = await service.GetHierarchyAsync(id);
|
||||
@@ -87,18 +104,20 @@ public class RegionController(RegionService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/dashboard")]
|
||||
[Authorize("region.view")]
|
||||
public async Task<IActionResult> GetDashboard(Guid id, [FromQuery] string period)
|
||||
{
|
||||
var result = await service.GetDashboardAsync(id, period);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
// Flat CRUD-list view over targets/budgets/tax-rules/agency-maps across all regions.
|
||||
[HttpGet("manage-entries")]
|
||||
[Authorize("region.view")]
|
||||
public async Task<IActionResult> GetManageEntries() =>
|
||||
Ok(await service.GetManageEntriesAsync());
|
||||
|
||||
[HttpDelete("manage-entries/{entryType}/{id:guid}")]
|
||||
[Authorize("region.manage")]
|
||||
public async Task<IActionResult> DeleteManageEntry(RegionManageEntryType entryType, Guid id, CancellationToken ct)
|
||||
{
|
||||
var deleted = await service.DeleteManageEntryAsync(entryType, id, ct);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Distribution.Infra.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Returns;
|
||||
@@ -7,10 +8,12 @@ namespace Distribution.Features.Returns;
|
||||
public class ReturnController(ReturnService service, ICurrentUserService currentUser) : ControllerBase
|
||||
{
|
||||
[HttpGet("api/returns")]
|
||||
[Authorize("return.view")]
|
||||
public async Task<IActionResult> GetAll() =>
|
||||
Ok(await service.GetAllAsync());
|
||||
|
||||
[HttpGet("api/returns/{id:guid}")]
|
||||
[Authorize("return.view")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var result = await service.GetByIdAsync(id);
|
||||
@@ -18,6 +21,7 @@ public class ReturnController(ReturnService service, ICurrentUserService current
|
||||
}
|
||||
|
||||
[HttpPatch("api/returns/{id:guid}/reasons")]
|
||||
[Authorize("return.manage")]
|
||||
public async Task<IActionResult> UpdateReasons(Guid id, UpdateSaleReturnReasonsRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
@@ -29,10 +33,12 @@ public class ReturnController(ReturnService service, ICurrentUserService current
|
||||
}
|
||||
|
||||
[HttpGet("api/shop-pos/{id:guid}/returns")]
|
||||
[Authorize("return.view")]
|
||||
public async Task<IActionResult> GetByShopPO(Guid id) =>
|
||||
Ok(await service.GetByShopPOIdAsync(id));
|
||||
|
||||
[HttpPost("api/shop-pos/{id:guid}/returns")]
|
||||
[Authorize("return.manage")]
|
||||
public async Task<IActionResult> Create(Guid id, CreateSaleReturnRequest request, CancellationToken ct)
|
||||
{
|
||||
var userId = currentUser.UserId ?? request.CreatedByUserId;
|
||||
|
||||
@@ -5,4 +5,8 @@ public interface IRoleRepository
|
||||
Task<Models.Role?> GetByIdAsync(Guid id);
|
||||
Task<List<Models.Role>> GetAllAsync();
|
||||
Task<List<Models.RolePermission>> GetPermissionsAsync(Guid roleId);
|
||||
Task AddAsync(Models.Role role);
|
||||
void Update(Models.Role role);
|
||||
void Remove(Models.Role role);
|
||||
Task ReplacePermissionsAsync(Guid roleId, List<Guid> permissionIds);
|
||||
}
|
||||
|
||||
@@ -23,4 +23,32 @@ public class RoleController(RoleService service) : ControllerBase
|
||||
var result = await service.GetPermissionsAsync(id);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Create(CreateRoleRequest 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, UpdateRoleRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.UpdateAsync(id, request, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}/permissions")]
|
||||
public async Task<IActionResult> AssignPermissions(Guid id, AssignRolePermissionsRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.AssignPermissionsAsync(id, request, ct);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
public async Task<IActionResult> Deactivate(Guid id, CancellationToken ct)
|
||||
{
|
||||
var deactivated = await service.DeactivateAsync(id, ct);
|
||||
return deactivated ? NoContent() : NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
namespace Distribution.Features.Role;
|
||||
|
||||
public record RoleResponse(Guid Id, string Name, int Level, bool IsActive, DateTime CreatedAt);
|
||||
|
||||
public record RolePermissionResponse(Guid PermissionId, string Code, string Description);
|
||||
public record CreateRoleRequest(string Name, int Level);
|
||||
public record UpdateRoleRequest(string Name, int Level);
|
||||
public record AssignRolePermissionsRequest(List<Guid> PermissionIds);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Distribution.Data;
|
||||
using Distribution.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Distribution.Features.Role;
|
||||
@@ -16,4 +17,29 @@ public class RoleRepository(AppDBContext context) : IRoleRepository
|
||||
.Include(rp => rp.Permission)
|
||||
.Where(rp => rp.FK_RoleId == roleId && rp.IsActive)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task AddAsync(Models.Role role) =>
|
||||
await context.Roles.AddAsync(role);
|
||||
|
||||
public void Update(Models.Role role) =>
|
||||
context.Roles.Update(role);
|
||||
|
||||
public void Remove(Models.Role role)
|
||||
{
|
||||
role.IsActive = false;
|
||||
context.Roles.Update(role);
|
||||
}
|
||||
|
||||
public async Task ReplacePermissionsAsync(Guid roleId, List<Guid> permissionIds)
|
||||
{
|
||||
var existing = await context.RolePermissions
|
||||
.Where(rp => rp.FK_RoleId == roleId)
|
||||
.ToListAsync();
|
||||
context.RolePermissions.RemoveRange(existing);
|
||||
context.RolePermissions.AddRange(permissionIds.Select(pid => new RolePermission
|
||||
{
|
||||
FK_RoleId = roleId,
|
||||
FK_PermissionId = pid
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
using Distribution.Infra.UoW;
|
||||
|
||||
namespace Distribution.Features.Role;
|
||||
|
||||
public class RoleService(IRoleRepository repo)
|
||||
public class RoleService(IRoleRepository repo, IUnitOfWork uow)
|
||||
{
|
||||
private static RoleResponse ToResponse(Models.Role r) =>
|
||||
new(r.Id, r.Name, r.Level, r.IsActive, r.CreatedAt);
|
||||
|
||||
public async Task<List<RoleResponse>> GetAllAsync() =>
|
||||
(await repo.GetAllAsync())
|
||||
.Where(r => r.IsActive)
|
||||
.Select(r => new RoleResponse(r.Id, r.Name, r.Level, r.IsActive, r.CreatedAt))
|
||||
.Select(ToResponse)
|
||||
.ToList();
|
||||
|
||||
public async Task<RoleResponse?> GetByIdAsync(Guid id)
|
||||
{
|
||||
var role = await repo.GetByIdAsync(id);
|
||||
if (role is null || !role.IsActive) return null;
|
||||
return new RoleResponse(role.Id, role.Name, role.Level, role.IsActive, role.CreatedAt);
|
||||
return ToResponse(role);
|
||||
}
|
||||
|
||||
public async Task<List<RolePermissionResponse>?> GetPermissionsAsync(Guid roleId)
|
||||
@@ -23,4 +28,77 @@ public class RoleService(IRoleRepository repo)
|
||||
.Select(rp => new RolePermissionResponse(rp.FK_PermissionId, rp.Permission.Code, rp.Permission.Description))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public async Task<RoleResponse> CreateAsync(CreateRoleRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var role = new Models.Role { Name = request.Name, Level = request.Level };
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
await repo.AddAsync(role);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return ToResponse(role);
|
||||
}
|
||||
|
||||
public async Task<RoleResponse?> UpdateAsync(Guid id, UpdateRoleRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var role = await repo.GetByIdAsync(id);
|
||||
if (role is null || !role.IsActive) return null;
|
||||
role.Name = request.Name;
|
||||
role.Level = request.Level;
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
repo.Update(role);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return ToResponse(role);
|
||||
}
|
||||
|
||||
public async Task<bool> DeactivateAsync(Guid id, CancellationToken ct = default)
|
||||
{
|
||||
var role = await repo.GetByIdAsync(id);
|
||||
if (role is null || !role.IsActive) return false;
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
repo.Remove(role);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<List<RolePermissionResponse>?> AssignPermissionsAsync(Guid id, AssignRolePermissionsRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var role = await repo.GetByIdAsync(id);
|
||||
if (role is null || !role.IsActive) return null;
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
await repo.ReplacePermissionsAsync(id, request.PermissionIds);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw;
|
||||
}
|
||||
return await GetPermissionsAsync(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,34 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Route;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/routes")]
|
||||
public class RouteController(RouteService service) : ControllerBase
|
||||
public class RouteController(
|
||||
RouteService service,
|
||||
IScopeAccessService scopeAccess,
|
||||
ICurrentUserService currentUser) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
[Authorize("route.view")]
|
||||
public async Task<IActionResult> GetAll() =>
|
||||
Ok(await service.GetAllAsync());
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
[Authorize("route.view")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var userId = currentUser.UserId;
|
||||
if (userId.HasValue && !await scopeAccess.HasRouteScopeAsync(userId.Value, id))
|
||||
return StatusCode(403);
|
||||
var result = await service.GetByIdAsync(id);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize("route.manage")]
|
||||
public async Task<IActionResult> Create(CreateRouteRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.CreateAsync(request, ct);
|
||||
@@ -25,6 +36,7 @@ public class RouteController(RouteService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
[Authorize("route.manage")]
|
||||
public async Task<IActionResult> Update(Guid id, UpdateRouteRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.UpdateAsync(id, request, ct);
|
||||
@@ -32,6 +44,7 @@ public class RouteController(RouteService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}/region")]
|
||||
[Authorize("route.manage")]
|
||||
public async Task<IActionResult> AssignToRegion(Guid id, AssignRouteToRegionRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.AssignToRegionAsync(id, request, ct);
|
||||
@@ -39,6 +52,7 @@ public class RouteController(RouteService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
[Authorize("route.manage")]
|
||||
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
||||
{
|
||||
var deleted = await service.DeleteAsync(id, ct);
|
||||
@@ -46,6 +60,7 @@ public class RouteController(RouteService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/stores")]
|
||||
[Authorize("route.manage")]
|
||||
public async Task<IActionResult> AssignStores(Guid id, AssignStoresToRouteRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.AssignStoresToRouteAsync(id, request, ct);
|
||||
@@ -53,6 +68,7 @@ public class RouteController(RouteService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}/owner")]
|
||||
[Authorize("route.manage")]
|
||||
public async Task<IActionResult> AssignOwner(Guid id, AssignRouteOwnerRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.AssignOwnerAsync(id, request, ct);
|
||||
@@ -60,10 +76,12 @@ public class RouteController(RouteService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/optimize")]
|
||||
[Authorize("route.manage")]
|
||||
public IActionResult OptimizeSequence(Guid id) =>
|
||||
StatusCode(StatusCodes.Status501NotImplemented);
|
||||
|
||||
[HttpPost("{id:guid}/visits/check-in")]
|
||||
[Authorize("route.manage")]
|
||||
public async Task<IActionResult> CheckIn(Guid id, RecordCheckInRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
@@ -75,6 +93,7 @@ public class RouteController(RouteService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPost("{id:guid}/visits/check-out")]
|
||||
[Authorize("route.manage")]
|
||||
public async Task<IActionResult> CheckOut(Guid id, RecordCheckOutRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.RecordCheckOutAsync(id, request, ct);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Scheme;
|
||||
|
||||
@@ -7,10 +8,12 @@ namespace Distribution.Features.Scheme;
|
||||
public class SchemeController(SchemeService service) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
[Authorize("scheme.view")]
|
||||
public async Task<IActionResult> GetAll([FromQuery] Guid? regionId) =>
|
||||
Ok(await service.GetAllAsync(regionId));
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
[Authorize("scheme.view")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var result = await service.GetByIdAsync(id);
|
||||
@@ -18,6 +21,7 @@ public class SchemeController(SchemeService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize("scheme.manage")]
|
||||
public async Task<IActionResult> Create(CreateSchemeRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
@@ -32,6 +36,7 @@ public class SchemeController(SchemeService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
[Authorize("scheme.manage")]
|
||||
public async Task<IActionResult> Update(Guid id, UpdateSchemeRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
@@ -46,6 +51,7 @@ public class SchemeController(SchemeService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
[Authorize("scheme.manage")]
|
||||
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
||||
{
|
||||
var deleted = await service.DeleteAsync(id, ct);
|
||||
@@ -53,6 +59,7 @@ public class SchemeController(SchemeService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}/free-goods-rule")]
|
||||
[Authorize("scheme.manage")]
|
||||
public async Task<IActionResult> SetFreeGoodsRule(Guid id, SetFreeGoodsRuleRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
@@ -67,6 +74,7 @@ public class SchemeController(SchemeService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}/free-goods-rule")]
|
||||
[Authorize("scheme.manage")]
|
||||
public async Task<IActionResult> ClearFreeGoodsRule(Guid id, CancellationToken ct)
|
||||
{
|
||||
var result = await service.ClearFreeGoodsRuleAsync(id, ct);
|
||||
@@ -74,6 +82,7 @@ public class SchemeController(SchemeService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}/bundle-components/{itemId:guid}")]
|
||||
[Authorize("scheme.manage")]
|
||||
public async Task<IActionResult> UpsertBundleComponent(Guid id, Guid itemId, UpsertBundleComponentRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
@@ -88,6 +97,7 @@ public class SchemeController(SchemeService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}/bundle-components/{itemId:guid}")]
|
||||
[Authorize("scheme.manage")]
|
||||
public async Task<IActionResult> RemoveBundleComponent(Guid id, Guid itemId, CancellationToken ct)
|
||||
{
|
||||
var result = await service.RemoveBundleComponentAsync(id, itemId, ct);
|
||||
@@ -95,6 +105,7 @@ public class SchemeController(SchemeService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpGet("eligible")]
|
||||
[Authorize("scheme.view")]
|
||||
public async Task<IActionResult> GetEligible([FromQuery] Guid storeId, [FromQuery] Guid orderId)
|
||||
{
|
||||
var result = await service.EvaluateEligibilityAsync(storeId, orderId);
|
||||
@@ -102,6 +113,7 @@ public class SchemeController(SchemeService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/spend")]
|
||||
[Authorize("scheme.view")]
|
||||
public async Task<IActionResult> GetSpend(Guid id)
|
||||
{
|
||||
var result = await service.GetSpendAsync(id);
|
||||
@@ -109,6 +121,7 @@ public class SchemeController(SchemeService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/roi")]
|
||||
[Authorize("scheme.view")]
|
||||
public async Task<IActionResult> GetROI(Guid id)
|
||||
{
|
||||
var result = await service.GetROIAsync(id);
|
||||
@@ -121,6 +134,7 @@ public class SchemeController(SchemeService service) : ControllerBase
|
||||
public class SchemeOrderController(SchemeService service) : ControllerBase
|
||||
{
|
||||
[HttpPost("{id:guid}/apply-scheme")]
|
||||
[Authorize("scheme.manage")]
|
||||
public async Task<IActionResult> ApplyScheme(Guid id, ApplySchemeRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Stock;
|
||||
@@ -8,9 +9,10 @@ namespace Distribution.Features.Stock;
|
||||
[Route("api/stock")]
|
||||
public class StockController(StockService service, ICurrentUserService currentUser) : ControllerBase
|
||||
{
|
||||
// ── Query ──────────────────────────────────────────────────────────────
|
||||
// ── Query ──────────────────────────────────────────────────────────────────
|
||||
|
||||
[HttpGet("balances")]
|
||||
[Authorize("stock.view")]
|
||||
public async Task<IActionResult> GetBalances(
|
||||
[FromQuery] Guid? locationId,
|
||||
[FromQuery] LocationType? locationType,
|
||||
@@ -23,6 +25,7 @@ public class StockController(StockService service, ICurrentUserService currentUs
|
||||
}
|
||||
|
||||
[HttpGet("low-stock")]
|
||||
[Authorize("stock.view")]
|
||||
public async Task<IActionResult> GetLowStock([FromQuery] Guid? locationId)
|
||||
{
|
||||
var result = await service.GetLowStockAsync(locationId);
|
||||
@@ -30,6 +33,7 @@ public class StockController(StockService service, ICurrentUserService currentUs
|
||||
}
|
||||
|
||||
[HttpGet("balances/{itemId}/total")]
|
||||
[Authorize("stock.view")]
|
||||
public async Task<IActionResult> GetItemTotal(Guid itemId)
|
||||
{
|
||||
var result = await service.GetItemTotalAsync(itemId);
|
||||
@@ -39,6 +43,7 @@ public class StockController(StockService service, ICurrentUserService currentUs
|
||||
|
||||
/// <summary>Next free number for auto-generated serials (e.g. after ...-0005 the answer is 6).</summary>
|
||||
[HttpGet("serials/next-sequence")]
|
||||
[Authorize("stock.view")]
|
||||
public async Task<IActionResult> GetNextSerialSequence([FromQuery] Guid itemId, [FromQuery] string prefix = "")
|
||||
{
|
||||
var next = await service.GetNextSerialSequenceAsync(itemId, prefix);
|
||||
@@ -46,6 +51,7 @@ public class StockController(StockService service, ICurrentUserService currentUs
|
||||
}
|
||||
|
||||
[HttpGet("movements")]
|
||||
//[Authorize("stock.view")]
|
||||
public async Task<IActionResult> GetMovements(
|
||||
[FromQuery] Guid? locationId,
|
||||
[FromQuery] Guid? itemId,
|
||||
@@ -77,9 +83,10 @@ public class StockController(StockService service, ICurrentUserService currentUs
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
// ── Write ──────────────────────────────────────────────────────────────
|
||||
// ── Write ──────────────────────────────────────────────────────────────────
|
||||
|
||||
[HttpPost("transfer")]
|
||||
[Authorize("stock.manage")]
|
||||
public async Task<IActionResult> Transfer(TransferRequest request, CancellationToken ct)
|
||||
{
|
||||
if (currentUser.UserId is null) return Unauthorized();
|
||||
@@ -92,6 +99,7 @@ public class StockController(StockService service, ICurrentUserService currentUs
|
||||
}
|
||||
|
||||
[HttpPost("damage")]
|
||||
[Authorize("stock.manage")]
|
||||
public async Task<IActionResult> RecordDamage(DamageRequest request, CancellationToken ct)
|
||||
{
|
||||
if (currentUser.UserId is null) return Unauthorized();
|
||||
@@ -104,6 +112,7 @@ public class StockController(StockService service, ICurrentUserService currentUs
|
||||
}
|
||||
|
||||
[HttpPost("customer-return")]
|
||||
[Authorize("stock.manage")]
|
||||
public async Task<IActionResult> RecordCustomerReturn(CustomerReturnRequest request, CancellationToken ct)
|
||||
{
|
||||
if (currentUser.UserId is null) return Unauthorized();
|
||||
@@ -116,6 +125,7 @@ public class StockController(StockService service, ICurrentUserService currentUs
|
||||
}
|
||||
|
||||
[HttpPost("adjustment")]
|
||||
[Authorize("stock.manage")]
|
||||
public async Task<IActionResult> Adjust(AdjustmentRequest request, CancellationToken ct)
|
||||
{
|
||||
// TEMPORARY: currentUser.UserId is always unset due to the broken auth middleware
|
||||
@@ -131,6 +141,7 @@ public class StockController(StockService service, ICurrentUserService currentUs
|
||||
}
|
||||
|
||||
[HttpPatch("movements/{id}/reason")]
|
||||
[Authorize("stock.manage")]
|
||||
public async Task<IActionResult> UpdateMovementReason(Guid id, UpdateMovementReasonRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
using Distribution.Models;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.Store;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/stores")]
|
||||
public class StoreController(StoreService service) : ControllerBase
|
||||
public class StoreController(
|
||||
StoreService service,
|
||||
IScopeAccessService scopeAccess,
|
||||
ICurrentUserService currentUser) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
[Authorize("store.view")]
|
||||
public async Task<IActionResult> GetAll(
|
||||
[FromQuery] Guid? routeId,
|
||||
[FromQuery] string? search,
|
||||
@@ -16,13 +22,18 @@ public class StoreController(StoreService service) : ControllerBase
|
||||
Ok(await service.GetAllAsync(routeId, search, page, pageSize));
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
[Authorize("store.view")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var userId = currentUser.UserId;
|
||||
if (userId.HasValue && !await scopeAccess.HasStoreScopeAsync(userId.Value, id))
|
||||
return StatusCode(403);
|
||||
var result = await service.GetByIdAsync(id);
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize("store.manage")]
|
||||
public async Task<IActionResult> Create(CreateStoreRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.CreateAsync(request, ct);
|
||||
@@ -30,6 +41,7 @@ public class StoreController(StoreService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
[Authorize("store.manage")]
|
||||
public async Task<IActionResult> Update(Guid id, UpdateStoreRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.UpdateAsync(id, request, ct);
|
||||
@@ -37,6 +49,7 @@ public class StoreController(StoreService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
[Authorize("store.manage")]
|
||||
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
||||
{
|
||||
var deleted = await service.DeleteAsync(id, ct);
|
||||
@@ -44,6 +57,7 @@ public class StoreController(StoreService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}/credit-limit")]
|
||||
[Authorize("store.manage")]
|
||||
public async Task<IActionResult> UpdateCreditLimit(Guid id, UpdateCreditLimitRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.UpdateCreditLimitAsync(id, request, ct);
|
||||
@@ -51,6 +65,7 @@ public class StoreController(StoreService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}/outstanding-balance")]
|
||||
[Authorize("store.manage")]
|
||||
public async Task<IActionResult> UpdateOutstandingBalance(Guid id, UpdateOutstandingBalanceRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.UpdateOutstandingBalanceAsync(id, request, ct);
|
||||
@@ -58,6 +73,7 @@ public class StoreController(StoreService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/outstanding-balance")]
|
||||
[Authorize("store.view")]
|
||||
public async Task<IActionResult> GetOutstandingBalance(Guid id)
|
||||
{
|
||||
var result = await service.GetOutstandingBalanceAsync(id);
|
||||
@@ -65,6 +81,7 @@ public class StoreController(StoreService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}/segment")]
|
||||
[Authorize("store.manage")]
|
||||
public async Task<IActionResult> ClassifySegment(Guid id, ClassifySegmentRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.ClassifySegmentAsync(id, request, ct);
|
||||
@@ -72,6 +89,7 @@ public class StoreController(StoreService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/visit-history")]
|
||||
[Authorize("store.view")]
|
||||
public async Task<IActionResult> GetVisitHistory(Guid id)
|
||||
{
|
||||
var result = await service.GetVisitHistoryAsync(id);
|
||||
@@ -79,6 +97,7 @@ public class StoreController(StoreService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/schemes/{schemeId:guid}/eligibility")]
|
||||
[Authorize("store.view")]
|
||||
public async Task<IActionResult> CheckSchemeEligibility(Guid id, Guid schemeId)
|
||||
{
|
||||
var result = await service.CheckSchemeEligibilityAsync(id, schemeId);
|
||||
@@ -86,6 +105,7 @@ public class StoreController(StoreService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/dashboard")]
|
||||
[Authorize("store.view")]
|
||||
public async Task<IActionResult> GetDashboard(Guid id, [FromQuery] string period)
|
||||
{
|
||||
var result = await service.GetDashboardAsync(id, period);
|
||||
@@ -100,6 +120,7 @@ public class StoreController(StoreService service) : ControllerBase
|
||||
}
|
||||
|
||||
[HttpPatch("{id:guid}/status")]
|
||||
[Authorize("store.manage")]
|
||||
public async Task<IActionResult> PatchStatus(Guid id, PatchStoreStatusRequest request, CancellationToken ct)
|
||||
{
|
||||
var result = await service.SetActiveStatusAsync(id, request.IsActive, ct);
|
||||
|
||||
@@ -11,7 +11,7 @@ public class StoreService(IStoreRepository repo, IUnitOfWork uow, ICurrentUserSe
|
||||
private const int GlobalScopeRoleLevel = 50;
|
||||
private const int CreditApproverRoleLevel = 20;
|
||||
|
||||
private bool HasGlobalScope => currentUser.RoleLevel >= GlobalScopeRoleLevel;
|
||||
private async Task<bool> HasGlobalScopeAsync() => await currentUser.GetRoleLevelAsync() >= GlobalScopeRoleLevel;
|
||||
|
||||
private async Task<bool> HasStoreScopeAsync(Guid storeId)
|
||||
{
|
||||
@@ -122,7 +122,7 @@ public class StoreService(IStoreRepository repo, IUnitOfWork uow, ICurrentUserSe
|
||||
|
||||
public async Task<StoreResponse?> UpdateCreditLimitAsync(Guid id, UpdateCreditLimitRequest request, CancellationToken ct = default)
|
||||
{
|
||||
if (currentUser.RoleLevel < CreditApproverRoleLevel) return null;
|
||||
if (await currentUser.GetRoleLevelAsync() < CreditApproverRoleLevel) return null;
|
||||
var store = await repo.GetByIdAsync(id);
|
||||
if (store is null || !store.IsActive) return null;
|
||||
if (!await HasStoreScopeAsync(id)) return null;
|
||||
@@ -143,7 +143,7 @@ public class StoreService(IStoreRepository repo, IUnitOfWork uow, ICurrentUserSe
|
||||
|
||||
public async Task<StoreResponse?> UpdateOutstandingBalanceAsync(Guid id, UpdateOutstandingBalanceRequest request, CancellationToken ct = default)
|
||||
{
|
||||
if (currentUser.RoleLevel < CreditApproverRoleLevel) return null;
|
||||
if (await currentUser.GetRoleLevelAsync() < CreditApproverRoleLevel) return null;
|
||||
var store = await repo.GetByIdAsync(id);
|
||||
if (store is null || !store.IsActive) return null;
|
||||
if (!await HasStoreScopeAsync(id)) return null;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Infra.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.SupplierClaims;
|
||||
@@ -7,10 +8,12 @@ namespace Distribution.Features.SupplierClaims;
|
||||
public class SupplierClaimController(SupplierClaimService service, ICurrentUserService currentUser) : ControllerBase
|
||||
{
|
||||
[HttpGet("api/supplier-claims")]
|
||||
[Authorize("supplier-claim.view")]
|
||||
public async Task<IActionResult> GetAll() =>
|
||||
Ok(await service.GetAllAsync());
|
||||
|
||||
[HttpGet("api/supplier-claims/{id:guid}")]
|
||||
[Authorize("supplier-claim.view")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
var result = await service.GetByIdAsync(id);
|
||||
@@ -18,10 +21,12 @@ public class SupplierClaimController(SupplierClaimService service, ICurrentUserS
|
||||
}
|
||||
|
||||
[HttpGet("api/purchase-orders/{id:guid}/claims")]
|
||||
[Authorize("supplier-claim.view")]
|
||||
public async Task<IActionResult> GetByPurchaseOrder(Guid id) =>
|
||||
Ok(await service.GetByPurchaseOrderIdAsync(id));
|
||||
|
||||
[HttpPost("api/purchase-orders/{id:guid}/claims")]
|
||||
[Authorize("supplier-claim.manage")]
|
||||
public async Task<IActionResult> Create(Guid id, CreateSupplierClaimRequest request, CancellationToken ct)
|
||||
{
|
||||
// TEMPORARY: currentUser.UserId is always unset due to the broken auth middleware
|
||||
@@ -37,6 +42,7 @@ public class SupplierClaimController(SupplierClaimService service, ICurrentUserS
|
||||
}
|
||||
|
||||
[HttpPut("api/supplier-claims/{id:guid}")]
|
||||
[Authorize("supplier-claim.manage")]
|
||||
public async Task<IActionResult> Update(Guid id, UpdateSupplierClaimRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
@@ -48,6 +54,7 @@ public class SupplierClaimController(SupplierClaimService service, ICurrentUserS
|
||||
}
|
||||
|
||||
[HttpPost("api/supplier-claims/{id:guid}/approve")]
|
||||
[Authorize("supplier-claim.manage")]
|
||||
public async Task<IActionResult> Approve(Guid id, ApproveSupplierClaimRequest request, CancellationToken ct)
|
||||
{
|
||||
var userId = currentUser.UserId ?? request.ApprovedByUserId;
|
||||
@@ -62,6 +69,7 @@ public class SupplierClaimController(SupplierClaimService service, ICurrentUserS
|
||||
}
|
||||
|
||||
[HttpPost("api/supplier-claims/{id:guid}/reject")]
|
||||
[Authorize("supplier-claim.manage")]
|
||||
public async Task<IActionResult> Reject(Guid id, RejectSupplierClaimRequest request, CancellationToken ct)
|
||||
{
|
||||
var userId = currentUser.UserId ?? request.RejectedByUserId;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Distribution.Infra.Authorization;
|
||||
using Distribution.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Distribution.Features.SysConfig;
|
||||
@@ -9,11 +10,12 @@ namespace Distribution.Features.SysConfig;
|
||||
public class SysConfigController(SysConfigService service, ICurrentUserService currentUser) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
[Authorize("sys-config.view")]
|
||||
public async Task<IActionResult> GetAll() =>
|
||||
Ok(await service.GetAllAsync());
|
||||
|
||||
// Typed shortcut — must be declared before {key} to take precedence
|
||||
[HttpGet("invoice-creation-mode")]
|
||||
[Authorize("sys-config.view")]
|
||||
public async Task<IActionResult> GetInvoiceCreationMode()
|
||||
{
|
||||
var mode = await service.GetInvoiceCreationModeAsync();
|
||||
@@ -21,6 +23,7 @@ public class SysConfigController(SysConfigService service, ICurrentUserService c
|
||||
}
|
||||
|
||||
[HttpPut("invoice-creation-mode")]
|
||||
[Authorize("sys-config.manage")]
|
||||
public async Task<IActionResult> SetInvoiceCreationMode(SetInvoiceCreationModeRequest request, CancellationToken ct)
|
||||
{
|
||||
var userId = currentUser.UserId ?? throw new UnauthorizedAccessException();
|
||||
@@ -33,6 +36,7 @@ public class SysConfigController(SysConfigService service, ICurrentUserService c
|
||||
}
|
||||
|
||||
[HttpGet("{key}")]
|
||||
[Authorize("sys-config.view")]
|
||||
public async Task<IActionResult> GetByKey(string key)
|
||||
{
|
||||
var result = await service.GetByKeyAsync(key);
|
||||
@@ -40,6 +44,7 @@ public class SysConfigController(SysConfigService service, ICurrentUserService c
|
||||
}
|
||||
|
||||
[HttpPut("{key}")]
|
||||
[Authorize("sys-config.manage")]
|
||||
public async Task<IActionResult> Set(string key, SetSysConfigRequest request, CancellationToken ct)
|
||||
{
|
||||
var userId = currentUser.UserId ?? request.UserId;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Distribution.Features.User;
|
||||
|
||||
public record CreateUserRequest(
|
||||
string Username,
|
||||
[EmailAddress] string Username,
|
||||
string Password,
|
||||
string FullName,
|
||||
Guid RoleId,
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
using Distribution.Features.Auth;
|
||||
using Distribution.Infra.UoW;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Distribution.Features.User;
|
||||
|
||||
public class UserService(IUserRepository repo, IUnitOfWork uow)
|
||||
public class UserService(IUserRepository repo, IUnitOfWork uow, IAuthHexClient authHex)
|
||||
{
|
||||
private readonly PasswordHasher<Models.User> _hasher = new();
|
||||
|
||||
private static UserResponse ToResponse(Models.User u) =>
|
||||
new(u.Id, u.Username, u.FullName, u.FK_RoleId, u.Role?.Name ?? string.Empty,
|
||||
u.FK_ReportsToUserId, u.IsActive, u.CreatedAt, u.UpdatedAt);
|
||||
@@ -33,12 +31,30 @@ public class UserService(IUserRepository repo, IUnitOfWork uow)
|
||||
FK_RoleId = request.RoleId,
|
||||
FK_ReportsToUserId = request.ReportsToUserId
|
||||
};
|
||||
user.PasswordHash = _hasher.HashPassword(user, request.Password);
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
await repo.AddAsync(user);
|
||||
|
||||
var authResult = await authHex.CallAsync("registerUser", new
|
||||
{
|
||||
userId = user.Id,
|
||||
userName = request.Username,
|
||||
email = request.Username,
|
||||
fullname = request.FullName,
|
||||
password = request.Password,
|
||||
roleId = request.RoleId,
|
||||
userTypeId = "00000001-0000-0000-0000-200000000000",
|
||||
sendCredentialsEmail = false
|
||||
}, reference: user.Id.ToString(), ct: ct);
|
||||
|
||||
if (!authResult.Success)
|
||||
{
|
||||
await uow.RollbackAsync(ct);
|
||||
throw new InvalidOperationException($"AuthHex registration failed: {authResult.Message}");
|
||||
}
|
||||
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch
|
||||
|
||||
Reference in New Issue
Block a user