Add new Hcs.Broker to communicate with ГИС ЖКХ via CryptoPro LibCore

This commit is contained in:
2025-09-28 15:45:15 +09:00
parent 904988780a
commit 2b49320014
171 changed files with 185618 additions and 0 deletions

View File

@ -0,0 +1,9 @@
namespace Hcs.Broker.Api.Request.Adapter
{
public interface IAck
{
string MessageGUID { get; set; }
string RequesterMessageGUID { get; set; }
}
}

View File

@ -0,0 +1,7 @@
namespace Hcs.Broker.Api.Request.Adapter
{
public interface IAsyncClient<TRequestHeader> where TRequestHeader : class
{
Task<IGetStateResponse> GetStateAsync(TRequestHeader header, IGetStateRequest request);
}
}

View File

@ -0,0 +1,9 @@
namespace Hcs.Broker.Api.Request.Adapter
{
public interface IErrorMessage
{
string ErrorCode { get; }
string Description { get; }
}
}

View File

@ -0,0 +1,7 @@
namespace Hcs.Broker.Api.Request.Adapter
{
public interface IGetStateRequest
{
string MessageGUID { get; set; }
}
}

View File

@ -0,0 +1,7 @@
namespace Hcs.Broker.Api.Request.Adapter
{
public interface IGetStateResponse
{
IGetStateResult GetStateResult { get; }
}
}

View File

@ -0,0 +1,7 @@
namespace Hcs.Broker.Api.Request.Adapter
{
public interface IGetStateResult
{
sbyte RequestState { get; }
}
}

View File

@ -0,0 +1,7 @@
namespace Hcs.Broker.Api.Request.Adapter
{
public interface IGetStateResultMany : IGetStateResult
{
object[] Items { get; }
}
}

View File

@ -0,0 +1,7 @@
namespace Hcs.Broker.Api.Request.Adapter
{
public interface IGetStateResultOne : IGetStateResult
{
object Item { get; }
}
}

View File

@ -0,0 +1,9 @@
namespace Hcs.Broker.Api.Request
{
internal enum AsyncRequestStateType
{
Received = 1,
InProgress,
Ready
}
}

View File

