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,366 @@
1
+ # Generated by Django 5.0.3 on 2024-04-15 09:05
2
+
3
+ import django.db.models.deletion
4
+ import django_fsm
5
+ import wbaccounting.models.entry_accounting_information
6
+ from django.conf import settings
7
+ from django.db import migrations, models
8
+ from django.db.models import Max
9
+ from django.utils.html import strip_tags
10
+
11
+
12
+ def swap_net_and_gross(apps, schema_editor):
13
+ # We are using the wrong notion for net and gross, we need to swap them
14
+ BookingEntry = apps.get_model("wbaccounting", "BookingEntry")
15
+ for bookingentry in BookingEntry.objects.all():
16
+ bookingentry.net_value, bookingentry.gross_value = bookingentry.gross_value, bookingentry.net_value
17
+ bookingentry.save()
18
+
19
+
20
+ def migrate_dates(apps, schema_editor):
21
+ # We are migrating from/to date from fields to parameters, as they are getting removed
22
+ # Also we are setting the reference date to to date, as it is the last date of the accounting period
23
+ BookingEntry = apps.get_model("wbaccounting", "BookingEntry")
24
+ for bookingentry in BookingEntry.objects.filter(from_date__isnull=False, to_date__isnull=False):
25
+ bookingentry.parameters = {
26
+ "from_date": bookingentry.from_date.strftime("%d.%m.%Y"),
27
+ "to_date": bookingentry.to_date.strftime("%d.%m.%Y"),
28
+ }
29
+ bookingentry.reference_date = bookingentry.to_date
30
+ bookingentry.save()
31
+
32
+ # As the reference date is set for the bookings entries, the
33
+ Invoice = apps.get_model("wbaccounting", "Invoice")
34
+ for invoice in Invoice.objects.filter(booking_entries__isnull=False):
35
+ invoice.reference_date = invoice.booking_entries.aggregate(mv=Max("reference_date")).get("mv")
36
+ invoice.save()
37
+
38
+
39
+ def strip_html_from_subject(apps, schema_editor):
40
+ EntryAccountingInformation = apps.get_model("wbaccounting", "EntryAccountingInformation")
41
+
42
+ for eai in EntryAccountingInformation.objects.all():
43
+ eai.email_subject = strip_tags(eai.email_subject)
44
+ eai.save()
45
+
46
+
47
+ def save_models(apps, schema_editor):
48
+ from wbaccounting.models import BookingEntry
49
+
50
+ for bookingentry in BookingEntry.objects.all():
51
+ bookingentry.save()
52
+
53
+
54
+ class Migration(migrations.Migration):
55
+ dependencies = [
56
+ ("currency", "0001_initial"),
57
+ ("directory", "0007_alter_bankingcontact_options"),
58
+ ("wbaccounting", "0008_alter_invoice_options"),
59
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
60
+ ]
61
+
62
+ operations = [
63
+ migrations.CreateModel(
64
+ name="InvoiceType",
65
+ fields=[
66
+ ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
67
+ ("name", models.CharField(max_length=100, unique=True, verbose_name="Name")),
68
+ ("processor", models.CharField(blank=True, max_length=128, null=True, verbose_name="Processor")),
69
+ ],
70
+ options={
71
+ "verbose_name": "Invoice Type",
72
+ "verbose_name_plural": "Invoice Types",
73
+ },
74
+ ),
75
+ migrations.AlterModelOptions(
76
+ name="bookingentry",
77
+ options={"verbose_name": "Booking", "verbose_name_plural": "Bookings"},
78
+ ),
79
+ migrations.AlterModelOptions(
80
+ name="entryaccountinginformation",
81
+ options={"verbose_name": "Accounting Information", "verbose_name_plural": "Accounting Information"},
82
+ ),
83
+ migrations.AlterModelOptions(
84
+ name="invoice",
85
+ options={
86
+ "permissions": (
87
+ ("can_generate_invoice", "Can Generate Invoice"),
88
+ ("administrate_invoice", "Can administer Invoice"),
89
+ ),
90
+ "verbose_name": "Invoice",
91
+ "verbose_name_plural": "Invoices",
92
+ },
93
+ ),
94
+ migrations.RemoveField(
95
+ model_name="bookingentry",
96
+ name="accrual_date",
97
+ ),
98
+ migrations.RemoveField(
99
+ model_name="bookingentry",
100
+ name="calculated_value",
101
+ ),
102
+ migrations.AddField(
103
+ model_name="bookingentry",
104
+ name="parameters",
105
+ field=models.JSONField(blank=True, null=True),
106
+ ),
107
+ migrations.AddField(
108
+ model_name="bookingentry",
109
+ name="reference_date",
110
+ field=models.DateField(blank=True, null=True, verbose_name="Reference Date"),
111
+ ),
112
+ migrations.AddField(
113
+ model_name="invoice",
114
+ name="reference_date",
115
+ field=models.DateField(blank=True, null=True, verbose_name="Reference Date"),
116
+ ),
117
+ migrations.RunSQL(sql="SET CONSTRAINTS ALL IMMEDIATE;"),
118
+ migrations.RunPython(
119
+ swap_net_and_gross,
120
+ reverse_code=migrations.RunPython.noop,
121
+ ),
122
+ migrations.RunPython(
123
+ migrate_dates,
124
+ reverse_code=migrations.RunPython.noop,
125
+ ),
126
+ migrations.RunSQL(sql="SET CONSTRAINTS ALL DEFERRED;"),
127
+ migrations.RemoveField(
128
+ model_name="bookingentry",
129
+ name="from_date",
130
+ ),
131
+ migrations.RemoveField(
132
+ model_name="bookingentry",
133
+ name="to_date",
134
+ ),
135
+ migrations.RemoveField(
136
+ model_name="bookingentry",
137
+ name="main_booking_entry",
138
+ ),
139
+ migrations.RemoveField(
140
+ model_name="bookingentry",
141
+ name="related_data",
142
+ ),
143
+ migrations.RemoveField(
144
+ model_name="bookingentry",
145
+ name="resolved",
146
+ ),
147
+ migrations.RemoveField(
148
+ model_name="entryaccountinginformation",
149
+ name="label_mapping",
150
+ ),
151
+ migrations.RemoveField(
152
+ model_name="entryaccountinginformation",
153
+ name="social_charges",
154
+ ),
155
+ migrations.AddField(
156
+ model_name="bookingentry",
157
+ name="backlinks",
158
+ field=models.JSONField(blank=True, null=True),
159
+ ),
160
+ migrations.AddField(
161
+ model_name="bookingentry",
162
+ name="due_date",
163
+ field=models.DateField(blank=True, null=True, verbose_name="Due Date"),
164
+ ),
165
+ migrations.AddField(
166
+ model_name="bookingentry",
167
+ name="generator",
168
+ field=models.CharField(blank=True, max_length=256, null=True),
169
+ ),
170
+ migrations.AddField(
171
+ model_name="bookingentry",
172
+ name="invoice_fx_rate",
173
+ field=models.DecimalField(
174
+ blank=True, decimal_places=6, max_digits=20, null=True, verbose_name="Invoice FX Rate"
175
+ ),
176
+ ),
177
+ migrations.AddField(
178
+ model_name="bookingentry",
179
+ name="invoice_gross_value",
180
+ field=models.DecimalField(
181
+ blank=True, decimal_places=4, max_digits=16, null=True, verbose_name="Invoice Gross Value"
182
+ ),
183
+ ),
184
+ migrations.AddField(
185
+ model_name="bookingentry",
186
+ name="invoice_net_value",
187
+ field=models.DecimalField(
188
+ blank=True, decimal_places=4, max_digits=16, null=True, verbose_name="Invoice Net Value"
189
+ ),
190
+ ),
191
+ migrations.AddField(
192
+ model_name="entryaccountinginformation",
193
+ name="booking_entry_generator",
194
+ field=models.CharField(blank=True, max_length=256, null=True),
195
+ ),
196
+ migrations.AddField(
197
+ model_name="entryaccountinginformation",
198
+ name="counterparty_is_private",
199
+ field=models.BooleanField(
200
+ default=False,
201
+ help_text="Hides all of the counterparty's invoices from non-eligible users",
202
+ verbose_name="Counterparty Is Private",
203
+ ),
204
+ ),
205
+ migrations.AddField(
206
+ model_name="entryaccountinginformation",
207
+ name="exempt_users",
208
+ field=models.ManyToManyField(
209
+ blank=True,
210
+ help_text="Exclusion list of users who are able to see private invoices for the counterparty",
211
+ related_name="private_accounting_information",
212
+ to=settings.AUTH_USER_MODEL,
213
+ verbose_name="Exempt Users",
214
+ ),
215
+ ),
216
+ migrations.AddField(
217
+ model_name="entryaccountinginformation",
218
+ name="send_mail",
219
+ field=models.BooleanField(default=True, verbose_name="Send Mail"),
220
+ ),
221
+ migrations.AddField(
222
+ model_name="invoice",
223
+ name="backlinks",
224
+ field=models.JSONField(blank=True, null=True),
225
+ ),
226
+ migrations.AddField(
227
+ model_name="invoice",
228
+ name="gross_value",
229
+ field=models.DecimalField(
230
+ blank=True, decimal_places=4, max_digits=16, null=True, verbose_name="Gross Value"
231
+ ),
232
+ ),
233
+ migrations.AddField(
234
+ model_name="invoice",
235
+ name="net_value",
236
+ field=models.DecimalField(
237
+ blank=True, decimal_places=4, max_digits=16, null=True, verbose_name="Net Value"
238
+ ),
239
+ ),
240
+ migrations.AlterField(
241
+ model_name="entryaccountinginformation",
242
+ name="default_currency",
243
+ field=models.ForeignKey(
244
+ blank=True,
245
+ default=wbaccounting.models.entry_accounting_information.default_currency,
246
+ null=True,
247
+ on_delete=django.db.models.deletion.PROTECT,
248
+ related_name="entry_accounting_informations",
249
+ to="currency.currency",
250
+ verbose_name="Default Currency",
251
+ ),
252
+ ),
253
+ migrations.AlterField(
254
+ model_name="entryaccountinginformation",
255
+ name="email_body",
256
+ field=models.TextField(
257
+ default=wbaccounting.models.entry_accounting_information.default_email_body, verbose_name="Body"
258
+ ),
259
+ ),
260
+ migrations.AlterField(
261
+ model_name="entryaccountinginformation",
262
+ name="entry",
263
+ field=models.OneToOneField(
264
+ on_delete=django.db.models.deletion.CASCADE,
265
+ related_name="entry_accounting_information",
266
+ to="directory.entry",
267
+ verbose_name="Counterparty",
268
+ ),
269
+ ),
270
+ migrations.AlterField(
271
+ model_name="invoice",
272
+ name="status",
273
+ field=django_fsm.FSMField(
274
+ choices=[
275
+ ("DRAFT", "Draft"),
276
+ ("SUBMITTED", "Submitted"),
277
+ ("APPROVED", "Approved"),
278
+ ("CANCELLED", "Cancelled"),
279
+ ("SENT", "Sent"),
280
+ ("PAID", "Paid"),
281
+ ],
282
+ default="DRAFT",
283
+ max_length=50,
284
+ verbose_name="Status",
285
+ ),
286
+ ),
287
+ migrations.AddField(
288
+ model_name="entryaccountinginformation",
289
+ name="default_invoice_type",
290
+ field=models.ForeignKey(
291
+ blank=True,
292
+ help_text="When invoicinging outstanding booking entries, this invoice type will be assigned to the corresponding invoice",
293
+ null=True,
294
+ on_delete=django.db.models.deletion.SET_NULL,
295
+ related_name="booking_entries",
296
+ to="wbaccounting.invoicetype",
297
+ verbose_name="Default Invoice Type",
298
+ ),
299
+ ),
300
+ migrations.AddField(
301
+ model_name="invoice",
302
+ name="invoice_type",
303
+ field=models.ForeignKey(
304
+ null=True,
305
+ on_delete=django.db.models.deletion.PROTECT,
306
+ related_name="invoices",
307
+ to="wbaccounting.invoicetype",
308
+ verbose_name="Type",
309
+ ),
310
+ ),
311
+ migrations.RenameField(
312
+ model_name="bookingentry",
313
+ old_name="resolved_date",
314
+ new_name="payment_date",
315
+ ),
316
+ migrations.AlterField(
317
+ model_name="bookingentry",
318
+ name="payment_date",
319
+ field=models.DateField(blank=True, null=True, verbose_name="Payment Date"),
320
+ ),
321
+ migrations.RemoveField(
322
+ model_name="invoice",
323
+ name="is_counterparty_invoice",
324
+ ),
325
+ migrations.AddField(
326
+ model_name="invoice",
327
+ name="payment_date",
328
+ field=models.DateField(blank=True, null=True, verbose_name="Payment Date"),
329
+ ),
330
+ migrations.AlterField(
331
+ model_name="invoice",
332
+ name="invoice_date",
333
+ field=models.DateField(verbose_name="Invoice Date"),
334
+ ),
335
+ migrations.AlterModelOptions(
336
+ name="entryaccountinginformation",
337
+ options={"verbose_name": "Counterparty", "verbose_name_plural": "Counterparties"},
338
+ ),
339
+ migrations.AlterField(
340
+ model_name="entryaccountinginformation",
341
+ name="email_subject",
342
+ field=models.CharField(default="{{invoice.title}}", max_length=1024, verbose_name="Subject"),
343
+ ),
344
+ migrations.AlterField(
345
+ model_name="entryaccountinginformation",
346
+ name="entry",
347
+ field=models.OneToOneField(
348
+ on_delete=django.db.models.deletion.CASCADE,
349
+ related_name="entry_accounting_information",
350
+ to="directory.entry",
351
+ verbose_name="Linked Counterparty",
352
+ ),
353
+ ),
354
+ migrations.RunSQL(sql="SET CONSTRAINTS ALL IMMEDIATE;"),
355
+ migrations.RunPython(
356
+ strip_html_from_subject,
357
+ reverse_code=migrations.RunPython.noop,
358
+ ),
359
+ migrations.RunSQL(sql="SET CONSTRAINTS ALL DEFERRED;"),
360
+ migrations.RunSQL(sql="SET CONSTRAINTS ALL IMMEDIATE;"),
361
+ migrations.RunPython(
362
+ save_models,
363
+ reverse_code=migrations.RunPython.noop,
364
+ ),
365
+ migrations.RunSQL(sql="SET CONSTRAINTS ALL DEFERRED;"),
366
+ ]
@@ -0,0 +1,20 @@
1
+ # Generated by Django 5.0.3 on 2024-04-29 11:33
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ dependencies = [
8
+ ("wbaccounting", "0009_invoicetype_alter_bookingentry_options_and_more"),
9
+ ]
10
+
11
+ operations = [
12
+ migrations.AlterModelOptions(
13
+ name="bookingentry",
14
+ options={
15
+ "permissions": (("can_generate_booking_entries", "Can Generate Bookings"),),
16
+ "verbose_name": "Booking",
17
+ "verbose_name_plural": "Bookings",
18
+ },
19
+ ),
20
+ ]
@@ -0,0 +1,103 @@
1
+ # Generated by Django 5.0.6 on 2024-06-05 08:07
2
+
3
+ from decimal import Decimal
4
+
5
+ import django.db.models.deletion
6
+ from django.db import migrations, models
7
+
8
+
9
+ class Migration(migrations.Migration):
10
+ dependencies = [
11
+ ("currency", "0001_initial"),
12
+ ("directory", "0007_alter_bankingcontact_options"),
13
+ ("io", "0007_alter_exportsource_query_params"),
14
+ ("wbaccounting", "0010_alter_bookingentry_options"),
15
+ ]
16
+
17
+ operations = [
18
+ migrations.CreateModel(
19
+ name="Transaction",
20
+ fields=[
21
+ ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
22
+ ("booking_date", models.DateField(verbose_name="Booking Date")),
23
+ ("value_date", models.DateField(verbose_name="Value Date")),
24
+ (
25
+ "fx_rate",
26
+ models.DecimalField(decimal_places=4, default=Decimal("1"), max_digits=10, verbose_name="FX Rate"),
27
+ ),
28
+ (
29
+ "value_local_ccy",
30
+ models.DecimalField(
31
+ blank=True, decimal_places=2, max_digits=19, null=True, verbose_name="Value (Local Currency)"
32
+ ),
33
+ ),
34
+ (
35
+ "value",
36
+ models.DecimalField(blank=True, decimal_places=2, max_digits=19, null=True, verbose_name="Value"),
37
+ ),
38
+ ("description", models.TextField(default="")),
39
+ (
40
+ "prenotification",
41
+ models.BooleanField(
42
+ default=False,
43
+ help_text="This field indicates that this transaction will happen sometime in the future.",
44
+ verbose_name="Prenotification",
45
+ ),
46
+ ),
47
+ ("_hash", models.CharField(blank=True, max_length=64, null=True)),
48
+ (
49
+ "bank_account",
50
+ models.ForeignKey(
51
+ on_delete=django.db.models.deletion.PROTECT,
52
+ related_name="wbaccounting_transactions",
53
+ to="directory.bankingcontact",
54
+ verbose_name="Linked Bank Account",
55
+ ),
56
+ ),
57
+ (
58
+ "currency",
59
+ models.ForeignKey(
60
+ blank=True,
61
+ null=True,
62
+ on_delete=django.db.models.deletion.PROTECT,
63
+ related_name="+",
64
+ to="currency.currency",
65
+ verbose_name="Currency",
66
+ ),
67
+ ),
68
+ (
69
+ "from_bank_account",
70
+ models.ForeignKey(
71
+ blank=True,
72
+ null=True,
73
+ on_delete=django.db.models.deletion.PROTECT,
74
+ related_name="+",
75
+ to="directory.bankingcontact",
76
+ verbose_name="Source Bank Account",
77
+ ),
78
+ ),
79
+ (
80
+ "import_source",
81
+ models.ForeignKey(
82
+ blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to="io.importsource"
83
+ ),
84
+ ),
85
+ (
86
+ "to_bank_account",
87
+ models.ForeignKey(
88
+ blank=True,
89
+ null=True,
90
+ on_delete=django.db.models.deletion.PROTECT,
91
+ related_name="+",
92
+ to="directory.bankingcontact",
93
+ verbose_name="Target Bank Account",
94
+ ),
95
+ ),
96
+ ],
97
+ options={
98
+ "verbose_name": "Transaction",
99
+ "verbose_name_plural": "Transactions",
100
+ "default_related_name": "transactions",
101
+ },
102
+ ),
103
+ ]
@@ -0,0 +1,25 @@
1
+ # Generated by Django 5.0.8 on 2024-09-18 08:57
2
+
3
+ from django.conf import settings
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+ dependencies = [
9
+ ("wbaccounting", "0011_transaction"),
10
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AddField(
15
+ model_name="entryaccountinginformation",
16
+ name="external_invoice_users",
17
+ field=models.ManyToManyField(
18
+ blank=True,
19
+ help_text="External users who are able to see the invoices generated for this counterparty",
20
+ related_name="external_accounting_information",
21
+ to=settings.AUTH_USER_MODEL,
22
+ verbose_name="External User",
23
+ ),
24
+ ),
25
+ ]
File without changes
@@ -0,0 +1,6 @@
1
+ from .booking_entry import BookingEntry
2
+ from .invoice import Invoice
3
+ from .invoice_type import InvoiceType
4
+ from .entry_accounting_information import EntryAccountingInformation
5
+ from .transactions import Transaction
6
+ from .model_tasks import submit_invoices_as_task
@@ -0,0 +1,167 @@
1
+ from decimal import Decimal
2
+
3
+ from django.db import models
4
+ from django.db.models.signals import post_delete, post_save
5
+ from django.dispatch import receiver
6
+ from wbcore.contrib.authentication.models import User
7
+ from wbcore.models import WBModel
8
+
9
+
10
+ class BookingEntryDefaultQuerySet(models.QuerySet):
11
+ def filter_for_user(self, user: User) -> models.QuerySet:
12
+ """
13
+ Filters booking entries based on if the current user can see the invoice.
14
+
15
+ Args:
16
+ user (User): The user for whom booking entries need to be filtered.
17
+
18
+ Returns:
19
+ QuerySet: A filtered queryset.
20
+ """
21
+
22
+ # Superuser and users with the admin permission can see all booking entries
23
+ if user.is_superuser or user.has_perm("wbaccounting.administrate_invoice"):
24
+ return self
25
+
26
+ # If the user doesn't have the view permission, nothing can be seen
27
+ if not user.has_perm("wbaccounting.view_bookingentry"):
28
+ return self.none()
29
+
30
+ # The user can see all booking entries where the counterparty is not private
31
+ # or where the user is part of the exempt users list
32
+ return self.filter(
33
+ models.Q(counterparty__entry_accounting_information__counterparty_is_private=False)
34
+ | models.Q(counterparty__entry_accounting_information__exempt_users=user)
35
+ )
36
+
37
+
38
+ class BookingEntry(WBModel):
39
+ class Meta:
40
+ verbose_name = "Booking"
41
+ verbose_name_plural = "Bookings"
42
+
43
+ permissions = (
44
+ (
45
+ "can_generate_booking_entries",
46
+ "Can Generate Bookings",
47
+ ),
48
+ )
49
+
50
+ def __str__(self):
51
+ return self.title
52
+
53
+ def save(self, *args, **kwargs):
54
+ # Reference date defaults to booking date if not specified
55
+ if not self.reference_date:
56
+ self.reference_date = self.booking_date
57
+
58
+ # If net value is specified, then gross value is determined based on VAT
59
+ if self.net_value:
60
+ self.gross_value = self.net_value + (self.net_value * self.vat)
61
+ # If net value is not specified, but gross value is, then net value is determined based on VAT
62
+ elif self.gross_value:
63
+ self.net_value = self.gross_value / (Decimal(1 + self.vat))
64
+ # If neither is defined the both are 0
65
+ else:
66
+ self.net_value, self.gross_value = 0, 0
67
+
68
+ # If an invoice is attached we need to compute net and gross value in the invoice currency
69
+ if self.invoice:
70
+ # We get the fx rate (If it is the same currency, then it will be 1)
71
+ self.invoice_fx_rate = self.currency.convert(self.booking_date, self.invoice.invoice_currency)
72
+ self.invoice_net_value = self.net_value * self.invoice_fx_rate
73
+ self.invoice_gross_value = self.gross_value * self.invoice_fx_rate
74
+
75
+ super().save(*args, **kwargs)
76
+
77
+ objects = BookingEntryDefaultQuerySet.as_manager()
78
+
79
+ # The title of the booking entry which describes what was booked
80
+ title = models.CharField(max_length=255, verbose_name="Title")
81
+
82
+ # The date when this booking entry is booked. Most likely the current date
83
+ booking_date = models.DateField(verbose_name="Booking Date")
84
+
85
+ # The date when this booking entry has to be paid / received. If None, this date is not important.
86
+ due_date = models.DateField(null=True, blank=True, verbose_name="Due Date")
87
+
88
+ # The date when this booking entry was paid / received. If None, this booking entry is still "open"
89
+ payment_date = models.DateField(null=True, blank=True, verbose_name="Payment Date")
90
+
91
+ # The reference date represents a date to which accounting period a booking entry belongs
92
+ reference_date = models.DateField(verbose_name="Reference Date", null=True, blank=True)
93
+
94
+ # Either Gross or Net has to be specified. If both are specifying then net is preferred.
95
+ # If only one is specified the other one is determined based on the given VAT. The default
96
+ # VAT is 0%. If the value of net/gross is negative, then money is paid.
97
+ gross_value = models.DecimalField(
98
+ max_digits=16, decimal_places=4, null=True, blank=True, verbose_name="Gross Value"
99
+ )
100
+ net_value = models.DecimalField(max_digits=16, decimal_places=4, null=True, blank=True, verbose_name="Net Value")
101
+ invoice_gross_value = models.DecimalField(
102
+ max_digits=16, decimal_places=4, null=True, blank=True, verbose_name="Invoice Gross Value"
103
+ )
104
+ invoice_net_value = models.DecimalField(
105
+ max_digits=16, decimal_places=4, null=True, blank=True, verbose_name="Invoice Net Value"
106
+ )
107
+ invoice_fx_rate = models.DecimalField(
108
+ max_digits=20, decimal_places=6, null=True, blank=True, verbose_name="Invoice FX Rate"
109
+ )
110
+ vat = models.DecimalField(max_digits=4, decimal_places=4, default=Decimal(0), verbose_name="VAT")
111
+
112
+ # The currency of the booking entry
113
+ currency = models.ForeignKey(
114
+ "currency.Currency", related_name="booking_entries", on_delete=models.PROTECT, verbose_name="Currency"
115
+ )
116
+
117
+ invoice = models.ForeignKey(
118
+ "Invoice",
119
+ related_name="booking_entries",
120
+ null=True,
121
+ blank=True,
122
+ on_delete=models.SET_NULL,
123
+ verbose_name="Invoice",
124
+ )
125
+
126
+ # The entry who has to pay / receive money
127
+ counterparty = models.ForeignKey(
128
+ "directory.Entry", related_name="booking_entries", on_delete=models.PROTECT, verbose_name="Counterparty"
129
+ )
130
+
131
+ # The backlinks stores information and the destination where to find the underlying data
132
+ # The backlinks is rendered in lists and instances as a button
133
+ backlinks = models.JSONField(null=True, blank=True)
134
+
135
+ # Extra parameters for rendering on an invoice
136
+ parameters = models.JSONField(null=True, blank=True)
137
+
138
+ # Generator that produced this Booking Entry
139
+ generator = models.CharField(max_length=256, null=True, blank=True)
140
+
141
+ @classmethod
142
+ def get_endpoint_basename(cls):
143
+ return "wbaccounting:bookingentry"
144
+
145
+ @classmethod
146
+ def get_representation_value_key(cls):
147
+ return "id"
148
+
149
+ @classmethod
150
+ def get_representation_label_key(cls):
151
+ return "{{title}}"
152
+
153
+ @classmethod
154
+ def get_representation_endpoint(cls):
155
+ return "wbaccounting:bookingentryrepresentation-list"
156
+
157
+
158
+ @receiver(post_save, sender=BookingEntry)
159
+ def booking_entry_changed(sender, instance: BookingEntry, created: bool, raw: bool, **kwargs):
160
+ if not raw and (invoice := instance.invoice):
161
+ invoice.save()
162
+
163
+
164
+ @receiver(post_delete, sender=BookingEntry)
165
+ def booking_entry_deleted(sender, instance, **kwargs):
166
+ if instance.invoice:
167
+ instance.invoice.save()