CreateOrUpdateStorageLocationCommandHandler.cs 9.27 KB
using MassTransit;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Rcs.Application.Common;
using Rcs.Application.MessageBus.Commands.StorageLocations;
using Rcs.Domain.Entities;
using Rcs.Domain.Repositories;

namespace Rcs.Infrastructure.MessageBus.Handlers.Commands.StorageLocations
{
    /// <summary>
    /// Create or update storage location command handler.
    /// </summary>
    public class CreateOrUpdateStorageLocationCommandHandler : IConsumer<CreateOrUpdateStorageLocationCommand>
    {
        private readonly ILogger<CreateOrUpdateStorageLocationCommandHandler> _logger;
        private readonly IStorageLocationRepository _repository;
        private readonly IStorageAreaRepository _storageAreaRepository;
        private readonly IStorageLocationTypeRepository _storageLocationTypeRepository;
        private readonly IMapNodeRepository _mapNodeRepository;

        public CreateOrUpdateStorageLocationCommandHandler(
            ILogger<CreateOrUpdateStorageLocationCommandHandler> logger,
            IStorageLocationRepository repository,
            IStorageAreaRepository storageAreaRepository,
            IStorageLocationTypeRepository storageLocationTypeRepository,
            IMapNodeRepository mapNodeRepository)
        {
            _logger = logger;
            _repository = repository;
            _storageAreaRepository = storageAreaRepository;
            _storageLocationTypeRepository = storageLocationTypeRepository;
            _mapNodeRepository = mapNodeRepository;
        }

