wbaccounting 2.2.1__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. wbaccounting/__init__.py +1 -0
  2. wbaccounting/admin/__init__.py +5 -0
  3. wbaccounting/admin/booking_entry.py +53 -0
  4. wbaccounting/admin/entry_accounting_information.py +10 -0
  5. wbaccounting/admin/invoice.py +26 -0
  6. wbaccounting/admin/invoice_type.py +8 -0
  7. wbaccounting/admin/transactions.py +16 -0
  8. wbaccounting/apps.py +5 -0
  9. wbaccounting/dynamic_preferences_registry.py +107 -0
  10. wbaccounting/factories/__init__.py +10 -0
  11. wbaccounting/factories/booking_entry.py +21 -0
  12. wbaccounting/factories/entry_accounting_information.py +46 -0
  13. wbaccounting/factories/invoice.py +43 -0
  14. wbaccounting/factories/transactions.py +32 -0
  15. wbaccounting/files/__init__.py +0 -0
  16. wbaccounting/files/invoice_document_file.py +134 -0
  17. wbaccounting/files/utils.py +331 -0
  18. wbaccounting/generators/__init__.py +6 -0
  19. wbaccounting/generators/base.py +120 -0
  20. wbaccounting/io/handlers/__init__.py +0 -0
  21. wbaccounting/io/handlers/transactions.py +32 -0
  22. wbaccounting/io/parsers/__init__.py +0 -0
  23. wbaccounting/io/parsers/societe_generale_lux.py +49 -0
  24. wbaccounting/io/parsers/societe_generale_lux_prenotification.py +60 -0
  25. wbaccounting/migrations/0001_initial_squashed_squashed_0005_alter_bookingentry_counterparty_and_more.py +284 -0
  26. wbaccounting/migrations/0006_alter_invoice_status.py +30 -0
  27. wbaccounting/migrations/0007_alter_invoice_options.py +23 -0
  28. wbaccounting/migrations/0008_alter_invoice_options.py +20 -0
  29. wbaccounting/migrations/0009_invoicetype_alter_bookingentry_options_and_more.py +366 -0
  30. wbaccounting/migrations/0010_alter_bookingentry_options.py +20 -0
  31. wbaccounting/migrations/0011_transaction.py +103 -0
  32. wbaccounting/migrations/0012_entryaccountinginformation_external_invoice_users.py +25 -0
  33. wbaccounting/migrations/__init__.py +0 -0
  34. wbaccounting/models/__init__.py +6 -0
  35. wbaccounting/models/booking_entry.py +167 -0
  36. wbaccounting/models/entry_accounting_information.py +157 -0
  37. wbaccounting/models/invoice.py +467 -0
  38. wbaccounting/models/invoice_type.py +30 -0
  39. wbaccounting/models/model_tasks.py +71 -0
  40. wbaccounting/models/transactions.py +112 -0
  41. wbaccounting/permissions.py +6 -0
  42. wbaccounting/processors/__init__.py +0 -0
  43. wbaccounting/processors/dummy_processor.py +5 -0
  44. wbaccounting/serializers/__init__.py +12 -0
  45. wbaccounting/serializers/booking_entry.py +78 -0
  46. wbaccounting/serializers/consolidated_invoice.py +109 -0
  47. wbaccounting/serializers/entry_accounting_information.py +149 -0
  48. wbaccounting/serializers/invoice.py +95 -0
  49. wbaccounting/serializers/invoice_type.py +16 -0
  50. wbaccounting/serializers/transactions.py +50 -0
  51. wbaccounting/tests/__init__.py +0 -0
  52. wbaccounting/tests/conftest.py +65 -0
  53. wbaccounting/tests/test_displays/__init__.py +0 -0
  54. wbaccounting/tests/test_displays/test_booking_entries.py +1 -0
  55. wbaccounting/tests/test_models/__init__.py +0 -0
  56. wbaccounting/tests/test_models/test_booking_entries.py +119 -0
  57. wbaccounting/tests/test_models/test_entry_accounting_information.py +81 -0
  58. wbaccounting/tests/test_models/test_invoice_types.py +21 -0
  59. wbaccounting/tests/test_models/test_invoices.py +73 -0
  60. wbaccounting/tests/test_models/test_transactions.py +40 -0
  61. wbaccounting/tests/test_processors.py +28 -0
  62. wbaccounting/tests/test_serializers/__init__.py +0 -0
  63. wbaccounting/tests/test_serializers/test_booking_entries.py +69 -0
  64. wbaccounting/tests/test_serializers/test_entry_accounting_information.py +64 -0
  65. wbaccounting/tests/test_serializers/test_invoice_types.py +35 -0
  66. wbaccounting/tests/test_serializers/test_transactions.py +72 -0
  67. wbaccounting/urls.py +68 -0
  68. wbaccounting/viewsets/__init__.py +12 -0
  69. wbaccounting/viewsets/booking_entry.py +61 -0
  70. wbaccounting/viewsets/buttons/__init__.py +3 -0
  71. wbaccounting/viewsets/buttons/booking_entry.py +15 -0
  72. wbaccounting/viewsets/buttons/entry_accounting_information.py +100 -0
  73. wbaccounting/viewsets/buttons/invoice.py +65 -0
  74. wbaccounting/viewsets/cashflows.py +124 -0
  75. wbaccounting/viewsets/display/__init__.py +8 -0
  76. wbaccounting/viewsets/display/booking_entry.py +58 -0
  77. wbaccounting/viewsets/display/cashflows.py +58 -0
  78. wbaccounting/viewsets/display/entry_accounting_information.py +91 -0
  79. wbaccounting/viewsets/display/invoice.py +218 -0
  80. wbaccounting/viewsets/display/invoice_type.py +19 -0
  81. wbaccounting/viewsets/display/transactions.py +35 -0
  82. wbaccounting/viewsets/endpoints/__init__.py +1 -0
  83. wbaccounting/viewsets/endpoints/invoice.py +6 -0
  84. wbaccounting/viewsets/entry_accounting_information.py +143 -0
  85. wbaccounting/viewsets/invoice.py +277 -0
  86. wbaccounting/viewsets/invoice_type.py +25 -0
  87. wbaccounting/viewsets/menu/__init__.py +6 -0
  88. wbaccounting/viewsets/menu/booking_entry.py +15 -0
  89. wbaccounting/viewsets/menu/cashflows.py +10 -0
  90. wbaccounting/viewsets/menu/entry_accounting_information.py +11 -0
  91. wbaccounting/viewsets/menu/invoice.py +15 -0
  92. wbaccounting/viewsets/menu/invoice_type.py +15 -0
  93. wbaccounting/viewsets/menu/transactions.py +15 -0
  94. wbaccounting/viewsets/titles/__init__.py +4 -0
  95. wbaccounting/viewsets/titles/booking_entry.py +12 -0
  96. wbaccounting/viewsets/titles/entry_accounting_information.py +12 -0
  97. wbaccounting/viewsets/titles/invoice.py +23 -0
  98. wbaccounting/viewsets/titles/invoice_type.py +12 -0
  99. wbaccounting/viewsets/transactions.py +34 -0
  100. wbaccounting-2.2.1.dist-info/METADATA +8 -0
  101. wbaccounting-2.2.1.dist-info/RECORD +102 -0
  102. wbaccounting-2.2.1.dist-info/WHEEL +5 -0
