diff --git a/docs/index.rst b/docs/index.rst index 8734618..0953f02 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -6,7 +6,7 @@ Welcome to modulbank's documentation! ===================================== -.. include:: ../README +.. include:: ../README.rst .. toctree:: :maxdepth: 4 diff --git a/modulbank/client.py b/modulbank/client.py index ac0d32b..f7efeac 100644 --- a/modulbank/client.py +++ b/modulbank/client.py @@ -101,11 +101,12 @@ class PaymentResponse: незагруженным платёжным поручениям при их наличии """ - def __init__(self, obj: {}): + def __init__(self, obj: dict, document: str = None): """ Конструктор - :param obj: JSON-объект ответа на импорт платежек в API МодульБанка. + :param dict obj: JSON-объект ответа на импорт платежек в API МодульБанка. + :param str document: Платёжное поручение в формате 1CClientBankExchange """ if 'totalLoaded' in obj: self.__total_loaded = obj['totalLoaded'] @@ -113,6 +114,7 @@ def __init__(self, obj: {}): self.__errors = obj['errors'] else: self.__errors = [] + self.__document = document @property def total_loaded(self) -> int: @@ -134,6 +136,16 @@ def errors(self) -> list: """ return self.__errors + @property + def document(self) -> str: + """ + Платёжное поручение в формате 1CClientBankExchange + + :return: Документ, отправленный в API МодульБанка + :rtype: str + """ + return self.__document + class ModulbankClient: """ @@ -285,7 +297,6 @@ def create_payment_draft(self, order: PaymentOrder) -> PaymentResponse: :raises UnexpectedResponseBodyModulbankException: Если не удалось обработать полученные данные. """ exchange = ClientBankExchange() - # noinspection PyTypeChecker self.__fill_client_bank_exchange(order, exchange) r = requests.post(self._api_url + 'operation-upload/1c', json={"document": exchange.document}, headers=self.__headers) @@ -294,37 +305,42 @@ def create_payment_draft(self, order: PaymentOrder) -> PaymentResponse: if r.status_code != 200: raise UnexpectedResponseStatusModulbankException(r.status_code) try: - res = PaymentResponse(r.json()) + res = PaymentResponse(r.json(), document=exchange.document) except ValueError: raise UnexpectedResponseBodyModulbankException(r.text) return res @staticmethod - def __fill_client_bank_exchange(order, exchange): + def __fill_client_bank_exchange(order: PaymentOrder, exchange: ClientBankExchange) -> None: """ Заполнение полей платежного поручения :param PaymentOrder order: Объект платёжного поручения :param ClientBankExchange exchange: Объект обмена данными в формате 1С + :return: None + :rtype: None """ exchange.УсловияОтбора.РасчСчет = order.account_num exchange.СекцияПлатежногоДокумента.Номер = order.doc_num exchange.СекцияПлатежногоДокумента.Дата = order.date exchange.СекцияПлатежногоДокумента.Сумма = order.amount exchange.СекцияПлатежногоДокумента.НазначениеПлатежа = order.purpose + exchange.СекцияПлатежногоДокумента.НазначениеПлатежа1 = order.purpose - exchange.СекцияПлатежногоДокумента.Плательщик1 = order.payer.name + exchange.СекцияПлатежногоДокумента.Плательщик = "%s %s" % (order.payer.inn, order.payer.name) exchange.СекцияПлатежногоДокумента.ПлательщикИНН = order.payer.inn exchange.СекцияПлатежногоДокумента.ПлательщикКПП = order.payer.kpp exchange.СекцияПлатежногоДокумента.ПлательщикСчет = order.payer.bank.account + exchange.СекцияПлатежногоДокумента.ПлательщикРасчСчет = order.payer.bank.account exchange.СекцияПлатежногоДокумента.ПлательщикБанк1 = order.payer.bank.name exchange.СекцияПлатежногоДокумента.ПлательщикБИК = order.payer.bank.bic exchange.СекцияПлатежногоДокумента.ПлательщикКорсчет = order.payer.bank.corr_acc - exchange.СекцияПлатежногоДокумента.Получатель1 = order.recipient.name + exchange.СекцияПлатежногоДокумента.Получатель = order.recipient.name exchange.СекцияПлатежногоДокумента.ПолучательИНН = order.recipient.inn exchange.СекцияПлатежногоДокумента.ПолучательКПП = order.recipient.kpp exchange.СекцияПлатежногоДокумента.ПолучательСчет = order.recipient.bank.account + exchange.СекцияПлатежногоДокумента.ПолучательРасчСчет = order.recipient.bank.account exchange.СекцияПлатежногоДокумента.ПолучательБанк1 = order.recipient.bank.name exchange.СекцияПлатежногоДокумента.ПолучательБИК = order.recipient.bank.bic exchange.СекцияПлатежногоДокумента.ПолучательКорсчет = order.recipient.bank.corr_acc diff --git a/modulbank/client_bank_exchange.py b/modulbank/client_bank_exchange.py index 6c05275..e115430 100644 --- a/modulbank/client_bank_exchange.py +++ b/modulbank/client_bank_exchange.py @@ -40,14 +40,14 @@ def document(self) -> str: return s @staticmethod - def __format_value(value): + def __format_value(value) -> str: if isinstance(value, datetime.date): - value = value.strftime('%d.%m.%Y') + return value.strftime('%d.%m.%Y') if isinstance(value, datetime.time): - value = value.strftime('%H:%M:%S') + return value.strftime('%H:%M:%S') if isinstance(value, Decimal): - value = str(value.quantize(Decimal('.01'), rounding=ROUND_HALF_DOWN)) - return value + return str(value.quantize(Decimal('.01'), rounding=ROUND_HALF_DOWN)) + return '' class GeneralSection(BaseSection): @@ -57,19 +57,14 @@ class GeneralSection(BaseSection): _fields = ['ВерсияФормата', 'Кодировка', 'Отправитель', 'Получатель', 'ДатаСоздания', 'ВремяСоздания'] _mandatory_fields = ['ВерсияФормата', 'Кодировка', 'Отправитель'] - def __init__(self, encoding='windows-1251'): + def __init__(self): BaseSection.__init__(self) for name in self._fields: self.__dict__[name] = None self.__dict__['ВерсияФормата'] = '1.02' - if encoding == 'windows-1251': - self.__dict__['Кодировка'] = 'Windows' - elif encoding == 'utf-8': - self.__dict__['Кодировка'] = 'UTF-8' - elif encoding == 'cp-866': - self.__dict__['Кодировка'] = 'DOS' + self.__dict__['Кодировка'] = 'Windows' self.__dict__['Отправитель'] = 'modulbank_python' self.__dict__['ДатаСоздания'] = datetime.date.today() self.__dict__['ВремяСоздания'] = datetime.datetime.now().time() @@ -117,19 +112,18 @@ class DocumentSection(BaseSection): 'ПлательщикКорсчет', 'ПолучательСчет', 'ДатаПоступило', 'Получатель', 'ПолучательИНН', 'Получатель1', 'Получатель2', 'Получатель3', 'Получатель4', 'ПолучательРасчСчет', 'ПолучательБанк1', 'ПолучательБанк2', 'ПолучательБИК', 'ПолучательКорсчет', 'ВидПлатежа', 'ВидОплаты', 'Код', 'НазначениеПлатежа', - 'НазначениеПлатежа 1', 'НазначениеПлатежа 2', 'НазначениеПлатежа 3', 'НазначениеПлатежа 4', - 'НазначениеПлатежа 5', 'НазначениеПлатежа 6', 'СтатусСоставителя', 'ПлательщикКПП', 'ПолучательКПП', + 'НазначениеПлатежа1', 'НазначениеПлатежа2', 'НазначениеПлатежа3', 'НазначениеПлатежа4', + 'НазначениеПлатежа5', 'НазначениеПлатежа6', 'СтатусСоставителя', 'ПлательщикКПП', 'ПолучательКПП', 'ПоказательКБК', 'ОКАТО', 'ПоказательОснования', 'ПоказательПериода', 'ПоказательНомера', 'ПоказательДаты', 'ПоказательТипа', 'Очередность', 'СрокАкцепта', 'ВидАккредитива', 'СрокПлатежа', 'УсловиеОплаты1', 'УсловиеОплаты2', 'УсловиеОплаты3', 'ПлатежПоПредст', 'ДополнУсловия', 'НомерСчетаПоставщика', 'ДатаОтсылкиДок'] _mandatory_fields = ['Номер', 'Дата', 'Сумма', 'ПлательщикСчет', 'Плательщик', 'ПлательщикИНН', 'Плательщик1', - 'ПлательщикРасчСчет', 'ПлательщикБанк1', 'ПлательщикБанк2', 'ПлательщикБИК', - 'ПлательщикКорсчет', 'ПолучательСчет', 'Получатель', 'ПолучательИНН', 'Получатель1', - 'ПолучательРасчСчет', 'ПолучательБанк1', 'ПолучательБанк2', 'ПолучательБИК', - 'ПолучательКорсчет', 'ВидОплаты', 'СтатусСоставителя', 'ПлательщикКПП', 'ПолучательКПП', - 'ПоказательКБК', 'ОКАТО', 'ПоказательОснования', 'ПоказательПериода', 'ПоказательНомера', - 'ПоказательДаты'] + 'ПлательщикРасчСчет', 'ПлательщикБанк1', 'ПлательщикБИК', 'ПлательщикКорсчет', + 'ПолучательСчет', 'Получатель', 'ПолучательИНН', 'Получатель1', 'ПолучательРасчСчет', + 'ПолучательБанк1', 'ПолучательБИК', 'ПолучательКорсчет', 'ВидОплаты', 'ВидПлатежа', + 'СтатусСоставителя', 'ПлательщикКПП', 'ПолучательКПП', 'ПоказательКБК', 'ОКАТО', + 'ПоказательОснования', 'ПоказательПериода', 'ПоказательНомера', 'ПоказательДаты'] def __init__(self): BaseSection.__init__(self) diff --git a/modulbank/structs.py b/modulbank/structs.py index 9fdb7c5..ac5f1c2 100644 --- a/modulbank/structs.py +++ b/modulbank/structs.py @@ -18,11 +18,11 @@ class Company: Компания, в которой состоит пользователь МодульБанка. """ - def __init__(self, obj: {}): + def __init__(self, obj: dict): """ Конструктор - :param obj: JSON-объект компании из API МодульБанка. + :param dict obj: JSON-объект компании из API МодульБанка. """ if 'companyId' in obj: self.__company_id = obj['companyId'] @@ -70,11 +70,11 @@ class Bank: Реквизиты банка (из платежных реквизитов организации). """ - def __init__(self, obj: {}): + def __init__(self, obj: dict): """ Конструктор - :param obj: JSON-объект банковского счёта (!) из API МодульБанка. + :param dict obj: JSON-объект банковского счёта (!) из API МодульБанка. """ if 'bankBic' in obj: self.__bic = obj['bankBic'] @@ -146,11 +146,11 @@ class BankAccount: Счёт компании-пользователя МодульБанка. """ - def __init__(self, obj: {}): + def __init__(self, obj: dict): """ Конструктор - :param obj: JSON-объект банковского счёта из API МодульБанка. + :param dict obj: JSON-объект банковского счёта из API МодульБанка. """ if 'id' in obj: self.__account_id = obj['id'] @@ -314,14 +314,14 @@ class BankShort: Номер счета, Название банка, БИК и корр. счёт. """ - def __init__(self, account=None, name=None, bic=None, corr_acc=None): + def __init__(self, account: str = None, name: str = None, bic: str = None, corr_acc: str = None): """ Конструктор - :param account: Номер счета - :param name: Название банка - :param bic: БИК - :param corr_acc: Корр. счёт + :param str account: Номер счета + :param str name: Название банка + :param str bic: БИК + :param str corr_acc: Корр. счёт """ self.__account = account self.__name = name @@ -379,15 +379,15 @@ class Contractor: Контрагент в операции. """ - def __init__(self, obj: dict = None, name=None, inn=None, kpp=None, bank=None): + def __init__(self, obj: dict = None, name: str = None, inn: str = None, kpp: str = None, bank: BankShort = None): """ Конструктор - :param obj: JSON-объект операции по счёту из API МодульБанка. - :param name: - :param inn: - :param kpp: - :param bank: + :param dict obj: JSON-объект операции по счёту из API МодульБанка. + :param str name: + :param str inn: + :param str kpp: + :param BankShort bank: """ if obj: if 'contragentName' in obj: @@ -437,7 +437,7 @@ def kpp(self) -> str: :return: КПП контрагента :rtype: str """ - return self.__kpp or '0' + return self.__kpp @property def bank(self) -> BankShort: @@ -455,11 +455,11 @@ class BudgetaryAndTax: Параметры бюджетных и налоговых платежей в операции. """ - def __init__(self, obj: {}): + def __init__(self, obj: dict): """ Конструктор - :param obj: JSON-объект операции по счёту из API МодульБанка. + :param dict obj: JSON-объект операции по счёту из API МодульБанка. """ if 'kbk' in obj: self.__kbk = obj['kbk'] @@ -576,11 +576,11 @@ class Operation: Операция по счёту """ - def __init__(self, obj: {}): + def __init__(self, obj: dict): """ Конструктор - :param obj: JSON-объект операции по счёту из API МодульБанка. + :param dict obj: JSON-объект операции по счёту из API МодульБанка. """ if 'id' in obj: self.__operation_id = obj['id'] @@ -625,7 +625,10 @@ def __init__(self, obj: {}): try: self.__created = datetime.datetime.strptime(obj['created'], '%Y-%m-%dT%H:%M:%S') except ValueError: - raise UnexpectedValueModulbankException('Created %s as datetime.datetime' % obj['created']) + try: + self.__created = datetime.datetime.strptime(obj['created'], '%Y-%m-%dT%H:%M:%S.%f') + except ValueError: + raise UnexpectedValueModulbankException('Created %s as datetime.datetime' % obj['created']) if 'docNumber' in obj: self.__doc_number = obj['docNumber'] if 'contragentName' in obj or 'contragentInn' in obj or 'contragentKpp' in obj \ @@ -818,15 +821,15 @@ def __init__(self, doc_num: str, account_num: str, amount: Decimal, purpose: str """ Конструктор - :param doc_num: Номер документа - :param account_num: Расчетный счет организации - :param amount: Сумма платежа - :param purpose: Назначение платежа одной строкой - :param payer: Плательщик :class:`Contractor` и его реквизиты - :param recipient: Получатель :class:`Contractor` и его реквизиты - :param payment_type: (опционально) Вид оплаты (вид операции). Для платежных поручений всегда 01 - :param priority: (опционально) Очередность платежа. Для обычных операций всегда 5 - :param date: (опционально) Дата списания средств с р/c. По умолчанию сегодняшнее число + :param str doc_num: Номер документа + :param str account_num: Расчетный счет организации + :param Decimal amount: Сумма платежа + :param str purpose: Назначение платежа одной строкой + :param Contractor payer: Плательщик :class:`Contractor` и его реквизиты + :param Contractor recipient: Получатель :class:`Contractor` и его реквизиты + :param str payment_type: (опционально) Вид оплаты (вид операции). Для платежных поручений всегда 01 + :param str priority: (опционально) Очередность платежа. Для обычных операций всегда 5 + :param datetime.date date: (опционально) Дата списания средств с р/c. По умолчанию сегодняшнее число """ self.__doc_num = doc_num self.__account_num = account_num @@ -938,7 +941,7 @@ def __init__(self, obj: dict = None): """ Конструктор - :param obj: JSON-объект уведомления о произошедшей транзакции из API МодульБанка. + :param dict obj: JSON-объект уведомления о произошедшей транзакции из API МодульБанка. """ self.__inn = 'companyInn' in obj and obj['companyInn'] or '' self.__kpp = 'contragentKpp' in obj and obj['contragentKpp'] or '' diff --git a/modulbank/version.py b/modulbank/version.py index ffcc925..156d6f9 100644 --- a/modulbank/version.py +++ b/modulbank/version.py @@ -1 +1 @@ -__version__ = '0.0.3' +__version__ = '0.0.4' diff --git a/tests/test_client.py b/tests/test_client.py index 3299d4b..236b2de 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -74,7 +74,7 @@ def test_operation(client: ModulbankClient): m.post("https://api.modulbank.ru/v1/operation-history/{id}".format(id=account_id), json=json_from_file('operations.json'), headers={'Content-Type': 'application/json; charset=utf-8'}) - res = client.operations(account_id, SearchOptions()) + res = client.operations(account_id) assert isinstance(res, list) assert len(res) > 0 assert isinstance(res[0], Operation) @@ -137,6 +137,20 @@ def test_operation_page(): assert len(res) == 5 +# noinspection PyShadowingNames +def test_operation_wo_page(client: ModulbankClient): + account_id = '58c20343-5d3b-422c-b98b-a5ec037df782' + with requests_mock.Mocker() as m: + m.post("https://api.modulbank.ru/v1/operation-history/{id}".format(id=account_id), + json=json_from_file('operations_from.json'), + headers={'Content-Type': 'application/json; charset=utf-8'}) + # noinspection PyTypeChecker + res = client.operations(account_id, SearchOptions(page=None)) + assert isinstance(res, list) + assert len(res) > 0 + assert isinstance(res[0], Operation) + + # noinspection PyShadowingNames def test_operation_category(client: ModulbankClient): account_id = '58c20343-5d3b-422c-b98b-a5ec037df782' @@ -177,6 +191,8 @@ def test_create_payment_draft(client): assert len(res.errors) == 0 assert isinstance(res.total_loaded, int) assert res.total_loaded == 1 + assert isinstance(res.document, str) + assert len(res.document) > 100 def test_company_str():