Skip to content

Commit

Permalink
Merge pull request #183 from mlebreuil/develop
Browse files Browse the repository at this point in the history
v2.2.5
  • Loading branch information
mlebreuil authored Sep 8, 2024
2 parents 3a7738e + e1ce2ce commit 354796a
Show file tree
Hide file tree
Showing 12 changed files with 141 additions and 16 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@

## Version 2

### Version 2.2.5

* Generally improve filtering options
* [178](https://github.com/mlebreuil/netbox-contract/issues/178) Add the possibility to filter on invoice number, and contract name through the API.
* [176](https://github.com/mlebreuil/netbox-contract/issues/176) Order accounting dimensions in tables alphabetically.
* [171](https://github.com/mlebreuil/netbox-contract/issues/171) It is now poaaible to define madatory accounting dimension by specifying their names in the 'mandatory_dimensions' list in the plugin settings. (see the "Customize the plugin" paragraph in the README.md file)

### Version 2.2.4

* [166](https://github.com/mlebreuil/netbox-contract/issues/166) Review the Contract view to include invoice template details and lines.
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,11 @@ PLUGINS_CONFIG = {
"project": "",
"cost center": ""
},
'mandatory_contract_fields': ['accounting_dimensions'],
'mandatory_contract_fields': [],
'hidden_contract_fields': [],
'mandatory_invoice_fields': ['accounting_dimensions'],
'mandatory_invoice_fields': [],
'hidden_invoice_fields': [],
'mandatory_dimensions': [],
}
}

Expand Down
30 changes: 30 additions & 0 deletions docs/accounting_dimensions.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,35 @@
# Accounting dimensions

> [!NOTE]
> account is considered a accounting dimensions as any other.
It is possible through the plugin config attribure 'mandatory_dimensions' to set some mandatory dimensions. the attribute will take a list of dimension names. For instance:

```python
# configuration.py
PLUGINS_CONFIG = {
'netbox_contract': {
'top_level_menu': True,
'mandatory_contract_fields': [],
'hidden_contract_fields': [],
'mandatory_invoice_fields': [],
'hidden_invoice_fields': [],
'mandatory_dimensions': ['account','project'],
}
}

```

Refer to the readme file for more information.

> [!WARNING]
> Accounting dimensions used to be set with a simple json field. Although the field is still available, it is recommended to add dimensions through invoice lines. You will find in the script folder a file which can be imported as netbox custom scripts module which contains a script to perform the migration. You wil need to adjust the script to your needs.
![Accounting dimensions](img/accrounting_dimensions.png "accounting dimensions")

- name: The name of the accounting dimensions (Account, Project, Entity ...)
- value: The value for this dimension.
- status: If the accounting dimension can still be used for new invoice lines.
- Comments: Self explanatory


2 changes: 1 addition & 1 deletion docs/contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
![Contract](img/contract.png "contract")

- External partie type: either an Circuit provider or Contract Service provider.
- Accounting dimensions: Will be copied to each invoice. Also this is still working the use invoice templates with accounting dimensions should be prefered.
- Accounting dimensions: Will be copied to each invoice. Although this this field is still available the use invoice templates with accounting dimensions should be prefered.
- Monthly / Yearly recuring costs: Only one of these two options can be used for each contract. The value will be used, along with the invoice frequency, to calculate each invoice amount.
- Invoice frequency : The number of month that each invoice covers.
- Parent: Contrats can be arranged in a parent / child hierarchie.
Expand Down
15 changes: 15 additions & 0 deletions docs/invoice.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
# Invoice

New invoices shold be created from the corresponding contract. Most of their fields will be derived from the contract invoice template. An invoice template is an invoice object which "Template" field is set to true. There can be only one invoice per contract.
Each invoice will be linked to one or more invoice line.

![Invoice](img/invoice.png "invoice")

- Number: The invoice number. Should correspond to your accounting sysstem invoice number.
- Template: Whether this isvoice is an invoice template for the corresponding contract(s). There can be only one invoice template per contract. The template will be used to define automatically the fields of a nre invoice, including the correpsonding accounting lines with their dimensions.
- Date: the date of the invoice
- Contracts: The contracts linked to the invoice.
- Period_start: The start of the contract periode covered by this invoice.
- Period_end: The end of the contract period covered by this invoice.
- currency: The currency of the invoice
- accounting_dimensions: The use of this field is deprecated. Invoice lines and the corresponding accounting dimensions should be used instead.
- Amount: The amount of the invoice
- Documents: A link to the corresponding document. This field is deprecated and the document plugin should be used instead.
- Comments: self explanatory.

Linked objects:

![Invoice linked objects](img/invoice_linked_objects.png "invoice linked objects")
10 changes: 8 additions & 2 deletions docs/invoice_line.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Invoice line

![Invoice line](img/invoice_line.png "invoice line")

An invoice line correspond to the accouning lines for the invoice.
You can define several accounting lines for an invoice but the sum of each line amount should match the invoice amount. this is enforced if you create the invoice line through the web ui.

![Invoice line](img/invoice_line.png "invoice line")

- Invoice: The corresponding invoice.
- Currency: The currency of the invoice
- Amount: the amount of the invoice. Whether you take into account VAT in this amount depends on the way your budget is contructed.
- Accounting dimensions: The accounting dimensions for the invoice.
- Comments: Self explanatory
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "netbox-contract"
version = "2.2.4"
version = "2.2.5"
authors = [
{ name="Marc Lebreuil", email="marc@famillelebreuil.net" },
]
Expand Down
3 changes: 2 additions & 1 deletion src/netbox_contract/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class ContractsConfig(PluginConfig):
name = 'netbox_contract'
verbose_name = 'Netbox contract'
description = 'Contract management plugin for Netbox'
version = '2.2.4'
version = '2.2.5'
author = 'Marc Lebreuil'
author_email = 'marc@famillelebreuil.net'
base_url = 'contracts'
Expand All @@ -22,6 +22,7 @@ class ContractsConfig(PluginConfig):
'hidden_contract_fields': [],
'mandatory_invoice_fields': [],
'hidden_invoice_fields': [],
'mandatory_dimensions': [],
}


Expand Down
2 changes: 2 additions & 0 deletions src/netbox_contract/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class ContractViewSet(NetBoxModelViewSet):
)
)
serializer_class = ContractSerializer
filterset_class = filtersets.ContractFilterSet


