diff --git a/Makefile b/Makefile index e8c44e37..8df285bd 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,14 @@ pip.install: - pip install -r requirements-dev.txt + pip install -U -r requirements-dev.txt pip.install.build: - pip install -r requirements-build.txt + pip install -U -r requirements-build.txt config.env: cp .env.sample .env test: - python -m unittest + python -m unittest $(args) fmt: black . diff --git a/examples/data/add_transaction_invoice.json b/examples/data/add_transaction_invoice.json index 958ea98a..294b63bb 100644 --- a/examples/data/add_transaction_invoice.json +++ b/examples/data/add_transaction_invoice.json @@ -1,9 +1,9 @@ { - "id": "85b71fbeaa1346169a634e4af5bb86cd", + "id": "9d4f1054607447798e9ce61e2a42d948", "resource": "transaction", "status": "pending", - "amount": "10.00", - "original_amount": "10.00", + "amount": "30.00", + "original_amount": "30.00", "currency": "BRL", "description": "meu boleto gerado para teste", "payment_type": "boleto", @@ -15,52 +15,57 @@ "discounts": null, "pre_authorization": null, "sales_receipt": null, - "on_behalf_of": "27e17b778b404a83bf8e25ec995e2ffe", - "customer": "e7eec0f640c14e21b35d20d58b49b584", - "statement_descriptor": "IMOBANCO BANCO DE COBRANCA IMOBILIARIA LTDA - EPP", + "on_behalf_of": "0e084bb6a60f47e8ac45949d5040eb92", + "customer": "ffe4b7a1f19c4a9da85b6d72c0b6201c", + "statement_descriptor": "Mateus Satiro", "payment_method": { - "id": "38a2932a929444a699a1a5b97ea0d2fb", + "id": "e77a3d54fc5d4dd9a4741bf6dc8b3c37", "zoop_boleto_id": null, "resource": "boleto", "description": "meu boleto gerado para teste", - "reference_number": "3360906", - "document_number": "3360906", - "expiration_date": "2020-06-20T00:00:00+00:00", - "payment_limit_date": "2020-06-30T00:00:00+00:00", - "recipient": "IMOBANCO BANCO DE COBRANCA IMOBILIARIA LTDA - EPP", + "reference_number": "6199780", + "document_number": "6199780", + "expiration_date": "2020-11-20T00:00:00+00:00", + "payment_limit_date": "2020-11-30T00:00:00+00:00", + "recipient": "Mateus Satiro", "bank_code": "109", - "customer": "e7eec0f640c14e21b35d20d58b49b584", + "customer": "ffe4b7a1f19c4a9da85b6d72c0b6201c", "address": null, - "sequence": "63864", - "url": "https://api-boleto-production.s3.amazonaws.com/foo/27e17b778b404a83bf8e25ec995e2ffe/5e8cfef73d502008309b4779.html", + "sequence": "93211", + "url": "https://api-boleto-production.s3.amazonaws.com/foo/0e084bb6a60f47e8ac45949d5040eb92/5f5a5e000d93d708cff3f646.html", "accepted": false, "printed": false, "downloaded": false, "fingerprint": null, "paid_at": null, - "uri": "/v1/marketplaces/foo/boletos/38a2932a929444a699a1a5b97ea0d2fb", - "barcode": "34191090323609068893231339210002482920000001000", + "uri": "/v1/marketplaces/foo/boletos/e77a3d54fc5d4dd9a4741bf6dc8b3c37", + "barcode": "34191090651997807893931339210002184450000003000", "metadata": {}, - "created_at": "2020-04-07T19:30:15+00:00", - "updated_at": "2020-04-07T22:30:15+00:00", + "created_at": "2020-09-10T14:10:24+00:00", + "updated_at": "2020-09-10T17:10:24+00:00", "status": "not_paid", "billing_instructions": { - "late_fee": { - "mode": "PERCENTAGE", - "start_date": "2020-06-20", - "percentage": "50" - }, "discount": [ { "mode": "FIXED", - "limit_date": "2020-06-20", + "limit_date": "2020-11-10", + "amount": "200" + }, + { + "mode": "FIXED", + "limit_date": "2020-11-20", "amount": "100" } ], + "late_fee": { + "mode": "FIXED", + "start_date": null, + "amount": "300" + }, "interest": { "mode": "MONTHLY_PERCENTAGE", - "start_date": "2020-06-20", - "percentage": "50.5556" + "start_date": null, + "percentage": "1" } } }, @@ -77,17 +82,17 @@ "fee_details": null, "location_latitude": null, "location_longitude": null, - "uri": "/v1/marketplaces/foo/transactions/85b71fbeaa1346169a634e4af5bb86cd", + "uri": "/v1/marketplaces/foo/transactions/9d4f1054607447798e9ce61e2a42d948", "metadata": {}, "expected_on": "1970-01-01T00:00:00+00:00", - "created_at": "2020-04-07T22:30:14+00:00", - "updated_at": "2020-04-07T22:30:15+00:00", + "created_at": "2020-09-10T17:10:23+00:00", + "updated_at": "2020-09-10T17:10:24+00:00", "payment_authorization": null, "history": [ { - "id": "ebd31542e48b44a596fe0cabdaa44f78", - "transaction": "85b71fbeaa1346169a634e4af5bb86cd", - "amount": "10.00", + "id": "a2002ebd3e444ce2bd0ab8b0373af0bc", + "transaction": "9d4f1054607447798e9ce61e2a42d948", + "amount": "30.00", "operation_type": "created", "status": "succeeded", "response_code": null, @@ -97,7 +102,7 @@ "authorization_nsu": null, "gatewayResponseTime": null, "authorizer": null, - "created_at": "2020-04-07 22:30:14" + "created_at": "2020-09-10 17:10:23" } ] } \ No newline at end of file diff --git a/examples/data/retrieve_buyer.json b/examples/data/retrieve_buyer.json index 7596f3bf..efb43423 100644 --- a/examples/data/retrieve_buyer.json +++ b/examples/data/retrieve_buyer.json @@ -19,7 +19,7 @@ "neighborhood": "fooofoo", "city": "Natal", "state": "RN", - "postal_code": "59100000", + "postal_code": "59150000", "country_code": "BR" }, "delinquent": false, @@ -27,7 +27,7 @@ "default_debit": null, "default_credit": null, "default_receipt_delivery_method": null, - "uri": "/v1/marketplaces/foo/buyers/ffe4b7a1f19c4a9da85b6d72c0b6201c", + "uri": "/v1/marketplaces/d77c2258b51d49269191502695f939f4/buyers/ffe4b7a1f19c4a9da85b6d72c0b6201c", "metadata": {}, "created_at": "2020-05-04T19:52:02+00:00", "updated_at": "2020-05-04T19:52:02+00:00" diff --git a/examples/data/retrieve_seller.json b/examples/data/retrieve_seller.json index f3031ea9..5014411e 100644 --- a/examples/data/retrieve_seller.json +++ b/examples/data/retrieve_seller.json @@ -1,28 +1,28 @@ { "id": "0e084bb6a60f47e8ac45949d5040eb92", - "status": "pending", + "status": "active", "resource": "seller", "type": "individual", "account_balance": "0.00", "current_balance": "0.00", - "first_name": "foo", - "last_name": "foo", - "email": "foo@bar.com", + "first_name": "Mateus", + "last_name": "Satiro", + "email": "mateus@teste.com.br", "description": null, "taxpayer_id": "13543402480", - "phone_number": "+55 84 99999-9999", + "phone_number": "84959595559", "birthdate": "1994-12-27", "website": null, "facebook": null, "twitter": null, "address": { - "line1": "foo", + "line1": "Rua Jundia\u00ed", "line2": "123", - "line3": "barbar", - "neighborhood": "fooofoo", + "line3": "apartamento 200", + "neighborhood": "Tirol", "city": "Natal", "state": "RN", - "postal_code": "59100000", + "postal_code": "59020120", "country_code": "BR" }, "statement_descriptor": null, @@ -33,13 +33,18 @@ "decline_on_fail_zipcode": false, "delinquent": false, "payment_methods": null, - "default_debit": null, + "default_debit": "4abf4010cc93414ca585463fdc7b44d6", "default_credit": null, - "merchant_code": null, - "terminal_code": null, - "uri": "/v1/marketplaces/foo/sellers/individuals/0e084bb6a60f47e8ac45949d5040eb92", - "marketplace_id": "foo", - "metadata": {}, + "merchant_code": "GT0000CC", + "terminal_code": "012000021220006", + "uri": "/v1/marketplaces/d77c2258b51d49269191502695f939f4/sellers/individuals/0e084bb6a60f47e8ac45949d5040eb92", + "marketplace_id": "d77c2258b51d49269191502695f939f4", + "metadata": { + "service": "imopay", + "id": "dc12498a-7e81-4fdc-8028-55ab6cc2f92b", + "imopay_id": "31ab5f46-2343-4389-a29a-733ed29da66d" + }, "created_at": "2020-04-27T19:35:55+00:00", - "updated_at": "2020-04-27T19:35:55+00:00" + "updated_at": "2020-06-08T18:50:16+00:00", + "payout_option": "CP" } \ No newline at end of file diff --git a/examples/transaction/add_transaction_invoice.py b/examples/transaction/add_transaction_invoice.py index 0710f2ea..1c8dd99e 100644 --- a/examples/transaction/add_transaction_invoice.py +++ b/examples/transaction/add_transaction_invoice.py @@ -1,7 +1,9 @@ import os from zoop_wrapper import ( - BillingConfiguration, + Fine, + Interest, + Discount, BillingInstructions, Invoice, Transaction, @@ -24,9 +26,10 @@ buyer_or_seller_id = buyer_id -quantia_em_centavos = "1000" -vencimento = "2020-08-20" -limite = "2020-08-30" +quantia_em_centavos = "3000" +vencimento = "2020-11-20" +pre_vencimento = "2020-11-10" +limite = "2020-11-30" t = Transaction( amount=quantia_em_centavos, @@ -39,22 +42,21 @@ expiration_date=vencimento, payment_limit_date=limite, billing_instructions=BillingInstructions( - late_fee=BillingConfiguration( - mode=BillingConfiguration.PERCENTAGE_MODE, - percentage=50, - start_date=vencimento, + late_fee=Fine( + mode=Fine.PERCENTAGE, + percentage=2, ), - interest=BillingConfiguration( - mode=BillingConfiguration.MONTHLY_PERCENTAGE_MODE, - percentage=50.5555555555, - start_date=vencimento, - ), - discount=BillingConfiguration( - amount=100, - is_discount=True, - limit_date=vencimento, - mode=BillingConfiguration.FIXED_MODE, + interest=Interest( + mode=Interest.MONTHLY_PERCENTAGE, + percentage=1, ), + discount=[ + Discount( + amount=200, + limit_date=pre_vencimento, + mode=Discount.FIXED, + ), + ], ), ), ) diff --git a/tests/factories/invoice.py b/tests/factories/invoice.py index 3a785521..1ec03eaa 100644 --- a/tests/factories/invoice.py +++ b/tests/factories/invoice.py @@ -2,49 +2,76 @@ from factory.faker import Faker from zoop_wrapper.models.invoice import ( - BillingConfiguration, + BaseModeObject, BillingInstructions, + Discount, + Fine, + Interest, Invoice, ) from tests.factories.base import ZoopObjectFactory, PaymentMethodFactory -class BillingConfigurationFactory(ZoopObjectFactory): +class BaseModeObjectFactory(ZoopObjectFactory): class Meta: - model = BillingConfiguration + model = BaseModeObject mode = None - is_discount = None -class FeeFactory(BillingConfigurationFactory): - is_discount = False - start_date = Faker("date_this_month") +class FixedBaseModeObjectFactory(BaseModeObjectFactory): + amount = Faker("pyfloat", positive=True, max_value=99) -class DiscountFactory(BillingConfigurationFactory): - is_discount = True - limit_date = Faker("date_this_month") +class PercentageBaseModeObjectFactory(BaseModeObject): + percentage = Faker("pyfloat", positive=True, max_value=99) -class PercentFeeFactory(FeeFactory): - mode = Faker("random_element", elements=BillingConfiguration.PERCENT_MODES) - percentage = Faker("pyfloat", positive=True, max_value=99) +class FixedFineFactory(FixedBaseModeObjectFactory): + class Meta: + model = Fine + mode = "FIXED" -class FixedFeeFactory(FeeFactory): - mode = BillingConfiguration.FIXED_MODE - amount = Faker("pyfloat", positive=True, max_value=99) +class PercentageFineFactory(PercentageBaseModeObjectFactory): + class Meta: + model = Fine -class PercentDiscountFactory(DiscountFactory): - mode = Faker("random_element", elements=BillingConfiguration.PERCENT_MODES) - percentage = Faker("pyfloat", positive=True, max_value=99) + mode = "PERCENTAGE" -class FixedDiscountFactory(DiscountFactory): - mode = BillingConfiguration.FIXED_MODE - amount = Faker("pyfloat", positive=True, max_value=99) +class FixedInterestFactory(FixedBaseModeObjectFactory): + class Meta: + model = Interest + + mode = "DAILY_AMOUNT" + + +class PercentageInterestFactory(PercentageBaseModeObjectFactory): + class Meta: + model = Interest + + mode = Faker( + "random_element", + elements=[Interest.DAILY_PERCENTAGE, Interest.MONTHLY_PERCENTAGE], + ) + + +class FixedDiscountFactory(FixedBaseModeObjectFactory): + class Meta: + model = Discount + + mode = "FIXED" + limit_date = Faker("date_this_month") + + +class PercentageDiscountFactory(PercentageBaseModeObjectFactory): + class Meta: + model = Discount + + mode = "PERCENTAGE" + limit_date = Faker("date_this_month") class BillingInstructionsFactory(ZoopObjectFactory): @@ -52,8 +79,8 @@ class Meta: model = BillingInstructions discount = SubFactory(FixedDiscountFactory) - interest = SubFactory(PercentFeeFactory) - late_fee = SubFactory(FixedFeeFactory) + interest = SubFactory(FixedInterestFactory) + late_fee = SubFactory(FixedFineFactory) class InvoiceFactory(PaymentMethodFactory): diff --git a/tests/models/base/test_zoop_object.py b/tests/models/base/test_zoop_object.py index cf5aef4e..7682bc03 100644 --- a/tests/models/base/test_zoop_object.py +++ b/tests/models/base/test_zoop_object.py @@ -8,7 +8,9 @@ class ZoopObjectTestCase(SetTestCase): def setUp(self) -> None: - self.patcher_fields = patch("zoop_wrapper.models.base.ZoopObject.get_fields") + self.patcher_all_fields = patch( + "zoop_wrapper.models.base.ZoopObject.get_all_fields" + ) self.patcher_required_fields = patch( "zoop_wrapper.models.base.ZoopObject.get_required_fields" ) @@ -19,14 +21,14 @@ def setUp(self) -> None: "zoop_wrapper.models.base.ZoopObject.get_original_different_fields_mapping" ) - self.mocked_fields = self.patcher_fields.start() + self.mocked_fields = self.patcher_all_fields.start() self.mocked_required_fields = self.patcher_required_fields.start() self.mocked_non_required_fields = self.patcher_non_required_fields.start() self.mocked_get_original_different_fields_mapping = ( self.patcher_get_original_different_fields_mapping.start() ) - self.addCleanup(self.patcher_fields.stop) + self.addCleanup(self.patcher_all_fields.stop) self.addCleanup(self.patcher_required_fields.stop) self.addCleanup(self.patcher_non_required_fields.stop) self.addCleanup(self.patcher_get_original_different_fields_mapping.stop) @@ -242,13 +244,13 @@ def test_to_dict(self): self.assertEqual(expected, result) - @staticmethod - def test_get_all_fields(): - mocked_get_fields = MagicMock() - instance = MagicMock(_allow_empty=False, get_fields=mocked_get_fields) + def test_get_all_fields(self): + instance = MagicMock() - ZoopObject.get_all_fields(instance) - mocked_get_fields.assert_called_once() + expected = {"id", "name", "modificado"} + + result = ZoopObject.get_all_fields(instance) + self.assertEqual(result, expected) @staticmethod def test_get_validation_fields(): @@ -260,9 +262,6 @@ def test_get_validation_fields(): ZoopObject.get_validation_fields(instance) mocked_required_fields.assert_called_once() - def test_get_fields(self): - self.assertEqual({"id", "name", "modificado"}, ZoopObject.get_fields()) - def test_get_required_fields(self): self.assertEqual({"id"}, ZoopObject.get_required_fields()) diff --git a/tests/models/invoice/test_base_mode.py b/tests/models/invoice/test_base_mode.py new file mode 100644 index 00000000..da41446e --- /dev/null +++ b/tests/models/invoice/test_base_mode.py @@ -0,0 +1,104 @@ +from unittest.mock import MagicMock + +from tests.utils import SetTestCase +from zoop_wrapper.exceptions import ValidationError +from zoop_wrapper.models.invoice import BaseModeObject + + +class BaseModeObjectTestCase(SetTestCase): + def test_modes(self): + modes = set() + + self.assertSetEqual(BaseModeObject.MODES, modes) + + def test_required_fields(self): + expected = {"mode"} + + result = BaseModeObject.get_required_fields() + + self.assertSetEqual(result, expected) + + def test_fixed_required_fields(self): + expected = {"amount", "mode"} + + result = BaseModeObject.get_fixed_required_fields() + + self.assertSetEqual(result, expected) + + def test_percentage_required_fields(self): + expected = {"percentage", "mode"} + + result = BaseModeObject.get_percentage_required_fields() + + self.assertSetEqual(result, expected) + + def test_init_custom_fields_1(self): + """ + Dado que: + - existe um objeto instance com mode=None e MODES={"foo"} + Quando for chamado BaseModeObject.init_custom_fields(instance, mode='foo') + Então instance.mode deve ser 'foo' + """ + instance = MagicMock(mode=None, MODES={"foo"}) + + self.assertEqual(instance.mode, None) + + BaseModeObject.init_custom_fields(instance, mode="foo") + + self.assertEqual(instance.mode, "foo") + + def test_init_custom_fields_2(self): + """ + Dado que: + - existe um objeto instance com mode=None + - BaseModeObject.MODES={'foo'} + Quando for chamado BaseModeObject.init_custom_fields(instance, mode='bar') + Então instance.mode deve ser None + """ + instance = MagicMock(mode=None, MODES={"foo"}) + + self.assertEqual(instance.mode, None) + + with self.assertRaises(ValidationError): + BaseModeObject.init_custom_fields(instance, mode="bar") + + self.assertEqual(instance.mode, None) + + def test_get_mode_required_fields_mapping(self): + instance = MagicMock() + + with self.assertRaises(NotImplementedError): + BaseModeObject.get_mode_required_fields_mapping(instance) + + def test_get_validation_fields(self): + """ + Dado que: + - existe um método mockado foo_type_required_fields + - existe um método mockado get_mode_required_fields_mapping que retorna: + - {"foo": foo_type_required_fields} + - existe um objeto instance com: + - mode='foo' + - MODES={"foo"} + - get_mode_required_fields_mapping=get_mode_required_fields_mapping + Quando for chamado BaseModeObject.get_validation_fields(instance) + Então: + - foo_type_required_fields deve ter sido chamado uma vez + - o resultado deve ser foo_type_required_fields.return_value + """ + + foo_type_required_fields = MagicMock() + get_mode_required_fields_mapping = MagicMock( + return_value={"foo": foo_type_required_fields} + ) + + instance = MagicMock( + mode="foo", + MODES={"foo"}, + get_mode_required_fields_mapping=get_mode_required_fields_mapping, + ) + + result = BaseModeObject.get_validation_fields(instance) + + foo_type_required_fields.assert_called_once() + + self.assertEqual(result, foo_type_required_fields.return_value) diff --git a/tests/models/invoice/test_billing_configuration.py b/tests/models/invoice/test_billing_configuration.py deleted file mode 100644 index 3d64cfac..00000000 --- a/tests/models/invoice/test_billing_configuration.py +++ /dev/null @@ -1,124 +0,0 @@ -from unittest.mock import patch, MagicMock - -from tests.utils import SetTestCase -from zoop_wrapper.exceptions import ValidationError -from zoop_wrapper.models.invoice import BillingConfiguration -from tests.factories.invoice import ( - BillingConfigurationFactory, - FixedDiscountFactory, - FixedFeeFactory, - PercentDiscountFactory, - PercentFeeFactory, -) - - -class BillingConfigurationTestCase(SetTestCase): - def test_create_set_type_fail(self): - self.assertRaises(ValidationError, BillingConfigurationFactory) - - def test_create_validation_fail(self): - self.assertRaises( - ValidationError, - BillingConfigurationFactory, - mode=BillingConfiguration.FIXED_MODE, - is_discount=False, - ) - - def test_create_empty(self): - instance = BillingConfigurationFactory( - mode=BillingConfiguration.FIXED_MODE, is_discount=False, allow_empty=True - ) - self.assertIsInstance(instance, BillingConfiguration) - - @patch("zoop_wrapper.models.invoice.BillingConfiguration.set_type") - def test_init_custom_fields(self, mocked_set_type): - instance = MagicMock(_allow_empty=False, set_type=mocked_set_type) - - BillingConfiguration.init_custom_fields(instance, "foo", "foo") - - self.assertIsInstance(mocked_set_type, MagicMock) - mocked_set_type.assert_called_once_with("foo", "foo") - - @staticmethod - def test_validate_mode(): - instance = MagicMock(_allow_empty=False, mode="foo") - BillingConfiguration.validate_mode(instance, BillingConfiguration.FIXED_MODE) - - def test_validate_mode_raise(self): - instance = MagicMock(_allow_empty=False, mode="foo") - self.assertRaises( - ValidationError, BillingConfiguration.validate_mode, instance, "foo" - ) - - def test_set_type(self): - instance = MagicMock(MODES=BillingConfiguration.MODES) - - BillingConfiguration.set_type(instance, BillingConfiguration.FIXED_MODE, True) - self.assertEqual(instance.mode, BillingConfiguration.FIXED_MODE) - self.assertEqual(instance.is_discount, True) - - def test_required_fields(self): - self.assertEqual({"mode"}, BillingConfiguration.get_required_fields()) - - def test_get_fee_required_fields(self): - self.assertIsSubSet( - {"start_date"}, BillingConfiguration.get_fee_required_fields() - ) - - def test_get_discount_required_fields(self): - self.assertIsSubSet( - {"limit_date"}, BillingConfiguration.get_discount_required_fields() - ) - - def test_get_fixed_required_fields(self): - self.assertIsSubSet( - {"amount"}, BillingConfiguration.get_fixed_required_fields() - ) - - def test_get_percent_required_fields(self): - self.assertIsSubSet( - {"percentage"}, BillingConfiguration.get_percent_required_fields() - ) - - def test_get_all_fields(self): - instance = MagicMock(get_validation_fields=MagicMock(return_value=set())) - - self.assertEqual(set(), BillingConfiguration.get_all_fields(instance)) - - def test_get_validation_fields_allow_empty(self): - instance = BillingConfiguration(allow_empty=True) - self.assertIsInstance(instance, BillingConfiguration) - - self.assertEqual({"mode"}, instance.get_validation_fields()) - - def test_get_validation_fields_fixed_discount(self): - instance = FixedDiscountFactory(allow_empty=True) - self.assertIsInstance(instance, BillingConfiguration) - - self.assertEqual( - {"mode", "limit_date", "amount"}, instance.get_validation_fields() - ) - - def test_get_validation_fields_percent_discount(self): - instance = PercentDiscountFactory(allow_empty=True) - self.assertIsInstance(instance, BillingConfiguration) - - self.assertEqual( - {"mode", "limit_date", "percentage"}, instance.get_validation_fields() - ) - - def test_get_validation_fields_fixed_fee(self): - instance = FixedFeeFactory(allow_empty=True) - self.assertIsInstance(instance, BillingConfiguration) - - self.assertEqual( - {"mode", "start_date", "amount"}, instance.get_validation_fields() - ) - - def test_get_validation_fields_percent_fee(self): - instance = PercentFeeFactory(allow_empty=True) - self.assertIsInstance(instance, BillingConfiguration) - - self.assertEqual( - {"mode", "start_date", "percentage"}, instance.get_validation_fields() - ) diff --git a/tests/models/invoice/test_billing_instructions.py b/tests/models/invoice/test_billing_instructions.py index d9d96ed9..2e6f11e2 100644 --- a/tests/models/invoice/test_billing_instructions.py +++ b/tests/models/invoice/test_billing_instructions.py @@ -1,26 +1,120 @@ from unittest.mock import MagicMock from tests.utils import SetTestCase -from zoop_wrapper.models.invoice import BillingInstructions, BillingConfiguration -from tests.factories.invoice import BillingInstructionsFactory +from zoop_wrapper.models.invoice import BillingInstructions, Fine, Interest, Discount +from tests.factories.invoice import ( + BillingInstructionsFactory, + FixedDiscountFactory, + FixedFineFactory, + FixedInterestFactory, +) class BillingInstructionsTestCase(SetTestCase): - def test_get_required_fields(self): + def test_get_non_required_fields(self): self.assertEqual( {"late_fee", "interest", "discount"}, - BillingInstructions.get_required_fields(), + BillingInstructions.get_non_required_fields(), ) def test_create(self): instance = BillingInstructionsFactory() self.assertIsInstance(instance, BillingInstructions) - def test_init_custom_fields(self): - instance = MagicMock() + def test_init_custom_fields_1(self): + """ + Serve para testar a inicialização correta dos campos! - BillingInstructions.init_custom_fields(instance) - self.assertIsInstance(instance.late_fee, BillingConfiguration) - self.assertIsInstance(instance.interest, BillingConfiguration) + Dado que existe uma instância mocada + Quando forem inicializados os campos: + - late_fee com dados válidos + - interest com dados válidos + - discount com dados válidos + Então: + - instância.late_fee deve ser válido + - instância.interest deve ser válido + - instância.discount deve ser válido + """ + instance = MagicMock(late_fee=None, interest=None, discount=None) + + BillingInstructions.init_custom_fields( + instance, + late_fee=FixedFineFactory().to_dict(), + interest=FixedInterestFactory().to_dict(), + discount=FixedDiscountFactory().to_dict(), + ) + self.assertIsInstance(instance.late_fee, Fine) + self.assertIsInstance(instance.interest, Interest) + self.assertIsInstance(instance.discount, list) + self.assertIsInstance(instance.discount[0], Discount) + + def test_init_custom_fields_2(self): + """ + Serve para testar a flexibilização do campo late_fee! + + Dado que existe uma instância mocada + Quando forem inicializados os campos: + - interest com dados válidos + - discount com dados válidos + Então: + - instância.interest deve ser válido + - instância.discount deve ser válido + """ + instance = MagicMock(late_fee=None, interest=None, discount=None) + + BillingInstructions.init_custom_fields( + instance, + interest=FixedInterestFactory().to_dict(), + discount=FixedDiscountFactory().to_dict(), + ) + self.assertEqual(instance.late_fee, None) + self.assertIsInstance(instance.interest, Interest) + self.assertIsInstance(instance.discount, list) + self.assertIsInstance(instance.discount[0], Discount) + + def test_init_custom_fields_3(self): + """ + Serve para testar a flexibilização do campo interest! + + Dado que existe uma instância mocada + Quando forem inicializados os campos: + - late_fee com dados válidos + - discount com dados válidos + Então: + - instância.late_fee deve ser válido + - instância.discount deve ser válido + """ + instance = MagicMock(late_fee=None, interest=None, discount=None) + + BillingInstructions.init_custom_fields( + instance, + late_fee=FixedFineFactory().to_dict(), + discount=FixedDiscountFactory().to_dict(), + ) + self.assertIsInstance(instance.late_fee, Fine) + self.assertEqual(instance.interest, None) self.assertIsInstance(instance.discount, list) - self.assertIsInstance(instance.discount[0], BillingConfiguration) + self.assertIsInstance(instance.discount[0], Discount) + + def test_init_custom_fields_4(self): + """ + Serve para testar a flexibilização do campo discount! + + Dado que existe uma instância mocada + Quando forem inicializados os campos: + - late_fee com dados válidos + - interest com dados válidos + Então: + - instância.late_fee deve ser válido + - instância.interest deve ser válido + """ + instance = MagicMock(late_fee=None, interest=None, discount=None) + + BillingInstructions.init_custom_fields( + instance, + late_fee=FixedFineFactory().to_dict(), + interest=FixedInterestFactory().to_dict(), + ) + self.assertIsInstance(instance.late_fee, Fine) + self.assertIsInstance(instance.interest, Interest) + self.assertEqual(instance.discount, None) diff --git a/tests/models/invoice/test_discount.py b/tests/models/invoice/test_discount.py new file mode 100644 index 00000000..825057ce --- /dev/null +++ b/tests/models/invoice/test_discount.py @@ -0,0 +1,35 @@ +from tests.utils import SetTestCase +from zoop_wrapper.models.invoice import Discount +from tests.factories.invoice import FixedDiscountFactory + + +class DiscountTestCase(SetTestCase): + def test_modes(self): + fixed = "FIXED" + percentage = "PERCENTAGE" + + self.assertEqual(Discount.FIXED, fixed) + self.assertEqual(Discount.PERCENTAGE, percentage) + + modes = {fixed, percentage} + + self.assertSetEqual(Discount.MODES, modes) + + def test_get_mode_required_fields_mapping(self): + instance: Discount = FixedDiscountFactory() + + expected = { + instance.FIXED: instance.get_fixed_required_fields, + instance.PERCENTAGE: instance.get_percentage_required_fields, + } + + result = instance.get_mode_required_fields_mapping() + + self.assertEqual(result, expected) + + def test_required_fields(self): + expected = {"limit_date", "mode"} + + result = Discount.get_required_fields() + + self.assertSetEqual(result, expected) diff --git a/tests/models/invoice/test_fine.py b/tests/models/invoice/test_fine.py new file mode 100644 index 00000000..e61fa105 --- /dev/null +++ b/tests/models/invoice/test_fine.py @@ -0,0 +1,35 @@ +from tests.utils import SetTestCase +from zoop_wrapper.models.invoice import Fine +from tests.factories.invoice import FixedFineFactory + + +class FineTestCase(SetTestCase): + def test_modes(self): + fixed = "FIXED" + percentage = "PERCENTAGE" + + self.assertEqual(Fine.FIXED, fixed) + self.assertEqual(Fine.PERCENTAGE, percentage) + + modes = {fixed, percentage} + + self.assertSetEqual(Fine.MODES, modes) + + def test_get_mode_required_fields_mapping(self): + instance: Fine = FixedFineFactory() + + expected = { + instance.FIXED: instance.get_fixed_required_fields, + instance.PERCENTAGE: instance.get_percentage_required_fields, + } + + result = instance.get_mode_required_fields_mapping() + + self.assertEqual(result, expected) + + def test_non_required_fields(self): + expected = {"start_date"} + + result = Fine.get_non_required_fields() + + self.assertSetEqual(result, expected) diff --git a/tests/models/invoice/test_interest.py b/tests/models/invoice/test_interest.py new file mode 100644 index 00000000..cc0ea6f8 --- /dev/null +++ b/tests/models/invoice/test_interest.py @@ -0,0 +1,38 @@ +from tests.utils import SetTestCase +from zoop_wrapper.models.invoice import Interest +from tests.factories.invoice import FixedInterestFactory + + +class InterestTestCase(SetTestCase): + def test_modes(self): + daily_amount = "DAILY_AMOUNT" + daily_percentage = "DAILY_PERCENTAGE" + monthly_percentage = "MONTHLY_PERCENTAGE" + + self.assertEqual(Interest.DAILY_AMOUNT, daily_amount) + self.assertEqual(Interest.DAILY_PERCENTAGE, daily_percentage) + self.assertEqual(Interest.MONTHLY_PERCENTAGE, monthly_percentage) + + modes = {daily_amount, daily_percentage, monthly_percentage} + + self.assertSetEqual(Interest.MODES, modes) + + def test_get_mode_required_fields_mapping(self): + instance: Interest = FixedInterestFactory() + + expected = { + instance.DAILY_AMOUNT: instance.get_fixed_required_fields, + instance.DAILY_PERCENTAGE: instance.get_percentage_required_fields, + instance.MONTHLY_PERCENTAGE: instance.get_percentage_required_fields, + } + + result = instance.get_mode_required_fields_mapping() + + self.assertEqual(result, expected) + + def test_non_required_fields(self): + expected = {"start_date"} + + result = Interest.get_non_required_fields() + + self.assertSetEqual(result, expected) diff --git a/tests/models/invoice/test_invoice.py b/tests/models/invoice/test_invoice.py index fb5c99ea..0ee31e9b 100644 --- a/tests/models/invoice/test_invoice.py +++ b/tests/models/invoice/test_invoice.py @@ -41,7 +41,7 @@ def test_non_required_fields(self): mocked_super_non_required_fields.assert_called_once() - def test_init_custom_fields(self): + def test_init_custom_fields_1(self): instance = MagicMock(spec=Invoice) billing_instructions = BillingInstructionsFactory().to_dict() @@ -50,6 +50,13 @@ def test_init_custom_fields(self): self.assertIsInstance(instance.billing_instructions, BillingInstructions) + def test_init_custom_fields_2(self): + instance = InvoiceFactory(billing_instructions=None) + + instance.init_custom_fields() + + self.assertNotIn("billing_instructions", instance.to_dict()) + def test_create(self): instance = InvoiceFactory() self.assertIsInstance(instance, Invoice) diff --git a/tests/wrapper/test_transactions_methods.py b/tests/wrapper/test_transactions_methods.py index 42911e56..52590c29 100644 --- a/tests/wrapper/test_transactions_methods.py +++ b/tests/wrapper/test_transactions_methods.py @@ -63,16 +63,8 @@ def test_add_transaction_invoice(self): "expiration_date": "2020-06-20", "payment_limit_date": "2020-06-30", "billing_instructions": { - "late_fee": { - "mode": "FIXED", - "percentage": 30, - "start_date": "2020-06-20", - }, - "interest": { - "mode": "MONTHLY_PERCENTAGE", - "percentage": 30, - "start_date": "2020-06-20", - }, + "late_fee": {"mode": "FIXED", "amount": 300}, + "interest": {"mode": "MONTHLY_PERCENTAGE", "percentage": 2}, "discount": [ {"mode": "FIXED", "amount": 300, "limit_date": "2020-06-20"} ], diff --git a/zoop_wrapper/__init__.py b/zoop_wrapper/__init__.py index 5a5c8e9d..cae3efc7 100644 --- a/zoop_wrapper/__init__.py +++ b/zoop_wrapper/__init__.py @@ -3,11 +3,13 @@ from .models import ( # noqa Address, BankAccount, - BillingConfiguration, BillingInstructions, Buyer, Card, + Discount, + Fine, InstallmentPlan, + Interest, Invoice, Person, Seller, diff --git a/zoop_wrapper/models/__init__.py b/zoop_wrapper/models/__init__.py index 940a53d8..f68f3b80 100644 --- a/zoop_wrapper/models/__init__.py +++ b/zoop_wrapper/models/__init__.py @@ -2,7 +2,7 @@ from .bank_account import BankAccount # noqa from .buyer import Buyer # noqa from .card import Card # noqa -from .invoice import Invoice, BillingConfiguration, BillingInstructions # noqa +from .invoice import Invoice, BillingInstructions, Fine, Interest, Discount # noqa from .seller import Seller # noqa from .token import Token # noqa from .transaction import Transaction, Source, InstallmentPlan # noqa diff --git a/zoop_wrapper/models/base.py b/zoop_wrapper/models/base.py index 6febf141..8c8ed97a 100644 --- a/zoop_wrapper/models/base.py +++ b/zoop_wrapper/models/base.py @@ -257,37 +257,42 @@ def validate_custom_fields(self, **kwargs): def get_validation_fields(self): """ - Get ``validation fields`` for instance.\n + Método para pegar os campos de validação!\n - This is necessary for classes/instances with - different fields based on type.\n + Isso é necessário para classes/instances com + diferentes campos obrigatórios definidos por + um tipo dinâmico!\n - Such as :class:`.Seller`, :class:`.BankAccount`, - :class:`.BillingConfiguration` and :class:`.Token`.\n + Tais como :class:`.Seller`, :class:`.BankAccount`, + :class:`.Fine` e :class:`.Token`.\n - Defaults to :meth:`get_required_fields`. + O padrão é :meth:`get_required_fields`. Returns: - ``set`` of fields to be used on validation + ``set`` de campos para serem utilizados na validação """ return self.get_required_fields() def get_all_fields(self): """ - get ``all fields`` for instance.\n + Método para pegar todos os campos!\n - This is necessary for classes/instances with - different fields based on type.\n + Isso é necessário para classes/instances com + diferentes campos obrigatórios definidos por + um tipo dinâmico!\n - Such as :class:`.Seller`, :class:`.BankAccount`, - :class:`.BillingConfiguration` and :class:`.Token`.\n + Tais como :class:`.Seller`, :class:`.BankAccount`, + :class:`.Fine` e :class:`.Token`.\n - Defaults to :meth:`get_fields`. + O padrão é :meth:`get_validation_fields` + :meth:`get_non_required_fields`. Returns: - ``set`` of all fields + ``set`` de todos os campos """ - return self.get_fields() + fields = set() + return fields.union( + self.get_validation_fields(), self.get_non_required_fields() + ) # noinspection PyMethodMayBeStatic def get_original_different_fields_mapping(self): @@ -300,18 +305,6 @@ def get_original_different_fields_mapping(self): """ return {} - @classmethod - def get_fields(cls): - """ - get ``set`` of ``all fields`` - - Returns: - ``set`` of fields - """ - required_fields = cls.get_required_fields() - non_required_fields = cls.get_non_required_fields() - return required_fields.union(non_required_fields) - @classmethod def get_required_fields(cls): """ diff --git a/zoop_wrapper/models/base.pyi b/zoop_wrapper/models/base.pyi index 2175e5a2..0af446ec 100644 --- a/zoop_wrapper/models/base.pyi +++ b/zoop_wrapper/models/base.pyi @@ -33,8 +33,6 @@ class ZoopObject: def get_all_fields(self) -> set: ... def get_original_different_fields_mapping(self) -> Dict[str, str]: ... @classmethod - def get_fields(cls) -> set: ... - @classmethod def get_required_fields(cls) -> set: ... @classmethod def get_non_required_fields(cls) -> set: ... diff --git a/zoop_wrapper/models/invoice.py b/zoop_wrapper/models/invoice.py index 3b5a8ead..1e7327fd 100644 --- a/zoop_wrapper/models/invoice.py +++ b/zoop_wrapper/models/invoice.py @@ -2,195 +2,120 @@ from ..exceptions import FieldError, ValidationError -class BillingConfiguration(ZoopObject): +class BaseModeObject(ZoopObject): """ - Represents a billing configuration object. - - It has ``dynamic types``! - - Can be a ``Fee`` or a ``Discount``.\n - And can be ``Fixed`` or ``Percentage``. - - So it can be ``Fee`` and ``Fixed``. - Or ``Fee`` and ``Percentage``. - Or ``Discount`` and ``Fixed``. - Or ``Discount`` and ``Percentage``. - - ``Percentage`` can be just :attr:`PERCENTAGE_MODE` or - :attr:`DAILY_PERCENTAGE_MODE` or - :attr:`MONTHLY_PERCENTAGE_MODE`. - - ``Fixed`` is :attr:`FIXED_MODE`. - - The ``type identifier`` is the :attr:`mode` attribute value as in the Zoop API.\n - The attr :attr:`is_discount` identify if it's a ``Discount`` or not. - - Attributes: - amount (int): integer amount for :attr:`FIXED_MODE` in 'centavos' - is_discount (bool): value representing if it's a ``Fee`` or ``Discount`` type - limit_date (str): limit date of Discount type - mode (str): value identifying if it's ``Fixed`` or ``Percentage`` type - start_date (str): start date for :attr:`FIXED_MODE` - percentage (float): float percentage for for ``Percentage`` types. - It has a ``max`` of ``4 decimal points`` and - is ``rounded up`` on the ``5º decimal point`` + Um objeto base que possui modos de quantia e porcentagem """ - PERCENTAGE_MODE = "PERCENTAGE" - DAILY_PERCENTAGE_MODE = "DAILY_PERCENTAGE" - MONTHLY_PERCENTAGE_MODE = "MONTHLY_PERCENTAGE" - FIXED_MODE = "FIXED" - - PERCENT_MODES = {PERCENTAGE_MODE, DAILY_PERCENTAGE_MODE, MONTHLY_PERCENTAGE_MODE} - MODES = PERCENT_MODES.union({FIXED_MODE}) - - def init_custom_fields(self, mode=None, is_discount=False, **kwarg): - """ - call :meth:`set_type` + MODES = set() - Args: - mode (str): value of mode - is_discount: boolen of verification - **kwarg: dict of kwargs + def init_custom_fields(self, mode=None, **kwargs): """ - self.set_type(mode, is_discount) - - def validate_mode(self, mode): + É necessário configurar o :attr:`mode` antes pois + ele influência no :meth:`get_validation_fields` """ - Validate the ``mode``. It must be in :attr:`MODES`. - Args: - mode: ``mode`` to be validated - - Raises: - :class:`.ValidationError`: when ``mode`` is not valid - """ - if mode not in BillingConfiguration.MODES: - if self._allow_empty: - return False + if mode not in self.MODES: raise ValidationError( - self, FieldError("mode", f"Must be one of {BillingConfiguration.MODES}") + self, + FieldError( + "mode", + f"o valor {mode} é inválido! Possíveis modos são {self.MODES}", + ), ) - return True - - def set_type(self, mode, is_discount): - """ - set :attr:`mode` and :attr:`is_discount` - Args: - mode (str): mode - is_discount (bool): value - """ - self.validate_mode(mode) setattr(self, "mode", mode) - setattr(self, "is_discount", is_discount) - def get_validation_fields(self): - """ - Get ``validation fields`` for instance. + def get_mode_required_fields_mapping(self): + raise NotImplementedError("Implemente o mapeamento!") - if ``type`` is ``Discount`` ``'fields'`` - is :meth:`get_discount_required_fields`\n + def get_validation_fields(self): + modes_required_fields_mapping = self.get_mode_required_fields_mapping() + required_method = modes_required_fields_mapping.get(self.mode) + return required_method() - else ``type`` is ``Fee``! ``'fields'`` - is :meth:`get_fee_required_fields` + @classmethod + def get_required_fields(cls): + return {"mode"} - if ``type`` is in :attr:`PERCENT_MODES` return ``'fields'`` union - :meth:`get_percent_required_fields`\n + @classmethod + def get_percentage_required_fields(cls): + fields = cls.get_required_fields() + return fields.union({"percentage"}) - else ``type`` is :attr:`FIXED_MODE` return ``'fields'`` union - :meth:`get_fixed_required_fields` + @classmethod + def get_fixed_required_fields(cls): + fields = cls.get_required_fields() + return fields.union({"amount"}) - Returns: - ``set`` of fields to be used on validation - """ - if not self.validate_mode(self.mode): - return self.get_required_fields() - if self.is_discount: - fields = self.get_discount_required_fields() - else: - fields = self.get_fee_required_fields() +class Fine(BaseModeObject): + """ + Representa a multa! - if self.mode in self.PERCENT_MODES: - return fields.union(self.get_percent_required_fields()) - else: - return fields.union(self.get_fixed_required_fields()) + https://docs.zoop.co/docs/multa-juros-e-descontos#multa + """ - def get_all_fields(self): - """ - Get ``all fields`` for instance. - Which are all the validation fields + FIXED = "FIXED" + PERCENTAGE = "PERCENTAGE" + MODES = {FIXED, PERCENTAGE} - Returns: - ``set`` of all fields - """ - return self.get_validation_fields() + def get_mode_required_fields_mapping(self): + return { + self.FIXED: self.get_fixed_required_fields, + self.PERCENTAGE: self.get_percentage_required_fields, + } @classmethod - def get_required_fields(cls): - fields = super().get_required_fields() - return fields.union({"mode"}) + def get_non_required_fields(cls): + return {"start_date"} - @classmethod - def get_fee_required_fields(cls): - """ - get ``set`` of ``required fields`` for ``Fee`` ``type`` - Returns: - ``set`` of fields - """ - fields = cls.get_required_fields() - return fields.union({"start_date"}) +class Interest(BaseModeObject): + """ + Representa um juros! - @classmethod - def get_discount_required_fields(cls): - """ - get ``set`` of ``required fields`` for ``Discount`` ``type``. + https://docs.zoop.co/docs/multa-juros-e-descontos#juros + """ - Returns: - ``set`` of fields - """ - fields = cls.get_required_fields() - return fields.union({"limit_date"}) + DAILY_AMOUNT = "DAILY_AMOUNT" + DAILY_PERCENTAGE = "DAILY_PERCENTAGE" + MONTHLY_PERCENTAGE = "MONTHLY_PERCENTAGE" + MODES = {DAILY_AMOUNT, DAILY_PERCENTAGE, MONTHLY_PERCENTAGE} + + def get_mode_required_fields_mapping(self): + return { + self.DAILY_AMOUNT: self.get_fixed_required_fields, + self.DAILY_PERCENTAGE: self.get_percentage_required_fields, + self.MONTHLY_PERCENTAGE: self.get_percentage_required_fields, + } @classmethod - def get_fixed_required_fields(cls): - """ - get ``set`` of ``required fields`` for ``Fixed`` ``type``. + def get_non_required_fields(cls): + return {"start_date"} - Returns: - ``set`` of fields - """ - fields = cls.get_required_fields() - return fields.union({"amount"}) - @classmethod - def get_percent_required_fields(cls): - """ - get ``set`` of ``required fields`` for ``Percent`` ``type``. +class Discount(BaseModeObject): + """ + Representa um desconto! - Returns: - ``set`` of fields - """ - fields = cls.get_required_fields() - return fields.union({"percentage"}) + https://docs.zoop.co/docs/multa-juros-e-descontos#descontos + """ - @classmethod - def from_dict_or_instance(cls, data, is_discount=False, **kwargs): - """ - call ``super().from_dict_or_instance`` with - the default of ``is_discount=False`` and passes kwargs. + FIXED = "FIXED" + PERCENTAGE = "PERCENTAGE" + MODES = {FIXED, PERCENTAGE} - Args: - data: dict of data or :class:`.Invoice` - is_discount: boolean - **kwargs: kwargs + def get_mode_required_fields_mapping(self): + return { + self.FIXED: self.get_fixed_required_fields, + self.PERCENTAGE: self.get_percentage_required_fields, + } - Returns: - instance initialized of :class:`BillingConfiguration` - """ - return super().from_dict_or_instance(data, is_discount=is_discount, **kwargs) + @classmethod + def get_required_fields(cls): + fields = super().get_required_fields() + return fields.union({"limit_date"}) class BillingInstructions(ZoopObject): @@ -205,7 +130,7 @@ class BillingInstructions(ZoopObject): def init_custom_fields(self, late_fee=None, interest=None, discount=None, **kwargs): """ - initialize late_fee, interest and discount. + Inicializa late_fee, interest e discount. Args: discount: dict or instance of BillingConfiguration model @@ -213,47 +138,38 @@ def init_custom_fields(self, late_fee=None, interest=None, discount=None, **kwar late_fee: dict or instance of BillingConfiguration model **kwargs: kwargs """ - setattr( - self, - "late_fee", - BillingConfiguration.from_dict_or_instance(late_fee, allow_empty=True), - ) - setattr( - self, - "interest", - BillingConfiguration.from_dict_or_instance(interest, allow_empty=True), - ) - if isinstance(discount, list): + if late_fee: setattr( self, - "discount", - [ - BillingConfiguration.from_dict_or_instance( - item, allow_empty=True, is_discount=True - ) - for item in discount - ], + "late_fee", + Fine.from_dict_or_instance(late_fee), + ) + + if interest: + setattr( + self, + "interest", + Interest.from_dict_or_instance(interest), ) - else: + + if discount: + if not isinstance(discount, list): + discount = [discount] setattr( self, "discount", - [ - BillingConfiguration.from_dict_or_instance( - discount, allow_empty=True, is_discount=True - ) - ], + [Discount.from_dict_or_instance(item) for item in discount], ) @classmethod - def get_required_fields(cls): + def get_non_required_fields(cls): """ - get set of required fields + Conjunto de campos não obrigatórios Returns: - ``set`` of fields + ``set`` de campos """ - fields = super().get_required_fields() + fields = super().get_non_required_fields() return fields.union({"late_fee", "interest", "discount"}) @@ -279,13 +195,12 @@ def init_custom_fields(self, billing_instructions=None, **kwargs): """ super().init_custom_fields(**kwargs) - setattr( - self, - "billing_instructions", - BillingInstructions.from_dict_or_instance( - billing_instructions, allow_empty=True - ), - ) + if billing_instructions: + setattr( + self, + "billing_instructions", + BillingInstructions.from_dict_or_instance(billing_instructions), + ) @classmethod def get_required_fields(cls): diff --git a/zoop_wrapper/models/invoice.pyi b/zoop_wrapper/models/invoice.pyi index 5293248f..ec4e2482 100644 --- a/zoop_wrapper/models/invoice.pyi +++ b/zoop_wrapper/models/invoice.pyi @@ -1,45 +1,58 @@ -from zoop_wrapper.models.base import ( - PaymentMethod as PaymentMethod, - ZoopObject as ZoopObject, -) -from typing import Any, Optional, List +from ..exceptions import FieldError as FieldError, ValidationError as ValidationError +from .base import PaymentMethod as PaymentMethod, ZoopObject as ZoopObject +from typing import Any, Optional, List, Set -class BillingConfiguration(ZoopObject): - PERCENTAGE_MODE: str = ... - DAILY_PERCENTAGE_MODE: str = ... - MONTHLY_PERCENTAGE_MODE: str = ... - FIXED_MODE: str = ... - PERCENT_MODES: Any = ... - MODES: Any = ... +class BaseModeObject(ZoopObject): + MODES: Set[str] = ... - is_discount: Optional[bool] - mode: Optional[str] - start_date: Optional[str] - limit_date: Optional[str] - amount: Optional[str] - percentage: Optional[str] - def init_custom_fields( - self, mode: Optional[str] = ..., is_discount: bool = ..., **kwarg: Any - ) -> None: ... - def validate_mode(self, mode: str): ... - def set_type(self, mode: str, is_discount: bool) -> None: ... - def get_validation_fields(self) -> set: ... - def get_all_fields(self) -> set: ... + mode: str = ... + percentage: Optional[str] = ... + amount: Optional[str] = ... + def init_custom_fields(self, mode: Optional[Any] = ..., **kwargs: Any) -> None: ... + def get_mode_required_fields_mapping(self) -> None: ... + def get_validation_fields(self): ... @classmethod - def get_required_fields(cls) -> set: ... + def get_required_fields(cls): ... @classmethod - def get_fee_required_fields(cls) -> set: ... + def get_percentage_required_fields(cls): ... + @classmethod + def get_fixed_required_fields(cls): ... + +class Fine(BaseModeObject): + FIXED: str = ... + PERCENTAGE: str = ... + MODES: Set[str] = ... + + start_date: Optional[str] = ... + def get_mode_required_fields_mapping(self): ... @classmethod - def get_discount_required_fields(cls) -> set: ... + def get_non_required_fields(cls): ... + +class Interest(BaseModeObject): + DAILY_AMOUNT: str = ... + DAILY_PERCENTAGE: str = ... + MONTHLY_PERCENTAGE: str = ... + MODES: Set[str] = ... + + start_date: Optional[str] = ... + def get_mode_required_fields_mapping(self): ... @classmethod - def get_fixed_required_fields(cls) -> set: ... + def get_non_required_fields(cls): ... + +class Discount(BaseModeObject): + FIXED: str = ... + PERCENTAGE: str = ... + MODES: Set[str] = ... + + limit_date: str = ... + def get_mode_required_fields_mapping(self): ... @classmethod - def get_percent_required_fields(cls) -> set: ... + def get_required_fields(cls): ... class BillingInstructions(ZoopObject): - late_fee: BillingConfiguration - interest: BillingConfiguration - discount: List[BillingConfiguration] + late_fee: Fine + interest: Interest + discount: List[Discount] def init_custom_fields( self, late_fee: Optional[Any] = ...,