feat: Implement inventory and warehouse management features
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 2m33s

- Add GetLowStockItemsResponseDto and LowStockItemDto for low stock item queries.
- Create CreateStockMovementCommand and its handler for managing stock movements.
- Implement CreateStockMovementCommandValidator for validating stock movement commands.
- Add GetStockMovementsQuery and its handler to retrieve stock movement records.
- Create GetStockMovementsResponseDto and StockMovementListDto for stock movement responses.
- Implement GetStockMovementsByInventoryItemQuery and its handler for fetching movements by inventory item.
- Add CreateWarehouseCommand and its handler for creating new warehouses.
- Implement CreateWarehouseCommandValidator for warehouse creation validation.
- Add DeleteWarehouseCommand and its handler for removing warehouses.
- Implement SetDefaultWarehouseCommand and its handler for setting a default warehouse.
- Create UpdateWarehouseCommand and its handler for updating warehouse details.
- Implement GetAllWarehousesQuery and its handler to retrieve all warehouses.
- Add GetWarehouseQuery and its handler for fetching a specific warehouse by ID.
- Implement SearchWarehousesQuery and its handler for searching warehouses based on criteria.
This commit is contained in:
masoodafar-web
2026-01-03 11:59:08 +03:30
parent 8c3d710253
commit dde4c68b2f
102 changed files with 2715 additions and 3721 deletions
@@ -1,247 +1,484 @@
using CMSMicroservice.Domain.Entities;
using CMSMicroservice.Application.WarehouseCQ.Commands.CreateWarehouse;
using CMSMicroservice.Application.WarehouseCQ.Commands.UpdateWarehouse;
using CMSMicroservice.Application.WarehouseCQ.Commands.DeleteWarehouse;
using CMSMicroservice.Application.WarehouseCQ.Commands.SetDefaultWarehouse;
using CMSMicroservice.Application.WarehouseCQ.Queries.GetWarehouse;
using CMSMicroservice.Application.WarehouseCQ.Queries.GetAllWarehouses;
using CMSMicroservice.Application.InventoryItemCQ.Commands.UpdateInventoryItem;
using CMSMicroservice.Application.InventoryItemCQ.Commands.UpdateInventoryQuantity;
using CMSMicroservice.Application.InventoryItemCQ.Commands.ReserveInventory;
using CMSMicroservice.Application.InventoryItemCQ.Commands.ReleaseReservedInventory;
using CMSMicroservice.Application.InventoryItemCQ.Commands.ReduceInventory;
using CMSMicroservice.Application.InventoryItemCQ.Commands.IncreaseInventory;
using CMSMicroservice.Application.InventoryItemCQ.Queries.GetInventoryItem;
using CMSMicroservice.Application.InventoryItemCQ.Queries.GetInventoryByProduct;
using CMSMicroservice.Application.InventoryItemCQ.Queries.GetAllInventoryItems;
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.Protobuf.Protos.Inventory;
using CMSMicroservice.Application.Features.Warehouses.Commands;
using CMSMicroservice.Application.Features.Warehouses.Queries;
using CMSMicroservice.Application.Features.InventoryItems.Commands;
using CMSMicroservice.Application.Features.InventoryItems.Queries;
using CMSMicroservice.Application.Features.StockMovements.Commands;
using CMSMicroservice.Application.Features.StockMovements.Queries;
using CMSMicroservice.Domain.Enums;
using Google.Protobuf.WellKnownTypes;
using ProtoProductType = CMSMicroservice.Protobuf.Protos.Inventory.ProductType;
using DomainProductType = CMSMicroservice.Domain.Enums.ProductType;
using ProtoStockMovementType = CMSMicroservice.Protobuf.Protos.Inventory.StockMovementType;
using Mapster;
using DomainStockMovementType = CMSMicroservice.Domain.Enums.StockMovementType;
using ProtoStockMovementType = CMSMicroservice.Protobuf.Protos.Inventory.StockMovementType;
using DomainProductType = CMSMicroservice.Domain.Enums.ProductType;
using ProtoProductType = CMSMicroservice.Protobuf.Protos.Inventory.ProductType;
namespace CMSMicroservice.WebApi.Common.Mappings;
/// <summary>
/// Mapster profile برای Inventory Protobuf به CQRS و Entities به DTOs
/// </summary>
public class InventoryProfile : IRegister
{
void IRegister.Register(TypeAdapterConfig config)
public void Register(TypeAdapterConfig config)
{
// =============================================
// Protobuf Request به CQRS Query/Command Mappings
// =============================================
// Warehouse Commands
// ==================== Warehouse Mappings ====================
// CreateWarehouse: Proto -> Command
config.NewConfig<CreateWarehouseRequest, CreateWarehouseCommand>()
.MapWith(src => new CreateWarehouseCommand
{
Name = src.Name,
Code = src.Code,
Address = src.Address,
IsDefault = src.IsDefault
});
.Map(dest => dest.Name, src => src.Name)
.Map(dest => dest.Code, src => src.Code)
.Map(dest => dest.Address, src => src.Address)
.Map(dest => dest.IsDefault, src => src.IsDefault)
.Map(dest => dest.IsActive, src => true); // Proto ندارد
// CreateWarehouse: ResponseDto -> Proto Response
config.NewConfig<CreateWarehouseResponseDto, CreateWarehouseResponse>()
.Map(dest => dest.Id, src => src.Id);
// UpdateWarehouse: Proto -> Command
config.NewConfig<UpdateWarehouseRequest, UpdateWarehouseCommand>()
.MapWith(src => new UpdateWarehouseCommand
.Map(dest => dest.Id, src => src.Id)
.Map(dest => dest.Name, src => src.Name)
.Map(dest => dest.Code, src => src.Code)
.Map(dest => dest.Address, src => src.Address)
.Map(dest => dest.IsActive, src => src.IsActive);
// DeleteWarehouse: Proto -> Command
config.NewConfig<DeleteWarehouseRequest, DeleteWarehouseCommand>()
.Map(dest => dest.Id, src => src.Id);
// SetDefaultWarehouse: Proto -> Command
config.NewConfig<SetDefaultWarehouseRequest, SetDefaultWarehouseCommand>()
.Map(dest => dest.Id, src => src.Id);
// GetWarehouse Query: Proto -> Query
config.NewConfig<GetWarehouseRequest, GetWarehouseQuery>()
.MapWith(src => new GetWarehouseQuery(src.Id));
// GetWarehouse Response: ResponseDto -> Proto (با WarehouseDto)
config.NewConfig<GetWarehouseResponseDto, GetWarehouseResponse>()
.Map(dest => dest.Warehouse, src => new WarehouseDto
{
Id = src.Id,
Name = src.Name,
Code = src.Code,
Address = src.Address,
IsActive = src.IsActive
Address = src.Address ?? string.Empty,
IsDefault = src.IsDefault,
IsActive = src.IsActive,
Created = Timestamp.FromDateTime(DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc)),
LastModified = Timestamp.FromDateTime(DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc))
});
config.NewConfig<DeleteWarehouseRequest, DeleteWarehouseCommand>()
.MapWith(src => new DeleteWarehouseCommand(src.Id));
config.NewConfig<SetDefaultWarehouseRequest, SetDefaultWarehouseCommand>()
.MapWith(src => new SetDefaultWarehouseCommand(src.Id));
// Warehouse Queries
config.NewConfig<GetWarehouseRequest, GetWarehouseByIdQuery>()
.MapWith(src => new GetWarehouseByIdQuery(src.Id));
// GetAllWarehouses Query: Proto -> Query
config.NewConfig<GetAllWarehousesRequest, GetAllWarehousesQuery>()
.MapWith(src => new GetAllWarehousesQuery());
// Inventory Item Queries
config.NewConfig<GetInventoryItemRequest, GetInventoryItemByIdQuery>()
.MapWith(src => new GetInventoryItemByIdQuery(src.Id));
// GetAllWarehouses Response: ResponseDto -> Proto
config.NewConfig<GetAllWarehousesResponseDto, GetAllWarehousesResponse>()
.Map(dest => dest.TotalCount, src => src.TotalCount)
.AfterMapping((src, dest) =>
{
dest.Warehouses.Clear();
foreach (var warehouse in src.Warehouses)
{
dest.Warehouses.Add(new WarehouseDto
{
Id = warehouse.Id,
Name = warehouse.Name,
Code = warehouse.Code,
Address = warehouse.Address ?? string.Empty,
IsDefault = warehouse.IsDefault,
IsActive = warehouse.IsActive,
Created = Timestamp.FromDateTime(DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc)),
LastModified = Timestamp.FromDateTime(DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc))
});
}
});
config.NewConfig<GetInventoryByProductRequest, GetInventoryItemByProductIdQuery>()
.MapWith(src => new GetInventoryItemByProductIdQuery(src.ProductId, null));
// ==================== Inventory Item Query Mappings ====================
config.NewConfig<GetInventoryByProductRequest, GetInventoryItemByDiscountProductIdQuery>()
.MapWith(src => new GetInventoryItemByDiscountProductIdQuery(src.ProductId, null));
// GetInventoryItem: Proto -> Query
config.NewConfig<GetInventoryItemRequest, GetInventoryItemQuery>()
.MapWith(src => new GetInventoryItemQuery(src.Id));
config.NewConfig<GetAllInventoryItemsRequest, SearchInventoryItemsQuery>()
.MapWith(src => new SearchInventoryItemsQuery(
null, // SearchTerm
null, // ProductType
null, // WarehouseId
null, // MinQuantity
null, // MaxQuantity
0, // Skip
50 // Take
));
// GetInventoryItem Response: ResponseDto -> Proto (با InventoryItemDto)
config.NewConfig<GetInventoryItemResponseDto, GetInventoryItemResponse>()
.Map(dest => dest.Item, src => MapToInventoryItemDto(src));
// GetInventoryByProduct: Proto -> Query
config.NewConfig<GetInventoryByProductRequest, GetInventoryByProductQuery>()
.Map(dest => dest.ProductId, src => src.ProductId)
.Map(dest => dest.ProductType, src => MapToDomainProductType(src.ProductType));
// GetInventoryByProduct Response: ResponseDto -> Proto
config.NewConfig<GetInventoryByProductResponseDto, GetInventoryByProductResponse>()
.Map(dest => dest.Item, src => MapToInventoryItemDto(src));
// GetAllInventoryItems: Proto -> Query
config.NewConfig<GetAllInventoryItemsRequest, GetAllInventoryItemsQuery>()
.Map(dest => dest.WarehouseId, src => src.WarehouseId != null ? (long?)src.WarehouseId.Value : null)
.Map(dest => dest.ProductType, src => src.ProductType != ProtoProductType.Unspecified
? (DomainProductType?)MapToDomainProductType(src.ProductType) : null)
.Map(dest => dest.SearchTerm, src => src.Search)
.Map(dest => dest.Skip, src => src.Page > 0 ? (src.Page - 1) * src.PageSize : 0)
.Map(dest => dest.Take, src => src.PageSize > 0 ? src.PageSize : 50);
// GetAllInventoryItems Response: ResponseDto -> Proto
config.NewConfig<GetAllInventoryItemsResponseDto, GetAllInventoryItemsResponse>()
.Map(dest => dest.TotalCount, src => src.TotalCount)
.AfterMapping((src, dest) =>
{
dest.Items.Clear();
foreach (var item in src.Items)
{
dest.Items.Add(MapListItemToInventoryItemDto(item));
}
});
// GetLowStockItems: Proto -> Query
config.NewConfig<GetLowStockItemsRequest, GetLowStockItemsQuery>()
.MapWith(src => new GetLowStockItemsQuery(null, 1));
.Map(dest => dest.WarehouseId, src => src.WarehouseId != null ? (long?)src.WarehouseId.Value : null)
.Map(dest => dest.Count, src => src.PageSize > 0 ? src.PageSize : 50);
// Inventory Item Commands
// GetLowStockItems Response: ResponseDto -> Proto
config.NewConfig<GetLowStockItemsResponseDto, GetLowStockItemsResponse>()
.Map(dest => dest.TotalCount, src => src.TotalCount)
.AfterMapping((src, dest) =>
{
dest.Items.Clear();
foreach (var item in src.Items)
{
dest.Items.Add(MapLowStockItemToInventoryItemDto(item));
}
});
// UpdateInventorySettings: Proto -> UpdateInventoryItemCommand
config.NewConfig<UpdateInventorySettingsRequest, UpdateInventoryItemCommand>()
.MapWith(src => new UpdateInventoryItemCommand
{
Id = src.Id,
LowStockThreshold = src.LowStockThreshold,
ReorderPoint = src.ReorderPoint,
MaxStockLevel = src.MaxStockLevel
});
.Map(dest => dest.Id, src => src.Id)
.Map(dest => dest.MinimumStock, src => (int?)src.LowStockThreshold)
.Map(dest => dest.MaximumStock, src => (int?)src.MaxStockLevel)
.Map(dest => dest.ReorderPoint, src => (int?)src.ReorderPoint);
// Stock Operation Commands
// ==================== Stock Operation Mappings ====================
// AddStock: Proto -> IncreaseInventoryCommand
// نکته: این نیاز به lookup در Service داره چون Proto از ProductId استفاده میکنه
config.NewConfig<AddStockRequest, IncreaseInventoryCommand>()
.MapWith(src => new IncreaseInventoryCommand
{
ProductId = src.ProductId,
ProductType = (DomainProductType)src.ProductType,
Quantity = src.Quantity,
Note = src.Note,
ReferenceNumber = src.ReferenceNumber,
WarehouseId = src.WarehouseId?.Value ?? 1
});
.Map(dest => dest.Quantity, src => src.Quantity)
.Map(dest => dest.ReferenceNumber, src => src.ReferenceNumber)
.Map(dest => dest.Note, src => src.Note);
// Id باید در Service تنظیم بشه
// AdjustStock: Proto -> UpdateInventoryQuantityCommand
config.NewConfig<AdjustStockRequest, UpdateInventoryQuantityCommand>()
.MapWith(src => new UpdateInventoryQuantityCommand
{
ProductId = src.ProductId,
ProductType = (DomainProductType)src.ProductType,
NewQuantity = src.NewQuantity,
Note = src.Note,
ReferenceNumber = src.ReferenceNumber,
WarehouseId = src.WarehouseId?.Value ?? 1
});
.Map(dest => dest.NewQuantity, src => src.NewQuantity)
.Map(dest => dest.Note, src => src.Reason)
.Map(dest => dest.ReferenceNumber, src => src.ReferenceNumber);
// Id باید در Service تنظیم بشه
// ReserveStock: Proto -> ReserveInventoryCommand
config.NewConfig<ReserveStockRequest, ReserveInventoryCommand>()
.MapWith(src => new ReserveInventoryCommand
{
ProductId = src.ProductId,
ProductType = (DomainProductType)src.ProductType,
Quantity = src.Quantity,
Note = src.Note,
ReferenceNumber = src.ReferenceNumber,
OrderId = src.OrderId?.Value,
WarehouseId = src.WarehouseId?.Value ?? 1
});
.Map(dest => dest.Quantity, src => src.Quantity)
.Map(dest => dest.OrderId, src => src.OrderId != null ? (long?)src.OrderId.Value : null)
.Map(dest => dest.DiscountOrderId, src => src.DiscountOrderId != null ? (long?)src.DiscountOrderId.Value : null);
// Id باید در Service تنظیم بشه
// ReleaseReservation: Proto -> ReleaseReservedInventoryCommand
config.NewConfig<ReleaseReservationRequest, ReleaseReservedInventoryCommand>()
.MapWith(src => new ReleaseReservedInventoryCommand
{
ProductId = src.ProductId,
ProductType = (DomainProductType)src.ProductType,
Quantity = src.Quantity,
Note = src.Note,
ReferenceNumber = src.ReferenceNumber,
WarehouseId = src.WarehouseId?.Value ?? 1
});
.Map(dest => dest.Quantity, src => src.Quantity)
.Map(dest => dest.OrderId, src => src.OrderId != null ? (long?)src.OrderId.Value : null)
.Map(dest => dest.DiscountOrderId, src => src.DiscountOrderId != null ? (long?)src.DiscountOrderId.Value : null);
// Id باید در Service تنظیم بشه
// ConfirmSale: Proto -> ReduceInventoryCommand
config.NewConfig<ConfirmSaleRequest, ReduceInventoryCommand>()
.MapWith(src => new ReduceInventoryCommand
{
ProductId = src.ProductId,
ProductType = (DomainProductType)src.ProductType,
Quantity = src.Quantity,
Note = src.Note,
ReferenceNumber = src.ReferenceNumber,
OrderId = src.OrderId?.Value,
WarehouseId = src.WarehouseId?.Value ?? 1
});
.Map(dest => dest.Quantity, src => src.Quantity)
.Map(dest => dest.OrderId, src => src.OrderId != null ? (long?)src.OrderId.Value : null)
.Map(dest => dest.DiscountOrderId, src => src.DiscountOrderId != null ? (long?)src.DiscountOrderId.Value : null)
.Map(dest => dest.FromReserved, src => src.FromReservation);
// Id باید در Service تنظیم بشه
// ProcessReturn: Proto -> IncreaseInventoryCommand
config.NewConfig<ProcessReturnRequest, IncreaseInventoryCommand>()
.MapWith(src => new IncreaseInventoryCommand
{
ProductId = src.ProductId,
ProductType = (DomainProductType)src.ProductType,
Quantity = src.Quantity,
Note = !string.IsNullOrEmpty(src.Note) ? src.Note : "Product return",
ReferenceNumber = src.ReferenceNumber,
OrderId = src.OrderId?.Value,
WarehouseId = src.WarehouseId?.Value ?? 1
});
.Map(dest => dest.Quantity, src => src.Quantity)
.Map(dest => dest.Note, src => src.Reason);
// Id باید در Service تنظیم بشه
// RecordLoss: Proto -> CreateStockMovementCommand
config.NewConfig<RecordLossRequest, CreateStockMovementCommand>()
.MapWith(src => new CreateStockMovementCommand
{
ProductId = src.ProductId,
ProductType = (DomainProductType)src.ProductType,
MovementType = DomainStockMovementType.Loss,
Quantity = src.Quantity,
Note = !string.IsNullOrEmpty(src.Note) ? src.Note : "Stock loss/damage",
ReferenceNumber = src.ReferenceNumber,
WarehouseId = src.WarehouseId?.Value ?? 1
});
// Stock Movement Queries
config.NewConfig<GetStockMovementsRequest, SearchStockMovementsQuery>()
.MapWith(src => new SearchStockMovementsQuery
{
InventoryItemId = src.InventoryItemId?.Value,
ProductId = src.ProductId?.Value,
ProductType = src.ProductType != ProtoProductType.Unspecified ? (DomainProductType?)src.ProductType : null,
MovementType = src.MovementType != ProtoStockMovementType.Unspecified ? (DomainStockMovementType?)src.MovementType : null,
StartDate = src.StartDate?.ToDateTime(),
EndDate = src.EndDate?.ToDateTime(),
Skip = src.Skip,
Take = src.Take > 0 ? src.Take : 50
});
config.NewConfig<GetStockMovementsByInventoryItemRequest, GetInventoryItemMovementHistoryQuery>()
.MapWith(src => new GetInventoryItemMovementHistoryQuery
{
InventoryItemId = src.InventoryItemId,
Skip = (src.Page - 1) * src.PageSize,
Take = src.PageSize > 0 ? src.PageSize : 50
});
// =============================================
// Entity به Protobuf DTO Mappings
// =============================================
// Warehouse Entity به WarehouseDto
config.NewConfig<Warehouse, WarehouseDto>()
.Map(dest => dest.Id, src => src.Id)
.Map(dest => dest.Name, src => src.Name)
.Map(dest => dest.Code, src => src.Code ?? string.Empty)
.Map(dest => dest.Address, src => src.Address ?? string.Empty)
.Map(dest => dest.IsDefault, src => src.IsDefault)
.Map(dest => dest.IsActive, src => src.IsActive);
// InventoryItem Entity به InventoryItemDto
config.NewConfig<InventoryItem, InventoryItemDto>()
.Map(dest => dest.Id, src => src.Id)
.Map(dest => dest.WarehouseId, src => src.WarehouseId)
.Map(dest => dest.WarehouseName, src => src.Warehouse != null ? src.Warehouse.Name : string.Empty)
.Map(dest => dest.ProductId, src => src.ProductId.HasValue ? new Int64Value { Value = src.ProductId.Value } : null)
.Map(dest => dest.DiscountProductId, src => src.DiscountProductId.HasValue ? new Int64Value { Value = src.DiscountProductId.Value } : null)
.Map(dest => dest.ProductType, src => (ProtoProductType)src.ProductType)
.Map(dest => dest.Quantity, src => src.Quantity)
.Map(dest => dest.ReservedQuantity, src => src.ReservedQuantity)
.Map(dest => dest.AvailableQuantity, src => src.AvailableQuantity)
.Map(dest => dest.LowStockThreshold, src => src.LowStockThreshold)
.Map(dest => dest.ReorderPoint, src => src.ReorderPoint)
.Map(dest => dest.MaxStockLevel, src => src.MaxStockLevel)
.Map(dest => dest.LastRestockedAt, src => src.LastRestockedAt.HasValue ? Timestamp.FromDateTime(src.LastRestockedAt.Value.ToUniversalTime()) : null)
.Map(dest => dest.LastSoldAt, src => src.LastSoldAt.HasValue ? Timestamp.FromDateTime(src.LastSoldAt.Value.ToUniversalTime()) : null)
.Map(dest => dest.ProductTitle, src => src.Product != null ? src.Product.Title : (src.DiscountProduct != null ? src.DiscountProduct.Title : string.Empty))
.Map(dest => dest.ProductPrice, src => src.Product != null ? src.Product.Price : (src.DiscountProduct != null ? src.DiscountProduct.Price : 0))
.Map(dest => dest.Created, src => Timestamp.FromDateTime(src.Created.ToUniversalTime()));
.Map(dest => dest.MovementType, src => MapToDomainStockMovementType(src.LossType))
.Map(dest => dest.Note, src => src.Reason)
.Map(dest => dest.ReferenceNumber, src => src.ReferenceNumber);
// InventoryItemId باید در Service تنظیم بشه
// StockMovement Entity به StockMovementDto
config.NewConfig<StockMovement, StockMovementDto>()
.Map(dest => dest.Id, src => src.Id)
// ==================== Stock Movement Query Mappings ====================
// GetStockMovements: Proto -> Query
config.NewConfig<GetStockMovementsRequest, GetStockMovementsQuery>()
.Map(dest => dest.InventoryItemId, src => src.InventoryItemId != null ? (long?)src.InventoryItemId.Value : null)
.Map(dest => dest.ProductId, src => src.ProductId != null ? (long?)src.ProductId.Value : null)
.Map(dest => dest.ProductType, src => src.ProductType != ProtoProductType.Unspecified
? (DomainProductType?)MapToDomainProductType(src.ProductType) : null)
.Map(dest => dest.MovementType, src => src.MovementType != ProtoStockMovementType.MovementTypeUnspecified
? (DomainStockMovementType?)MapToDomainStockMovementType(src.MovementType) : null)
.Map(dest => dest.StartDate, src => src.FromDate != null ? (DateTime?)src.FromDate.ToDateTime() : null)
.Map(dest => dest.EndDate, src => src.ToDate != null ? (DateTime?)src.ToDate.ToDateTime() : null)
.Map(dest => dest.Skip, src => src.Page > 0 ? (src.Page - 1) * src.PageSize : 0)
.Map(dest => dest.Take, src => src.PageSize > 0 ? src.PageSize : 50);
// GetStockMovements Response: ResponseDto -> Proto
config.NewConfig<GetStockMovementsResponseDto, GetStockMovementsResponse>()
.Map(dest => dest.TotalCount, src => src.TotalCount)
.AfterMapping((src, dest) =>
{
dest.Movements.Clear();
foreach (var movement in src.Movements)
{
dest.Movements.Add(MapToStockMovementDto(movement));
}
});
// GetStockMovementsByInventoryItem: Proto -> Query
config.NewConfig<GetStockMovementsByInventoryItemRequest, GetStockMovementsByInventoryItemQuery>()
.Map(dest => dest.InventoryItemId, src => src.InventoryItemId)
.Map(dest => dest.MovementType, src => (ProtoStockMovementType)src.MovementType)
.Map(dest => dest.Quantity, src => src.Quantity)
.Map(dest => dest.QuantityBefore, src => src.QuantityBefore)
.Map(dest => dest.QuantityAfter, src => src.QuantityAfter)
.Map(dest => dest.Note, src => src.Note ?? string.Empty)
.Map(dest => dest.ReferenceNumber, src => src.ReferenceNumber ?? string.Empty)
.Map(dest => dest.OrderId, src => src.OrderId.HasValue ? new Int64Value { Value = src.OrderId.Value } : null)
.Map(dest => dest.DiscountOrderId, src => src.DiscountOrderId.HasValue ? new Int64Value { Value = src.DiscountOrderId.Value } : null)
.Map(dest => dest.PerformedByUserId, src => src.PerformedByUserId.HasValue ? new Int64Value { Value = src.PerformedByUserId.Value } : null)
.Map(dest => dest.Created, src => Timestamp.FromDateTime(src.Created.ToUniversalTime()))
.Map(dest => dest.ProductTitle, src => src.InventoryItem != null && src.InventoryItem.Product != null ? src.InventoryItem.Product.Title : (src.InventoryItem != null && src.InventoryItem.DiscountProduct != null ? src.InventoryItem.DiscountProduct.Title : string.Empty));
.Map(dest => dest.Skip, src => src.Page > 0 ? (src.Page - 1) * src.PageSize : 0)
.Map(dest => dest.Take, src => src.PageSize > 0 ? src.PageSize : 50);
// GetStockMovementsByInventoryItem Response: ResponseDto -> Proto
config.NewConfig<GetStockMovementsByInventoryItemResponseDto, GetStockMovementsByInventoryItemResponse>()
.Map(dest => dest.TotalCount, src => src.TotalCount)
.AfterMapping((src, dest) =>
{
dest.Movements.Clear();
foreach (var movement in src.Movements)
{
dest.Movements.Add(MapHistoryToStockMovementDto(movement));
}
});
}
// ==================== Helper Methods ====================
private static InventoryItemDto MapToInventoryItemDto(GetInventoryItemResponseDto src)
{
return new InventoryItemDto
{
Id = src.Id,
ProductId = src.ProductId,
DiscountProductId = src.DiscountProductId,
ProductType = MapToProtoProductType(src.ProductType),
Quantity = src.Quantity,
ReservedQuantity = src.ReservedQuantity,
AvailableQuantity = src.AvailableQuantity,
LowStockThreshold = src.LowStockThreshold,
ReorderPoint = 0, // Entity ندارد
MaxStockLevel = src.MaxStockLevel,
LastRestockedAt = src.LastRestockedAt.HasValue
? Timestamp.FromDateTime(DateTime.SpecifyKind(src.LastRestockedAt.Value, DateTimeKind.Utc))
: null,
LastSoldAt = src.LastSoldAt.HasValue
? Timestamp.FromDateTime(DateTime.SpecifyKind(src.LastSoldAt.Value, DateTimeKind.Utc))
: null,
WarehouseId = src.WarehouseId,
WarehouseName = src.WarehouseName ?? string.Empty,
ProductTitle = src.ProductTitle ?? string.Empty,
ProductPrice = src.ProductPrice.HasValue ? (long)src.ProductPrice.Value : 0,
Created = Timestamp.FromDateTime(DateTime.SpecifyKind(src.Created, DateTimeKind.Utc))
};
}
private static InventoryItemDto MapToInventoryItemDto(GetInventoryByProductResponseDto src)
{
return new InventoryItemDto
{
Id = src.Id,
ProductId = src.ProductId,
DiscountProductId = src.DiscountProductId,
ProductType = MapToProtoProductType(src.ProductType),
Quantity = src.Quantity,
ReservedQuantity = src.ReservedQuantity,
AvailableQuantity = src.AvailableQuantity,
LowStockThreshold = src.LowStockThreshold,
ReorderPoint = 0,
MaxStockLevel = src.MaxStockLevel,
LastRestockedAt = src.LastRestockedAt.HasValue
? Timestamp.FromDateTime(DateTime.SpecifyKind(src.LastRestockedAt.Value, DateTimeKind.Utc))
: null,
LastSoldAt = src.LastSoldAt.HasValue
? Timestamp.FromDateTime(DateTime.SpecifyKind(src.LastSoldAt.Value, DateTimeKind.Utc))
: null,
WarehouseId = src.WarehouseId,
WarehouseName = src.WarehouseName ?? string.Empty,
ProductTitle = src.ProductTitle ?? string.Empty,
ProductPrice = src.ProductPrice.HasValue ? (long)src.ProductPrice.Value : 0,
Created = Timestamp.FromDateTime(DateTime.SpecifyKind(src.Created, DateTimeKind.Utc))
};
}
private static InventoryItemDto MapListItemToInventoryItemDto(InventoryItemListDto src)
{
return new InventoryItemDto
{
Id = src.Id,
ProductId = src.ProductId,
DiscountProductId = src.DiscountProductId,
ProductType = MapToProtoProductType(src.ProductType),
Quantity = src.Quantity,
ReservedQuantity = src.ReservedQuantity,
AvailableQuantity = src.AvailableQuantity,
LowStockThreshold = src.LowStockThreshold,
ReorderPoint = 0,
MaxStockLevel = src.MaxStockLevel,
LastRestockedAt = src.LastRestockedAt.HasValue
? Timestamp.FromDateTime(DateTime.SpecifyKind(src.LastRestockedAt.Value, DateTimeKind.Utc))
: null,
LastSoldAt = src.LastSoldAt.HasValue
? Timestamp.FromDateTime(DateTime.SpecifyKind(src.LastSoldAt.Value, DateTimeKind.Utc))
: null,
WarehouseId = src.WarehouseId,
WarehouseName = src.WarehouseName ?? string.Empty,
ProductTitle = src.ProductTitle ?? string.Empty,
ProductPrice = src.ProductPrice.HasValue ? (long)src.ProductPrice.Value : 0,
Created = Timestamp.FromDateTime(DateTime.SpecifyKind(src.Created, DateTimeKind.Utc))
};
}
private static InventoryItemDto MapLowStockItemToInventoryItemDto(LowStockItemDto src)
{
return new InventoryItemDto
{
Id = src.Id,
ProductId = src.ProductId,
DiscountProductId = src.DiscountProductId,
ProductType = MapToProtoProductType(src.ProductType),
Quantity = src.Quantity,
ReservedQuantity = src.ReservedQuantity,
AvailableQuantity = src.AvailableQuantity,
LowStockThreshold = src.LowStockThreshold,
ReorderPoint = 0,
MaxStockLevel = 0,
LastRestockedAt = null,
LastSoldAt = null,
WarehouseId = src.WarehouseId,
WarehouseName = src.WarehouseName ?? string.Empty,
ProductTitle = src.ProductTitle ?? string.Empty,
ProductPrice = 0,
Created = Timestamp.FromDateTime(DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc))
};
}
private static StockMovementDto MapToStockMovementDto(StockMovementListDto src)
{
return new StockMovementDto
{
Id = src.Id,
InventoryItemId = src.InventoryItemId,
MovementType = MapToProtoStockMovementType(src.MovementType),
Quantity = src.Quantity,
QuantityBefore = src.QuantityBefore,
QuantityAfter = src.QuantityAfter,
OrderId = src.OrderId,
DiscountOrderId = src.DiscountOrderId,
ReferenceNumber = src.ReferenceNumber ?? string.Empty,
Note = src.Note ?? string.Empty,
PerformedByUserId = src.PerformedByUserId,
Created = Timestamp.FromDateTime(DateTime.SpecifyKind(src.Created, DateTimeKind.Utc)),
ProductTitle = src.ProductTitle ?? string.Empty
};
}
private static StockMovementDto MapHistoryToStockMovementDto(StockMovementHistoryDto src)
{
return new StockMovementDto
{
Id = src.Id,
InventoryItemId = 0, // Not available in history DTO
MovementType = MapToProtoStockMovementType(src.MovementType),
Quantity = src.Quantity,
QuantityBefore = src.QuantityBefore,
QuantityAfter = src.QuantityAfter,
OrderId = src.OrderId,
DiscountOrderId = src.DiscountOrderId,
ReferenceNumber = src.ReferenceNumber ?? string.Empty,
Note = src.Note ?? string.Empty,
PerformedByUserId = src.PerformedByUserId,
Created = Timestamp.FromDateTime(DateTime.SpecifyKind(src.Created, DateTimeKind.Utc)),
ProductTitle = string.Empty
};
}
// ==================== Enum Mapping Methods ====================
private static DomainProductType MapToDomainProductType(ProtoProductType protoType)
{
return protoType switch
{
ProtoProductType.RegularProduct => DomainProductType.RegularProduct,
ProtoProductType.DiscountProduct => DomainProductType.DiscountProduct,
_ => DomainProductType.RegularProduct
};
}
private static ProtoProductType MapToProtoProductType(DomainProductType domainType)
{
return domainType switch
{
DomainProductType.RegularProduct => ProtoProductType.RegularProduct,
DomainProductType.DiscountProduct => ProtoProductType.DiscountProduct,
_ => ProtoProductType.Unspecified
};
}
private static DomainStockMovementType MapToDomainStockMovementType(ProtoStockMovementType protoType)
{
return protoType switch
{
ProtoStockMovementType.InitialStock => DomainStockMovementType.InitialStock,
ProtoStockMovementType.Restock => DomainStockMovementType.Restock,
ProtoStockMovementType.Return => DomainStockMovementType.Return,
ProtoStockMovementType.Sale => DomainStockMovementType.Sale,
ProtoStockMovementType.AdjustmentIncrease => DomainStockMovementType.AdjustmentPlus,
ProtoStockMovementType.AdjustmentDecrease => DomainStockMovementType.AdjustmentMinus,
ProtoStockMovementType.Reserved => DomainStockMovementType.Reserved,
ProtoStockMovementType.Released => DomainStockMovementType.Released,
ProtoStockMovementType.Loss => DomainStockMovementType.Lost,
ProtoStockMovementType.Damaged => DomainStockMovementType.Damaged,
ProtoStockMovementType.Expired => DomainStockMovementType.Damaged, // Domain ندارد Expired
ProtoStockMovementType.TransferOut => DomainStockMovementType.TransferOut,
ProtoStockMovementType.TransferIn => DomainStockMovementType.TransferIn,
_ => DomainStockMovementType.AdjustmentPlus
};
}
private static ProtoStockMovementType MapToProtoStockMovementType(DomainStockMovementType domainType)
{
return domainType switch
{
DomainStockMovementType.InitialStock => ProtoStockMovementType.InitialStock,
DomainStockMovementType.Restock => ProtoStockMovementType.Restock,
DomainStockMovementType.Return => ProtoStockMovementType.Return,
DomainStockMovementType.Sale => ProtoStockMovementType.Sale,
DomainStockMovementType.AdjustmentPlus => ProtoStockMovementType.AdjustmentIncrease,
DomainStockMovementType.AdjustmentMinus => ProtoStockMovementType.AdjustmentDecrease,
DomainStockMovementType.Reserved => ProtoStockMovementType.Reserved,
DomainStockMovementType.Released => ProtoStockMovementType.Released,
DomainStockMovementType.Lost => ProtoStockMovementType.Loss,
DomainStockMovementType.Damaged => ProtoStockMovementType.Damaged,
DomainStockMovementType.TransferOut => ProtoStockMovementType.TransferOut,
DomainStockMovementType.TransferIn => ProtoStockMovementType.TransferIn,
_ => ProtoStockMovementType.MovementTypeUnspecified
};
}
}