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,277 @@
1
+ from typing import Any
2
+
3
+ from django.core.exceptions import FieldError
4
+ from django.db.models import (
5
+ CharField,
6
+ Count,
7
+ DecimalField,
8
+ ExpressionWrapper,
9
+ F,
10
+ IntegerField,
11
+ Q,
12
+ QuerySet,
13
+ Sum,
14
+ Value,
15
+ )
16
+ from django.db.models.functions import Concat
17
+ from dynamic_preferences.registries import global_preferences_registry
18
+ from rest_framework import filters, status
19
+ from rest_framework.decorators import action
20
+ from rest_framework.request import Request
21
+ from rest_framework.response import Response
22
+ from wbaccounting.models import Invoice, submit_invoices_as_task
23
+ from wbaccounting.models.model_tasks import (
24
+ approve_invoices_as_task,
25
+ pay_invoices_as_task,
26
+ )
27
+ from wbaccounting.permissions import IsInvoiceAdmin
28
+ from wbaccounting.serializers import (
29
+ ConsolidatedInvoiceSerializer,
30
+ InvoiceModelSerializer,
31
+ InvoiceRepresentationSerializer,
32
+ )
33
+ from wbaccounting.viewsets.buttons import (
34
+ ConsolidatedInvoiceButtonConfig,
35
+ InvoiceButtonConfig,
36
+ )
37
+ from wbaccounting.viewsets.display import (
38
+ ConsolidatedInvoiceDisplayConfig,
39
+ InvoiceDisplayConfig,
40
+ )
41
+ from wbaccounting.viewsets.endpoints import ConsolidatedInvoiceEndpointConfig
42
+ from wbaccounting.viewsets.titles import (
43
+ ConsolidatedInvoiceTitleConfig,
44
+ InvoiceTitleConfig,
45
+ )
46
+ from wbcore import viewsets
47
+ from wbcore.contrib.currency.models import Currency, CurrencyFXRates
48
+ from wbcore.filters import DjangoFilterBackend
49
+
50
+
51
+ class InvoiceRepresentationViewSet(viewsets.RepresentationViewSet):
52
+ serializer_class = InvoiceRepresentationSerializer
53
+ filter_backends = (filters.OrderingFilter,)
54
+ queryset = Invoice.objects.all()
55
+ ordering_fields = ["invoice_date"]
56
+ ordering = ["invoice_date"]
57
+
58
+ def get_queryset(self):
59
+ return Invoice.objects.filter_for_user(self.request.user) # type: ignore
60
+
61
+
62
+ class InvoiceModelViewSet(viewsets.ModelViewSet):
63
+ serializer_class = InvoiceModelSerializer
64
+
65
+ filterset_fields = {
66
+ "counterparty": ["exact"],
67
+ "status": ["exact"],
68
+ "title": ["exact", "icontains"],
69
+ "invoice_date": ["gte", "exact", "lte"],
70
+ "invoice_type": ["exact"],
71
+ "reference_date": ["gte", "exact", "lte"],
72
+ }
73
+
74
+ search_fields = ["counterparty__computed_str", "title", "invoice_type__name"]
75
+ ordering_fields = (
76
+ "invoice_date",
77
+ "counterparty__computed_str",
78
+ "invoice_type__name",
79
+ "reference_date",
80
+ "gross_value",
81
+ "net_value",
82
+ )
83
+ ordering = (
84
+ "-invoice_date",
85
+ "id",
86
+ )
87
+ queryset = Invoice.objects.all()
88
+
89
+ button_config_class = InvoiceButtonConfig
90
+ display_config_class = InvoiceDisplayConfig
91
+ title_config_class = InvoiceTitleConfig
92
+
93
+ def get_queryset(self) -> QuerySet[Invoice]:
94
+ invoices = Invoice.objects.filter_for_user(self.request.user) # type: ignore
95
+
96
+ if eai_id := self.kwargs.get("entry_accounting_information_id", None):
97
+ invoices = invoices.filter(counterparty__entry_accounting_information__id=eai_id)
98
+
99
+ return invoices.select_related(
100
+ "counterparty",
101
+ "invoice_currency",
102
+ "invoice_type",
103
+ )
104
+
105
+
106
+ class ConsolidatedInvoiceViewSet(viewsets.ViewSet):
107
+ IDENTIFIER = "wbaccounting:consolidated-invoice"
108
+ title_config_class = ConsolidatedInvoiceTitleConfig
109
+ serializer_class = ConsolidatedInvoiceSerializer
110
+ permission_classes = []
111
+ display_config_class = ConsolidatedInvoiceDisplayConfig
112
+ button_config_class = ConsolidatedInvoiceButtonConfig
113
+ filterset_fields = {"status": ["exact"]}
114
+ filter_backends = (DjangoFilterBackend,)
115
+ endpoint_config_class = ConsolidatedInvoiceEndpointConfig
116
+ queryset = Invoice.objects.none()
117
+
118
+ def list(self, request):
119
+ queryset = self.get_queryset()
120
+ if serializer := self.get_serializer_class():
121
+ serializer = serializer(queryset, many=True, context={"request": request, "view": self})
122
+ return Response({"results": serializer.data})
123
+ return Response({"results": {}})
124
+
125
+ def get_lookup(self) -> dict:
126
+ lookup = {}
127
+ if (request := getattr(self, "request")) and (lookup_str := request.GET.get("lookup")):
128
+ lookup = dict(map(lambda x: x.split(":"), lookup_str.split(",")))
129
+ return lookup
130
+
131
+ def get_lookup_mapping(self, currency_symbol: str = "") -> "list[tuple]":
132
+ return [
133
+ ("reference_date", "reference_date", "gross_value_default_currency", Value(currency_symbol)),
134
+ ("invoice_type", "invoice_type__name", "gross_value_default_currency", Value(currency_symbol)),
135
+ ("invoice_currency", "invoice_currency__symbol", "gross_value", F("invoice_currency__symbol")),
136
+ ("counterparty", "counterparty__computed_str", "gross_value", F("invoice_currency__symbol")),
137
+ ("id", "title", "gross_value", F("invoice_currency__symbol")),
138
+ (
139
+ "booking_entries",
140
+ "booking_entries__title",
141
+ "booking_entries__gross_value",
142
+ F("booking_entries__currency__symbol"),
143
+ ),
144
+ ]
145
+
146
+ def get_queryset(self):
147
+ # We get the default currency id, as we potentially need it to convert some aggregates
148
+ default_currency = Currency.objects.get(
149
+ key=global_preferences_registry.manager()["currency__default_currency"]
150
+ )
151
+
152
+ # lookup is a GET parameter in the format of ?lookup=key1:param1,key2:param2
153
+ # we convert it to a dictionairy for easier access
154
+ lookup = self.get_lookup()
155
+
156
+ # the number of items in the lookup dictionairy define the depth of the treeview, e.g
157
+ # depth 0 means the first level, depth 1 the second, and so on
158
+ depth = len(lookup.keys())
159
+
160
+ # if there is a status GET parameter, we add it to make sure we only look at aggregated invoices
161
+ # with that status
162
+ status_filter = {}
163
+ if status := self.request.GET.get("status"):
164
+ status_filter["status"] = status
165
+
166
+ # We filter the queryset with the lookup dictionairy and the status dictionairy
167
+ # In case a wrong lookup was parsed in here a FieldError is raised and we return
168
+ # an empty queryset
169
+ try:
170
+ queryset = Invoice.objects.filter_for_user(self.request.user).filter(**lookup, **status_filter) # type: ignore
171
+ except FieldError:
172
+ return Invoice.objects.none()
173
+
174
+ # Filter out all invoice that do not have a reference date.
175
+ queryset = queryset.exclude(reference_date__isnull=True)
176
+
177
+ # Depending on the depth and the default currency we get a tuple with the following elements
178
+ # 0: reference to the field that will be used as an id
179
+ # 1: reference to the name of the group
180
+ # 2: reference to which field is used to sum up the invoices
181
+ # 3: reference to which currency is used
182
+ group_by = self.get_lookup_mapping(default_currency.symbol or "USD")[depth]
183
+
184
+ # If the depth is smaller than 2, this means we have to convert the values to the default
185
+ # currency FX Rate. This is because the first 2 levels (level 0 and 1) are group by date
186
+ # and group by invoice type, which are currency agnostic. The third level (level 2) is by
187
+ # currency, so we don't have to convert the invoice currency anymore
188
+ if depth < 2:
189
+ queryset = queryset.annotate(
190
+ fx_rate_default_currency=CurrencyFXRates.get_fx_rates_subquery_for_two_currencies(
191
+ "reference_date", "invoice_currency", default_currency
192
+ ),
193
+ gross_value_default_currency=ExpressionWrapper(
194
+ F("gross_value") * F("fx_rate_default_currency"),
195
+ output_field=DecimalField(decimal_places=2, max_digits=15),
196
+ ),
197
+ )
198
+
199
+ # if the id reference is not id (or level 4), we annotate the field as the id field
200
+ # we cannot do this for level 4 (or when the id field is id) because django will
201
+ # crash otherwise.
202
+ if group_by[0] != "id":
203
+ queryset = queryset.values(group_by[0]).annotate(id=F(group_by[0]))
204
+
205
+ # We annotate all values received from the get_lookup_mapping
206
+ queryset = queryset.annotate(
207
+ currency_symbol=group_by[3],
208
+ value=Sum(group_by[2]),
209
+ group=F(group_by[1]),
210
+ _group_key=Value(None, output_field=CharField(max_length=255)),
211
+ depth=Value(depth, output_field=IntegerField()),
212
+ num_draft=Count("pk", filter=Q(status="DRAFT")),
213
+ num_submitted=Count("pk", filter=Q(status="SUBMITTED")),
214
+ num_sent=Count("pk", filter=Q(status="SENT")),
215
+ num_paid=Count("pk", filter=Q(status="PAID")),
216
+ )
217
+
218
+ # If the group_by id field is not booking entries (or not the last level), then we annotate a _group_key
219
+ # We do this to give the frontend an understanding on how to open the next row. We don't do this on the
220
+ # last level, to ensure that the last group cannot be further extended
221
+ if group_by[0] != "booking_entries":
222
+ queryset = queryset.annotate(
223
+ _group_key=Concat(Value(group_by[0] + ":"), F(group_by[0]), output_field=CharField(max_length=255)),
224
+ )
225
+
226
+ return queryset.order_by("-reference_date").values(
227
+ "id",
228
+ "reference_date",
229
+ "value",
230
+ "currency_symbol",
231
+ "_group_key",
232
+ "group",
233
+ "depth",
234
+ "num_draft",
235
+ "num_submitted",
236
+ "num_sent",
237
+ "num_paid",
238
+ )
239
+
240
+ def get_underlying_invoices(self, request: Request, pk: Any, source_status: Invoice.Status) -> QuerySet[Invoice]:
241
+ """Returns all of the current row's underlying invoices filtered by `source_status`.
242
+
243
+ Args:
244
+ request: HttpRequest
245
+ pk: Pk of the current row. Type varies accordingly.
246
+ source_status (Invoice.Status): We need to filter by the state where a certain transition is allowed
247
+
248
+ Returns:
249
+ QuerySet(Invoice): A queryset of invoices
250
+ """
251
+
252
+ lookup = self.get_lookup()
253
+ depth = len(lookup.keys())
254
+ status_filter = request.GET.get("status", "")
255
+ pk_filter = self.get_lookup_mapping()[depth][0]
256
+ if depth < 5 and (not status_filter or status_filter == source_status.value):
257
+ return Invoice.objects.filter(**lookup | {pk_filter: pk}, status=source_status)
258
+
259
+ return Invoice.objects.none()
260
+
261
+ @action(detail=True, methods=["PATCH"])
262
+ def submit(self, request: Request, pk: Any):
263
+ underlying_invoices = self.get_underlying_invoices(request, pk, Invoice.Status.DRAFT)
264
+ submit_invoices_as_task.delay(underlying_invoices.values_list("id", flat=True)) # type: ignore
265
+ return Response(status=status.HTTP_200_OK)
266
+
267
+ @action(detail=True, methods=["PATCH"], permission_classes=[IsInvoiceAdmin])
268
+ def approve(self, request: Request, pk: Any):
269
+ underlying_invoices = self.get_underlying_invoices(request, pk, Invoice.Status.SUBMITTED)
270
+ approve_invoices_as_task.delay(underlying_invoices.values_list("id", flat=True)) # type: ignore
271
+ return Response(status=status.HTTP_200_OK)
272
+
273
+ @action(detail=True, methods=["PATCH"], permission_classes=[IsInvoiceAdmin])
274
+ def pay(self, request: Request, pk: Any):
275
+ underlying_invoices = self.get_underlying_invoices(request, pk, Invoice.Status.SENT)
276
+ pay_invoices_as_task.delay(underlying_invoices.values_list("id", flat=True)) # type: ignore
277
+ return Response(status=status.HTTP_200_OK)
@@ -0,0 +1,25 @@
1
+ from wbaccounting.models import InvoiceType
2
+ from wbaccounting.serializers import (
3
+ InvoiceTypeModelSerializer,
4
+ InvoiceTypeRepresentationSerializer,
5
+ )
6
+ from wbaccounting.viewsets.display import InvoiceTypeDisplayConfig
7
+ from wbaccounting.viewsets.titles import InvoiceTypeTitleConfig
8
+ from wbcore import viewsets
9
+
10
+
11
+ class InvoiceTypeModelViewSet(viewsets.ModelViewSet):
12
+ queryset = InvoiceType.objects.all()
13
+ serializer_class = InvoiceTypeModelSerializer
14
+ search_fields = ordering_fields = ("name", "processor")
15
+ ordering = ("name", "id")
16
+ display_config_class = InvoiceTypeDisplayConfig
17
+ filterset_fields = {"name": ["exact", "icontains"], "processor": ["exact", "icontains"]}
18
+ title_config_class = InvoiceTypeTitleConfig
19
+
20
+
21
+ class InvoiceTypeRepresentationViewSet(viewsets.RepresentationViewSet):
22
+ serializer_class = InvoiceTypeRepresentationSerializer
23
+ queryset = InvoiceType.objects.all()
24
+ search_fields = ordering_fields = ("name",)
25
+ ordering = ("name", "id")
@@ -0,0 +1,6 @@
1
+ from .booking_entry import BOOKINGENTRY_MENUITEM
2
+ from .entry_accounting_information import ENTRYACCOUNTINGINFORMATION_MENUITEM
3
+ from .invoice import INVOICE_MENUITEM
4
+ from .invoice_type import INVOICETYPE_MENUITEM
5
+ from .transactions import TRANSACTION_MENUITEM
6
+ from .cashflows import CASHFLOW_MENUITEM
@@ -0,0 +1,15 @@
1
+ from wbcore.menus import ItemPermission, MenuItem
2
+ from wbcore.permissions.shortcuts import is_internal_user
3
+
4
+ BOOKINGENTRY_MENUITEM = MenuItem(
5
+ label="Bookings",
6
+ endpoint="wbaccounting:bookingentry-list",
7
+ permission=ItemPermission(
8
+ method=lambda request: is_internal_user(request.user), permissions=["wbaccounting.view_bookingentry"]
9
+ ),
10
+ add=MenuItem(
11
+ label="Create Booking",
12
+ endpoint="wbaccounting:bookingentry-list",
13
+ permission=ItemPermission(permissions=["wbaccounting.add_bookingentry"]),
14
+ ),
15
+ )
@@ -0,0 +1,10 @@
1
+ from wbcore.menus import ItemPermission, MenuItem
2
+ from wbcore.permissions.shortcuts import is_internal_user
3
+
4
+ CASHFLOW_MENUITEM = MenuItem(
5
+ label="Cashflow",
6
+ endpoint="wbaccounting:futurecashflow-list",
7
+ permission=ItemPermission(
8
+ method=lambda request: is_internal_user(request.user), permissions=["wbaccounting.view_transaction"]
9
+ ),
10
+ )
@@ -0,0 +1,11 @@
1
+ from wbcore.menus import ItemPermission, MenuItem
2
+ from wbcore.permissions.shortcuts import is_internal_user
3
+
4
+ ENTRYACCOUNTINGINFORMATION_MENUITEM = MenuItem(
5
+ label="Counterparties",
6
+ endpoint="wbaccounting:entryaccountinginformation-list",
7
+ permission=ItemPermission(
8
+ method=lambda request: is_internal_user(request.user),
9
+ permissions=["wbaccounting.view_entryaccountinginformation"],
10
+ ),
11
+ )
@@ -0,0 +1,15 @@
1
+ from wbcore.menus import ItemPermission, MenuItem
2
+ from wbcore.permissions.shortcuts import is_internal_user
3
+
4
+ INVOICE_MENUITEM = MenuItem(
5
+ label="Invoices",
6
+ endpoint="wbaccounting:invoice-list",
7
+ permission=ItemPermission(
8
+ method=lambda request: is_internal_user(request.user), permissions=["wbaccounting.view_invoice"]
9
+ ),
10
+ add=MenuItem(
11
+ label="Create Invoice",
12
+ endpoint="wbaccounting:invoice-list",
13
+ permission=ItemPermission(permissions=["wbaccounting.add_invoice"]),
14
+ ),
15
+ )
@@ -0,0 +1,15 @@
1
+ from wbcore.menus import ItemPermission, MenuItem
2
+ from wbcore.permissions.shortcuts import is_internal_user
3
+
4
+ INVOICETYPE_MENUITEM = MenuItem(
5
+ label="Invoice Types",
6
+ endpoint="wbaccounting:invoicetype-list",
7
+ permission=ItemPermission(
8
+ method=lambda request: is_internal_user(request.user), permissions=["wbaccounting.view_invoicetype"]
9
+ ),
10
+ add=MenuItem(
11
+ label="Create Invoice Type",
12
+ endpoint="wbaccounting:invoicetype-list",
13
+ permission=ItemPermission(permissions=["wbaccounting.add_invoicetype"]),
14
+ ),
15
+ )
@@ -0,0 +1,15 @@
1
+ from wbcore.menus import ItemPermission, MenuItem
2
+ from wbcore.permissions.shortcuts import is_internal_user
3
+
4
+ TRANSACTION_MENUITEM = MenuItem(
5
+ label="Transactions",
6
+ endpoint="wbaccounting:transaction-list",
7
+ permission=ItemPermission(
8
+ method=lambda request: is_internal_user(request.user), permissions=["wbaccounting.view_transaction"]
9
+ ),
10
+ add=MenuItem(
11
+ label="Create Transaction",
12
+ endpoint="wbaccounting:transaction-list",
13
+ permission=ItemPermission(permissions=["wbaccounting.add_transaction"]),
14
+ ),
15
+ )
@@ -0,0 +1,4 @@
1
+ from .booking_entry import BookingEntryTitleConfig
2
+ from .entry_accounting_information import EntryAccountingInformationTitleConfig
3
+ from .invoice import InvoiceTitleConfig, ConsolidatedInvoiceTitleConfig
4
+ from .invoice_type import InvoiceTypeTitleConfig
@@ -0,0 +1,12 @@
1
+ from wbcore.metadata.configs.titles import TitleViewConfig
2
+
3
+
4
+ class BookingEntryTitleConfig(TitleViewConfig):
5
+ def get_instance_title(self):
6
+ return "Booking: {{title}}"
7
+
8
+ def get_list_title(self):
9
+ return "Booking"
10
+
11
+ def get_create_title(self):
12
+ return "New Booking"
@@ -0,0 +1,12 @@
1
+ from wbcore.metadata.configs.titles import TitleViewConfig
2
+
3
+
4
+ class EntryAccountingInformationTitleConfig(TitleViewConfig):
5
+ def get_instance_title(self):
6
+ return "Counterparty: {{ _entry.computed_str }}"
7
+
8
+ def get_list_title(self):
9
+ return "Counterparties"
10
+
11
+ def get_create_title(self):
12
+ return "New Counterparty"
@@ -0,0 +1,23 @@
1
+ from wbcore.metadata.configs.titles import TitleViewConfig
2
+
3
+
4
+ class InvoiceTitleConfig(TitleViewConfig):
5
+ def get_instance_title(self):
6
+ return "Invoice {{title}}"
7
+
8
+ def get_list_title(self):
9
+ return "Invoices"
10
+
11
+ def get_create_title(self):
12
+ return "New Invoice"
13
+
14
+
15
+ class ConsolidatedInvoiceTitleConfig(TitleViewConfig):
16
+ def get_instance_title(self):
17
+ return "Consolidated Invoice"
18
+
19
+ def get_list_title(self):
20
+ return "Consolidated Invoices"
21
+
22
+ def get_create_title(self):
23
+ return "New Consolidated Invoice"
@@ -0,0 +1,12 @@
1
+ from wbcore.metadata.configs.titles import TitleViewConfig
2
+
3
+
4
+ class InvoiceTypeTitleConfig(TitleViewConfig):
5
+ def get_instance_title(self):
6
+ return "Invoice Type"
7
+
8
+ def get_list_title(self):
9
+ return "Invoice Types"
10
+
11
+ def get_create_title(self):
12
+ return "New Invoice Type"
@@ -0,0 +1,34 @@
1
+ from django.db.models import F, QuerySet
2
+ from wbaccounting.models import Transaction
3
+ from wbaccounting.serializers import (
4
+ TransactionModelSerializer,
5
+ TransactionRepresentationSerializer,
6
+ )
7
+ from wbaccounting.viewsets.display import TransactionDisplayConfig
8
+ from wbcore import viewsets
9
+
10
+
11
+ class TransactionModelViewSet(viewsets.ModelViewSet):
12
+ queryset = Transaction.objects.all()
13
+ serializer_class = TransactionModelSerializer
14
+ search_fields = ("bank_account__isin", "from_bank_account__isin", "to_bank_account__isin")
15
+ ordering = ordering_fields = ("booking_date", "value", "id")
16
+ display_config_class = TransactionDisplayConfig
17
+
18
+ def get_queryset(self) -> QuerySet[Transaction]:
19
+ return (
20
+ super()
21
+ .get_queryset()
22
+ .filter_for_user(user=self.request.user)
23
+ .annotate(bank_account_currency_symbol=F("bank_account__currency__symbol"))
24
+ )
25
+
26
+
27
+ class TransactionRepresentationViewSet(viewsets.RepresentationViewSet):
28
+ serializer_class = TransactionRepresentationSerializer
29
+ queryset = Transaction.objects.all()
30
+ search_fields = ("bank_account__isin", "from_bank_account__isin", "to_bank_account__isin")
31
+ ordering = ordering_fields = ("booking_date", "value", "id")
32
+
33
+ def get_queryset(self) -> QuerySet[Transaction]:
34
+ return super().get_queryset().filter_for_user(user=self.request.user)
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.3
2
+ Name: wbaccounting
3
+ Version: 2.2.1
4
+ Summary: A workbench module for managing invoicing and simple accounting.
5
+ Author-email: Christopher Wittlinger <c.wittlinger@stainly.com>
6
+ Requires-Dist: reportlab==3.*
7
+ Requires-Dist: schwifty==2024.5.*
8
+ Requires-Dist: wbcore
@@ -0,0 +1,102 @@
1
+ wbaccounting/__init__.py,sha256=J-j-u0itpEFT6irdmWmixQqYMadNl1X91TxUmoiLHMI,22
2
+ wbaccounting/apps.py,sha256=g0CDsvZoOQFiCiWg9v-DJv99oPpm4LTQYunOZ5n2gTM,99
3
+ wbaccounting/dynamic_preferences_registry.py,sha256=yT-fTQ_v_BCYhpZO-ARUdZ2XMQVIIyNORKRY3l3Fba0,3356
4
+ wbaccounting/permissions.py,sha256=QE9SOnkq3vbra9VGdgDSn93NtYHNjvTzl-6XhUN9XK0,213
5
+ wbaccounting/urls.py,sha256=YysjpJjtUXw7SvthS2kCKxBhVZLXsRHC4Thg_nuOIRs,3000
6
+ wbaccounting/admin/__init__.py,sha256=klh0BoNdyTz54qRp1a1PSP0u7G4LbgQE_m3C68Z-ObE,284
7
+ wbaccounting/admin/booking_entry.py,sha256=DYjOTEeO7AtRBDpdNs50Vcs-M83PwyTcSpxWDQ0L1nI,1890
8
+ wbaccounting/admin/entry_accounting_information.py,sha256=kct4dNJ6UpsHOAZIvvblHpXuH1Q32cIIkh3TNDcaGrc,423
9
+ wbaccounting/admin/invoice.py,sha256=X_6VhGIUDTkDW4SlKsefZUY_1cGzuWYR5C6klyAUqyg,762
10
+ wbaccounting/admin/invoice_type.py,sha256=FeTTvWyXnKcLdIZmHV0Ak4Y6THIbh44zur8stsh6Lug,244
11
+ wbaccounting/admin/transactions.py,sha256=crnJ2OyusszSZQJO3Ehc63NAy_dNHALXcp78cszr5Z0,423
12
+ wbaccounting/factories/__init__.py,sha256=3F29_EXN8nmuI-sbhpc8gzTksdQ9QRnLfyGw8Hfua4M,309
13
+ wbaccounting/factories/booking_entry.py,sha256=7btt5pbbkubNJqtyxKIQ8ybK9Wl6S8NhmFJRkF3JRRs,927
14
+ wbaccounting/factories/entry_accounting_information.py,sha256=9Y7zLFutAEXuAO1R2ZtRKVeBtTunRchOtFJw_RV003c,1728
15
+ wbaccounting/factories/invoice.py,sha256=0GwLv3-aCi6iVsEVdXWYScEEKIGsZnGPizNKHXuGwk4,1717
16
+ wbaccounting/factories/transactions.py,sha256=aY8nMGpixlBRNLOUS9SagtBJgdJSxYvv_F1jgElcvxA,1231
17
+ wbaccounting/files/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ wbaccounting/files/invoice_document_file.py,sha256=kPgYYrjIly8fIP-ezddnzyOlySsfV7BG2VzUQRiG46U,4258
19
+ wbaccounting/files/utils.py,sha256=p1EpTFKhlw8vKtHQW7BZ32nCaeOqP0KEHsAe6Rn5ko0,14111
20
+ wbaccounting/generators/__init__.py,sha256=HMzN5HbicO03Ji5YabzVUScI7UWkC-VfdwaWeEvtplg,138
21
+ wbaccounting/generators/base.py,sha256=RJrQ6XAhyDVuJ5PmD_2BNGUCe9FS_b8sHtlnsweXaeI,4621
22
+ wbaccounting/io/handlers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
+ wbaccounting/io/handlers/transactions.py,sha256=wu_25akE-J-LKvs-1-erjtON2ffnkhsX0grTF5vfa6o,1415
24
+ wbaccounting/io/parsers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
+ wbaccounting/io/parsers/societe_generale_lux.py,sha256=nbNDnM6S886GLLh3USPrqWwBINW2ZG2jl6dzvG8VRUw,1692
26
+ wbaccounting/io/parsers/societe_generale_lux_prenotification.py,sha256=45C3zKyYLo1oTjnGxNCdit9ipYF7r7VF2j1BV7yaZ_I,2327
27
+ wbaccounting/migrations/0001_initial_squashed_squashed_0005_alter_bookingentry_counterparty_and_more.py,sha256=3bGsMTmv-4XABUuazo3FIxOKIuHg0obHC19NGWSzqQs,11449
28
+ wbaccounting/migrations/0006_alter_invoice_status.py,sha256=b0cTeDaRbNyp9s1DLP75Vxy2O0FIRU366Mref7MrxY4,859
29
+ wbaccounting/migrations/0007_alter_invoice_options.py,sha256=vvHUwv2WSjDINnuyzoxoXDrG5tRlEiGxOzPwdL_VgC8,634
30
+ wbaccounting/migrations/0008_alter_invoice_options.py,sha256=q8d_MFIgJNTwZlAntMNqXfsWBDw3VjhljeWeS3Cg-HQ,528
31
+ wbaccounting/migrations/0009_invoicetype_alter_bookingentry_options_and_more.py,sha256=LcvrkJmWoAN55dBTKt0kXpsgmqBgAT3ozCV4BOFeCwA,14023
32
+ wbaccounting/migrations/0010_alter_bookingentry_options.py,sha256=MubJcJ864jphINUO3EbDf5IJ9D-HqTRqA_3PsNf9HKg,564
33
+ wbaccounting/migrations/0011_transaction.py,sha256=CAFDQiZFIP-xJ__GqCyvsmMUazgMUGw_cR7H40GWxz0,4085
34
+ wbaccounting/migrations/0012_entryaccountinginformation_external_invoice_users.py,sha256=4uGToqhfM5VGMgHNnBgvASjoVTox1sYvjx0RY_mgbDA,815
35
+ wbaccounting/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
+ wbaccounting/models/__init__.py,sha256=qk2F9CHovzTR-opHJpPpp9HbblLX95izFNfL0FnQCts,263
37
+ wbaccounting/models/booking_entry.py,sha256=usKjt_g_4Y9dfS7-7DYzB7zxJ1eQ3UW5PwIFOSruBtQ,6655
38
+ wbaccounting/models/entry_accounting_information.py,sha256=UtA3r-VJveMXYL580cu60JPsSY7gYfw890ELzxsg8Zk,5778
39
+ wbaccounting/models/invoice.py,sha256=ry8KeEnjI9Sjaq4NwmUsrRXiEiFvUlBabmPrQfFoWM4,18521
40
+ wbaccounting/models/invoice_type.py,sha256=SMYKJGyL7rm4_7Sb6yaLT88ZIqXDmh9LA2KpyELsDBg,855
41
+ wbaccounting/models/model_tasks.py,sha256=lBB8fN9VL2Ib12Chx5mHnYlm01xUPQE5-OZdrhk4GCY,2165
42
+ wbaccounting/models/transactions.py,sha256=nzOLRFO3Slf-XaEMqlUB0AtiW9hR7gdQCdYEchOfWsk,3874
43
+ wbaccounting/processors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
44
+ wbaccounting/processors/dummy_processor.py,sha256=eHeH6IeUlPHcN1vggzmtVcffH0xgYFqAmA4CycVOK2I,158
45
+ wbaccounting/serializers/__init__.py,sha256=GPDCuRvKe8k203tq_6hxzWDqmq_WoHTEolstkbBDmi0,576
46
+ wbaccounting/serializers/booking_entry.py,sha256=_JwxbzOHjDLp0qLwg4DCdAA5n_zxAbRIG57yWU_Y8Ao,2723
47
+ wbaccounting/serializers/consolidated_invoice.py,sha256=EoyHhfJ23eC1LNuCaISvhTZbfO1QA_YhmQoduMNT5Eo,4346
48
+ wbaccounting/serializers/entry_accounting_information.py,sha256=iHr2oTnAG_Y_GUW8Av77brsQos5lh8O7kn3stONxYy4,6045
49
+ wbaccounting/serializers/invoice.py,sha256=GnuYUcey9tZp6sGvvT16Uj_UXRsBb5VwXDVnRoYNcdc,3581
50
+ wbaccounting/serializers/invoice_type.py,sha256=I_gKHC6CaEHlAghAKv4PPqYAZZJWrCzEZhTxZqA8hxQ,489
51
+ wbaccounting/serializers/transactions.py,sha256=CEKHkK1SYkWb72BLgketLsLRKJ74GPp8VOkPSIRRwAA,1975
52
+ wbaccounting/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
+ wbaccounting/tests/conftest.py,sha256=wKMgjTqQNvkbgZVf_RtNhVEf8Y1XXUZetjVqBI-M2uA,1874
54
+ wbaccounting/tests/test_processors.py,sha256=_ux2EJJ6XDTfZg9_UACrjAIEXVDHVwao6ydBzliBYi4,761
55
+ wbaccounting/tests/test_displays/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
+ wbaccounting/tests/test_displays/test_booking_entries.py,sha256=Z1qDAsYhkHRs2PwinGGG8R-1MUIYsg0bdCkgMBpJW3U,14
57
+ wbaccounting/tests/test_models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
58
+ wbaccounting/tests/test_models/test_booking_entries.py,sha256=z0BOIrUCUgBiq12HqGm6iYUu54_90HwZoc-Ls3D6NCw,6486
59
+ wbaccounting/tests/test_models/test_entry_accounting_information.py,sha256=CrOJNowjaH2ZQd8XbrpgOn1LIRDFjCaFEit-ZBzs2n8,4472
60
+ wbaccounting/tests/test_models/test_invoice_types.py,sha256=AvrkQFn5ffrYLJdPGlbV8YveeImTJh0mWjoyC0DdDi4,700
61
+ wbaccounting/tests/test_models/test_invoices.py,sha256=wufZdTv8m38SfcQWmJYox0EmKFjImgzxexpP2T7eKAs,3868
62
+ wbaccounting/tests/test_models/test_transactions.py,sha256=DWFePCqwznYKURzRxysaEN2MAQCG0rWrEIgod2Hk7sQ,1719
63
+ wbaccounting/tests/test_serializers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
64
+ wbaccounting/tests/test_serializers/test_booking_entries.py,sha256=fnyap47qoyj8-Mb1I0c2HL1xA3QHcv8g5OFC3klwJBI,2569
65
+ wbaccounting/tests/test_serializers/test_entry_accounting_information.py,sha256=aVINzrnUcWEDJKcBfv2OWGrzpvClx9X8XmoAPOOEvxc,2373
66
+ wbaccounting/tests/test_serializers/test_invoice_types.py,sha256=BN8obY9Z0BeNyyXxzW4aTEx5A90WVYqdonoqB4e4SDs,1214
67
+ wbaccounting/tests/test_serializers/test_transactions.py,sha256=ybp-1gAPb6veiSAwIQVIZ1nLxWt6WU-EkDbtrV2j-5M,3454
68
+ wbaccounting/viewsets/__init__.py,sha256=up2PNbfvbIwpcL8EuefLvldf6hqr2D86MtCKFNH1w04,608
69
+ wbaccounting/viewsets/booking_entry.py,sha256=4JN8zOO5Bazq6CMydQjyhmt3BUKs2HUCPpZhxw_z_lU,2219
70
+ wbaccounting/viewsets/cashflows.py,sha256=yrqxdN4PdTZGaa78eQA8-4sxxBnubyvnefQWDrEbE5w,4760
71
+ wbaccounting/viewsets/entry_accounting_information.py,sha256=NRW9qbYyCin-cm8h0aQ3WSp3CHKyoRKs6JQY8SU0aGM,6024
72
+ wbaccounting/viewsets/invoice.py,sha256=6xwoNmqAv7ScW4KrSFPi0Skm-lBNnZxVxlIF5fElW-w,11668
73
+ wbaccounting/viewsets/invoice_type.py,sha256=yfrZza-M3Nr08tHNITqWOmfwcvf9-8jpxxHou-ofrBI,999
74
+ wbaccounting/viewsets/transactions.py,sha256=YqG7pG6da63nlqYYzzUnpksfU3uuaOH1DH3EPldGeeY,1389
75
+ wbaccounting/viewsets/buttons/__init__.py,sha256=1DWfhlibtxzgNnvAQWjoV6x8WyYT6frg--zAS0j4rmc,207
76
+ wbaccounting/viewsets/buttons/booking_entry.py,sha256=MswXOp6O2PCDWHfoysnTlc3CoY9qa1X1i3rVNjfrnCU,534
77
+ wbaccounting/viewsets/buttons/entry_accounting_information.py,sha256=unXZUDd-hUG2Cx9onVwkC7AvEyoUfdtZOew25kW6zQA,4696
78
+ wbaccounting/viewsets/buttons/invoice.py,sha256=KvpJNLo0XGImxy_r9a8VNdDilJ2-qsgBuf20ABBsy-M,2492
79
+ wbaccounting/viewsets/display/__init__.py,sha256=yM13b6E_-JyECcIw0TvBWeFhoSbPd08fxOqrMLrgzX4,373
80
+ wbaccounting/viewsets/display/booking_entry.py,sha256=SpdGy21wXRd22YedrCrpj3EL_kk7aQL4CxBw1OyqjQI,2633
81
+ wbaccounting/viewsets/display/cashflows.py,sha256=mVPP4sejDMkRl03ZAaZnCW1fa552BY91ufwJyeebFjg,2398
82
+ wbaccounting/viewsets/display/entry_accounting_information.py,sha256=2JofsKjzQpoOSf1Afp-T6kICT7w9Xz8DUA8yj8WkWes,3781
83
+ wbaccounting/viewsets/display/invoice.py,sha256=hxDGFN2t_7bKSrUldEc5KZ2Q81aoxylyYMST9YyFalU,8904
84
+ wbaccounting/viewsets/display/invoice_type.py,sha256=tZVaBOLjNI-PbDBnsTh9GT0TwtX9hcoXR7jGusrcn0Q,642
85
+ wbaccounting/viewsets/display/transactions.py,sha256=di5DKaPfHZl0m8ns0WQ3HG1WCkDUOqS1Rpgd8_saBgQ,1512
86
+ wbaccounting/viewsets/endpoints/__init__.py,sha256=RM-bRpTt7XRE3Glhzj0oc_ZK6kAT83vTBffoljHEqIc,55
87
+ wbaccounting/viewsets/endpoints/invoice.py,sha256=-sIPWerCCrWtKsNX5zTjrKSWvZdP6p4qOGb5NMb7YS8,213
88
+ wbaccounting/viewsets/menu/__init__.py,sha256=t6JpxtH_PxqAK7iZueKJS75jpu4ZhUAEKOwftLqg0xk,300
89
+ wbaccounting/viewsets/menu/booking_entry.py,sha256=ZTn0ksW0EEtON8_0J1ZqW-M1PNnsToFrWpHHRb4nO70,552
90
+ wbaccounting/viewsets/menu/cashflows.py,sha256=WBYucafeYSAkyxD5KGLh2okE-UIYklSUqlMMs7XF2pc,359
91
+ wbaccounting/viewsets/menu/entry_accounting_information.py,sha256=WgiLRFSKHga0GrMeY5vVqRJuvwjQBCD4H7_kUUUjCus,419
92
+ wbaccounting/viewsets/menu/invoice.py,sha256=4AhVeVXSly3lF1_yaiSldy7UAP74uau-iOs-_yvUOi8,527
93
+ wbaccounting/viewsets/menu/invoice_type.py,sha256=fF1Pq-eFIpYs4ei8_Emls8Du32txTqJz9-kdlPgJrZE,557
94
+ wbaccounting/viewsets/menu/transactions.py,sha256=Pvhztgqd4cO0UyTRy917UDaaGLJj38acUO9e664Ut2I,555
95
+ wbaccounting/viewsets/titles/__init__.py,sha256=MiSeieoir_2wdRzVIF3BLO-49O5MLl52DSj7t_xj1io,252
96
+ wbaccounting/viewsets/titles/booking_entry.py,sha256=SNQJOYXf3QcjTtL3Co2uIYZC69dudts9xwKg4kR19Vw,297
97
+ wbaccounting/viewsets/titles/entry_accounting_information.py,sha256=76kXkK5ij-HV29tt8efUuvplQ9d1CrQklT8sSv6zHYE,344
98
+ wbaccounting/viewsets/titles/invoice.py,sha256=mr-iufJpM2SYuvexZ3XPLjBmQgPatgIMn1JC4eHLdnY,566
99
+ wbaccounting/viewsets/titles/invoice_type.py,sha256=szgA0iSYVILjLvhFhuLcq_WhMWv04_ESEVvogMYOkyk,301
100
+ wbaccounting-2.2.1.dist-info/METADATA,sha256=OHMrcNTHBzFj0mHZN_H_eXK-6vb4a-WUW4zVaoQKhnE,280
101
+ wbaccounting-2.2.1.dist-info/WHEEL,sha256=aO3RJuuiFXItVSnAUEmQ0yRBvv9e1sbJh68PtuQkyAE,105
102
+ wbaccounting-2.2.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.26.3
3
+ Root-Is-Purelib: true
4
+ Tag: py2-none-any
5
+ Tag: py3-none-any