class InvoiceViewSet(NetBoxModelViewSet):
Expand All @@ -44,6 +45,7 @@ class InvoiceLineViewSet(NetBoxModelViewSet):
'invoice', 'accounting_dimensions', 'tags'
)
serializer_class = InvoiceLineSerializer
filterset_class = filtersets.InvoiceLineFilterSet


class AccountingDimensionViewSet(NetBoxModelViewSet):
Expand Down
51 changes: 47 additions & 4 deletions src/netbox_contract/filtersets.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,42 @@
import django_filters
from django.db.models import Q
from netbox.filtersets import NetBoxModelFilterSet
from tenancy.filtersets import TenancyFilterSet

from .models import (
AccountingDimension,
AccountingDimensionStatusChoices,
Contract,
ContractAssignment,
CurrencyChoices,
InternalEntityChoices,
Invoice,
InvoiceLine,
ServiceProvider,
StatusChoices,
)


class ContractFilterSet(NetBoxModelFilterSet):
class ContractFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
status = django_filters.MultipleChoiceFilter(choices=StatusChoices, null_value=None)
internal_partie = django_filters.MultipleChoiceFilter(
choices=InternalEntityChoices, null_value=None
)
currency = django_filters.MultipleChoiceFilter(
choices=CurrencyChoices, null_value=None
)

class Meta:
model = Contract
fields = ('id', 'internal_partie', 'status', 'parent')
fields = (
'id',
'name',
'external_reference',
'start_date',
'end_date',
'initial_term',
'parent',
)