@ -0,0 +1,53 @@
using Hcs.Broker.Api.Request.Adapter;
using Hcs.Service.Async.Bills;
namespace Hcs.Service.Async.Bills
{
#pragma warning disable IDE1006
public partial class getStateResult : IGetStateResultMany { }
#pragma warning restore IDE1006
public partial class BillsPortsTypeAsyncClient : 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.Broker.Api.Request.Bills
{
internal class BillsRequestBase(Client client) :
RequestBase<getStateResult,
BillsPortsTypeAsyncClient,
BillsPortsTypeAsync,
RequestHeader,
AckRequestAck,
ErrorMessageType,
getStateRequest>(client)
{
protected override EndPoint EndPoint => EndPoint.BillsAsync;
protected override bool EnableMinimalResponseWaitDelay => false;
protected override bool CanBeRestarted => false;
protected override int RestartTimeoutMinutes => 20;
}
}

View File

@ -0,0 +1,67 @@
using Hcs.Broker.Internal;
using Hcs.Service.Async.Bills;
namespace Hcs.Broker.Api.Request.Bills
{
internal class ExportPaymentDocumentDataRequest(Client client) : BillsRequestBase(client)
{
protected override bool CanBeRestarted => true;
internal async Task<IEnumerable<exportPaymentDocumentResultType>> ExecuteByPaymentDocumentIDAsync(string paymentDocumentID, CancellationToken token)
{
var request = new exportPaymentDocumentRequest()
{
Id = Constants.SIGNED_XML_ELEMENT_ID,
version = "13.1.0.1",
Items = [paymentDocumentID],
ItemsElementName = [ItemsChoiceType7.PaymentDocumentID]
};
var result = await SendAndWaitResultAsync(request, async asyncClient =>
{
var response = await asyncClient.exportPaymentDocumentDataAsync(CreateRequestHeader(), request);
return response.AckRequest.Ack;
}, token);
return result.Items.OfType<exportPaymentDocumentResultType>();
}
internal async Task<IEnumerable<exportPaymentDocumentResultType>> ExecuteByAccountNumberAsync(short year, int month, string fiasHouseGuid, string accountNumber, CancellationToken token)
{
var request = new exportPaymentDocumentRequest()
{
Id = Constants.SIGNED_XML_ELEMENT_ID,
version = "13.1.0.1",
Items = [year, month, fiasHouseGuid, accountNumber],
ItemsElementName = [ItemsChoiceType7.Year, ItemsChoiceType7.Month, ItemsChoiceType7.FIASHouseGuid, ItemsChoiceType7.AccountNumber]
};
var result = await SendAndWaitResultAsync(request, async asyncClient =>
{
var response = await asyncClient.exportPaymentDocumentDataAsync(CreateRequestHeader(), request);
return response.AckRequest.Ack;
}, token);
return result.Items.OfType<exportPaymentDocumentResultType>();
}
internal async Task<IEnumerable<exportPaymentDocumentResultType>> ExecuteByPaymentDocumentNumberAsync(short year, int month, string fiasHouseGuid, string paymentDocumentNumber, CancellationToken token)
{
var request = new exportPaymentDocumentRequest()
{
Id = Constants.SIGNED_XML_ELEMENT_ID,
version = "13.1.0.1",
Items = [year, month, fiasHouseGuid, paymentDocumentNumber],
ItemsElementName = [ItemsChoiceType7.Year, ItemsChoiceType7.Month, ItemsChoiceType7.FIASHouseGuid, ItemsChoiceType7.PaymentDocumentNumber]
};
var result = await SendAndWaitResultAsync(request, async asyncClient =>
{
var response = await asyncClient.exportPaymentDocumentDataAsync(CreateRequestHeader(), request);
return response.AckRequest.Ack;
}, token);
return result.Items.OfType<exportPaymentDocumentResultType>();
}
}
}

View File

@ -0,0 +1,271 @@
using Hcs.Broker.Api.Payload.Bills;
using Hcs.Broker.Api.Request.Exception;
using Hcs.Broker.Api.Type;
using Hcs.Broker.Internal;
using Hcs.Service.Async.Bills;
namespace Hcs.Broker.Api.Request.Bills
{
internal class ImportPaymentDocumentDataRequest(Client client) : BillsRequestBase(client)
{
internal async Task<bool> ExecuteAsync(ImportPaymentDocumentDataPayload payload, CancellationToken token)
{
// TODO: Добавить проверку пейлоада
var request = GetRequestFromPayload(payload);
var result = await SendAndWaitResultAsync(request, async asyncClient =>
{
var response = await asyncClient.importPaymentDocumentDataAsync(CreateRequestHeader(), request);
return response.AckRequest.Ack;
}, token);
result.Items.OfType<ErrorMessageType>().ToList().ForEach(error =>
{
throw RemoteException.CreateNew(error.ErrorCode, error.Description);
});
result.Items.OfType<CommonResultType>().ToList().ForEach(commonResult =>
{
commonResult.Items.OfType<ErrorMessageType>().ToList().ForEach(error =>
{
throw RemoteException.CreateNew(error.ErrorCode, error.Description);
});
});
return true;
}
private importPaymentDocumentRequest GetRequestFromPayload(ImportPaymentDocumentDataPayload payload)
{
var items = new List<object>();
if (payload.confirmAmountsCorrect)
{
items.Add(true);
}
items.Add(payload.month);
items.Add(payload.year);
var paymentInformations = new Dictionary<ImportPaymentDocumentDataPayload.PaymentInformation, importPaymentDocumentRequestPaymentInformation>();
foreach (var entry in payload.paymentInformation)
{
var paymentInformation = new importPaymentDocumentRequestPaymentInformation()
{
TransportGUID = Guid.NewGuid().ToString(),
BankBIK = entry.bankBIK,
operatingAccountNumber = entry.operatingAccountNumber
};
paymentInformations.Add(entry, paymentInformation);
items.Add(paymentInformation);
}
foreach (var entry in payload.paymentDocument)
{
var chargeInfo = new List<object>();
foreach (var subEntry in entry.chargeInfo)
{
if (subEntry is ImportPaymentDocumentDataPayload.MunicipalService municipalService)
{
var item = new PDServiceChargeTypeMunicipalService()
{
ServiceType = new nsiRef()
{
Code = municipalService.serviceType.Code,
GUID = municipalService.serviceType.GUID
},
Rate = municipalService.rate,
TotalPayable = municipalService.totalPayable
};
if (municipalService.moneyRecalculation.HasValue)
{
item.ServiceCharge = new ServiceChargeImportType()
{
MoneyRecalculation = municipalService.moneyRecalculation.Value,
MoneyRecalculationSpecified = true
};
}
if (municipalService.moneyDiscount.HasValue)
{
item.ServiceCharge ??= new ServiceChargeImportType();
item.ServiceCharge.MoneyDiscount = municipalService.moneyDiscount.Value;
item.ServiceCharge.MoneyDiscountSpecified = true;
}
if (municipalService.houseOverallNeedsNorm.HasValue)
{
item.ServiceInformation = new ServiceInformation()
{
houseOverallNeedsNorm = municipalService.houseOverallNeedsNorm.Value,
houseOverallNeedsNormSpecified = true
};
}
if (municipalService.individualConsumptionNorm.HasValue)
{
item.ServiceInformation ??= new ServiceInformation();
item.ServiceInformation.individualConsumptionNorm = municipalService.individualConsumptionNorm.Value;
item.ServiceInformation.individualConsumptionNormSpecified = true;
}
if (municipalService.individualConsumptionCurrentValue.HasValue)
{
item.ServiceInformation ??= new ServiceInformation();
item.ServiceInformation.individualConsumptionCurrentValue = municipalService.individualConsumptionCurrentValue.Value;
item.ServiceInformation.individualConsumptionCurrentValueSpecified = true;
}
if (municipalService.houseOverallNeedsCurrentValue.HasValue)
{
item.ServiceInformation ??= new ServiceInformation();
item.ServiceInformation.houseOverallNeedsCurrentValue = municipalService.houseOverallNeedsCurrentValue.Value;
item.ServiceInformation.houseOverallNeedsCurrentValueSpecified = true;
}
if (municipalService.houseTotalIndividualConsumption.HasValue)
{
item.ServiceInformation ??= new ServiceInformation();
item.ServiceInformation.houseTotalIndividualConsumption = municipalService.houseTotalIndividualConsumption.Value;
item.ServiceInformation.houseTotalIndividualConsumptionSpecified = true;
}
if (municipalService.houseTotalHouseOverallNeeds.HasValue)
{
item.ServiceInformation ??= new ServiceInformation();
item.ServiceInformation.houseTotalHouseOverallNeeds = municipalService.houseTotalHouseOverallNeeds.Value;
item.ServiceInformation.houseTotalHouseOverallNeedsSpecified = true;
}
var consumption = new List<PDServiceChargeTypeMunicipalServiceVolume>();
if (municipalService.individualConsumptionVolumeDeterminingMethod.HasValue)
{
consumption.Add(new PDServiceChargeTypeMunicipalServiceVolume()
{
determiningMethod = municipalService.individualConsumptionVolumeDeterminingMethod.Value.ToServiceType(),
determiningMethodSpecified = true,
type = PDServiceChargeTypeMunicipalServiceVolumeType.I,
typeSpecified = true,
Value = municipalService.individualConsumptionVolumeValue.Value
});
}
if (municipalService.overallConsumptionVolumeDeterminingMethod.HasValue)
{
consumption.Add(new PDServiceChargeTypeMunicipalServiceVolume()
{
determiningMethod = municipalService.overallConsumptionVolumeDeterminingMethod.Value.ToServiceType(),
determiningMethodSpecified = true,
type = PDServiceChargeTypeMunicipalServiceVolumeType.O,
typeSpecified = true,
Value = municipalService.overallConsumptionVolumeValue.Value
});
}
item.Consumption = [.. consumption];
if (municipalService.multiplyingFactorRatio.HasValue)
{
item.MultiplyingFactor = new PDServiceChargeTypeMunicipalServiceMultiplyingFactor()
{
Ratio = municipalService.multiplyingFactorRatio.Value
};
if (municipalService.amountOfExcessFees.HasValue)
{
item.MultiplyingFactor.AmountOfExcessFees = municipalService.amountOfExcessFees.Value;
item.MultiplyingFactor.AmountOfExcessFeesSpecified = true;
}
}
if (municipalService.municipalServiceIndividualConsumptionPayable.HasValue)
{
item.MunicipalServiceIndividualConsumptionPayable = municipalService.municipalServiceIndividualConsumptionPayable.Value;
item.MunicipalServiceIndividualConsumptionPayableSpecified = true;
}
if (municipalService.municipalServiceCommunalConsumptionPayable.HasValue)
{
item.MunicipalServiceCommunalConsumptionPayable = municipalService.municipalServiceCommunalConsumptionPayable.Value;
item.MunicipalServiceCommunalConsumptionPayableSpecified = true;
}
if (municipalService.amountOfPaymentMunicipalServiceIndividualConsumption.HasValue)
{
item.AmountOfPaymentMunicipalServiceIndividualConsumption = municipalService.amountOfPaymentMunicipalServiceIndividualConsumption.Value;
item.AmountOfPaymentMunicipalServiceIndividualConsumptionSpecified = true;
}
if (municipalService.amountOfPaymentMunicipalServiceCommunalConsumption.HasValue)
{
item.AmountOfPaymentMunicipalServiceCommunalConsumption = municipalService.amountOfPaymentMunicipalServiceCommunalConsumption.Value;
item.AmountOfPaymentMunicipalServiceCommunalConsumptionSpecified = true;
}
if (municipalService.accountingPeriodTotal.HasValue)
{
item.AccountingPeriodTotal = municipalService.accountingPeriodTotal.Value;
item.AccountingPeriodTotalSpecified = true;
}
chargeInfo.Add(new PaymentDocumentTypeChargeInfo()
{
Item = item
});
}
// TODO: Обработать ошибку
}
var paymentDocument = new importPaymentDocumentRequestPaymentDocument()
{
TransportGUID = Guid.NewGuid().ToString(),
Items1 = [paymentInformations[entry.paymentInformation].TransportGUID],
AccountGuid = entry.accountGuid,
PaymentDocumentNumber = entry.paymentDocumentNumber,
Items = [.. chargeInfo],
Item = true,
ItemElementName = entry.exposeNotWithdraw ? ItemChoiceType5.Expose : ItemChoiceType5.Withdraw
};
if (entry.debtPreviousPeriods.HasValue)
{
paymentDocument.DebtPreviousPeriods = entry.debtPreviousPeriods.Value;
paymentDocument.DebtPreviousPeriodsSpecified = true;
}
if (entry.advanceBllingPeriod.HasValue)
{
paymentDocument.AdvanceBllingPeriod = entry.advanceBllingPeriod.Value;
paymentDocument.AdvanceBllingPeriodSpecified = true;
}
if (entry.totalPayableByPDWithDebtAndAdvance.HasValue)
{
paymentDocument.TotalPayableByPDWithDebtAndAdvance = entry.totalPayableByPDWithDebtAndAdvance.Value;
paymentDocument.TotalPayableByPDWithDebtAndAdvanceSpecified = true;
}
if (entry.totalPayableByPD.HasValue)
{
paymentDocument.TotalPayableByPD = entry.totalPayableByPD.Value;
paymentDocument.TotalPayableByPDSpecified = true;
}
if (entry.paidCash.HasValue)
{
paymentDocument.PaidCash = entry.paidCash.Value;
paymentDocument.PaidCashSpecified = true;
}
if (entry.dateOfLastReceivedPayment.HasValue)
{
paymentDocument.DateOfLastReceivedPayment = entry.dateOfLastReceivedPayment.Value;
paymentDocument.DateOfLastReceivedPaymentSpecified = true;
}
items.Add(paymentDocument);
}
// http://open-gkh.ru/Bills/importPaymentDocumentRequest.html
return new importPaymentDocumentRequest
{
Id = Constants.SIGNED_XML_ELEMENT_ID,
version = "11.2.0.16",
Items = [.. items]
};
}
}
}

View File

@ -0,0 +1,53 @@
using Hcs.Broker.Api.Request.Adapter;
using Hcs.Service.Async.DeviceMetering;
namespace Hcs.Service.Async.DeviceMetering
{
#pragma warning disable IDE1006
public partial class getStateResult : IGetStateResultMany { }
#pragma warning restore IDE1006
public partial class DeviceMeteringPortTypesAsyncClient : 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.Broker.Api.Request.DeviceMetering
{
internal class DeviceMeteringRequestBase(Client client) :
RequestBase<getStateResult,
DeviceMeteringPortTypesAsyncClient,
DeviceMeteringPortTypesAsync,
RequestHeader,
AckRequestAck,
ErrorMessageType,
getStateRequest>(client)
{
protected override EndPoint EndPoint => EndPoint.DeviceMeteringAsync;
protected override bool EnableMinimalResponseWaitDelay => true;
protected override bool CanBeRestarted => true;
protected override int RestartTimeoutMinutes => 20;
}
}

View File

@ -0,0 +1,174 @@
using Hcs.Broker.Api.Payload.DeviceMetering;
using Hcs.Broker.Internal;
using Hcs.Service.Async.DeviceMetering;
namespace Hcs.Broker.Api.Request.DeviceMetering
{
internal class ExportMeteringDeviceHistoryRequest(Client client) : DeviceMeteringRequestBase(client)
{
protected override bool EnableMinimalResponseWaitDelay => false;
internal async Task<IEnumerable<exportMeteringDeviceHistoryResultType>> ExecuteAsync(ExportMeteringDeviceHistoryPayload payload, CancellationToken token)
{
ThrowIfPayloadIncorrect(payload);
var request = GetRequestFromPayload(payload);
var result = await SendAndWaitResultAsync(request, async asyncClient =>
{
var response = await asyncClient.exportMeteringDeviceHistoryAsync(CreateRequestHeader(), request);
return response.AckRequest.Ack;
}, token);
return result.Items.OfType<exportMeteringDeviceHistoryResultType>();
}
private void ThrowIfPayloadIncorrect(ExportMeteringDeviceHistoryPayload payload)
{
if (payload.meteringDeviceType?.Length <= 0 && payload.municipalResource?.Length <= 0 &&
payload.meteringDeviceRootGUID?.Length <= 0)
{
throw new ArgumentException($"{nameof(payload.meteringDeviceType)}/{nameof(payload.municipalResource)}/{nameof(payload.meteringDeviceRootGUID)} are empty");
}
if (payload.meteringDeviceType?.Length + payload.municipalResource?.Length +
payload.meteringDeviceRootGUID?.Length > 100)
{
throw new ArgumentException($"Too much {nameof(payload.meteringDeviceType)}/{nameof(payload.municipalResource)}/{nameof(payload.meteringDeviceRootGUID)} values");
}
if (payload.inputDateFrom.HasValue && payload.inputDateTo.HasValue)
{
if (payload.inputDateFrom.HasValue && payload.inputDateTo.HasValue &&
payload.inputDateFrom.Value > payload.inputDateTo.Value)
{
throw new ArgumentException($"{nameof(payload.inputDateFrom)} must be earlier than {nameof(payload.inputDateTo)}");
}
if (payload.inputDateTo.Value - payload.inputDateFrom.Value > TimeSpan.FromDays(
DateTime.DaysInMonth(payload.inputDateTo.Value.Year, payload.inputDateTo.Value.Month) +
DateTime.DaysInMonth(payload.inputDateFrom.Value.Year, payload.inputDateFrom.Value.Month)))
{
throw new ArgumentException($"Too big range from {nameof(payload.inputDateFrom)} to {nameof(payload.inputDateTo)}");
}
}
else if (payload.inputDateFrom.HasValue && !payload.inputDateTo.HasValue)
{
throw new ArgumentException($"{nameof(payload.inputDateTo)} is null");
}
else if (!payload.inputDateFrom.HasValue && payload.inputDateTo.HasValue)
{
throw new ArgumentException($"{nameof(payload.inputDateFrom)} is null");
}
}
private exportMeteringDeviceHistoryRequest GetRequestFromPayload(ExportMeteringDeviceHistoryPayload payload)
{
var items = new List<object>();
var itemsElementName = new List<ItemsChoiceType4>();
if (payload.meteringDeviceType != null)
{
foreach (var meteringDeviceType in payload.meteringDeviceType)
{
items.Add(new nsiRef()
{
Code = meteringDeviceType.Code,
GUID = meteringDeviceType.GUID
});
itemsElementName.Add(ItemsChoiceType4.MeteringDeviceType);
}
}
if (payload.municipalResource != null)
{
foreach (var municipalResource in payload.municipalResource)
{
items.Add(new nsiRef()
{
Code = municipalResource.Code,
GUID = municipalResource.GUID
});
itemsElementName.Add(ItemsChoiceType4.MunicipalResource);
}
}
if (payload.meteringDeviceRootGUID != null)
{
foreach (var meteringDeviceRootGUID in payload.meteringDeviceRootGUID)
{
items.Add(meteringDeviceRootGUID);
itemsElementName.Add(ItemsChoiceType4.MeteringDeviceRootGUID);
}
}
// http://open-gkh.ru/DeviceMetering/exportMeteringDeviceHistoryRequest.html
var request = new exportMeteringDeviceHistoryRequest
{
Id = Constants.SIGNED_XML_ELEMENT_ID,
version = "13.1.3.1",
FIASHouseGuid = payload.fiasHouseGuid,
Items = [.. items],
ItemsElementName = [.. itemsElementName]
};
if (payload.commissioningDateFrom.HasValue)
{
request.CommissioningDateFrom = payload.commissioningDateFrom.Value;
request.CommissioningDateFromSpecified = true;
}
if (payload.сommissioningDateTo.HasValue)
{
request.CommissioningDateTo = payload.сommissioningDateTo.Value;
request.CommissioningDateToSpecified = true;
}
if (payload.serchArchived.HasValue)
{
request.SerchArchived = payload.serchArchived.Value;
request.SerchArchivedSpecified = true;
}
if (payload.archiveDateFrom.HasValue)
{
request.ArchiveDateFrom = payload.archiveDateFrom.Value;
request.ArchiveDateFromSpecified = true;
}
if (payload.archiveDateTo.HasValue)
{
request.ArchiveDateTo = payload.archiveDateTo.Value;
request.ArchiveDateToSpecified = true;
}
if (payload.inputDateFrom.HasValue)
{
request.inputDateFrom = payload.inputDateFrom.Value;
request.inputDateFromSpecified = true;
}
if (payload.inputDateTo.HasValue)
{
request.inputDateTo = payload.inputDateTo.Value;
request.inputDateToSpecified = true;
}
if (payload.excludePersonAsDataSource.HasValue)
{
request.ExcludePersonAsDataSource = payload.excludePersonAsDataSource.Value;
request.ExcludePersonAsDataSourceSpecified = true;
}
if (payload.excludeCurrentOrgAsDataSource.HasValue)
{
request.ExcludeCurrentOrgAsDataSource = payload.excludeCurrentOrgAsDataSource.Value;
request.ExcludeCurrentOrgAsDataSourceSpecified = true;
}
if (payload.excludeOtherOrgAsDataSource.HasValue)
{
request.ExcludeOtherOrgAsDataSource = payload.excludeOtherOrgAsDataSource.Value;
request.ExcludeOtherOrgAsDataSourceSpecified = true;
}
return request;
}
}
}

View File

@ -0,0 +1,43 @@
using Hcs.Broker.Api.Request.Exception;
using Hcs.Broker.Internal;
using Hcs.Service.Async.DeviceMetering;
namespace Hcs.Broker.Api.Request.DeviceMetering
{
internal class ImportMeteringDeviceValuesRequest(Client client) : DeviceMeteringRequestBase(client)
{
protected override bool CanBeRestarted => false;
internal async Task<bool> ExecuteAsync(importMeteringDeviceValuesRequestMeteringDevicesValues values, CancellationToken token)
{
// http://open-gkh.ru/DeviceMetering/importMeteringDeviceValuesRequest.html
var request = new importMeteringDeviceValuesRequest
{
Id = Constants.SIGNED_XML_ELEMENT_ID,
version = "10.0.1.1",
MeteringDevicesValues = [values]
};
var result = await SendAndWaitResultAsync(request, async asyncClient =>
{
var response = await asyncClient.importMeteringDeviceValuesAsync(CreateRequestHeader(), request);
return response.AckRequest.Ack;
}, token);
result.Items.OfType<ErrorMessageType>().ToList().ForEach(error =>
{
throw RemoteException.CreateNew(error.ErrorCode, error.Description);
});
result.Items.OfType<CommonResultType>().ToList().ForEach(commonResult =>
{
commonResult.Items.OfType<ErrorMessageType>().ToList().ForEach(error =>
{
throw RemoteException.CreateNew(error.ErrorCode, error.Description);
});
});
return true;
}
}
}

View File

@ -0,0 +1,16 @@
namespace Hcs.Broker.Api.Request
{
internal enum EndPoint
{
BillsAsync,
DebtRequestsAsync,
DeviceMeteringAsync,
HomeManagementAsync,
LicensesAsync,
NsiAsync,
NsiCommonAsync,
OrgRegistryAsync,
OrgRegistryCommonAsync,
PaymentsAsync
}
}

View File

@ -0,0 +1,28 @@
namespace Hcs.Broker.Api.Request
{
internal class EndPointLocator
{
private static readonly Dictionary<EndPoint, string> endPoints;
static EndPointLocator()
{
endPoints ??= [];
endPoints.Add(EndPoint.BillsAsync, "ext-bus-bills-service/services/BillsAsync");
endPoints.Add(EndPoint.DebtRequestsAsync, "ext-bus-debtreq-service/services/DebtRequestsAsync");
endPoints.Add(EndPoint.DeviceMeteringAsync, "ext-bus-device-metering-service/services/DeviceMeteringAsync");
endPoints.Add(EndPoint.HomeManagementAsync, "ext-bus-home-management-service/services/HomeManagementAsync");
endPoints.Add(EndPoint.LicensesAsync, "ext-bus-licenses-service/services/LicensesAsync");
endPoints.Add(EndPoint.NsiAsync, "ext-bus-nsi-service/services/NsiAsync");
endPoints.Add(EndPoint.NsiCommonAsync, "ext-bus-nsi-common-service/services/NsiCommonAsync");
endPoints.Add(EndPoint.OrgRegistryAsync, "ext-bus-org-registry-service/services/OrgRegistryAsync");
endPoints.Add(EndPoint.OrgRegistryCommonAsync, "ext-bus-org-registry-common-service/services/OrgRegistryCommonAsync");
endPoints.Add(EndPoint.PaymentsAsync, "ext-bus-payment-service/services/PaymentAsync");
}
internal static string GetPath(EndPoint endPoint)
{
return endPoints[endPoint];
}
}
}

View File

@ -0,0 +1,17 @@
namespace Hcs.Broker.Api.Request.Exception
{
/// <summary>
/// Исключение указывает на то, что сервер обнаружил что у
/// него нет объектов для выдачи по условию
/// </summary>
internal class NoResultsRemoteException : RemoteException
{
public NoResultsRemoteException(string description) : base(NO_OBJECTS_FOR_EXPORT, description) { }
public NoResultsRemoteException(string errorCode, string description) :
base(errorCode, description) { }
public NoResultsRemoteException(string errorCode, string description, System.Exception nested) :
base(errorCode, description, nested) { }
}
}

View File

@ -0,0 +1,63 @@
using Hcs.Broker.Internal;
namespace Hcs.Broker.Api.Request.Exception
{
internal class RemoteException : System.Exception
{
internal const string NO_OBJECTS_FOR_EXPORT = "INT002012";
internal const string MISSING_IN_REGISTRY = "INT002000";
internal const string ACCESS_DENIED = "AUT011003";
internal string ErrorCode { get; private set; }
internal string Description { get; private set; }
public RemoteException(string errorCode, string description)
: base(Combine(errorCode, description))
{
ErrorCode = errorCode;
Description = description;
}
public RemoteException(string errorCode, string description, System.Exception nestedException)
: base(Combine(errorCode, description), nestedException)
{
ErrorCode = errorCode;
Description = description;
}
private static string Combine(string errorCode, string description)
{
return $"Remote server returned an error: [{errorCode}] {description}";
}
internal static RemoteException CreateNew(string errorCode, string description, System.Exception nested = null)
{
if (string.Compare(errorCode, NO_OBJECTS_FOR_EXPORT) == 0)
{
return new NoResultsRemoteException(errorCode, description, nested);
}
return new RemoteException(errorCode, description);
}
internal static RemoteException CreateNew(RemoteException nested)
{
if (nested == null)
{
throw new ArgumentNullException(nameof(nested));
}
return CreateNew(nested.ErrorCode, nested.Description, nested);
}
/// <summary>
/// Возвращает true, если ошибка @e или ее вложенные ошибки содержат @errorCode
/// </summary>
internal static bool ContainsErrorCode(SystemException e, string errorCode)
{
if (e == null)
{
return false;
}
return Util.EnumerateInnerExceptions(e).OfType<RemoteException>().Where(x => x.ErrorCode == errorCode).Any();
}
}
}

View File

@ -0,0 +1,9 @@
namespace Hcs.Broker.Api.Request.Exception
{
internal class RestartTimeoutException : System.Exception
{
public RestartTimeoutException(string message) : base(message) { }
public RestartTimeoutException(string message, System.Exception innerException) : base(message, innerException) { }
}
}

View File

@ -0,0 +1,23 @@
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
namespace Hcs.Broker.Api.Request
{
internal class GostSigningEndpointBehavior(Client client) : IEndpointBehavior
{
private readonly Client client = client;
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.ClientMessageInspectors.Add(
new GostSigningMessageInspector(client));
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { }
public void Validate(ServiceEndpoint endpoint) { }
}
}

View File

@ -0,0 +1,170 @@
using CryptoPro.Security.Cryptography;
using CryptoPro.Security.Cryptography.X509Certificates;
using CryptoPro.Security.Cryptography.Xml;
using Hcs.Broker.Internal;
using System.Security.Cryptography;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.Text;
using System.Xml;
namespace Hcs.Broker.Api.Request
{
/// <summary>
/// Фильтр сообщений добавляет в XML-сообщение электронную подпись XADES/GOST
/// </summary>
internal class GostSigningMessageInspector(Client client) : IClientMessageInspector
{
private readonly Client client = client;
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
try
{
var filterHeader = "[Message Inspector]";
PurgeDebuggerHeaders(ref request);
var messageBody = GetMessageBodyString(ref request, Encoding.UTF8);
if (!messageBody.Contains(Constants.SIGNED_XML_ELEMENT_ID))
{
client.TryCaptureMessage(true, messageBody);
}
else
{
client.TryLog($"{filterHeader} signing message with key [{client.Certificate.FriendlyName}]...");
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml(messageBody);
var signedXml = SignXmlFileXades(
xmlDocument,
client.Certificate,
client.Certificate.PrivateKey as Gost3410_2012_256CryptoServiceProvider,
CpSignedXml.XmlDsigGost3411_2012_256Url);
stopwatch.Stop();
client.TryLog($"{filterHeader} message signed in {stopwatch.ElapsedMilliseconds} ms");
client.TryCaptureMessage(true, signedXml);
request = Message.CreateMessage(
XmlReaderFromString(signedXml), int.MaxValue, request.Version);
}
}
catch (System.Exception ex)
{
throw new System.Exception($"Exception occured in {GetType().Name}", ex);
}
return null;
}
private void PurgeDebuggerHeaders(ref Message request)
{
var limit = request.Headers.Count;
for (var i = 0; i < limit; ++i)
{
if (request.Headers[i].Name.Equals("VsDebuggerCausalityData"))
{
request.Headers.RemoveAt(i);
break;
}
}
}
private string GetMessageBodyString(ref Message request, Encoding encoding)
{
var mb = request.CreateBufferedCopy(int.MaxValue);
request = mb.CreateMessage();
var s = new MemoryStream();
var xw = XmlWriter.Create(s);
mb.CreateMessage().WriteMessage(xw);
xw.Flush();
s.Position = 0;
var bXML = new byte[s.Length];
s.Read(bXML, 0, (int)s.Length);
if (bXML[0] != (byte)'<')
{
return encoding.GetString(bXML, 3, bXML.Length - 3);
}
else
{
return encoding.GetString(bXML, 0, bXML.Length);
}
}
private string SignXmlFileXades(
XmlDocument doc,
CpX509Certificate certificate,
AsymmetricAlgorithm key,
string digestMethod,
bool useDsPrefix = false)
{
var keyInfo = new CpKeyInfo();
keyInfo.AddClause(new CpKeyInfoX509Data(certificate));
var signedXml = new CpXadesSignedXml(doc)
{
SigningKey = key,
KeyInfo = keyInfo
};
var reference = new CpReference()
{
Uri = "",
DigestMethod = digestMethod
};
var signTransform = new CpXmlDsigEnvelopedSignatureTransform();
reference.AddTransform(signTransform);
var c14nTransform = new CpXmlDsigC14NTransform();
reference.AddTransform(c14nTransform);
signedXml.AddReference(reference);
if (useDsPrefix)
{
signedXml.SignatureNodePrefix = "ds";
}
signedXml.ComputeXadesSignature();
var xmlDigitalSignature = signedXml.GetXml();
doc.DocumentElement.AppendChild(doc.ImportNode(xmlDigitalSignature, true));
if (doc.FirstChild is XmlDeclaration)
{
doc.RemoveChild(doc.FirstChild);
}
return doc.ToString();
}
private XmlReader XmlReaderFromString(string xml)
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write(xml);
writer.Flush();
stream.Position = 0;
return XmlReader.Create(stream);
}
public void AfterReceiveReply(ref Message reply, object correlationState)
{
client.TryCaptureMessage(false, reply.ToString());
}
}
}

