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,7 @@
namespace Hcs.Broker.Api
{
public abstract class ApiBase(Client client)
{
protected Client client = client;
}
}

View File

@ -0,0 +1,65 @@
using Hcs.Broker.Api.Payload.Bills;
using Hcs.Broker.Api.Request.Bills;
using Hcs.Service.Async.Bills;
namespace Hcs.Broker.Api
{
// http://open-gkh.ru/BillsServiceAsync/
public class BillsApi(Client client) : ApiBase(client)
{
/// <summary>
/// Экспорт платежных документов
/// </summary>
/// <param name="paymentDocumentID">Идентификатор платежного документа</param>
/// <param name="token">Токен отмены</param>
/// <returns>Платежные документы</returns>
public async Task<IEnumerable<exportPaymentDocumentResultType>> ExportPaymentDocumentDataByPaymentDocumentIDAsync(string paymentDocumentID, CancellationToken token = default)
{
var request = new ExportPaymentDocumentDataRequest(client);
return await request.ExecuteByPaymentDocumentIDAsync(paymentDocumentID, token);
}
/// <summary>
/// Экспорт платежных документов
/// </summary>
/// <param name="year">Год</param>
/// <param name="month">Месяц</param>
/// <param name="fiasHouseGuid">Глобальный уникальный идентификатор дома по ФИАС</param>
/// <param name="accountNumber">Номер лицевого счета/иной идентификатор плательщика</param>
/// <param name="token">Токен отмены</param>
/// <returns>Платежные документы</returns>
public async Task<IEnumerable<exportPaymentDocumentResultType>> ExportPaymentDocumentDataByAccountNumberAsync(short year, int month, string fiasHouseGuid, string accountNumber, CancellationToken token = default)
{
var request = new ExportPaymentDocumentDataRequest(client);
return await request.ExecuteByAccountNumberAsync(year, month, fiasHouseGuid, accountNumber, token);
}
/// <summary>
/// Экспорт платежных документов
/// </summary>
/// <param name="year">Год</param>
/// <param name="month">Месяц</param>
/// <param name="fiasHouseGuid">Глобальный уникальный идентификатор дома по ФИАС</param>
/// <param name="paymentDocumentNumber">Номер платежного документа, по которому внесена плата,
/// присвоенный такому документу исполнителем в целях осуществления расчетов по внесению платы</param>
/// <param name="token">Токен отмены</param>
/// <returns>Платежные документы</returns>
public async Task<IEnumerable<exportPaymentDocumentResultType>> ExportPaymentDocumentDataByPaymentDocumentNumberAsync(short year, int month, string fiasHouseGuid, string paymentDocumentNumber, CancellationToken token = default)
{
var request = new ExportPaymentDocumentDataRequest(client);
return await request.ExecuteByPaymentDocumentNumberAsync(year, month, fiasHouseGuid, paymentDocumentNumber, token);
}
/// <summary>
/// Импорт сведений о платежных документах
/// </summary>
/// <param name="payload">Пейлоад сведений о платежных документах</param>
/// <param name="token">Токен отмены</param>
/// <returns>true, если операция выполнена успешно, иначе - false</returns>
public async Task<bool> ImportPaymentDocumentDataAsync(ImportPaymentDocumentDataPayload payload, CancellationToken token = default)
{
var request = new ImportPaymentDocumentDataRequest(client);
return await request.ExecuteAsync(payload, token);
}
}
}

View File

@ -0,0 +1,34 @@
using Hcs.Broker.Api.Payload.DeviceMetering;
using Hcs.Broker.Api.Request.DeviceMetering;
using Hcs.Service.Async.DeviceMetering;
namespace Hcs.Broker.Api
{
// http://open-gkh.ru/DeviceMeteringServiceAsync/
public class DeviceMeteringApi(Client client) : ApiBase(client)
{
/// <summary>
/// Экспорт истории показаний и поверок приборов учета пользователя, установленных в указанном доме
/// </summary>
/// <param name="payload">Пейлоад выборки ПУ</param>
/// <param name="token">Токен отмены</param>
/// <returns>Лицевые счета</returns>
public async Task<IEnumerable<exportMeteringDeviceHistoryResultType>> ExportMeteringDeviceHistoryAsync(ExportMeteringDeviceHistoryPayload payload, CancellationToken token = default)
{
var request = new ExportMeteringDeviceHistoryRequest(client);
return await request.ExecuteAsync(payload, token);
}
/// <summary>
/// Импорт показаний приборов учета
/// </summary>
/// <param name="values">Показания прибора учета</param>
/// <param name="token">Токен отмены</param>
/// <returns>true, если операция выполнена успешно, иначе - false</returns>
public async Task<bool> ImportMeteringDeviceValuesAsync(importMeteringDeviceValuesRequestMeteringDevicesValues values, CancellationToken token = default)
{
var request = new ImportMeteringDeviceValuesRequest(client);
return await request.ExecuteAsync(values, token);
}
}
}

View File

@ -0,0 +1,165 @@
using Hcs.Broker.Api.Payload.HouseManagement;
using Hcs.Broker.Api.Request.HouseManagement;
using Hcs.Service.Async.HouseManagement;
namespace Hcs.Broker.Api
{
// http://open-gkh.ru/HouseManagementServiceAsync/
public class HouseManagementApi(Client client) : ApiBase(client)
{
/// <summary>
/// Экспорт лицевых счетов
/// </summary>
/// <param name="fiasHouseGuid">Глобальный уникальный идентификатор дома по ФИАС</param>
/// <param name="token">Токен отмены</param>
/// <returns>Лицевые счета</returns>
public async Task<IEnumerable<exportAccountResultType>> ExportAccountAsync(string fiasHouseGuid, CancellationToken token = default)
{
var request = new ExportAccountRequest(client);
return await request.ExecuteAsync(fiasHouseGuid, token);
}
/// <summary>
/// Возвращает информацию о доме
/// </summary>
/// <param name="fiasHouseGuid">Глобальный уникальный идентификатор дома по ФИАС</param>
/// <param name="token">Токен отмены</param>
/// <returns>Информация о доме</returns>
public async Task<IEnumerable<exportHouseResultType>> ExportHouseAsync(string fiasHouseGuid, CancellationToken token = default)
{
var request = new ExportHouseRequest(client);
return await request.ExecuteAsync(fiasHouseGuid, token);
}
/// <summary>
/// Возвращает все договора ресурсоснабжения
/// </summary>
/// <param name="token">Токен отмены</param>
/// <returns>Договора ресурсоснабжения</returns>
public async Task<IEnumerable<exportSupplyResourceContractResultType>> ExportSupplyResourceContractDataAsync(CancellationToken token = default)
{
var request = new ExportSupplyResourceContractDataRequest(client);
return await request.ExecuteAsync(token);
}
/// <summary>
/// Возвращает договор ресурсоснабжения по его идентификатору в ГИС ЖКХ
/// </summary>
/// <param name="contractRootGuid">Идентификатор договора ресурсоснабжения в ГИС ЖКХ</param>
/// <param name="token">Токен отмены</param>
/// <returns>Договор ресурсоснабжения</returns>
public async Task<exportSupplyResourceContractResultType> ExportSupplyResourceContractDataAsync(Guid contractRootGuid, CancellationToken token = default)
{
var request = new ExportSupplyResourceContractDataRequest(client);
return await request.ExecuteAsync(contractRootGuid, token);
}
/// <summary>
/// Возвращает договор ресурсоснабжения по номеру договора в ГИС ЖКХ
/// </summary>
/// <param name="contractNumber">Номер договора ресурсоснабжения в ГИС ЖКХ</param>
/// <param name="token">Токен отмены</param>
/// <returns>Договор ресурсоснабжения</returns>
public async Task<exportSupplyResourceContractResultType> ExportSupplyResourceContractDataAsync(string contractNumber, CancellationToken token = default)
{
var request = new ExportSupplyResourceContractDataRequest(client);
return await request.ExecuteAsync(contractNumber, token);
}
/// <summary>
/// Возвращает объекты жилищного фонда из договора ресурсоснабжения по его идентификатору
/// </summary>
/// <param name="contractRootGuid">Идентификатор договора ресурсоснабжения в ГИС ЖКХ</param>
/// <param name="token">Токен отмены</param>
/// <returns>Объекты жилищного фонда</returns>
public async Task<IEnumerable<exportSupplyResourceContractObjectAddressResultType>> ExportSupplyResourceContractObjectAddressDataAsync(Guid contractRootGuid, CancellationToken token = default)
{
var request = new ExportSupplyResourceContractObjectAddressDataRequest(client);
return await request.ExecuteAsync(contractRootGuid, token);
}
/// <summary>
/// Импорт лицевого счета
/// </summary>
/// <param name="payload">Пейлоад лицевого счета</param>
/// <param name="token">Токен отмены</param>
/// <returns>true, если операция выполнена успешно, иначе - false</returns>
public async Task<bool> ImportAccountDataAsync(ImportAccountDataPayload payload, CancellationToken token = default)
{
var request = new ImportAccountDataRequest(client);
return await request.ExecuteAsync(payload, token);
}
/// <summary>
/// Импорт сведений о ДУ (создание ДУ)
/// </summary>
/// <param name="payload">Пейлоад ДУ</param>
/// <param name="token">Токен отмены</param>
/// <returns>Импортированный договор</returns>
public async Task<importContractResultType> ImportContractDataAsync(ImportContractDataPayload payload, CancellationToken token = default)
{
var request = new ImportContractDataRequest(client);
return await request.ExecuteAsync(payload, token);
}
/// <summary>
/// Импорт данных дома
/// </summary>
/// <param name="payload">Пейлоад данных дома</param>
/// <param name="token">Токен отмены</param>
/// <returns>true, если операция выполнена успешно, иначе - false</returns>
public async Task<bool> ImportHouseUODataAsync(ImportLivingHouseUODataPayload payload, CancellationToken token = default)
{
var request = new ImportHouseUODataRequest(client);
return await request.ExecuteAsync(payload, token);
}
/// <summary>
/// Импорт прибора учета
/// </summary>
/// <param name="meteringDevice">Прибор учета</param>
/// <param name="token">Токен отмены</param>
/// <returns>true, если операция выполнена успешно, иначе - false</returns>
public async Task<bool> ImportMeteringDeviceDataAsync(MeteringDeviceFullInformationType meteringDevice, CancellationToken token = default)
{
var request = new ImportMeteringDeviceDataRequest(client);
return await request.ExecuteAsync(meteringDevice, token);
}
/// <summary>
/// Импорт новости для информирования граждан
/// </summary>
/// <param name="payload">Пейлоад новости</param>
/// <param name="token">Токен отмены</param>
/// <returns>true, если операция выполнена успешно, иначе - false</returns>
public async Task<bool> ImportNotificationDataAsync(ImportNotificationDataPayload payload, CancellationToken token = default)
{
var request = new ImportNotificationDataRequest(client);
return await request.ExecuteAsync(payload, token);
}
/// <summary>
/// Импорт договора ресурсоснабжения с РСО
/// </summary>
/// <param name="payload">Пейлоад договора ресурсоснабжения</param>
/// <param name="token">Токен отмены</param>
/// <returns>Импортированный договор</returns>
public async Task<getStateResultImportResultCommonResultImportSupplyResourceContract> ImportSupplyResourceContractDataAsync(ImportSupplyResourceContractDataPayload payload, CancellationToken token = default)
{
var request = new ImportSupplyResourceContractDataRequest(client);
return await request.ExecuteAsync(payload, token);
}
/// <summary>
/// Импорт проекта договора ресурсоснабжения с РСО
/// </summary>
/// <param name="payload">Пейлоад проекта договора ресурсоснабжения</param>
/// <param name="token">Токен отмены</param>
/// <returns>Импортированный проект договора</returns>
public async Task<getStateResultImportResultCommonResultImportSupplyResourceContractProject> ImportSupplyResourceContractProjectAsync(ImportSupplyResourceContractProjectPayload payload, CancellationToken token = default)
{
var request = new ImportSupplyResourceContractProjectRequest(client);
return await request.ExecuteAsync(payload, token);
}
}
}

