netbox-contract 2.2.2__tar.gz → 2.2.4__tar.gz
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.
- {netbox_contract-2.2.2/src/netbox_contract.egg-info → netbox_contract-2.2.4}/PKG-INFO +1 -1
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/pyproject.toml +1 -1
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/__init__.py +1 -1
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/api/serializers.py +162 -7
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/forms.py +31 -4
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/templates/netbox_contract/contract.html +29 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/templates/netbox_contract/invoice.html +5 -5
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/views.py +22 -4
- {netbox_contract-2.2.2 → netbox_contract-2.2.4/src/netbox_contract.egg-info}/PKG-INFO +1 -1
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/LICENSE +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/MANIFEST.in +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/README.md +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/setup.cfg +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/setup.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/api/__init__.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/api/urls.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/api/views.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/constants.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/filtersets.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0001_initial.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0002_alter_contract_circuit_and_more.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0003_alter_contract_custom_field_data_and_more.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0004_contract_currency_invoice_currency.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0005_contract_accounting_dimensions_and_more.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0006_alter_contract_circuit.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0007_invoice_date.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0008_invoice_comments.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0009_contract_external_reference.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0010_invoice_contracts.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0011_auto_20230122_2112.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0012_remove_invoice_contract.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0013_contract_documents_invoice_documents.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0014_contract_end_date.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0015_contractassignement.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0016_contract_parent.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0017_alter_contract_accounting_dimensions.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0018_contract_external_partie_object_id_and_more.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0019_auto_20230924_1813.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0020_alter_contract_external_partie.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0021_alter_contract_external_partie.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0022_alter_contract_internal_partie_alter_contract_parent.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0023_rename_contractassignement_contractassignment_and_more.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0024_remove_contract_external_partie.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0025_remove_contract_circuit_and_more.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0026_auto_20240421_1550.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0027_invoiceline.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0028_invoiceline_accounting_dimensions.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0029_remove_invoiceline_accounting_dimensions_and_more.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0030_alter_invoiceline_accounting_dimensions.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0031_contract_invoice_template_invoice_template.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0032_alter_invoice_period_end_alter_invoice_period_start_and_more.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0033_remove_contract_invoice_template.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0034_contract_yrc_alter_contract_mrc.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0035_accountingdimension_status_and_more.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0036_alter_invoiceline_accounting_dimensions.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/__init__.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/models.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/navigation.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/search.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/tables.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/template_content.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/templates/contract_assignments_bottom.html +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/templates/contract_list_bottom.html +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/templates/netbox_contract/accountingdimension.html +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/templates/netbox_contract/contractassignment.html +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/templates/netbox_contract/invoiceline.html +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/templates/netbox_contract/serviceprovider.html +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/urls.py +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract.egg-info/SOURCES.txt +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract.egg-info/dependency_links.txt +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract.egg-info/not-zip-safe +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract.egg-info/requires.txt +0 -0
- {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract.egg-info/top_level.txt +0 -0
|
@@ -5,7 +5,7 @@ class ContractsConfig(PluginConfig):
|
|
|
5
5
|
name = 'netbox_contract'
|
|
6
6
|
verbose_name = 'Netbox contract'
|
|
7
7
|
description = 'Contract management plugin for Netbox'
|
|
8
|
-
version = '2.2.
|
|
8
|
+
version = '2.2.4'
|
|
9
9
|
author = 'Marc Lebreuil'
|
|
10
10
|
author_email = 'marc@famillelebreuil.net'
|
|
11
11
|
base_url = 'contracts'
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from django.contrib.auth.models import ContentType
|
|
2
|
+
from django.core.exceptions import ObjectDoesNotExist
|
|
2
3
|
from drf_yasg.utils import swagger_serializer_method
|
|
3
|
-
from netbox.api.fields import ContentTypeField
|
|
4
|
+
from netbox.api.fields import ContentTypeField, SerializedPKRelatedField
|
|
4
5
|
from netbox.api.serializers import NetBoxModelSerializer, WritableNestedSerializer
|
|
5
6
|
from netbox.constants import NESTED_SERIALIZER_PREFIX
|
|
6
7
|
from rest_framework import serializers
|
|
@@ -31,10 +32,46 @@ class NestedContractSerializer(WritableNestedSerializer):
|
|
|
31
32
|
url = serializers.HyperlinkedIdentityField(
|
|
32
33
|
view_name='plugins-api:netbox_contract-api:contract-detail'
|
|
33
34
|
)
|
|
35
|
+
yrc = serializers.DecimalField(max_digits=10, decimal_places=2, read_only=True)
|
|
36
|
+
tenant = NestedTenantSerializer(many=False, required=False)
|
|
37
|
+
external_partie_object_type = ContentTypeField(queryset=ContentType.objects.all())
|
|
38
|
+
external_partie_object = serializers.SerializerMethodField(read_only=True)
|
|
34
39
|
|
|
35
40
|
class Meta:
|
|
36
41
|
model = Contract
|
|
37
|
-
fields =
|
|
42
|
+
fields = fields = (
|
|
43
|
+
'id',
|
|
44
|
+
'url',
|
|
45
|
+
'display',
|
|
46
|
+
'name',
|
|
47
|
+
'external_partie_object_type',
|
|
48
|
+
'external_partie_object_id',
|
|
49
|
+
'external_partie_object',
|
|
50
|
+
'external_reference',
|
|
51
|
+
'internal_partie',
|
|
52
|
+
'tenant',
|
|
53
|
+
'status',
|
|
54
|
+
'start_date',
|
|
55
|
+
'end_date',
|
|
56
|
+
'initial_term',
|
|
57
|
+
'renewal_term',
|
|
58
|
+
'currency',
|
|
59
|
+
'accounting_dimensions',
|
|
60
|
+
'mrc',
|
|
61
|
+
'yrc',
|
|
62
|
+
'nrc',
|
|
63
|
+
'invoice_frequency',
|
|
64
|
+
'comments',
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
@swagger_serializer_method(serializer_or_field=serializers.JSONField)
|
|
68
|
+
def get_external_partie_object(self, instance):
|
|
69
|
+
serializer = get_serializer_for_model(
|
|
70
|
+
instance.external_partie_object_type.model_class(),
|
|
71
|
+
prefix=NESTED_SERIALIZER_PREFIX,
|
|
72
|
+
)
|
|
73
|
+
context = {'request': self.context['request']}
|
|
74
|
+
return serializer(instance.external_partie_object, context=context).data
|
|
38
75
|
|
|
39
76
|
|
|
40
77
|
class NestedInvoiceSerializer(WritableNestedSerializer):
|
|
@@ -134,8 +171,25 @@ class ContractSerializer(NetBoxModelSerializer):
|
|
|
134
171
|
'url',
|
|
135
172
|
'display',
|
|
136
173
|
'name',
|
|
174
|
+
'external_partie_object_type',
|
|
175
|
+
'external_partie_object_id',
|
|
137
176
|
'external_partie_object',
|
|
177
|
+
'external_reference',
|
|
178
|
+
'internal_partie',
|
|
179
|
+
'tenant',
|
|
138
180
|
'status',
|
|
181
|
+
'start_date',
|
|
182
|
+
'end_date',
|
|
183
|
+
'initial_term',
|
|
184
|
+
'renewal_term',
|
|
185
|
+
'currency',
|
|
186
|
+
'accounting_dimensions',
|
|
187
|
+
'mrc',
|
|
188
|
+
'yrc',
|
|
189
|
+
'nrc',
|
|
190
|
+
'invoice_frequency',
|
|
191
|
+
'comments',
|
|
192
|
+
'parent',
|
|
139
193
|
)
|
|
140
194
|
|
|
141
195
|
@swagger_serializer_method(serializer_or_field=serializers.JSONField)
|
|
@@ -152,7 +206,12 @@ class InvoiceSerializer(NetBoxModelSerializer):
|
|
|
152
206
|
url = serializers.HyperlinkedIdentityField(
|
|
153
207
|
view_name='plugins-api:netbox_contract-api:invoice-detail'
|
|
154
208
|
)
|
|
155
|
-
contracts =
|
|
209
|
+
contracts = SerializedPKRelatedField(
|
|
210
|
+
queryset=Contract.objects.all(),
|
|
211
|
+
serializer=ContractSerializer,
|
|
212
|
+
required=False,
|
|
213
|
+
many=True,
|
|
214
|
+
)
|
|
156
215
|
|
|
157
216
|
class Meta:
|
|
158
217
|
model = Invoice
|
|
@@ -162,6 +221,7 @@ class InvoiceSerializer(NetBoxModelSerializer):
|
|
|
162
221
|
'display',
|
|
163
222
|
'number',
|
|
164
223
|
'date',
|
|
224
|
+
'template',
|
|
165
225
|
'contracts',
|
|
166
226
|
'period_start',
|
|
167
227
|
'period_end',
|
|
@@ -174,7 +234,87 @@ class InvoiceSerializer(NetBoxModelSerializer):
|
|
|
174
234
|
'created',
|
|
175
235
|
'last_updated',
|
|
176
236
|
)
|
|
177
|
-
brief_fields = (
|
|
237
|
+
brief_fields = (
|
|
238
|
+
'id',
|
|
239
|
+
'url',
|
|
240
|
+
'display',
|
|
241
|
+
'number',
|
|
242
|
+
'date',
|
|
243
|
+
'template',
|
|
244
|
+
'contracts',
|
|
245
|
+
'period_start',
|
|
246
|
+
'period_end',
|
|
247
|
+
'currency',
|
|
248
|
+
'accounting_dimensions',
|
|
249
|
+
'amount',
|
|
250
|
+
'comments',
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
def validate(self, data):
|
|
254
|
+
data = super().validate(data)
|
|
255
|
+
|
|
256
|
+
# template checks
|
|
257
|
+
if data['template']:
|
|
258
|
+
# Check that there is only one invoice template per contract
|
|
259
|
+
contracts = data['contracts']
|
|
260
|
+
for contract in contracts:
|
|
261
|
+
for invoice in contract.invoices.all():
|
|
262
|
+
if invoice.template and invoice != self.instance:
|
|
263
|
+
raise serializers.ValidationError(
|
|
264
|
+
'Only one invoice template allowed per contract'
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
# Prefix the invoice name with _template
|
|
268
|
+
data['number'] = '_invoice_template_' + contract.name
|
|
269
|
+
|
|
270
|
+
# set the periode start and end date to null
|
|
271
|
+
data['period_start'] = None
|
|
272
|
+
data['period_end'] = None
|
|
273
|
+
return data
|
|
274
|
+
|
|
275
|
+
def create(self, validated_data):
|
|
276
|
+
instance = super().create(validated_data)
|
|
277
|
+
|
|
278
|
+
if not instance.template:
|
|
279
|
+
contracts = instance.contracts.all()
|
|
280
|
+
|
|
281
|
+
for contract in contracts:
|
|
282
|
+
try:
|
|
283
|
+
template_exists = True
|
|
284
|
+
invoice_template = Invoice.objects.get(
|
|
285
|
+
template=True, contracts=contract
|
|
286
|
+
)
|
|
287
|
+
except ObjectDoesNotExist:
|
|
288
|
+
template_exists = False
|
|
289
|
+
|
|
290
|
+
if template_exists:
|
|
291
|
+
first = True
|
|
292
|
+
for line in invoice_template.invoicelines.all():
|
|
293
|
+
dimensions = line.accounting_dimensions.all()
|
|
294
|
+
line.pk = None
|
|
295
|
+
line.id = None
|
|
296
|
+
line._state.adding = True
|
|
297
|
+
line.invoice = instance
|
|
298
|
+
|
|
299
|
+
# adjust the first invoice line amount
|
|
300
|
+
amount = validated_data['amount']
|
|
301
|
+
if (
|
|
302
|
+
first
|
|
303
|
+
and amount != invoice_template.total_invoicelines_amount
|
|
304
|
+
):
|
|
305
|
+
line.amount = (
|
|
306
|
+
line.amount
|
|
307
|
+
+ amount
|
|
308
|
+
- invoice_template.total_invoicelines_amount
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
line.save()
|
|
312
|
+
|
|
313
|
+
for dimension in dimensions:
|
|
314
|
+
line.accounting_dimensions.add(dimension)
|
|
315
|
+
first = False
|
|
316
|
+
|
|
317
|
+
return instance
|
|
178
318
|
|
|
179
319
|
|
|
180
320
|
class ServiceProviderSerializer(NetBoxModelSerializer):
|
|
@@ -235,8 +375,11 @@ class InvoiceLineSerializer(NetBoxModelSerializer):
|
|
|
235
375
|
view_name='plugins-api:netbox_contract-api:invoiceline-detail'
|
|
236
376
|
)
|
|
237
377
|
invoice = NestedInvoiceSerializer(many=False, required=False)
|
|
238
|
-
accounting_dimensions =
|
|
239
|
-
|
|
378
|
+
accounting_dimensions = SerializedPKRelatedField(
|
|
379
|
+
queryset=AccountingDimension.objects.all(),
|
|
380
|
+
serializer=NestedAccountingDimensionSerializer,
|
|
381
|
+
required=False,
|
|
382
|
+
many=True,
|
|
240
383
|
)
|
|
241
384
|
|
|
242
385
|
class Meta:
|
|
@@ -264,6 +407,18 @@ class InvoiceLineSerializer(NetBoxModelSerializer):
|
|
|
264
407
|
'name',
|
|
265
408
|
)
|
|
266
409
|
|
|
410
|
+
def validate(self, data):
|
|
411
|
+
super().validate(data)
|
|
412
|
+
# check for duplicate dimensions
|
|
413
|
+
accounting_dimensions = data['accounting_dimensions']
|
|
414
|
+
dimensions_names = []
|
|
415
|
+
for dimension in accounting_dimensions:
|
|
416
|
+
if dimension.name in dimensions_names:
|
|
417
|
+
raise serializers.ValidationError('duplicate accounting dimension')
|
|
418
|
+
else:
|
|
419
|
+
dimensions_names.append(dimension.name)
|
|
420
|
+
return data
|
|
421
|
+
|
|
267
422
|
|
|
268
423
|
class AccountingDimensionSerializer(NetBoxModelSerializer):
|
|
269
424
|
url = serializers.HyperlinkedIdentityField(
|
|
@@ -284,4 +439,4 @@ class AccountingDimensionSerializer(NetBoxModelSerializer):
|
|
|
284
439
|
'created',
|
|
285
440
|
'last_updated',
|
|
286
441
|
)
|
|
287
|
-
brief_fields = ('name', 'value', 'url', 'display')
|
|
442
|
+
brief_fields = ('id', 'name', 'value', 'url', 'display')
|
|
@@ -16,6 +16,7 @@ from utilities.forms.fields import (
|
|
|
16
16
|
CSVChoiceField,
|
|
17
17
|
CSVContentTypeField,
|
|
18
18
|
CSVModelChoiceField,
|
|
19
|
+
CSVModelMultipleChoiceField,
|
|
19
20
|
DynamicModelChoiceField,
|
|
20
21
|
DynamicModelMultipleChoiceField,
|
|
21
22
|
JSONField,
|
|
@@ -29,6 +30,7 @@ from .models import (
|
|
|
29
30
|
AccountingDimensionStatusChoices,
|
|
30
31
|
Contract,
|
|
31
32
|
ContractAssignment,
|
|
33
|
+
CurrencyChoices,
|
|
32
34
|
InternalEntityChoices,
|
|
33
35
|
Invoice,
|
|
34
36
|
InvoiceLine,
|
|
@@ -226,13 +228,17 @@ class ContractCSVForm(NetBoxModelImportForm):
|
|
|
226
228
|
|
|
227
229
|
class ContractBulkEditForm(NetBoxModelBulkEditForm):
|
|
228
230
|
name = forms.CharField(max_length=100, required=False)
|
|
229
|
-
|
|
230
231
|
external_reference = forms.CharField(max_length=100, required=False)
|
|
231
232
|
internal_partie = forms.ChoiceField(choices=InternalEntityChoices, required=False)
|
|
233
|
+
tenant = DynamicModelChoiceField(queryset=Tenant.objects.all(), required=False)
|
|
234
|
+
accounting_dimensions = Dimensions(required=False)
|
|
232
235
|
comments = CommentField(required=False)
|
|
233
236
|
parent = DynamicModelChoiceField(queryset=Contract.objects.all(), required=False)
|
|
234
237
|
|
|
235
|
-
nullable_fields = (
|
|
238
|
+
nullable_fields = (
|
|
239
|
+
'accounting_dimensions',
|
|
240
|
+
'comments',
|
|
241
|
+
)
|
|
236
242
|
model = Contract
|
|
237
243
|
|
|
238
244
|
|
|
@@ -240,6 +246,10 @@ class ContractBulkEditForm(NetBoxModelBulkEditForm):
|
|
|
240
246
|
|
|
241
247
|
|
|
242
248
|
class InvoiceForm(NetBoxModelForm):
|
|
249
|
+
number = forms.CharField(
|
|
250
|
+
max_length=100,
|
|
251
|
+
help_text='Invoice template name will be overriden to _invoice_template_contract name',
|
|
252
|
+
)
|
|
243
253
|
contracts = DynamicModelMultipleChoiceField(
|
|
244
254
|
queryset=Contract.objects.all(), required=False
|
|
245
255
|
)
|
|
@@ -381,9 +391,23 @@ class InvoiceCSVForm(NetBoxModelImportForm):
|
|
|
381
391
|
|
|
382
392
|
class InvoiceBulkEditForm(NetBoxModelBulkEditForm):
|
|
383
393
|
number = forms.CharField(max_length=100, required=True)
|
|
394
|
+
template = forms.BooleanField(required=False)
|
|
395
|
+
date = forms.DateField(required=False)
|
|
384
396
|
contracts = DynamicModelMultipleChoiceField(
|
|
385
397
|
queryset=Contract.objects.all(), required=False
|
|
386
398
|
)
|
|
399
|
+
period_start = forms.DateField(required=False)
|
|
400
|
+
period_end = forms.DateField(required=False)
|
|
401
|
+
currency = forms.ChoiceField(choices=CurrencyChoices, required=False)
|
|
402
|
+
accounting_dimensions = Dimensions(required=False)
|
|
403
|
+
amount = forms.DecimalField(max_digits=10, decimal_places=2, required=False)
|
|
404
|
+
documents = forms.URLField(required=False)
|
|
405
|
+
comments = CommentField()
|
|
406
|
+
nullable_fields = (
|
|
407
|
+
'accounting_dimensions',
|
|
408
|
+
'comments',
|
|
409
|
+
)
|
|
410
|
+
|
|
387
411
|
model = Invoice
|
|
388
412
|
|
|
389
413
|
|
|
@@ -459,7 +483,7 @@ class ContractAssignmentImportForm(NetBoxModelImportForm):
|
|
|
459
483
|
|
|
460
484
|
class InvoiceLineForm(NetBoxModelForm):
|
|
461
485
|
invoice = DynamicModelChoiceField(queryset=Invoice.objects.all())
|
|
462
|
-
accounting_dimensions =
|
|
486
|
+
accounting_dimensions = DynamicModelMultipleChoiceField(
|
|
463
487
|
queryset=AccountingDimension.objects.all(), required=False
|
|
464
488
|
)
|
|
465
489
|
|
|
@@ -490,6 +514,9 @@ class InvoiceLineForm(NetBoxModelForm):
|
|
|
490
514
|
class InvoiceLineFilterSetForm(NetBoxModelFilterSetForm):
|
|
491
515
|
model = InvoiceLine
|
|
492
516
|
invoice = DynamicModelChoiceField(queryset=Invoice.objects.all(), required=False)
|
|
517
|
+
accounting_dimensions = DynamicModelChoiceField(
|
|
518
|
+
queryset=AccountingDimension.objects.all(), required=False
|
|
519
|
+
)
|
|
493
520
|
|
|
494
521
|
|
|
495
522
|
class InvoiceLineImportForm(NetBoxModelImportForm):
|
|
@@ -498,7 +525,7 @@ class InvoiceLineImportForm(NetBoxModelImportForm):
|
|
|
498
525
|
to_field_name='number',
|
|
499
526
|
help_text='Invoice number',
|
|
500
527
|
)
|
|
501
|
-
accounting_dimensions =
|
|
528
|
+
accounting_dimensions = CSVModelMultipleChoiceField(
|
|
502
529
|
queryset=AccountingDimension.objects.all(),
|
|
503
530
|
to_field_name='id',
|
|
504
531
|
help_text='accounting dimension id',
|
|
@@ -120,6 +120,33 @@
|
|
|
120
120
|
<div class="col col-md-6">
|
|
121
121
|
{% include 'inc/panels/tags.html' %}
|
|
122
122
|
{% include 'inc/panels/comments.html' %}
|
|
123
|
+
{% if invoice_template %}
|
|
124
|
+
<div class="card">
|
|
125
|
+
<h5 class="card-header">
|
|
126
|
+
Invoice template
|
|
127
|
+
</h5>
|
|
128
|
+
<table class="table table-hover attr-table">
|
|
129
|
+
<tr>
|
|
130
|
+
<th scope="row">Number</th>
|
|
131
|
+
<td>
|
|
132
|
+
<a href="{{ invoice_template.get_absolute_url }}">{{ invoice_template.number }}</a>
|
|
133
|
+
</td>
|
|
134
|
+
</tr>
|
|
135
|
+
<tr>
|
|
136
|
+
<th scope="row">Currency</th>
|
|
137
|
+
<td>{{ invoice_template.currency }}</td>
|
|
138
|
+
</tr>
|
|
139
|
+
<tr>
|
|
140
|
+
<th scope="row">Total amount</th>
|
|
141
|
+
<td>{{ invoice_template.total_invoicelines_amount }}</td>
|
|
142
|
+
</tr>
|
|
143
|
+
</table>
|
|
144
|
+
<h5 class="card-header">
|
|
145
|
+
Invoice template lines
|
|
146
|
+
</h5>
|
|
147
|
+
{% render_table invoicelines_table %}
|
|
148
|
+
</div>
|
|
149
|
+
{% endif %}
|
|
123
150
|
</div>
|
|
124
151
|
</div>
|
|
125
152
|
<div class="row">
|
|
@@ -130,6 +157,7 @@
|
|
|
130
157
|
</div>
|
|
131
158
|
</div>
|
|
132
159
|
</div>
|
|
160
|
+
{% if childs_table %}
|
|
133
161
|
<div class="row">
|
|
134
162
|
<div class="col col-md-12">
|
|
135
163
|
<div class="card">
|
|
@@ -138,6 +166,7 @@
|
|
|
138
166
|
</div>
|
|
139
167
|
</div>
|
|
140
168
|
</div>
|
|
169
|
+
{% endif %}
|
|
141
170
|
{% if perms.netbox_contract.view_invoice %}
|
|
142
171
|
<div class="row">
|
|
143
172
|
<div class="col col-md-12">
|
|
@@ -13,16 +13,16 @@
|
|
|
13
13
|
<div class="row mb-3">
|
|
14
14
|
<div class="col col-md-6">
|
|
15
15
|
<div class="card">
|
|
16
|
-
|
|
16
|
+
{% if object.template %}
|
|
17
|
+
<h5 class="card-header">Invoice template</h5>
|
|
18
|
+
{% else %}
|
|
19
|
+
<h5 class="card-header">Invoice</h5>
|
|
20
|
+
{% endif %}
|
|
17
21
|
<table class="table table-hover attr-table">
|
|
18
22
|
<tr>
|
|
19
23
|
<th scope="row">Number</th>
|
|
20
24
|
<td>{{ object.number }}</td>
|
|
21
25
|
</tr>
|
|
22
|
-
<tr>
|
|
23
|
-
<th scope="row">Template</th>
|
|
24
|
-
<td>{{ object.template }}</td>
|
|
25
|
-
</tr>
|
|
26
26
|
<tr>
|
|
27
27
|
<th scope="row">Date</th>
|
|
28
28
|
<td>{{ object.date }}</td>
|
|
@@ -138,20 +138,38 @@ class ContractView(generic.ObjectView):
|
|
|
138
138
|
)
|
|
139
139
|
|
|
140
140
|
def get_extra_context(self, request, instance):
|
|
141
|
-
invoices_table = tables.InvoiceListTable(
|
|
141
|
+
invoices_table = tables.InvoiceListTable(
|
|
142
|
+
instance.invoices.exclude(template=True)
|
|
143
|
+
)
|
|
144
|
+
invoices_table.columns.hide('contracts')
|
|
142
145
|
invoices_table.configure(request)
|
|
143
146
|
assignments_table = tables.ContractAssignmentContractTable(
|
|
144
147
|
instance.assignments.all()
|
|
145
148
|
)
|
|
149
|
+
invoice_template = instance.invoices.filter(template=True).first()
|
|
150
|
+
if invoice_template:
|
|
151
|
+
invoicelines_table = tables.InvoiceLineListTable(
|
|
152
|
+
invoice_template.invoicelines.all()
|
|
153
|
+
)
|
|
154
|
+
invoicelines_table.columns.hide('invoice')
|
|
155
|
+
invoicelines_table.columns.hide('currency')
|
|
156
|
+
invoicelines_table.configure(request)
|
|
157
|
+
else:
|
|
158
|
+
invoicelines_table = None
|
|
146
159
|
assignments_table.configure(request)
|
|
147
|
-
|
|
148
|
-
|
|
160
|
+
if instance.childs.all():
|
|
161
|
+
childs_table = tables.ContractListBottomTable(instance.childs.all())
|
|
162
|
+
childs_table.configure(request)
|
|
163
|
+
else:
|
|
164
|
+
childs_table = None
|
|
149
165
|
|
|
150
166
|
hidden_fields = plugin_settings.get('hidden_contract_fields')
|
|
151
167
|
|
|
152
168
|
return {
|
|
153
169
|
'hidden_fields': hidden_fields,
|
|
154
170
|
'invoices_table': invoices_table,
|
|
171
|
+
'invoice_template': invoice_template,
|
|
172
|
+
'invoicelines_table': invoicelines_table,
|
|
155
173
|
'assignments_table': assignments_table,
|
|
156
174
|
'childs_table': childs_table,
|
|
157
175
|
}
|
|
@@ -278,7 +296,7 @@ class InvoiceEditView(generic.ObjectEditView):
|
|
|
278
296
|
contract = Contract.objects.get(pk=initial_data['contracts'])
|
|
279
297
|
|
|
280
298
|
try:
|
|
281
|
-
last_invoice = contract.invoices.
|
|
299
|
+
last_invoice = contract.invoices.exclude(template=True).latest(
|
|
282
300
|
'period_end'
|
|
283
301
|
)
|
|
284
302
|
new_period_start = last_invoice.period_end + timedelta(days=1)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0001_initial.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0007_invoice_date.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0027_invoiceline.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|