View File

@ -0,0 +1,30 @@
using Hcs.Broker.Internal;
using Hcs.Service.Async.HouseManagement;
namespace Hcs.Broker.Api.Request.HouseManagement
{
internal class ExportAccountRequest(Client client) : HouseManagementRequestBase(client)
{
protected override bool EnableMinimalResponseWaitDelay => false;
internal async Task<IEnumerable<exportAccountResultType>> ExecuteAsync(string fiasHouseGuid, CancellationToken token)
{
// http://open-gkh.ru/HouseManagement/exportAccountRequest.html
var request = new exportAccountRequest
{
Id = Constants.SIGNED_XML_ELEMENT_ID,
version = "10.0.1.1",
Items = [fiasHouseGuid],
ItemsElementName = [ItemsChoiceType26.FIASHouseGuid]
};
var result = await SendAndWaitResultAsync(request, async asyncClient =>
{
var response = await asyncClient.exportAccountDataAsync(CreateRequestHeader(), request);
return response.AckRequest.Ack;
}, token);
return result.Items.OfType<exportAccountResultType>();
}
}
}

View File

@ -0,0 +1,29 @@
using Hcs.Broker.Internal;
using Hcs.Service.Async.HouseManagement;
namespace Hcs.Broker.Api.Request.HouseManagement
{
internal class ExportHouseRequest(Client client) : HouseManagementRequestBase(client)
{
protected override bool EnableMinimalResponseWaitDelay => false;
internal async Task<IEnumerable<exportHouseResultType>> ExecuteAsync(string fiasHouseGuid, CancellationToken token)
{
// http://open-gkh.ru/HouseManagement/exportHouseRequest.html
var request = new exportHouseRequest
{
Id = Constants.SIGNED_XML_ELEMENT_ID,
version = "15.6.0.1",
FIASHouseGuid = fiasHouseGuid
};
var result = await SendAndWaitResultAsync(request, async asyncClient =>
{
var response = await asyncClient.exportHouseDataAsync(CreateRequestHeader(), request);
return response.AckRequest.Ack;
}, token);
return result.Items.OfType<exportHouseResultType>();
}
}
}

View File

@ -0,0 +1,130 @@
using Hcs.Broker.Api.Request.Exception;
using Hcs.Broker.Internal;
using Hcs.Service.Async.HouseManagement;
namespace Hcs.Broker.Api.Request.HouseManagement
{
internal class ExportSupplyResourceContractDataRequest(Client client) : HouseManagementRequestBase(client)
{
protected override bool EnableMinimalResponseWaitDelay => false;
internal async Task<IEnumerable<exportSupplyResourceContractResultType>> ExecuteAsync(CancellationToken token)
{
var result = new List<exportSupplyResourceContractResultType>();
void OnResultReceived(exportSupplyResourceContractResultType[] contracts)
{
if (contracts?.Length > 0)
{
result.AddRange(contracts);
}
}
var pageNum = 0;
Guid? exportContractRootGuid = null;
while (true)
{
pageNum++;
client.TryLog($"Querying page #{pageNum}...");
var data = await QueryBatchAsync(null, null, exportContractRootGuid, OnResultReceived, token);
if (data.IsLastPage)
{
break;
}
exportContractRootGuid = data.NextGuid;
}
return result;
}
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;
}
internal async Task<exportSupplyResourceContractResultType> ExecuteAsync(string contractNumber, CancellationToken token)
{
exportSupplyResourceContractResultType result = null;
void OnResultReceived(exportSupplyResourceContractResultType[] contracts)
{
result = contracts?.Length > 0 ? contracts[0] : null;
}
await QueryBatchAsync(null, contractNumber, 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]
};
try
{
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);
}
catch (NoResultsRemoteException)
{
return PaginationData.CreateLastPageData();
}
catch (System.Exception e)
{
throw e;
}
}
}
}

View File

@ -0,0 +1,91 @@
using Hcs.Broker.Api.Request.Exception;
using Hcs.Broker.Internal;
using Hcs.Service.Async.HouseManagement;
namespace Hcs.Broker.Api.Request.HouseManagement
{
internal class ExportSupplyResourceContractObjectAddressDataRequest(Client client) : HouseManagementRequestBase(client)
{
internal async Task<IEnumerable<exportSupplyResourceContractObjectAddressResultType>> ExecuteAsync(Guid contractRootGuid, CancellationToken token)
{
var result = new List<exportSupplyResourceContractObjectAddressResultType>();
void OnResultReceived(exportSupplyResourceContractObjectAddressResultType[] addresses)
{
if (addresses?.Length > 0)
{
result.AddRange(addresses);
}
}
var pageNum = 0;
Guid? exportObjectGuid = null;
while (true)
{
pageNum++;
client.TryLog($"Querying page #{pageNum}...");
var data = await QueryBatchAsync(contractRootGuid, exportObjectGuid, OnResultReceived, token);
if (data.IsLastPage)
{
break;
}
exportObjectGuid = data.NextGuid;
}
return result;
}
private async Task<PaginationData> QueryBatchAsync(
Guid contractRootGuid, Guid? exportObjectGuid,
Action<exportSupplyResourceContractObjectAddressResultType[]> onResultReceived,
CancellationToken token)
{
var itemsElementName = new List<ItemsChoiceType34>();
var items = new List<string>();
itemsElementName.Add(ItemsChoiceType34.ContractRootGUID);
items.Add(contractRootGuid.ToString());
if (exportObjectGuid.HasValue)
{
itemsElementName.Add(ItemsChoiceType34.ExportObjectGUID);
items.Add(exportObjectGuid.ToString());
}
// http://open-gkh.ru/HouseManagement/exportSupplyResourceContractObjectAddressRequest.html
var request = new exportSupplyResourceContractObjectAddressRequest
{
Id = Constants.SIGNED_XML_ELEMENT_ID,
version = "13.1.1.1",
ItemsElementName = [.. itemsElementName],
Items = [.. items]
};
try
{
var result = await SendAndWaitResultAsync(request, async asyncClient =>
{
var ackResponse = await asyncClient.exportSupplyResourceContractObjectAddressDataAsync(
CreateRequestHeader(), request);
return ackResponse.AckRequest.Ack;
}, token);
var contractResult = result.Items.OfType<getStateResultExportSupplyResourceContractObjectAddress>().First();
onResultReceived?.Invoke(contractResult.ObjectAddress);
return new PaginationData(contractResult.Item);
}
catch (NoResultsRemoteException)
{
return PaginationData.CreateLastPageData();
}
catch (System.Exception e)
{
throw e;
}
}
}
}

