Initial commit

This commit is contained in:
2026-07-13 09:25:40 +07:00
parent c08ff54676
commit bccfb156d7
1938 changed files with 641646 additions and 0 deletions

View File

@@ -0,0 +1,370 @@
using RobotNet10.FleetManager.Shared.DTOs.RobotModel;
using RobotNet10.MapEditor.Shared.DTOs.Edge;
using RobotNet10.MapEditor.Shared.DTOs.Node;
using RobotNet10.MapManager.Data;
using RobotNet10.MapManager.Services;
namespace RobotNet10.FleetManager.Services;
/// <summary>
/// Service implementation for managing robot model map data based on VehicleType filtering
/// </summary>
public class RobotModelMapService(
IRobotModelService robotModelService,
IVehicleTypeService vehicleTypeService,
IMapQueryService mapQueryService,
ILayoutService layoutService,
Logger<RobotModelMapService> logger) : IRobotModelMapService
{
private readonly IRobotModelService _robotModelService = robotModelService;
private readonly IVehicleTypeService _vehicleTypeService = vehicleTypeService;
private readonly IMapQueryService _mapQueryService = mapQueryService;
private readonly ILayoutService _layoutService = layoutService;
private readonly Logger<RobotModelMapService> _logger = logger;
public async Task<List<Node>> GetFilteredNodesAsync(Guid robotModelId)
{
// Get RobotModel
var robotModel = await _robotModelService.GetByIdAsync(robotModelId)
?? throw new KeyNotFoundException($"RobotModel with ID {robotModelId} not found.");
// Validate VehicleTypeId is set
if (!robotModel.VehicleTypeId.HasValue)
{
throw new InvalidOperationException($"RobotModel '{robotModel.ModelName}' does not have VehicleTypeId set.");
}
var vehicleTypeId = robotModel.VehicleTypeId.Value;
// Validate VehicleType exists
var vehicleType = await _vehicleTypeService.GetByIdAsync(vehicleTypeId)
?? throw new KeyNotFoundException($"VehicleType with ID {vehicleTypeId} not found.");
if (!vehicleType.IsActive)
{
_logger.Warning($"VehicleType '{vehicleType.VehicleTypeName}' is not active.");
}
// Get filtered nodes: nodes that have NodeVehicleProperties for this VehicleType
var filteredNodes = await _mapQueryService.GetNodesByVehicleTypeAsync(vehicleTypeId);
_logger.Info($"Found {filteredNodes.Count} filtered nodes for RobotModel '{robotModel.ModelName}' (VehicleType: '{vehicleType.VehicleTypeName}')");
return filteredNodes;
}
public async Task<List<Edge>> GetFilteredEdgesAsync(Guid robotModelId)
{
// Get RobotModel
var robotModel = await _robotModelService.GetByIdAsync(robotModelId)
?? throw new KeyNotFoundException($"RobotModel with ID {robotModelId} not found.");
// Validate VehicleTypeId is set
if (!robotModel.VehicleTypeId.HasValue)
{
throw new InvalidOperationException($"RobotModel '{robotModel.ModelName}' does not have VehicleTypeId set.");
}
var vehicleTypeId = robotModel.VehicleTypeId.Value;
// Validate VehicleType exists
var vehicleType = await _vehicleTypeService.GetByIdAsync(vehicleTypeId)
?? throw new KeyNotFoundException($"VehicleType with ID {vehicleTypeId} not found.");
// Get filtered edges: edges that have EdgeVehicleProperties for this VehicleType
var filteredEdges = await _mapQueryService.GetEdgesByVehicleTypeAsync(vehicleTypeId);
_logger.Info($"Found {filteredEdges.Count} filtered edges for RobotModel '{robotModel.ModelName}' (VehicleType: '{vehicleType.VehicleTypeName}')");
return filteredEdges;
}
public async Task<List<Node>> GetFilteredNodesByLevelAsync(Guid robotModelId, Guid levelId)
{
// Get RobotModel
var robotModel = await _robotModelService.GetByIdAsync(robotModelId)
?? throw new KeyNotFoundException($"RobotModel with ID {robotModelId} not found.");
// Validate VehicleTypeId is set
if (!robotModel.VehicleTypeId.HasValue)
{
throw new InvalidOperationException($"RobotModel '{robotModel.ModelName}' does not have VehicleTypeId set.");
}
var vehicleTypeId = robotModel.VehicleTypeId.Value;
// Validate VehicleType exists
var vehicleType = await _vehicleTypeService.GetByIdAsync(vehicleTypeId)
?? throw new KeyNotFoundException($"VehicleType with ID {vehicleTypeId} not found.");
if (!vehicleType.IsActive)
{
_logger.Warning($"VehicleType '{vehicleType.VehicleTypeName}' is not active.");
}
// Get filtered nodes: nodes that have NodeVehicleProperties for this VehicleType and belong to the specified level
var filteredNodes = await _mapQueryService.GetNodesByVehicleTypeAndLevelAsync(vehicleTypeId, levelId);
_logger.Info($"Found {filteredNodes.Count} filtered nodes for RobotModel '{robotModel.ModelName}' (VehicleType: '{vehicleType.VehicleTypeName}') in LevelId={levelId}");
return filteredNodes;
}
public async Task<List<Edge>> GetFilteredEdgesByLevelAsync(Guid robotModelId, Guid levelId)
{
// Get RobotModel
var robotModel = await _robotModelService.GetByIdAsync(robotModelId)
?? throw new KeyNotFoundException($"RobotModel with ID {robotModelId} not found.");
// Validate VehicleTypeId is set
if (!robotModel.VehicleTypeId.HasValue)
{
throw new InvalidOperationException($"RobotModel '{robotModel.ModelName}' does not have VehicleTypeId set.");
}
var vehicleTypeId = robotModel.VehicleTypeId.Value;
// Validate VehicleType exists
var vehicleType = await _vehicleTypeService.GetByIdAsync(vehicleTypeId)
?? throw new KeyNotFoundException($"VehicleType with ID {vehicleTypeId} not found.");
// Get filtered edges: edges that have EdgeVehicleProperties for this VehicleType and belong to the specified level
var filteredEdges = await _mapQueryService.GetEdgesByVehicleTypeAndLevelAsync(vehicleTypeId, levelId);
_logger.Info($"Found {filteredEdges.Count} filtered edges for RobotModel '{robotModel.ModelName}' (VehicleType: '{vehicleType.VehicleTypeName}') in LevelId={levelId}");
return filteredEdges;
}
public async Task<RobotModelMapDataDto> GetValidatedMapDataAsync(Guid robotModelId)
{
// Get RobotModel
var robotModel = await _robotModelService.GetByIdAsync(robotModelId)
?? throw new KeyNotFoundException($"RobotModel with ID {robotModelId} not found.");
// Validate VehicleTypeId is set
if (!robotModel.VehicleTypeId.HasValue)
{
throw new InvalidOperationException($"RobotModel '{robotModel.ModelName}' does not have VehicleTypeId set.");
}
var vehicleTypeId = robotModel.VehicleTypeId.Value;
// Get VehicleType
var vehicleType = await _vehicleTypeService.GetByIdAsync(vehicleTypeId)
?? throw new KeyNotFoundException($"VehicleType with ID {vehicleTypeId} not found.");
// Get filtered nodes and edges (from all levels that match VehicleType)
// Note: If RobotModel needs to filter by specific LevelId, MapId should be added to RobotModel
var filteredNodes = await GetFilteredNodesAsync(robotModelId);
var filteredEdges = await GetFilteredEdgesAsync(robotModelId);
// Get total counts across all levels (for reference)
// In the future, if RobotModel has MapId (LevelId), we can filter by specific level
int totalNodesInLevel = await _mapQueryService.GetTotalNodesCountAsync();
int totalEdgesInLevel = await _mapQueryService.GetTotalEdgesCountAsync();
string? levelName = null;
Guid? levelId = null;
// If we have filtered nodes, get the level from first node (for display)
if (filteredNodes.Count > 0)
{
var firstNode = filteredNodes.First();
var level = await _layoutService.GetLevelAsync(firstNode.LevelId);
levelName = level?.LayoutLevelId;
levelId = level?.Id;
}
// Validate: Remove nodes without edges, edges without both nodes
var nodeIds = new HashSet<Guid>(filteredNodes.Select(n => n.Id));
var validNodeIds = new HashSet<Guid>();
var validEdges = new List<Edge>();
// First pass: Find edges that have both start and end nodes in filtered nodes
foreach (var edge in filteredEdges)
{
if (nodeIds.Contains(edge.StartNodeId) && nodeIds.Contains(edge.EndNodeId))
{
validEdges.Add(edge);
validNodeIds.Add(edge.StartNodeId);
validNodeIds.Add(edge.EndNodeId);
}
}
// Second pass: Keep only nodes that are connected by valid edges
var validNodes = filteredNodes.Where(n => validNodeIds.Contains(n.Id)).ToList();
// Count removed items
int nodesRemoved = filteredNodes.Count - validNodes.Count;
int edgesRemoved = filteredEdges.Count - validEdges.Count;
// Build validation result
var validationResult = new MapValidationResultDto
{
IsValid = nodesRemoved == 0 && edgesRemoved == 0,
NodesRemoved = nodesRemoved,
EdgesRemoved = edgesRemoved
};
// Add errors for removed nodes
foreach (var node in filteredNodes.Where(n => !validNodeIds.Contains(n.Id)))
{
validationResult.Errors.Add(new ValidationError
{
Code = "NODE_NO_EDGES",
Message = $"Node '{node.NodeId}' has no connected edges in the filtered map",
EntityId = node.Id.ToString(),
EntityType = "Node"
});
}
// Add errors for removed edges
foreach (var edge in filteredEdges.Where(e => !validEdges.Contains(e)))
{
var missingStart = !nodeIds.Contains(edge.StartNodeId);
var missingEnd = !nodeIds.Contains(edge.EndNodeId);
if (missingStart && missingEnd)
{
validationResult.Errors.Add(new ValidationError
{
Code = "EDGE_MISSING_BOTH_NODES",
Message = $"Edge '{edge.EdgeId}' is missing both start and end nodes",
EntityId = edge.Id.ToString(),
EntityType = "Edge"
});
}
else if (missingStart)
{
validationResult.Errors.Add(new ValidationError
{
Code = "EDGE_MISSING_START_NODE",
Message = $"Edge '{edge.EdgeId}' is missing start node",
EntityId = edge.Id.ToString(),
EntityType = "Edge"
});
}
else if (missingEnd)
{
validationResult.Errors.Add(new ValidationError
{
Code = "EDGE_MISSING_END_NODE",
Message = $"Edge '{edge.EdgeId}' is missing end node",
EntityId = edge.Id.ToString(),
EntityType = "Edge"
});
}
}
// Convert to DTOs
var nodeDtos = validNodes.Select(n => new NodeDto
{
Id = n.Id,
LevelId = n.LevelId,
NodeId = n.NodeId,
NodeName = n.NodeName,
NodeDescription = n.NodeDescription,
MapId = n.MapId,
X = n.X,
Y = n.Y,
VehicleProperties = [.. n.VehicleProperties
.Where(vp => vp.VehicleTypeId == vehicleTypeId)
.Select(vp => new NodeVehiclePropertyDto
{
Id = vp.Id,
NodeId = vp.NodeId,
VehicleTypeId = vp.VehicleTypeId,
Theta = vp.Theta,
Actions = vp.Actions
})]
}).ToList();
var edgeDtos = validEdges.Select(e => new EdgeDto
{
Id = e.Id,
LevelId = e.LevelId,
EdgeId = e.EdgeId,
EdgeName = e.EdgeName,
EdgeDescription = e.EdgeDescription,
StartNodeId = e.StartNodeId,
EndNodeId = e.EndNodeId,
StartNode = nodeDtos.FirstOrDefault(n => n.Id == e.StartNodeId),
EndNode = nodeDtos.FirstOrDefault(n => n.Id == e.EndNodeId),
VehicleProperties = [.. e.VehicleProperties
.Where(vp => vp.VehicleTypeId == vehicleTypeId)
.Select(vp => new EdgeVehiclePropertyDto
{
Id = vp.Id,
EdgeId = vp.EdgeId,
VehicleTypeId = vp.VehicleTypeId,
VehicleOrientation = vp.VehicleOrientation,
OrientationType = vp.OrientationType,
RotationAllowed = vp.RotationAllowed,
RotationAtStartNodeAllowed = vp.RotationAtStartNodeAllowed,
RotationAtEndNodeAllowed = vp.RotationAtEndNodeAllowed,
MaxSpeed = vp.MaxSpeed,
MaxRotationSpeed = vp.MaxRotationSpeed,
MinHeight = vp.MinHeight,
MaxHeight = vp.MaxHeight,
LoadRestriction = null,
Actions = vp.Actions
})]
}).ToList();
var result = new RobotModelMapDataDto
{
RobotModelId = robotModel.Id,
RobotModelName = robotModel.ModelName,
VehicleTypeId = vehicleTypeId,
VehicleTypeName = vehicleType.VehicleTypeName,
LevelId = levelId,
LevelName = levelName,
ValidNodes = nodeDtos,
ValidEdges = edgeDtos,
TotalNodesInLevel = totalNodesInLevel,
TotalEdgesInLevel = totalEdgesInLevel,
FilteredNodesCount = filteredNodes.Count,
FilteredEdgesCount = filteredEdges.Count,
ValidationResult = validationResult
};
_logger.Info($"Validated map data for RobotModel '{robotModel.ModelName}': {validNodes.Count} valid nodes, {validEdges.Count} valid edges (removed {nodesRemoved} nodes, {edgesRemoved} edges)");
return result;
}
public async Task<MapValidationResultDto> ValidateMapForRobotModelAsync(Guid robotModelId)
{
var mapData = await GetValidatedMapDataAsync(robotModelId);
return mapData.ValidationResult;
}
public async Task<bool> HasValidMapConfigurationAsync(Guid robotModelId)
{
try
{
var robotModel = await _robotModelService.GetByIdAsync(robotModelId);
if (robotModel == null)
return false;
if (!robotModel.VehicleTypeId.HasValue)
return false;
// Check if VehicleType exists and is active
var vehicleType = await _vehicleTypeService.GetByIdAsync(robotModel.VehicleTypeId.Value);
if (vehicleType == null || !vehicleType.IsActive)
return false;
// Validate map data
var validationResult = await ValidateMapForRobotModelAsync(robotModelId);
return validationResult.IsValid;
}
catch (Exception ex)
{
_logger.Error($"Error checking valid map configuration for RobotModel {robotModelId}: {ex.Message}");
return false;
}
}
}