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,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()
|