29
Hcs.Broker/Api/NsiApi.cs Normal file
View File

@ -0,0 +1,29 @@
using Hcs.Broker.Api.Request.Exception;
using Hcs.Broker.Api.Request.Nsi;
using Hcs.Service.Async.Nsi;
namespace Hcs.Broker.Api
{
// http://open-gkh.ru/NsiService/
public class NsiApi(Client client) : ApiBase(client)
{
/// <summary>
/// Возвращает данные справочника поставщика информации
/// </summary>
/// <param name="registryNumber">Реестровый номер справочника</param>
/// <param name="token">Токен отмены</param>
/// <returns>Данные справочника</returns>
public async Task<IEnumerable<NsiItemType>> ExportDataProviderNsiItemAsync(exportDataProviderNsiItemRequestRegistryNumber registryNumber, CancellationToken token = default)
{
try
{
var request = new ExportDataProviderNsiItemRequest(client);
return await request.ExecuteAsync(registryNumber, token);
}
catch (NoResultsRemoteException)
{
return [];
}
}
}
}

View File

@ -0,0 +1,49 @@
using Hcs.Broker.Api.Request.Exception;
using Hcs.Broker.Api.Request.NsiCommon;
using Hcs.Service.Async.NsiCommon;
namespace Hcs.Broker.Api
{
// http://open-gkh.ru/NsiCommonService/
public class NsiCommonApi(Client client) : ApiBase(client)
{
/// <summary>
/// Возвращает данные общесистемного справочника
/// </summary>
/// <param name="registryNumber">Реестровый номер справочника</param>
/// <param name="listGroup">Группа справочников, где NSI - общесистемный, а NSIRAO - ОЖФ</param>
/// <param name="token">Токен отмены</param>
/// <returns>Данные общесистемного справочника</returns>
public async Task<NsiItemType> ExportNsiItemAsync(int registryNumber, ListGroup listGroup, CancellationToken token = default)
{
try
{
var request = new ExportNsiItemRequest(client);
return await request.ExecuteAsync(registryNumber, listGroup, token);
}
catch (NoResultsRemoteException)
{
return null;
}
}
/// <summary>
/// Возвращает перечень общесистемных справочников с указанием даты последнего изменения каждого из них
/// </summary>
/// <param name="listGroup">Группа справочников, где NSI - общесистемный, а NSIRAO - ОЖФ</param>
/// <param name="token">Токен отмены</param>
/// <returns>Перечень общесистемных справочников</returns>
public async Task<NsiListType> ExportNsiListAsync(ListGroup listGroup, CancellationToken token = default)
{
try
{
var request = new ExportNsiListRequest(client);
return await request.ExecuteAsync(listGroup, token);
}
catch (NoResultsRemoteException)
{
return null;
}
}
}
}

View File

@ -0,0 +1,49 @@
using Hcs.Broker.Api.Request.Exception;
using Hcs.Broker.Api.Request.OrgRegistryCommon;
using Hcs.Service.Async.OrgRegistryCommon;
namespace Hcs.Broker.Api
{
// http://open-gkh.ru/OrganizationsRegistryCommonAsyncService/
public class OrgRegistryCommonApi(Client client) : ApiBase(client)
{
/// <summary>
/// Экспорт сведений о поставщиках информации ИС
/// </summary>
/// <param name="isActual">Выгрузить только активных поставщиков данных</param>
/// <param name="token">Токен отмены</param>
/// <returns>Сведения о поставщиках данных</returns>
public async Task<IEnumerable<exportDataProviderResultType>> ExportDataProviderAsync(bool isActual, CancellationToken token = default)
{
try
{
var request = new ExportDataProviderRequest(client);
return await request.ExecuteAsync(isActual, token);
}
catch (NoResultsRemoteException)
{
return [];
}
}
/// <summary>
/// Экспорт сведений из реестра организаций
/// </summary>
/// <param name="ogrn">ОГРН</param>
/// <param name="kpp">КПП</param>
/// <param name="token">Токен отмены</param>
/// <returns>Сведения из реестра организаций</returns>
public async Task<IEnumerable<exportOrgRegistryResultType>> ExportOrgRegistryAsync(string ogrn, string kpp, CancellationToken token = default)
{
try
{
var request = new ExportOrgRegistryRequest(client);
return await request.ExecuteAsync(ogrn, kpp, token);
}
catch (NoResultsRemoteException)
{
return [];
}
}
}
}

View File