View File

@ -0,0 +1,78 @@
using Hcs.Broker.Api.Request.Adapter;
using Hcs.Broker.Api.Request.Exception;
using Hcs.Service.Async.HouseManagement;
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.Broker.Api.Request.HouseManagement
{
internal class HouseManagementRequestBase(Client 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;
protected getStateResultImportResultCommonResult[] GetCommonResults(IEnumerable<getStateResultImportResult> importResults)
{
var result = new List<getStateResultImportResultCommonResult>();
foreach (var importResult in importResults)
{
importResult.Items.OfType<ErrorMessageType>().ToList().ForEach(error =>
{
throw RemoteException.CreateNew(error.ErrorCode, error.Description);
});
var commonResults = importResult.Items.OfType<getStateResultImportResultCommonResult>();
foreach (var commonResult in commonResults)
{
commonResult.Items.OfType<CommonResultTypeError>().ToList().ForEach(error =>
{
throw RemoteException.CreateNew(error.ErrorCode, error.Description);
});
}
result.AddRange(commonResults);
}
return [.. result];
}
}
}

View File

@ -0,0 +1,114 @@
using Hcs.Broker.Api.Payload.HouseManagement;
using Hcs.Broker.Api.Request.Exception;
using Hcs.Broker.Internal;
using Hcs.Service.Async.HouseManagement;
namespace Hcs.Broker.Api.Request.HouseManagement
{
internal class ImportAccountDataRequest(Client client) : HouseManagementRequestBase(client)
{
protected override bool CanBeRestarted => false;
internal async Task<bool> ExecuteAsync(ImportAccountDataPayload payload, CancellationToken token)
{
// TODO: Добавить проверку пейлоада
// http://open-gkh.ru/HouseManagement/importAccountRequest.html
var request = new importAccountRequest
{
Id = Constants.SIGNED_XML_ELEMENT_ID,
version = "10.0.1.1",
Account = [GetAccountFromPayload(payload)]
};
var result = await SendAndWaitResultAsync(request, async asyncClient =>
{
var response = await asyncClient.importAccountDataAsync(CreateRequestHeader(), request);
return response.AckRequest.Ack;
}, token);
result.Items.OfType<ErrorMessageType>().ToList().ForEach(error =>
{
throw RemoteException.CreateNew(error.ErrorCode, error.Description);
});
var importResults = result.Items.OfType<getStateResultImportResult>();
var commonResults = GetCommonResults(importResults);
foreach (var commonResult in commonResults)
{
if (commonResult.ItemElementName == ItemChoiceType26.ImportAccount)
{
return commonResult.Item is getStateResultImportResultCommonResultImportAccount;
}
}
return false;
}
private importAccountRequestAccount GetAccountFromPayload(ImportAccountDataPayload payload)
{
var account = new importAccountRequestAccount()
{
TransportGUID = Guid.NewGuid().ToString(),
AccountNumber = payload.accountNumber,
AccountGUID = payload.accountGUID,
AccountReasons = payload.accountReasons,
Item = true,
Accommodation = payload.accomodations,
PayerInfo = payload.payerInfo
};
switch (payload.accountType)
{
case ImportAccountDataPayload.AccountType.UO:
account.ItemElementName = ItemChoiceType8.isUOAccount;
break;
case ImportAccountDataPayload.AccountType.RSO:
account.ItemElementName = ItemChoiceType8.isRSOAccount;
break;
case ImportAccountDataPayload.AccountType.CR:
account.ItemElementName = ItemChoiceType8.isCRAccount;
break;
case ImportAccountDataPayload.AccountType.RC:
account.ItemElementName = ItemChoiceType8.isRCAccount;
break;
case ImportAccountDataPayload.AccountType.OGVorOMS:
account.ItemElementName = ItemChoiceType8.isOGVorOMSAccount;
break;
case ImportAccountDataPayload.AccountType.TKO:
account.ItemElementName = ItemChoiceType8.isTKOAccount;
break;
}
if (payload.livingPersonsNumber.HasValue)
{
account.LivingPersonsNumber = payload.livingPersonsNumber.Value.ToString();
}
if (payload.totalSquare.HasValue)
{
account.TotalSquare = payload.totalSquare.Value;
account.TotalSquareSpecified = true;
}
if (payload.residentialSquare.HasValue)
{
account.ResidentialSquare = payload.residentialSquare.Value;
account.ResidentialSquareSpecified = true;
}
if (payload.heatedArea.HasValue)
{
account.HeatedArea = payload.heatedArea.Value;
account.HeatedAreaSpecified = true;
}
return account;
}
}
}

View File

@ -0,0 +1,81 @@
using Hcs.Broker.Api.Payload.HouseManagement;
using Hcs.Broker.Api.Request.Exception;
using Hcs.Broker.Internal;
using Hcs.Service.Async.HouseManagement;
namespace Hcs.Broker.Api.Request.HouseManagement
{
internal class ImportContractDataRequest(Client client) : HouseManagementRequestBase(client)
{
protected override bool CanBeRestarted => false;
internal async Task<importContractResultType> ExecuteAsync(ImportContractDataPayload payload, CancellationToken token)
{
// TODO: Добавить проверку пейлоада
// http://open-gkh.ru/HouseManagement/importContractRequest/Contract.html
var contract = new importContractRequestContract
{
TransportGUID = Guid.NewGuid().ToString(),
Item = GetContractFromPayload(payload)
};
// http://open-gkh.ru/HouseManagement/importContractRequest.html
var request = new importContractRequest
{
Id = Constants.SIGNED_XML_ELEMENT_ID,
version = "11.9.0.1",
Contract = [contract]
};
var result = await SendAndWaitResultAsync(request, async asyncClient =>
{
var response = await asyncClient.importContractDataAsync(CreateRequestHeader(), request);
return response.AckRequest.Ack;
}, token);
result.Items.OfType<ErrorMessageType>().ToList().ForEach(error =>
{
throw RemoteException.CreateNew(error.ErrorCode, error.Description);
});
var importResults = result.Items.OfType<getStateResultImportResult>();
var commonResults = GetCommonResults(importResults);
foreach (var commonResult in commonResults)
{
if (commonResult.ItemElementName == ItemChoiceType26.importContract)
{
if (commonResult.Item is importContractResultType importedContract)
{
return importedContract;
}
}
}
return null;
}
private importContractRequestContractPlacingContract GetContractFromPayload(ImportContractDataPayload payload)
{
// http://open-gkh.ru/HouseManagement/importContractRequest/Contract/PlacingContract.html
var contract = new importContractRequestContractPlacingContract()
{
ContractObject = payload.contractObjects,
DocNum = payload.docNum,
SigningDate = payload.signingDate,
EffectiveDate = payload.effectiveDate,
PlanDateComptetion = payload.planDateComptetion,
Item = true,
ItemElementName = ItemChoiceType13.Owners,
ContractBase = new nsiRef()
{
Code = payload.contractBase.Code,
GUID = payload.contractBase.GUID
},
DateDetails = payload.dateDetailsType,
ContractAttachment = payload.contractAttachment
};
return contract;
}
}
}

View File

@ -0,0 +1,113 @@
using Hcs.Broker.Api.Payload.HouseManagement;
using Hcs.Broker.Api.Request.Exception;
using Hcs.Broker.Internal;
using Hcs.Service.Async.HouseManagement;
namespace Hcs.Broker.Api.Request.HouseManagement
{
internal class ImportHouseUODataRequest(Client client) : HouseManagementRequestBase(client)
{
protected override bool CanBeRestarted => false;
internal async Task<bool> ExecuteAsync(ImportLivingHouseUODataPayload payload, CancellationToken token)
{
// TODO: Добавить проверку пейлоада
// http://open-gkh.ru/HouseManagement/importHouseUORequest.html
var request = new importHouseUORequest
{
Id = Constants.SIGNED_XML_ELEMENT_ID,
version = "13.2.3.2",
Item = GetLivingHouseFromPayload(payload)
};
var result = await SendAndWaitResultAsync(request, async asyncClient =>
{
var response = await asyncClient.importHouseUODataAsync(CreateRequestHeader(), request);
return response.AckRequest.Ack;
}, token);
result.Items.OfType<ErrorMessageType>().ToList().ForEach(error =>
{
throw RemoteException.CreateNew(error.ErrorCode, error.Description);
});
var importResults = result.Items.OfType<getStateResultImportResult>();
var commonResults = GetCommonResults(importResults);
foreach (var commonResult in commonResults)
{
if (commonResult.ItemElementName == ItemChoiceType26.ImportHouseUO)
{
return commonResult.Item is OGFImportStatusType;
}
}
return false;
}
private importHouseUORequestLivingHouse GetLivingHouseFromPayload(ImportLivingHouseUODataPayload payload)
{
var livingHouse = new importHouseUORequestLivingHouseLivingHouseToCreate()
{
TransportGUID = Guid.NewGuid().ToString(),
BasicCharacteristicts = new HouseBasicUOType()
{
FIASHouseGuid = payload.fiasHouseGuid.ToString(),
TotalSquare = payload.totalSquare,
State = new nsiRef()
{
Code = payload.state.Code,
GUID = payload.state.GUID
},
LifeCycleStage = new nsiRef()
{
Code = payload.lifeCycleStage.Code,
GUID = payload.lifeCycleStage.GUID
},
UsedYear = payload.usedYear,
FloorCount = payload.floorCount,
OKTMO = payload.oktmo,
OlsonTZ = new nsiRef()
{
Code = payload.olsonTZ.Code,
GUID = payload.olsonTZ.GUID
},
CulturalHeritage = payload.culturalHeritage,
OGFData = payload.ogfData,
// TODO: Разобраться с кадастровым номером
Items = [true, payload.conditionalNumber],
ItemsElementName = [ItemsChoiceType3.NoCadastralNumber, ItemsChoiceType3.ConditionalNumber]
}
};
if (!payload.isMunicipalProperty)
{
livingHouse.BasicCharacteristicts.IsMunicipalProperty = false;
livingHouse.BasicCharacteristicts.IsMunicipalPropertySpecified = true;
}
if (!payload.isRegionProperty)
{
livingHouse.BasicCharacteristicts.IsRegionProperty = false;
livingHouse.BasicCharacteristicts.IsRegionPropertySpecified = true;
}
if (payload.hasBlocks)
{
livingHouse.HasBlocks = true;
livingHouse.HasBlocksSpecified = true;
}
if (payload.hasMultipleHousesWithSameAddress)
{
livingHouse.HasMultipleHousesWithSameAddress = true;
livingHouse.HasMultipleHousesWithSameAddressSpecified = true;
}
return new importHouseUORequestLivingHouse()
{
Item = livingHouse
};
}
}
}

View File

@ -0,0 +1,52 @@
using Hcs.Broker.Api.Request.Exception;
using Hcs.Broker.Internal;
using Hcs.Service.Async.HouseManagement;
namespace Hcs.Broker.Api.Request.HouseManagement
{
internal class ImportMeteringDeviceDataRequest(Client client) : HouseManagementRequestBase(client)
{
protected override bool CanBeRestarted => false;
internal async Task<bool> ExecuteAsync(MeteringDeviceFullInformationType meteringDevice, CancellationToken token)
{
// http://open-gkh.ru/HouseManagement/importMeteringDeviceDataRequest.html
var request = new importMeteringDeviceDataRequest
{
Id = Constants.SIGNED_XML_ELEMENT_ID,
version = "11.1.0.8",
MeteringDevice =
[
new importMeteringDeviceDataRequestMeteringDevice()
{
TransportGUID = Guid.NewGuid().ToString(),
Item = meteringDevice
}
]
};
var result = await SendAndWaitResultAsync(request, async asyncClient =>
{
var response = await asyncClient.importMeteringDeviceDataAsync(CreateRequestHeader(), request);
return response.AckRequest.Ack;
}, token);
result.Items.OfType<ErrorMessageType>().ToList().ForEach(error =>
{
throw RemoteException.CreateNew(error.ErrorCode, error.Description);
});
var importResults = result.Items.OfType<getStateResultImportResult>();
var commonResults = GetCommonResults(importResults);
foreach (var commonResult in commonResults)
{
if (commonResult.ItemElementName == ItemChoiceType26.importMeteringDevice)
{
return commonResult.Item is getStateResultImportResultCommonResultImportMeteringDevice;
}
}
return false;
}
}
}

View File