@@ -0,0 +1,50 @@
1
+ from wbaccounting.models import Transaction
2
+ from wbcore import serializers
3
+ from wbcore.contrib.currency.serializers import CurrencyRepresentationSerializer
4
+ from wbcore.contrib.directory.serializers import BankingContactRepresentationSerializer
5
+
6
+
7
+ class TransactionRepresentationSerializer(serializers.RepresentationSerializer):
8
+ _bank_account = BankingContactRepresentationSerializer(source="bank_account")
9
+ _detail = serializers.HyperlinkField(reverse_name="wbaccounting:transaction-detail")
10
+
11
+ class Meta:
12
+ model = Transaction
13
+ fields = ("id", "booking_date", "bank_account", "_bank_account", "value", "_detail")
14
+
15
+
16
+ class TransactionModelSerializer(serializers.ModelSerializer):
17
+ _bank_account = BankingContactRepresentationSerializer(source="bank_account")
18
+ _from_bank_account = BankingContactRepresentationSerializer(source="from_bank_account")
19
+ _to_bank_account = BankingContactRepresentationSerializer(source="to_bank_account")
20
+ _currency = CurrencyRepresentationSerializer(source="currency")
21
+ bank_account_currency_symbol = serializers.CharField(read_only=True)
22
+
23
+ class Meta:
24
+ model = Transaction
25
+ decorators = {
26
+ "value": serializers.decorator(
27
+ decorator_type="text", position="left", value="{{bank_account_currency_symbol}}"
28
+ ),
29
+ "value_local_ccy": serializers.decorator(
30
+ decorator_type="text", position="left", value="{{_currency.symbol}}"
31
+ ),
32
+ }
33
+ fields = (
34
+ "id",
35
+ "booking_date",
36
+ "value_date",
37
+ "bank_account",
38
+ "_bank_account",
39
+ "from_bank_account",
40
+ "_from_bank_account",
41
+ "to_bank_account",
42
+ "_to_bank_account",
43
+ "currency",
44
+ "_currency",
45
+ "fx_rate",
46
+ "value_local_ccy",
47
+ "description",
48
+ "value",
49
+ "bank_account_currency_symbol",
50
+ )
File without changes
@@ -0,0 +1,65 @@
1
+ from decimal import Decimal
2
+
3
+ import factory
4
+ import pytest
5
+ from django.apps import apps
6
+ from django.db.models.signals import pre_migrate
7
+ from pytest_factoryboy import register
8
+ from wbaccounting.factories import (
9
+ BookingEntryFactory,
10
+ EntryAccountingInformationFactory,
11
+ InvoiceFactory,
12
+ InvoiceTypeFactory,
13
+ LocalCurrencyTransactionFactory,
14
+ TransactionFactory,
15
+ )
16
+ from wbcore.contrib.authentication.factories import (
17
+ SuperUserFactory,
18
+ UserActivityFactory,
19
+ UserFactory,
20
+ )
21
+ from wbcore.contrib.currency.factories import CurrencyFactory, CurrencyFXRatesFactory
22
+ from wbcore.contrib.directory.factories import (
23
+ BankingContactFactory,
24
+ EntryFactory,
25
+ PersonFactory,
26
+ )
27
+ from wbcore.contrib.geography.tests.signals import app_pre_migration
28
+ from wbcore.tests.conftest import *
29
+
30
+ register(BookingEntryFactory)
31
+ register(InvoiceFactory)
32
+ register(InvoiceTypeFactory)
33
+ register(EntryAccountingInformationFactory)
34
+
35
+ register(TransactionFactory)
36
+ register(TransactionFactory, "transaction_no_value_date", value_date=None)
37
+ register(
38
+ TransactionFactory,
39
+ "transaction_fx",
40
+ fx_rate=factory.Faker("pydecimal", min_value=Decimal(0.1), max_value=Decimal(0.9)),
41
+ )
42
+ register(LocalCurrencyTransactionFactory, "transaction_local_ccy")
43
+ register(
44
+ LocalCurrencyTransactionFactory,
45
+ "transaction_local_ccy_fx",
46
+ fx_rate=factory.Faker("pydecimal", min_value=Decimal(0.1), max_value=Decimal(0.9)),
47
+ )
48
+
49
+
50
+ register(CurrencyFactory)
51
+ register(CurrencyFXRatesFactory)
52
+
53
+ register(EntryFactory)
54
+ register(UserFactory)
55
+ register(PersonFactory)
56
+ register(SuperUserFactory)
57
+ register(UserActivityFactory)
58
+ register(BankingContactFactory)
59
+
60
+ pre_migrate.connect(app_pre_migration, sender=apps.get_app_config("wbaccounting"))
61
+
62
+
63
+ @pytest.fixture
64
+ def booking_entries(request, booking_entry_factory):
65
+ return [booking_entry_factory.create() for _ in range(request.param)]
File without changes
@@ -0,0 +1 @@
1
+ import pytest
File without changes
@@ -0,0 +1,119 @@
1
+ from decimal import Decimal
2
+
3
+ import pytest
4
+ from pytest_factoryboy import LazyFixture
5
+ from wbaccounting.models import BookingEntry, EntryAccountingInformation, Invoice
6
+ from wbcore.contrib.authentication.models import User
7
+
8
+
9
+ @pytest.mark.django_db
10
+ class TestBookingEntry:
11
+ def test_str(self, booking_entry: BookingEntry):
12
+ assert str(booking_entry) == booking_entry.title
13
+
14
+ @pytest.mark.parametrize(
15
+ "method,return_value",
16
+ [
17
+ ("get_endpoint_basename", "wbaccounting:bookingentry"),
18
+ ("get_representation_value_key", "id"),
19
+ ("get_representation_label_key", "{{title}}"),
20
+ ("get_representation_endpoint", "wbaccounting:bookingentryrepresentation-list"),
21
+ ],
22
+ )
23
+ def test_wbmodel_methods(self, method: str, return_value: str):
24
+ assert getattr(BookingEntry, method)() == return_value
25
+
26
+ @pytest.mark.parametrize("user__is_superuser", [False])
27
+ def test_filter_for_user_no_superuser(self, booking_entry: BookingEntry, user: User):
28
+ booking_entries = BookingEntry.objects.filter_for_user(user) # type: ignore
29
+ assert booking_entry not in booking_entries
30
+
31
+ @pytest.mark.parametrize("user__is_superuser", [True])
32
+ def test_filter_for_user_superuser(self, booking_entry: BookingEntry, user: User):
33
+ booking_entries = BookingEntry.objects.filter_for_user(user) # type: ignore
34
+ assert booking_entry in booking_entries
35
+
36
+ @pytest.mark.parametrize("user__is_superuser", [False])
37
+ @pytest.mark.parametrize("user__user_permissions", [(["wbaccounting.view_bookingentry"])])
38
+ @pytest.mark.parametrize("booking_entry__counterparty", [LazyFixture("entry")])
39
+ @pytest.mark.parametrize("entry_accounting_information__entry", [LazyFixture("entry")])
40
+ @pytest.mark.parametrize("entry_accounting_information__counterparty_is_private", [False])
41
+ def test_filter_for_user_public_counterparty(
42
+ self, booking_entry: BookingEntry, user: User, entry_accounting_information: EntryAccountingInformation
43
+ ):
44
+ booking_entries = BookingEntry.objects.filter_for_user(user) # type: ignore
45
+ assert booking_entry in booking_entries
46
+
47
+ @pytest.mark.parametrize("user__is_superuser", [False])
48
+ @pytest.mark.parametrize("user__user_permissions", [(["wbaccounting.view_bookingentry"])])
49
+ @pytest.mark.parametrize("booking_entry__counterparty", [LazyFixture("entry")])
50
+ @pytest.mark.parametrize("entry_accounting_information__entry", [LazyFixture("entry")])
51
+ @pytest.mark.parametrize("entry_accounting_information__counterparty_is_private", [True])
52
+ def test_filter_for_user_private_counterparty(
53
+ self, booking_entry: BookingEntry, user: User, entry_accounting_information: EntryAccountingInformation
54
+ ):
55
+ booking_entries = BookingEntry.objects.filter_for_user(user) # type: ignore
56
+ assert booking_entry not in booking_entries
57
+
58
+ @pytest.mark.parametrize("user__is_superuser", [False])
59
+ @pytest.mark.parametrize("user__user_permissions", [(["wbaccounting.view_bookingentry"])])
60
+ @pytest.mark.parametrize("booking_entry__counterparty", [LazyFixture("entry")])
61
+ @pytest.mark.parametrize("entry_accounting_information__entry", [LazyFixture("entry")])
62
+ @pytest.mark.parametrize("entry_accounting_information__counterparty_is_private", [True])
63
+ def test_filter_for_user_private_counterparty_with_exempt(
64
+ self, booking_entry: BookingEntry, user: User, entry_accounting_information: EntryAccountingInformation
65
+ ):
66
+ entry_accounting_information.exempt_users.add(user)
67
+ booking_entries = BookingEntry.objects.filter_for_user(user) # type: ignore
68
+ assert booking_entry in booking_entries
69
+
70
+ @pytest.mark.parametrize("booking_entry__net_value", [Decimal(90)])
71
+ @pytest.mark.parametrize("booking_entry__gross_value", [None])
72
+ @pytest.mark.parametrize("booking_entry__vat", [Decimal(0.1)])
73
+ def test_net_without_gross(self, booking_entry: BookingEntry):
74
+ assert pytest.approx(booking_entry.gross_value) == Decimal(99)
75
+
76
+ @pytest.mark.parametrize("booking_entry__net_value", [None])
77
+ @pytest.mark.parametrize("booking_entry__gross_value", [Decimal(99)])
78
+ @pytest.mark.parametrize("booking_entry__vat", [Decimal(0.1)])
79
+ def test_gross_without_net(self, booking_entry: BookingEntry):
80
+ assert pytest.approx(booking_entry.net_value) == Decimal(90)
81
+
82
+ @pytest.mark.parametrize("booking_entry__net_value", [Decimal(90)])
83
+ @pytest.mark.parametrize("booking_entry__gross_value", [Decimal(100)])
84
+ @pytest.mark.parametrize("booking_entry__vat", [Decimal(0.1)])
85
+ def test_net_with_gross(self, booking_entry: BookingEntry):
86
+ assert pytest.approx(booking_entry.gross_value) == Decimal(99)
87
+
88
+ @pytest.mark.parametrize("booking_entry__net_value", [None])
89
+ @pytest.mark.parametrize("booking_entry__gross_value", [None])
90
+ @pytest.mark.parametrize("booking_entry__vat", [Decimal(0.1)])
91
+ def test_net_and_gross_none(self, booking_entry: BookingEntry):
92
+ assert booking_entry.net_value == Decimal(0) and booking_entry.gross_value == Decimal(0)
93
+
94
+ def test_invoice_value_same_currency(self, booking_entry: BookingEntry):
95
+ assert booking_entry.net_value == booking_entry.invoice_net_value
96
+ assert booking_entry.gross_value == booking_entry.invoice_gross_value
97
+
98
+ def test_invoice_value_different_currency(
99
+ self, booking_entry: BookingEntry, invoice: Invoice, currency_factory, mocker
100
+ ):
101
+ # Change the invoice currency
102
+ invoice.invoice_currency = currency_factory.create()
103
+ invoice.save()
104
+
105
+ # Patch the currency convert method to no rely on the database
106
+ mocker.patch("wbcore.contrib.currency.models.Currency.convert", return_value=Decimal(1.1))
107
+ # Run the save method to trigger fx computation
108
+ booking_entry.save()
109
+ assert booking_entry.invoice_net_value == pytest.approx(booking_entry.net_value * booking_entry.invoice_fx_rate) # type: ignore
110
+
111
+ def test_invoice_save_on_change(self, booking_entry: BookingEntry, mocker):
112
+ mocked_save = mocker.patch("wbaccounting.models.invoice.Invoice.save", autospec=True)
113
+ booking_entry.save()
114
+ mocked_save.assert_called_once()
115
+
116
+ def test_invoice_delete_on_change(self, booking_entry: BookingEntry, mocker):
117
+ mocked_save = mocker.patch("wbaccounting.models.invoice.Invoice.save", autospec=True)
118
+ booking_entry.delete()
119
+ mocked_save.assert_called_once()
@@ -0,0 +1,81 @@
1
+ import pytest
2
+ from dynamic_preferences.registries import global_preferences_registry
3
+ from wbaccounting.models import BookingEntry, EntryAccountingInformation
4
+ from wbaccounting.models.entry_accounting_information import (
5
+ default_currency,
6
+ default_email_body,
7
+ )
8
+ from wbcore.contrib.authentication.models import User
9
+ from wbcore.contrib.currency.models import Currency
10
+
11
+
12
+ @pytest.mark.django_db
13
+ class TestEntryAccountingInformation:
14
+ def test_str(self, entry_accounting_information: EntryAccountingInformation):
15
+ assert str(entry_accounting_information) == f"Counterparty: {entry_accounting_information.entry.computed_str}"
16
+
17
+ @pytest.mark.parametrize(
18
+ "method,return_value",
19
+ [
20
+ ("get_endpoint_basename", "wbaccounting:entryaccountinginformation"),
21
+ ("get_representation_value_key", "id"),
22
+ ("get_representation_label_key", "{{entry_repr}}"),
23
+ ("get_representation_endpoint", "wbaccounting:entryaccountinginformationrepresentation-list"),
24
+ ],
25
+ )
26
+ def test_wbmodel_methods(self, method: str, return_value: str):
27
+ assert getattr(EntryAccountingInformation, method)() == return_value
28
+
29
+ @pytest.mark.parametrize("user__is_superuser", [False])
30
+ def test_filter_for_user_no_superuser(self, entry_accounting_information: EntryAccountingInformation, user: User):
31
+ entry_accounting_information_list = EntryAccountingInformation.objects.filter_for_user(user) # type: ignore
32
+ assert entry_accounting_information not in entry_accounting_information_list
33
+
34
+ @pytest.mark.parametrize("user__is_superuser", [True])
35
+ def test_filter_for_user_superuser(self, entry_accounting_information: EntryAccountingInformation, user: User):
36
+ entry_accounting_information_list = EntryAccountingInformation.objects.filter_for_user(user) # type: ignore
37
+ assert entry_accounting_information in entry_accounting_information_list
38
+
39
+ @pytest.mark.parametrize("user__is_superuser", [False])
40
+ @pytest.mark.parametrize("user__user_permissions", [(["wbaccounting.view_entryaccountinginformation"])])
41
+ @pytest.mark.parametrize("entry_accounting_information__counterparty_is_private", [False])
42
+ def test_filter_for_user_public_counterparty(
43
+ self, entry_accounting_information: EntryAccountingInformation, user: User
44
+ ):
45
+ entry_accounting_information_list = EntryAccountingInformation.objects.filter_for_user(user) # type: ignore
46
+ assert entry_accounting_information in entry_accounting_information_list
47
+
48
+ @pytest.mark.parametrize("user__is_superuser", [False])
49
+ @pytest.mark.parametrize("user__user_permissions", [(["wbaccounting.view_entryaccountinginformation"])])
50
+ @pytest.mark.parametrize("entry_accounting_information__counterparty_is_private", [True])
51
+ def test_filter_for_user_private_counterparty(
52
+ self, user: User, entry_accounting_information: EntryAccountingInformation
53
+ ):
54
+ entry_accounting_information_list = EntryAccountingInformation.objects.filter_for_user(user) # type: ignore
55
+ assert entry_accounting_information not in entry_accounting_information_list
56
+
57
+ @pytest.mark.parametrize("user__is_superuser", [False])
58
+ @pytest.mark.parametrize("user__user_permissions", [(["wbaccounting.view_entryaccountinginformation"])])
59
+ @pytest.mark.parametrize("entry_accounting_information__counterparty_is_private", [True])
60
+ def test_filter_for_user_private_counterparty_with_exempt(
61
+ self, booking_entry: BookingEntry, user: User, entry_accounting_information: EntryAccountingInformation
62
+ ):
63
+ entry_accounting_information.exempt_users.add(user)
64
+ entry_accounting_information_list = EntryAccountingInformation.objects.filter_for_user(user) # type: ignore
65
+ assert entry_accounting_information in entry_accounting_information_list
66
+
67
+ def test_default_email_body(self):
68
+ assert default_email_body() == ""
69
+
70
+ def test_custom_default_email_body(self):
71
+ global_preferences_registry.manager()["wbaccounting__invoice_email_body"] = "Custom Body"
72
+ assert default_email_body() == "Custom Body"
73
+
74
+ def test_default_currency(self):
75
+ assert default_currency() is None
76
+
77
+ def test_custom_default_currency(self, currency: Currency):
78
+ global_preferences_registry.manager()[
79
+ "wbaccounting__default_entry_account_information_currency_key"
80
+ ] = currency.key
81
+ assert default_currency().pk is currency.pk
@@ -0,0 +1,21 @@
1
+ import pytest
2
+ from wbaccounting.models import InvoiceType
3
+
4
+
5
+ @pytest.mark.django_db
6
+ class TestInvoiceType:
7
+ def test_str(self, invoice_type: InvoiceType):
8
+ assert str(invoice_type) == invoice_type.name
9
+
10
+ @pytest.mark.parametrize(
11
+ "method,return_value",
12
+ [
13
+ ("get_endpoint_basename", "wbaccounting:invoicetype"),
14
+ ("get_representation_value_key", "id"),
15
+ ("get_representation_label_key", "{{name}}"),
16
+ ("get_representation_endpoint", "wbaccounting:invoicetyperepresentation-list"),
17
+ ],
18
+ )
19
+ def test_wbmodel_methods(self, method: str, return_value: str):
20
+ assert getattr(InvoiceType, method)() == return_value
21
+
@@ -0,0 +1,73 @@
1
+ import pytest
2
+ from dynamic_preferences.models import GlobalPreferenceModel
3
+ from dynamic_preferences.registries import global_preferences_registry
4
+ from pytest_factoryboy.fixture import LazyFixture
5
+ from wbaccounting.models import BookingEntry, EntryAccountingInformation, Invoice
6
+ from wbaccounting.models.entry_accounting_information import (
7
+ default_email_body,
8
+ default_currency,
9
+ )
10
+ from wbcore.contrib.authentication.models import User
11
+ from wbcore.contrib.currency.models import Currency
12
+
13
+
14
+ @pytest.mark.django_db
15
+ class TestInvoice:
16
+ def test_str(self, invoice: Invoice):
17
+ assert str(invoice) == invoice.title
18
+
19
+ @pytest.mark.parametrize(
20
+ "method,return_value",
21
+ [
22
+ ("get_endpoint_basename", "wbaccounting:invoice"),
23
+ ("get_representation_value_key", "id"),
24
+ ("get_representation_label_key", "{{title}} ({{invoice_date}})"),
25
+ ("get_representation_endpoint", "wbaccounting:invoicerepresentation-list"),
26
+ ],
27
+ )
28
+ def test_wbmodel_methods(self, method: str, return_value: str):
29
+ assert getattr(Invoice, method)() == return_value
30
+
31
+ @pytest.mark.parametrize("user__is_superuser", [False])
32
+ def test_filter_for_user_no_superuser(self, invoice: Invoice, user: User):
33
+ invoices = Invoice.objects.filter_for_user(user) # type: ignore
34
+ assert invoice not in invoices
35
+
36
+ @pytest.mark.parametrize("user__is_superuser", [True])
37
+ def test_filter_for_user_superuser(self, invoice: Invoice, user: User):
38
+ invoices = Invoice.objects.filter_for_user(user) # type: ignore
39
+ assert invoice in invoices
40
+
41
+ @pytest.mark.parametrize("user__is_superuser", [False])
42
+ @pytest.mark.parametrize("user__user_permissions", [(["wbaccounting.view_invoice"])])
43
+ @pytest.mark.parametrize("invoice__counterparty", [LazyFixture("entry")])
44
+ @pytest.mark.parametrize("entry_accounting_information__entry", [LazyFixture("entry")])
45
+ @pytest.mark.parametrize("entry_accounting_information__counterparty_is_private", [False])
46
+ def test_filter_for_user_public_counterparty(
47
+ self, invoice: Invoice, user: User, entry_accounting_information: EntryAccountingInformation
48
+ ):
49
+ invoices = Invoice.objects.filter_for_user(user) # type: ignore
50
+ assert invoice in invoices
51
+
52
+ @pytest.mark.parametrize("user__is_superuser", [False])
53
+ @pytest.mark.parametrize("user__user_permissions", [(["wbaccounting.view_entryaccountinginformation"])])
54
+ @pytest.mark.parametrize("invoice__counterparty", [LazyFixture("entry")])
55
+ @pytest.mark.parametrize("entry_accounting_information__entry", [LazyFixture("entry")])
56
+ @pytest.mark.parametrize("entry_accounting_information__counterparty_is_private", [True])
57
+ def test_filter_for_user_private_counterparty(
58
+ self, user: User, invoice: Invoice, entry_accounting_information: EntryAccountingInformation
59
+ ):
60
+ invoices = Invoice.objects.filter_for_user(user) # type: ignore
61
+ assert invoice not in invoices
62
+
63
+ @pytest.mark.parametrize("user__is_superuser", [False])
64
+ @pytest.mark.parametrize("user__user_permissions", [(["wbaccounting.view_entryaccountinginformation"])])
65
+ @pytest.mark.parametrize("invoice__counterparty", [LazyFixture("entry")])
66
+ @pytest.mark.parametrize("entry_accounting_information__entry", [LazyFixture("entry")])
67
+ @pytest.mark.parametrize("entry_accounting_information__counterparty_is_private", [True])
68
+ def test_filter_for_user_private_counterparty_with_exempt(
69
+ self, invoice: Invoice, user: User, entry_accounting_information: EntryAccountingInformation
70
+ ):
71
+ entry_accounting_information.exempt_users.add(user)
72
+ entry_accounting_information_list = EntryAccountingInformation.objects.filter_for_user(user) # type: ignore
73
+ assert entry_accounting_information in entry_accounting_information_list
@@ -0,0 +1,40 @@
1
+ import pytest
2
+ from wbaccounting.models import Transaction
3
+
4
+
5
+ @pytest.mark.django_db
6
+ class TestTransaction:
7
+ def test_str(self, transaction: Transaction):
8
+ assert str(transaction) == f"{transaction.booking_date:%d.%m.%Y}: {transaction.value:.2f}"
9
+
10
+ @pytest.mark.parametrize(
11
+ "method,return_value",
12
+ [
13
+ ("get_endpoint_basename", "wbaccounting:transaction"),
14
+ ("get_representation_value_key", "id"),
15
+ ("get_representation_label_key", "{{booking_date}}: {{value}}"),
16
+ ("get_representation_endpoint", "wbaccounting:transactionrepresentation-list"),
17
+ ],
18
+ )
19
+ def test_wbmodel_methods(self, method: str, return_value: str):
20
+ assert getattr(Transaction, method)() == return_value
21
+
22
+ def test_no_value_date(self, transaction_no_value_date: Transaction):
23
+ assert transaction_no_value_date.value_date == transaction_no_value_date.booking_date
24
+
25
+ def test_value(self, transaction: Transaction):
26
+ assert pytest.approx(transaction.value_local_ccy) == transaction.value
27
+
28
+ def test_value_different_fx_rate(self, transaction_fx: Transaction):
29
+ tfx = transaction_fx
30
+ assert tfx.value is not None
31
+ assert pytest.approx(tfx.value / tfx.fx_rate) == pytest.approx(tfx.value_local_ccy)
32
+
33
+ def test_local_ccy(self, transaction_local_ccy: Transaction):
34
+ tlc = transaction_local_ccy
35
+ assert pytest.approx(tlc.value) == tlc.value_local_ccy
36
+
37
+ def test_local_ccy_different_fx_rate(self, transaction_local_ccy_fx: Transaction):
38
+ tlcf = transaction_local_ccy_fx
39
+ assert tlcf.value_local_ccy is not None
40
+ assert pytest.approx(tlcf.value) == (tlcf.value_local_ccy * tlcf.fx_rate)
@@ -0,0 +1,28 @@
1
+ import pytest
2
+
3
+
4
+ @pytest.mark.django_db
5
+ def test_dummy_processor(invoice, mocker):
6
+ from wbaccounting.processors import dummy_processor
7
+
8
+ invoice_type = invoice.invoice_type
9
+ invoice_type.processor = "wbaccounting.processors.dummy_processor.processor"
10
+ invoice_type.save()
11
+ invoice.status = invoice.Status.SUBMITTED
12
+ invoice.save()
13
+
14
+ dummy_processor = mocker.spy(dummy_processor, "processor")
15
+ invoice.approve()
16
+ invoice.save()
17
+ dummy_processor.assert_called_once()
18
+ assert invoice.status == invoice.Status.SENT
19
+
20
+
21
+ @pytest.mark.django_db
22
+ def test_no_processor(invoice):
23
+ invoice.status = invoice.Status.SUBMITTED
24
+ invoice.save()
25
+
26
+ invoice.approve()
27
+ invoice.save()
28
+ assert invoice.status == invoice.Status.APPROVED
File without changes
@@ -0,0 +1,69 @@
1
+ import factory
2
+ import pytest
3
+ from django.test import RequestFactory
4
+ from rest_framework.reverse import reverse
5
+ from wbaccounting.models import BookingEntry
6
+ from wbaccounting.serializers import (
7
+ BookingEntryModelSerializer,
8
+ BookingEntryRepresentationSerializer,
9
+ )
10
+
11
+
12
+ @pytest.mark.django_db
13
+ class TestBookingEntryModelSerializer:
14
+ def test_serialize(self, booking_entry: BookingEntry):
15
+ serializer = BookingEntryModelSerializer(booking_entry)
16
+ assert isinstance(serializer.data, dict)
17
+
18
+ def test_serialize_contains_keys(self, booking_entry: BookingEntry):
19
+ serializer = BookingEntryModelSerializer(booking_entry)
20
+ assert (
21
+ "id",
22
+ "title",
23
+ "booking_date",
24
+ "due_date",
25
+ "payment_date",
26
+ "reference_date",
27
+ "net_value",
28
+ "gross_value",
29
+ "vat",
30
+ "invoice_net_value",
31
+ "invoice_gross_value",
32
+ "invoice_fx_rate",
33
+ "currency",
34
+ "_currency",
35
+ "counterparty",
36
+ "_counterparty",
37
+ "invoice",
38
+ "_invoice",
39
+ "invoice_currency",
40
+ "_additional_resources",
41
+ "_buttons",
42
+ ) == tuple(
43
+ serializer.data.keys()
44
+ ) # type: ignore
45
+
46
+ @pytest.mark.parametrize("decorator", ["net_value", "gross_value"])
47
+ def test_serialize_decorators(self, booking_entry: BookingEntry, rf: RequestFactory, decorator: str):
48
+ serializer = BookingEntryModelSerializer(booking_entry)
49
+ _, representation = serializer[decorator].get_representation(rf.get("/"), decorator) # type: ignore
50
+ assert representation["decorators"] == [{"position": "left", "type": "text", "value": "{{_currency.symbol}}"}]
51
+
52
+ def test_deserialize(self, booking_entry_factory, entry, currency, invoice):
53
+ data = factory.build(dict, FACTORY_CLASS=booking_entry_factory)
54
+ data["currency"] = currency.pk
55
+ data["counterparty"] = entry.pk
56
+ data["invoice"] = invoice.pk
57
+ serializer = BookingEntryModelSerializer(data=data)
58
+ assert serializer.is_valid()
59
+
60
+
61
+ @pytest.mark.django_db
62
+ class TestBookingEntryRepresentationSerializer:
63
+ def test_serialize(self, booking_entry: BookingEntry):
64
+ serializer = BookingEntryRepresentationSerializer(booking_entry)
65
+ assert serializer.data == {
66
+ "id": booking_entry.pk,
67
+ "title": booking_entry.title,
68
+ "_detail": reverse("wbaccounting:bookingentry-detail", args=[booking_entry.pk]),
69
+ }
@@ -0,0 +1,64 @@
1
+ import factory
2
+ import pytest
3
+ from wbaccounting.models import EntryAccountingInformation
4
+ from wbaccounting.serializers import (
5
+ EntryAccountingInformationModelSerializer,
6
+ EntryAccountingInformationRepresentationSerializer,
7
+ )
8
+
9
+
10
+ @pytest.mark.django_db
11
+ class TestEntryAccountingInformationModelSerializer:
12
+ def test_serialize(self, entry_accounting_information: EntryAccountingInformation):
13
+ serializer = EntryAccountingInformationModelSerializer(entry_accounting_information)
14
+ assert isinstance(serializer.data, dict)
15
+
16
+ #
17
+ def test_serialize_contains_keys(self, entry_accounting_information: EntryAccountingInformation):
18
+ serializer = EntryAccountingInformationModelSerializer(entry_accounting_information)
19
+ assert (
20
+ "id",
21
+ "entry",
22
+ "_entry",
23
+ "tax_id",
24
+ "vat",
25
+ "default_currency",
26
+ "_default_currency",
27
+ "default_invoice_type",
28
+ "_default_invoice_type",
29
+ "email_to",
30
+ "email_cc",
31
+ "email_bcc",
32
+ "_email_to",
33
+ "_email_cc",
34
+ "_email_bcc",
35
+ "email_subject",
36
+ "email_body",
37
+ "send_mail",
38
+ "counterparty_is_private",
39
+ "exempt_users",
40
+ "_exempt_users",
41
+ "booking_entry_generator",
42
+ "external_invoice_users",
43
+ "_external_invoice_users",
44
+ "_additional_resources",
45
+ ) == tuple(
46
+ serializer.data.keys()
47
+ ) # type: ignore
48
+
49
+ def test_deserialize(self, entry_accounting_information_factory, entry, currency):
50
+ data = factory.build(dict, FACTORY_CLASS=entry_accounting_information_factory)
51
+ data["entry"] = entry.pk
52
+ data["default_currency"] = currency.pk
53
+ serializer = EntryAccountingInformationModelSerializer(data=data)
54
+ assert serializer.is_valid()
55
+
56
+
57
+ @pytest.mark.django_db
58
+ class TestEntryAccountingInformationRepresentationSerializer:
59
+ def test_serialize(self, entry_accounting_information: EntryAccountingInformation):
60
+ serializer = EntryAccountingInformationRepresentationSerializer(entry_accounting_information)
61
+ assert serializer.data == {
62
+ "id": entry_accounting_information.pk,
63
+ "entry_repr": entry_accounting_information.entry.computed_str,
64
+ }
@@ -0,0 +1,35 @@
1
+ import factory
2
+ import pytest
3
+ from rest_framework.reverse import reverse
4
+ from wbaccounting.models import InvoiceType
5
+ from wbaccounting.serializers import (
6
+ InvoiceTypeModelSerializer,
7
+ InvoiceTypeRepresentationSerializer,
8
+ )
9
+
10
+
11
+ @pytest.mark.django_db
12
+ class TestInvoiceTypeModelSerializer:
13
+ def test_serialize(self, invoice_type: InvoiceType):
14
+ serializer = InvoiceTypeModelSerializer(invoice_type)
15
+ assert serializer.data == {
16
+ "id": invoice_type.pk,
17
+ "name": invoice_type.name,
18
+ "processor": invoice_type.processor,
19
+ }
20
+
21
+ def test_deserialize(self, invoice_type_factory):
22
+ data = factory.build(dict, FACTORY_CLASS=invoice_type_factory)
23
+ serializer = InvoiceTypeModelSerializer(data=data)
24
+ assert serializer.is_valid()
25
+
26
+
27
+ @pytest.mark.django_db
28
+ class TestInvoiceTypeRepresentationSerializer:
29
+ def test_serialize(self, invoice_type: InvoiceType):
30
+ serializer = InvoiceTypeRepresentationSerializer(invoice_type)
31
+ assert serializer.data == {
32
+ "id": invoice_type.pk,
33
+ "name": invoice_type.name,
34
+ "_detail": reverse("wbaccounting:invoicetype-detail", args=[invoice_type.pk]),
35
+ }