Initial commit
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RobotNet10.FleetManager.Data;
|
||||
using RobotNet10.FleetManager.Events;
|
||||
using RobotNet10.FleetManager.Events.Events;
|
||||
using RobotNet10.FleetManager.Shared.DTOs.RobotModel;
|
||||
|
||||
namespace RobotNet10.FleetManager.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service implementation for managing robot models.
|
||||
/// Handles business logic for CRUD operations on robot models.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This service validates business rules such as duplicate model names,
|
||||
/// checks for associated robots before deletion, and provides search functionality.
|
||||
/// </remarks>
|
||||
public class RobotModelService(
|
||||
ApplicationDbContext context,
|
||||
Logger<RobotModelService> logger,
|
||||
IRobotEventBus? eventBus = null) : IRobotModelService
|
||||
{
|
||||
private readonly ApplicationDbContext _context = context;
|
||||
private readonly Logger<RobotModelService> _logger = logger;
|
||||
private readonly IRobotEventBus? _eventBus = eventBus;
|
||||
|
||||
public async Task<RobotModel> CreateAsync(CreateRobotModelRequest request)
|
||||
{
|
||||
// Check if model name already exists
|
||||
if (await ExistsAsync(request.ModelName))
|
||||
{
|
||||
throw new InvalidOperationException($"Robot model with name '{request.ModelName}' already exists.");
|
||||
}
|
||||
|
||||
var robotModel = new RobotModel
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
ModelName = request.ModelName,
|
||||
Length = request.Length,
|
||||
Width = request.Width,
|
||||
ImageWidth = request.ImageWidth,
|
||||
ImageHeight = request.ImageHeight,
|
||||
NavigationPointX = request.NavigationPointX,
|
||||
NavigationPointY = request.NavigationPointY,
|
||||
NavigationType = request.NavigationType,
|
||||
VehicleTypeId = request.VehicleTypeId,
|
||||
CreatedDate = DateTime.UtcNow
|
||||
};
|
||||
|
||||
_context.RobotModels.Add(robotModel);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.Info($"Created robot model {robotModel.ModelName} with ID {robotModel.Id}");
|
||||
return robotModel;
|
||||
}
|
||||
|
||||
public async Task<List<RobotModel>> GetAllAsync()
|
||||
{
|
||||
return await _context.RobotModels
|
||||
.AsNoTracking()
|
||||
.OrderBy(rm => rm.ModelName)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<RobotModel?> GetByIdAsync(Guid id)
|
||||
{
|
||||
return await _context.RobotModels
|
||||
.AsNoTracking()
|
||||
.Include(rm => rm.Robots)
|
||||
.FirstOrDefaultAsync(rm => rm.Id == id);
|
||||
}
|
||||
|
||||
public async Task<RobotModel> UpdateAsync(Guid id, UpdateRobotModelRequest request)
|
||||
{
|
||||
var robotModel = await _context.RobotModels.FindAsync(id) ?? throw new KeyNotFoundException($"Robot model with ID {id} not found.");
|
||||
|
||||
// Check if model name is being changed and if new name already exists
|
||||
if (request.ModelName != null && request.ModelName != robotModel.ModelName)
|
||||
{
|
||||
if (await ExistsAsync(request.ModelName))
|
||||
{
|
||||
throw new InvalidOperationException($"Robot model with name '{request.ModelName}' already exists.");
|
||||
}
|
||||
robotModel.ModelName = request.ModelName;
|
||||
}
|
||||
|
||||
if (request.Length.HasValue)
|
||||
robotModel.Length = request.Length.Value;
|
||||
if (request.Width.HasValue)
|
||||
robotModel.Width = request.Width.Value;
|
||||
if (request.ImageWidth.HasValue)
|
||||
robotModel.ImageWidth = request.ImageWidth.Value;
|
||||
if (request.ImageHeight.HasValue)
|
||||
robotModel.ImageHeight = request.ImageHeight.Value;
|
||||
if (request.NavigationPointX.HasValue)
|
||||
robotModel.NavigationPointX = request.NavigationPointX.Value;
|
||||
if (request.NavigationPointY.HasValue)
|
||||
robotModel.NavigationPointY = request.NavigationPointY.Value;
|
||||
if (request.NavigationType.HasValue)
|
||||
robotModel.NavigationType = request.NavigationType.Value;
|
||||
|
||||
// VehicleTypeId: nullable, can be set or cleared
|
||||
// Note: In ASP.NET Core, if VehicleTypeId is in the JSON request, it will be bound
|
||||
// If not in JSON, it remains the default (null for nullable Guid?)
|
||||
// For simplicity, we always update VehicleTypeId if the request contains it
|
||||
// This allows setting to null by sending "VehicleTypeId": null in JSON
|
||||
// To avoid updating when not provided, we'd need a different approach (e.g., use a wrapper DTO)
|
||||
// For now, we'll always update if the property exists in the request object
|
||||
robotModel.VehicleTypeId = request.VehicleTypeId;
|
||||
|
||||
robotModel.UpdatedDate = DateTime.UtcNow;
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.Info($"Updated robot model {robotModel.ModelName} with ID {robotModel.Id}");
|
||||
|
||||
// Publish event for cache invalidation
|
||||
if (_eventBus != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get all robots using this model
|
||||
var affectedRobots = await _context.Robots
|
||||
.Where(r => r.ModelId == robotModel.Id)
|
||||
.Select(r => r.RobotId)
|
||||
.ToListAsync();
|
||||
|
||||
var eventData = new RobotModelUpdatedEvent
|
||||
{
|
||||
ModelId = robotModel.Id,
|
||||
AffectedRobotIds = affectedRobots
|
||||
};
|
||||
|
||||
_eventBus.PublishRobotModelUpdated(eventData);
|
||||
_logger.Debug($"Published RobotModelUpdated event for ModelId {robotModel.Id} affecting {affectedRobots.Count} robot(s)");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error($"Error publishing RobotModelUpdated event: {ex.Message}");
|
||||
// Don't fail the update if event publishing fails
|
||||
}
|
||||
}
|
||||
|
||||
return robotModel;
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAsync(Guid id)
|
||||
{
|
||||
var robotModel = await _context.RobotModels
|
||||
.Include(rm => rm.Robots)
|
||||
.FirstOrDefaultAsync(rm => rm.Id == id);
|
||||
|
||||
if (robotModel == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if there are any robots using this model
|
||||
if (robotModel.Robots.Count != 0)
|
||||
{
|
||||
throw new InvalidOperationException($"Cannot delete robot model '{robotModel.ModelName}' because it is being used by {robotModel.Robots.Count} robot(s).");
|
||||
}
|
||||
|
||||
_context.RobotModels.Remove(robotModel);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.Info($"Deleted robot model {robotModel.ModelName} with ID {robotModel.Id}");
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsAsync(string modelName)
|
||||
{
|
||||
return await _context.RobotModels
|
||||
.AnyAsync(rm => rm.ModelName == modelName);
|
||||
}
|
||||
|
||||
public async Task<List<RobotModel>> SearchAsync(string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
return await GetAllAsync();
|
||||
}
|
||||
|
||||
var lowerQuery = query.ToLowerInvariant();
|
||||
return await _context.RobotModels
|
||||
.AsNoTracking()
|
||||
.Where(rm => rm.ModelName.ToLower().Contains(lowerQuery))
|
||||
.OrderBy(rm => rm.ModelName)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<RobotModelUsageInfoDto> GetUsageInfoAsync(Guid id)
|
||||
{
|
||||
var robotModel = await _context.RobotModels
|
||||
.AsNoTracking()
|
||||
.Include(rm => rm.Robots)
|
||||
.FirstOrDefaultAsync(rm => rm.Id == id);
|
||||
|
||||
return robotModel == null
|
||||
? throw new KeyNotFoundException($"Robot model with ID {id} not found.")
|
||||
: new RobotModelUsageInfoDto
|
||||
{
|
||||
Id = robotModel.Id,
|
||||
ModelName = robotModel.ModelName,
|
||||
RobotCount = robotModel.Robots.Count
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user