@ -0,0 +1,108 @@
using Hcs.Broker.Api.Payload.HouseManagement;
using Hcs.Broker.Api.Request.Exception;
using Hcs.Broker.Internal;
using Hcs.Service.Async.HouseManagement;
namespace Hcs.Broker.Api.Request.HouseManagement
{
internal class ImportNotificationDataRequest(Client client) : HouseManagementRequestBase(client)
{
protected override bool CanBeRestarted => false;
internal async Task<bool> ExecuteAsync(ImportNotificationDataPayload payload, CancellationToken token)
{
// TODO: Добавить проверку пейлоада
var notification = new importNotificationRequestNotification()
{
TransportGUID = Guid.NewGuid().ToString(),
Item = GetNotificationFromPayload(payload)
};
// http://open-gkh.ru/HouseManagement/importNotificationRequest.html
var request = new importNotificationRequest
{
Id = Constants.SIGNED_XML_ELEMENT_ID,
version = "13.2.2.0",
notification = [notification]
};
var result = await SendAndWaitResultAsync(request, async asyncClient =>
{
var response = await asyncClient.importNotificationDataAsync(CreateRequestHeader(), request);
return response.AckRequest.Ack;
}, token);
result.Items.OfType<ErrorMessageType>().ToList().ForEach(error =>
{
throw RemoteException.CreateNew(error.ErrorCode, error.Description);
});
var importResults = result.Items.OfType<getStateResultImportResult>();
var commonResults = GetCommonResults(importResults);
return true;
}
private importNotificationRequestNotificationCreate GetNotificationFromPayload(ImportNotificationDataPayload payload)
{
var notification = new importNotificationRequestNotificationCreate();
if (!string.IsNullOrEmpty(payload.topic))
{
notification.Item = payload.topic;
}
else
{
notification.Item = payload.topicFromRegistry;
}
if (payload.isImportant)
{
notification.IsImportant = true;
notification.IsImportantSpecified = true;
}
notification.content = payload.content;
var items = new List<object>();
var itemsElementName = new List<ItemsChoiceType29>();
foreach (var tuple in payload.destinations)
{
items.Add(tuple.Item2);
itemsElementName.Add(tuple.Item1);
}
notification.Items = [.. items];
notification.ItemsElementName = [.. itemsElementName];
if (payload.isNotLimit)
{
notification.Items1 = [true];
notification.Items1ElementName = [Items1ChoiceType.IsNotLimit];
}
else
{
notification.Items1 = [payload.startDate.Value, payload.endDate.Value];
notification.Items1ElementName = [Items1ChoiceType.StartDate, Items1ChoiceType.EndDate];
}
// TODO: Добавить добавление аттачмента
if (payload.isShipOff)
{
notification.IsShipOff = true;
notification.IsShipOffSpecified = true;
}
if (payload.isForPublishToMobileApp)
{
notification.IsForPublishToMobileApp = true;
notification.IsForPublishToMobileAppSpecified = true;
}
notification.MobileAppData = payload.mobileAppData;
return notification;
}
}
}

View File

@ -0,0 +1,297 @@
using Hcs.Broker.Api.Payload.HouseManagement;
using Hcs.Broker.Api.Request.Exception;
using Hcs.Broker.Internal;
using Hcs.Service.Async.HouseManagement;
namespace Hcs.Broker.Api.Request.HouseManagement
{
internal class ImportSupplyResourceContractDataRequest(Client client) : HouseManagementRequestBase(client)
{
protected override bool CanBeRestarted => false;
internal async Task<getStateResultImportResultCommonResultImportSupplyResourceContract> ExecuteAsync(ImportSupplyResourceContractDataPayload payload, CancellationToken token)
{
ThrowIfPayloadIncorrect(payload);
// http://open-gkh.ru/HouseManagement/SupplyResourceContractType.html
var contract = new importSupplyResourceContractRequestContract
{
TransportGUID = Guid.NewGuid().ToString(),
Item1 = GetContractFromPayload(payload)
};
// http://open-gkh.ru/HouseManagement/importSupplyResourceContractRequest.html
var request = new importSupplyResourceContractRequest
{
Id = Constants.SIGNED_XML_ELEMENT_ID,
version = "11.3.0.5",
Contract = [contract]
};
var result = await SendAndWaitResultAsync(request, async asyncClient =>
{
var response = await asyncClient.importSupplyResourceContractDataAsync(CreateRequestHeader(), request);
return response.AckRequest.Ack;
}, token);
result.Items.OfType<ErrorMessageType>().ToList().ForEach(error =>
{
throw RemoteException.CreateNew(error.ErrorCode, error.Description);
});
var importResults = result.Items.OfType<getStateResultImportResult>();
var commonResults = GetCommonResults(importResults);
foreach (var commonResult in commonResults)
{
if (commonResult.ItemElementName == ItemChoiceType26.ImportSupplyResourceContract)
{
if (commonResult.Item is getStateResultImportResultCommonResultImportSupplyResourceContract importedContract)
{
return importedContract;
}
}
}
return null;
}
private void ThrowIfPayloadIncorrect(ImportSupplyResourceContractDataPayload payload)
{
if (string.IsNullOrEmpty(payload.contractNumber))
{
throw new ArgumentException($"{nameof(payload.contractNumber)} is empty");
}
if (payload.signingDate.Equals(default) || payload.effectiveDate.Equals(default))
{
throw new ArgumentException($"{nameof(payload.signingDate)} OR/AND {nameof(payload.effectiveDate)} are default");
}
if (!payload.comptetionDate.HasValue && payload.automaticRollOverOneYear)
{
throw new ArgumentException($"{nameof(payload.comptetionDate)} is null but {nameof(payload.automaticRollOverOneYear)} has value");
}
if (payload.period == null && (payload.volumeDepends.HasValue && payload.volumeDepends.Value
|| payload.meteringDeviceInformation.HasValue && payload.meteringDeviceInformation.Value))
{
throw new ArgumentException($"{nameof(payload.period)} is null but {nameof(payload.volumeDepends)} OR/AND {nameof(payload.meteringDeviceInformation)} have value");
}
if (payload.indicationsAnyDay && payload.period == null)
{
throw new ArgumentException($"{nameof(payload.indicationsAnyDay)} has value but {nameof(payload.period)} is null");
}
// TODO: Add counterparty check
if (payload.plannedVolumeType.HasValue && !payload.isPlannedVolume)
{
throw new ArgumentException($"{nameof(payload.plannedVolumeType)} has value but {nameof(payload.isPlannedVolume)} is false");
}
if (payload.contractSubject == null || payload.contractSubject.Length == 0)
{
throw new ArgumentException($"{nameof(payload.contractSubject)} is empty");
}
if (payload.contractSubject != null && payload.contractSubject.Length > 100)
{
throw new ArgumentException($"{nameof(payload.contractSubject)} exceeds its limit ({payload.contractSubject.Length} of 100)");
}
if (payload.countingResource.HasValue && (!payload.accrualProcedure.HasValue ||
payload.accrualProcedure.Value == SupplyResourceContractTypeAccrualProcedure.O))
{
throw new ArgumentException($"{nameof(payload.countingResource)} has value but {nameof(payload.accrualProcedure)} is null OR has inappropriate value");
}
// TODO: Add noConnectionToWaterSupply check
if (payload.objectAddress == null || payload.objectAddress.Length == 0)
{
throw new ArgumentException($"{nameof(payload.objectAddress)} is empty");
}
// TODO: Add quality check
// TODO: Add otherQualityIndicator check
// TODO: Add temperatureChart check
if (payload.billingDate == null && (payload.counterparty is not SupplyResourceContractTypeOrganization ||
payload.meteringDeviceInformation.HasValue))
{
throw new ArgumentException($"{nameof(payload.billingDate)} is null but {nameof(payload.meteringDeviceInformation)} has value");
}
if (payload.billingDate != null && payload.oneTimePayment.HasValue && payload.oneTimePayment.Value)
{
throw new ArgumentException($"{nameof(payload.billingDate)} has value but {nameof(payload.oneTimePayment)} is true");
}
if (payload.paymentDate == null && payload.counterparty is not SupplyResourceContractTypeOrganization &&
payload.isContract && payload.oneTimePayment.HasValue && !payload.oneTimePayment.Value)
{
throw new ArgumentException($"{nameof(payload.paymentDate)} is null but should have value");
}
if (payload.paymentDate != null && payload.oneTimePayment.HasValue && payload.oneTimePayment.Value)
{
throw new ArgumentException($"{nameof(payload.paymentDate)} has value but {nameof(payload.oneTimePayment)} is true");
}
if (payload.providingInformationDate == null && payload.counterparty is SupplyResourceContractTypeOrganization &&
payload.countingResource.HasValue && payload.countingResource.Value == SupplyResourceContractTypeCountingResource.R &&
payload.isContract)
{
throw new ArgumentException($"{nameof(payload.providingInformationDate)} is null but should have value");
}
if (!payload.meteringDeviceInformation.HasValue &&
payload.countingResource.HasValue && payload.countingResource == SupplyResourceContractTypeCountingResource.R)
{
throw new ArgumentException($"{nameof(payload.meteringDeviceInformation)} is null but should have value");
}
if (payload.volumeDepends.HasValue && (payload.counterparty is SupplyResourceContractTypeOrganization ||
payload.oneTimePayment.HasValue && payload.oneTimePayment.Value))
{
throw new ArgumentException($"{nameof(payload.volumeDepends)} has value but should have not one");
}
if (payload.oneTimePayment.HasValue && payload.counterparty is SupplyResourceContractTypeOrganization)
{
throw new ArgumentException($"{nameof(payload.oneTimePayment)} has value but {nameof(payload.counterparty)} has inappropriate value");
}
// TODO: Add accrualProcedure check
}
private SupplyResourceContractType GetContractFromPayload(ImportSupplyResourceContractDataPayload payload)
{
// http://open-gkh.ru/HouseManagement/SupplyResourceContractType.html
var contract = new SupplyResourceContractType();
if (payload.isContract)
{
var isContract = new SupplyResourceContractTypeIsContract()
{
ContractNumber = payload.contractNumber,
SigningDate = payload.signingDate,
EffectiveDate = payload.effectiveDate
};
contract.Item = isContract;
}
else
{
var isNotContract = new SupplyResourceContractTypeIsNotContract()
{
ContractNumber = payload.contractNumber,
SigningDate = payload.signingDate,
SigningDateSpecified = true,
EffectiveDate = payload.effectiveDate,
EffectiveDateSpecified = true
};
contract.Item = isNotContract;
}
var items = new List<object>();
var itemsElementName = new List<ItemsChoiceType9>();
if (payload.indefiniteTerm)
{
items.Add(payload.indefiniteTerm);
itemsElementName.Add(ItemsChoiceType9.IndefiniteTerm);
}
if (payload.automaticRollOverOneYear)
{
items.Add(payload.automaticRollOverOneYear);
itemsElementName.Add(ItemsChoiceType9.AutomaticRollOverOneYear);
items.Add(payload.comptetionDate.Value);
itemsElementName.Add(ItemsChoiceType9.ComptetionDate);
}
if (items.Count > 0 && itemsElementName.Count > 0)
{
contract.Items = [.. items];
contract.ItemsElementName = [.. itemsElementName];
}
if (payload.period != null)
{
contract.Period = payload.period;
}
if (payload.indicationsAnyDay)
{
contract.IndicationsAnyDay = true;
contract.IndicationsAnyDaySpecified = true;
}
if (payload.contractBase != null && payload.contractBase.Length > 0)
{
contract.ContractBase = payload.contractBase;
}
contract.Item1 = payload.counterparty;
contract.IsPlannedVolume = payload.isPlannedVolume;
if (payload.plannedVolumeType.HasValue)
{
contract.PlannedVolumeType = payload.plannedVolumeType.Value;
contract.PlannedVolumeTypeSpecified = true;
}
contract.ContractSubject = payload.contractSubject;
if (payload.countingResource.HasValue)
{
contract.CountingResource = payload.countingResource.Value;
contract.CountingResourceSpecified = true;
}
contract.SpecifyingQualityIndicators = payload.specifyingQualityIndicators;
if (payload.noConnectionToWaterSupply)
{
contract.NoConnectionToWaterSupply = true;
contract.NoConnectionToWaterSupplySpecified = true;
}
contract.ObjectAddress = payload.objectAddress;
contract.Quality = payload.quality;
contract.OtherQualityIndicator = payload.otherQualityIndicator;
contract.TemperatureChart = payload.temperatureChart;
contract.BillingDate = payload.billingDate;
contract.PaymentDate = payload.paymentDate;
contract.ProvidingInformationDate = payload.providingInformationDate;
if (payload.meteringDeviceInformation.HasValue)
{
contract.MeteringDeviceInformation = payload.meteringDeviceInformation.Value;
contract.MeteringDeviceInformationSpecified = true;
}
if (payload.volumeDepends.HasValue)
{
contract.VolumeDepends = payload.volumeDepends.Value;
contract.VolumeDependsSpecified = true;
}
if (payload.oneTimePayment.HasValue)
{
contract.OneTimePayment = payload.oneTimePayment.Value;
contract.OneTimePaymentSpecified = true;
}
if (payload.accrualProcedure.HasValue)
{
contract.AccrualProcedure = payload.accrualProcedure.Value;
contract.AccrualProcedureSpecified = true;
}
contract.Tariff = payload.tariff;
contract.Norm = payload.norm;
return contract;
}
}
}

View File