        public async Task Consume(ConsumeContext<CreateOrUpdateStorageLocationCommand> context)
        {
            var command = context.Message;

            try
            {
                ValidateCommand(command);

                if (!Guid.TryParse(command.AreaId, out var areaId))
                {
                    throw new InvalidOperationException("AreaId format is invalid");
                }

                var area = await _storageAreaRepository.GetByIdAsync(areaId, context.CancellationToken);
                if (area == null)
                {
                    throw new InvalidOperationException($"Storage area not found: {areaId}");
                }

                Guid? locationTypeId = null;
                if (!string.IsNullOrWhiteSpace(command.LocationTypeId))
                {
                    if (!Guid.TryParse(command.LocationTypeId, out var parsedLocationTypeId))
                    {
                        throw new InvalidOperationException("LocationTypeId format is invalid");
                    }

                    var locationType = await _storageLocationTypeRepository.GetByIdAsync(parsedLocationTypeId, context.CancellationToken);
                    if (locationType == null)
                    {
                        throw new InvalidOperationException($"Storage location type not found: {parsedLocationTypeId}");
                    }

                    locationTypeId = parsedLocationTypeId;
                }

                Guid? mapNodeId = null;
                if (!string.IsNullOrWhiteSpace(command.MapNodeId))
                {
                    var rawMapNodeValue = command.MapNodeId.Trim();
                    MapNode? mapNode = null;

                    if (Guid.TryParse(rawMapNodeValue, out var parsedMapNodeId))
                    {
                        mapNode = await _mapNodeRepository.GetByIdAsync(parsedMapNodeId, context.CancellationToken);
                    }
                    else
                    {
                        mapNode = await _mapNodeRepository.GetByNodeCodeAsync(rawMapNodeValue, context.CancellationToken);
                    }

                    if (mapNode == null)
                    {
                        throw new InvalidOperationException($"Map node not found: {rawMapNodeValue}");
                    }

                    mapNodeId = mapNode.NodeId;
                }

                var locationCode = command.LocationCode.Trim();

                if (!Guid.TryParse(command.LocationId, out var locationId))
                {
                    var existingByCode = await _repository.GetByLocationCodeAsync(locationCode, context.CancellationToken);
                    if (existingByCode != null)
                    {
                        throw new InvalidOperationException($"LocationCode already exists: {locationCode}");
                    }

                    if (mapNodeId.HasValue)
                    {
                        var existingByMapNode = await _repository.GetQueryable()
                            .FirstOrDefaultAsync(x => x.MapNodeId == mapNodeId.Value, context.CancellationToken);
                        if (existingByMapNode != null)
                        {
                            throw new InvalidOperationException($"Map node already bound by another storage location: {mapNodeId.Value}");
                        }
                    }

                    var entity = new StorageLocation
                    {
                        LocationId = Guid.NewGuid(),
                        AreaId = areaId,
                        LocationTypeId = locationTypeId,
                        MapNodeId = mapNodeId,
                        LocationCode = locationCode,
                        LocationName = string.IsNullOrWhiteSpace(command.LocationName) ? null : command.LocationName.Trim(),
                        LayerNumber = command.LayerNumber,
                        Orientation = command.Orientation,
                        Width = command.Width,
                        Depth = command.Depth,
                        Height = command.Height,
                        MaxLoadWeight = command.MaxLoadWeight,
                        Status = (StorageLocationStatus)command.Status,
                        IsActive = command.IsActive,
                        CreatedAt = DateTime.Now,
                        UpdatedAt = DateTime.Now,
                    };

                    await _repository.AddAsync(entity, context.CancellationToken);
                    await _repository.SaveChangesAsync(context.CancellationToken);
                }
                else
                {
                    var entity = await _repository.GetByIdAsync(locationId, context.CancellationToken);
                    if (entity == null)
                    {
                        throw new InvalidOperationException($"Storage location not found: {locationId}");
                    }

                    var existingByCode = await _repository.GetByLocationCodeAsync(locationCode, context.CancellationToken);
                    if (existingByCode != null && existingByCode.LocationId != locationId)
                    {
                        throw new InvalidOperationException($"LocationCode already exists: {locationCode}");
                    }

                    if (mapNodeId.HasValue)
                    {
                        var existingByMapNode = await _repository.GetQueryable()
                            .FirstOrDefaultAsync(x => x.MapNodeId == mapNodeId.Value, context.CancellationToken);
                        if (existingByMapNode != null && existingByMapNode.LocationId != locationId)
                        {
                            throw new InvalidOperationException($"Map node already bound by another storage location: {mapNodeId.Value}");
                        }
                    }

                    entity.AreaId = areaId;
                    entity.LocationTypeId = locationTypeId;
                    entity.MapNodeId = mapNodeId;
                    entity.LocationCode = locationCode;
                    entity.LocationName = string.IsNullOrWhiteSpace(command.LocationName) ? null : command.LocationName.Trim();
                    entity.LayerNumber = command.LayerNumber;
                    entity.Orientation = command.Orientation;
                    entity.Width = command.Width;
                    entity.Depth = command.Depth;
                    entity.Height = command.Height;
                    entity.MaxLoadWeight = command.MaxLoadWeight;
                    entity.Status = (StorageLocationStatus)command.Status;
                    entity.IsActive = command.IsActive;
                    entity.UpdatedAt = DateTime.Now;

                    await _repository.UpdateAsync(entity, context.CancellationToken);
                    await _repository.SaveChangesAsync(context.CancellationToken);
                }

                await context.RespondAsync(ApiResponse.Successful());
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Create or update storage location failed");
                await context.RespondAsync(ApiResponse.Failed(ex.Message));
            }
        }

        private static void ValidateCommand(CreateOrUpdateStorageLocationCommand command)
        {
            if (string.IsNullOrWhiteSpace(command.LocationCode))
            {
                throw new InvalidOperationException("LocationCode is required");
            }

            if (string.IsNullOrWhiteSpace(command.AreaId))
            {
                throw new InvalidOperationException("AreaId is required");
            }

            if (!Enum.IsDefined(typeof(StorageLocationStatus), command.Status))
            {
                throw new InvalidOperationException($"Status is invalid: {command.Status}");
            }
        }
    }
}