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.
Files changed (73) hide show
  1. {netbox_contract-2.2.2/src/netbox_contract.egg-info → netbox_contract-2.2.4}/PKG-INFO +1 -1
  2. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/pyproject.toml +1 -1
  3. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/__init__.py +1 -1
  4. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/api/serializers.py +162 -7
  5. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/forms.py +31 -4
  6. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/templates/netbox_contract/contract.html +29 -0
  7. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/templates/netbox_contract/invoice.html +5 -5
  8. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/views.py +22 -4
  9. {netbox_contract-2.2.2 → netbox_contract-2.2.4/src/netbox_contract.egg-info}/PKG-INFO +1 -1
  10. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/LICENSE +0 -0
  11. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/MANIFEST.in +0 -0
  12. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/README.md +0 -0
  13. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/setup.cfg +0 -0
  14. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/setup.py +0 -0
  15. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/api/__init__.py +0 -0
  16. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/api/urls.py +0 -0
  17. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/api/views.py +0 -0
  18. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/constants.py +0 -0
  19. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/filtersets.py +0 -0
  20. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0001_initial.py +0 -0
  21. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0002_alter_contract_circuit_and_more.py +0 -0
  22. {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
  23. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0004_contract_currency_invoice_currency.py +0 -0
  24. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0005_contract_accounting_dimensions_and_more.py +0 -0
  25. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0006_alter_contract_circuit.py +0 -0
  26. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0007_invoice_date.py +0 -0
  27. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0008_invoice_comments.py +0 -0
  28. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0009_contract_external_reference.py +0 -0
  29. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0010_invoice_contracts.py +0 -0
  30. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0011_auto_20230122_2112.py +0 -0
  31. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0012_remove_invoice_contract.py +0 -0
  32. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0013_contract_documents_invoice_documents.py +0 -0
  33. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0014_contract_end_date.py +0 -0
  34. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0015_contractassignement.py +0 -0
  35. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0016_contract_parent.py +0 -0
  36. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0017_alter_contract_accounting_dimensions.py +0 -0
  37. {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
  38. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0019_auto_20230924_1813.py +0 -0
  39. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0020_alter_contract_external_partie.py +0 -0
  40. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0021_alter_contract_external_partie.py +0 -0
  41. {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
  42. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0023_rename_contractassignement_contractassignment_and_more.py +0 -0
  43. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0024_remove_contract_external_partie.py +0 -0
  44. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0025_remove_contract_circuit_and_more.py +0 -0
  45. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0026_auto_20240421_1550.py +0 -0
  46. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0027_invoiceline.py +0 -0
  47. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0028_invoiceline_accounting_dimensions.py +0 -0
  48. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0029_remove_invoiceline_accounting_dimensions_and_more.py +0 -0
  49. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0030_alter_invoiceline_accounting_dimensions.py +0 -0
  50. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0031_contract_invoice_template_invoice_template.py +0 -0
  51. {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
  52. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0033_remove_contract_invoice_template.py +0 -0
  53. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0034_contract_yrc_alter_contract_mrc.py +0 -0
  54. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0035_accountingdimension_status_and_more.py +0 -0
  55. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/0036_alter_invoiceline_accounting_dimensions.py +0 -0
  56. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/migrations/__init__.py +0 -0
  57. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/models.py +0 -0
  58. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/navigation.py +0 -0
  59. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/search.py +0 -0
  60. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/tables.py +0 -0
  61. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/template_content.py +0 -0
  62. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/templates/contract_assignments_bottom.html +0 -0
  63. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/templates/contract_list_bottom.html +0 -0
  64. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/templates/netbox_contract/accountingdimension.html +0 -0
  65. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/templates/netbox_contract/contractassignment.html +0 -0
  66. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/templates/netbox_contract/invoiceline.html +0 -0
  67. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/templates/netbox_contract/serviceprovider.html +0 -0
  68. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract/urls.py +0 -0
  69. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract.egg-info/SOURCES.txt +0 -0
  70. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract.egg-info/dependency_links.txt +0 -0
  71. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract.egg-info/not-zip-safe +0 -0
  72. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/src/netbox_contract.egg-info/requires.txt +0 -0
  73. {netbox_contract-2.2.2 → netbox_contract-2.2.4}/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.2
3
+ Version: 2.2.4
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "netbox-contract"
3
- version = "2.2.2"
3
+ version = "2.2.4"
4
4
  authors = [
5
5
  { name="Marc Lebreuil", email="marc@famillelebreuil.net" },
6
6
  ]
@@ -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.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 = ('id', 'url', 'display', 'name')
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 = NestedContractSerializer(many=True, required=False)
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 = ('id', 'url', 'display', 'number', 'contracts')
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 = NestedAccountingDimensionSerializer(
239
- many=True, required=False
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 = ('comments',)
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 = forms.ModelMultipleChoiceField(
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 = CSVModelChoiceField(
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
- <h5 class="card-header">Invoices</h5>
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(instance.invoices.all())
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
- childs_table = tables.ContractListBottomTable(instance.childs.all())
148
- childs_table.configure(request)
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.filter(template=False).latest(
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: netbox-contract
3
- Version: 2.2.2
3
+ Version: 2.2.4
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
File without changes