@ -0,0 +1,239 @@
using Hcs.Broker.Api.Registry;
using Hcs.Broker.Api.Type;
namespace Hcs.Broker.Api.Payload.Bills
{
// http://open-gkh.ru/Bills/importPaymentDocumentRequest.html
public class ImportPaymentDocumentDataPayload
{
// http://open-gkh.ru/Bills/importPaymentDocumentRequest/PaymentInformation.html
public class PaymentInformation
{
/// <summary>
/// БИК банка получателя
/// </summary>
public string bankBIK;
/// <summary>
/// Номер расчетного счета
/// </summary>
public string operatingAccountNumber;
}
/// <summary>
/// Начисление по услуге
/// </summary>
// http://open-gkh.ru/Bills/PaymentDocumentType/ChargeInfo.html
public interface IChargeInfo { }
/// <summary>
/// Главная коммунальная услуга
/// </summary>
// http://open-gkh.ru/Bills/PDServiceChargeType/MunicipalService.html
public class MunicipalService : IChargeInfo
{
/// <summary>
/// Необязательное. Перерасчеты, корректировки, руб.
/// </summary>
public decimal? moneyRecalculation;
/// <summary>
/// Необязательное. Льготы, субсидии, скидки, руб.
/// </summary>
public decimal? moneyDiscount;
/// <summary>
/// Необязательное. Норматив потребления коммунальных ресурсов в целях использования и содержания
/// общего имущества в многоквартирном доме.
/// </summary>
public decimal? houseOverallNeedsNorm;
/// <summary>
/// Необязательное. Норматив потребления коммунальных услуг.
/// </summary>
public decimal? individualConsumptionNorm;
/// <summary>
/// Необязательное. Текущие показания приборов учёта коммунальных ресурсов - индивидуальных
/// (квартирных).
/// </summary>
public decimal? individualConsumptionCurrentValue;
/// <summary>
/// Необязательное. Текущие показания приборов учёта коммунальных ресурсов - коллективных (общедомовых).
/// </summary>
public decimal? houseOverallNeedsCurrentValue;
/// <summary>
/// Необязательное. Суммарный объём коммунальных ресурсов в многоквартирном доме - в помещениях дома.
/// </summary>
public decimal? houseTotalIndividualConsumption;
/// <summary>
/// Необязательное. Суммарный объём коммунальных ресурсов в многоквартирном доме - в целях содержания
/// общего имущества в многоквартирном доме.
/// </summary>
public decimal? houseTotalHouseOverallNeeds;
/// <summary>
/// Необязательное. Способ определения объема коммунальных ресурсов при индивидуальном потреблении.
/// </summary>
public MunicipalServiceVolumeDeterminingMethod? individualConsumptionVolumeDeterminingMethod;
/// <summary>
/// Необязательное. Объем/площадь/кол-во коммунальных ресурсов при индивидуальном потреблении.
/// </summary>
public decimal? individualConsumptionVolumeValue;
/// <summary>
/// Необязательное. Способ определения объема коммунальных ресурсов при содержании общего имущества.
/// </summary>
public MunicipalServiceVolumeDeterminingMethod? overallConsumptionVolumeDeterminingMethod;
/// <summary>
/// Необязательное. Объем/площадь/кол-во коммунальных ресурсов при содержании общего имущества.
/// </summary>
public decimal? overallConsumptionVolumeValue;
/// <summary>
/// Необязательное. Размер повышающего коэффициента.
/// </summary>
public decimal? multiplyingFactorRatio;
/// <summary>
/// Необязательное. Размер превышения платы, рассчитанной с применением повышающего коэффициента над
/// размером платы, рассчитанной без учета повышающего коэффициента, руб.
/// </summary>
public decimal? amountOfExcessFees;
/// <summary>
/// К оплате за индивидуальное потребление коммунальной услуги, руб.
/// </summary>
public decimal? municipalServiceIndividualConsumptionPayable;
/// <summary>
/// К оплате за общедомовое потребление коммунальной услуги, руб.
/// </summary>
public decimal? municipalServiceCommunalConsumptionPayable;
/// <summary>
/// Необязательное. Размер платы за коммунальные услуги, индивидуальное потребление.
/// </summary>
public decimal? amountOfPaymentMunicipalServiceIndividualConsumption;
/// <summary>
/// Необязательное. Размер платы за коммунальные услуги, общедомовые нужды.
/// </summary>
public decimal? amountOfPaymentMunicipalServiceCommunalConsumption;
/// <summary>
/// Код услуги из справочника "Вид коммунальной услуги" НСИ 3
/// </summary>
public RegistryElement serviceType;
/// <summary>
/// Тариф/Размер платы на кв.м, руб./Размер взноса на кв.м, руб.
/// </summary>
public decimal rate;
/// <summary>
/// К оплате за расчетный период, руб.
/// </summary>
public decimal totalPayable;
/// <summary>
/// Необязательное. Начислено за расчетный период (без перерасчетов и льгот), руб.
/// </summary>
public decimal? accountingPeriodTotal;
}
// http://open-gkh.ru/Bills/importPaymentDocumentRequest/PaymentDocument.html
public class PaymentDocument
{
/// <summary>
/// Платежный реквизит
/// </summary>
public PaymentInformation paymentInformation;
/// <summary>
/// Идентификатор лицевого счета
/// </summary>
public string accountGuid;
/// <summary>
/// Необязательное. Номер платежного документа, по которому внесена плата, присвоенный такому
/// документу исполнителем в целях осуществления расчетов по внесению платы
/// </summary>
public string paymentDocumentNumber;
/// <summary>
/// Начисления по услугам
/// </summary>
public List<IChargeInfo> chargeInfo;
/// <summary>
/// Если true, то выставлен на оплату, иначе - отозван
/// </summary>
public bool exposeNotWithdraw;
/// <summary>
/// Необязательное. Задолженность за предыдущие периоды, руб.
/// </summary>
public decimal? debtPreviousPeriods;
/// <summary>
/// Необязательное. Аванс на начало расчетного периода, руб.
/// </summary>
public decimal? advanceBllingPeriod;
/// <summary>
/// Необязательное. Итого к оплате за расчетный период c учетом задолженности/переплаты, руб.
/// (по всему платежному документу)
/// </summary>
public decimal? totalPayableByPDWithDebtAndAdvance;
/// <summary>
/// Необязательное. Сумма к оплате за расчетный период, руб. (по всему платежному документу).
/// </summary>
public decimal? totalPayableByPD;
/// <summary>
/// Необязательное. Оплачено денежных средств, руб.
/// </summary>
public decimal? paidCash;
/// <summary>
/// Необязательное. Дата последней поступившей оплаты
/// </summary>
public DateTime? dateOfLastReceivedPayment;
}
/// <summary>
/// Необязательное. Если true, то передаваемые данные платежных документов, следует считать верными,
/// даже если они отличаются от автоматически рассчитанных системой значений. В том случае, если параметр
/// не заполнен, то ГИС ЖХК будет проводить автоматическую проверку рассчитываемых сумм по ПД.
/// </summary>
public bool confirmAmountsCorrect;
/// <summary>
/// Месяц расчетного периода платежного документа
/// </summary>
public int month;
/// <summary>
/// Год расчетного периода платежного документа
/// </summary>
public short year;
/// <summary>
/// Сведения о платежных реквизитах получателя платежа - бизнес-ключ поиска размещенных платежных
/// реквизитов в ГИС ЖКХ
/// </summary>
public PaymentInformation[] paymentInformation;
/// <summary>
/// Размещаемый платежный документ. Максимум 500.
/// </summary>
public PaymentDocument[] paymentDocument;
}
}

View File

@ -0,0 +1,90 @@
using Hcs.Broker.Api.Registry;
namespace Hcs.Broker.Api.Payload.DeviceMetering
{
// http://open-gkh.ru/DeviceMetering/exportMeteringDeviceHistoryRequest.html
public class ExportMeteringDeviceHistoryPayload
{
/// <summary>
/// Необязательное. Список из уникальных идентификаторов домов по ФИАС, в которых установлены ПУ
/// пользователей. Если не указано, то будут экспортироваться данные по всем ПУ пользователей.
/// </summary>
public string[] fiasHouseGuid;
/// <summary>
/// Выборочное. Выбор между <see cref="meteringDeviceType"/>, <see cref="municipalResource"/> и
/// <see cref="meteringDeviceRootGUID"/>. Тип прибора учета (НСИ 27). Максимум 100 по выбранным.
/// </summary>
public RegistryElement[] meteringDeviceType;
/// <summary>
/// Выборочное. Выбор между <see cref="meteringDeviceType"/>, <see cref="municipalResource"/> и
/// <see cref="meteringDeviceRootGUID"/>. Вид коммунального ресурса (НСИ 2). Максимум 100 по выбранным.
/// </summary>
public RegistryElement[] municipalResource;
/// <summary>
/// Выборочное. Выбор между <see cref="meteringDeviceType"/>, <see cref="municipalResource"/> и
/// <see cref="meteringDeviceRootGUID"/>. Идентификатор ПУ. Максимум 100 по выбранным.
/// </summary>
public string[] meteringDeviceRootGUID;
/// <summary>
/// Необязательное. Дата ввода в эксплуатацию "С".
/// </summary>
public DateTime? commissioningDateFrom;
/// <summary>
/// Необязательное. Дата ввода в эксплуатацию "П".
/// </summary>
public DateTime? сommissioningDateTo;
/// <summary>
/// Необязательное. Выгружать архивированные или нет.
/// </summary>
public bool? serchArchived;
/// <summary>
/// Необязательное. Дата архивации "С".
/// </summary>
public DateTime? archiveDateFrom;
/// <summary>
/// Необязательное. Дата архивации "По".
/// </summary>
public DateTime? archiveDateTo;
/// <summary>
/// Необязательное. Дата начала периода, за который выгружаются показания и поверки ПУ (по дате
/// снятия показаний). Период выгрузки показаний ПУ (определяемый элементами <see cref="inputDateFrom"/>
/// и <see cref="inputDateTo"/>) не должен выходить за пределы двух последовательных календарных месяцев.
/// </summary>
public DateTime? inputDateFrom;
/// <summary>
/// Необязательное. Дата окончания периода, за который выгружаются показания и поверки ПУ (по дате
/// снятия показаний). Период выгрузки показаний ПУ (определяемый элементами <see cref="inputDateFrom"/>
/// и <see cref="inputDateTo"/>) не должен выходить за пределы двух последовательных календарных месяцев.
/// </summary>
public DateTime? inputDateTo;
/// <summary>
/// Необязательное. Если флаг сброшен или отсутствует, то показания, введенные в систему гражданином,
/// включаются в выгрузку. Если флаг установлен, то такие показания в выгрузку не включаются.
/// </summary>
public bool? excludePersonAsDataSource;
/// <summary>
/// Необязательное. Если флаг сброшен или отсутствует, то показания, введенные в систему текущей
/// организацией, включаются в выгрузку. Если флаг установлен, то такие показания в выгрузку не включаются.
/// </summary>
public bool? excludeCurrentOrgAsDataSource;
/// <summary>
/// Необязательное. Если флаг сброшен или отсутствует, то показания, введенные в систему организациями
/// отличной от текущей, включаются в выгрузку. Если флаг установлен, то такие показания в выгрузку
/// не включаются.
/// </summary>
public bool? excludeOtherOrgAsDataSource;
}
}

View File

@ -0,0 +1,97 @@
using Hcs.Service.Async.HouseManagement;
namespace Hcs.Broker.Api.Payload.HouseManagement
{
// http://open-gkh.ru/HouseManagement/importAccountRequest/Account.html
public class ImportAccountDataPayload
{
/// <summary>
/// Тип лицевого счета
/// </summary>
public enum AccountType
{
/// <summary>
/// Лицевой счет для оплаты за жилое помещение и коммунальные услуги
/// </summary>
UO,
/// <summary>
/// Лицевой счет для оплаты за коммунальные услуги
/// </summary>
RSO,
/// <summary>
/// Лицевой счет для оплаты капитального ремонта
/// </summary>
CR,
/// <summary>
/// Лицевой счет РКЦ
/// </summary>
RC,
/// <summary>
/// Лицевой счет ОГВ/ОМС
/// </summary>
OGVorOMS,
/// <summary>
/// Лицевой счет ТКО
/// </summary>
TKO
}
/// <summary>
/// Необязательное. Номер лицевого счета или иной идентификатор плательщика. Максимум 30 символов.
/// </summary>
public string accountNumber;
/// <summary>
/// Необязательное. Идентификатор ЛС в ГИС ЖКХ (при обновлении данных ЛС).
/// </summary>
public string accountGUID;
/// <summary>
/// Необязательное. Конкретизация оснований ЛС (договоров ресурсоснабжения, договоров социального найма,
/// договоров по обращению с ТКО).
/// </summary>
public AccountReasonsImportType accountReasons;
/// <summary>
/// Тип лицевого счета
/// </summary>
public AccountType accountType;
/// <summary>
/// Необязательное. Количество проживающих, не больше 9999.
/// </summary>
public uint? livingPersonsNumber;
/// <summary>
/// Необязательное. Общая площадь для ЛС. Не более 4 цифр после целой.
/// </summary>
public decimal? totalSquare;
/// <summary>
/// Необязательное. Жилая площадь. Не более 4 цифр после целой.
/// </summary>
public decimal? residentialSquare;
/// <summary>
/// Необязательное. Отапливаемая площадь. Не более 4 цифр после целой.
/// </summary>
public decimal? heatedArea;
// TODO: Добавить причину закрытия лицевого счета
/// <summary>
/// Помещения
/// </summary>
public AccountTypeAccommodation[] accomodations;
/// <summary>
/// Сведения о платильщике
/// </summary>
public AccountTypePayerInfo payerInfo;
}
}

