private Task <Result <GetRouteNetworkDetailsResult> > QueryByInterestIds(GetRouteNetworkDetails query)
        {
            RouteNetworkElementIdList routeElementsToQuery = new RouteNetworkElementIdList();

            List <RouteNetworkInterest> interestsToReturn = new List <RouteNetworkInterest>();

            // Find all interest to return and create a list of route network elements at the same time
            var interestsProjection = _eventStore.Projections.Get <InterestsProjection>();

            foreach (var interestId in query.InterestIdsToQuery)
            {
                var interestQueryResult = interestsProjection.GetInterest(interestId);

                // Here we return a error result, because we're dealing with invalid interest ids provided by the client
                if (interestQueryResult.IsFailed)
                {
                    return(Task.FromResult(
                               Result.Fail <GetRouteNetworkDetailsResult>(new GetRouteNetworkDetailsError(GetRouteNetworkDetailsErrorCodes.INVALID_QUERY_ARGUMENT_ERROR_LOOKING_UP_SPECIFIED_INTEREST_BY_ID, $"Error looking up interest by id: {interestId}")).
                               WithError(interestQueryResult.Errors.First())
                               ));
                }

                interestsToReturn.Add(interestQueryResult.Value);

                routeElementsToQuery.AddRange(interestQueryResult.Value.RouteNetworkElementRefs);
            }

            // TODO: Fix so that we don't create substitudes when something is deleted
            var getRouteNetworkElementsResult = _routeNodeRepository.GetRouteElements(routeElementsToQuery, true);

            // Here we create an exception, because this situation should not be allowed by the system
            if (getRouteNetworkElementsResult.IsFailed)
            {
                throw new ApplicationException($"Unexpected error querying route elements referenced by interests. All the interest exists and therefore the route network elements must also exists. Validation that route elements having interest cannot be deleted seems not to be working! Initial query:\r\n" + JsonConvert.SerializeObject(query));
            }

            var mappedRouteNetworkElements = MapRouteElementDomainObjectsToQueryObjects(query.RouteNetworkElementFilter, getRouteNetworkElementsResult.Value);

            var queryResult = new GetRouteNetworkDetailsResult(mappedRouteNetworkElements, interestsToReturn.ToArray());

            // Add interest reference information
            AddInterestReferencesToRouteNetworkElements(query, queryResult);

            return(Task.FromResult(
                       Result.Ok <GetRouteNetworkDetailsResult>(
                           queryResult
                           )
                       ));
        }
        private RouteNetworkElementIdList MergeWalks(ValidatedRouteNetworkWalk firstSpanEquipmentWalk, ValidatedRouteNetworkWalk secondSpanEquipmentWalk)
        {
            var result = new RouteNetworkElementIdList();

            // first span equipment -> second span equipment
            if (firstSpanEquipmentWalk.ToNodeId == secondSpanEquipmentWalk.FromNodeId)
            {
                result.AddRange(firstSpanEquipmentWalk.SegmentIds);
                result.AddRange(secondSpanEquipmentWalk.SegmentIds);
            }
            // first span equipment -> second span equipment (reversed)
            else if (firstSpanEquipmentWalk.ToNodeId == secondSpanEquipmentWalk.ToNodeId)
            {
                secondSpanEquipmentWalk = secondSpanEquipmentWalk.Reverse();

                result.AddRange(firstSpanEquipmentWalk.SegmentIds);
                result.AddRange(secondSpanEquipmentWalk.SegmentIds);
            }
            // second span equipment -> first span equipment
            else if (firstSpanEquipmentWalk.FromNodeId == secondSpanEquipmentWalk.ToNodeId)
            {
                result.AddRange(secondSpanEquipmentWalk.SegmentIds);
                result.AddRange(firstSpanEquipmentWalk.SegmentIds);
            }
            // second span equipment (reversed) -> first span equipment
            else if (firstSpanEquipmentWalk.FromNodeId == secondSpanEquipmentWalk.FromNodeId)
            {
                secondSpanEquipmentWalk = secondSpanEquipmentWalk.Reverse();

                result.AddRange(secondSpanEquipmentWalk.SegmentIds);
                result.AddRange(firstSpanEquipmentWalk.SegmentIds);
            }
            else
            {
                throw new ApplicationException("Merge Walk logic is broken");
            }

            return(result);
        }
        public Task <Result> HandleAsync(MoveSpanEquipment command)
        {
            var utilityNetwork = _eventStore.Projections.Get <UtilityNetworkProjection>();

            // Because the client is allowed to provide either a span equipment or segment id, we need look it up via the utility network graph
            if (!utilityNetwork.TryGetEquipment <SpanEquipment>(command.SpanEquipmentOrSegmentId, out SpanEquipment spanEquipment))
            {
                return(Task.FromResult(Result.Fail(new MoveSpanEquipmentError(MoveSpanEquipmentErrorCodes.SPAN_EQUIPMENT_NOT_FOUND, $"Cannot find any span equipment or segment in the utility graph with id: {command.SpanEquipmentOrSegmentId}"))));
            }

            // Get interest information from existing span equipment
            var existingWalk = GetInterestInformation(spanEquipment);

            // Validate the new walk
            var newWalkValidationResult = _commandDispatcher.HandleAsync <ValidateWalkOfInterest, Result <ValidatedRouteNetworkWalk> >(new ValidateWalkOfInterest(command.NewWalkIds)).Result;

            // If the new walk fails to validate, return the error to the client
            if (newWalkValidationResult.IsFailed)
            {
                return(Task.FromResult(Result.Fail(newWalkValidationResult.Errors.First())));
            }

            var newWalk = newWalkValidationResult.Value;

            // If the walk has not changed return error as well
            if (existingWalk.Equals(newWalk))
            {
                return(Task.FromResult(Result.Fail(new MoveSpanEquipmentError(MoveSpanEquipmentErrorCodes.NEW_WALK_EQUALS_EXISTING_WALK, $"The new walk specified is not different from the existing walk of the span equipment."))));
            }

            // Reverse new walk if one of its ends are opposite of existing walk ends
            if (newWalk.FromNodeId == existingWalk.ToNodeId || newWalk.ToNodeId == existingWalk.FromNodeId)
            {
                newWalk = newWalk.Reverse();
            }

            // Try to do the move of the span equipment
            var spanEquipmentAR = _eventStore.Aggregates.Load <SpanEquipmentAR>(spanEquipment.Id);

            var commandContext = new CommandContext(command.CorrelationId, command.CmdId, command.UserContext);

            var moveSpanEquipmentResult = spanEquipmentAR.Move(commandContext, newWalk, existingWalk);

            if (moveSpanEquipmentResult.IsFailed)
            {
                return(Task.FromResult(Result.Fail(moveSpanEquipmentResult.Errors.First())));
            }

            // If we got to here, then the span equipment move was validated fine, so we can update the walk of interest
            var newSegmentIds = new RouteNetworkElementIdList();

            newSegmentIds.AddRange(newWalk.SegmentIds);

            var updateWalkOfInterestCommand = new UpdateWalkOfInterest(spanEquipment.WalkOfInterestId, newSegmentIds)
            {
                UserContext = commandContext.UserContext
            };

            var updateWalkOfInterestCommandResult = _commandDispatcher.HandleAsync <UpdateWalkOfInterest, Result <RouteNetworkInterest> >(updateWalkOfInterestCommand).Result;

            if (updateWalkOfInterestCommandResult.IsFailed)
            {
                throw new ApplicationException($"Got unexpected error result: {updateWalkOfInterestCommandResult.Errors.First().Message} trying to update walk of interest belonging to span equipment with id: {spanEquipment.Id} while processing the MoveSpanEquipment command: " + JsonConvert.SerializeObject(command));
            }

            _eventStore.Aggregates.Store(spanEquipmentAR);

            NotifyExternalServicesAboutSpanEquipmentChange(spanEquipment.Id, existingWalk, newWalk);

            return(Task.FromResult(moveSpanEquipmentResult));
        }
