Add resource supply contract export by its guid

This commit is contained in:
2025-08-25 12:15:28 +09:00
parent 90acf7e801
commit 4d9817050f
77 changed files with 86934 additions and 103 deletions

View File

@ -0,0 +1,24 @@
using Hcs.Client.Api.Request.HouseManagement;
using Hcs.Service.Async.HouseManagement;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Hcs.Client.Api
{
// http://open-gkh.ru/HouseManagementServiceAsync/
public class HouseManagementApi(ClientBase client) : ApiBase(client)
{
/// <summary>
/// Возвращает договор ресурсоснабжения по его идентификатору в ГИС ЖКХ
/// </summary>
/// <param name="contractRootGuid">Идентификатор договора ресурсоснабжения в ГИС ЖКХ</param>
/// <param name="token">Токен отмены</param>
/// <returns>Договор ресурсоснабжения</returns>
public async Task<exportSupplyResourceContractResultType> ExportSupplyResourceContractDataAsync(Guid contractRootGuid, CancellationToken token = default)
{
var request = new ExportSupplyResourceContractDataRequest(client);
return await request.ExecuteAsync(contractRootGuid, token);
}
}
}

View File

@ -16,7 +16,7 @@ namespace Hcs.Client.Api
/// <param name="registryNumber">Реестровый номер справочника</param>
/// <param name="token">Токен отмены</param>
/// <returns>Данные справочника</returns>
public async Task<IEnumerable<NsiItemType>> ExportDataProviderNsiItem(exportDataProviderNsiItemRequestRegistryNumber registryNumber, CancellationToken token = default)
public async Task<IEnumerable<NsiItemType>> ExportDataProviderNsiItemAsync(exportDataProviderNsiItemRequestRegistryNumber registryNumber, CancellationToken token = default)
{
try
{

View File

@ -16,7 +16,7 @@ namespace Hcs.Client.Api
/// <param name="listGroup">Группа справочников, где NSI - общесистемный, а NSIRAO - ОЖФ</param>
/// <param name="token">Токен отмены</param>
/// <returns>Данные общесистемного справочника</returns>
public async Task<NsiItemType> ExportNsiItem(int registryNumber, ListGroup listGroup, CancellationToken token = default)
public async Task<NsiItemType> ExportNsiItemAsync(int registryNumber, ListGroup listGroup, CancellationToken token = default)
{
try
{

View File

@ -0,0 +1,77 @@
using Hcs.Client.Internal;
using Hcs.Service.Async.HouseManagement;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Hcs.Client.Api.Request.HouseManagement
{
internal class ExportSupplyResourceContractDataRequest(ClientBase client) : HouseManagementRequestBase(client)
{
protected override bool EnableMinimalResponseWaitDelay => false;
internal async Task<exportSupplyResourceContractResultType> ExecuteAsync(Guid contractRootGuid, CancellationToken token)
{
exportSupplyResourceContractResultType result = null;
void OnResultReceived(exportSupplyResourceContractResultType[] contracts)
{
result = contracts.Length > 0 ? contracts[0] : null;
}
await QueryBatchAsync(contractRootGuid, null, null, OnResultReceived, token);
return result;
}
private async Task<PaginationData> QueryBatchAsync(
Guid? contractRootGuid, string contractNumber, Guid? exportContractRootGuid,
Action<exportSupplyResourceContractResultType[]> onResultReceived,
CancellationToken token)
{
var itemsElementName = new List<ItemsChoiceType32>();
var items = new List<object>();
if (contractRootGuid.HasValue)
{
itemsElementName.Add(ItemsChoiceType32.ContractRootGUID);
items.Add(contractRootGuid.ToString());
}
if (!string.IsNullOrEmpty(contractNumber))
{
itemsElementName.Add(ItemsChoiceType32.ContractNumber);
items.Add(contractNumber);
}
if (exportContractRootGuid.HasValue)
{
itemsElementName.Add(ItemsChoiceType32.ExportContractRootGUID);
items.Add(exportContractRootGuid.ToString());
}
// http://open-gkh.ru/HouseManagement/exportSupplyResourceContractRequest.html
var request = new exportSupplyResourceContractRequest
{
Id = Constants.SIGNED_XML_ELEMENT_ID,
version = "13.1.1.1",
ItemsElementName = [.. itemsElementName],
Items = [.. items]
};
var result = await SendAndWaitResultAsync(request, async asyncClient =>
{
var ackResponse = await asyncClient.exportSupplyResourceContractDataAsync(
CreateRequestHeader(), request);
return ackResponse.AckRequest.Ack;
}, token);
var contractResult = result.Items.OfType<getStateResultExportSupplyResourceContractResult>().First();
onResultReceived?.Invoke(contractResult.Contract);
return new PaginationData(contractResult.Item);
}
}
}

View File

@ -0,0 +1,55 @@
using Hcs.Client.Api.Request;
using Hcs.Client.Api.Request.Adapter;
using Hcs.Service.Async.HouseManagement;
using System.Threading.Tasks;
namespace Hcs.Service.Async.HouseManagement
{
#pragma warning disable IDE1006
public partial class getStateResult : IGetStateResultMany { }
#pragma warning restore IDE1006
public partial class HouseManagementPortsTypeAsyncClient : IAsyncClient<RequestHeader>
{
public async Task<IGetStateResponse> GetStateAsync(RequestHeader header, IGetStateRequest request)
{
return await getStateAsync(header, (getStateRequest)request);
}
}
#pragma warning disable IDE1006
public partial class getStateResponse : IGetStateResponse
#pragma warning restore IDE1006
{
public IGetStateResult GetStateResult => getStateResult;
}
public partial class AckRequestAck : IAck { }
public partial class ErrorMessageType : IErrorMessage { }
#pragma warning disable IDE1006
public partial class getStateRequest : IGetStateRequest { }
#pragma warning restore IDE1006
}
namespace Hcs.Client.Api.Request.HouseManagement
{
internal class HouseManagementRequestBase(ClientBase client) :
RequestBase<getStateResult,
HouseManagementPortsTypeAsyncClient,
HouseManagementPortsTypeAsync,
RequestHeader,
AckRequestAck,
ErrorMessageType,
getStateRequest>(client)
{
protected override EndPoint EndPoint => EndPoint.HomeManagementAsync;
protected override bool EnableMinimalResponseWaitDelay => true;
protected override bool CanBeRestarted => true;
protected override int RestartTimeoutMinutes => 20;
}
}

View File

@ -19,7 +19,7 @@ namespace Hcs.Client.Api.Request.Nsi
RegistryNumber = registryNumber
};
var result = await SendAndWaitResultAsync(request, async(asyncClient) =>
var result = await SendAndWaitResultAsync(request, async asyncClient =>
{
var response = await asyncClient.exportDataProviderNsiItemAsync(CreateRequestHeader(), request);
return response.AckRequest.Ack;

View File

@ -5,7 +5,9 @@ using System.Threading.Tasks;
namespace Hcs.Service.Async.Nsi
{
#pragma warning disable IDE1006
public partial class getStateResult : IGetStateResultMany { }
#pragma warning restore IDE1006
public partial class NsiPortsTypeAsyncClient : IAsyncClient<RequestHeader>
{
@ -15,7 +17,9 @@ namespace Hcs.Service.Async.Nsi
}
}
#pragma warning disable IDE1006
public partial class getStateResponse : IGetStateResponse
#pragma warning restore IDE1006
{
public IGetStateResult GetStateResult => getStateResult;
}
@ -24,7 +28,9 @@ namespace Hcs.Service.Async.Nsi
public partial class ErrorMessageType : IErrorMessage { }
#pragma warning disable IDE1006
public partial class getStateRequest : IGetStateRequest { }
#pragma warning restore IDE1006
}
namespace Hcs.Client.Api.Request.Nsi

View File

@ -18,7 +18,7 @@ namespace Hcs.Client.Api.Request.NsiCommon
ListGroup = listGroup
};
var result = await SendAndWaitResultAsync(request, async (asyncClient) =>
var result = await SendAndWaitResultAsync(request, async asyncClient =>
{
var response = await asyncClient.exportNsiItemAsync(CreateRequestHeader(), request);
return response.AckRequest.Ack;

View File

@ -0,0 +1,54 @@
using System;
namespace Hcs.Client.Api.Request
{
internal class PaginationData
{
/// <summary>
/// Состояние, указывающее на то, что это последняя страница
/// </summary>
internal bool IsLastPage { get; private set; }
/// <summary>
/// Идентификатор следующей страницы
/// </summary>
internal Guid NextGuid { get; private set; }
public PaginationData(object item)
{
if (item == null)
{
throw new System.Exception($"[{nameof(PaginationData)}] item is null");
}
else if (item is bool boolItem)
{
if (boolItem == false)
{
throw new System.Exception($"[{nameof(PaginationData)}] item is false");
}
IsLastPage = true;
}
else if (item is string stringItem)
{
IsLastPage = false;
NextGuid = Guid.Parse(stringItem);
}
else
{
throw new System.Exception($"[{nameof(PaginationData)}] failed to handle item of {item.GetType().FullName} type");
}
}
internal static PaginationData CreateLastPageData()
{
return new PaginationData(true);
}
public override string ToString()
{
return $"[{nameof(PaginationData)}] {nameof(IsLastPage)} = {IsLastPage}" +
(IsLastPage ? "" : $", {nameof(NextGuid)} = {NextGuid}");
}
}
}

View File

@ -130,38 +130,15 @@ namespace Hcs.Client.Api.Request
{
try
{
if (request == null)
if (CanBeRestarted)
{
throw new ArgumentNullException(nameof(request));
return await RunRepeatableTaskInsistentlyAsync(
async () => await ExecuteSendAndWaitResultAsync(request, sender, token), token);
}
var version = RequestHelper.GetRequestVersionString(request);
client.TryLog($"Executing request {RemoteAddress.Uri}/{request.GetType().Name} of version {version}...");
TAck ack;
var stopWatch = System.Diagnostics.Stopwatch.StartNew();
using (var asyncClient = CreateAsyncClient())
else
{
ack = await sender(asyncClient);
return await ExecuteSendAndWaitResultAsync(request, sender, token);
}
stopWatch.Stop();
client.TryLog($"Request executed in {stopWatch.ElapsedMilliseconds} ms, result GUID is {ack.MessageGUID}");
var result = await WaitForResultAsync(ack, true, token);
if (result is IQueryable queryableResult)
{
queryableResult.OfType<TErrorMessage>().ToList().ForEach(x =>
{
throw RemoteException.CreateNew(x.ErrorCode, x.Description);
});
}
else if (result is TErrorMessage x)
{
throw RemoteException.CreateNew(x.ErrorCode, x.Description);
}
return result;
}
catch (RestartTimeoutException e)
{
@ -175,6 +152,108 @@ namespace Hcs.Client.Api.Request
}
}
/// <summary>
/// Для запросов к серверу которые можно направлять несколько раз, разрешаем
/// серверу аномально отказаться. Предполагается, что здесь мы игнорируем
/// только жесткие отказы серверной инфраструктуры, которые указывают
/// что запрос даже не был принят в обработку. Также все запросы на
/// чтение можно повторять в случае их серверных системных ошибок.
/// </summary>
protected async Task<TRepeatableResult> RunRepeatableTaskInsistentlyAsync<TRepeatableResult>(
Func<Task<TRepeatableResult>> func, CancellationToken token)
{
var afterErrorDelaySec = 120;
for (var attempt = 1; ; attempt++)
{
try
{
return await func();
}
catch (System.Exception e)
{
if (CanIgnoreSuchException(e, out string marker))
{
client.TryLog($"Ignoring error of attempt #{attempt} with type [{marker}]");
client.TryLog($"Waiting {afterErrorDelaySec} sec until next attempt...");
await Task.Delay(afterErrorDelaySec * 1000, token);
continue;
}
if (e is RestartTimeoutException)
{
throw e;
}
if (e is RemoteException)
{
throw RemoteException.CreateNew(e as RemoteException);
}
throw new System.Exception("Cannot ignore this exception", e);
}
}
}
private bool CanIgnoreSuchException(System.Exception e, out string resultMarker)
{
foreach (var marker in ignorableSystemErrorMarkers)
{
var found = Util.EnumerateInnerExceptions(e).Find(
x => x.Message != null && x.Message.Contains(marker));
if (found != null)
{
resultMarker = marker;
return true;
}
}
resultMarker = null;
return false;
}
private async Task<TResult> ExecuteSendAndWaitResultAsync(
object request,
Func<TAsyncClient, Task<TAck>> sender,
CancellationToken token)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
var version = RequestHelper.GetRequestVersionString(request);
client.TryLog($"Executing request {RemoteAddress.Uri}/{request.GetType().Name} of version {version}...");
TAck ack;
var stopWatch = System.Diagnostics.Stopwatch.StartNew();
using (var asyncClient = CreateAsyncClient())
{
ack = await sender(asyncClient);
}
stopWatch.Stop();
client.TryLog($"Request executed in {stopWatch.ElapsedMilliseconds} ms, result GUID is {ack.MessageGUID}");
var result = await WaitForResultAsync(ack, true, token);
if (result is IQueryable queryableResult)
{
queryableResult.OfType<TErrorMessage>().ToList().ForEach(x =>
{
throw RemoteException.CreateNew(x.ErrorCode, x.Description);
});
}
else if (result is TErrorMessage x)
{
throw RemoteException.CreateNew(x.ErrorCode, x.Description);
}
return result;
}
private TAsyncClient CreateAsyncClient()
{
var asyncClient = (TAsyncClient)Activator.CreateInstance(typeof(TAsyncClient), binding, RemoteAddress);
@ -340,68 +419,5 @@ namespace Hcs.Client.Api.Request
}
}
}
/// <summary>
/// Для запросов к серверу которые можно направлять несколько раз, разрешаем
/// серверу аномально отказаться. Предполагается, что здесь мы игнорируем
/// только жесткие отказы серверной инфраструктуры, которые указывают
/// что запрос даже не был принят в обработку. Также все запросы на
/// чтение можно повторять в случае их серверных системных ошибок.
/// </summary>
protected async Task<TRepeatableResult> RunRepeatableTaskInsistentlyAsync<TRepeatableResult>(
Func<Task<TRepeatableResult>> func, CancellationToken token)
{
var afterErrorDelaySec = 120;
for (var attempt = 1; ; attempt++)
{
try
{
return await func();
}
catch (System.Exception e)
{
if (CanIgnoreSuchException(e, out string marker))
{
client.TryLog($"Ignoring error of attempt #{attempt} with type [{marker}]");
client.TryLog($"Waiting {afterErrorDelaySec} sec until next attempt...");
await Task.Delay(afterErrorDelaySec * 1000, token);
continue;
}
if (e is RestartTimeoutException)
{
throw e;
}
if (e is RemoteException)
{
throw RemoteException.CreateNew(e as RemoteException);
}
throw new System.Exception("Cannot ignore this exception", e);
}
}
}
private bool CanIgnoreSuchException(System.Exception e, out string resultMarker)
{
foreach (var marker in ignorableSystemErrorMarkers)
{
var found = Util.EnumerateInnerExceptions(e).Find(
x => x.Message != null && x.Message.Contains(marker));
if (found != null)
{
resultMarker = marker;
return true;
}
}
resultMarker = null;
return false;
}
}
}

View File

@ -11,6 +11,8 @@ namespace Hcs.Client
/// </summary>
public class UniClient : ClientBase
{
public HouseManagementApi HouseManagement => new(this);
public NsiApi Nsi => new(this);
public NsiCommonApi NsiCommon => new(this);