feat: Implement file management and authorization features
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 3m9s
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 3m9s
- Add RequiresPermissionAttribute for gRPC method access control. - Create IFileManagementService interface for file upload and management. - Implement AddProductImageCommand and handler for adding product images. - Implement CreateNewProductsCommand and handler for creating new products with image uploads. - Implement DeleteProductsCommand and handler for deleting products and their associations. - Implement RemoveProductImageCommand and handler for removing product images from galleries. - Implement UpdateProductsCommand and handler for updating product details and images. - Create GetProductGalleryQuery and handler for retrieving product galleries. - Implement PermissionService for role-based access control using JWT claims. - Implement FileManagementService for handling file uploads and image optimization. - Define gRPC service and messages for file management in fms.proto. - Add FluentValidation for request validation in various commands. - Create PermissionInterceptor for enforcing permissions on gRPC methods.
This commit is contained in:
@@ -18,9 +18,12 @@ using CMSMicroservice.Application.InventoryItemCQ.Queries.GetLowStockItems;
|
||||
using CMSMicroservice.Application.StockMovementCQ.Commands.CreateStockMovement;
|
||||
using CMSMicroservice.Application.StockMovementCQ.Queries.GetStockMovements;
|
||||
using CMSMicroservice.Application.StockMovementCQ.Queries.GetStockMovementsByInventoryItem;
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Grpc.Core;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Linq;
|
||||
|
||||
namespace CMSMicroservice.WebApi.Services;
|
||||
|
||||
@@ -28,11 +31,13 @@ public class InventoryService : InventoryContract.InventoryContractBase
|
||||
{
|
||||
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
|
||||
private readonly IMediator _mediator;
|
||||
private readonly IApplicationDbContext _context;
|
||||
|
||||
public InventoryService(IDispatchRequestToCQRS dispatchRequestToCQRS, IMediator mediator)
|
||||
public InventoryService(IDispatchRequestToCQRS dispatchRequestToCQRS, IMediator mediator, IApplicationDbContext context)
|
||||
{
|
||||
_dispatchRequestToCQRS = dispatchRequestToCQRS;
|
||||
_mediator = mediator;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
// ========== Warehouse Management ==========
|
||||
@@ -198,28 +203,109 @@ public class InventoryService : InventoryContract.InventoryContractBase
|
||||
}
|
||||
}
|
||||
|
||||
public override Task<ReserveStockResponse> ReserveStock(ReserveStockRequest request, ServerCallContext context)
|
||||
public override async Task<ReserveStockResponse> ReserveStock(ReserveStockRequest request, ServerCallContext context)
|
||||
{
|
||||
// TODO: Implement with product lookup
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "ReserveStock requires product lookup - not yet implemented"));
|
||||
var inventoryItem = await _mediator.Send(
|
||||
new GetInventoryByProductQuery
|
||||
{
|
||||
ProductId = request.ProductId,
|
||||
ProductType = (Domain.Enums.ProductType)request.ProductType
|
||||
},
|
||||
context.CancellationToken);
|
||||
|
||||
if (inventoryItem == null)
|
||||
return new ReserveStockResponse { Success = false, Message = "آیتم موجودی یافت نشد", AvailableQuantity = 0 };
|
||||
|
||||
var available = inventoryItem.Quantity - inventoryItem.ReservedQuantity;
|
||||
if (available < request.Quantity)
|
||||
return new ReserveStockResponse { Success = false, Message = $"موجودی کافی نیست. موجود: {available}", AvailableQuantity = available };
|
||||
|
||||
await _mediator.Send(
|
||||
new ReserveInventoryCommand
|
||||
{
|
||||
Id = inventoryItem.Id,
|
||||
Quantity = request.Quantity,
|
||||
ReferenceNumber = request.OrderId != null ? $"ORDER-{request.OrderId.Value}" : $"RESERVE-{DateTime.UtcNow.Ticks}"
|
||||
},
|
||||
context.CancellationToken);
|
||||
|
||||
return new ReserveStockResponse { Success = true, Message = "رزرو با موفقیت انجام شد", AvailableQuantity = available - request.Quantity };
|
||||
}
|
||||
|
||||
public override Task<Empty> ReleaseReservation(ReleaseReservationRequest request, ServerCallContext context)
|
||||
public override async Task<Empty> ReleaseReservation(ReleaseReservationRequest request, ServerCallContext context)
|
||||
{
|
||||
// TODO: Implement with product lookup
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "ReleaseReservation requires product lookup - not yet implemented"));
|
||||
var inventoryItem = await _mediator.Send(
|
||||
new GetInventoryByProductQuery
|
||||
{
|
||||
ProductId = request.ProductId,
|
||||
ProductType = (Domain.Enums.ProductType)request.ProductType
|
||||
},
|
||||
context.CancellationToken);
|
||||
|
||||
if (inventoryItem == null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "آیتم موجودی یافت نشد"));
|
||||
|
||||
await _mediator.Send(
|
||||
new ReleaseReservedInventoryCommand
|
||||
{
|
||||
Id = inventoryItem.Id,
|
||||
Quantity = request.Quantity,
|
||||
ReferenceNumber = request.OrderId != null ? $"RELEASE-ORDER-{request.OrderId.Value}" : $"RELEASE-{DateTime.UtcNow.Ticks}"
|
||||
},
|
||||
context.CancellationToken);
|
||||
|
||||
return new Empty();
|
||||
}
|
||||
|
||||
public override Task<Empty> ConfirmSale(ConfirmSaleRequest request, ServerCallContext context)
|
||||
public override async Task<Empty> ConfirmSale(ConfirmSaleRequest request, ServerCallContext context)
|
||||
{
|
||||
// TODO: Implement with product lookup
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "ConfirmSale requires product lookup - not yet implemented"));
|
||||
var inventoryItem = await _mediator.Send(
|
||||
new GetInventoryByProductQuery
|
||||
{
|
||||
ProductId = request.ProductId,
|
||||
ProductType = (Domain.Enums.ProductType)request.ProductType
|
||||
},
|
||||
context.CancellationToken);
|
||||
|
||||
if (inventoryItem == null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "آیتم موجودی یافت نشد"));
|
||||
|
||||
await _mediator.Send(
|
||||
new ReduceInventoryCommand
|
||||
{
|
||||
Id = inventoryItem.Id,
|
||||
Quantity = request.Quantity,
|
||||
FromReserved = request.FromReservation,
|
||||
ReferenceNumber = request.OrderId != null ? $"SALE-ORDER-{request.OrderId.Value}" : $"SALE-{DateTime.UtcNow.Ticks}"
|
||||
},
|
||||
context.CancellationToken);
|
||||
|
||||
return new Empty();
|
||||
}
|
||||
|
||||
public override Task<ProcessReturnResponse> ProcessReturn(ProcessReturnRequest request, ServerCallContext context)
|
||||
public override async Task<ProcessReturnResponse> ProcessReturn(ProcessReturnRequest request, ServerCallContext context)
|
||||
{
|
||||
// TODO: Implement with product lookup
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "ProcessReturn requires product lookup - not yet implemented"));
|
||||
var inventoryItem = await _mediator.Send(
|
||||
new GetInventoryByProductQuery
|
||||
{
|
||||
ProductId = request.ProductId,
|
||||
ProductType = (Domain.Enums.ProductType)request.ProductType
|
||||
},
|
||||
context.CancellationToken);
|
||||
|
||||
if (inventoryItem == null)
|
||||
throw new RpcException(new Status(StatusCode.NotFound, "آیتم موجودی یافت نشد"));
|
||||
|
||||
var result = await _mediator.Send(
|
||||
new IncreaseInventoryCommand
|
||||
{
|
||||
Id = inventoryItem.Id,
|
||||
Quantity = request.Quantity,
|
||||
ReferenceNumber = request.OrderId != null ? $"RETURN-ORDER-{request.OrderId.Value}" : $"RETURN-{DateTime.UtcNow.Ticks}"
|
||||
},
|
||||
context.CancellationToken);
|
||||
|
||||
return new ProcessReturnResponse { NewQuantity = result.NewQuantity };
|
||||
}
|
||||
|
||||
public override async Task<Empty> RecordLoss(RecordLossRequest request, ServerCallContext context)
|
||||
@@ -255,10 +341,48 @@ public class InventoryService : InventoryContract.InventoryContractBase
|
||||
|
||||
// ========== Bulk Operations ==========
|
||||
|
||||
public override Task<BulkAddStockResponse> BulkAddStock(BulkAddStockRequest request, ServerCallContext context)
|
||||
public override async Task<BulkAddStockResponse> BulkAddStock(BulkAddStockRequest request, ServerCallContext context)
|
||||
{
|
||||
// TODO: Implement with product lookup
|
||||
throw new RpcException(new Status(StatusCode.Unimplemented, "BulkAddStock requires product lookup - not yet implemented"));
|
||||
var response = new BulkAddStockResponse();
|
||||
|
||||
foreach (var item in request.Items)
|
||||
{
|
||||
try
|
||||
{
|
||||
var inventoryItem = await _mediator.Send(
|
||||
new GetInventoryByProductQuery
|
||||
{
|
||||
ProductId = item.ProductId,
|
||||
ProductType = (Domain.Enums.ProductType)item.ProductType
|
||||
},
|
||||
context.CancellationToken);
|
||||
|
||||
if (inventoryItem == null)
|
||||
{
|
||||
response.FailedCount++;
|
||||
response.Errors.Add($"ProductId={item.ProductId}: آیتم موجودی یافت نشد");
|
||||
continue;
|
||||
}
|
||||
|
||||
await _mediator.Send(
|
||||
new IncreaseInventoryCommand
|
||||
{
|
||||
Id = inventoryItem.Id,
|
||||
Quantity = item.Quantity,
|
||||
ReferenceNumber = request.ReferenceNumber ?? $"BULK-{DateTime.UtcNow.Ticks}"
|
||||
},
|
||||
context.CancellationToken);
|
||||
|
||||
response.SuccessCount++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
response.FailedCount++;
|
||||
response.Errors.Add($"ProductId={item.ProductId}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// ========== Stock Movements ==========
|
||||
@@ -275,28 +399,74 @@ public class InventoryService : InventoryContract.InventoryContractBase
|
||||
|
||||
// ========== Reports ==========
|
||||
|
||||
public override Task<GetInventorySummaryResponse> GetInventorySummary(GetInventorySummaryRequest request, ServerCallContext context)
|
||||
public override async Task<GetInventorySummaryResponse> GetInventorySummary(GetInventorySummaryRequest request, ServerCallContext context)
|
||||
{
|
||||
// TODO: Implement summary query
|
||||
return Task.FromResult(new GetInventorySummaryResponse
|
||||
var query = _context.InventoryItems
|
||||
.Include(i => i.Product)
|
||||
.Include(i => i.DiscountProduct)
|
||||
.Where(i => !i.IsDeleted);
|
||||
|
||||
if (request.WarehouseId != null)
|
||||
query = query.Where(i => i.WarehouseId == request.WarehouseId.Value);
|
||||
|
||||
var items = await query.ToListAsync(context.CancellationToken);
|
||||
|
||||
var regularProducts = items.Where(i => i.ProductType == Domain.Enums.ProductType.RegularProduct).ToList();
|
||||
var discountProducts = items.Where(i => i.ProductType == Domain.Enums.ProductType.DiscountProduct).ToList();
|
||||
|
||||
long totalStockValue = items.Sum(i =>
|
||||
{
|
||||
TotalProducts = 0,
|
||||
TotalDiscountProducts = 0,
|
||||
TotalQuantity = 0,
|
||||
TotalReserved = 0,
|
||||
LowStockCount = 0,
|
||||
OutOfStockCount = 0,
|
||||
TotalStockValue = 0
|
||||
long unitPrice = i.Product?.Price ?? i.DiscountProduct?.Price ?? 0;
|
||||
return (long)i.Quantity * unitPrice;
|
||||
});
|
||||
|
||||
return new GetInventorySummaryResponse
|
||||
{
|
||||
TotalProducts = regularProducts.Count,
|
||||
TotalDiscountProducts = discountProducts.Count,
|
||||
TotalQuantity = items.Sum(i => i.Quantity),
|
||||
TotalReserved = items.Sum(i => i.ReservedQuantity),
|
||||
LowStockCount = items.Count(i => i.Quantity <= i.LowStockThreshold && i.Quantity > 0),
|
||||
OutOfStockCount = items.Count(i => i.Quantity == 0),
|
||||
TotalStockValue = totalStockValue
|
||||
};
|
||||
}
|
||||
|
||||
public override Task<GetStockValueReportResponse> GetStockValueReport(GetStockValueReportRequest request, ServerCallContext context)
|
||||
public override async Task<GetStockValueReportResponse> GetStockValueReport(GetStockValueReportRequest request, ServerCallContext context)
|
||||
{
|
||||
// TODO: Implement stock value report query
|
||||
return Task.FromResult(new GetStockValueReportResponse
|
||||
var query = _context.InventoryItems
|
||||
.Include(i => i.Product)
|
||||
.Include(i => i.DiscountProduct)
|
||||
.Where(i => !i.IsDeleted);
|
||||
|
||||
if (request.WarehouseId != null)
|
||||
query = query.Where(i => i.WarehouseId == request.WarehouseId.Value);
|
||||
|
||||
if (request.ProductType != ProductType.Unspecified)
|
||||
query = query.Where(i => i.ProductType == (Domain.Enums.ProductType)request.ProductType);
|
||||
|
||||
var dbItems = await query.ToListAsync(context.CancellationToken);
|
||||
|
||||
var items = dbItems.Select(i =>
|
||||
{
|
||||
TotalValue = 0,
|
||||
TotalItems = 0
|
||||
});
|
||||
long unitPrice = i.Product?.Price ?? i.DiscountProduct?.Price ?? 0;
|
||||
string title = i.Product?.Title ?? i.DiscountProduct?.Title ?? string.Empty;
|
||||
return new StockValueItem
|
||||
{
|
||||
ProductId = i.ProductId ?? i.DiscountProductId ?? 0,
|
||||
ProductTitle = title,
|
||||
ProductType = (ProductType)i.ProductType,
|
||||
Quantity = i.Quantity,
|
||||
UnitPrice = unitPrice,
|
||||
TotalValue = (long)i.Quantity * unitPrice
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
return new GetStockValueReportResponse
|
||||
{
|
||||
Items = { items },
|
||||
TotalValue = items.Sum(i => i.TotalValue),
|
||||
TotalItems = items.Count
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user