@ -0,0 +1,301 @@
using Hcs.Broker.Api.Payload.HouseManagement;
using Hcs.Broker.Api.Request.Exception;
using Hcs.Broker.Internal;
using Hcs.Service.Async.HouseManagement;
namespace Hcs.Broker.Api.Request.HouseManagement
{
internal class ImportSupplyResourceContractProjectRequest(Client client) : HouseManagementRequestBase(client)
{
protected override bool CanBeRestarted => false;
internal async Task<getStateResultImportResultCommonResultImportSupplyResourceContractProject> ExecuteAsync(ImportSupplyResourceContractProjectPayload payload, CancellationToken token)
{
ThrowIfPayloadIncorrect(payload);
// http://open-gkh.ru/HouseManagement/importSupplyResourceContractProjectRequest/Contract.html
var contract = new importSupplyResourceContractProjectRequestContract
{
TransportGUID = Guid.NewGuid().ToString(),
Item1 = GetContractFromPayload(payload)
};
// http://open-gkh.ru/HouseManagement/importSupplyResourceContractProjectRequest.html
var request = new importSupplyResourceContractProjectRequest
{
Id = Constants.SIGNED_XML_ELEMENT_ID,
version = "11.7.0.3",
Contract = [contract]
};
var result = await SendAndWaitResultAsync(request, async asyncClient =>
{
var response = await asyncClient.importSupplyResourceContractProjectDataAsync(CreateRequestHeader(), request);
return response.AckRequest.Ack;
}, token);
result.Items.OfType<ErrorMessageType>().ToList().ForEach(error =>
{
throw RemoteException.CreateNew(error.ErrorCode, error.Description);
});
var importResults = result.Items.OfType<getStateResultImportResult>();
var commonResults = GetCommonResults(importResults);
foreach (var commonResult in commonResults)
{
if (commonResult.ItemElementName == ItemChoiceType26.ImportSupplyResourceContractProject)
{
if (commonResult.Item is getStateResultImportResultCommonResultImportSupplyResourceContractProject importedContract)
{
return importedContract;
}
}
}
return null;
}
// TODO: Дополнить проверки
private void ThrowIfPayloadIncorrect(ImportSupplyResourceContractProjectPayload payload)
{
if (string.IsNullOrEmpty(payload.contractNumber))
{
throw new ArgumentException($"{nameof(payload.contractNumber)} is empty");
}
if (payload.signingDate.Equals(default) || payload.effectiveDate.Equals(default))
{
throw new ArgumentException($"{nameof(payload.signingDate)} OR/AND {nameof(payload.effectiveDate)} are default");
}
if (!payload.comptetionDate.HasValue && payload.automaticRollOverOneYear)
{
throw new ArgumentException($"{nameof(payload.comptetionDate)} is null but {nameof(payload.automaticRollOverOneYear)} has value");
}
if (payload.period == null && (payload.volumeDepends.HasValue && payload.volumeDepends.Value
|| payload.meteringDeviceInformation.HasValue && payload.meteringDeviceInformation.Value))
{
throw new ArgumentException($"{nameof(payload.period)} is null but {nameof(payload.volumeDepends)} OR/AND {nameof(payload.meteringDeviceInformation)} have value");
}
if (payload.indicationsAnyDay && payload.period == null)
{
throw new ArgumentException($"{nameof(payload.indicationsAnyDay)} has value but {nameof(payload.period)} is null");
}
// TODO: Add counterparty check
if (payload.plannedVolumeType.HasValue && !payload.isPlannedVolume)
{
throw new ArgumentException($"{nameof(payload.plannedVolumeType)} has value but {nameof(payload.isPlannedVolume)} is false");
}
if (payload.contractSubject == null || payload.contractSubject.Length == 0)
{
throw new ArgumentException($"{nameof(payload.contractSubject)} is empty");
}
if (payload.contractSubject != null && payload.contractSubject.Length > 100)
{
throw new ArgumentException($"{nameof(payload.contractSubject)} exceeds its limit ({payload.contractSubject.Length} of 100)");
}
if (payload.countingResource.HasValue && (!payload.accrualProcedure.HasValue ||
payload.accrualProcedure.Value == SupplyResourceContractProjectTypeAccrualProcedure.O))
{
throw new ArgumentException($"{nameof(payload.countingResource)} has value but {nameof(payload.accrualProcedure)} is null OR has inappropriate value");
}
// TODO: Add noConnectionToWaterSupply check
// TODO: Add quality check
// TODO: Add otherQualityIndicator check
// TODO: Add temperatureChart check
if (payload.billingDate == null && (payload.counterparty is not SupplyResourceContractTypeOrganization ||
payload.meteringDeviceInformation.HasValue))
{
throw new ArgumentException($"{nameof(payload.billingDate)} is null but {nameof(payload.meteringDeviceInformation)} has value");
}
if (payload.billingDate != null && payload.oneTimePayment.HasValue && payload.oneTimePayment.Value)
{
throw new ArgumentException($"{nameof(payload.billingDate)} has value but {nameof(payload.oneTimePayment)} is true");
}
if (payload.paymentDate == null && payload.counterparty is not SupplyResourceContractTypeOrganization &&
payload.isContract && payload.oneTimePayment.HasValue && !payload.oneTimePayment.Value)
{
throw new ArgumentException($"{nameof(payload.paymentDate)} is null but should have value");
}
if (payload.paymentDate != null && payload.oneTimePayment.HasValue && payload.oneTimePayment.Value)
{
throw new ArgumentException($"{nameof(payload.paymentDate)} has value but {nameof(payload.oneTimePayment)} is true");
}
if (payload.providingInformationDate == null && payload.counterparty is SupplyResourceContractTypeOrganization &&
payload.countingResource.HasValue && payload.countingResource.Value == SupplyResourceContractProjectTypeCountingResource.R &&
payload.isContract)
{
throw new ArgumentException($"{nameof(payload.providingInformationDate)} is null but should have value");
}
if (!payload.meteringDeviceInformation.HasValue &&
payload.countingResource.HasValue && payload.countingResource == SupplyResourceContractProjectTypeCountingResource.R)
{
throw new ArgumentException($"{nameof(payload.meteringDeviceInformation)} is null but should have value");
}
if (payload.volumeDepends.HasValue && (payload.counterparty is SupplyResourceContractTypeOrganization ||
payload.oneTimePayment.HasValue && payload.oneTimePayment.Value))
{
throw new ArgumentException($"{nameof(payload.volumeDepends)} has value but should have not one");
}
if (payload.oneTimePayment.HasValue && payload.counterparty is SupplyResourceContractTypeOrganization)
{
throw new ArgumentException($"{nameof(payload.oneTimePayment)} has value but {nameof(payload.counterparty)} has inappropriate value");
}
// TODO: Add accrualProcedure check
}
private SupplyResourceContractProjectType GetContractFromPayload(ImportSupplyResourceContractProjectPayload payload)
{
// http://open-gkh.ru/HouseManagement/SupplyResourceContractProjectType.html
var contract = new SupplyResourceContractProjectType();
if (payload.isContract)
{
var isContract = new SupplyResourceContractProjectTypeIsContract()
{
ContractNumber = payload.contractNumber,
SigningDate = payload.signingDate,
EffectiveDate = payload.effectiveDate
};
contract.Item = isContract;
}
else
{
var isNotContract = new SupplyResourceContractProjectTypeIsNotContract()
{
ContractNumber = payload.contractNumber,
SigningDate = payload.signingDate,
SigningDateSpecified = true,
EffectiveDate = payload.effectiveDate,
EffectiveDateSpecified = true
};
contract.Item = isNotContract;
}
var items = new List<object>();
var itemsElementName = new List<ItemsChoiceType14>();
if (payload.indefiniteTerm)
{
items.Add(payload.indefiniteTerm);
itemsElementName.Add(ItemsChoiceType14.IndefiniteTerm);
}
if (payload.automaticRollOverOneYear)
{
items.Add(payload.automaticRollOverOneYear);
itemsElementName.Add(ItemsChoiceType14.AutomaticRollOverOneYear);
items.Add(payload.comptetionDate.Value);
itemsElementName.Add(ItemsChoiceType14.ComptetionDate);
}
if (items.Count > 0 && itemsElementName.Count > 0)
{
contract.Items = [.. items];
contract.ItemsElementName = [.. itemsElementName];
}
if (payload.period != null)
{
contract.Period = payload.period;
}
if (payload.indicationsAnyDay)
{
contract.IndicationsAnyDay = true;
contract.IndicationsAnyDaySpecified = true;
}
if (payload.contractBase != null && payload.contractBase.Length > 0)
{
contract.ContractBase = payload.contractBase;
}
contract.Item1 = payload.counterparty;
contract.IsPlannedVolume = payload.isPlannedVolume;
if (payload.plannedVolumeType.HasValue)
{
contract.PlannedVolumeType = payload.plannedVolumeType.Value;
contract.PlannedVolumeTypeSpecified = true;
}
contract.ContractSubject = payload.contractSubject;
if (payload.countingResource.HasValue)
{
contract.CountingResource = payload.countingResource.Value;
contract.CountingResourceSpecified = true;
}
contract.SpecifyingQualityIndicators = payload.specifyingQualityIndicators;
if (payload.noConnectionToWaterSupply)
{
contract.NoConnectionToWaterSupply = true;
contract.NoConnectionToWaterSupplySpecified = true;
}
contract.Quality = payload.quality;
contract.OtherQualityIndicator = payload.otherQualityIndicator;
contract.TemperatureChart = payload.temperatureChart;
contract.BillingDate = payload.billingDate;
contract.PaymentDate = payload.paymentDate;
contract.ProvidingInformationDate = payload.providingInformationDate;
if (payload.meteringDeviceInformation.HasValue)
{
contract.MeteringDeviceInformation = payload.meteringDeviceInformation.Value;
contract.MeteringDeviceInformationSpecified = true;
}
if (payload.volumeDepends.HasValue)
{
contract.VolumeDepends = payload.volumeDepends.Value;
contract.VolumeDependsSpecified = true;
}
if (payload.oneTimePayment.HasValue)
{
contract.OneTimePayment = payload.oneTimePayment.Value;
contract.OneTimePaymentSpecified = true;
}
if (payload.accrualProcedure.HasValue)
{
contract.AccrualProcedure = payload.accrualProcedure.Value;
contract.AccrualProcedureSpecified = true;
}
if (!string.IsNullOrEmpty(payload.regionCodeFias))
{
contract.RegionalSettings = new SupplyResourceContractProjectTypeRegionalSettings()
{
Region = new RegionType()
{
code = payload.regionCodeFias
},
Tariff = payload.tariff,
Norm = payload.norm
};
}
return contract;
}
}
}

View File

@ -0,0 +1,27 @@
using Hcs.Broker.Internal;
using Hcs.Service.Async.Nsi;
namespace Hcs.Broker.Api.Request.Nsi
{
internal class ExportDataProviderNsiItemRequest(Client client) : NsiRequestBase(client)
{
internal async Task<IEnumerable<NsiItemType>> ExecuteAsync(exportDataProviderNsiItemRequestRegistryNumber registryNumber, CancellationToken token)
{
// http://open-gkh.ru/Nsi/exportDataProviderNsiItemRequest.html
var request = new exportDataProviderNsiItemRequest
{
Id = Constants.SIGNED_XML_ELEMENT_ID,
version = "10.0.1.2",
RegistryNumber = registryNumber
};
var result = await SendAndWaitResultAsync(request, async asyncClient =>
{
var response = await asyncClient.exportDataProviderNsiItemAsync(CreateRequestHeader(), request);
return response.AckRequest.Ack;
}, token);
return result.Items.OfType<NsiItemType>();
}
}
}

View File