Esempio n. 4
0
        private void PlaceConduit(string externalId, Guid specificationId, List <Guid> segmentIds, List <Guid> additionalStructureSpecIds, string markingColor)
        {
            Guid correlationId = Guid.NewGuid();

            RouteNetworkElementIdList walkIds = new RouteNetworkElementIdList();

            walkIds.AddRange(segmentIds);

            // Register walk of interest
            var walkOfInterestId = Guid.NewGuid();
            var registerWalkOfInterestCommand = new RegisterWalkOfInterest(correlationId, new UserContext("conversion", _bentleyMultiConduitConversion), walkOfInterestId, walkIds);

            var registerWalkOfInterestCommandResult = _commandDispatcher.HandleAsync <RegisterWalkOfInterest, Result <RouteNetworkInterest> >(registerWalkOfInterestCommand).Result;

            if (registerWalkOfInterestCommandResult.IsFailed)
            {
                _logger.LogInformation("Failed to add conduit: " + externalId + " Error: " + registerWalkOfInterestCommandResult.Errors.First().Message);
                return;
            }

            // conduit name
            var nextConduitSeqStr = _eventStore.Sequences.GetNextVal("conduit").ToString();

            var conduitName = "R" + nextConduitSeqStr.PadLeft(6, '0');
            var namingInfo  = new NamingInfo(conduitName, null);

            // Place conduit
            var placeSpanEquipmentCommand = new PlaceSpanEquipmentInRouteNetwork(correlationId, new UserContext("conversion", _bentleyMultiConduitConversion), Guid.NewGuid(), specificationId, registerWalkOfInterestCommandResult.Value)
            {
                MarkingInfo = markingColor != null ? new MarkingInfo()
                {
                    MarkingColor = markingColor
                } : null,
                NamingInfo = namingInfo
            };

            var placeSpanEquipmentResult = _commandDispatcher.HandleAsync <PlaceSpanEquipmentInRouteNetwork, Result>(placeSpanEquipmentCommand).Result;

            if (placeSpanEquipmentResult.IsFailed)
            {
                _logger.LogInformation("Failed to add conduit: " + externalId + " Error: " + placeSpanEquipmentResult.Errors.First().Message);
                return;
            }

            // Place additional structures
            if (additionalStructureSpecIds.Count > 0)
            {
                var addStructure = new PlaceAdditionalStructuresInSpanEquipment(correlationId, new UserContext("conversion", _bentleyMultiConduitConversion),
                                                                                spanEquipmentId: placeSpanEquipmentCommand.SpanEquipmentId,
                                                                                structureSpecificationIds: additionalStructureSpecIds.ToArray()
                                                                                );

                var addStructureResult = _commandDispatcher.HandleAsync <PlaceAdditionalStructuresInSpanEquipment, Result>(addStructure).Result;

                if (addStructureResult.IsFailed)
                {
                    _logger.LogInformation("Failed to add additional structures to: " + externalId + " Error: " + placeSpanEquipmentResult.Errors.First().Message);
                    return;
                }
            }
        }