View File

@ -0,0 +1,68 @@
using Hcs.Broker.Api.Registry;
using Hcs.Service.Async.HouseManagement;
namespace Hcs.Broker.Api.Payload.HouseManagement
{
public class ImportContractDataPayload
{
// TODO: LicenseRequest
/// <summary>
/// Объекты управления
/// </summary>
public importContractRequestContractPlacingContractContractObject[] contractObjects;
/// <summary>
/// Номер документа
/// </summary>
public string docNum;
/// <summary>
/// Дата заключения
/// </summary>
public DateTime signingDate;
/// <summary>
/// Дата вступления в силу
/// </summary>
public DateTime effectiveDate;
/// <summary>
/// Планируемая дата окончания
/// </summary>
public DateTime planDateComptetion;
// TODO: Вторая сторона договора
// TODO: Protocol
/// <summary>
/// Ссылка на НСИ "Основание заключения договора" (реестровый номер 58)
/// </summary>
public RegistryElement contractBase;
/// <summary>
/// Сведения о сроках
/// </summary>
public DateDetailsType dateDetailsType;
/// <summary>
/// Договор на управление и приложения
/// </summary>
public AttachmentType[] contractAttachment;
// TODO: AgreementAttachment
// TODO: SignedOwners
// TODO: CommissioningPermitAgreement
// TODO: Charter
// TODO: LocalGovernmentDecision
// TODO: RegistryDecisionID
// TODO: AutomaticRollOverOneYear
}
}

View File

@ -0,0 +1,94 @@
using Hcs.Broker.Api.Registry;
using Hcs.Service.Async.HouseManagement;
namespace Hcs.Broker.Api.Payload.HouseManagement
{
// http://open-gkh.ru/HouseManagement/importHouseUORequest/LivingHouse/LivingHouseToCreate.html
// http://open-gkh.ru/HouseManagement/HouseBasicUOType.html
public class ImportLivingHouseUODataPayload
{
/// <summary>
/// Глобальный уникальный идентификатор дома по ФИАС
/// </summary>
public Guid fiasHouseGuid;
/// <summary>
/// Общая площадь здания
/// </summary>
public decimal totalSquare;
/// <summary>
/// Состояние (НСИ 24)
/// </summary>
public RegistryElement state;
/// <summary>
/// Необязательное. Стадия жизненного цикла (НСИ 338).
/// </summary>
public RegistryElement lifeCycleStage;
/// <summary>
/// Год ввода в эксплуатацию. До 2215 включительно.
/// </summary>
public short usedYear;
/// <summary>
/// Количество этажей. До 999 включительно.
/// </summary>
public int floorCount;
/// <summary>
/// Необязательное. ОКТМО (обязательное для всех территорий, за исключением города и космодрома
/// "Байконур"). Значение из ФИАС при наличии.
/// </summary>
public OKTMORefType oktmo;
/// <summary>
/// Часовая зона. Справочник 32.
/// </summary>
public RegistryElement olsonTZ;
/// <summary>
/// Наличие у дома статуса объекта культурного наследия
/// </summary>
public bool culturalHeritage;
/// <summary>
/// Необязательное. Данные ОЖФ
/// </summary>
public OGFData[] ogfData;
/// <summary>
/// Необязательное. Дом находится в муниципальной собственности и в полном объеме используется
/// в качестве общежития. Принимает только false.
/// </summary>
public bool isMunicipalProperty;
/// <summary>
/// Необязательное. Дом находится в собственности субъекта Российской Федерации и в полном объеме
/// используется в качестве общежития. Принимает только false.
/// </summary>
public bool isRegionProperty;
/// <summary>
/// Кадастровый номер
/// </summary>
public string cadastralNumber;
/// <summary>
/// Условный номер. При указании в ГИС ЖКХ осуществляется привязка к ЕГРП (поиск в ЕГРП выполняется
/// по условному номеру).
/// </summary>
public string conditionalNumber;
/// <summary>
/// Необязательное. Жилой дом блокированной застройки (если не указан - аналог false).
/// </summary>
public bool hasBlocks;
/// <summary>
/// Необязательное. Несколько жилых домов с одинаковым адресом (если не указан - аналог false)
/// </summary>
public bool hasMultipleHousesWithSameAddress;
}
}

View File

@ -0,0 +1,73 @@
using Hcs.Service.Async.HouseManagement;
namespace Hcs.Broker.Api.Payload.HouseManagement
{
// http://open-gkh.ru/HouseManagement/importNotificationRequest/notification/Create.html
public class ImportNotificationDataPayload
{
/// <summary>
/// Выборочное. Строковое представление темы, вместо ссылки на справочник. Максимальная длина
/// текста равно 200 символам.
/// </summary>
public string topic;
/// <summary>
/// Выборочное. Тема из справочника 364, заместо строкового представления темы.
/// </summary>
public nsiRef topicFromRegistry;
/// <summary>
/// Необязательное. Показывает высокую важность новости.
/// </summary>
public bool isImportant;
/// <summary>
/// Необязательное. Текст новости с максимальной длиной в 5000 символов.
/// </summary>
public string content;
/// <summary>
/// Адресаты. Подходящие типы для значения:
/// <see cref="string"/>, если это глобальный уникальный идентификатор дома по ФИАС,
/// <see cref="importNotificationRequestNotificationCreateRoomOwners"/>,
/// <see cref="RegOrgType"/> либо true, если все дома.
/// </summary>
public List<Tuple<ItemsChoiceType29, object>> destinations;
/// <summary>
/// Выборочное. Если true, то новость всегда актуальна. Иначе период актуальности берется из
/// <see cref="startDate"/> и <see cref="endDate"/>.
/// </summary>
public bool isNotLimit;
/// <summary>
/// Условное. Период актуальности "С". Обязательно задается в случае <see cref="isNotLimit"/> = false.
/// </summary>
public DateTime? startDate;
/// <summary>
/// Условное. Период актуальности "ДО". Обязательно задается в случае <see cref="isNotLimit"/> = false.
/// </summary>
public DateTime? endDate;
/// <summary>
/// Необязательное. Документы новостей.
/// </summary>
public AttachmentType[] attachment;
/// <summary>
/// Необязательное. Если true, то новость отправляется адресатам.
/// </summary>
public bool isShipOff;
/// <summary>
/// Необязательное. Признак "Для публикации в мобильном приложении".
/// </summary>
public bool isForPublishToMobileApp;
/// <summary>
/// Необязательное. Информация для новости, публикуемой в мобильном приложении.
/// </summary>
public importNotificationRequestNotificationCreateMobileAppData mobileAppData;
}
}

View File

