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:
@ -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:
|
||||
|
@ -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'
|
||||
|
30
InvenTree/order/migrations/0092_auto_20230419_0250.py
Normal file
30
InvenTree/order/migrations/0092_auto_20230419_0250.py
Normal 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'),
|
||||
),
|
||||
]
|
@ -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(
|
||||
|
@ -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',
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
Reference in New Issue
Block a user