Esempio n. 5
0
        public Result PlaceCableSpanEquipment(NpgsqlCommand logCmd, Guid spanEquipmentId, string externalId, Guid specificationId, List <Guid> routeSegmentIds, List <CableConduitRel> conduitRels)
        {
            Guid correlationId = Guid.NewGuid();

            RouteNetworkElementIdList walkIds = new RouteNetworkElementIdList();

            walkIds.AddRange(routeSegmentIds);

            // Cable name
            var nextConduitSeqStr = _eventStore.Sequences.GetNextVal("cable").ToString();
            var conduitName       = "K" + nextConduitSeqStr.PadLeft(6, '0');
            var namingInfo        = new NamingInfo(conduitName, null);

            // HACK use NE id
            var neIdSplit = externalId.Split(':');

            namingInfo = new NamingInfo("K" + neIdSplit.Last(), null);

            System.Diagnostics.Debug.WriteLine("---------------------------------------------------------------------------------------------------------------------------------------");
            System.Diagnostics.Debug.WriteLine($"*** Place cable: {externalId} ***");
            System.Diagnostics.Debug.WriteLine("---------------------------------------------------------------------------------------------------------------------------------------");

            // Get validated walk of interest
            var walk = new RouteNetworkElementIdList();

            walk.AddRange(routeSegmentIds);

            var validateInterestCommand = new ValidateWalkOfInterest(correlationId, new UserContext("conversion", _workTaskId), walk);

            var validateInterestResult = _commandDispatcher.HandleAsync <ValidateWalkOfInterest, Result <ValidatedRouteNetworkWalk> >(validateInterestCommand).Result;

            if (validateInterestResult.IsFailed)
            {
                return(Result.Fail(validateInterestResult.Errors.First()));
            }

            // trace all conduits
            var conduitsTraceResult = TraceAllConduits(conduitRels);

            foreach (var conduitTrace in conduitsTraceResult)
            {
                System.Diagnostics.Debug.WriteLine($"NE conduit path found starting in {conduitTrace.ConduitName} node {conduitTrace.OriginalTrace.FromRouteNodeName} ({conduitTrace.OriginalTrace.FromRouteNodeId}) <-> {conduitTrace.OriginalTrace.ToRouteNodeName} ({conduitTrace.OriginalTrace.ToRouteNodeId}) span segment id: {conduitTrace.SpanSegmentId}");
            }

            var routingHops = BuildRouteHops(validateInterestResult.Value, conduitsTraceResult, externalId);

            System.Diagnostics.Debug.WriteLine("---------------------------------------------------------------------------------------------------------------------------------------");

            foreach (var hop in routingHops)
            {
                System.Diagnostics.Debug.WriteLine($"Routing hop: start route node id: {hop.StartRouteNode} span equipment id: {hop.StartSpanSegmentId}");
            }

            // Place cable
            var placeSpanEquipmentCommand = new PlaceSpanEquipmentInUtilityNetwork(correlationId, new UserContext("conversion", _workTaskId), spanEquipmentId, specificationId, routingHops.ToArray())
            {
                NamingInfo = namingInfo,
            };


            var placeSpanEquipmentResult = _commandDispatcher.HandleAsync <PlaceSpanEquipmentInUtilityNetwork, Result>(placeSpanEquipmentCommand).Result;

            if (placeSpanEquipmentResult.IsFailed)
            {
                var errorMsg = "Failed to route cable: " + externalId + " through conduit network: " + placeSpanEquipmentResult.Errors.First().Message;

                System.Diagnostics.Debug.WriteLine("---------------------------------------------------------------------------------------------------------------------------------------");
                System.Diagnostics.Debug.WriteLine(errorMsg);

                _logger.LogInformation(errorMsg);

                // Try place cable directly in route network
                var placeSpanEquipmentDirectlyInRouteNetworkCmd = new PlaceSpanEquipmentInUtilityNetwork(correlationId, new UserContext("conversion", _workTaskId), spanEquipmentId, specificationId,
                                                                                                         new RoutingHop[] { new RoutingHop(validateInterestResult.Value.RouteNetworkElementRefs.ToArray()) }
                                                                                                         )
                {
                    NamingInfo = namingInfo,
                };

                var placeSpanEquipmentDirectlyInRouteNetworkResult = _commandDispatcher.HandleAsync <PlaceSpanEquipmentInUtilityNetwork, Result>(placeSpanEquipmentDirectlyInRouteNetworkCmd).Result;

                if (placeSpanEquipmentDirectlyInRouteNetworkResult.IsFailed)
                {
                    errorMsg = "GENERAL FAILURE PLACING DIRECTLY IN ROUTE NETWORK: " + placeSpanEquipmentResult.Errors.First().Message + " " + errorMsg;
                }

                LogStatus((NpgsqlCommand)logCmd, _tableName, errorMsg, externalId);
                return(placeSpanEquipmentResult);
            }

            return(Result.Ok());
        }