@ -0,0 +1,211 @@
using Hcs.Service.Async.HouseManagement;
namespace Hcs.Broker.Api.Payload.HouseManagement
{
// http://open-gkh.ru/HouseManagement/SupplyResourceContractType.html
public class ImportSupplyResourceContractDataPayload
{
/// <summary>
/// Если договор не является публичным и/или присутствует заключенный на бумажном носителе
/// (электронной форме) и/или не заключен в отношении нежилых помещений в многоквартирных домах,
/// то равно true, иначе - false
/// </summary>
public bool isContract;
/// <summary>
/// Номер договора
/// </summary>
public string contractNumber;
/// <summary>
/// Дата заключения
/// </summary>
public DateTime signingDate;
/// <summary>
/// Дата вступления в силу
/// </summary>
public DateTime effectiveDate;
/// <summary>
/// Необязательное. Договор заключен на неопределенный срок или нет.
/// </summary>
public bool indefiniteTerm;
/// <summary>
/// Необязательное. Автоматически пролонгировать договор на один год при наступлении
/// даты окончания действия или нет.
/// </summary>
public bool automaticRollOverOneYear;
/// <summary>
/// Условное. Дата окончания действия. Обязательно для заполнения, если
/// <see cref="automaticRollOverOneYear"/> = true.
/// </summary>
public DateTime? comptetionDate;
/// <summary>
/// Условное. Период передачи текущих показаний по индивидуальным приборам учета. Обязателен для
/// заполнения, если поле <see cref="volumeDepends"/> = true ИЛИ если поле
/// <see cref="meteringDeviceInformation"/> = true.
/// </summary>
public SupplyResourceContractTypePeriod period;
/// <summary>
/// Необязательное. Показывает, разрешена ли гражданам передача текущих показаний по
/// индивидуальным приборам учета в любой день месяца. Заполнение возможно только если: в настройках
/// организации установлена настройка "Разрешить передачу гражданам показаний индивидуальных или общих
/// (квартирных) приборов учета в любой день месяца" ИЛИ в настройках организации установлена настройка
/// "Разрешить передачу гражданам показаний индивидуальных или общих (квартирных) приборов учета только
/// в сроки, установленные в договоре, или в любой день месяца, если в договоре установлен признак
/// "Разрешить передачу показаний приборов учета в любой день месяца" И заполнен <see cref="period"/>.
/// </summary>
public bool indicationsAnyDay;
/// <summary>
/// Необязательное. Ссылка на НСИ "Основание заключения договора" (реестровый номер 58). Значения
/// брать из <see cref="Registry.Registry58"/>.
/// </summary>
public nsiRef[] contractBase;
/// <summary>
/// Вторая сторона договора. Подходящие типы:
/// <see cref="SupplyResourceContractTypeApartmentBuildingOwner"/>,
/// <see cref="SupplyResourceContractTypeApartmentBuildingRepresentativeOwner"/>,
/// <see cref="SupplyResourceContractTypeApartmentBuildingSoleOwner"/>,
/// <see cref="SupplyResourceContractTypeLivingHouseOwner"/>,
/// <see cref="SupplyResourceContractTypeOrganization"/> либо true, если это договор оферты.
/// </summary>
public object counterparty;
/// <summary>
/// Если в договоре в наличии плановый объем и режим подачи поставки ресурсов то true, иначе - false
/// </summary>
public bool isPlannedVolume;
/// <summary>
/// Необязательное. Тип ведения планового объема и режима подачи: D - в разрезе договора,
/// O - в разрезе объектов жилищного фонда. Заполняется при наличии в договоре планового объема и
/// режима поставки ресурсов.
/// </summary>
public SupplyResourceContractTypePlannedVolumeType? plannedVolumeType;
/// <summary>
/// Предмет договора. Максимум 100 записей.
/// </summary>
public SupplyResourceContractTypeContractSubject[] contractSubject;
/// <summary>
/// Условное. Размещение информации о начислениях за коммунальные услуги осуществляет: R(SO)- РСО,
/// P(roprietor) - исполнитель коммунальных услуг. Заполняется, если порядок размещения информации
/// о начислениях за коммунальные услуги ведется в разрезе договора.
/// </summary>
public SupplyResourceContractTypeCountingResource? countingResource;
/// <summary>
/// Показатели качества коммунальных ресурсов и температурный график ведутся: D - в разрезе договора,
/// O - в разрезе объектов жилищного фонда
/// </summary>
public SupplyResourceContractTypeSpecifyingQualityIndicators specifyingQualityIndicators;
/// <summary>
/// Необязательное. Признак "Отсутствие присоединения сетей объектов жилищного фонда к централизованной
/// системе водоснабжения". Может быть указан, только если показатели качества коммунальных ресурсов
/// ведутся в разрезе договора и предмет договора включает коммунальную услугу "Холодное водоснабжение"
/// И/ИЛИ "Горячее водоснабжение".
/// </summary>
public bool noConnectionToWaterSupply;
/// <summary>
/// Условное. Данные об объекте жилищного фонда. При импорте договора должен быть добавлен как минимум
/// один адрес объекта жилищного фонда.
/// </summary>
public SupplyResourceContractTypeObjectAddress[] objectAddress;
/// <summary>
/// Необязательное. Показатель качества (содержащийся в справочнике показателей качества). Если
/// показатели указываются в разрезе договора, то ссылка на ОЖФ не заполняется. Если показатели
/// указываются в разрезе ОЖФ, то ссылка на ОЖФ обязательна.
/// </summary>
public SupplyResourceContractTypeQuality[] quality;
/// <summary>
/// Необязательное. Иной показатель качества коммунального ресурса (не содержащийся в справочнике
/// показателей качества). Если показатели указываются в разрезе договора, то ссылка на ОЖФ
/// не заполняется. Если показатели указываются в разрезе ОЖФ, то ссылка на ОЖФ обязательна.
/// </summary>
public SupplyResourceContractTypeOtherQualityIndicator[] otherQualityIndicator;
/// <summary>
/// Необязательное. Информация о температурном графике. Если показатели качества указываются в разрезе
/// договора, то ссылка на ОЖФ в данном элементе не заполняется и элемент может заполняться только если
/// в предмете договора хотя бы раз встречается ресурс "Тепловая энергия". Если показатели качества
/// указываются в разрезе ОЖФ, то ссылка на ОЖФ обязательна и элемент заполняется только если
/// в рамках ОЖФ встречается ресурс "Тепловая энергия".
/// </summary>
public SupplyResourceContractTypeTemperatureChart[] temperatureChart;
/// <summary>
/// Условное. Срок представления (выставления) платежных документов, не позднее. Является
/// обязательным, если вторая сторона договора отличается от "Управляющая организация" ИЛИ если
/// заполнено поле <see cref="meteringDeviceInformation"/>. Не заполняется, если
/// <see cref="oneTimePayment"/> = true.
/// </summary>
public SupplyResourceContractTypeBillingDate billingDate;
/// <summary>
/// Условное. Срок внесения платы, не позднее. Является обязательным, если вторая сторона договора
/// отличается от "Управляющая организация" И договор не является публичным и/или присутствует
/// заключенный на бумажном носителе или в электронной форме И в поле
/// <see cref="oneTimePayment"/> = false. Не заполняется, если <see cref="oneTimePayment"/> = true.
/// </summary>
public SupplyResourceContractTypePaymentDate paymentDate;
/// <summary>
/// Условное. Срок предоставления информации о поступивших платежах, не позднее. Является
/// обязательным, если второй стороной договора является "Управляющая организация",
/// "Размещение информации о начислениях за коммунальные услуги осуществляет" = "РСО" И
/// договор не является публичным и/или присутствует заключенный на бумажном носителе или в
/// электронной форме.
/// </summary>
public SupplyResourceContractTypeProvidingInformationDate providingInformationDate;
/// <summary>
/// Условное. Указывает на то, что размещение информации об индивидуальных приборах учета и их
/// показаниях осуществляет ресурсоснабжающая организация или нет. Обязательно для заполнения,
/// если в <see cref="countingResource"/> указано "РСО". В остальных случаях не заполняется.
/// </summary>
public bool? meteringDeviceInformation;
/// <summary>
/// Необязательное. Указывает на то, что объем поставки ресурса(ов) определяется на основании прибора
/// учета или нет. Поле не заполняется, если вторая сторона договора "Управляющая организация"
/// ИЛИ поле <see cref="oneTimePayment"/> = true.
/// </summary>
public bool? volumeDepends;
/// <summary>
/// Необязательное. Указывает на то, что оплата предоставленных услуг осуществляется ли единоразово
/// при отгрузке указанных ресурсов без заведения лицевых счетов для потребителей. Доступно
/// для заполнения, только если вторая сторона договора отлична от "Управляющая организация".
/// </summary>
public bool? oneTimePayment;
/// <summary>
/// Необязательное. Порядок размещения информации о начислениях за коммунальные услуги ведется: D - в
/// разрезе договора, O - в разрезе объектов жилищного фонда. Заполняется, если второй стороной договора
/// является исполнитель коммунальных услуг.
/// </summary>
public SupplyResourceContractTypeAccrualProcedure? accrualProcedure;
/// <summary>
/// Необязательное. Информация о применяемом тарифе.
/// </summary>
public SupplyResourceContractTypeTariff[] tariff;
/// <summary>
/// Необязательное. Информация о нормативе потребления коммунальной услуги.
/// </summary>
public SupplyResourceContractTypeNorm[] norm;
}
}

View File

