diff --git a/Hcs.Client/Client/Api/BillsApi.cs b/Hcs.Client/Client/Api/BillsApi.cs new file mode 100644 index 0000000..505279c --- /dev/null +++ b/Hcs.Client/Client/Api/BillsApi.cs @@ -0,0 +1,23 @@ +using Hcs.Client.Api.Payload.Bills; +using Hcs.Client.Api.Request.Bills; +using System.Threading; +using System.Threading.Tasks; + +namespace Hcs.Client.Api +{ + // http://open-gkh.ru/BillsServiceAsync/ + public class BillsApi(ClientBase client) : ApiBase(client) + { + /// + /// Импорт сведений о платежных документах + /// + /// Пейлоад сведений о платежных документах + /// Токен отмены + /// true, если операция выполнена успешно, иначе - false + public async Task ImportPaymentDocumentAsync(ImportPaymentDocumentPayload payload, CancellationToken token = default) + { + var request = new ImportPaymentDocumentRequest(client); + return await request.ExecuteAsync(payload, token); + } + } +} diff --git a/Hcs.Client/Client/Api/Payload/Bills/ImportPaymentDocumentPayload.cs b/Hcs.Client/Client/Api/Payload/Bills/ImportPaymentDocumentPayload.cs new file mode 100644 index 0000000..30c47d7 --- /dev/null +++ b/Hcs.Client/Client/Api/Payload/Bills/ImportPaymentDocumentPayload.cs @@ -0,0 +1,229 @@ +using Hcs.Client.Api.Registry; +using Hcs.Client.Api.Type; +using System; +using System.Collections.Generic; + +namespace Hcs.Client.Api.Payload.Bills +{ + // http://open-gkh.ru/Bills/importPaymentDocumentRequest.html + public class ImportPaymentDocumentPayload + { + // http://open-gkh.ru/Bills/importPaymentDocumentRequest/PaymentInformation.html + public class PaymentInformation + { + /// + /// БИК банка получателя + /// + public string bankBIK; + + /// + /// Номер расчетного счета + /// + public string operatingAccountNumber; + } + + /// + /// Начисление по услуге + /// + // http://open-gkh.ru/Bills/PaymentDocumentType/ChargeInfo.html + public interface IChargeInfo { } + + /// + /// Главная коммунальная услуга + /// + // http://open-gkh.ru/Bills/PDServiceChargeType/MunicipalService.html + public class MunicipalService : IChargeInfo + { + /// + /// Необязательное. Перерасчеты, корректировки, руб. + /// + public decimal? moneyRecalculation; + + /// + /// Необязательное. Льготы, субсидии, скидки, руб. + /// + public decimal? moneyDiscount; + + /// + /// Необязательное. Норматив потребления коммунальных ресурсов в целях использования и содержания + /// общего имущества в многоквартирном доме. + /// + public decimal? houseOverallNeedsNorm; + + /// + /// Необязательное. Норматив потребления коммунальных услуг. + /// + public decimal? individualConsumptionNorm; + + /// + /// Необязательное. Текущие показания приборов учёта коммунальных ресурсов - индивидуальных + /// (квартирных). + /// + public decimal? individualConsumptionCurrentValue; + + /// + /// Необязательное. Текущие показания приборов учёта коммунальных ресурсов - коллективных (общедомовых). + /// + public decimal? houseOverallNeedsCurrentValue; + + /// + /// Необязательное. Суммарный объём коммунальных ресурсов в многоквартирном доме - в помещениях дома. + /// + public decimal? houseTotalIndividualConsumption; + + /// + /// Необязательное. Суммарный объём коммунальных ресурсов в многоквартирном доме - в целях содержания + /// общего имущества в многоквартирном доме. + /// + public decimal? houseTotalHouseOverallNeeds; + + /// + /// Необязательное. Способ определения объема коммунальных ресурсов при индивидуальном потреблении. + /// + public MunicipalServiceVolumeDeterminingMethod? individualConsumptionVolumeDeterminingMethod; + + /// + /// Необязательное. Объем/площадь/кол-во коммунальных ресурсов при индивидуальном потреблении. + /// + public decimal? individualConsumptionVolumeValue; + + /// + /// Необязательное. Способ определения объема коммунальных ресурсов при содержании общего имущества. + /// + public MunicipalServiceVolumeDeterminingMethod? overallConsumptionVolumeDeterminingMethod; + + /// + /// Необязательное. Объем/площадь/кол-во коммунальных ресурсов при содержании общего имущества. + /// + public decimal? overallConsumptionVolumeValue; + + /// + /// Необязательное. Размер повышающего коэффициента. + /// + public decimal? multiplyingFactorRatio; + + /// + /// Необязательное. Размер превышения платы, рассчитанной с применением повышающего коэффициента над + /// размером платы, рассчитанной без учета повышающего коэффициента, руб. + /// + public decimal? amountOfExcessFees; + + /// + /// К оплате за индивидуальное потребление коммунальной услуги, руб. + /// + public decimal? municipalServiceIndividualConsumptionPayable; + + /// + /// К оплате за общедомовое потребление коммунальной услуги, руб. + /// + public decimal? municipalServiceCommunalConsumptionPayable; + + /// + /// Необязательное. Размер платы за коммунальные услуги, индивидуальное потребление. + /// + public decimal? amountOfPaymentMunicipalServiceIndividualConsumption; + + /// + /// Необязательное. Размер платы за коммунальные услуги, общедомовые нужды. + /// + public decimal? amountOfPaymentMunicipalServiceCommunalConsumption; + + /// + /// Код услуги из справочника "Вид коммунальной услуги" НСИ 3 + /// + public RegistryElement serviceType; + + /// + /// Тариф/Размер платы на кв.м, руб./Размер взноса на кв.м, руб. + /// + public decimal rate; + + /// + /// К оплате за расчетный период, руб. + /// + public decimal totalPayable; + + /// + /// Необязательное. Начислено за расчетный период (без перерасчетов и льгот), руб. + /// + public decimal? accountingPeriodTotal; + } + + // http://open-gkh.ru/Bills/importPaymentDocumentRequest/PaymentDocument.html + public class PaymentDocument + { + /// + /// Платежный реквизит + /// + public PaymentInformation paymentInformation; + + /// + /// Идентификатор лицевого счета + /// + public string accountGuid; + + /// + /// Необязательное. Номер платежного документа, по которому внесена плата, присвоенный такому + /// документу исполнителем в целях осуществления расчетов по внесению платы + /// + public string paymentDocumentNumber; + + /// + /// Начисления по услугам + /// + public List chargeInfo; + + /// + /// Необязательное. Задолженность за предыдущие периоды, руб. + /// + public decimal? debtPreviousPeriods; + + /// + /// Необязательное. Аванс на начало расчетного периода, руб. + /// + public decimal? advanceBllingPeriod; + + /// + /// Необязательное. Итого к оплате за расчетный период c учетом задолженности/переплаты, руб. + /// (по всему платежному документу) + /// + public decimal? totalPayableByPDWithDebtAndAdvance; + + /// + /// Необязательное. Сумма к оплате за расчетный период, руб. (по всему платежному документу). + /// + public decimal? totalPayableByPD; + + /// + /// Необязательное. Оплачено денежных средств, руб. + /// + public decimal? paidCash; + + /// + /// Необязательное. Дата последней поступившей оплаты + /// + public DateTime? dateOfLastReceivedPayment; + } + + /// + /// Месяц расчетного периода платежного документа + /// + public int month; + + /// + /// Год расчетного периода платежного документа + /// + public short year; + + /// + /// Сведения о платежных реквизитах получателя платежа - бизнес-ключ поиска размещенных платежных + /// реквизитов в ГИС ЖКХ + /// + public PaymentInformation[] paymentInformation; + + /// + /// Размещаемый платежный документ. Максимум 500. + /// + public PaymentDocument[] paymentDocument; + } +} diff --git a/Hcs.Client/Client/Api/Request/Bills/BillsRequestBase.cs b/Hcs.Client/Client/Api/Request/Bills/BillsRequestBase.cs new file mode 100644 index 0000000..3021a34 --- /dev/null +++ b/Hcs.Client/Client/Api/Request/Bills/BillsRequestBase.cs @@ -0,0 +1,55 @@ +using Hcs.Client.Api.Request; +using Hcs.Client.Api.Request.Adapter; +using Hcs.Service.Async.Bills; +using System.Threading.Tasks; + +namespace Hcs.Service.Async.Bills +{ +#pragma warning disable IDE1006 + public partial class getStateResult : IGetStateResultMany { } +#pragma warning restore IDE1006 + + public partial class BillsPortsTypeAsyncClient : IAsyncClient + { + public async Task GetStateAsync(RequestHeader header, IGetStateRequest request) + { + return await getStateAsync(header, (getStateRequest)request); + } + } + +#pragma warning disable IDE1006 + public partial class getStateResponse : IGetStateResponse +#pragma warning restore IDE1006 + { + public IGetStateResult GetStateResult => getStateResult; + } + + public partial class AckRequestAck : IAck { } + + public partial class ErrorMessageType : IErrorMessage { } + +#pragma warning disable IDE1006 + public partial class getStateRequest : IGetStateRequest { } +#pragma warning restore IDE1006 +} + +namespace Hcs.Client.Api.Request.Bills +{ + internal class BillsRequestBase(ClientBase client) : + RequestBase(client) + { + protected override EndPoint EndPoint => EndPoint.BillsAsync; + + protected override bool EnableMinimalResponseWaitDelay => true; + + protected override bool CanBeRestarted => true; + + protected override int RestartTimeoutMinutes => 20; + } +} diff --git a/Hcs.Client/Client/Api/Request/Bills/ImportPaymentDocumentRequest.cs b/Hcs.Client/Client/Api/Request/Bills/ImportPaymentDocumentRequest.cs new file mode 100644 index 0000000..4cd29e2 --- /dev/null +++ b/Hcs.Client/Client/Api/Request/Bills/ImportPaymentDocumentRequest.cs @@ -0,0 +1,274 @@ +using Hcs.Client.Api.Payload.Bills; +using Hcs.Client.Api.Request.Exception; +using Hcs.Client.Api.Type; +using Hcs.Client.Internal; +using Hcs.Service.Async.Bills; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Hcs.Client.Api.Request.Bills +{ + internal class ImportPaymentDocumentRequest(ClientBase client) : BillsRequestBase(client) + { + protected override bool CanBeRestarted => false; + + internal async Task ExecuteAsync(ImportPaymentDocumentPayload 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().ToList().ForEach(error => + { + throw RemoteException.CreateNew(error.ErrorCode, error.Description); + }); + + result.Items.OfType().ToList().ForEach(commonResult => + { + commonResult.Items.OfType().ToList().ForEach(error => + { + throw RemoteException.CreateNew(error.ErrorCode, error.Description); + }); + }); + + return true; + } + + private importPaymentDocumentRequest GetRequestFromPayload(ImportPaymentDocumentPayload payload) + { + var items = new List + { + payload.month, + payload.year + }; + + var paymentInformations = new Dictionary(); + 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(); + foreach (var subEntry in entry.chargeInfo) + { + if (subEntry is ImportPaymentDocumentPayload.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(); + 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] + }; + + 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] + }; + } + } +} diff --git a/Hcs.Client/Client/Api/Type/MunicipalServiceVolumeDeterminingMethod.cs b/Hcs.Client/Client/Api/Type/MunicipalServiceVolumeDeterminingMethod.cs new file mode 100644 index 0000000..cddc7f9 --- /dev/null +++ b/Hcs.Client/Client/Api/Type/MunicipalServiceVolumeDeterminingMethod.cs @@ -0,0 +1,33 @@ +using Hcs.Service.Async.Bills; +using System; + +namespace Hcs.Client.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"); + } + } +} diff --git a/Hcs.Client/Client/UniClient.cs b/Hcs.Client/Client/UniClient.cs index 189fde9..cf23e38 100644 --- a/Hcs.Client/Client/UniClient.cs +++ b/Hcs.Client/Client/UniClient.cs @@ -11,6 +11,8 @@ namespace Hcs.Client /// public class UniClient : ClientBase { + public BillsApi Bills => new(this); + public DeviceMeteringApi DeviceMetering => new(this); public HouseManagementApi HouseManagement => new(this); diff --git a/Hcs.Client/Hcs.Client.csproj b/Hcs.Client/Hcs.Client.csproj index c0b9ee2..a55cb9f 100644 --- a/Hcs.Client/Hcs.Client.csproj +++ b/Hcs.Client/Hcs.Client.csproj @@ -65,11 +65,13 @@ + + @@ -90,6 +92,8 @@ + + @@ -125,6 +129,7 @@ + @@ -1121,6 +1126,7 @@ +