Esempio n. 6
0
        public SpanEquipmentMutations(ICommandDispatcher commandDispatcher, IEventStore eventStore)
        {
            Description = "Span equipment mutations";

            FieldAsync <CommandResultType>(
                "placeSpanEquipmentInRouteNetwork",
                description: "Place a span equipment (i.e. conduit, cable whatwever) in the route network",
                arguments: new QueryArguments(
                    new QueryArgument <NonNullGraphType <IdGraphType> > {
                Name = "spanEquipmentId"
            },
                    new QueryArgument <NonNullGraphType <IdGraphType> > {
                Name = "spanEquipmentSpecificationId"
            },
                    new QueryArgument <NonNullGraphType <ListGraphType <IdGraphType> > > {
                Name = "routeSegmentIds"
            },
                    new QueryArgument <IdGraphType> {
                Name = "manufacturerId"
            },
                    new QueryArgument <MarkingInfoInputType> {
                Name = "markingInfo"
            },
                    new QueryArgument <NamingInfoInputType> {
                Name = "namingInfo"
            },
                    new QueryArgument <AddressInfoInputType> {
                Name = "addressInfo"
            }
                    ),
                resolve: async context =>
            {
                var spanEquipmentId = context.GetArgument <Guid>("spanEquipmentId");
                var spanEquipmentSpecificationId = context.GetArgument <Guid>("spanEquipmentSpecificationId");
                var routeSegmentIds = context.GetArgument <List <Guid> >("routeSegmentIds");
                var manufacturerId  = context.GetArgument <Guid>("manufacturerId");
                var markingInfo     = context.GetArgument <MarkingInfo>("markingInfo");
                var namingInfo      = context.GetArgument <NamingInfo>("namingInfo");
                var addressInfo     = context.GetArgument <AddressInfo>("addressInfo");

                // Name conduit span equipment
                // TODO: Refactor into som class responsible for span equipment naming
                var spec   = eventStore.Projections.Get <SpanEquipmentSpecificationsProjection>().Specifications[spanEquipmentSpecificationId];
                namingInfo = CalculateName(eventStore, namingInfo, spec);

                var correlationId = Guid.NewGuid();

                var userContext = context.UserContext as GraphQLUserContext;
                var userName    = userContext.Username;

                // TODO: Get from work manager
                var workTaskId         = Guid.Parse("54800ae5-13a5-4b03-8626-a63b66a25568");
                var commandUserContext = new UserContext(userName, workTaskId);

                var spanEquipments = eventStore.Projections.Get <UtilityNetworkProjection>().SpanEquipmentsByEquipmentId;

                // First register the walk in the route network where the client want to place the span equipment
                var walkOfInterestId = Guid.NewGuid();
                var walk             = new RouteNetworkElementIdList();
                walk.AddRange(routeSegmentIds);

                var registerWalkOfInterestCommand = new RegisterWalkOfInterest(correlationId, commandUserContext, walkOfInterestId, walk);

                var registerWalkOfInterestCommandResult = await commandDispatcher.HandleAsync <RegisterWalkOfInterest, Result <RouteNetworkInterest> >(registerWalkOfInterestCommand);

                if (registerWalkOfInterestCommandResult.IsFailed)
                {
                    return(new CommandResult(registerWalkOfInterestCommandResult));
                }

                // Now place the conduit in the walk
                var placeSpanEquipmentCommand = new PlaceSpanEquipmentInRouteNetwork(
                    correlationId, commandUserContext, spanEquipmentId, spanEquipmentSpecificationId, registerWalkOfInterestCommandResult.Value)
                {
                    ManufacturerId = manufacturerId,
                    NamingInfo     = namingInfo,
                    MarkingInfo    = markingInfo,
                    LifecycleInfo  = new LifecycleInfo(DeploymentStateEnum.InService, null, null),
                    AddressInfo    = addressInfo
                };

                var placeSpanEquipmentResult = await commandDispatcher.HandleAsync <PlaceSpanEquipmentInRouteNetwork, Result>(placeSpanEquipmentCommand);

                // Unregister interest if place span equipment failed
                if (placeSpanEquipmentResult.IsFailed)
                {
                    var unregisterCommandResult = await commandDispatcher.HandleAsync <UnregisterInterest, Result>(
                        new UnregisterInterest(correlationId, commandUserContext, walkOfInterestId));

                    if (unregisterCommandResult.IsFailed)
                    {
                        return(new CommandResult(unregisterCommandResult));
                    }
                }

                return(new CommandResult(placeSpanEquipmentResult));
            }
                );

            FieldAsync <CommandResultType>(
                "affixSpanEquipmentToNodeContainer",
                description: "Affix a span equipment to a node container - i.e. to some condult closure, man hole etc.",
                arguments: new QueryArguments(
                    new QueryArgument <NonNullGraphType <ListGraphType <IdGraphType> > > {
                Name = "spanSegmentIds"
            },
                    new QueryArgument <NonNullGraphType <IdGraphType> > {
                Name = "nodeContainerId"
            },
                    new QueryArgument <NonNullGraphType <NodeContainerSideEnumType> > {
                Name = "nodeContainerSide"
            }
                    ),
                resolve: async context =>
            {
                var spanSegmentIds  = context.GetArgument <List <Guid> >("spanSegmentIds");
                var nodeContainerId = context.GetArgument <Guid>("nodeContainerId");
                var side            = context.GetArgument <NodeContainerSideEnum>("nodeContainerSide");

                var correlationId = Guid.NewGuid();

                var userContext = context.UserContext as GraphQLUserContext;
                var userName    = userContext.Username;

                // TODO: Get from work manager
                var workTaskId = Guid.Parse("54800ae5-13a5-4b03-8626-a63b66a25568");

                var commandUserContext = new UserContext(userName, workTaskId);

                foreach (var spanSegmentId in spanSegmentIds)
                {
                    var affixCommand = new AffixSpanEquipmentToNodeContainer(correlationId, commandUserContext, spanSegmentId, nodeContainerId, side);

                    var affixCommandResult = await commandDispatcher.HandleAsync <AffixSpanEquipmentToNodeContainer, Result>(affixCommand);

                    if (affixCommandResult.IsFailed)
                    {
                        return(new CommandResult(affixCommandResult));
                    }
                }

                return(new CommandResult(Result.Ok()));
            }
                );

            FieldAsync <CommandResultType>(
                "detachSpanEquipmentFromNodeContainer",
                description: "Detach a span equipment from a node container - i.e. from some condult closure, man hole etc.",
                arguments: new QueryArguments(
                    new QueryArgument <ListGraphType <IdGraphType> > {
                Name = "spanSegmentIds"
            },
                    new QueryArgument <NonNullGraphType <IdGraphType> > {
                Name = "routeNodeId"
            }
                    ),
                resolve: async context =>
            {
                var spanSegmentIds = context.GetArgument <List <Guid> >("spanSegmentIds");
                var routeNodeId    = context.GetArgument <Guid>("routeNodeId");

                var correlationId = Guid.NewGuid();

                var userContext = context.UserContext as GraphQLUserContext;
                var userName    = userContext.Username;

                // TODO: Get from work manager
                var workTaskId = Guid.Parse("54800ae5-13a5-4b03-8626-a63b66a25568");

                var commandUserContext = new UserContext(userName, workTaskId)
                {
                    EditingRouteNodeId = routeNodeId
                };

                foreach (var spanSegmentId in spanSegmentIds)
                {
                    var detachCommand       = new DetachSpanEquipmentFromNodeContainer(correlationId, commandUserContext, spanSegmentId, routeNodeId);
                    var detachCommandResult = await commandDispatcher.HandleAsync <DetachSpanEquipmentFromNodeContainer, Result>(detachCommand);

                    if (detachCommandResult.IsFailed)
                    {
                        return(new CommandResult(detachCommandResult));
                    }
                }

                return(new CommandResult(Result.Ok()));
            }
                );

            FieldAsync <CommandResultType>(
                "cutSpanSegments",
                description: "Cut the span segments belonging to som span equipment at the route node specified",
                arguments: new QueryArguments(
                    new QueryArgument <NonNullGraphType <IdGraphType> > {
                Name = "routeNodeId"
            },
                    new QueryArgument <NonNullGraphType <ListGraphType <IdGraphType> > > {
                Name = "spanSegmentstoCut"
            }
                    ),
                resolve: async context =>
            {
                var routeNodeId      = context.GetArgument <Guid>("routeNodeId");
                var spanSegmentToCut = context.GetArgument <Guid[]>("spanSegmentstoCut");

                var correlationId = Guid.NewGuid();

                var userContext = context.UserContext as GraphQLUserContext;
                var userName    = userContext.Username;

                // TODO: Get from work manager
                var workTaskId = Guid.Parse("54800ae5-13a5-4b03-8626-a63b66a25568");

                var commandUserContext = new UserContext(userName, workTaskId)
                {
                    EditingRouteNodeId = routeNodeId
                };

                var cutCmd    = new CutSpanSegmentsAtRouteNode(correlationId, commandUserContext, routeNodeId, spanSegmentToCut);
                var cutResult = await commandDispatcher.HandleAsync <CutSpanSegmentsAtRouteNode, Result>(cutCmd);

                return(new CommandResult(cutResult));
            }
                );

            FieldAsync <CommandResultType>(
                "connectSpanSegments",
                description: "Connect the span segments belonging to two different span equipment at the route node specified",
                arguments: new QueryArguments(
                    new QueryArgument <NonNullGraphType <IdGraphType> > {
                Name = "routeNodeId"
            },
                    new QueryArgument <NonNullGraphType <ListGraphType <IdGraphType> > > {
                Name = "spanSegmentsToConnect"
            }
                    ),
                resolve: async context =>
            {
                var routeNodeId          = context.GetArgument <Guid>("routeNodeId");
                var spanSegmentToConnect = context.GetArgument <Guid[]>("spanSegmentsToConnect");

                var correlationId = Guid.NewGuid();

                var userContext = context.UserContext as GraphQLUserContext;
                var userName    = userContext.Username;

                // TODO: Get from work manager
                var workTaskId = Guid.Parse("54800ae5-13a5-4b03-8626-a63b66a25568");

                var commandUserContext = new UserContext(userName, workTaskId)
                {
                    EditingRouteNodeId = routeNodeId
                };

                var connectCmd    = new ConnectSpanSegmentsAtRouteNode(correlationId, commandUserContext, routeNodeId, spanSegmentToConnect);
                var connectResult = await commandDispatcher.HandleAsync <ConnectSpanSegmentsAtRouteNode, Result>(connectCmd);

                return(new CommandResult(connectResult));
            }
                );

            FieldAsync <CommandResultType>(
                "disconnectSpanSegments",
                description: "Disconnect two span segments belonging to two different span equipment at the route node specified",
                arguments: new QueryArguments(
                    new QueryArgument <NonNullGraphType <IdGraphType> > {
                Name = "routeNodeId"
            },
                    new QueryArgument <NonNullGraphType <ListGraphType <IdGraphType> > > {
                Name = "spanSegmentsToDisconnect"
            }
                    ),
                resolve: async context =>
            {
                var routeNodeId             = context.GetArgument <Guid>("routeNodeId");
                var spanSegmentToDisconnect = context.GetArgument <Guid[]>("spanSegmentsToDisconnect");

                var correlationId = Guid.NewGuid();

                var userContext = context.UserContext as GraphQLUserContext;
                var userName    = userContext.Username;

                // TODO: Get from work manager
                var workTaskId = Guid.Parse("54800ae5-13a5-4b03-8626-a63b66a25568");

                var commandUserContext = new UserContext(userName, workTaskId)
                {
                    EditingRouteNodeId = routeNodeId
                };

                var disconnectCmd    = new DisconnectSpanSegmentsAtRouteNode(correlationId, commandUserContext, routeNodeId, spanSegmentToDisconnect);
                var disconnectResult = await commandDispatcher.HandleAsync <DisconnectSpanSegmentsAtRouteNode, Result>(disconnectCmd);

                return(new CommandResult(disconnectResult));
            }
                );

            FieldAsync <CommandResultType>(
                "addAdditionalInnerSpanStructures",
                description: "Add inner span structures to an existing span equipment",
                arguments: new QueryArguments(
                    new QueryArgument <NonNullGraphType <IdGraphType> > {
                Name = "spanEquipmentOrSegmentId"
            },
                    new QueryArgument <NonNullGraphType <ListGraphType <IdGraphType> > > {
                Name = "spanStructureSpecificationIds"
            }
                    ),
                resolve: async context =>
            {
                var spanEquipmentOrSegmentId = context.GetArgument <Guid>("spanEquipmentOrSegmentId");
                var specificationsId         = context.GetArgument <Guid[]>("spanStructureSpecificationIds");

                var correlationId = Guid.NewGuid();

                var userContext = context.UserContext as GraphQLUserContext;
                var userName    = userContext.Username;

                // TODO: Get from work manager
                var workTaskId = Guid.Parse("54800ae5-13a5-4b03-8626-a63b66a25568");

                var commandUserContext = new UserContext(userName, workTaskId);

                var addStructure = new PlaceAdditionalStructuresInSpanEquipment(
                    correlationId: correlationId,
                    userContext: commandUserContext,
                    spanEquipmentId: spanEquipmentOrSegmentId,
                    structureSpecificationIds: specificationsId
                    );

                var addStructureResult = await commandDispatcher.HandleAsync <PlaceAdditionalStructuresInSpanEquipment, Result>(addStructure);

                return(new CommandResult(addStructureResult));
            }
                );

            FieldAsync <CommandResultType>(
                "removeSpanStructure",
                description: "Remove inner or outer span structure of a span equipment. When the outer span structure is removed the entire span equipment is removed from the network.",
                arguments: new QueryArguments(
                    new QueryArgument <NonNullGraphType <IdGraphType> > {
                Name = "spanSegmentId"
            }
                    ),
                resolve: async context =>
            {
                var spanSegmentId = context.GetArgument <Guid>("spanSegmentId");

                var correlationId = Guid.NewGuid();

                var userContext = context.UserContext as GraphQLUserContext;
                var userName    = userContext.Username;

                // TODO: Get from work manager
                var workTaskId = Guid.Parse("54800ae5-13a5-4b03-8626-a63b66a25568");

                var commandUserContext = new UserContext(userName, workTaskId);

                var removeStructure = new RemoveSpanStructureFromSpanEquipment(
                    correlationId: correlationId,
                    userContext: commandUserContext,
                    spanSegmentId: spanSegmentId
                    );

                var removeStructureResult = await commandDispatcher.HandleAsync <RemoveSpanStructureFromSpanEquipment, Result>(removeStructure);

                return(new CommandResult(removeStructureResult));
            }
                );

            FieldAsync <CommandResultType>(
                "move",
                description: "Move a span equipment / change its walk in the route network",
                arguments: new QueryArguments(
                    new QueryArgument <NonNullGraphType <IdGraphType> > {
                Name = "spanEquipmentOrSegmentId"
            },
                    new QueryArgument <NonNullGraphType <ListGraphType <IdGraphType> > > {
                Name = "routeSegmentIds"
            }
                    ),
                resolve: async context =>
            {
                var spanEquipmentOrSegmentId = context.GetArgument <Guid>("spanEquipmentOrSegmentId");

                var correlationId = Guid.NewGuid();

                var userContext = context.UserContext as GraphQLUserContext;
                var userName    = userContext.Username;

                // TODO: Get from work manager
                var workTaskId = Guid.Parse("54800ae5-13a5-4b03-8626-a63b66a25568");

                var commandUserContext = new UserContext(userName, workTaskId);

                Guid[] routeSegmentIds = context.GetArgument <Guid[]>("routeSegmentIds");

                RouteNetworkElementIdList newWalkIds = new();
                newWalkIds.AddRange(routeSegmentIds);

                var moveCmd = new MoveSpanEquipment(
                    correlationId: correlationId,
                    userContext: commandUserContext,
                    spanEquipmentId: spanEquipmentOrSegmentId,
                    newWalkIds: newWalkIds
                    );

                var moveCmdResult = await commandDispatcher.HandleAsync <MoveSpanEquipment, Result>(moveCmd);

                return(new CommandResult(moveCmdResult));
            }
                );

            FieldAsync <CommandResultType>(
                "updateProperties",
                description: "Mutation that can be used to change the span equipment specification, manufacturer and/or marking information",
                arguments: new QueryArguments(
                    new QueryArgument <NonNullGraphType <IdGraphType> > {
                Name = "spanEquipmentOrSegmentId"
            },
                    new QueryArgument <IdGraphType> {
                Name = "spanEquipmentSpecificationId"
            },
                    new QueryArgument <IdGraphType> {
                Name = "manufacturerId"
            },
                    new QueryArgument <MarkingInfoInputType> {
                Name = "markingInfo"
            },
                    new QueryArgument <AddressInfoInputType> {
                Name = "addressInfo"
            }
                    ),
                resolve: async context =>
            {
                var spanEquipmentOrSegmentId = context.GetArgument <Guid>("spanEquipmentOrSegmentId");

                var correlationId = Guid.NewGuid();

                var userContext = context.UserContext as GraphQLUserContext;
                var userName    = userContext.Username;

                // TODO: Get from work manager
                var workTaskId = Guid.Parse("54800ae5-13a5-4b03-8626-a63b66a25568");

                var commandUserContext = new UserContext(userName, workTaskId);

                var updateCmd = new UpdateSpanEquipmentProperties(correlationId, commandUserContext, spanEquipmentOrSegmentId: spanEquipmentOrSegmentId)
                {
                    SpecificationId = context.HasArgument("spanEquipmentSpecificationId") ? context.GetArgument <Guid>("spanEquipmentSpecificationId") : null,
                    ManufacturerId  = context.HasArgument("manufacturerId") ? context.GetArgument <Guid>("manufacturerId") : null,
                    MarkingInfo     = context.HasArgument("markingInfo") ? context.GetArgument <MarkingInfo>("markingInfo") : null,
                    AddressInfo     = context.HasArgument("addressInfo") ? context.GetArgument <AddressInfo>("addressInfo") : null
                };

                var updateResult = await commandDispatcher.HandleAsync <UpdateSpanEquipmentProperties, Result>(updateCmd);

                return(new CommandResult(updateResult));
            }
                );

            FieldAsync <CommandResultType>(
                "affixSpanEquipmentToParent",
                description: "Affix a span equipment to a parent span equipment - i.e. put a cable inside a conduit",
                arguments: new QueryArguments(
                    new QueryArgument <NonNullGraphType <IdGraphType> > {
                Name = "routeNodeId"
            },
                    new QueryArgument <NonNullGraphType <IdGraphType> > {
                Name = "spanSegmentId1"
            },
                    new QueryArgument <NonNullGraphType <IdGraphType> > {
                Name = "spanSegmentId2"
            }
                    ),
                resolve: async context =>
            {
                var routeNodeId    = context.GetArgument <Guid>("routeNodeId");
                var spanSegmentId1 = context.GetArgument <Guid>("spanSegmentId1");
                var spanSegmentId2 = context.GetArgument <Guid>("spanSegmentId2");

                var correlationId = Guid.NewGuid();

                var userContext = context.UserContext as GraphQLUserContext;
                var userName    = userContext.Username;

                // TODO: Get from work manager
                var workTaskId = Guid.Parse("54800ae5-13a5-4b03-8626-a63b66a25568");

                var commandUserContext = new UserContext(userName, workTaskId);

                var affixCommand       = new AffixSpanEquipmentToParent(correlationId, commandUserContext, routeNodeId, spanSegmentId1, spanSegmentId2);
                var affixCommandResult = await commandDispatcher.HandleAsync <AffixSpanEquipmentToParent, Result>(affixCommand);

                if (affixCommandResult.IsFailed)
                {
                    return(new CommandResult(affixCommandResult));
                }

                return(new CommandResult(Result.Ok()));
            }
                );

            FieldAsync <CommandResultType>(
                "connectToTerminalEquipment",
                description: "Connect one or more span segments inside a span equipment to terminals inside a terminal equipmment",
                arguments: new QueryArguments(
                    new QueryArgument <NonNullGraphType <IdGraphType> > {
                Name = "routeNodeId"
            },
                    new QueryArgument <NonNullGraphType <ListGraphType <ConnectSpanSegmentToTerminalOperationInputType> > > {
                Name = "connects"
            }
                    ),
                resolve: async context =>
            {
                var routeNodeId = context.GetArgument <Guid>("routeNodeId");
                var connects    = context.GetArgument <ConnectSpanSegmentToTerminalOperation[]>("connects");

                var correlationId = Guid.NewGuid();

                var userContext = context.UserContext as GraphQLUserContext;
                var userName    = userContext.Username;

                // TODO: Get from work manager
                var workTaskId = Guid.Parse("54800ae5-13a5-4b03-8626-a63b66a25568");

                var commandUserContext = new UserContext(userName, workTaskId);

                var connectCommand       = new ConnectSpanSegmentsWithTerminalsAtRouteNode(correlationId, commandUserContext, routeNodeId, connects);
                var connectCommandResult = await commandDispatcher.HandleAsync <ConnectSpanSegmentsWithTerminalsAtRouteNode, Result>(connectCommand);

                if (connectCommandResult.IsFailed)
                {
                    return(new CommandResult(connectCommandResult));
                }

                return(new CommandResult(Result.Ok()));
            }
                );

            FieldAsync <CommandResultType>(
                "disconnectFromTerminalEquipment",
                description: "Disconnect one or more span segments inside a span equipment from terminals inside a terminal equipmment",
                arguments: new QueryArguments(
                    new QueryArgument <NonNullGraphType <IdGraphType> > {
                Name = "routeNodeId"
            },
                    new QueryArgument <NonNullGraphType <ListGraphType <DisconnectSpanSegmentFromTerminalOperationInputType> > > {
                Name = "disconnects"
            }
                    ),
                resolve: async context =>
            {
                var routeNodeId = context.GetArgument <Guid>("routeNodeId");
                var connects    = context.GetArgument <DisconnectSpanSegmentFromTerminalOperation[]>("disconnects");

                var correlationId = Guid.NewGuid();

                var userContext = context.UserContext as GraphQLUserContext;
                var userName    = userContext.Username;

                // TODO: Get from work manager
                var workTaskId = Guid.Parse("54800ae5-13a5-4b03-8626-a63b66a25568");

                var commandUserContext = new UserContext(userName, workTaskId);
                var connectCommand     = new DisconnectSpanSegmentsFromTerminalsAtRouteNode(correlationId, commandUserContext, routeNodeId, connects);

                var connectCommandResult = await commandDispatcher.HandleAsync <DisconnectSpanSegmentsFromTerminalsAtRouteNode, Result>(connectCommand);

                if (connectCommandResult.IsFailed)
                {
                    return(new CommandResult(connectCommandResult));
                }

                return(new CommandResult(Result.Ok()));
            }
                );
        }
        private Result PlaceConduitSpanEquipment(NpgsqlCommand logCmd, Guid spanEquipmentId, string externalId, Guid specificationId, List <Guid> segmentIds, List <Guid> additionalStructureSpecIds, string markingColor, Guid?accessAddressId, Guid?unitAddressId, string?addressRemark)
        {
            Guid correlationId = Guid.NewGuid();

            RouteNetworkElementIdList walkIds = new RouteNetworkElementIdList();

            walkIds.AddRange(segmentIds);

            // Register walk of interest
            var walkOfInterestId = Guid.NewGuid();
            var registerWalkOfInterestCommand = new RegisterWalkOfInterest(correlationId, new UserContext("conversion", _neMultiConduitConversion), walkOfInterestId, walkIds);

            var registerWalkOfInterestCommandResult = _commandDispatcher.HandleAsync <RegisterWalkOfInterest, Result <RouteNetworkInterest> >(registerWalkOfInterestCommand).Result;

            if (registerWalkOfInterestCommandResult.IsFailed)
            {
                _logger.LogInformation("Failed to add conduit: " + externalId + " Error: " + registerWalkOfInterestCommandResult.Errors.First().Message);
                LogStatus((NpgsqlCommand)logCmd, _tableName, registerWalkOfInterestCommandResult.Errors.First().Message, externalId);
                return(registerWalkOfInterestCommandResult);
            }

            // conduit name
            var nextConduitSeqStr = _eventStore.Sequences.GetNextVal("conduit").ToString();

            var conduitName = "R" + nextConduitSeqStr.PadLeft(6, '0');
            var namingInfo  = new NamingInfo(conduitName, null);

            AddressInfo?addressInfo = null;

            if (accessAddressId != null || unitAddressId != null && addressRemark != null)
            {
                addressInfo = new AddressInfo()
                {
                    AccessAddressId = accessAddressId, UnitAddressId = unitAddressId, Remark = addressRemark
                };
            }

            // Place conduit
            var placeSpanEquipmentCommand = new PlaceSpanEquipmentInRouteNetwork(correlationId, new UserContext("conversion", _neMultiConduitConversion), spanEquipmentId, specificationId, registerWalkOfInterestCommandResult.Value)
            {
                MarkingInfo = markingColor != null ? new MarkingInfo()
                {
                    MarkingColor = markingColor
                } : null,
                NamingInfo  = namingInfo,
                AddressInfo = addressInfo
            };

            var placeSpanEquipmentResult = _commandDispatcher.HandleAsync <PlaceSpanEquipmentInRouteNetwork, Result>(placeSpanEquipmentCommand).Result;

            if (placeSpanEquipmentResult.IsFailed)
            {
                _logger.LogInformation("Failed to add conduit: " + externalId + " Error: " + placeSpanEquipmentResult.Errors.First().Message);
                LogStatus((NpgsqlCommand)logCmd, _tableName, placeSpanEquipmentResult.Errors.First().Message, externalId);
                return(placeSpanEquipmentResult);
            }

            // Place additional structures
            if (additionalStructureSpecIds.Count > 0)
            {
                var addStructure = new PlaceAdditionalStructuresInSpanEquipment(correlationId, new UserContext("conversion", _neMultiConduitConversion),
                                                                                spanEquipmentId: placeSpanEquipmentCommand.SpanEquipmentId,
                                                                                structureSpecificationIds: additionalStructureSpecIds.ToArray()
                                                                                );

                var addStructureResult = _commandDispatcher.HandleAsync <PlaceAdditionalStructuresInSpanEquipment, Result>(addStructure).Result;

                if (addStructureResult.IsFailed)
                {
                    _logger.LogInformation("Failed to add additional structures to: " + externalId + " Error: " + placeSpanEquipmentResult.Errors.First().Message);
                    LogStatus((NpgsqlCommand)logCmd, _tableName, addStructureResult.Errors.First().Message, externalId);
                    return(addStructureResult);
                }
            }

            return(Result.Ok());
        }