@ -0,0 +1,213 @@
using Hcs.Service.Async.HouseManagement;
namespace Hcs.Broker.Api.Payload.HouseManagement
{
// http://open-gkh.ru/HouseManagement/SupplyResourceContractType.html
public class ImportSupplyResourceContractProjectPayload
{
/// <summary>
/// Если договор не является публичным и/или присутствует заключенный на бумажном носителе
/// (электронной форме) и/или не заключен в отношении нежилых помещений в многоквартирных домах,
/// то равно true, иначе - false
/// </summary>
public bool isContract;
/// <summary>
/// Номер договора
/// </summary>
public string contractNumber;
/// <summary>
/// Дата заключения
/// </summary>
public DateTime signingDate;
/// <summary>
/// Дата вступления в силу
/// </summary>
public DateTime effectiveDate;
/// <summary>
/// Необязательное. Договор заключен на неопределенный срок или нет.
/// </summary>
public bool indefiniteTerm;
/// <summary>
/// Необязательное. Автоматически пролонгировать договор на один год при наступлении
/// даты окончания действия или нет.
/// </summary>
public bool automaticRollOverOneYear;
/// <summary>
/// Условное. Дата окончания действия. Обязательно для заполнения, если
/// <see cref="automaticRollOverOneYear"/> = true.
/// </summary>
public DateTime? comptetionDate;
/// <summary>
/// Условное. Период передачи текущих показаний по индивидуальным приборам учета. Обязателен для
/// заполнения, если поле <see cref="volumeDepends"/> = true ИЛИ если поле
/// <see cref="meteringDeviceInformation"/> = true.
/// </summary>
public SupplyResourceContractProjectTypePeriod period;
/// <summary>
/// Необязательное. Показывает, разрешена ли гражданам передача текущих показаний по
/// индивидуальным приборам учета в любой день месяца. Заполнение возможно только если: в настройках
/// организации установлена настройка "Разрешить передачу гражданам показаний индивидуальных или общих
/// (квартирных) приборов учета в любой день месяца" ИЛИ в настройках организации установлена настройка
/// "Разрешить передачу гражданам показаний индивидуальных или общих (квартирных) приборов учета только
/// в сроки, установленные в договоре, или в любой день месяца, если в договоре установлен признак
/// "Разрешить передачу показаний приборов учета в любой день месяца" И заполнен <see cref="period"/>.
/// </summary>
public bool indicationsAnyDay;
/// <summary>
/// Необязательное. Ссылка на НСИ "Основание заключения договора" (реестровый номер 58). Значения
/// брать из <see cref="Registry.Registry58"/>.
/// </summary>
public nsiRef[] contractBase;
/// <summary>
/// Вторая сторона договора. Подходящие типы:
/// <see cref="SupplyResourceContractProjectTypeApartmentBuildingOwner"/>,
/// <see cref="SupplyResourceContractProjectTypeApartmentBuildingRepresentativeOwner"/>,
/// <see cref="SupplyResourceContractProjectTypeApartmentBuildingSoleOwner"/>,
/// <see cref="SupplyResourceContractProjectTypeLivingHouseOwner"/>,
/// <see cref="SupplyResourceContractProjectTypeOrganization"/> либо true, если это договор оферты.
/// </summary>
public object counterparty;
/// <summary>
/// Если в договоре в наличии плановый объем и режим подачи поставки ресурсов то true, иначе - false
/// </summary>
public bool isPlannedVolume;
/// <summary>
/// Необязательное. Тип ведения планового объема и режима подачи: D - в разрезе договора,
/// O - в разрезе объектов жилищного фонда. Заполняется при наличии в договоре планового объема и
/// режима поставки ресурсов.
/// </summary>
public SupplyResourceContractProjectTypePlannedVolumeType? plannedVolumeType;
/// <summary>
/// Предмет договора. Максимум 100 записей.
/// </summary>
public SupplyResourceContractProjectTypeContractSubject[] contractSubject;
/// <summary>
/// Условное. Размещение информации о начислениях за коммунальные услуги осуществляет: R(SO)- РСО,
/// P(roprietor) - исполнитель коммунальных услуг. Заполняется, если порядок размещения информации
/// о начислениях за коммунальные услуги ведется в разрезе договора.
/// </summary>
public SupplyResourceContractProjectTypeCountingResource? countingResource;
/// <summary>
/// Показатели качества коммунальных ресурсов и температурный график ведутся: D - в разрезе договора,
/// O - в разрезе объектов жилищного фонда
/// </summary>
public SupplyResourceContractProjectTypeSpecifyingQualityIndicators specifyingQualityIndicators;
/// <summary>
/// Необязательное. Признак "Отсутствие присоединения сетей объектов жилищного фонда к централизованной
/// системе водоснабжения". Может быть указан, только если показатели качества коммунальных ресурсов
/// ведутся в разрезе договора и предмет договора включает коммунальную услугу "Холодное водоснабжение"
/// И/ИЛИ "Горячее водоснабжение".
/// </summary>
public bool noConnectionToWaterSupply;
/// <summary>
/// Необязательное. Показатель качества (содержащийся в справочнике показателей качества). Обязательно
/// для заполнения, если показатели качества указываются в разрезе договора. Для пары КУ и КР
/// "Горячее водоснабжение" и "Питьевая вода" указываются актуальные показатели, определенные для КР
/// "Горячая вода" в справочнике показателей качества коммунальных ресурсов. Для пары КУ и КР
/// "Горячее водоснабжение" и "Тепловая энергия" информация о показателях качества не заполняется
/// только в том случае, если для договора (если показатели ведутся в разрезе договора) или
/// ОЖФ в договоре (если показатели ведутся в разрезе ОЖФ) также указана пара КУ и КР
/// "Горячее водоснабжение" и "Питьевая вода".
/// </summary>
public SupplyResourceContractProjectTypeQuality[] quality;
/// <summary>
/// Необязательное. Иной показатель качества коммунального ресурса (не содержащийся в справочнике
/// показателей качества).
/// </summary>
public SupplyResourceContractProjectTypeOtherQualityIndicator[] otherQualityIndicator;
/// <summary>
/// Необязательное. Информация о температурном графике. Доступно для заполнения только если в
/// предмете договора хотя бы раз встречается ресурс "Тепловая энергия".
/// </summary>
public SupplyResourceContractProjectTypeTemperatureChart[] temperatureChart;
/// <summary>
/// Условное. Срок представления (выставления) платежных документов, не позднее. Является
/// обязательным, если вторая сторона договора отличается от "Управляющая организация" ИЛИ если
/// заполнено поле <see cref="meteringDeviceInformation"/>. Не заполняется, если
/// <see cref="oneTimePayment"/> = true.
/// </summary>
public SupplyResourceContractProjectTypeBillingDate billingDate;
/// <summary>
/// Условное. Срок внесения платы, не позднее. Является обязательным, если вторая сторона договора
/// отличается от "Управляющая организация" И договор не является публичным и/или присутствует
/// заключенный на бумажном носителе или в электронной форме И в поле
/// <see cref="oneTimePayment"/> = false. Не заполняется, если <see cref="oneTimePayment"/> = true.
/// </summary>
public SupplyResourceContractProjectTypePaymentDate paymentDate;
/// <summary>
/// Условное. Срок предоставления информации о поступивших платежах, не позднее. Является
/// обязательным, если второй стороной договора является "Управляющая организация",
/// "Размещение информации о начислениях за коммунальные услуги осуществляет" = "РСО" И
/// договор не является публичным и/или присутствует заключенный на бумажном носителе или в
/// электронной форме.
/// </summary>
public SupplyResourceContractProjectTypeProvidingInformationDate providingInformationDate;
/// <summary>
/// Условное. Указывает на то, что размещение информации об индивидуальных приборах учета и их
/// показаниях осуществляет ресурсоснабжающая организация или нет. Обязательно для заполнения,
/// если в <see cref="countingResource"/> указано "РСО". В остальных случаях не заполняется.
/// </summary>
public bool? meteringDeviceInformation;
/// <summary>
/// Необязательное. Указывает на то, что объем поставки ресурса(ов) определяется на основании прибора
/// учета или нет. Поле не заполняется, если вторая сторона договора "Управляющая организация"
/// ИЛИ поле <see cref="oneTimePayment"/> = true.
/// </summary>
public bool? volumeDepends;
/// <summary>
/// Необязательное. Указывает на то, что оплата предоставленных услуг осуществляется ли единоразово
/// при отгрузке указанных ресурсов без заведения лицевых счетов для потребителей. Доступно
/// для заполнения, только если вторая сторона договора отлична от "Управляющая организация".
/// </summary>
public bool? oneTimePayment;
/// <summary>
/// Необязательное. Порядок размещения информации о начислениях за коммунальные услуги ведется: D - в
/// разрезе договора, O - в разрезе объектов жилищного фонда. Заполняется, если второй стороной договора
/// является исполнитель коммунальных услуг.
/// </summary>
public SupplyResourceContractProjectTypeAccrualProcedure? accrualProcedure;
/// <summary>
/// Кода ФИАС региона РФ, в котором применяются тарифы и/или нормативы потребления КУ (у поставщика
/// данных должна быть подтвержденная функция РСО в этом Субъекте РФ).
/// </summary>
public string regionCodeFias;
/// <summary>
/// Необязательное. Информация о применяемом тарифе. Заполнется при указании <see cref="regionCodeFias"/>.
/// </summary>
public SupplyResourceContractProjectTypeRegionalSettingsTariff[] tariff;
/// <summary>
/// Необязательное. Информация о нормативе потребления коммунальной услуги. Заполнется при
/// указании <see cref="regionCodeFias"/>.
/// </summary>
public SupplyResourceContractProjectTypeRegionalSettingsNorm[] norm;
}
}

View File

@ -0,0 +1,46 @@
namespace Hcs.Broker.Api.Payload.Payments
{
// http://open-gkh.ru/Payment/importNotificationsOfOrderExecutionRequest/NotificationOfOrderExecution139Type.html
public class ImportNotificationsOfOrderExecutionPayload
{
/// <summary>
/// Уникальный номер платежа (идентификатор операции)
/// </summary>
public string orderId;
/// <summary>
/// Дата
/// </summary>
public DateTime orderDate;
/// <summary>
/// Сумма оплаты (в копейках)
/// </summary>
public decimal amount;
/// <summary>
/// Необязательное. Признак онлайн-оплаты.
/// </summary>
public bool? onlinePayment;
/// <summary>
/// Необязательное. Год. Указывать совместно с <see cref="month"/>.
/// </summary>
public short? year;
/// <summary>
/// Необязательное. Месяц. Указывать совместно с <see cref="year"/>.
/// </summary>
public int? month;
/// <summary>
/// Идентификатор платежного документа
/// </summary>
public string paymentDocumentId;
/// <summary>
/// GUID платежного документа
/// </summary>
public string paymentDocumentGUID;
}
}

View File

@ -0,0 +1,36 @@
namespace Hcs.Broker.Api.Payload.Payments
{
// http://open-gkh.ru/Payment/importSupplierNotificationsOfOrderExecutionRequest/SupplierNotificationOfOrderExecution.html
public class ImportSupplierNotificationsOfOrderExecutionPayload
{
/// <summary>
/// Дата внесения платы (в случае отсутствия: дата поступления средств)
/// </summary>
public DateTime orderDate;
/// <summary>
/// Необязательное. Месяц, за который вносится плата. Указывать совместно с <see cref="year"/>.
/// </summary>
public int? month;
/// <summary>
/// Необязательное. Год, за который вносится плата. Указывать совместно с <see cref="month"/>.
/// </summary>
public short? year;
/// <summary>
/// Идентификатор платежного документа
/// </summary>
public string paymentDocumentId;
/// <summary>
/// Сумма
/// </summary>
public decimal amount;
/// <summary>
/// Необязательное. Признак онлайн-оплаты.
/// </summary>
public bool? onlinePayment;
}
}

View File

@ -0,0 +1,33 @@
using Hcs.Broker.Api.Payload.Payments;
using Hcs.Broker.Api.Request.Payments;
namespace Hcs.Broker.Api
{
// http://open-gkh.ru/PaymentsServiceAsync/
public class PaymentsApi(Client client) : ApiBase(client)
{
/// <summary>
/// ВИ_ОПЛАТАЗВ. Передать перечень документов "Извещение о принятии к исполнению распоряжения".
/// </summary>
/// <param name="payload">Пейлоад документа</param>
/// <param name="token">Токен отмены</param>
/// <returns>true, если операция выполнена успешно, иначе - false</returns>
public async Task<bool> ImportNotificationsOfOrderExecutionAsync(ImportNotificationsOfOrderExecutionPayload payload, CancellationToken token = default)
{
var request = new ImportNotificationsOfOrderExecutionRequest(client);
return await request.ExecuteAsync(payload, token);
}
/// <summary>
/// Импорт пакета документов "Извещение о принятии к исполнению распоряжения", размещаемых исполнителем
/// </summary>
/// <param name="payload">Пейлоад документа</param>
/// <param name="token">Токен отмены</param>
/// <returns>true, если операция выполнена успешно, иначе - false</returns>
public async Task<bool> ImportSupplierNotificationsOfOrderExecutionAsync(ImportSupplierNotificationsOfOrderExecutionPayload payload, CancellationToken token = default)
{
var request = new ImportSupplierNotificationsOfOrderExecutionRequest(client);
return await request.ExecuteAsync(payload, token);
}
}
}

