2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-07-01 11:10:54 +00:00

Project code support (#4636)

* Support image uploads in the "notes" markdown fields

- Implemented using the existing EasyMDE library
- Copy / paste support
- Drag / drop support

* Remove debug message

* Updated API version

* Better UX when saving notes

* Pin PIP version (for testing)

* Bug fixes

- Fix typo
- Use correct serializer type

* Add unit testing

* Update role permissions

* Typo fix

* Update migration file

* Adds a notes mixin class to be used for refactoring

* Refactor existing models with notes to use the new mixin

* Add helper function for finding all model types with a certain mixin

* Refactor barcode plugin to use new method

* Typo fix

* Add daily task to delete old / unused notes

* Add ProjectCode model

(cherry picked from commit 382a0a2fc32c930d46ed3fe0c6d2cae654c2209d)

* Adds IsStaffOrReadyOnly permissions

- Authenticated users get read-only access
- Staff users get read/write access

(cherry picked from commit 53d04da86c4c866fd9c909d147d93844186470b4)

* Adds API endpoints for project codes

(cherry picked from commit 5ae1da23b2eae4e1168bc6fe28a3544dedc4a1b4)

* Add migration file for projectcode model

(cherry picked from commit 5f8717712c65df853ea69907d33e185fd91df7ee)

* Add project code configuration page to the global settings view

* Add 'project code' field to orders

* Add ability to set / edit the project code for various order models

* Add project code info to order list tables

* Add configuration options for project code integration

* Allow orders to be filtered by project code

* Refactor table_filters.js

- Allow orders to be filtered dynamically by project code

* Bump API version

* Fixes

* Add resource mixin for exporting project code in order list

* Add "has_project_code" filter

* javascript fix

* Edit / delete project codes via API

- Also refactor some existing JS

* Move MetadataMixin to InvenTree.models

To prevent circular imports

(cherry picked from commit d23b013881eaffe612dfbfcdfc5dff6d729068c6)

* Fixes for circular imports

* Add metadata for ProjectCode model

* Add Metadata API endpoint for ProjectCode

* Add unit testing for ProjectCode API endpoints
This commit is contained in:
Oliver
2023-04-20 00:47:07 +10:00
committed by GitHub
parent eafd2ac966
commit 070e2afcea
39 changed files with 1315 additions and 683 deletions

View File

@ -1,6 +1,7 @@
"""Admin functionality for the 'order' app"""
from django.contrib import admin
from django.utils.translation import gettext_lazy as _
import import_export.widgets as widgets
from import_export.admin import ImportExportModelAdmin
@ -10,6 +11,19 @@ import order.models as models
from InvenTree.admin import InvenTreeResource
class ProjectCodeResourceMixin:
"""Mixin for exporting project code data"""
project_code = Field(attribute='project_code', column_name=_('Project Code'))
def dehydrate_project_code(self, order):
"""Return the project code value, not the pk"""
if order.project_code:
return order.project_code.code
else:
return ''
# region general classes
class GeneralExtraLineAdmin:
"""Admin class template for the 'ExtraLineItem' models"""
@ -94,7 +108,7 @@ class SalesOrderAdmin(ImportExportModelAdmin):
autocomplete_fields = ('customer',)
class PurchaseOrderResource(InvenTreeResource):
class PurchaseOrderResource(ProjectCodeResourceMixin, InvenTreeResource):
"""Class for managing import / export of PurchaseOrder data."""
class Meta:
@ -141,7 +155,7 @@ class PurchaseOrderExtraLineResource(InvenTreeResource):
model = models.PurchaseOrderExtraLine
class SalesOrderResource(InvenTreeResource):
class SalesOrderResource(ProjectCodeResourceMixin, InvenTreeResource):
"""Class for managing import / export of SalesOrder data."""
class Meta:
@ -276,7 +290,7 @@ class SalesOrderAllocationAdmin(ImportExportModelAdmin):
autocomplete_fields = ('line', 'shipment', 'item',)
class ReturnOrderResource(InvenTreeResource):
class ReturnOrderResource(ProjectCodeResourceMixin, InvenTreeResource):
"""Class for managing import / export of ReturnOrder data"""
class Meta:

View File

@ -16,7 +16,7 @@ from rest_framework.response import Response
import order.models as models
import order.serializers as serializers
from common.models import InvenTreeSetting
from common.models import InvenTreeSetting, ProjectCode
from common.settings import settings
from company.models import SupplierPart
from InvenTree.api import (APIDownloadMixin, AttachmentMixin,
@ -136,6 +136,21 @@ class OrderFilter(rest_filters.FilterSet):
else:
return queryset.exclude(status__in=self.Meta.model.get_status_class().OPEN)
project_code = rest_filters.ModelChoiceFilter(
queryset=ProjectCode.objects.all(),
field_name='project_code'
)
has_project_code = rest_filters.BooleanFilter(label='has_project_code', method='filter_has_project_code')
def filter_has_project_code(self, queryset, name, value):
"""Filter by whether or not the order has a project code"""
if str2bool(value):
return queryset.exclude(project_code=None)
else:
return queryset.filter(project_code=None)
class LineItemFilter(rest_filters.FilterSet):
"""Base class for custom API filters for order line item list(s)"""
@ -307,12 +322,14 @@ class PurchaseOrderList(PurchaseOrderMixin, APIDownloadMixin, ListCreateAPI):
ordering_field_aliases = {
'reference': ['reference_int', 'reference'],
'project_code': ['project_code__code'],
}
search_fields = [
'reference',
'supplier__name',
'supplier_reference',
'project_code__code',
'description',
]
@ -325,6 +342,7 @@ class PurchaseOrderList(PurchaseOrderMixin, APIDownloadMixin, ListCreateAPI):
'status',
'responsible',
'total_price',
'project_code',
]
ordering = '-reference'
@ -685,6 +703,7 @@ class SalesOrderList(SalesOrderMixin, APIDownloadMixin, ListCreateAPI):
ordering_field_aliases = {
'reference': ['reference_int', 'reference'],
'project_code': ['project_code__code'],
}
filterset_fields = [
@ -701,6 +720,7 @@ class SalesOrderList(SalesOrderMixin, APIDownloadMixin, ListCreateAPI):
'line_items',
'shipment_date',
'total_price',
'project_code',
]
search_fields = [
@ -708,6 +728,7 @@ class SalesOrderList(SalesOrderMixin, APIDownloadMixin, ListCreateAPI):
'reference',
'description',
'customer_reference',
'project_code__code',
]
ordering = '-reference'
@ -1138,6 +1159,7 @@ class ReturnOrderList(ReturnOrderMixin, APIDownloadMixin, ListCreateAPI):
ordering_field_aliases = {
'reference': ['reference_int', 'reference'],
'project_code': ['project_code__code'],
}
ordering_fields = [
@ -1148,6 +1170,7 @@ class ReturnOrderList(ReturnOrderMixin, APIDownloadMixin, ListCreateAPI):
'line_items',
'status',
'target_date',
'project_code',
]
search_fields = [
@ -1155,6 +1178,7 @@ class ReturnOrderList(ReturnOrderMixin, APIDownloadMixin, ListCreateAPI):
'reference',
'description',
'customer_reference',
'project_code__code',
]
ordering = '-reference'

View File

@ -0,0 +1,30 @@
# Generated by Django 3.2.18 on 2023-04-19 02:50
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('common', '0018_projectcode'),
('order', '0091_auto_20230419_0037'),
]
operations = [
migrations.AddField(
model_name='purchaseorder',
name='project_code',
field=models.ForeignKey(blank=True, help_text='Select project code for this order', null=True, on_delete=django.db.models.deletion.SET_NULL, to='common.projectcode', verbose_name='Project Code'),
),
migrations.AddField(
model_name='returnorder',
name='project_code',
field=models.ForeignKey(blank=True, help_text='Select project code for this order', null=True, on_delete=django.db.models.deletion.SET_NULL, to='common.projectcode', verbose_name='Project Code'),
),
migrations.AddField(
model_name='salesorder',
name='project_code',
field=models.ForeignKey(blank=True, help_text='Select project code for this order', null=True, on_delete=django.db.models.deletion.SET_NULL, to='common.projectcode', verbose_name='Project Code'),
),
]