@ -0,0 +1,53 @@
using Hcs.Broker.Api.Request.Adapter;
using Hcs.Service.Async.Nsi;
namespace Hcs.Service.Async.Nsi
{
#pragma warning disable IDE1006
public partial class getStateResult : IGetStateResultMany { }
#pragma warning restore IDE1006
public partial class NsiPortsTypeAsyncClient : 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.Broker.Api.Request.Nsi
{
internal class NsiRequestBase(Client client) :
RequestBase<getStateResult,
NsiPortsTypeAsyncClient,
NsiPortsTypeAsync,
RequestHeader,
AckRequestAck,
ErrorMessageType,
getStateRequest>(client)
{
protected override EndPoint EndPoint => EndPoint.NsiAsync;
protected override bool EnableMinimalResponseWaitDelay => true;
protected override bool CanBeRestarted => true;
protected override int RestartTimeoutMinutes => 20;
}
}

View File

@ -0,0 +1,28 @@
using Hcs.Broker.Internal;
using Hcs.Service.Async.NsiCommon;
namespace Hcs.Broker.Api.Request.NsiCommon
{
internal class ExportNsiItemRequest(Client client) : NsiCommonRequestBase(client)
{
internal async Task<NsiItemType> ExecuteAsync(int registryNumber, ListGroup listGroup, CancellationToken token)
{
// http://open-gkh.ru/NsiCommon/exportNsiItemRequest.html
var request = new exportNsiItemRequest
{
Id = Constants.SIGNED_XML_ELEMENT_ID,
version = "10.0.1.2",
RegistryNumber = registryNumber.ToString(),
ListGroup = listGroup
};
var result = await SendAndWaitResultAsync(request, async asyncClient =>
{
var response = await asyncClient.exportNsiItemAsync(CreateRequestHeader(), request);
return response.AckRequest.Ack;
}, token);
return result.Item as NsiItemType;
}
}
}

View File

@ -0,0 +1,27 @@
using Hcs.Broker.Internal;
using Hcs.Service.Async.NsiCommon;
namespace Hcs.Broker.Api.Request.NsiCommon
{
internal class ExportNsiListRequest(Client client) : NsiCommonRequestBase(client)
{
internal async Task<NsiListType> ExecuteAsync(ListGroup listGroup, CancellationToken token)
{
// http://open-gkh.ru/NsiCommon/exportNsiListRequest.html
var request = new exportNsiListRequest
{
Id = Constants.SIGNED_XML_ELEMENT_ID,
version = "10.0.1.2",
ListGroup = listGroup
};
var result = await SendAndWaitResultAsync(request, async asyncClient =>
{
var response = await asyncClient.exportNsiListAsync(CreateRequestHeader(), request);
return response.AckRequest.Ack;
}, token);
return result.Item as NsiListType;
}
}
}

View File

@ -0,0 +1,53 @@
using Hcs.Broker.Api.Request.Adapter;
using Hcs.Service.Async.NsiCommon;
namespace Hcs.Service.Async.NsiCommon
{
#pragma warning disable IDE1006
public partial class getStateResult : IGetStateResultOne { }
#pragma warning restore IDE1006
public partial class NsiPortsTypeAsyncClient : IAsyncClient<ISRequestHeader>
{
public async Task<IGetStateResponse> GetStateAsync(ISRequestHeader 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.Broker.Api.Request.NsiCommon
{
internal class NsiCommonRequestBase(Client client) :
RequestBase<getStateResult,
NsiPortsTypeAsyncClient,
NsiPortsTypeAsync,
ISRequestHeader,
AckRequestAck,
ErrorMessageType,
getStateRequest>(client)
{
protected override EndPoint EndPoint => EndPoint.NsiCommonAsync;
protected override bool EnableMinimalResponseWaitDelay => true;
protected override bool CanBeRestarted => true;
protected override int RestartTimeoutMinutes => 20;
}
}

View File

@ -0,0 +1,32 @@
using Hcs.Broker.Internal;
using Hcs.Service.Async.OrgRegistryCommon;
namespace Hcs.Broker.Api.Request.OrgRegistryCommon
{
internal class ExportDataProviderRequest(Client client) : OrgRegistryCommonRequestBase(client)
{
internal async Task<IEnumerable<exportDataProviderResultType>> ExecuteAsync(bool isActual, CancellationToken token)
{
// http://open-gkh.ru/OrganizationsRegistryCommon/exportDataProviderRequest.html
var request = new exportDataProviderRequest
{
Id = Constants.SIGNED_XML_ELEMENT_ID,
version = "10.0.2.1"
};
if (isActual)
{
request.IsActual = true;
request.IsActualSpecified = true;
}
var result = await SendAndWaitResultAsync(request, async asyncClient =>
{
var response = await asyncClient.exportDataProviderAsync(CreateRequestHeader(), request);
return response.AckRequest.Ack;
}, token);
return result.Items.OfType<exportDataProviderResultType>();
}
}
}

View File

@ -0,0 +1,46 @@
using Hcs.Broker.Internal;
using Hcs.Service.Async.OrgRegistryCommon;
namespace Hcs.Broker.Api.Request.OrgRegistryCommon
{
internal class ExportOrgRegistryRequest(Client client) : OrgRegistryCommonRequestBase(client)
{
private const int OGRN_LENGTH = 13;
internal async Task<IEnumerable<exportOrgRegistryResultType>> ExecuteAsync(string ogrn, string kpp, CancellationToken token)
{
if (ogrn.Length != OGRN_LENGTH)
{
throw new System.ArgumentException($"The length of {ogrn} is incorrect");
}
var criteria = new exportOrgRegistryRequestSearchCriteria();
if (!string.IsNullOrEmpty(kpp))
{
criteria.Items = [ogrn, kpp];
criteria.ItemsElementName = [ItemsChoiceType3.OGRN, ItemsChoiceType3.KPP];
}
else
{
criteria.Items = [ogrn];
criteria.ItemsElementName = [ItemsChoiceType3.OGRN];
}
// http://open-gkh.ru/OrganizationsRegistryCommon/exportOrgRegistryRequest.html
var request = new exportOrgRegistryRequest
{
Id = Constants.SIGNED_XML_ELEMENT_ID,
version = "10.0.2.1",
SearchCriteria = [criteria]
};
var result = await SendAndWaitResultAsync(request, async asyncClient =>
{
var response = await asyncClient.exportOrgRegistryAsync(CreateRequestHeader(), request);
return response.AckRequest.Ack;
}, token);
return result.Items.OfType<exportOrgRegistryResultType>();
}
}
}

View File

@ -0,0 +1,53 @@
using Hcs.Broker.Api.Request.Adapter;
using Hcs.Service.Async.OrgRegistryCommon;
namespace Hcs.Service.Async.OrgRegistryCommon
{
#pragma warning disable IDE1006
public partial class getStateResult : IGetStateResultMany { }
#pragma warning restore IDE1006
public partial class RegOrgPortsTypeAsyncClient : IAsyncClient<ISRequestHeader>
{
public async Task<IGetStateResponse> GetStateAsync(ISRequestHeader 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.Broker.Api.Request.OrgRegistryCommon
{
internal class OrgRegistryCommonRequestBase(Client client) :
RequestBase<getStateResult,
RegOrgPortsTypeAsyncClient,
RegOrgPortsTypeAsync,
ISRequestHeader,
AckRequestAck,
ErrorMessageType,
getStateRequest>(client)
{
protected override EndPoint EndPoint => EndPoint.OrgRegistryCommonAsync;
protected override bool EnableMinimalResponseWaitDelay => true;
protected override bool CanBeRestarted => true;
protected override int RestartTimeoutMinutes => 20;
}
}

View File

@ -0,0 +1,52 @@
namespace Hcs.Broker.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

@ -0,0 +1,107 @@
using Hcs.Broker.Api.Payload.Payments;
using Hcs.Broker.Api.Request.Exception;
using Hcs.Broker.Internal;
using Hcs.Service.Async.Payments;
namespace Hcs.Broker.Api.Request.Payments
{
internal class ImportNotificationsOfOrderExecutionRequest(Client client) : PaymentsRequestBase(client)
{
protected override bool CanBeRestarted => false;
internal async Task<bool> ExecuteAsync(ImportNotificationsOfOrderExecutionPayload payload, CancellationToken token)
{
ThrowIfPayloadIncorrect(payload);
// http://open-gkh.ru/Payment/importNotificationsOfOrderExecutionRequest.html
var request = new importNotificationsOfOrderExecutionRequest
{
Id = Constants.SIGNED_XML_ELEMENT_ID,
version = "10.0.1.1",
Items = [GetNotificationFromPayload(payload)]
};
var result = await SendAndWaitResultAsync(request, async asyncClient =>
{
var response = await asyncClient.importNotificationsOfOrderExecutionAsync(CreateRequestHeader(), request);
return response.AckRequest.Ack;
}, token);
result.Items.OfType<ErrorMessageType>().ToList().ForEach(error =>
{
throw RemoteException.CreateNew(error.ErrorCode, error.Description);
});
result.Items.OfType<CommonResultType>().ToList().ForEach(commonResult =>
{
commonResult.Items.OfType<ErrorMessageType>().ToList().ForEach(error =>
{
throw RemoteException.CreateNew(error.ErrorCode, error.Description);
});
});
return true;
}
private void ThrowIfPayloadIncorrect(ImportNotificationsOfOrderExecutionPayload payload)
{
if (string.IsNullOrEmpty(payload.orderId))
{
throw new ArgumentException($"{nameof(payload.orderId)} is empty");
}
if (payload.month.HasValue && !payload.year.HasValue)
{
throw new ArgumentException($"{nameof(payload.month)} has value but {nameof(payload.year)} has not");
}
if (!payload.month.HasValue && payload.year.HasValue)
{
throw new ArgumentException($"{nameof(payload.year)} has value but {nameof(payload.month)} has not");
}
if (string.IsNullOrEmpty(payload.paymentDocumentId))
{
throw new ArgumentException($"{nameof(payload.paymentDocumentId)} is empty");
}
if (string.IsNullOrEmpty(payload.paymentDocumentGUID))
{
throw new ArgumentException($"{nameof(payload.paymentDocumentGUID)} is empty");
}
}
private importNotificationsOfOrderExecutionRequestNotificationOfOrderExecution139Type GetNotificationFromPayload(ImportNotificationsOfOrderExecutionPayload payload)
{
var notification = new importNotificationsOfOrderExecutionRequestNotificationOfOrderExecution139Type()
{
TransportGUID = Guid.NewGuid().ToString(),
OrderInfo = new NotificationOfOrderExecution139TypeOrderInfo()
{
OrderID = payload.orderId,
OrderDate = payload.orderDate,
Amount = payload.amount,
Items = [payload.paymentDocumentId, payload.paymentDocumentGUID],
ItemsElementName = [ItemsChoiceType4.PaymentDocumentID, ItemsChoiceType4.PaymentDocumentGUID]
}
};
if (payload.onlinePayment.HasValue && payload.onlinePayment.Value)
{
notification.OrderInfo.OnlinePayment = true;
notification.OrderInfo.OnlinePaymentSpecified = true;
}
if (payload.month.HasValue)
{
notification.OrderInfo.MonthAndYear = new NotificationOfOrderExecution139TypeOrderInfoMonthAndYear()
{
Year = payload.year.Value,
Month = payload.month.Value
};
}
return notification;
}
}
}

View File

@ -0,0 +1,93 @@
using Hcs.Broker.Api.Payload.Payments;
using Hcs.Broker.Api.Request.Exception;
using Hcs.Broker.Internal;
using Hcs.Service.Async.Payments;
namespace Hcs.Broker.Api.Request.Payments
{
internal class ImportSupplierNotificationsOfOrderExecutionRequest(Client client) : PaymentsRequestBase(client)
{
protected override bool CanBeRestarted => false;
internal async Task<bool> ExecuteAsync(ImportSupplierNotificationsOfOrderExecutionPayload payload, CancellationToken token)
{
ThrowIfPayloadIncorrect(payload);
// http://open-gkh.ru/Payment/importSupplierNotificationsOfOrderExecutionRequest.html
var request = new importSupplierNotificationsOfOrderExecutionRequest
{
Id = Constants.SIGNED_XML_ELEMENT_ID,
version = "10.0.1.1",
SupplierNotificationOfOrderExecution = [GetNotificationFromPayload(payload)]
};
var result = await SendAndWaitResultAsync(request, async asyncClient =>
{
var response = await asyncClient.importSupplierNotificationsOfOrderExecutionAsync(CreateRequestHeader(), request);
return response.AckRequest.Ack;
}, token);
result.Items.OfType<ErrorMessageType>().ToList().ForEach(error =>
{
throw RemoteException.CreateNew(error.ErrorCode, error.Description);
});
result.Items.OfType<CommonResultType>().ToList().ForEach(commonResult =>
{
commonResult.Items.OfType<ErrorMessageType>().ToList().ForEach(error =>
{
throw RemoteException.CreateNew(error.ErrorCode, error.Description);
});
});
return true;
}
private void ThrowIfPayloadIncorrect(ImportSupplierNotificationsOfOrderExecutionPayload payload)
{
if (payload.month.HasValue && !payload.year.HasValue)
{
throw new ArgumentException($"{nameof(payload.month)} has value but {nameof(payload.year)} has not");
}
if (!payload.month.HasValue && payload.year.HasValue)
{
throw new ArgumentException($"{nameof(payload.year)} has value but {nameof(payload.month)} has not");
}
if (string.IsNullOrEmpty(payload.paymentDocumentId))
{
throw new ArgumentException($"{nameof(payload.paymentDocumentId)} is empty");
}
}
private importSupplierNotificationsOfOrderExecutionRequestSupplierNotificationOfOrderExecution GetNotificationFromPayload(ImportSupplierNotificationsOfOrderExecutionPayload payload)
{
var notification = new importSupplierNotificationsOfOrderExecutionRequestSupplierNotificationOfOrderExecution()
{
TransportGUID = Guid.NewGuid().ToString(),
OrderDate = payload.orderDate,
Item = payload.paymentDocumentId,
ItemElementName = ItemChoiceType1.PaymentDocumentID,
Amount = payload.amount
};
if (payload.month.HasValue)
{
notification.OrderPeriod = new SupplierNotificationOfOrderExecutionTypeOrderPeriod()
{
Month = payload.month.Value,
Year = payload.year.Value
};
}
if (payload.onlinePayment.HasValue && payload.onlinePayment.Value)
{
notification.OnlinePayment = true;
notification.OnlinePaymentSpecified = true;
}
return notification;
}
}
}

View File

@ -0,0 +1,55 @@
using Hcs.Broker.Api.Request;
using Hcs.Broker.Api.Request.Adapter;
using Hcs.Service.Async.Payments;
using System.Threading.Tasks;
namespace Hcs.Service.Async.Payments
{
#pragma warning disable IDE1006
public partial class getStateResult : IGetStateResultMany { }
#pragma warning restore IDE1006
public partial class PaymentPortsTypeAsyncClient : 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.Broker.Api.Request.Payments
{
internal class PaymentsRequestBase(Client client) :
RequestBase<getStateResult,
PaymentPortsTypeAsyncClient,
PaymentPortsTypeAsync,
RequestHeader,
AckRequestAck,
ErrorMessageType,
getStateRequest>(client)
{
protected override EndPoint EndPoint => EndPoint.PaymentsAsync;
protected override bool EnableMinimalResponseWaitDelay => true;
protected override bool CanBeRestarted => true;
protected override int RestartTimeoutMinutes => 20;
}
}

View File

@ -0,0 +1,419 @@
using Hcs.Broker.Api.Request.Adapter;
using Hcs.Broker.Api.Request.Exception;
using Hcs.Broker.Internal;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.Text;
namespace Hcs.Broker.Api.Request
{
internal abstract class RequestBase<TResult, TAsyncClient, TChannel, TRequestHeader, TAck, TErrorMessage, TGetStateRequest>
where TResult : IGetStateResult
where TAsyncClient : ClientBase<TChannel>, TChannel, IAsyncClient<TRequestHeader>
where TChannel : class
where TRequestHeader : class
where TAck : IAck
where TErrorMessage : IErrorMessage
where TGetStateRequest : IGetStateRequest, new()
{
private const int RESPONSE_WAIT_DELAY_MIN = 2;
private const int RESPONSE_WAIT_DELAY_MAX = 5;
// "[EXP001000] Произошла ошибка при передаче данных. Попробуйте осуществить передачу данных повторно".
// Видимо, эту ошибку нельзя включать здесь. Предположительно это маркер DDOS защиты и если отправлять
// точно такой же пакет повторно, то ошибка входит в бесконечный цикл - необходимо заново
// собирать пакет с новыми кодами и временем и новой подписью. Такую ошибку надо обнаруживать
// на более высоком уровне и заново отправлять запрос новым пакетом.
private static readonly string[] ignorableSystemErrorMarkers = [
"Истекло время ожидания шлюза",
"Базовое соединение закрыто: Соединение, которое должно было работать, было разорвано сервером",
"Попробуйте осуществить передачу данных повторно",
"(502) Недопустимый шлюз",
"(503) Сервер не доступен"
];
protected Client client;
protected CustomBinding binding;
protected abstract EndPoint EndPoint { get; }
/// <summary>
/// Для запросов, возвращающих мало данных, можно попробовать сократить
/// начальный период ожидания подготовки ответа
/// </summary>
protected abstract bool EnableMinimalResponseWaitDelay { get; }
/// <summary>
/// Указывает на то, что можно ли этот метод перезапускать в случае зависания
/// ожидания или в случае сбоя на сервере
/// </summary>
protected abstract bool CanBeRestarted { get; }
/// <summary>
/// Для противодействия зависанию ожидания вводится предел ожидания в минутах
/// для запросов, которые можно перезапустить заново с теми же параметрами
/// </summary>
protected abstract int RestartTimeoutMinutes { get; }
private EndpointAddress RemoteAddress => new(client.ComposeEndpointUri(EndPointLocator.GetPath(EndPoint)));
private string ThreadIdText => $"(Thread #{ThreadId})";
/// <summary>
/// Возвращает идентификатор текущего исполняемого потока
/// </summary>
private int ThreadId => Environment.CurrentManagedThreadId;
public RequestBase(Client client)
{
this.client = client;
ConfigureBinding();
}
private void ConfigureBinding()
{
binding = new CustomBinding
{
CloseTimeout = TimeSpan.FromSeconds(180),
OpenTimeout = TimeSpan.FromSeconds(180),
ReceiveTimeout = TimeSpan.FromSeconds(180),
SendTimeout = TimeSpan.FromSeconds(180)
};
binding.Elements.Add(new TextMessageEncodingBindingElement
{
MessageVersion = MessageVersion.Soap11,
WriteEncoding = Encoding.UTF8
});
if (client.UseTunnel)
{
if (!System.Diagnostics.Process.GetProcessesByName("stunnel").Any())
{
throw new System.Exception("stunnel is not running");
}
binding.Elements.Add(new HttpTransportBindingElement
{
AuthenticationScheme = (client.IsPPAK ? System.Net.AuthenticationSchemes.Digest : System.Net.AuthenticationSchemes.Basic),
MaxReceivedMessageSize = int.MaxValue,
UseDefaultWebProxy = false
});
}
else
{
binding.Elements.Add(new HttpsTransportBindingElement
{
AuthenticationScheme = (client.IsPPAK ? System.Net.AuthenticationSchemes.Digest : System.Net.AuthenticationSchemes.Basic),
MaxReceivedMessageSize = int.MaxValue,
RequireClientCertificate = true,
UseDefaultWebProxy = false
});
}
}
protected async Task<TResult> SendAndWaitResultAsync(
object request,
Func<TAsyncClient, Task<TAck>> sender,
CancellationToken token)
{
token.ThrowIfCancellationRequested();
while (true)
{
try
{
if (CanBeRestarted)
{
return await RunRepeatableTaskInsistentlyAsync(
async () => await ExecuteSendAndWaitResultAsync(request, sender, token), token);
}
else
{
return await ExecuteSendAndWaitResultAsync(request, sender, token);
}
}
catch (RestartTimeoutException e)
{
if (!CanBeRestarted)
{
throw new System.Exception("Cannot restart request execution on timeout", e);
}
client.TryLog($"Restarting {request.GetType().Name} request execution...");
}
}
}
/// <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);
ConfigureEndpointCredentials(asyncClient.Endpoint, asyncClient.ClientCredentials);
return asyncClient;
}
private void ConfigureEndpointCredentials(
ServiceEndpoint serviceEndpoint, ClientCredentials clientCredentials)
{
serviceEndpoint.EndpointBehaviors.Add(new GostSigningEndpointBehavior(client));
if (!client.IsPPAK)
{
clientCredentials.UserName.UserName = Constants.NAME_SIT;
clientCredentials.UserName.Password = Constants.PASSWORD_SIT;
}
System.Net.ServicePointManager.ServerCertificateValidationCallback = delegate (
object sender, X509Certificate serverCertificate, X509Chain chain, System.Net.Security.SslPolicyErrors sslPolicyErrors)
{
return true;
};
if (!client.UseTunnel)
{
clientCredentials.ClientCertificate.SetCertificate(
StoreLocation.CurrentUser,
StoreName.My,
X509FindType.FindByThumbprint,
client.Certificate.Thumbprint);
}
}
/// <summary>
/// Основной алгоритм ожидания ответа на асинхронный запрос.
/// Из документации ГИС ЖКХ:
/// Также рекомендуем придерживаться следующего алгоритма отправки запросов на получение статуса обработки пакета в случае использования асинхронных сервисов ГИС ЖКХ (в рамках одного MessageGUID):
/// - первый запрос getState направлять не ранее чем через 10 секунд, после получения квитанции о приеме пакета с бизнес-данными от сервиса ГИС КЖХ;
/// - в случае, если на первый запрос getSate получен результат с RequestState равным "1" или "2", то следующий запрос getState необходимо направлять не ранее чем через 60 секунд после отправки предыдущего запроса;
/// - в случае, если на второй запрос getSate получен результат с RequestState равным "1" или "2", то следующий запрос getState необходимо направлять не ранее чем через 300 секунд после отправки предыдущего запроса;
/// - в случае, если на третий запрос getSate получен результат с RequestState равным "1" или "2", то следующий запрос getState необходимо направлять не ранее чем через 900 секунд после отправки предыдущего запроса;
/// - в случае, если на четвертый(и все последующие запросы) getState получен результат с RequestState равным "1" или "2", то следующий запрос getState необходимо направлять не ранее чем через 1800 секунд после отправки предыдущего запроса.
/// </summary>
private async Task<TResult> WaitForResultAsync(
TAck ack, bool withInitialDelay, CancellationToken token)
{
TResult result;
var startTime = DateTime.Now;
for (var attempts = 1; ; attempts++)
{
token.ThrowIfCancellationRequested();
var delaySec = EnableMinimalResponseWaitDelay ? RESPONSE_WAIT_DELAY_MIN : RESPONSE_WAIT_DELAY_MAX;
if (attempts >= 2)
{
delaySec = RESPONSE_WAIT_DELAY_MAX;
}
if (attempts >= 3)
{
delaySec = RESPONSE_WAIT_DELAY_MAX * 2;
}
if (attempts >= 5)
{
delaySec = RESPONSE_WAIT_DELAY_MAX * 4;
}
if (attempts >= 7)
{
delaySec = RESPONSE_WAIT_DELAY_MAX * 8;
}
if (attempts >= 9)
{
delaySec = RESPONSE_WAIT_DELAY_MAX * 16;
}
if (attempts >= 12)
{
delaySec = RESPONSE_WAIT_DELAY_MAX * 60;
}
if (attempts > 1 || withInitialDelay)
{
var minutesElapsed = (int)(DateTime.Now - startTime).TotalMinutes;
if (CanBeRestarted && minutesElapsed > RestartTimeoutMinutes)
{
throw new RestartTimeoutException($"{RestartTimeoutMinutes} minute(s) wait time exceeded");
}
client.TryLog($"Waiting {delaySec} sec for attempt #{attempts}" +
$" to get response ({minutesElapsed} minute(s) elapsed)...");
await Task.Delay(delaySec * 1000, token);
}
client.TryLog($"Requesting response, attempt #{attempts} in {ThreadIdText}...");
result = await TryGetResultAsync(ack);
if (result != null)
{
break;
}
}
client.TryLog($"Response received!");
return result;
}
/// <summary>
/// Выполняет однократную проверку наличия результата.
/// Возвращает default если результата еще нет.
/// </summary>
private async Task<TResult> TryGetResultAsync(TAck ack)
{
using var asyncClient = CreateAsyncClient();
var requestHeader = RequestHelper.CreateHeader<TRequestHeader>(client);
var requestBody = new TGetStateRequest
{
MessageGUID = ack.MessageGUID
};
var response = await asyncClient.GetStateAsync(requestHeader, requestBody);
var result = response.GetStateResult;
if (result.RequestState == (int)AsyncRequestStateType.Ready)
{
return (TResult)result;
}
return default;
}
protected TRequestHeader CreateRequestHeader()
{
return RequestHelper.CreateHeader<TRequestHeader>(client);
}
/// <summary>
/// Исполнение повторяемой операции некоторое допустимое число ошибок
/// </summary>
protected async Task<TRepeatableResult> RunRepeatableTaskAsync<TRepeatableResult>(
Func<Task<TRepeatableResult>> taskFunc, Func<System.Exception, bool> canIgnoreFunc, int maxAttempts)
{
for (var attempts = 1; ; attempts++)
{
try
{
return await taskFunc();
}
catch (System.Exception e)
{
if (canIgnoreFunc(e))
{
if (attempts < maxAttempts)
{
client.TryLog($"Ignoring error of attempt #{attempts} of {maxAttempts} attempts");
continue;
}
throw new System.Exception("Too much attempts with error");
}
throw e;
}
}
}
}
}

View File

@ -0,0 +1,98 @@
namespace Hcs.Broker.Api.Request
{
internal static class RequestHelper
{
/// <summary>
/// Подготовка заголовка сообщения отправляемого в ГИС ЖКХ с обязательными атрибутами.
/// Заголовки могут быть разного типа для разных типов сообщений, но имена полей одинаковые.
/// </summary>
internal static THeader CreateHeader<THeader>(Client client) where THeader : class
{
try
{
var instance = Activator.CreateInstance(typeof(THeader));
foreach (var prop in instance.GetType().GetProperties())
{
switch (prop.Name)
{
case "Item":
prop.SetValue(instance, client.OrgPPAGUID);
break;
case "ItemElementName":
prop.SetValue(instance, Enum.Parse(prop.PropertyType, "orgPPAGUID"));
break;
case "MessageGUID":
prop.SetValue(instance, Guid.NewGuid().ToString());
break;
case "Date":
prop.SetValue(instance, DateTime.Now);
break;
case "IsOperatorSignatureSpecified":
if (client.Role == OrganizationRole.RC || client.Role == OrganizationRole.RSO)
{
prop.SetValue(instance, true);
}
break;
case "IsOperatorSignature":
if (client.Role == OrganizationRole.RC || client.Role == OrganizationRole.RSO)
{
prop.SetValue(instance, true);
}
break;
}
}
return instance as THeader;
}
catch (ArgumentNullException e)
{
throw new ApplicationException($"Error occured while building request header: {e.Message}");
}
catch (SystemException e)
{
throw new ApplicationException($"Error occured while building request header: {e.GetBaseException().Message}");
}
}
/// <summary>
/// Для объекта запроса возвращает значение строки свойства version
/// </summary>
internal static string GetRequestVersionString(object requestObject)
{
if (requestObject == null)
{
return null;
}
var versionHost = requestObject;
if (versionHost != null)
{
var versionProperty = versionHost.GetType().GetProperties().FirstOrDefault(x => x.Name == "version");
if (versionProperty != null)
{
return versionProperty.GetValue(versionHost) as string;
}
}
foreach (var field in requestObject.GetType().GetFields())
{
versionHost = field.GetValue(requestObject);
if (versionHost != null)
{
var versionProperty = versionHost.GetType().GetProperties().FirstOrDefault(x => x.Name == "version");
if (versionProperty != null)
{
return versionProperty.GetValue(versionHost) as string;
}
}
}
return null;
}
}
}