View File

@ -0,0 +1,16 @@
namespace Hcs.Broker.Api.Registry
{
/// <summary>
/// НСИ "Межповерочный интервал" (реестровый номер 16).
/// Взято из https://dom.gosuslugi.ru/opendataapi/nsi-16/v1.
/// </summary>
public static class Registry16
{
/// <summary>
/// 4 года
/// </summary>
public static RegistryElement Element4 => new(
"4",
"e5e3288d-2994-41f7-8e44-f329b09ab77f");
}
}

View File

@ -0,0 +1,16 @@
namespace Hcs.Broker.Api.Registry
{
/// <summary>
/// НСИ "Коммунальный ресурс" (реестровый номер 2).
/// Взято из https://dom.gosuslugi.ru/opendataapi/nsi-2/v1.
/// </summary>
public static class Registry2
{
/// <summary>
/// Тепловая энергия
/// </summary>
public static RegistryElement Element5 => new(
"5",
"44af905f-09c3-4cc3-b749-70048a53d8cf");
}
}

View File

@ -0,0 +1,16 @@
namespace Hcs.Broker.Api.Registry
{
/// <summary>
/// НСИ "Тарифицируемый ресурс" (реестровый номер 239).
/// Взято из https://dom.gosuslugi.ru/opendataapi/nsi-239/v1.
/// </summary>
public static class Registry239
{
/// <summary>
/// Тепловая энергия
/// </summary>
public static RegistryElement Element4 => new(
"4",
"eec6e4b8-76c8-4fce-99b7-c95718edad19");
}
}

View File

@ -0,0 +1,37 @@
namespace Hcs.Broker.Api.Registry
{
/// <summary>
/// НСИ "Состояние дома" (реестровый номер 24).
/// Взято из https://dom.gosuslugi.ru/opendataapi/nsi-24/v1.
/// </summary>
public static class Registry24
{
/// <summary>
/// Аварийный
/// </summary>
public static RegistryElement Element1 => new(
"1",
"cbe05853-a91b-43cc-a2cb-06cdfa97d492");
/// <summary>
/// Исправный
/// </summary>
public static RegistryElement Element2 => new(
"2",
"2d3ae73e-6c72-4740-9122-9c632d1893a7");
/// <summary>
/// Ветхий
/// </summary>
public static RegistryElement Element3 => new(
"3",
"bf083ae4-e4ec-4ace-b190-4d009e5cd1a1");
/// <summary>
/// Не выбран
/// </summary>
public static RegistryElement Element4 => new(
"4",
"4ee07c0b-82d6-41f4-a8c5-2cff784bbd9c");
}
}

View File

@ -0,0 +1,16 @@
namespace Hcs.Broker.Api.Registry
{
/// <summary>
/// НСИ "Тип прибора учета" (реестровый номер 27).
/// Взято из https://dom.gosuslugi.ru/opendataapi/nsi-27/v1.
/// </summary>
public static class Registry27
{
/// <summary>
/// Индивидуальный
/// </summary>
public static RegistryElement Element1 => new(
"1",
"3a9687b5-caed-4ec6-8a08-f4d3d012f2c7");
}
}

View File

@ -0,0 +1,23 @@
namespace Hcs.Broker.Api.Registry
{
/// <summary>
/// НСИ "Показатели качества коммунальных ресурсов" (реестровый номер 276).
/// Взято из https://dom.gosuslugi.ru/opendataapi/nsi-276/v1.
/// </summary>
public static class Registry276
{
/// <summary>
/// Величина тепловой нагрузки
/// </summary>
public static RegistryElement Element4 => new(
"4",
"51dd6edc-83fe-4810-8b62-4dc85a75e9a3");
/// <summary>
/// Диапазон давления теплоносителя в подающем трубопроводе
/// </summary>
public static RegistryElement Element10 => new(
"10",
"a5a17c90-cc4b-4f32-a22b-6e06cd42a68c");
}
}

View File

@ -0,0 +1,16 @@
namespace Hcs.Broker.Api.Registry
{
/// <summary>
/// НСИ "Вид коммунальной услуги" (реестровый номер 3).
/// Взято из https://dom.gosuslugi.ru/opendataapi/nsi-3/v1.
/// </summary>
public static class Registry3
{
/// <summary>
/// Отопление
/// </summary>
public static RegistryElement Element6 => new(
"6",
"74925764-ddf3-4b4b-b18d-85994187c13a");
}
}

View File

@ -0,0 +1,16 @@
namespace Hcs.Broker.Api.Registry
{
/// <summary>
/// НСИ "Часовые зоны по Olson" (реестровый номер 32).
/// Взято из https://dom.gosuslugi.ru/opendataapi/nsi-32/v1.
/// </summary>
public static class Registry32
{
/// <summary>
/// Иркутск
/// </summary>
public static RegistryElement Element11 => new(
"11",
"244ae392-0b96-46f2-80ea-4dac32e7326a");
}
}

View File

@ -0,0 +1,58 @@
namespace Hcs.Broker.Api.Registry
{
/// <summary>
/// НСИ "Стадия жизненного цикла" (реестровый номер 338).
/// Взято из https://dom.gosuslugi.ru/opendataapi/nsi-338/v1.
/// </summary>
public static class Registry338
{
/// <summary>
/// Эксплуатация
/// </summary>
public static RegistryElement Element1 => new(
"1",
"29b18683-5195-4ef4-83fc-71bf45597d46");
/// <summary>
/// Реконструкция
/// </summary>
public static RegistryElement Element2 => new(
"2",
"75764145-f181-47e5-bff1-1306a46eb20e");
/// <summary>
/// Капитальный ремонт с отселением
/// </summary>
public static RegistryElement Element3 => new(
"3",
"dee170df-db42-4cd6-9e5e-b62be91b3663");
/// <summary>
/// Капитальный ремонт без отселения
/// </summary>
public static RegistryElement Element4 => new(
"4",
"91dc91e2-6883-4c84-b711-53f57f28dbe2");
/// <summary>
/// Снос
/// </summary>
public static RegistryElement Element5 => new(
"5",
"cc358aa9-10b3-4d6a-bbec-c5f6b14950f6");
/// <summary>
/// Не эксплуатируется, расселен
/// </summary>
public static RegistryElement Element6 => new(
"6",
"4bed3d7e-6015-428e-b4b4-7b7aec171c0d");
/// <summary>
/// Выведен из эксплуатации
/// </summary>
public static RegistryElement Element7 => new(
"7",
"f3edc065-c1a1-4110-96fa-03313ae7a039");
}
}

View File

@ -0,0 +1,15 @@
namespace Hcs.Broker.Api.Registry
{
/// <summary>
/// НСИ "Вид коммунальной услуги" (реестровый номер 51)
/// </summary>
public static class Registry51
{
/// <summary>
/// Отопление
/// </summary>
public static RegistryElement Element6_1 => new(
"6.1",
"14ad13a3-45ce-408b-b641-6fc59554f803");
}
}

View File

@ -0,0 +1,79 @@
namespace Hcs.Broker.Api.Registry
{
/// <summary>
/// НСИ "Основание заключения договора" (реестровый номер 58).
/// Взято из https://dom.gosuslugi.ru/opendataapi/nsi-58/v1.
/// </summary>
public static class Registry58
{
/// <summary>
/// Решение собрания собственников
/// </summary>
public static RegistryElement Element1 => new(
"1",
"110d48b2-32a9-4a44-939c-b784d9794621");
/// <summary>
/// Открытый конкурс
/// </summary>
public static RegistryElement Element2 => new(
"2",
"a9dc59c3-d53f-42eb-ba98-cf8c74d88d36");
/// <summary>
/// Договор управления
/// </summary>
public static RegistryElement Element3 => new(
"3",
"11efe618-79f8-4f53-bfd6-11620e8e9e1e");
/// <summary>
/// Устав
/// </summary>
public static RegistryElement Element4 => new(
"4",
"a2eb920c-8163-4958-812a-ad153a5dfde6");
/// <summary>
/// Решение правления
/// </summary>
public static RegistryElement Element5 => new(
"5",
"58639715-2708-4b8e-a5e6-7cae4ddbf03b");
/// <summary>
/// Решение органа управления застройщика
/// </summary>
public static RegistryElement Element6 => new(
"6",
"9b606ef5-7701-4a12-a837-d81b50939160");
/// <summary>
/// Заявление потребителя
/// </summary>
public static RegistryElement Element7 => new(
"7",
"93cd9d85-91b8-4bf9-ae48-c5f1e691949f");
/// <summary>
/// Нормативный правовой акт
/// </summary>
public static RegistryElement Element8 => new(
"8",
"8b8ee37b-fa79-40cc-b98d-0e51f0c38d03");
/// <summary>
/// Разрешение на ввод в эксплуатацию
/// </summary>
public static RegistryElement Element9 => new(
"9",
"16331000-d96e-4a33-a6c7-3cb9eacf4927");
/// <summary>
/// Устав
/// </summary>
public static RegistryElement Element10 => new(
"10",
"555638ae-a207-46fa-99bd-88bdb297c45a");
}
}

View File

@ -0,0 +1,9 @@
namespace Hcs.Broker.Api.Registry
{
public class RegistryElement(string code, string guid)
{
public string Code { get; } = code;
public string GUID { get; } = guid;
}
}

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;
}
}
}

View File