View File

@ -28,6 +28,7 @@ import InvenTree.tasks
import order.validators
import stock.models
import users.models as UserModels
from common.models import ProjectCode
from common.notifications import InvenTreeNotificationBodies
from common.settings import currency_code_default
from company.models import Company, Contact, SupplierPart
@ -36,13 +37,13 @@ from InvenTree.fields import (InvenTreeModelMoneyField, InvenTreeURLField,
RoundingDecimalField)
from InvenTree.helpers import decimal2string, getSetting, notify_responsible
from InvenTree.models import (InvenTreeAttachment, InvenTreeBarcodeMixin,
InvenTreeNotesMixin, ReferenceIndexingMixin)
InvenTreeNotesMixin, MetadataMixin,
ReferenceIndexingMixin)
from InvenTree.status_codes import (PurchaseOrderStatus, ReturnOrderLineStatus,
ReturnOrderStatus, SalesOrderStatus,
StockHistoryCode, StockStatus)
from part import models as PartModels
from plugin.events import trigger_event
from plugin.models import MetadataMixin
logger = logging.getLogger('inventree')
@ -199,6 +200,8 @@ class Order(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, Reference
description = models.CharField(max_length=250, blank=True, verbose_name=_('Description'), help_text=_('Order description (optional)'))
project_code = models.ForeignKey(ProjectCode, on_delete=models.SET_NULL, blank=True, null=True, verbose_name=_('Project Code'), help_text=_('Select project code for this order'))
link = InvenTreeURLField(blank=True, verbose_name=_('Link'), help_text=_('Link to external page'))
target_date = models.DateField(

View File

@ -13,6 +13,7 @@ from rest_framework import serializers
from rest_framework.serializers import ValidationError
from sql_util.utils import SubqueryCount
import common.serializers
import order.models
import part.filters
import stock.models
@ -64,6 +65,9 @@ class AbstractOrderSerializer(serializers.Serializer):
# Detail for responsible field
responsible_detail = OwnerSerializer(source='responsible', read_only=True, many=False)
# Detail for project code field
project_code_detail = common.serializers.ProjectCodeSerializer(source='project_code', read_only=True, many=False)
# Boolean field indicating if this order is overdue (Note: must be annotated)
overdue = serializers.BooleanField(required=False, read_only=True)
@ -96,6 +100,8 @@ class AbstractOrderSerializer(serializers.Serializer):
'description',
'line_items',
'link',
'project_code',
'project_code_detail',
'reference',
'responsible',
'responsible_detail',

View File

@ -115,6 +115,7 @@ src="{% static 'img/blank_image.png' %}"
<td>{% trans "Order Description" %}</td>
<td>{{ order.description }}{% include "clip.html" %}</td>
</tr>
{% include "project_code_data.html" with instance=order %}
{% include "barcode_data.html" with instance=order %}
<tr>
<td><span class='fas fa-info'></span></td>

View File

@ -107,9 +107,8 @@ src="{% static 'img/blank_image.png' %}"
<td>{% trans "Order Description" %}</td>
<td>{{ order.description }}{% include "clip.html" %}</td>
</tr>
{% include "project_code_data.html" with instance=order %}
{% include "barcode_data.html" with instance=order %}
<tr>
<td><span class='fas fa-info'></span></td>
<td>{% trans "Order Status" %}</td>

View File

@ -112,6 +112,7 @@ src="{% static 'img/blank_image.png' %}"
<td>{% trans "Order Description" %}</td>
<td>{{ order.description }}{% include "clip.html" %}</td>
</tr>
{% include "project_code_data.html" with instance=order %}
{% include "barcode_data.html" with instance=order %}
<tr>
<td><span class='fas fa-info'></span></td>