netbox-contract 2.2.3__tar.gz → 2.2.5__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.3/src/netbox_contract.egg-info → netbox_contract-2.2.5}/PKG-INFO +4 -3
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/README.md +3 -2
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/pyproject.toml +1 -1
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/__init__.py +2 -1
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/api/serializers.py +161 -6
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/api/views.py +2 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/filtersets.py +47 -4
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/forms.py +43 -6
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/tables.py +3 -1
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/templates/netbox_contract/contract.html +29 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/templates/netbox_contract/invoice.html +5 -5
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/views.py +21 -3
- {netbox_contract-2.2.3 → netbox_contract-2.2.5/src/netbox_contract.egg-info}/PKG-INFO +4 -3
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/LICENSE +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/MANIFEST.in +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/setup.cfg +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/setup.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/api/__init__.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/api/urls.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/constants.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0001_initial.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0002_alter_contract_circuit_and_more.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0003_alter_contract_custom_field_data_and_more.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0004_contract_currency_invoice_currency.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0005_contract_accounting_dimensions_and_more.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0006_alter_contract_circuit.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0007_invoice_date.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0008_invoice_comments.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0009_contract_external_reference.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0010_invoice_contracts.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0011_auto_20230122_2112.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0012_remove_invoice_contract.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0013_contract_documents_invoice_documents.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0014_contract_end_date.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0015_contractassignement.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0016_contract_parent.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0017_alter_contract_accounting_dimensions.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0018_contract_external_partie_object_id_and_more.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0019_auto_20230924_1813.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0020_alter_contract_external_partie.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0021_alter_contract_external_partie.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0022_alter_contract_internal_partie_alter_contract_parent.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0023_rename_contractassignement_contractassignment_and_more.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0024_remove_contract_external_partie.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0025_remove_contract_circuit_and_more.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0026_auto_20240421_1550.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0027_invoiceline.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0028_invoiceline_accounting_dimensions.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0029_remove_invoiceline_accounting_dimensions_and_more.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0030_alter_invoiceline_accounting_dimensions.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0031_contract_invoice_template_invoice_template.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0032_alter_invoice_period_end_alter_invoice_period_start_and_more.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0033_remove_contract_invoice_template.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0034_contract_yrc_alter_contract_mrc.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0035_accountingdimension_status_and_more.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/0036_alter_invoiceline_accounting_dimensions.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/migrations/__init__.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/models.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/navigation.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/search.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/template_content.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/templates/contract_assignments_bottom.html +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/templates/contract_list_bottom.html +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/templates/netbox_contract/accountingdimension.html +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/templates/netbox_contract/contractassignment.html +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/templates/netbox_contract/invoiceline.html +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/templates/netbox_contract/serviceprovider.html +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract/urls.py +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract.egg-info/SOURCES.txt +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract.egg-info/dependency_links.txt +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract.egg-info/not-zip-safe +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract.egg-info/requires.txt +0 -0
- {netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: netbox-contract
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.5
|
|
4
4
|
Summary: Contract management plugin for Netbox
|
|
5
5
|
Author-email: Marc Lebreuil <marc@famillelebreuil.net>
|
|
6
6
|
Project-URL: Homepage, https://github.com/mlebreuil/netbox-contract
|
|
@@ -62,10 +62,11 @@ PLUGINS_CONFIG = {
|
|
|
62
62
|
"project": "",
|
|
63
63
|
"cost center": ""
|
|
64
64
|
},
|
|
65
|
-
'mandatory_contract_fields': [
|
|
65
|
+
'mandatory_contract_fields': [],
|
|
66
66
|
'hidden_contract_fields': [],
|
|
67
|
-
'mandatory_invoice_fields': [
|
|
67
|
+
'mandatory_invoice_fields': [],
|
|
68
68
|
'hidden_invoice_fields': [],
|
|
69
|
+
'mandatory_dimensions': [],
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
72
|
|
|
@@ -46,10 +46,11 @@ PLUGINS_CONFIG = {
|
|
|
46
46
|
"project": "",
|
|
47
47
|
"cost center": ""
|
|
48
48
|
},
|
|
49
|
-
'mandatory_contract_fields': [
|
|
49
|
+
'mandatory_contract_fields': [],
|
|
50
50
|
'hidden_contract_fields': [],
|
|
51
|
-
'mandatory_invoice_fields': [
|
|
51
|
+
'mandatory_invoice_fields': [],
|
|
52
52
|
'hidden_invoice_fields': [],
|
|
53
|
+
'mandatory_dimensions': [],
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
|
|
@@ -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.5'
|
|
9
9
|
author = 'Marc Lebreuil'
|
|
10
10
|
author_email = 'marc@famillelebreuil.net'
|
|
11
11
|
base_url = 'contracts'
|
|
@@ -22,6 +22,7 @@ class ContractsConfig(PluginConfig):
|
|
|
22
22
|
'hidden_contract_fields': [],
|
|
23
23
|
'mandatory_invoice_fields': [],
|
|
24
24
|
'hidden_invoice_fields': [],
|
|
25
|
+
'mandatory_dimensions': [],
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
|
|
@@ -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(
|
|
@@ -21,6 +21,7 @@ class ContractViewSet(NetBoxModelViewSet):
|
|
|
21
21
|
)
|
|
22
22
|
)
|
|
23
23
|
serializer_class = ContractSerializer
|
|
24
|
+
filterset_class = filtersets.ContractFilterSet
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
class InvoiceViewSet(NetBoxModelViewSet):
|
|
@@ -44,6 +45,7 @@ class InvoiceLineViewSet(NetBoxModelViewSet):
|
|
|
44
45
|
'invoice', 'accounting_dimensions', 'tags'
|
|
45
46
|
)
|
|
46
47
|
serializer_class = InvoiceLineSerializer
|
|
48
|
+
filterset_class = filtersets.InvoiceLineFilterSet
|
|
47
49
|
|
|
48
50
|
|
|
49
51
|
class AccountingDimensionViewSet(NetBoxModelViewSet):
|
|
@@ -1,20 +1,42 @@
|
|
|
1
|
+
import django_filters
|
|
1
2
|
from django.db.models import Q
|
|
2
3
|
from netbox.filtersets import NetBoxModelFilterSet
|
|
4
|
+
from tenancy.filtersets import TenancyFilterSet
|
|
3
5
|
|
|
4
6
|
from .models import (
|
|
5
7
|
AccountingDimension,
|
|
8
|
+
AccountingDimensionStatusChoices,
|
|
6
9
|
Contract,
|
|
7
10
|
ContractAssignment,
|
|
11
|
+
CurrencyChoices,
|
|
12
|
+
InternalEntityChoices,
|
|
8
13
|
Invoice,
|
|
9
14
|
InvoiceLine,
|
|
10
15
|
ServiceProvider,
|
|
16
|
+
StatusChoices,
|
|
11
17
|
)
|
|
12
18
|
|
|
13
19
|
|
|
14
|
-
class ContractFilterSet(NetBoxModelFilterSet):
|
|
20
|
+
class ContractFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
|
21
|
+
status = django_filters.MultipleChoiceFilter(choices=StatusChoices, null_value=None)
|
|
22
|
+
internal_partie = django_filters.MultipleChoiceFilter(
|
|
23
|
+
choices=InternalEntityChoices, null_value=None
|
|
24
|
+
)
|
|
25
|
+
currency = django_filters.MultipleChoiceFilter(
|
|
26
|
+
choices=CurrencyChoices, null_value=None
|
|
27
|
+
)
|
|
28
|
+
|
|
15
29
|
class Meta:
|
|
16
30
|
model = Contract
|
|
17
|
-
fields = (
|
|
31
|
+
fields = (
|
|
32
|
+
'id',
|
|
33
|
+
'name',
|
|
34
|
+
'external_reference',
|
|
35
|
+
'start_date',
|
|
36
|
+
'end_date',
|
|
37
|
+
'initial_term',
|
|
38
|
+
'parent',
|
|
39
|
+
)
|
|
18
40
|
|
|
19
41
|
def search(self, queryset, name, value):
|
|
20
42
|
return queryset.filter(
|
|
@@ -26,9 +48,22 @@ class ContractFilterSet(NetBoxModelFilterSet):
|
|
|
26
48
|
|
|
27
49
|
|
|
28
50
|
class InvoiceFilterSet(NetBoxModelFilterSet):
|
|
51
|
+
currency = django_filters.MultipleChoiceFilter(
|
|
52
|
+
choices=CurrencyChoices, null_value=None
|
|
53
|
+
)
|
|
54
|
+
|
|
29
55
|
class Meta:
|
|
30
56
|
model = Invoice
|
|
31
|
-
fields = (
|
|
57
|
+
fields = (
|
|
58
|
+
'id',
|
|
59
|
+
'number',
|
|
60
|
+
'template',
|
|
61
|
+
'date',
|
|
62
|
+
'contracts',
|
|
63
|
+
'period_start',
|
|
64
|
+
'period_end',
|
|
65
|
+
'amount',
|
|
66
|
+
)
|
|
32
67
|
|
|
33
68
|
def search(self, queryset, name, value):
|
|
34
69
|
return queryset.filter(
|
|
@@ -55,9 +90,13 @@ class ContractAssignmentFilterSet(NetBoxModelFilterSet):
|
|
|
55
90
|
|
|
56
91
|
|
|
57
92
|
class InvoiceLineFilterSet(NetBoxModelFilterSet):
|
|
93
|
+
currency = django_filters.MultipleChoiceFilter(
|
|
94
|
+
choices=CurrencyChoices, null_value=None
|
|
95
|
+
)
|
|
96
|
+
|
|
58
97
|
class Meta:
|
|
59
98
|
model = InvoiceLine
|
|
60
|
-
fields = ('id', 'invoice')
|
|
99
|
+
fields = ('id', 'invoice', 'accounting_dimensions')
|
|
61
100
|
|
|
62
101
|
def search(self, queryset, name, value):
|
|
63
102
|
return queryset.filter(
|
|
@@ -66,6 +105,10 @@ class InvoiceLineFilterSet(NetBoxModelFilterSet):
|
|
|
66
105
|
|
|
67
106
|
|
|
68
107
|
class AccountingDimensionFilterSet(NetBoxModelFilterSet):
|
|
108
|
+
status = django_filters.MultipleChoiceFilter(
|
|
109
|
+
choices=AccountingDimensionStatusChoices, null_value=None
|
|
110
|
+
)
|
|
111
|
+
|
|
69
112
|
class Meta:
|
|
70
113
|
model = AccountingDimension
|
|
71
114
|
fields = ('name', 'value')
|
|
@@ -2,7 +2,6 @@ from django import forms
|
|
|
2
2
|
from django.conf import settings
|
|
3
3
|
from django.contrib.contenttypes.models import ContentType
|
|
4
4
|
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
|
5
|
-
from extras.filters import TagFilter
|
|
6
5
|
from netbox.forms import (
|
|
7
6
|
NetBoxModelBulkEditForm,
|
|
8
7
|
NetBoxModelFilterSetForm,
|
|
@@ -10,6 +9,7 @@ from netbox.forms import (
|
|
|
10
9
|
NetBoxModelImportForm,
|
|
11
10
|
)
|
|
12
11
|
from tenancy.models import Tenant
|
|
12
|
+
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES
|
|
13
13
|
from utilities.forms.fields import (
|
|
14
14
|
CommentField,
|
|
15
15
|
ContentTypeChoiceField,
|
|
@@ -21,6 +21,7 @@ from utilities.forms.fields import (
|
|
|
21
21
|
DynamicModelMultipleChoiceField,
|
|
22
22
|
JSONField,
|
|
23
23
|
SlugField,
|
|
24
|
+
TagFilterField,
|
|
24
25
|
)
|
|
25
26
|
from utilities.forms.widgets import DatePicker, HTMXSelect
|
|
26
27
|
|
|
@@ -30,6 +31,7 @@ from .models import (
|
|
|
30
31
|
AccountingDimensionStatusChoices,
|
|
31
32
|
Contract,
|
|
32
33
|
ContractAssignment,
|
|
34
|
+
CurrencyChoices,
|
|
33
35
|
InternalEntityChoices,
|
|
34
36
|
Invoice,
|
|
35
37
|
InvoiceLine,
|
|
@@ -160,9 +162,11 @@ class ContractFilterSetForm(NetBoxModelFilterSetForm):
|
|
|
160
162
|
|
|
161
163
|
tenant = DynamicModelChoiceField(queryset=Tenant.objects.all(), required=False)
|
|
162
164
|
external_reference = forms.CharField(required=False)
|
|
163
|
-
internal_partie = forms.
|
|
165
|
+
internal_partie = forms.ChoiceField(choices=InternalEntityChoices, required=False)
|
|
164
166
|
status = forms.ChoiceField(choices=StatusChoices, required=False)
|
|
167
|
+
currency = forms.ChoiceField(choices=CurrencyChoices, required=False)
|
|
165
168
|
parent = DynamicModelChoiceField(queryset=Contract.objects.all(), required=False)
|
|
169
|
+
tag = TagFilterField(model)
|
|
166
170
|
|
|
167
171
|
|
|
168
172
|
class ContractCSVForm(NetBoxModelImportForm):
|
|
@@ -227,13 +231,17 @@ class ContractCSVForm(NetBoxModelImportForm):
|
|
|
227
231
|
|
|
228
232
|
class ContractBulkEditForm(NetBoxModelBulkEditForm):
|
|
229
233
|
name = forms.CharField(max_length=100, required=False)
|
|
230
|
-
|
|
231
234
|
external_reference = forms.CharField(max_length=100, required=False)
|
|
232
235
|
internal_partie = forms.ChoiceField(choices=InternalEntityChoices, required=False)
|
|
236
|
+
tenant = DynamicModelChoiceField(queryset=Tenant.objects.all(), required=False)
|
|
237
|
+
accounting_dimensions = Dimensions(required=False)
|
|
233
238
|
comments = CommentField(required=False)
|
|
234
239
|
parent = DynamicModelChoiceField(queryset=Contract.objects.all(), required=False)
|
|
235
240
|
|
|
236
|
-
nullable_fields = (
|
|
241
|
+
nullable_fields = (
|
|
242
|
+
'accounting_dimensions',
|
|
243
|
+
'comments',
|
|
244
|
+
)
|
|
237
245
|
model = Contract
|
|
238
246
|
|
|
239
247
|
|
|
@@ -354,9 +362,15 @@ class InvoiceForm(NetBoxModelForm):
|
|
|
354
362
|
|
|
355
363
|
class InvoiceFilterSetForm(NetBoxModelFilterSetForm):
|
|
356
364
|
model = Invoice
|
|
365
|
+
number = forms.CharField(required=False)
|
|
366
|
+
template = forms.NullBooleanField(
|
|
367
|
+
required=False, widget=forms.Select(choices=BOOLEAN_WITH_BLANK_CHOICES)
|
|
368
|
+
)
|
|
369
|
+
currency = forms.ChoiceField(choices=CurrencyChoices, required=False)
|
|
357
370
|
contracts = DynamicModelMultipleChoiceField(
|
|
358
371
|
queryset=Contract.objects.all(), required=False
|
|
359
372
|
)
|
|
373
|
+
tag = TagFilterField(model)
|
|
360
374
|
|
|
361
375
|
|
|
362
376
|
class InvoiceCSVForm(NetBoxModelImportForm):
|
|
@@ -386,9 +400,23 @@ class InvoiceCSVForm(NetBoxModelImportForm):
|
|
|
386
400
|
|
|
387
401
|
class InvoiceBulkEditForm(NetBoxModelBulkEditForm):
|
|
388
402
|
number = forms.CharField(max_length=100, required=True)
|
|
403
|
+
template = forms.BooleanField(required=False)
|
|
404
|
+
date = forms.DateField(required=False)
|
|
389
405
|
contracts = DynamicModelMultipleChoiceField(
|
|
390
406
|
queryset=Contract.objects.all(), required=False
|
|
391
407
|
)
|
|
408
|
+
period_start = forms.DateField(required=False)
|
|
409
|
+
period_end = forms.DateField(required=False)
|
|
410
|
+
currency = forms.ChoiceField(choices=CurrencyChoices, required=False)
|
|
411
|
+
accounting_dimensions = Dimensions(required=False)
|
|
412
|
+
amount = forms.DecimalField(max_digits=10, decimal_places=2, required=False)
|
|
413
|
+
documents = forms.URLField(required=False)
|
|
414
|
+
comments = CommentField()
|
|
415
|
+
nullable_fields = (
|
|
416
|
+
'accounting_dimensions',
|
|
417
|
+
'comments',
|
|
418
|
+
)
|
|
419
|
+
|
|
392
420
|
model = Invoice
|
|
393
421
|
|
|
394
422
|
|
|
@@ -406,7 +434,8 @@ class ServiceProviderForm(NetBoxModelForm):
|
|
|
406
434
|
|
|
407
435
|
class ServiceProviderFilterSetForm(NetBoxModelFilterSetForm):
|
|
408
436
|
model = ServiceProvider
|
|
409
|
-
|
|
437
|
+
name = forms.CharField(required=False)
|
|
438
|
+
tag = TagFilterField(model)
|
|
410
439
|
|
|
411
440
|
|
|
412
441
|
class ServiceProviderCSVForm(NetBoxModelImportForm):
|
|
@@ -480,6 +509,12 @@ class InvoiceLineForm(NetBoxModelForm):
|
|
|
480
509
|
else:
|
|
481
510
|
dimensions_names.append(dimension.name)
|
|
482
511
|
|
|
512
|
+
# Make sure mandatory dimensions are present
|
|
513
|
+
mandatory_dimensions = plugin_settings.get('mandatory_dimensions')
|
|
514
|
+
for dimension in mandatory_dimensions:
|
|
515
|
+
if dimension not in dimensions_names:
|
|
516
|
+
raise ValidationError(f'dimension {dimension} missing')
|
|
517
|
+
|
|
483
518
|
class Meta:
|
|
484
519
|
model = InvoiceLine
|
|
485
520
|
fields = [
|
|
@@ -495,9 +530,11 @@ class InvoiceLineForm(NetBoxModelForm):
|
|
|
495
530
|
class InvoiceLineFilterSetForm(NetBoxModelFilterSetForm):
|
|
496
531
|
model = InvoiceLine
|
|
497
532
|
invoice = DynamicModelChoiceField(queryset=Invoice.objects.all(), required=False)
|
|
498
|
-
accounting_dimensions =
|
|
533
|
+
accounting_dimensions = DynamicModelMultipleChoiceField(
|
|
499
534
|
queryset=AccountingDimension.objects.all(), required=False
|
|
500
535
|
)
|
|
536
|
+
currency = forms.ChoiceField(choices=CurrencyChoices, required=False)
|
|
537
|
+
tag = TagFilterField(model)
|
|
501
538
|
|
|
502
539
|
|
|
503
540
|
class InvoiceLineImportForm(NetBoxModelImportForm):
|
|
@@ -200,7 +200,9 @@ class ServiceProviderListTable(NetBoxTable):
|
|
|
200
200
|
|
|
201
201
|
class InvoiceLineListTable(NetBoxTable):
|
|
202
202
|
invoice = tables.Column(linkify=True)
|
|
203
|
-
accounting_dimensions = tables.ManyToManyColumn(
|
|
203
|
+
accounting_dimensions = tables.ManyToManyColumn(
|
|
204
|
+
linkify=True, filter=lambda qs: qs.order_by('name')
|
|
205
|
+
)
|
|
204
206
|
|
|
205
207
|
class Meta(NetBoxTable.Meta):
|
|
206
208
|
model = InvoiceLine
|
|
@@ -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
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: netbox-contract
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.5
|
|
4
4
|
Summary: Contract management plugin for Netbox
|
|
5
5
|
Author-email: Marc Lebreuil <marc@famillelebreuil.net>
|
|
6
6
|
Project-URL: Homepage, https://github.com/mlebreuil/netbox-contract
|
|
@@ -62,10 +62,11 @@ PLUGINS_CONFIG = {
|
|
|
62
62
|
"project": "",
|
|
63
63
|
"cost center": ""
|
|
64
64
|
},
|
|
65
|
-
'mandatory_contract_fields': [
|
|
65
|
+
'mandatory_contract_fields': [],
|
|
66
66
|
'hidden_contract_fields': [],
|
|
67
|
-
'mandatory_invoice_fields': [
|
|
67
|
+
'mandatory_invoice_fields': [],
|
|
68
68
|
'hidden_invoice_fields': [],
|
|
69
|
+
'mandatory_dimensions': [],
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
72
|
|
|
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.3 → netbox_contract-2.2.5}/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.3 → netbox_contract-2.2.5}/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.3 → netbox_contract-2.2.5}/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
|
{netbox_contract-2.2.3 → netbox_contract-2.2.5}/src/netbox_contract.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|