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.
- wbaccounting/__init__.py +1 -0
- wbaccounting/admin/__init__.py +5 -0
- wbaccounting/admin/booking_entry.py +53 -0
- wbaccounting/admin/entry_accounting_information.py +10 -0
- wbaccounting/admin/invoice.py +26 -0
- wbaccounting/admin/invoice_type.py +8 -0
- wbaccounting/admin/transactions.py +16 -0
- wbaccounting/apps.py +5 -0
- wbaccounting/dynamic_preferences_registry.py +107 -0
- wbaccounting/factories/__init__.py +10 -0
- wbaccounting/factories/booking_entry.py +21 -0
- wbaccounting/factories/entry_accounting_information.py +46 -0
- wbaccounting/factories/invoice.py +43 -0
- wbaccounting/factories/transactions.py +32 -0
- wbaccounting/files/__init__.py +0 -0
- wbaccounting/files/invoice_document_file.py +134 -0
- wbaccounting/files/utils.py +331 -0
- wbaccounting/generators/__init__.py +6 -0
- wbaccounting/generators/base.py +120 -0
- wbaccounting/io/handlers/__init__.py +0 -0
- wbaccounting/io/handlers/transactions.py +32 -0
- wbaccounting/io/parsers/__init__.py +0 -0
- wbaccounting/io/parsers/societe_generale_lux.py +49 -0
- wbaccounting/io/parsers/societe_generale_lux_prenotification.py +60 -0
- wbaccounting/migrations/0001_initial_squashed_squashed_0005_alter_bookingentry_counterparty_and_more.py +284 -0
- wbaccounting/migrations/0006_alter_invoice_status.py +30 -0
- wbaccounting/migrations/0007_alter_invoice_options.py +23 -0
- wbaccounting/migrations/0008_alter_invoice_options.py +20 -0
- wbaccounting/migrations/0009_invoicetype_alter_bookingentry_options_and_more.py +366 -0
- wbaccounting/migrations/0010_alter_bookingentry_options.py +20 -0
- wbaccounting/migrations/0011_transaction.py +103 -0
- wbaccounting/migrations/0012_entryaccountinginformation_external_invoice_users.py +25 -0
- wbaccounting/migrations/__init__.py +0 -0
- wbaccounting/models/__init__.py +6 -0
- wbaccounting/models/booking_entry.py +167 -0
- wbaccounting/models/entry_accounting_information.py +157 -0
- wbaccounting/models/invoice.py +467 -0
- wbaccounting/models/invoice_type.py +30 -0
- wbaccounting/models/model_tasks.py +71 -0
- wbaccounting/models/transactions.py +112 -0
- wbaccounting/permissions.py +6 -0
- wbaccounting/processors/__init__.py +0 -0
- wbaccounting/processors/dummy_processor.py +5 -0
- wbaccounting/serializers/__init__.py +12 -0
- wbaccounting/serializers/booking_entry.py +78 -0
- wbaccounting/serializers/consolidated_invoice.py +109 -0
- wbaccounting/serializers/entry_accounting_information.py +149 -0
- wbaccounting/serializers/invoice.py +95 -0
- wbaccounting/serializers/invoice_type.py +16 -0
- wbaccounting/serializers/transactions.py +50 -0
- wbaccounting/tests/__init__.py +0 -0
- wbaccounting/tests/conftest.py +65 -0
- wbaccounting/tests/test_displays/__init__.py +0 -0
- wbaccounting/tests/test_displays/test_booking_entries.py +1 -0
- wbaccounting/tests/test_models/__init__.py +0 -0
- wbaccounting/tests/test_models/test_booking_entries.py +119 -0
- wbaccounting/tests/test_models/test_entry_accounting_information.py +81 -0
- wbaccounting/tests/test_models/test_invoice_types.py +21 -0
- wbaccounting/tests/test_models/test_invoices.py +73 -0
- wbaccounting/tests/test_models/test_transactions.py +40 -0
- wbaccounting/tests/test_processors.py +28 -0
- wbaccounting/tests/test_serializers/__init__.py +0 -0
- wbaccounting/tests/test_serializers/test_booking_entries.py +69 -0
- wbaccounting/tests/test_serializers/test_entry_accounting_information.py +64 -0
- wbaccounting/tests/test_serializers/test_invoice_types.py +35 -0
- wbaccounting/tests/test_serializers/test_transactions.py +72 -0
- wbaccounting/urls.py +68 -0
- wbaccounting/viewsets/__init__.py +12 -0
- wbaccounting/viewsets/booking_entry.py +61 -0
- wbaccounting/viewsets/buttons/__init__.py +3 -0
- wbaccounting/viewsets/buttons/booking_entry.py +15 -0
- wbaccounting/viewsets/buttons/entry_accounting_information.py +100 -0
- wbaccounting/viewsets/buttons/invoice.py +65 -0
- wbaccounting/viewsets/cashflows.py +124 -0
- wbaccounting/viewsets/display/__init__.py +8 -0
- wbaccounting/viewsets/display/booking_entry.py +58 -0
- wbaccounting/viewsets/display/cashflows.py +58 -0
- wbaccounting/viewsets/display/entry_accounting_information.py +91 -0
- wbaccounting/viewsets/display/invoice.py +218 -0
- wbaccounting/viewsets/display/invoice_type.py +19 -0
- wbaccounting/viewsets/display/transactions.py +35 -0
- wbaccounting/viewsets/endpoints/__init__.py +1 -0
- wbaccounting/viewsets/endpoints/invoice.py +6 -0
- wbaccounting/viewsets/entry_accounting_information.py +143 -0
- wbaccounting/viewsets/invoice.py +277 -0
- wbaccounting/viewsets/invoice_type.py +25 -0
- wbaccounting/viewsets/menu/__init__.py +6 -0
- wbaccounting/viewsets/menu/booking_entry.py +15 -0
- wbaccounting/viewsets/menu/cashflows.py +10 -0
- wbaccounting/viewsets/menu/entry_accounting_information.py +11 -0
- wbaccounting/viewsets/menu/invoice.py +15 -0
- wbaccounting/viewsets/menu/invoice_type.py +15 -0
- wbaccounting/viewsets/menu/transactions.py +15 -0
- wbaccounting/viewsets/titles/__init__.py +4 -0
- wbaccounting/viewsets/titles/booking_entry.py +12 -0
- wbaccounting/viewsets/titles/entry_accounting_information.py +12 -0
- wbaccounting/viewsets/titles/invoice.py +23 -0
- wbaccounting/viewsets/titles/invoice_type.py +12 -0
- wbaccounting/viewsets/transactions.py +34 -0
- wbaccounting-2.2.1.dist-info/METADATA +8 -0
- wbaccounting-2.2.1.dist-info/RECORD +102 -0
- 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
|
+
}
|