def search(self, queryset, name, value):
return queryset.filter(
Expand All @@ -26,9 +48,22 @@ def search(self, queryset, name, value):


class InvoiceFilterSet(NetBoxModelFilterSet):
currency = django_filters.MultipleChoiceFilter(
choices=CurrencyChoices, null_value=None
)

class Meta:
model = Invoice
fields = ('id', 'contracts')
fields = (
'id',
'number',
'template',
'date',
'contracts',
'period_start',
'period_end',
'amount',
)

def search(self, queryset, name, value):
return queryset.filter(
Expand All @@ -55,9 +90,13 @@ def search(self, queryset, name, value):


class InvoiceLineFilterSet(NetBoxModelFilterSet):
currency = django_filters.MultipleChoiceFilter(
choices=CurrencyChoices, null_value=None
)

class Meta:
model = InvoiceLine
fields = ('id', 'invoice')
fields = ('id', 'invoice', 'accounting_dimensions')

def search(self, queryset, name, value):
return queryset.filter(
Expand All @@ -66,6 +105,10 @@ def search(self, queryset, name, value):


class AccountingDimensionFilterSet(NetBoxModelFilterSet):
status = django_filters.MultipleChoiceFilter(
choices=AccountingDimensionStatusChoices, null_value=None
)

class Meta:
model = AccountingDimension
fields = ('name', 'value')
Expand Down
26 changes: 22 additions & 4 deletions src/netbox_contract/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from extras.filters import TagFilter
from netbox.forms import (
NetBoxModelBulkEditForm,
NetBoxModelFilterSetForm,
NetBoxModelForm,
NetBoxModelImportForm,
)
from tenancy.models import Tenant
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES
from utilities.forms.fields import (
CommentField,
ContentTypeChoiceField,
Expand All @@ -21,6 +21,7 @@
DynamicModelMultipleChoiceField,
JSONField,
SlugField,
TagFilterField,
)
from utilities.forms.widgets import DatePicker, HTMXSelect

Expand Down Expand Up @@ -161,9 +162,11 @@ class ContractFilterSetForm(NetBoxModelFilterSetForm):

tenant = DynamicModelChoiceField(queryset=Tenant.objects.all(), required=False)
external_reference = forms.CharField(required=False)
internal_partie = forms.CharField(required=False)
internal_partie = forms.ChoiceField(choices=InternalEntityChoices, required=False)
status = forms.ChoiceField(choices=StatusChoices, required=False)
currency = forms.ChoiceField(choices=CurrencyChoices, required=False)
parent = DynamicModelChoiceField(queryset=Contract.objects.all(), required=False)
tag = TagFilterField(model)


class ContractCSVForm(NetBoxModelImportForm):
Expand Down Expand Up @@ -359,9 +362,15 @@ class Meta:

class InvoiceFilterSetForm(NetBoxModelFilterSetForm):
model = Invoice
number = forms.CharField(required=False)
template = forms.NullBooleanField(
required=False, widget=forms.Select(choices=BOOLEAN_WITH_BLANK_CHOICES)
)
currency = forms.ChoiceField(choices=CurrencyChoices, required=False)
contracts = DynamicModelMultipleChoiceField(
queryset=Contract.objects.all(), required=False
)
tag = TagFilterField(model)


class InvoiceCSVForm(NetBoxModelImportForm):
Expand Down Expand Up @@ -425,7 +434,8 @@ class Meta:

class ServiceProviderFilterSetForm(NetBoxModelFilterSetForm):
model = ServiceProvider
tag = TagFilter()
name = forms.CharField(required=False)
tag = TagFilterField(model)


class ServiceProviderCSVForm(NetBoxModelImportForm):
Expand Down Expand Up @@ -499,6 +509,12 @@ def clean(self):
else:
dimensions_names.append(dimension.name)

# Make sure mandatory dimensions are present
mandatory_dimensions = plugin_settings.get('mandatory_dimensions')
for dimension in mandatory_dimensions:
if dimension not in dimensions_names:
raise ValidationError(f'dimension {dimension} missing')

class Meta:
model = InvoiceLine
fields = [
Expand All @@ -514,9 +530,11 @@ class Meta:
class InvoiceLineFilterSetForm(NetBoxModelFilterSetForm):
model = InvoiceLine
invoice = DynamicModelChoiceField(queryset=Invoice.objects.all(), required=False)
accounting_dimensions = DynamicModelChoiceField(
accounting_dimensions = DynamicModelMultipleChoiceField(
queryset=AccountingDimension.objects.all(), required=False
)
currency = forms.ChoiceField(choices=CurrencyChoices, required=False)
tag = TagFilterField(model)


class InvoiceLineImportForm(NetBoxModelImportForm):
Expand Down
4 changes: 3 additions & 1 deletion src/netbox_contract/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,9 @@ class Meta(NetBoxTable.Meta):

class InvoiceLineListTable(NetBoxTable):
invoice = tables.Column(linkify=True)
accounting_dimensions = tables.ManyToManyColumn(linkify=True)
accounting_dimensions = tables.ManyToManyColumn(
linkify=True, filter=lambda qs: qs.order_by('name')
)

class Meta(NetBoxTable.Meta):
model = InvoiceLine
Expand Down

0 comments on commit 354796a

Please sign in to comment.