@ -0,0 +1,32 @@
using Hcs.Service.Async.Bills;
namespace Hcs.Broker.Api.Type
{
// http://open-gkh.ru/Bills/PDServiceChargeType/MunicipalService/Consumption/Volume/determiningMethod.html
public enum MunicipalServiceVolumeDeterminingMethod
{
Norm,
MeteringDevice,
Other
}
internal static class MunicipalServiceVolumeDeterminingMethodExtensions
{
internal static PDServiceChargeTypeMunicipalServiceVolumeDeterminingMethod ToServiceType(this MunicipalServiceVolumeDeterminingMethod type)
{
switch (type)
{
case MunicipalServiceVolumeDeterminingMethod.Norm:
return PDServiceChargeTypeMunicipalServiceVolumeDeterminingMethod.N;
case MunicipalServiceVolumeDeterminingMethod.MeteringDevice:
return PDServiceChargeTypeMunicipalServiceVolumeDeterminingMethod.M;
case MunicipalServiceVolumeDeterminingMethod.Other:
return PDServiceChargeTypeMunicipalServiceVolumeDeterminingMethod.O;
}
throw new NotImplementedException($"Cannot convert {type} to service type");
}
}
}

107
Hcs.Broker/Client.cs Normal file
View File

@ -0,0 +1,107 @@
using CryptoPro.Security.Cryptography.X509Certificates;
using Hcs.Broker.Api;
using Hcs.Broker.Internal;
using Hcs.Broker.Logger;
using Hcs.Broker.MessageCapturer;
using System.Security.Cryptography.X509Certificates;
namespace Hcs.Broker
{
/// <summary>
/// Клиент для вызова всех реализованных функций интеграции с ГИС ЖКХ
/// </summary>
public class Client
{
/// <summary>
/// Идентификатор поставщика данных ГИС ЖКХ
/// </summary>
public string OrgPPAGUID { get; set; }
/// <summary>
/// Исполнитель/сотрудник ГИС ЖКХ, от которого будут регистрироваться ответы
/// </summary>
public string ExecutorGUID { get; set; }
/// <summary>
/// Признак, указывающий на то, что используется ли внешний туннель (stunnel)
/// </summary>
public bool UseTunnel { get; set; }
/// <summary>
/// Если true, то запросы будут выполняться на промышленном стенде, иначе - на тестовом
/// </summary>
public bool IsPPAK { get; set; }
/// <summary>
/// Роль
/// </summary>
public OrganizationRole Role { get; set; }
/// <summary>
/// Устанавливаемый пользователем приемник отладочных сообщений
/// </summary>
public ILogger Logger { get; set; }
/// <summary>
/// Устанавливаемый пользователем механизм перехвата содержимого отправляемых
/// и принимаемых пакетов
/// </summary>
public IMessageCapturer MessageCapturer { get; set; }
public BillsApi Bills => new(this);
public DeviceMeteringApi DeviceMetering => new(this);
public HouseManagementApi HouseManagement => new(this);
public NsiApi Nsi => new(this);
public NsiCommonApi NsiCommon => new(this);
public OrgRegistryCommonApi OrgRegistryCommon => new(this);
public PaymentsApi Payments => new(this);
/// <summary>
/// Сертификат клиента для применения при формировании запросов
/// </summary>
internal CpX509Certificate2 Certificate { get; set; }
public void SetSigningCertificate(string serialNumber)
{
using var store = new CpX509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
var cert = store.Certificates.Find(X509FindType.FindBySerialNumber, serialNumber, true)[0];
Certificate = cert ?? throw new ArgumentNullException("Certificate not found");
}
internal string ComposeEndpointUri(string endpointName)
{
if (UseTunnel)
{
return $"http://{Constants.URI_TUNNEL}/{endpointName}";
}
return IsPPAK
? $"https://{Constants.URI_PPAK}/{endpointName}"
: $"https://{Constants.URI_SIT_02}/{endpointName}";
}
/// <summary>
/// Пробует вывести сообщение в установленный приемник отладочных сообщений
/// </summary>
internal void TryLog(string message)
{
Logger?.WriteLine(message);
}
/// <summary>
/// Пробует отправить тело сообщения в установленный перехватчик
/// </summary>
internal void TryCaptureMessage(bool sent, string messageBody)
{
MessageCapturer?.CaptureMessage(sent, messageBody);
}
}
}

View File

@ -0,0 +1,16 @@
{
"ExtendedData": {
"inputs": [
"../../Wsdl/wsdl_xsd_v.15.7.0.1/bills/hcs-bills-service-async.wsdl"
],
"collectionTypes": [
"System.Array",
"System.Collections.Generic.Dictionary`2"
],
"namespaceMappings": [
"*, Hcs.Service.Async.Bills"
],
"targetFramework": "net8.0",
"typeReuseMode": "All"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,16 @@
{
"ExtendedData": {
"inputs": [
"../../Wsdl/wsdl_xsd_v.15.7.0.1/device-metering/hcs-device-metering-service-async.wsdl"
],
"collectionTypes": [
"System.Array",
"System.Collections.Generic.Dictionary`2"
],
"namespaceMappings": [
"*, Hcs.Service.Async.DeviceMetering"
],
"targetFramework": "net8.0",
"typeReuseMode": "All"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,16 @@
{
"ExtendedData": {
"inputs": [
"../../Wsdl/wsdl_xsd_v.15.7.0.1/house-management/hcs-house-management-service-async.wsdl"
],
"collectionTypes": [
"System.Array",
"System.Collections.Generic.Dictionary`2"
],
"namespaceMappings": [
"*, Hcs.Service.Async.HouseManagement"
],
"targetFramework": "net8.0",
"typeReuseMode": "All"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,16 @@
{
"ExtendedData": {
"inputs": [
"../../Wsdl/wsdl_xsd_v.15.7.0.1/nsi/hcs-nsi-service-async.wsdl"
],
"collectionTypes": [
"System.Array",
"System.Collections.Generic.Dictionary`2"
],
"namespaceMappings": [
"*, Hcs.Service.Async.Nsi"
],
"targetFramework": "net8.0",
"typeReuseMode": "All"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,16 @@
{
"ExtendedData": {
"inputs": [
"../../Wsdl/wsdl_xsd_v.15.7.0.1/nsi-common/hcs-nsi-common-service-async.wsdl"
],
"collectionTypes": [
"System.Array",
"System.Collections.Generic.Dictionary`2"
],
"namespaceMappings": [
"*, Hcs.Service.Async.NsiCommon"
],
"targetFramework": "net8.0",
"typeReuseMode": "All"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,16 @@
{
"ExtendedData": {
"inputs": [
"../../Wsdl/wsdl_xsd_v.15.7.0.1/organizations-registry-common/hcs-organizations-registry-common-service-async.wsdl"
],
"collectionTypes": [
"System.Array",
"System.Collections.Generic.Dictionary`2"
],
"namespaceMappings": [
"*, Hcs.Service.Async.OrgRegistryCommon"
],
"targetFramework": "net8.0",
"typeReuseMode": "All"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,16 @@
{
"ExtendedData": {
"inputs": [
"../../Wsdl/wsdl_xsd_v.15.7.0.1/payment/hcs-payment-service-async.wsdl"
],
"collectionTypes": [
"System.Array",
"System.Collections.Generic.Dictionary`2"
],
"namespaceMappings": [
"*, Hcs.Service.Async.Payments"
],
"targetFramework": "net8.0",
"typeReuseMode": "All"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CryptoPro.Security.Cryptography.Xml" Version="2025.7.21" />
<PackageReference Include="System.ServiceModel.Http" Version="8.*" />
<PackageReference Include="System.ServiceModel.NetTcp" Version="8.*" />
<PackageReference Include="System.ServiceModel.Primitives" Version="8.*" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,25 @@
namespace Hcs.Broker.Internal
{
internal static class Constants
{
/// <summary>
/// Имя XML-элемента в сообщении, которое будет подписываться в фильтре
/// отправки подписывающем XML
/// </summary>
internal const string SIGNED_XML_ELEMENT_ID = "signed-data-container";
/// <summary>
/// Если PIN сертификата не указан пользователем, применяется это значение
/// по умолчанию для сертификатов RuToken
/// </summary>
internal const string DEFAULT_CERTIFICATE_PIN = "12345678";
internal const string URI_PPAK = "api.dom.gosuslugi.ru";
internal const string URI_SIT_01 = "sit01.dom.test.gosuslugi.ru:10081";
internal const string URI_SIT_02 = "sit02.dom.test.gosuslugi.ru:10081";
internal const string URI_TUNNEL = "127.0.0.1:8080";
internal const string NAME_SIT = "sit";
internal const string PASSWORD_SIT = "xw{p&&Ee3b9r8?amJv*]";
}
}

View File

@ -0,0 +1,61 @@
using System.Text;
namespace Hcs.Broker.Internal
{
internal static class Util
{
/// <summary>
/// Возвращает список все вложенных исключений для данного исключения
/// </summary>
internal static List<Exception> EnumerateInnerExceptions(Exception e)
{
var list = new List<Exception>();
WalkInnerExceptionsRecurse(e, list);
return list;
}
private static void WalkInnerExceptionsRecurse(Exception e, List<Exception> list)
{
if (e == null || list.Contains(e))
{
return;
}
list.Add(e);
WalkInnerExceptionsRecurse(e.InnerException, list);
if (e is AggregateException)
{
var aggregate = e as AggregateException;
foreach (var inner in aggregate.InnerExceptions)
{
WalkInnerExceptionsRecurse(inner, list);
}
}
}
internal static string FormatDate(DateTime date)
{
return date.ToString("yyyyMMdd");
}
internal static string FormatDate(DateTime? date)
{
return (date == null) ? string.Empty : FormatDate((DateTime)date);
}
/// <summary>
/// Преобразует массиб байтов в строку в формате binhex
/// </summary>
internal static string ConvertToHexString(byte[] ba)
{
var buf = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
{
buf.AppendFormat("{0:x2}", b);
}
return buf.ToString();
}
}
}

Some files were not shown because too many files have changed in this diff Show More